From e9c2c73fe78b6c0e5dda9f37ceb13c78f190a63f Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 10 Dec 2024 13:29:58 -0500 Subject: [PATCH 1/3] chore: rename be routes --- apps/marginfi-v2-ui/src/components/common/Stake/stake.tsx | 2 +- .../marginfi-v2-ui/src/pages/api/{birdeye => tokens}/markets.ts | 0 .../src/pages/api/{birdeye/index.ts => tokens/multi.ts} | 0 .../src/pages/api/{birdeye => tokens}/overview.ts | 0 packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename apps/marginfi-v2-ui/src/pages/api/{birdeye => tokens}/markets.ts (100%) rename apps/marginfi-v2-ui/src/pages/api/{birdeye/index.ts => tokens/multi.ts} (100%) rename apps/marginfi-v2-ui/src/pages/api/{birdeye => tokens}/overview.ts (100%) diff --git a/apps/marginfi-v2-ui/src/components/common/Stake/stake.tsx b/apps/marginfi-v2-ui/src/components/common/Stake/stake.tsx index 8c9233601f..db88ef7322 100644 --- a/apps/marginfi-v2-ui/src/components/common/Stake/stake.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Stake/stake.tsx @@ -46,7 +46,7 @@ const Stake = () => { React.useEffect(() => { const fetchIntegrations = async () => { try { - const res = await fetch(`/api/birdeye/markets?token=` + LST_MINT); + const res = await fetch(`/api/tokens/markets?token=` + LST_MINT); if (!res.ok) { return; } diff --git a/apps/marginfi-v2-ui/src/pages/api/birdeye/markets.ts b/apps/marginfi-v2-ui/src/pages/api/tokens/markets.ts similarity index 100% rename from apps/marginfi-v2-ui/src/pages/api/birdeye/markets.ts rename to apps/marginfi-v2-ui/src/pages/api/tokens/markets.ts diff --git a/apps/marginfi-v2-ui/src/pages/api/birdeye/index.ts b/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts similarity index 100% rename from apps/marginfi-v2-ui/src/pages/api/birdeye/index.ts rename to apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts diff --git a/apps/marginfi-v2-ui/src/pages/api/birdeye/overview.ts b/apps/marginfi-v2-ui/src/pages/api/tokens/overview.ts similarity index 100% rename from apps/marginfi-v2-ui/src/pages/api/birdeye/overview.ts rename to apps/marginfi-v2-ui/src/pages/api/tokens/overview.ts diff --git a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts index d7275d77f3..4a0e9b801c 100644 --- a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts +++ b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts @@ -142,7 +142,7 @@ function makeBankInfo(bank: Bank, oraclePrice: OraclePrice, emissionTokenData?: export async function fetchBirdeyePrices(mints: PublicKey[], apiKey?: string): Promise { const mintList = mints.map((mint) => mint.toBase58()).join(","); - const response = await fetch(`/api/birdeye?mintList=${mintList}`, { + const response = await fetch(`/api/tokens/multi?mintList=${mintList}`, { method: "GET", headers: { "Content-Type": "application/json", From 89b088cc9de0e16b9b8367ff8d4ac7e8a581c0be Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 10 Dec 2024 13:30:54 -0500 Subject: [PATCH 2/3] feat: restrict multi price tokens --- .../src/pages/api/tokens/multi.ts | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts b/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts index a10dd0b427..9a0780e786 100644 --- a/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts +++ b/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts @@ -1,4 +1,8 @@ import { NextApiRequest, NextApiResponse } from "next"; +import { BankMetadata, loadBankMetadatas } from "@mrgnlabs/mrgn-common"; +import { Connection, PublicKey } from "@solana/web3.js"; +import { MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { getConfig } from "@mrgnlabs/marginfi-client-v2"; const BIRDEYE_API = "https://public-api.birdeye.so"; @@ -15,9 +19,52 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) controller.abort(); }, 5000); - // Fetch from API and update cache + let bankMetadataCache: { + [address: string]: BankMetadata; + } = {}; + try { - const response = await fetch(`${BIRDEYE_API}/defi/multi_price?list_address=${mintList}`, { + // load bank metadata + bankMetadataCache = await loadBankMetadatas(); + + // init MarginfiClient to get banks with emissions + const connection = new Connection(process.env.PRIVATE_RPC_ENDPOINT_OVERRIDE!); + const bankAddresses = Object.keys(bankMetadataCache).map((address) => new PublicKey(address)); + + const marginfiClient = await MarginfiClient.fetch(getConfig("production"), {} as any, connection, { + preloadedBankAddresses: bankAddresses, + readOnly: true, + }); + + // all supported tokens, banks / emissions mints + const allTokens = [ + ...new Set([ + ...Object.values(bankMetadataCache).map((bank) => bank.tokenAddress), + ...[...marginfiClient.banks.values()] + .map((bank) => bank.emissionsMint.toBase58()) + .filter((mint) => mint !== PublicKey.default.toBase58()), + ]), + ]; + + // filter out restricted tokens + const requestedMints = (mintList as string).split(","); + const supportedMints = requestedMints.filter((mint) => allTokens.includes(mint)); + const restrictedMints = requestedMints.filter((mint) => !allTokens.includes(mint)); + + if (restrictedMints.length > 0) { + console.log("Filtered out restricted tokens:", restrictedMints); + } + + // if no supported tokens, return error + if (supportedMints.length === 0) { + res.status(400).json({ + error: "No supported tokens in request", + }); + return; + } + + // continue with birdeye API call only for supported tokens + const response = await fetch(`${BIRDEYE_API}/defi/multi_price?list_address=${supportedMints.join(",")}`, { headers: { Accept: "application/json", "X-Api-Key": process.env.BIRDEYE_API_KEY || "", @@ -28,7 +75,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) clearTimeout(timeoutId); if (!response.ok) { - throw new Error("Network response was not ok"); + return res.status(response.status).json({ + error: `Birdeye API error: ${response.status} ${response.statusText}`, + }); } const data = await response.json(); @@ -37,6 +86,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) res.status(200).json(data); } catch (error) { console.error("Error:", error); - res.status(500).json({ error: "Error fetching data" }); + res.status(500).json({ + error: error instanceof Error ? error.message : "Error fetching data", + }); } } From 1084e3e6e2261fb4aeb1358fd23d89b9feafd006 Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 10 Dec 2024 15:00:55 -0500 Subject: [PATCH 3/3] chore: fetch bank data directly in multi route --- .../src/pages/api/tokens/multi.ts | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts b/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts index 9a0780e786..0efb73f327 100644 --- a/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts +++ b/apps/marginfi-v2-ui/src/pages/api/tokens/multi.ts @@ -1,8 +1,14 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { BankMetadata, loadBankMetadatas } from "@mrgnlabs/mrgn-common"; +import { + BankMetadata, + loadBankMetadatas, + chunkedGetRawMultipleAccountInfoOrdered, + Wallet, +} from "@mrgnlabs/mrgn-common"; import { Connection, PublicKey } from "@solana/web3.js"; -import { MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; -import { getConfig } from "@mrgnlabs/marginfi-client-v2"; +import { Program, AnchorProvider } from "@coral-xyz/anchor"; +import { Bank, BankRaw, MARGINFI_IDL, MarginfiIdlType, MarginfiProgram } from "@mrgnlabs/marginfi-client-v2"; +import config from "~/config/marginfi"; const BIRDEYE_API = "https://public-api.birdeye.so"; @@ -27,21 +33,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // load bank metadata bankMetadataCache = await loadBankMetadatas(); - // init MarginfiClient to get banks with emissions - const connection = new Connection(process.env.PRIVATE_RPC_ENDPOINT_OVERRIDE!); - const bankAddresses = Object.keys(bankMetadataCache).map((address) => new PublicKey(address)); + // fetch mfi banks to get emissions mints + const connection = new Connection(process.env.PRIVATE_RPC_ENDPOINT_OVERRIDE || ""); + const idl = { ...MARGINFI_IDL, address: config.mfiConfig.programId.toBase58() } as unknown as MarginfiIdlType; - const marginfiClient = await MarginfiClient.fetch(getConfig("production"), {} as any, connection, { - preloadedBankAddresses: bankAddresses, - readOnly: true, + const provider = new AnchorProvider(connection, {} as Wallet, { + ...AnchorProvider.defaultOptions(), + commitment: connection.commitment ?? AnchorProvider.defaultOptions().commitment, }); + const program = new Program(idl, provider) as any as MarginfiProgram; + + const bankAddresses = Object.keys(bankMetadataCache); + + const banksAis = await chunkedGetRawMultipleAccountInfoOrdered(connection, bankAddresses); + let banksMap: { address: PublicKey; data: BankRaw }[] = banksAis.map((account, index) => ({ + address: new PublicKey(bankAddresses[index]), + data: Bank.decodeBankRaw(account.data, program.idl), + })); // all supported tokens, banks / emissions mints const allTokens = [ ...new Set([ ...Object.values(bankMetadataCache).map((bank) => bank.tokenAddress), - ...[...marginfiClient.banks.values()] - .map((bank) => bank.emissionsMint.toBase58()) + ...banksMap + .map((bank) => bank.data.emissionsMint.toBase58()) .filter((mint) => mint !== PublicKey.default.toBase58()), ]), ];