From 70c3a231d7a4ea409d47fbb327cf3e1dc79e135d Mon Sep 17 00:00:00 2001 From: Cody Lamson Date: Sat, 14 Dec 2024 12:07:32 +0100 Subject: [PATCH] Notify token 2022 (#319) * improved display on main page and display token 2022 where needed * linting fixes * formatting fix --- debug-ui/app/components/AppWalletProvider.tsx | 52 +++++++++- debug-ui/app/components/Chart.tsx | 13 +-- debug-ui/app/components/Orderbook.tsx | 8 +- debug-ui/app/page.tsx | 99 ++++++++++++------- debug-ui/lib/address-labels.ts | 10 +- debug-ui/lib/types.ts | 4 + debug-ui/lib/util.ts | 13 +++ 7 files changed, 147 insertions(+), 52 deletions(-) diff --git a/debug-ui/app/components/AppWalletProvider.tsx b/debug-ui/app/components/AppWalletProvider.tsx index b4824bd28..dc12029be 100644 --- a/debug-ui/app/components/AppWalletProvider.tsx +++ b/debug-ui/app/components/AppWalletProvider.tsx @@ -33,8 +33,14 @@ import { getClusterFromConnection, } from '@cks-systems/manifest-sdk/utils/solana'; import NavBar from './NavBar'; -import { ActiveByAddr, LabelsByAddr, VolumeByAddr } from '@/lib/types'; +import { + ActiveByAddr, + LabelsByAddr, + VolumeByAddr, + HasToken22ByAddr, +} from '@/lib/types'; import { fetchAndSetMfxAddrLabels } from '@/lib/address-labels'; +import { checkForToken22 } from '@/lib/util'; require('react-toastify/dist/ReactToastify.css'); require('@solana/wallet-adapter-react-ui/styles.css'); @@ -44,11 +50,14 @@ interface AppStateContextValue { network: WalletAdapterNetwork | null; marketAddrs: string[]; labelsByAddr: LabelsByAddr; + infoByAddr: LabelsByAddr; activeByAddr: ActiveByAddr; marketVolumes: VolumeByAddr; dailyVolumes: VolumeByAddr; + hasToken22ByAddr: HasToken22ByAddr; setMarketAddrs: Dispatch>; setLabelsByAddr: Dispatch>; + setInfoByAddr: Dispatch>; setActiveByAddr: Dispatch>; setMarketVolumes: Dispatch>; } @@ -75,8 +84,10 @@ const AppWalletProvider = ({ const [marketVolumes, setMarketVolumes] = useState({}); const [dailyVolumes, setDailyVolumes] = useState({}); const [labelsByAddr, setLabelsByAddr] = useState({}); + const [infoByAddr, setInfoByAddr] = useState({}); const [activeByAddr, setActiveByAddr] = useState({}); const [loading, setLoading] = useState(false); + const [has22ByAddr, setHas22ByAddr] = useState({}); const setupRun = useRef(false); const rpcUrl = process.env.NEXT_PUBLIC_RPC_URL; @@ -157,6 +168,7 @@ const AppWalletProvider = ({ // Fine to do an N^2 search until the number of markets gets too big. const activeByAddr: ActiveByAddr = {}; + const marketsByAddr: { [key: string]: Market } = {}; marketProgramAccounts.forEach( ( acct1: Readonly<{ @@ -168,6 +180,7 @@ const AppWalletProvider = ({ address: acct1.pubkey, buffer: acct1.account.data, }); + marketsByAddr[acct1.pubkey.toBase58()] = market; let foundBigger: boolean = false; marketProgramAccounts.forEach( @@ -198,7 +211,39 @@ const AppWalletProvider = ({ ); setActiveByAddr(activeByAddr); - fetchAndSetMfxAddrLabels(conn, marketProgramAccounts, setLabelsByAddr); + const activeAddrs = Object.entries(activeByAddr) + .filter(([_, active]) => active) + .map(([addr]) => addr); + const res: [string, boolean][] = await Promise.all( + activeAddrs.map(async (addr) => { + const market = marketsByAddr[addr]; + if (!market) { + throw new Error( + 'missing market in mapping. this should never happen', + ); + } + const [quoteIs22, baseIs22] = await Promise.all([ + checkForToken22(conn, market.quoteMint()), + checkForToken22(conn, market.baseMint()), + ]); + const has22 = quoteIs22 || baseIs22; + + return [addr, has22]; + }), + ); + const hasToken22ByAddr: HasToken22ByAddr = res.reduce((acc, curr) => { + const [addr, has22] = curr; + acc[addr] = has22; + return acc; + }, {} as HasToken22ByAddr); + setHas22ByAddr(hasToken22ByAddr); + + fetchAndSetMfxAddrLabels( + conn, + marketProgramAccounts, + setLabelsByAddr, + setInfoByAddr, + ); const tickers = await fetch( 'https://mfx-stats-mainnet.fly.dev/tickers', @@ -241,11 +286,14 @@ const AppWalletProvider = ({ marketVolumes, activeByAddr, labelsByAddr, + infoByAddr, setLabelsByAddr, + setInfoByAddr, setMarketAddrs, setMarketVolumes, setActiveByAddr, dailyVolumes, + hasToken22ByAddr: has22ByAddr, loading, }} > diff --git a/debug-ui/app/components/Chart.tsx b/debug-ui/app/components/Chart.tsx index ab339c598..d97627c85 100644 --- a/debug-ui/app/components/Chart.tsx +++ b/debug-ui/app/components/Chart.tsx @@ -14,7 +14,6 @@ import { useConnection } from '@solana/wallet-adapter-react'; import { PublicKey } from '@solana/web3.js'; import { toast } from 'react-toastify'; import { useAppState } from './AppWalletProvider'; -import { addrToLabel } from '@/lib/address-labels'; const Chart = ({ marketAddress }: { marketAddress: string }): ReactElement => { const chartContainerRef = useRef(null); @@ -22,10 +21,9 @@ const Chart = ({ marketAddress }: { marketAddress: string }): ReactElement => { const candlestickSeriesRef = useRef | null>(null); const marketRef = useRef(null); // To track the latest market value - const { labelsByAddr } = useAppState(); + const { labelsByAddr, hasToken22ByAddr } = useAppState(); const [chartEntries, setChartEntries] = useState([]); - const [marketName, setMarketName] = useState(marketAddress); const { connection: conn } = useConnection(); @@ -38,8 +36,6 @@ const Chart = ({ marketAddress }: { marketAddress: string }): ReactElement => { console.log('got market', m); marketRef.current = m; }); - - setMarketName(addrToLabel(marketAddress, labelsByAddr)); }, [conn, marketAddress, labelsByAddr]); useEffect(() => { @@ -190,7 +186,12 @@ const Chart = ({ marketAddress }: { marketAddress: string }): ReactElement => { return (

- {marketName} + {labelsByAddr[marketAddress]} + {hasToken22ByAddr[marketAddress] && ( + + TOKEN_2022 + + )}

diff --git a/debug-ui/app/components/Orderbook.tsx b/debug-ui/app/components/Orderbook.tsx index e99791393..731d688be 100644 --- a/debug-ui/app/components/Orderbook.tsx +++ b/debug-ui/app/components/Orderbook.tsx @@ -34,10 +34,10 @@ const Orderbook = ({ }, ); - return () => { + return (): void => { conn.removeAccountChangeListener(accountChangeListenerId); }; - }, [marketAddress]); + }, [conn, marketAddress]); useEffect(() => { try { @@ -80,10 +80,10 @@ const Orderbook = ({ } }); - return () => { + return (): void => { conn.removeSlotUpdateListener(slotUpdateListenerId); }; - }, []); + }, [conn]); const formatOrder = (restingOrder: RestingOrder, i: number): ReactElement => { const pk = wallet?.adapter?.publicKey; diff --git a/debug-ui/app/page.tsx b/debug-ui/app/page.tsx index 6acc0f4d5..b39110a56 100644 --- a/debug-ui/app/page.tsx +++ b/debug-ui/app/page.tsx @@ -13,79 +13,102 @@ const Home = (): ReactElement => { marketAddrs, loading, labelsByAddr, + infoByAddr, marketVolumes, activeByAddr, dailyVolumes, + hasToken22ByAddr, } = useAppState(); const [showAll, setShowAll] = useState(false); - function handleShowAllChange(event: { target: { checked: any } }) { + function handleShowAllChange(event: { target: { checked: boolean } }): void { setShowAll(event.target.checked); } return (
-
-

- Disclaimer: By accessing and using Manifest, you acknowledge and agree - that you do so at your own risk. This platform is intended for - developers ONLY and may not be actively supported or maintained. The - developers, contributors, and associated parties are not liable for - any losses, damages, or claims arising from your use of this platform. - This platform is provided "as is" without any warranties or +

+

+ + Disclaimer + + By accessing and using Manifest, you acknowledge and agree that you do + so at your own risk. This platform is intended for developers ONLY and + may not be actively supported or maintained. The developers, + contributors, and associated parties are not liable for any losses, + damages, or claims arising from your use of this platform. This + platform is provided "as is" without any warranties or guarantees. Users are responsible for complying with all applicable laws and regulations in their jurisdiction. Please exercise caution.

{loading ? ( -

Loading markets...

+

Loading markets...

) : marketAddrs.length > 0 ? ( <> -

- Existing Markets -

-
    +
    +

    + Existing Markets +

    +
    + + Show All +
    +
    + +
      {marketAddrs.map( (market, index) => (showAll || activeByAddr[market]) && (
    • {addrToLabel(market, labelsByAddr)} - {marketVolumes[market] != 0 - ? ' Total: $' + - marketVolumes[market]?.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }) - : ''} - {dailyVolumes[market] != 0 && - dailyVolumes[market] !== undefined - ? ' | 24 Hour: $' + - dailyVolumes[market]?.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }) - : ''} + + {hasToken22ByAddr[market] && ( + + TOKEN_2022 + + )} +
      + {marketVolumes[market] !== 0 && ( + <> + Total: $ + {marketVolumes[market]?.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + + )} + {dailyVolumes[market] !== 0 && + dailyVolumes[market] !== undefined && ( + <> + {' | 24 Hour: $'} + {dailyVolumes[market]?.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + + )} +
    • ), )}
    - - Show All ) : ( -

    No markets found.

    +

    No markets found.

    )}
diff --git a/debug-ui/lib/address-labels.ts b/debug-ui/lib/address-labels.ts index 8cbe24fe9..6e9197aa4 100644 --- a/debug-ui/lib/address-labels.ts +++ b/debug-ui/lib/address-labels.ts @@ -44,6 +44,7 @@ export const fetchAndSetMfxAddrLabels = async ( conn: Connection, marketProgramAccounts: GetProgramAccountsResponse, setLabelsByAddr: Dispatch>, + setInfoByAddr: Dispatch>, ): Promise> => { const mints = new Set(); const markets: Market[] = []; @@ -75,12 +76,17 @@ export const fetchAndSetMfxAddrLabels = async ( ); const marketLabels: LabelsByAddr = {}; + const infoByAddr: LabelsByAddr = {}; for (const m of markets) { - marketLabels[m.address.toBase58()] = - `MFX-${pubkeyToLabel(m.baseMint(), mintLabels)}/${pubkeyToLabel(m.quoteMint(), mintLabels)}-${shortenPub(m.address)}`; + const marketAddr = m.address.toBase58(); + marketLabels[marketAddr] = + `${pubkeyToLabel(m.baseMint(), mintLabels)}/${pubkeyToLabel(m.quoteMint(), mintLabels)}`; + infoByAddr[marketAddr] = + `base: ${m.baseMint().toBase58()} quote: ${m.quoteMint().toBase58()} market: ${marketAddr}`; } setLabelsByAddr({ ...mintLabels, ...marketLabels }); + setInfoByAddr({ ...infoByAddr }); return mints; }; diff --git a/debug-ui/lib/types.ts b/debug-ui/lib/types.ts index d61d9fc09..ccd3ccc79 100644 --- a/debug-ui/lib/types.ts +++ b/debug-ui/lib/types.ts @@ -12,6 +12,10 @@ export interface VolumeByAddr { [addr: string]: number; } +export interface HasToken22ByAddr { + [addr: string]: boolean; +} + export type FillResultUi = { market: string; maker: string; diff --git a/debug-ui/lib/util.ts b/debug-ui/lib/util.ts index 509f8dc67..b29459619 100644 --- a/debug-ui/lib/util.ts +++ b/debug-ui/lib/util.ts @@ -1,4 +1,5 @@ import { ManifestClient } from '@cks-systems/manifest-sdk'; +import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { SendTransactionOptions, WalletAdapterNetwork, @@ -92,3 +93,15 @@ export const shortenAddress = (address: string): string => { export const shortenSig = (address: string): string => { return `${address.slice(0, 6)}...${address.slice(-6)}`; }; + +export const checkForToken22 = async ( + conn: Connection, + mint: PublicKey, +): Promise => { + const acc = await conn.getAccountInfo(mint); + if (!acc) { + throw new Error('checkForToken22: account does not exist'); + } + + return acc.owner.toBase58() !== TOKEN_PROGRAM_ID.toBase58(); +};