diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/CreatePoolDialog.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/CreatePoolDialog.tsx index ebea8551c8..5c543e8c0a 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/CreatePoolDialog.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/CreatePoolDialog.tsx @@ -11,7 +11,6 @@ import { useIsMobile } from "~/hooks/use-is-mobile"; import { Dialog, DialogContent, DialogTrigger } from "~/components/ui/dialog"; import { Button } from "~/components/ui/button"; -import type { TokenData } from "~/types"; import { PoolData, PoolMintData, CreatePoolState } from "./types"; import { @@ -23,6 +22,7 @@ import { CreatePoolConfigure, CreatePoolReview, } from "./components"; +import { TokenData } from "~/types"; type CreatePoolDialogProps = { trigger?: React.ReactNode; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-loading/create-pool-loading.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-loading/create-pool-loading.tsx index 9436899382..862c7783e8 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-loading/create-pool-loading.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-loading/create-pool-loading.tsx @@ -18,7 +18,7 @@ import { getConfig, instructions, } from "@mrgnlabs/marginfi-client-v2"; -import { cn, getBearerToken, getFeeAccount, createReferalTokenAccount } from "@mrgnlabs/mrgn-utils"; +import { cn, getBearerToken, getFeeAccount, createReferalTokenAccountIxs } from "@mrgnlabs/mrgn-utils"; import { addTransactionMetadata, SolanaTransaction, TransactionType } from "@mrgnlabs/mrgn-common"; import { Button } from "~/components/ui/button"; @@ -54,11 +54,12 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: const steps = React.useMemo( () => [ - { label: "Step 1", description: "Setting up a switchboard oracle" }, - { label: "Step 2", description: "Generating transactions" }, - { label: "Step 3", description: "Indexing pool" }, - { label: "Step 4", description: "Executing transactions" }, - { label: "Step 5", description: "Finalizing pool" }, + { label: "Step 1", description: "Setting up oracles" }, + { label: "Step 2", description: "Saving token image data" }, + { label: "Step 3", description: "Generating transactions" }, + { label: "Step 4", description: "Indexing pool" }, + { label: "Step 5", description: "Executing transactions" }, + { label: "Step 6", description: "Finalizing pool" }, ], [] ); @@ -86,15 +87,20 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: [connection, wallet] ); - const savePermissionlessPool = async (poolObject: { group: string; asset: string; quote: string; lut: string }) => { + const savePermissionlessPool = async (poolObject: { + group: string; + asset: string; + quote: string; + lut: string; + admin: string; + }) => { try { const formattedPoolObject = { base_bank: poolObject.asset, - created_by: "mfi1dtjy2mJ9J21UoaQ5dsRnbcg4MBU1CTacVyBp1HF", // TODO: update this - featured: true, + created_by: poolObject.admin, group: poolObject.group, lookup_tables: [poolObject.lut], - quote_banks: [poolObject.quote], + quote_bank: poolObject.quote, }; const response = await fetch("/api/pool/create", { @@ -107,7 +113,6 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: if (response.ok) { const data = await response.json(); - console.log("Pool added:", data); return data; } else { console.error("Failed to add pool"); @@ -123,7 +128,8 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: const verifiedPoolData = verifyPoolData(poolData); if (!verifiedPoolData) return; - const { tokenMint, quoteMint, tokenSymbol, quoteSymbol, tokenConfig, quoteConfig } = verifiedPoolData; + const { tokenMint, quoteMint, tokenSymbol, quoteSymbol, tokenConfig, quoteConfig, tokenIcon, quoteIcon } = + verifiedPoolData; setStatus("loading"); @@ -142,10 +148,9 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: let updatedTokenOracleConfig = { ...tokenConfig.oracleConfig }; let updatedQuoteOracleConfig = { ...quoteConfig.oracleConfig }; - if (updatedTokenOracleConfig?.keys?.length === 0) { + if (!updatedTokenOracleConfig?.keys || updatedTokenOracleConfig?.keys?.length === 0) { const oracleCreationToken = await initializeOracle(tokenMint, tokenSymbol); if (!oracleCreationToken) throw new Error("Oracle creation failed"); - updatedTokenOracleConfig = { setup: OracleSetup.SwitchboardPull, keys: [oracleCreationToken.feedPubkey], @@ -153,10 +158,9 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: pullFeedIx.push(oracleCreationToken); } - if (updatedQuoteOracleConfig?.keys?.length === 0) { + if (!updatedQuoteOracleConfig?.keys || updatedQuoteOracleConfig?.keys?.length === 0) { const oracleCreationQuote = await initializeOracle(quoteMint, quoteSymbol); if (!oracleCreationQuote) throw new Error("Oracle creation failed"); - updatedQuoteOracleConfig = { setup: OracleSetup.SwitchboardPull, keys: [oracleCreationQuote.feedPubkey], @@ -166,6 +170,23 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: setActiveStep(1); + // upload images + const uploadImageResponse = await uploadImage(tokenIcon, tokenMint.toBase58()); + if (!uploadImageResponse) { + setStatus("error"); + console.error("Failed to upload image"); + return; + } + + const uploadQuoteImageResponse = await uploadImage(quoteIcon, quoteMint.toBase58()); + if (!uploadQuoteImageResponse) { + setStatus("error"); + console.error("Failed to upload image"); + return; + } + + setActiveStep(2); + // create group ix const groupIxWrapped = await client.makeCreateMarginfiGroupIx(seeds.marginfiGroupSeed.publicKey); // create bank ix wrapper (quote) @@ -194,7 +215,6 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: ); // add oracle to banks - if ( !updatedTokenOracleConfig.setup || !updatedTokenOracleConfig.keys || @@ -204,22 +224,22 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: throw new Error("Oracle setup or keys not found"); } - const addOracleToBanksIxs = []; - addOracleToBanksIxs.push( - await addOracleToBanksIx( - client.program, - seeds.stableBankSeed.publicKey, - updatedQuoteOracleConfig.keys[0], - updatedQuoteOracleConfig.setup - ) + const addOracleToQuoteBankIx = await addOracleToBanksIx( + client.program, + seeds.stableBankSeed.publicKey, + updatedQuoteOracleConfig.keys[0], + updatedQuoteOracleConfig.setup, + seeds.marginfiGroupSeed.publicKey, + wallet.publicKey ); - addOracleToBanksIxs.push( - await addOracleToBanksIx( - client.program, - seeds.tokenBankSeed.publicKey, - updatedTokenOracleConfig.keys[0], - updatedTokenOracleConfig.setup - ) + + const addOracleToTokenBankIx = await addOracleToBanksIx( + client.program, + seeds.tokenBankSeed.publicKey, + updatedTokenOracleConfig.keys[0], + updatedTokenOracleConfig.setup, + seeds.marginfiGroupSeed.publicKey, + wallet.publicKey ); // create lut ix @@ -237,8 +257,7 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: const feeAccount = getFeeAccount(tokenMint); let referralTokenAccountIxs: TransactionInstruction[] = []; if (feeAccount) { - referralTokenAccountIxs = (await createReferalTokenAccount(connection, wallet.publicKey, tokenMint)) - .instructions; + referralTokenAccountIxs = await createReferalTokenAccountIxs(connection, wallet.publicKey, tokenMint); } // transactions @@ -247,7 +266,7 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: value: { blockhash }, } = await connection.getLatestBlockhashAndContext(); - // bundle tip & create oracle transaction + // create oracle transaction pullFeedIx.forEach((ix) => { transactions.push(createTransaction([ix.pullFeedIx], wallet.publicKey, [ix.feedSeed], blockhash)); }); @@ -262,12 +281,12 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: ) ); - // create quote bank & referal token account transaction + // create quote bank & referal token account transaction ...referralTokenAccountIxs transactions.push( createTransaction( - [...quoteBankIxWrapper.instructions, ...referralTokenAccountIxs], + [...quoteBankIxWrapper.instructions, ...addOracleToQuoteBankIx.instructions], wallet.publicKey, - [seeds.stableBankSeed, ...quoteBankIxWrapper.keys], + [seeds.stableBankSeed, ...quoteBankIxWrapper.keys, ...addOracleToQuoteBankIx.keys], blockhash ) ); @@ -275,14 +294,14 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: // create token bank transaction transactions.push( createTransaction( - [...tokenBankIxWrapper.instructions], + [...tokenBankIxWrapper.instructions, ...addOracleToTokenBankIx.instructions], wallet.publicKey, [seeds.tokenBankSeed, ...tokenBankIxWrapper.keys], blockhash ) ); - setActiveStep(2); + setActiveStep(3); try { const response = await savePermissionlessPool({ @@ -290,6 +309,7 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: asset: seeds.tokenBankSeed.publicKey.toBase58(), quote: seeds.stableBankSeed.publicKey.toBase58(), lut: lutAddress.toBase58(), + admin: wallet.publicKey.toBase58(), }); console.log("Pool saved:", response); } catch (error) { @@ -298,7 +318,7 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: throw error; } - setActiveStep(3); + setActiveStep(4); // transaction execution const sigs = await client.processTransactions(transactions, { @@ -318,14 +338,14 @@ export const CreatePoolLoading = ({ poolData, setPoolData, setCreatePoolState }: console.error("Failed to create permissionless pool"); console.error(error); } - }, [connection, createOracleIx, initializeClient, poolData, setCreatePoolState, setPoolData, wallet]); + }, [connection, initializeClient, initializeOracle, poolData, setCreatePoolState, setPoolData, wallet.publicKey]); React.useEffect(() => { if (!initialized.current) { initialized.current = true; createPermissionlessBankBundle(); } - }, []); + }, [createPermissionlessBankBundle]); return ( <> @@ -398,12 +418,22 @@ type VerifiedPoolData = { quoteMint: PublicKey; tokenSymbol: string; quoteSymbol: string; + tokenIcon: string; + quoteIcon: string; tokenConfig: { bankConfig: BankConfigOpt; oracleConfig: OracleConfigOpt | null }; quoteConfig: { bankConfig: BankConfigOpt; oracleConfig: OracleConfigOpt | null }; }; const verifyPoolData = (poolData: PoolData | null): VerifiedPoolData | null => { - if (!poolData || !poolData.token || !poolData.quoteToken || !poolData.tokenConfig || !poolData.quoteTokenConfig) { + if ( + !poolData || + !poolData.token || + !poolData.quoteToken || + !poolData.tokenConfig || + !poolData.quoteTokenConfig || + !poolData.token.icon || + !poolData.quoteToken.icon + ) { return null; } @@ -414,5 +444,46 @@ const verifyPoolData = (poolData: PoolData | null): VerifiedPoolData | null => { quoteSymbol: poolData.quoteToken.symbol, tokenConfig: poolData.tokenConfig, quoteConfig: poolData.quoteTokenConfig, + tokenIcon: poolData.token.icon, + quoteIcon: poolData.quoteToken.icon, }; }; + +const uploadImage = async (fileUrl: string, mint: string): Promise => { + try { + // Fetch the file from the URL + const fileResponse = await fetch(fileUrl); + if (!fileResponse.ok) throw new Error("Failed to fetch file from URL"); + + // Convert the response to a Blob + const fileBlob = await fileResponse.blob(); + const fileParts = fileUrl.split("/"); + const fileName = fileParts[fileParts.length - 1]; + const extension = fileName.split(".").pop() || ""; + const filename = `${mint}.${extension}`; + + // Prepare the upload request + const response = await fetch(`/api/pool/addImage?filename=${filename}`, { + method: "POST", + }); + + if (response.status === 409) return true; + + const { url, fields } = await response.json(); + const formData = new FormData(); + Object.entries({ ...fields, file: fileBlob }).forEach(([key, value]) => { + formData.append(key, value as string | Blob); + }); + + // Upload the file + const upload = await fetch(url, { + method: "POST", + body: formData, + }); + + return upload.ok; + } catch (error) { + console.error("Error uploading image:", error); + return false; + } +}; diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-quote.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-quote.tsx index f5794b8758..849fc5c5bc 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-quote.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/CreatePoolDialog/components/create-pool-quote.tsx @@ -12,6 +12,8 @@ import { Input } from "~/components/ui/input"; import { Skeleton } from "~/components/ui/skeleton"; import { useTradeStoreV2 } from "~/store"; import { ArenaPoolSummary } from "~/types/trade-store.types"; +import { TOKEN_ICON_BASE_URL } from "~/config/trade"; + import type { PoolMintData } from "../types"; type CreatePoolMintProps = { @@ -152,7 +154,7 @@ export const CreatePoolQuote = ({ tokenData, isSearchingToken, setIsOpen, fetchT
{/* eslint-disable-next-line @next/next/no-img-element */}
diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/PoolCard.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/PoolCard.tsx index b12d5c27e3..76a95a3cfe 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/PoolCard.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/PoolCard.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import Image from "next/image"; import { percentFormatter, shortenAddress, dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common"; -import { cn, useIsMobile } from "@mrgnlabs/mrgn-utils"; +import { cn } from "@mrgnlabs/mrgn-utils"; import { minidenticon } from "minidenticons"; import { Button } from "~/components/ui/button"; @@ -21,32 +21,35 @@ type PoolCardProps = { }; export const PoolCard = ({ poolData }: PoolCardProps) => { - const [tokenDataByMint, groupsByGroupPk] = useTradeStoreV2((state) => [state.tokenDataByMint, state.groupsByGroupPk]); + const [tokenVolumeDataByMint, groupsByGroupPk] = useTradeStoreV2((state) => [ + state.tokenVolumeDataByMint, + state.groupsByGroupPk, + ]); - const { tokenData, quoteTokenData } = React.useMemo(() => { - const tokenData = tokenDataByMint[poolData.tokenSummary.mint.toBase58()]; - const quoteTokenData = tokenDataByMint[poolData.quoteSummary.mint.toBase58()]; - return { tokenData, quoteTokenData }; - }, [poolData, tokenDataByMint]); + const { tokenVolumeData, quoteTokenVolumeData } = React.useMemo(() => { + const tokenVolumeData = tokenVolumeDataByMint[poolData.tokenSummary.mint.toBase58()]; + const quoteTokenVolumeData = tokenVolumeDataByMint[poolData.quoteSummary.mint.toBase58()]; + return { tokenVolumeData, quoteTokenVolumeData }; + }, [poolData, tokenVolumeDataByMint]); const isStableQuote = React.useMemo(() => { - return 0.99 < quoteTokenData?.price && quoteTokenData?.price < 1.01; - }, [quoteTokenData]); + return 0.99 < quoteTokenVolumeData?.price && quoteTokenVolumeData?.price < 1.01; + }, [quoteTokenVolumeData]); const [showUSDPrice, setShowUSDPrice] = React.useState(false); const tokenPrice = React.useMemo(() => { if (showUSDPrice) { - return tokenData?.price; + return tokenVolumeData?.price; } - return tokenData?.price / quoteTokenData?.price; - }, [showUSDPrice, tokenData, quoteTokenData]); + return tokenVolumeData?.price / quoteTokenVolumeData?.price; + }, [showUSDPrice, tokenVolumeData, quoteTokenVolumeData]); const tokenPriceChange = React.useMemo(() => { if (showUSDPrice) { - return tokenData?.priceChange24h; + return tokenVolumeData?.priceChange24h; } - return tokenData?.priceChange24h - quoteTokenData?.priceChange24h; - }, [showUSDPrice, tokenData, quoteTokenData]); + return tokenVolumeData?.priceChange24h - quoteTokenVolumeData?.priceChange24h; + }, [showUSDPrice, tokenVolumeData, quoteTokenVolumeData]); const fundingRate = React.useMemo(() => { const fundingRateShort = @@ -80,8 +83,7 @@ export const PoolCard = ({ poolData }: PoolCardProps) => { href={`/trade/${poolData.groupPk.toBase58()}`} className="flex items-center gap-2 justify-between cursor-pointer" > - {/* eslint-disable-next-line @next/next/no-img-element */} - { - {tokenData && ( + {tokenVolumeData && (
Price
{ {dynamicNumeralFormatter(tokenPrice, { ignoreMinDisplay: true, })}{" "} - {showUSDPrice ? "USD" : quoteTokenData?.symbol} + {showUSDPrice ? "USD" : poolData.tokenSummary.tokenSymbol} {!isStableQuote && } {tokenPriceChange && ( 0 ? "text-mrgn-success" : "text-mrgn-error")}> @@ -195,18 +197,18 @@ export const PoolCard = ({ poolData }: PoolCardProps) => {
24hr vol
$ - {dynamicNumeralFormatter(tokenData.volume24h, { + {dynamicNumeralFormatter(tokenVolumeData.volume24h, { maxDisplay: 1000, })} - {tokenData.volumeChange24h && ( + {tokenVolumeData.volumeChange24h && ( 0 ? "text-mrgn-success" : "text-mrgn-error" + tokenVolumeData.volumeChange24h > 0 ? "text-mrgn-success" : "text-mrgn-error" )} > - {tokenData.volumeChange24h > 0 && "+"} - {percentFormatter.format(tokenData.volumeChange24h / 100)} + {tokenVolumeData.volumeChange24h > 0 && "+"} + {percentFormatter.format(tokenVolumeData.volumeChange24h / 100)} )}
diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/PoolListItem.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/PoolListItem.tsx index c09f4b9458..93d6430c12 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/PoolListItem.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/PoolListItem.tsx @@ -19,13 +19,16 @@ type PoolListItemProps = { }; export const PoolListItem = ({ poolData, last }: PoolListItemProps) => { - const [tokenDataByMint, groupsByGroupPk] = useTradeStoreV2((state) => [state.tokenDataByMint, state.groupsByGroupPk]); + const [tokenVolumeDataByMint, groupsByGroupPk] = useTradeStoreV2((state) => [ + state.tokenVolumeDataByMint, + state.groupsByGroupPk, + ]); - const { tokenData, quoteTokenData } = React.useMemo(() => { - const tokenData = tokenDataByMint[poolData.tokenSummary.mint.toBase58()]; - const quoteTokenData = tokenDataByMint[poolData.quoteSummary.mint.toBase58()]; - return { tokenData, quoteTokenData }; - }, [poolData, tokenDataByMint]); + const { tokenVolumeData, quoteTokenVolumeData } = React.useMemo(() => { + const tokenVolumeData = tokenVolumeDataByMint[poolData.tokenSummary.mint.toBase58()]; + const quoteTokenVolumeData = tokenVolumeDataByMint[poolData.quoteSummary.mint.toBase58()]; + return { tokenVolumeData, quoteTokenVolumeData }; + }, [poolData, tokenVolumeDataByMint]); const fundingRate = React.useMemo(() => { const fundingRateShort = @@ -53,8 +56,7 @@ export const PoolListItem = ({ poolData, last }: PoolListItemProps) => { return (
- {/* eslint-disable-next-line @next/next/no-img-element */} - {poolData.tokenSummary.tokenSymbol} { {poolData.tokenSummary.tokenSymbol}/{poolData.quoteSummary.tokenSymbol}
- {tokenData && ( + {tokenVolumeData && ( <>
$ - {dynamicNumeralFormatter(tokenData.price / quoteTokenData.price, { + {dynamicNumeralFormatter(tokenVolumeData.price / quoteTokenVolumeData.price, { ignoreMinDisplay: true, })}
- 0 ? "text-mrgn-success" : "text-mrgn-error")}> - {tokenData.priceChange24h > 0 && "+"} - {percentFormatter.format(tokenData.priceChange24h / 100)} + 0 ? "text-mrgn-success" : "text-mrgn-error")} + > + {tokenVolumeData.priceChange24h > 0 && "+"} + {percentFormatter.format(tokenVolumeData.priceChange24h / 100)}
$ - {dynamicNumeralFormatter(tokenData.volume24h, { + {dynamicNumeralFormatter(tokenVolumeData.volume24h, { maxDisplay: 1000, })} - {tokenData.volumeChange24h && ( + {tokenVolumeData.volumeChange24h && ( 0 ? "text-mrgn-success" : "text-mrgn-error")} + className={cn( + "text-xs ml-2", + tokenVolumeData.volumeChange24h > 0 ? "text-mrgn-success" : "text-mrgn-error" + )} > - {tokenData.volumeChange24h > 0 && "+"} - {percentFormatter.format(tokenData.volumeChange24h / 100)} + {tokenVolumeData.volumeChange24h > 0 && "+"} + {percentFormatter.format(tokenVolumeData.volumeChange24h / 100)} )}
diff --git a/apps/marginfi-v2-trading/src/components/common/Pool/PoolSearch/components/PoolSearchDefault.tsx b/apps/marginfi-v2-trading/src/components/common/Pool/PoolSearch/components/PoolSearchDefault.tsx index 8f51b5fb6e..901e83fc30 100644 --- a/apps/marginfi-v2-trading/src/components/common/Pool/PoolSearch/components/PoolSearchDefault.tsx +++ b/apps/marginfi-v2-trading/src/components/common/Pool/PoolSearch/components/PoolSearchDefault.tsx @@ -1,19 +1,15 @@ import React from "react"; import type { FuseResult } from "fuse.js"; +import Image from "next/image"; +import { IconCommand, IconX } from "@tabler/icons-react"; -import { - tokenPriceFormatter, - numeralFormatter, - percentFormatter, - dynamicNumeralFormatter, -} from "@mrgnlabs/mrgn-common"; +import { percentFormatter, dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common"; import { cn } from "@mrgnlabs/mrgn-utils"; -import { IconCommand, IconX } from "@tabler/icons-react"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "~/components/ui/command"; import { Button } from "~/components/ui/button"; -import { ArenaPoolSummary, ArenaPoolV2 } from "~/types/trade-store.types"; +import { ArenaPoolSummary } from "~/types"; import { useTradeStoreV2 } from "~/store"; type PoolSearchDefaultProps = { @@ -133,14 +129,14 @@ export const PoolSearchDefault = ({ >
- {tokenBank.meta.tokenSymbol} -
- {tokenBank.meta.tokenSymbol} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {extendedPool.tokenBank.meta.tokenSymbol} - {/* eslint-disable-next-line @next/next/no-img-element */} - {extendedPool.quoteBank.meta.tokenSymbol}
- {/* eslint-disable-next-line @next/next/no-img-element */} - {extendedPool.tokenBank.meta.tokenSymbol}
- {/* eslint-disable-next-line @next/next/no-img-element */} - {extendedPool.quoteBank.meta.tokenSymbol} { className="flex items-center gap-3 transition-colors shrink-0" >
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenSymbol} - {/* eslint-disable-next-line @next/next/no-img-element */} - { className="flex items-center gap-3 transition-colors shrink-0" >
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenSymbol} - {/* eslint-disable-next-line @next/next/no-img-element */} - { href={`/trade/${arenaPool.groupPk.toBase58()}`} className="flex items-center gap-3 font-medium text-muted-foreground md:gap-4" > - {/* eslint-disable-next-line @next/next/no-img-element */} - {arenaPool.tokenBank.meta.tokenSymbol} { height={24} className="bg-background border rounded-full h-[24px] w-[24px] object-cover" /> - {/* eslint-disable-next-line @next/next/no-img-element */} - {arenaPool.quoteBank.meta.tokenSymbol} {arenaPool.tokenBank && ( - // eslint-disable-next-line @next/next/no-img-element - {(arenaPool.tokenBank?.meta.tokenSymbol 0.05 ? "text-error" : Number(actionTransaction.actionQuote.priceImpactPct) > 0.01 - ? "text-alert" - : "text-success", + ? "text-alert" + : "text-success", "text-right" )} > diff --git a/apps/marginfi-v2-trading/src/components/common/TokenCombobox/TokenCombobox.tsx b/apps/marginfi-v2-trading/src/components/common/TokenCombobox/TokenCombobox.tsx index 1fbff51861..6dfb815475 100644 --- a/apps/marginfi-v2-trading/src/components/common/TokenCombobox/TokenCombobox.tsx +++ b/apps/marginfi-v2-trading/src/components/common/TokenCombobox/TokenCombobox.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Image from "next/image"; import { IconChevronDown } from "@tabler/icons-react"; import { percentFormatter, tokenPriceFormatter } from "@mrgnlabs/mrgn-common"; @@ -6,10 +7,9 @@ import { Desktop, Mobile, cn } from "@mrgnlabs/mrgn-utils"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "~/components/ui/command"; import { Drawer, DrawerContent, DrawerTrigger } from "~/components/ui/drawer"; - +import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; import { ArenaPoolV2Extended } from "~/types/trade-store.types"; import { useExtendedPools } from "~/hooks/useExtendedPools"; -import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; type TokenComboboxProps = { selected: ArenaPoolV2Extended | null; @@ -68,16 +68,14 @@ export const TokenCombobox = ({ selected, setSelected, children }: TokenCombobox }} >
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenName} - {/* eslint-disable-next-line @next/next/no-img-element */} -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenName} - {/* eslint-disable-next-line @next/next/no-img-element */} - { return (
- {/* eslint-disable-next-line @next/next/no-img-element */} - {selected?.tokenBank.meta.tokenSymbol { className="group bg-background border rounded-xl absolute -top-5 left-3.5 px-2 py-1.5 flex items-center gap-2 transition-colors hover:bg-accent" >
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenSymbol} - {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.quoteBank.meta.tokenSymbol}
- {/* eslint-disable-next-line @next/next/no-img-element */} - {bank.meta.tokenSymbol} { className="group bg-background border rounded-xl absolute -top-5 left-3.5 px-2 py-1.5 flex items-center gap-2 transition-colors hover:bg-accent" >
- {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenSymbol} - {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.quoteBank.meta.tokenSymbol}
- {/* eslint-disable-next-line @next/next/no-img-element */} - {bank.meta.tokenSymbol} ) : (
- minidenticon { >
- {extendedPool.tokenBank.meta.tokenSymbol} - {extendedPool.quoteBank.meta.tokenSymbol} className="group bg-background border rounded-xl absolute -top-5 left-3.5 px-2 py-1.5 flex items-center gap-2 transition-colors hover:bg-accent" >
- {extendedPool.tokenBank.meta.tokenSymbol} - {extendedPool.quoteBank.meta.tokenSymbol}
- {bank.meta.tokenSymbol} + {bank.meta.tokenSymbol} {bank.meta.tokenSymbol}
diff --git a/apps/marginfi-v2-trading/src/components/common/admin/admin-pool-detail/pool-detail-header/pool-detail-header.tsx b/apps/marginfi-v2-trading/src/components/common/admin/admin-pool-detail/pool-detail-header/pool-detail-header.tsx index 378a4fa65b..43b9f5ca91 100644 --- a/apps/marginfi-v2-trading/src/components/common/admin/admin-pool-detail/pool-detail-header/pool-detail-header.tsx +++ b/apps/marginfi-v2-trading/src/components/common/admin/admin-pool-detail/pool-detail-header/pool-detail-header.tsx @@ -1,6 +1,7 @@ import React from "react"; import { IconExternalLink } from "@tabler/icons-react"; import Link from "next/link"; +import Image from "next/image"; import { percentFormatter, numeralFormatter, tokenPriceFormatter, shortenAddress } from "@mrgnlabs/mrgn-common"; import { cn } from "@mrgnlabs/mrgn-utils"; @@ -36,7 +37,7 @@ export const AdminPoolDetailHeader = ({ activePool }: AdminPoolDetailHeaderProps
- {extendedPool.tokenBank.meta.tokenSymbol}
- {quoteBank.meta.tokenSymbol} res.json())) as any as TradeGroupsCache; - - const bankAddresses = Object.values(groupsCache).flat(); - - 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), - })); - - const groupSummaries = Object.entries(groupsCache).reduce( - (acc, [groupPk, bankAddresses]) => { - const quoteBankAddress = bankAddresses[0]; - const tokenBankAddress = bankAddresses[1]; - - const tokenBank = banksMap.find((b) => b.address.toBase58() === tokenBankAddress); - const quoteBank = banksMap.find((b) => b.address.toBase58() === quoteBankAddress); - - if (!tokenBank || !quoteBank) { - throw new Error(`Bank not found for group ${groupPk}`); - } - - const tokenBankSummary = formatBankSummary(tokenBank.data, tokenBankAddress); - const quoteBankSummary = formatBankSummary(quoteBank.data, quoteBankAddress); - - acc[groupPk] = { - tokenBankSummary, - quoteBankSummary, - }; - - return acc; - }, - {} as Record< - string, - { - tokenBankSummary: BankSummary; - quoteBankSummary: BankSummary; - } - > - ); - - res.setHeader("Cache-Control", `s-maxage=${S_MAXAGE_TIME}, stale-while-revalidate=${STALE_WHILE_REVALIDATE_TIME}`); - return res.status(200).json(groupSummaries); - } catch (error) { - console.error("Error:", error); - return res.status(500).json({ error: "Error fetching data" }); - } -} - -interface BankSummary { - bankPk: string; - mint: string; - totalDeposits: number; - totalBorrows: number; - availableLiquidity: number; -} - -const formatBankSummary = (bank: BankRaw, bankPk: string) => { - const totalAssetShares = wrappedI80F48toBigNumber(bank.totalAssetShares); - const totalLiabilityShares = wrappedI80F48toBigNumber(bank.totalLiabilityShares); - const summary: BankSummary = { - bankPk, - mint: bank.mint.toBase58(), - totalDeposits: nativeToUi(totalAssetShares, bank.mintDecimals), - totalBorrows: nativeToUi(totalLiabilityShares, bank.mintDecimals), - availableLiquidity: nativeToUi(totalAssetShares.minus(totalLiabilityShares), bank.mintDecimals), - }; - return summary; -}; diff --git a/apps/marginfi-v2-trading/src/pages/api/token/arenaTokens.ts b/apps/marginfi-v2-trading/src/pages/api/token/arenaTokens.ts index 4cc4863165..cb421809ba 100644 --- a/apps/marginfi-v2-trading/src/pages/api/token/arenaTokens.ts +++ b/apps/marginfi-v2-trading/src/pages/api/token/arenaTokens.ts @@ -1,8 +1,6 @@ -import { BankMetadata, loadBankMetadatas } from "@mrgnlabs/mrgn-common"; import { NextApiRequest, NextApiResponse } from "next"; -import { BANK_METADATA_MAP } from "~/config/trade"; -import type { TokenData } from "~/types"; +import { TokenVolumeData } from "~/types"; import { PoolListApiResponse } from "~/types/api.types"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -26,7 +24,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // Fetch token volume let tokenVolumePromisses: Promise[] = []; - let tokenMetadataPromisses: Promise[] = []; tokens.forEach((tokenBatch) => { const addresses = tokenBatch.join(","); @@ -48,38 +45,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) body: JSON.stringify(bodyData), }) ); - - //A list of addresses in string separated by commas (,) - - tokenMetadataPromisses.push( - fetch(`https://public-api.birdeye.so/defi/v3/token/meta-data/multiple?list_address=${addresses}`, { - method: "GET", - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-Api-Key": process.env.BIRDEYE_API_KEY || "", - }, - }) - ); }); // Fetch from API and update cache try { const volumeResponses = await Promise.all(tokenVolumePromisses); - const metadataResponses = await Promise.all(tokenMetadataPromisses); - if (!volumeResponses.every((response) => response.ok) || !metadataResponses.every((response) => response.ok)) { + if (!volumeResponses.every((response) => response.ok)) { throw new Error("Network response was not ok"); } - const volumeDataRaw = (await Promise.all( + const volumeDataRaw: TokenVolumeDataRawResponse[] = await Promise.all( volumeResponses.map((response) => response.json()) - )) as TokenVolumeDataRaw[]; - const metadataDataRaw = (await Promise.all( - metadataResponses.map((response) => response.json()) - )) as TokenMetaDataRaw[]; + ); - if (!volumeDataRaw || !metadataDataRaw) { + if (!volumeDataRaw) { res.status(404).json({ error: "Token data not found" }); return; } @@ -92,29 +72,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }), {} ), - }.data as TokenVolumeData; + }.data as TokenVolumeDataResponse; - const metadataData = { - data: metadataDataRaw.reduce( - (acc, curr) => ({ - ...acc, - ...curr.data, - }), - {} - ), - }.data as TokenMetaData; - - const metadataDataList = Object.values(metadataData); - - const tokenDatas: TokenData[] = metadataDataList.map((data) => { - const volume = volumeData[data.address]; + const volumeDataList = Object.values(volumeData); + const tokenDatas: TokenVolumeData[] = volumeDataList.map((volume, index) => { return { - address: data.address, - name: data.name, - symbol: data.symbol, - imageUrl: data.logo_uri, - decimals: data.decimals, + mint: allTokens[index], price: volume.price, priceChange24h: volume.priceChangePercent, volume24h: volume.volumeUSD, @@ -134,7 +98,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } } -type TokenVolumeData = { +type TokenVolumeDataResponse = { [address: string]: { price: number; updateUnixTime: number; @@ -145,31 +109,10 @@ type TokenVolumeData = { }; }; -type TokenVolumeDataRaw = { +type TokenVolumeDataRawResponse = { data: TokenVolumeData; }; -type TokenMetaData = { - [address: string]: { - address: string; - symbol: string; - name: string; - decimals: number; - extensions: { - coingecko_id: string; - website: string; - twitter: string; - discord: string; - medium: string; - }; - logo_uri: string; - }; -}; - -type TokenMetaDataRaw = { - data: TokenMetaData; -}; - function extractHost(referer: string | undefined): string | undefined { if (!referer) { return undefined; diff --git a/apps/marginfi-v2-trading/src/pages/api/token/overview.ts b/apps/marginfi-v2-trading/src/pages/api/token/overview.ts index 18a8f4c628..d60a4b1cf8 100644 --- a/apps/marginfi-v2-trading/src/pages/api/token/overview.ts +++ b/apps/marginfi-v2-trading/src/pages/api/token/overview.ts @@ -1,6 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; - -import type { TokenData } from "~/types"; +import { TokenData } from "~/types"; function cdnImageUrl(url: string) { return `https://img.fotofolio.xyz/?url=${encodeURIComponent(url)}`; @@ -26,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!response.ok) { throw new Error("Network response was not ok"); } - const { data } = await response.json(); + const { data } = (await response.json()) as ApiResponse; if (!data) { res.status(404).json({ error: "Token data not found" }); @@ -56,3 +55,220 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) res.status(500).json({ error: "Error fetching data" }); } } + +type ExtensionsResponse = { + coingeckoId: string; + serumV3Usdc: string; + serumV3Usdt: string; + website: string; + telegram: string | null; + twitter: string; + description: string; + discord: string; + medium: string; +}; + +type TokenDataResponse = { + address: string; + decimals: number; + symbol: string; + name: string; + marketCap: number; + fdv: number; + extensions: ExtensionsResponse; + logoURI: string; + liquidity: number; + lastTradeUnixTime: number; + lastTradeHumanTime: string; + price: number; + history30mPrice: number; + priceChange30mPercent: number; + history1hPrice: number; + priceChange1hPercent: number; + history2hPrice: number; + priceChange2hPercent: number; + history4hPrice: number; + priceChange4hPercent: number; + history6hPrice: number; + priceChange6hPercent: number; + history8hPrice: number; + priceChange8hPercent: number; + history12hPrice: number; + priceChange12hPercent: number; + history24hPrice: number; + priceChange24hPercent: number; + uniqueWallet30m: number; + uniqueWalletHistory30m: number; + uniqueWallet30mChangePercent: number; + uniqueWallet1h: number; + uniqueWalletHistory1h: number; + uniqueWallet1hChangePercent: number; + uniqueWallet2h: number; + uniqueWalletHistory2h: number; + uniqueWallet2hChangePercent: number; + uniqueWallet4h: number; + uniqueWalletHistory4h: number; + uniqueWallet4hChangePercent: number; + uniqueWallet8h: number; + uniqueWalletHistory8h: number; + uniqueWallet8hChangePercent: number; + uniqueWallet24h: number; + uniqueWalletHistory24h: number; + uniqueWallet24hChangePercent: number; + supply: number; + totalSupply: number; + mc: number; + circulatingSupply: number; + realMc: number; + holder: number; + trade30m: number; + tradeHistory30m: number; + trade30mChangePercent: number; + sell30m: number; + sellHistory30m: number; + sell30mChangePercent: number; + buy30m: number; + buyHistory30m: number; + buy30mChangePercent: number; + v30m: number; + v30mUSD: number; + vHistory30m: number; + vHistory30mUSD: number; + v30mChangePercent: number; + vBuy30m: number; + vBuy30mUSD: number; + vBuyHistory30m: number; + vBuyHistory30mUSD: number; + vBuy30mChangePercent: number; + vSell30m: number; + vSell30mUSD: number; + vSellHistory30m: number; + vSellHistory30mUSD: number; + vSell30mChangePercent: number; + trade1h: number; + tradeHistory1h: number; + trade1hChangePercent: number; + sell1h: number; + sellHistory1h: number; + sell1hChangePercent: number; + buy1h: number; + buyHistory1h: number; + buy1hChangePercent: number; + v1h: number; + v1hUSD: number; + vHistory1h: number; + vHistory1hUSD: number; + v1hChangePercent: number; + vBuy1h: number; + vBuy1hUSD: number; + vBuyHistory1h: number; + vBuyHistory1hUSD: number; + vBuy1hChangePercent: number; + vSell1h: number; + vSell1hUSD: number; + vSellHistory1h: number; + vSellHistory1hUSD: number; + vSell1hChangePercent: number; + trade2h: number; + tradeHistory2h: number; + trade2hChangePercent: number; + sell2h: number; + sellHistory2h: number; + sell2hChangePercent: number; + buy2h: number; + buyHistory2h: number; + buy2hChangePercent: number; + v2h: number; + v2hUSD: number; + vHistory2h: number; + vHistory2hUSD: number; + v2hChangePercent: number; + vBuy2h: number; + vBuy2hUSD: number; + vBuyHistory2h: number; + vBuyHistory2hUSD: number; + vBuy2hChangePercent: number; + vSell2h: number; + vSell2hUSD: number; + vSellHistory2h: number; + vSellHistory2hUSD: number; + vSell2hChangePercent: number; + trade4h: number; + tradeHistory4h: number; + trade4hChangePercent: number; + sell4h: number; + sellHistory4h: number; + sell4hChangePercent: number; + buy4h: number; + buyHistory4h: number; + buy4hChangePercent: number; + v4h: number; + v4hUSD: number; + vHistory4h: number; + vHistory4hUSD: number; + v4hChangePercent: number; + vBuy4h: number; + vBuy4hUSD: number; + vBuyHistory4h: number; + vBuyHistory4hUSD: number; + vBuy4hChangePercent: number; + vSell4h: number; + vSell4hUSD: number; + vSellHistory4h: number; + vSellHistory4hUSD: number; + vSell4hChangePercent: number; + trade8h: number; + tradeHistory8h: number; + trade8hChangePercent: number; + sell8h: number; + sellHistory8h: number; + sell8hChangePercent: number; + buy8h: number; + buyHistory8h: number; + buy8hChangePercent: number; + v8h: number; + v8hUSD: number; + vHistory8h: number; + vHistory8hUSD: number; + v8hChangePercent: number; + vBuy8h: number; + vBuy8hUSD: number; + vBuyHistory8h: number; + vBuyHistory8hUSD: number; + vBuy8hChangePercent: number; + vSell8h: number; + vSell8hUSD: number; + vSellHistory8h: number; + vSellHistory8hUSD: number; + vSell8hChangePercent: number; + trade24h: number; + tradeHistory24h: number; + trade24hChangePercent: number; + sell24h: number; + sellHistory24h: number; + sell24hChangePercent: number; + buy24h: number; + buyHistory24h: number; + buy24hChangePercent: number; + v24h: number; + v24hUSD: number; + vHistory24h: number; + vHistory24hUSD: number; + v24hChangePercent: number; + vBuy24h: number; + vBuy24hUSD: number; + vBuyHistory24h: number; + vBuyHistory24hUSD: number; + vBuy24hChangePercent: number; + vSell24h: number; + vSell24hUSD: number; + vSellHistory24h: number; + vSellHistory24hUSD: number; + vSell24hChangePercent: number; + numberMarkets: number; +}; + +type ApiResponse = { + data: TokenDataResponse; + success: boolean; +}; diff --git a/apps/marginfi-v2-trading/src/pages/portfolio.tsx b/apps/marginfi-v2-trading/src/pages/portfolio.tsx index e1ea89e604..f5c73613fa 100644 --- a/apps/marginfi-v2-trading/src/pages/portfolio.tsx +++ b/apps/marginfi-v2-trading/src/pages/portfolio.tsx @@ -1,20 +1,20 @@ import React from "react"; - +import { GetStaticProps } from "next"; +import Image from "next/image"; import Link from "next/link"; import { dynamicNumeralFormatter, groupedNumberFormatterDyn } from "@mrgnlabs/mrgn-common"; import { cn } from "@mrgnlabs/mrgn-utils"; -import { useTradeStoreV2, useUiStore } from "~/store"; +import { useTradeStoreV2 } from "~/store"; +import { GroupStatus } from "~/types"; +import { StaticArenaProps, getArenaStaticProps } from "~/utils"; import { PageHeading } from "~/components/common/PageHeading"; import { PositionCard, LpPositionList } from "~/components/common/Portfolio"; import { Loader } from "~/components/common/Loader"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { useExtendedPools } from "~/hooks/useExtendedPools"; -import { GroupStatus } from "~/types/trade-store.types"; -import { GetStaticProps } from "next"; -import { StaticArenaProps, getArenaStaticProps } from "~/utils"; import { Skeleton } from "~/components/ui/skeleton"; import { PnlBadge, PnlLabel } from "~/components/common/pnl-display"; import { GeoBlockingWrapper } from "~/components/common/geo-blocking-wrapper"; @@ -132,8 +132,7 @@ export default function PortfolioPage({ initialData }: StaticArenaProps) {
    {portfolioCombined.slice(0, 5).map((pool, index) => (
  • - {/* eslint-disable-next-line @next/next/no-img-element */} - {pool.tokenBank.meta.tokenSymbol} )}
- ); diff --git a/apps/marginfi-v2-trading/src/store/tradeStoreV2.ts b/apps/marginfi-v2-trading/src/store/tradeStoreV2.ts index dfa3cca3e7..fd3267e71b 100644 --- a/apps/marginfi-v2-trading/src/store/tradeStoreV2.ts +++ b/apps/marginfi-v2-trading/src/store/tradeStoreV2.ts @@ -25,7 +25,7 @@ import { unpackAccount, } from "@mrgnlabs/mrgn-common"; -import { POOLS_PER_PAGE } from "~/config/trade"; +import { POOLS_PER_PAGE, TOKEN_ICON_BASE_URL } from "~/config/trade"; import { ArenaBank, ArenaPoolPnl, @@ -34,7 +34,7 @@ import { ArenaPoolV2, BankData, GroupStatus, - TokenData, + TokenVolumeData, } from "~/types/trade-store.types"; import { OraclePriceV2ApiResponse } from "~/types/api.types"; import { @@ -75,7 +75,7 @@ type TradeStoreV2State = { groupsByGroupPk: Record; banksByBankPk: Record; positionsByGroupPk: Record; - tokenDataByMint: Record; + tokenVolumeDataByMint: Record; marginfiAccountByGroupPk: Record; mintDataByMint: MintDataMap; pythFeedIdMap: Map; @@ -187,7 +187,7 @@ const stateCreator: StateCreator = (set, get) => ({ groupsByGroupPk: {}, banksByBankPk: {}, marginfiAccountByGroupPk: {}, - tokenDataByMint: {}, + tokenVolumeDataByMint: {}, mintDataByMint: new Map(), arenaPoolsSummaryFuse: null, arenaPoolsFuse: null, @@ -222,21 +222,20 @@ const stateCreator: StateCreator = (set, get) => ({ if (!arenaState) { throw new Error("Failed to fetch arena state"); } - - const tokenDetailsByMint = arenaState.tokenDetails.reduce( + const tokenVolumeDataByMint = arenaState.tokenVolumeData.reduce( (acc, detail, index) => { - acc[detail.address] = detail; + acc[detail.mint] = detail; return acc; }, - {} as Record + {} as Record ); const groupSummaryByGroup: Record = arenaState.poolData.reduce( (acc, pool) => { const { address: quoteBankPk, mint: quoteMint, details: quoteBankData } = pool.quote_bank; const { address: tokenBankPk, mint: tokenMint, details: tokenBankDetails } = pool.base_bank; - const tokenDetailsQuote = tokenDetailsByMint[quoteMint.address]; - const tokenDetailsToken = tokenDetailsByMint[tokenMint.address]; + // const tokenDetailsQuote = tokenDetailsByMint[quoteMint.address]; + // const tokenDetailsToken = tokenDetailsByMint[tokenMint.address]; acc[pool.group] = { groupPk: new PublicKey(pool.group), @@ -244,8 +243,8 @@ const stateCreator: StateCreator = (set, get) => ({ tokenSummary: { bankPk: new PublicKey(tokenBankPk), mint: new PublicKey(tokenMint.address), - tokenName: tokenDetailsToken.name, - tokenSymbol: tokenDetailsToken.symbol, + tokenName: tokenMint.name, + tokenSymbol: tokenMint.symbol, bankData: { totalDeposits: tokenBankDetails.total_deposits, totalBorrows: tokenBankDetails.total_borrows, @@ -255,15 +254,15 @@ const stateCreator: StateCreator = (set, get) => ({ borrowRate: tokenBankDetails.borrow_rate, availableLiquidity: 0, } as BankData, - tokenData: tokenDetailsToken, - tokenLogoUri: tokenDetailsToken.imageUrl, // `https://storage.googleapis.com/mrgn-public/mrgn-token-icons/${tokenMint.address}.png`, + tokenVolumeData: tokenVolumeDataByMint[tokenMint.address], + tokenLogoUri: `${TOKEN_ICON_BASE_URL}${tokenMint.address}.png`, tokenProgram: new PublicKey(tokenMint.token_program), }, quoteSummary: { bankPk: new PublicKey(quoteBankPk), mint: new PublicKey(quoteMint.address), - tokenName: tokenDetailsQuote.name, - tokenSymbol: tokenDetailsQuote.symbol, + tokenName: quoteMint.name, + tokenSymbol: quoteMint.symbol, bankData: { totalDeposits: quoteBankData.total_deposits, totalBorrows: quoteBankData.total_borrows, @@ -273,8 +272,8 @@ const stateCreator: StateCreator = (set, get) => ({ borrowRate: quoteBankData.borrow_rate, availableLiquidity: 0, } as BankData, - tokenData: tokenDetailsQuote, - tokenLogoUri: `https://storage.googleapis.com/mrgn-public/mrgn-token-icons/${quoteMint.address}.png`, + tokenVolumeData: tokenVolumeDataByMint[quoteMint.address], + tokenLogoUri: `${TOKEN_ICON_BASE_URL}${quoteMint.address}.png`, tokenProgram: new PublicKey(quoteMint.token_program), }, createdAt: pool.created_at, @@ -286,7 +285,7 @@ const stateCreator: StateCreator = (set, get) => ({ {} as Record ); - const sortedGroups = sortSummaryPools(groupSummaryByGroup, tokenDetailsByMint, get().sortBy); + const sortedGroups = sortSummaryPools(groupSummaryByGroup, tokenVolumeDataByMint, get().sortBy); const totalPages = Math.ceil(Object.keys(sortedGroups).length / POOLS_PER_PAGE); const currentPage = get().currentPage || 1; @@ -312,7 +311,7 @@ const stateCreator: StateCreator = (set, get) => ({ set({ arenaPoolsSummary: sortedGroups, - tokenDataByMint: tokenDetailsByMint, + tokenVolumeDataByMint: tokenVolumeDataByMint, arenaPoolsSummaryFuse: fuse, initialized: true, totalPages, @@ -329,7 +328,7 @@ const stateCreator: StateCreator = (set, get) => ({ // if arguments are not provided use the current store state const connection = args.connection || get().connection; const wallet = args.wallet || get().wallet; - const { arenaPoolsSummary, tokenDataByMint } = get(); + const { arenaPoolsSummary, tokenVolumeDataByMint } = get(); // errors thrown if these conditions are not met should be investigated if (!connection) throw new Error("Connection not found in fetching extended arena groups"); @@ -386,7 +385,7 @@ const stateCreator: StateCreator = (set, get) => ({ } ); - let extendedBankInfos = compileExtendedArenaBank(banksWithPriceAndToken, tokenDataByMint); + let extendedBankInfos = compileExtendedArenaBank(banksWithPriceAndToken, tokenVolumeDataByMint); let nativeSolBalance = 0; let tokenAccountMap: TokenAccountMap | null = null; @@ -442,27 +441,6 @@ const stateCreator: StateCreator = (set, get) => ({ {} as Record ); - // if (!lutByGroupPk || Object.keys(lutByGroupPk).length === 0) { - // const lutResults: Record> | null> = {}; - - // // Create lookup promises for each group - // Object.entries(arenaPoolsSummary).forEach(([groupPk, summary]) => { - // lutResults[groupPk] = summary.luts ? connection.getAddressLookupTable(new PublicKey(summary.luts[0])) : null; - // }); - - // const results = await Promise.all(Object.values(lutResults)); - - // const updatedLutByGroupPk = Object.keys(lutResults).reduce((acc, groupPk, index) => { - // const lutAccount = results[index]?.value; - // if (lutAccount) { - // acc[groupPk] = [lutAccount]; - // } - // return acc; - // }, {} as Record); - - // set({ lutByGroupPk: updatedLutByGroupPk }); - // } - set({ arenaPools, poolsFetched: true, @@ -571,7 +549,7 @@ const stateCreator: StateCreator = (set, get) => ({ const connection = args.connection || get().connection; const wallet = args.wallet || get().wallet; const bankAddresses = args.banks; - const { pythFeedIdMap, arenaPoolsSummary, oraclePrices, tokenDataByMint } = get(); + const { pythFeedIdMap, arenaPoolsSummary, oraclePrices, tokenVolumeDataByMint } = get(); if (!connection) throw new Error("Connection not found"); @@ -603,7 +581,7 @@ const stateCreator: StateCreator = (set, get) => ({ } ); - let extendedBankInfos = compileExtendedArenaBank(banksWithPriceAndToken, tokenDataByMint); + let extendedBankInfos = compileExtendedArenaBank(banksWithPriceAndToken, tokenVolumeDataByMint); let nativeSolBalance = 0; let tokenAccountMap: TokenAccountMap | null = null; @@ -717,10 +695,10 @@ const stateCreator: StateCreator = (set, get) => ({ }, setSortBy: (sortBy: TradePoolFilterStates) => { - const { arenaPoolsSummary, tokenDataByMint, arenaPools, banksByBankPk } = get(); + const { arenaPoolsSummary, tokenVolumeDataByMint, arenaPools, banksByBankPk } = get(); - const sortedPoolsSummary = sortSummaryPools(arenaPoolsSummary, tokenDataByMint, sortBy); - const sortedPools = sortPools(arenaPools, tokenDataByMint, banksByBankPk, sortBy); + const sortedPoolsSummary = sortSummaryPools(arenaPoolsSummary, tokenVolumeDataByMint, sortBy); + const sortedPools = sortPools(arenaPools, tokenVolumeDataByMint, banksByBankPk, sortBy); set({ sortBy, arenaPoolsSummary: sortedPoolsSummary, arenaPools: sortedPools }); }, @@ -884,7 +862,7 @@ const stateCreator: StateCreator = (set, get) => ({ const sortPools = ( arenaPools: Record, - tokenDataByMint: Record, + tokenVolumeDataByMint: Record, banksByBankPk: Record, sortBy: TradePoolFilterStates ) => { @@ -892,8 +870,8 @@ const sortPools = ( const timestampOrder = Object.keys(arenaPools).reverse(); const sortedGroups = groups.sort((a, b) => { - const aTokenData = tokenDataByMint[a.tokenBankPk.toBase58()]; - const bTokenData = tokenDataByMint[b.tokenBankPk.toBase58()]; + const aTokenData = tokenVolumeDataByMint[a.tokenBankPk.toBase58()]; + const bTokenData = tokenVolumeDataByMint[b.tokenBankPk.toBase58()]; const aBankData = banksByBankPk[a.tokenBankPk.toBase58()]; const aQuoteBankData = banksByBankPk[a.quoteBankPk.toBase58()]; @@ -949,15 +927,15 @@ const sortPools = ( const sortSummaryPools = ( arenaPools: Record, - tokenDataByMint: Record, + tokenVolumeDataByMint: Record, sortBy: TradePoolFilterStates ) => { const groups = [...Object.values(arenaPools)]; const timestampOrder = Object.keys(arenaPools).reverse(); const sortedGroups = groups.sort((a, b) => { - const aTokenData = tokenDataByMint[a.tokenSummary.mint.toBase58()]; - const bTokenData = tokenDataByMint[b.tokenSummary.mint.toBase58()]; + const aTokenData = tokenVolumeDataByMint[a.tokenSummary.mint.toBase58()]; + const bTokenData = tokenVolumeDataByMint[b.tokenSummary.mint.toBase58()]; if (sortBy === TradePoolFilterStates.TIMESTAMP) { const aCreatedAt = new Date(a.createdAt).getTime(); diff --git a/apps/marginfi-v2-trading/src/types/trade-store.types.ts b/apps/marginfi-v2-trading/src/types/trade-store.types.ts index f9ee7b050b..9b810b46ed 100644 --- a/apps/marginfi-v2-trading/src/types/trade-store.types.ts +++ b/apps/marginfi-v2-trading/src/types/trade-store.types.ts @@ -64,7 +64,7 @@ export type BankSummary = { tokenProgram: PublicKey; bankData: BankData; - tokenData: TokenData; + tokenVolumeData: TokenVolumeData; }; export type ArenaPoolSummary = { @@ -130,6 +130,17 @@ export enum PoolTypes { LST = "lst", } +export type TokenVolumeData = { + mint: string; + price: number; + priceChange24h: number; + volume24h: number; + volumeChange24h: number; + volume4h: number; + volumeChange4h: number; + marketcap: number; +}; + export type TokenData = { address: string; name: string; diff --git a/apps/marginfi-v2-trading/src/utils/trade-store.utils.ts b/apps/marginfi-v2-trading/src/utils/trade-store.utils.ts index 96601e0670..7d7ed7bbde 100644 --- a/apps/marginfi-v2-trading/src/utils/trade-store.utils.ts +++ b/apps/marginfi-v2-trading/src/utils/trade-store.utils.ts @@ -1,15 +1,12 @@ +import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; +import { GetStaticProps, NextApiRequest } from "next"; + import { Bank, BankRaw, MarginfiAccount, MarginfiProgram, MintData, OraclePrice } from "@mrgnlabs/marginfi-client-v2"; import { fetchTokenAccounts, makeExtendedBankInfo, TokenAccount, UserDataProps } from "@mrgnlabs/marginfi-v2-ui-state"; import { BankMetadata, TokenMetadata } from "@mrgnlabs/mrgn-common"; -import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; -import { GetStaticProps, GetStaticPropsContext, NextApiRequest } from "next"; -import { TokenData } from "~/types"; -import { - PoolListApiResponse, - PoolPnlApiResponse, - PoolPnlMapApiResponse, - PoolPositionsApiResponse, -} from "~/types/api.types"; + +import { TOKEN_ICON_BASE_URL } from "~/config/trade"; +import { PoolListApiResponse, PoolPnlApiResponse, PoolPositionsApiResponse } from "~/types/api.types"; import { ArenaBank, ArenaPoolPnl, @@ -17,6 +14,7 @@ import { ArenaPoolSummary, ArenaPoolV2, GroupStatus, + TokenVolumeData, } from "~/types/trade-store.types"; /** @@ -27,7 +25,7 @@ import { */ export type InitialArenaState = { - tokenDetails: TokenData[]; + tokenVolumeData: TokenVolumeData[]; poolData: PoolListApiResponse[]; }; @@ -43,7 +41,7 @@ export const getArenaStaticProps: GetStaticProps = async (cont const emptyState: InitialArenaState = { poolData: [], - tokenDetails: [], + tokenVolumeData: [], }; let groupPk: string | null = null; @@ -75,17 +73,15 @@ export const getArenaStaticProps: GetStaticProps = async (cont if (groupPk) { const poolData = initialData.poolData.find((pool) => pool.group === groupPk); - const tokenDetails = initialData.tokenDetails.find( - (token) => token.address === poolData?.base_bank?.mint.address.toString() - ); - const quoteTokenDetails = initialData.tokenDetails.find( - (token) => token.address === poolData?.quote_bank?.mint.address.toString() - ); - - if (poolData && tokenDetails && quoteTokenDetails) { - metadata.title = `Trade ${tokenDetails.symbol}/${quoteTokenDetails.symbol} with leverage in The Arena.`; - metadata.description = `Trade ${tokenDetails.symbol} / ${quoteTokenDetails.symbol} with leverage in The Arena.`; - metadata.image = `${baseUrl}/api/share-image/generate?tokenSymbol=${tokenDetails.symbol}&tokenImageUrl=${tokenDetails.imageUrl}"eTokenSymbol=${quoteTokenDetails.symbol}"eTokenImageUrl=${quoteTokenDetails.imageUrl}`; + + if (poolData) { + const tokenSymbol = poolData.base_bank.mint.symbol; + const quoteSymbol = poolData.quote_bank.mint.symbol; + const tokenImageUrl = `${TOKEN_ICON_BASE_URL}${poolData.base_bank.mint.address}.png`; + const quoteImageUrl = `${TOKEN_ICON_BASE_URL}${poolData.quote_bank.mint.address}.png`; + metadata.title = `Trade ${tokenSymbol}/${quoteSymbol} with leverage in The Arena.`; + metadata.description = `Trade ${tokenSymbol} / ${quoteSymbol} with leverage in The Arena.`; + metadata.image = `${baseUrl}/api/share-image/generate?tokenSymbol=${tokenSymbol}&tokenImageUrl=${tokenImageUrl}"eTokenSymbol=${quoteSymbol}"eTokenImageUrl=${quoteImageUrl}`; } } @@ -116,7 +112,7 @@ export const fetchInitialArenaState = async (baseUrl?: string): Promise res.json() as Promise), fetch(`${baseUrl}/api/token/arenaTokens`, { headers: { @@ -128,7 +124,7 @@ export const fetchInitialArenaState = async (baseUrl?: string): Promise + tokenVolumeDataByMint: Record ): ArenaBank[] => { return banksWithPriceAndToken.map(({ bank, oraclePrice, tokenMetadata }) => { const extendedBankInfo = makeExtendedBankInfo(tokenMetadata, bank, oraclePrice, undefined, undefined, true); const mintAddress = bank.mint.toBase58(); - const tokenData = tokenDataByMint[mintAddress]; - if (!tokenData) { + const tokenVolumeData = tokenVolumeDataByMint[mintAddress]; + if (!tokenVolumeData) { console.error("Failed to parse token data"); } const extendedArenaBank = { ...extendedBankInfo, tokenData: { - price: tokenData.price, - priceChange24hr: tokenData.priceChange24h, - volume24hr: tokenData.volume24h, - volumeChange24hr: tokenData.volumeChange24h, - marketCap: tokenData.marketcap, + price: tokenVolumeData.price, + priceChange24hr: tokenVolumeData.priceChange24h, + volume24hr: tokenVolumeData.volume24h, + volumeChange24hr: tokenVolumeData.volumeChange24h, + marketCap: tokenVolumeData.marketcap, }, } as ArenaBank; @@ -322,7 +318,7 @@ export const updateArenaBankWithUserData = async ( })); // fetch - const [tokenAccounts] = await Promise.all([fetchTokenAccounts(connection, owner, bankInfos, tokenDatas)]); + const [tokenAccounts] = await Promise.all([fetchTokenAccounts(connection, owner, bankInfos, tokenDatas, false)]); const nativeSolBalance = tokenAccounts.nativeSolBalance; const tokenAccountMap = tokenAccounts.tokenAccountMap; diff --git a/apps/marginfi-v2-ui/src/components/common/Stake/components/arena-integration-card.tsx b/apps/marginfi-v2-ui/src/components/common/Stake/components/arena-integration-card.tsx index 71d9878458..6d0f9f8593 100644 --- a/apps/marginfi-v2-ui/src/components/common/Stake/components/arena-integration-card.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Stake/components/arena-integration-card.tsx @@ -2,11 +2,9 @@ import React from "react"; import Image from "next/image"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { percentFormatter, usdFormatter } from "@mrgnlabs/mrgn-common"; -import { getDepositsData, getRateData, getTokenImageURL } from "@mrgnlabs/mrgn-utils"; +import { getTokenImageURL } from "@mrgnlabs/mrgn-utils"; import { PublicKey } from "@solana/web3.js"; -import { useWallet } from "~/components/wallet-v2"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { IconArena, IconLST } from "~/components/ui/icons"; @@ -18,10 +16,6 @@ type ArenaIntegrationCardProps = { }; const ArenaIntegrationCard = ({ lstBank, goatBank }: ArenaIntegrationCardProps) => { - // const lstDepositData = React.useMemo(() => getDepositsData(lstBank, true, true), [lstBank]); - // const goatDepositData = React.useMemo(() => getDepositsData(goatBank, true, true), [goatBank]); - // const rateData = React.useMemo(() => getRateData(lstBank, true), [lstBank]); - return ( diff --git a/packages/marginfi-client-v2/src/instructions.ts b/packages/marginfi-client-v2/src/instructions.ts index bcb1fa6e4c..8978123ff5 100644 --- a/packages/marginfi-client-v2/src/instructions.ts +++ b/packages/marginfi-client-v2/src/instructions.ts @@ -341,6 +341,8 @@ async function makeLendingPoolConfigureBankOracleIx( mfProgram: MarginfiProgram, accounts: { bank: PublicKey; + group?: PublicKey; + admin?: PublicKey; }, args: { setup: number; @@ -349,8 +351,10 @@ async function makeLendingPoolConfigureBankOracleIx( ) { return mfProgram.methods .lendingPoolConfigureBankOracle(args.setup, args.oracle) - .accounts({ + .accountsPartial({ bank: accounts.bank, + group: accounts.group, + admin: accounts.admin, }) .instruction(); } diff --git a/packages/marginfi-client-v2/src/services/bank/bank.service.ts b/packages/marginfi-client-v2/src/services/bank/bank.service.ts index 8c828e0761..6929886173 100644 --- a/packages/marginfi-client-v2/src/services/bank/bank.service.ts +++ b/packages/marginfi-client-v2/src/services/bank/bank.service.ts @@ -10,12 +10,16 @@ export async function addOracleToBanksIx( program: MarginfiProgram, bankAddress: PublicKey, oracle: PublicKey, - setup: OracleSetup + setup: OracleSetup, + groupAddress?: PublicKey, + adminAddress?: PublicKey ): Promise { const ix = await instructions.makeLendingPoolConfigureBankOracleIx( program, { bank: bankAddress, + group: groupAddress, + admin: adminAddress, }, { setup: serializeOracleSetupToIndex(setup), diff --git a/packages/marginfi-v2-ui-state/src/lib/mrgnlend/utils/account.utils.ts b/packages/marginfi-v2-ui-state/src/lib/mrgnlend/utils/account.utils.ts index 378c05130a..76d7b8e801 100644 --- a/packages/marginfi-v2-ui-state/src/lib/mrgnlend/utils/account.utils.ts +++ b/packages/marginfi-v2-ui-state/src/lib/mrgnlend/utils/account.utils.ts @@ -56,7 +56,8 @@ async function fetchTokenAccounts( connection: Connection, walletAddress: PublicKey, bankInfos: { mint: PublicKey; mintDecimals: number; bankAddress: PublicKey; assetTag?: number }[], - mintDatas: Map + mintDatas: Map, + fetchStakeAccounts: boolean = true ): Promise<{ nativeSolBalance: number; tokenAccountMap: TokenAccountMap; @@ -88,7 +89,7 @@ async function fetchTokenAccounts( } // get users native stake accounts - const stakeAccounts = await getStakeAccountsCached(walletAddress); + const stakeAccounts = fetchStakeAccounts ? await getStakeAccountsCached(walletAddress) : []; const ataAddresses = mintList.map((mint) => { const mintData = mintDatas.get(mint.bankAddress.toBase58()); diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/components/apy-stat/apy-stat.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/components/apy-stat/apy-stat.tsx index 582ed60059..a2ba8e14e0 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/components/apy-stat/apy-stat.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/components/apy-stat/apy-stat.tsx @@ -1,12 +1,11 @@ import React from "react"; -import Image from "next/image"; +import { IconChevronDown } from "@tabler/icons-react"; import { calcNetLoopingApy, cn } from "@mrgnlabs/mrgn-utils"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; - import { percentFormatter } from "@mrgnlabs/mrgn-common"; + import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; -import { IconChevronDown } from "@tabler/icons-react"; type ApyStatProps = { selectedBank: ExtendedBankInfo | null; @@ -86,6 +85,7 @@ export const ApyStat = ({
  • + {/* eslint-disable-next-line @next/next/no-img-element */}
    + {/* eslint-disable-next-line @next/next/no-img-element */}
    -
    - {/* {bothBanksSelected && netApy && ( -
    - - - Net APY - - - {bothBanksSelected && selectedBank && selectedRepayBank && ( - <> -
      - {[selectedBank, selectedRepayBank].map((bank, index) => { - const isDepositBank = index === 0; - return ( - <> -
    • -
      - {bank.meta.tokenName} - {bank.meta.tokenSymbol} -
      - - {percentFormatter.format( - isDepositBank ? depositTokenApy.tokenApy : borrowTokenApy.tokenApy - )} - -
    • - - {isDepositBank && isDepositingLst && ( -
    • -
      - {bank.meta.tokenName} -
      - {bank.meta.tokenSymbol} stake yield -
      -
      - - {percentFormatter.format(depositTokenApy.lstApy)} - -
    • - )} - - {!isDepositBank && isBorrowingLst && ( -
    • -
      - {bank.meta.tokenName} -
      - {bank.meta.tokenSymbol} stake yield -
      -
      - - {percentFormatter.format(borrowTokenApy.lstApy)} - -
    • - )} - - ); - })} -
    - - )} -
    -
    - {netApy} APY -
    - )} */}
    ); diff --git a/packages/mrgn-utils/src/jup-referral.utils.ts b/packages/mrgn-utils/src/jup-referral.utils.ts index 4dfc5da28a..3a33665ec7 100644 --- a/packages/mrgn-utils/src/jup-referral.utils.ts +++ b/packages/mrgn-utils/src/jup-referral.utils.ts @@ -1,11 +1,16 @@ import { ReferralProvider } from "@jup-ag/referral-sdk"; -import { Connection, PublicKey } from "@solana/web3.js"; +import { InstructionsWrapper } from "@mrgnlabs/mrgn-common"; +import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js"; export const TOKEN_2022_MINTS = ["2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"]; export const REFERRAL_PROGRAM_ID = new PublicKey("REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3"); export const REFERRAL_ACCOUNT_PUBKEY = new PublicKey("Mm7HcujSK2JzPW4eX7g4oqTXbWYDuFxapNMHXe8yp1B"); -export async function createReferalTokenAccount(connection: Connection, payer: PublicKey, mint: PublicKey) { +export async function createReferalTokenAccountIxs( + connection: Connection, + payer: PublicKey, + mint: PublicKey +): Promise { const provider = new ReferralProvider(connection); const { tx, referralTokenAccountPubKey } = await provider.initializeReferralTokenAccount({ @@ -14,7 +19,15 @@ export async function createReferalTokenAccount(connection: Connection, payer: P mint, }); - return tx; + const accountInfo = await connection.getAccountInfo(referralTokenAccountPubKey); + + if (accountInfo) { + return []; + } + + console.log("tx", tx); + + return tx.instructions; } export const getFeeAccount = (mint: PublicKey) => { diff --git a/packages/mrgn-utils/src/mrgnUtils.ts b/packages/mrgn-utils/src/mrgnUtils.ts index 8eced783ab..4fa8825cc3 100644 --- a/packages/mrgn-utils/src/mrgnUtils.ts +++ b/packages/mrgn-utils/src/mrgnUtils.ts @@ -45,8 +45,8 @@ export function computeBankRateRaw(bank: ExtendedBankInfo, lendingMode: LendingM ? bank.info.state.emissionsRate : 0 : bank.info.state.emissions == Emissions.Borrowing - ? bank.info.state.emissionsRate - : 0; + ? bank.info.state.emissionsRate + : 0; const aprRate = interestRate + emissionRate; const apyRate = aprToApy(aprRate); @@ -126,7 +126,10 @@ export function extractErrorString(error: any, fallback?: string): string { return fallback ?? "Unrecognized error"; } -export function getTokenImageURL(bank: ExtendedBankInfo | PublicKey): string { +export function getTokenImageURL( + bank: ExtendedBankInfo | PublicKey, + baseUrl: string = "https://storage.googleapis.com/mrgn-public/mrgn-token-icons/" +): string { const verifyPublicKey = (key: ExtendedBankInfo | PublicKey) => { try { const _ = new PublicKey(key).toBytes(); @@ -139,7 +142,7 @@ export function getTokenImageURL(bank: ExtendedBankInfo | PublicKey): string { const isPublicKey = verifyPublicKey(bank); const mintAddress = isPublicKey ? (bank as PublicKey) : (bank as ExtendedBankInfo).info.rawBank.mint; - return `https://storage.googleapis.com/mrgn-public/mrgn-token-icons/${mintAddress.toBase58()}.png`; + return `${baseUrl}${mintAddress.toBase58()}.png`; } export function isBankOracleStale(bank: ExtendedBankInfo) {