UNPKG

@saberhq/sail

Version:

Account caching and batched loading for React-based Solana applications.

206 lines 7.46 kB
import { mapSome } from "@saberhq/solana-contrib"; import { deserializeMint, networkToChainId, Token } from "@saberhq/token-utils"; import { useSolana } from "@saberhq/use-solana"; import { PublicKey } from "@solana/web3.js"; import { useMemo } from "react"; import { useQueries, useQuery } from "react-query"; import { fetchNullableWithSessionCache } from ".."; import { useSail } from "../provider"; import { makeListMemoKey } from "../utils"; import { usePubkey } from "./usePubkey"; // const makeCertifiedTokenInfoURLCDN = (chainId: number, address: string) => // `https://cdn.jsdelivr.net/gh/CLBExchange/certified-token-list/${chainId}/${address}.json`; const makeCertifiedTokenInfoURL = (chainId, address) => `https://raw.githubusercontent.com/CLBExchange/certified-token-list/master/${chainId}/${address}.json`; const normalizeMint = (mint) => { if (!mint) { return mint; } // default pubkey is treated as null if (mint.equals(PublicKey.default)) { return null; } return mint; }; const makeCertifiedTokenQuery = (network, address) => ({ queryKey: ["sail/certifiedTokenInfo", network, address], queryFn: async ({ signal }) => { if (address === null || address === undefined) { return address; } const chainId = networkToChainId(network); const info = await fetchNullableWithSessionCache(makeCertifiedTokenInfoURL(chainId, address), signal); if (info === null) { return null; } return new Token(info); }, // these should never be stale, since token mints are immutable (other than supply) staleTime: Infinity, }); /** * Loads multiple tokens from the Certified Token List. * @param mint * @returns */ export const useCertifiedTokens = (mints) => { const { network } = useSolana(); return useQueries(mints.map((mint) => makeCertifiedTokenQuery(network, mint))); }; /** * Loads a token from the Certified Token List. * @param mint * @returns */ export const useCertifiedToken = (mint) => { const { network } = useSolana(); return useQuery(makeCertifiedTokenQuery(network, mint)); }; /** * Constructs a query to load a token from the Certified Token List, or from the blockchain if * it cannot be found. * * @returns Token query */ export const makeBatchedTokensQuery = ({ network, addresses, fetchKeys, }) => { var _a; return ({ queryKey: [ "sail/batchedTokens", network, ...((_a = mapSome(addresses, (a) => a.map((address) => address === null || address === void 0 ? void 0 : address.toString()))) !== null && _a !== void 0 ? _a : [ addresses, ]), ], queryFn: async ({ signal, }) => { const addressesToFetch = []; if (!addresses) { return addresses; } const data = await Promise.all(addresses.map(async (address, i) => { if (address === null || address === undefined) { return address; } const chainId = networkToChainId(network); const info = await fetchNullableWithSessionCache(makeCertifiedTokenInfoURL(chainId, address.toString()), signal); if (info !== null) { return new Token(info); } addressesToFetch.push({ key: address, index: i }); })); if (signal === null || signal === void 0 ? void 0 : signal.aborted) { throw new Error("Query aborted"); } const tokenDatas = await fetchKeys(addressesToFetch.map((a) => a.key)); tokenDatas.forEach((tokenData, i) => { var _a; const index = (_a = addressesToFetch[i]) === null || _a === void 0 ? void 0 : _a.index; if (index === undefined) { return; } if (!tokenData || !tokenData.data) { data[index] = null; return; } const raw = tokenData.data.accountInfo.data; const parsed = deserializeMint(raw); const token = Token.fromMint(tokenData.data.accountId, parsed.decimals, { chainId: networkToChainId(network), }); data[index] = token; }); return data; }, // these should never be stale, since token mints are immutable (other than supply) staleTime: Infinity, }); }; /** * Constructs a query to load a token from the Certified Token List, or from the blockchain if * it cannot be found. * * @returns Token query */ export const makeTokenQuery = ({ network, address, fetchKeys, }) => ({ queryKey: ["sail/tokenInfo", network, address === null || address === void 0 ? void 0 : address.toString()], queryFn: async ({ signal }) => { if (address === null || address === undefined) { return address; } const chainId = networkToChainId(network); const info = await fetchNullableWithSessionCache(makeCertifiedTokenInfoURL(chainId, address.toString()), signal); if (info !== null) { return new Token(info); } const [tokenData] = await fetchKeys([address]); if (!tokenData) { return null; } if (!tokenData.data) { return tokenData.data; } const raw = tokenData.data.accountInfo.data; const parsed = deserializeMint(raw); return Token.fromMint(address, parsed.decimals, { chainId: networkToChainId(network), }); }, // these should never be stale, since token mints are immutable (other than supply) staleTime: Infinity, }); const useNormalizedMints = (mints) => { return useMemo(() => { var _a; return (_a = mints === null || mints === void 0 ? void 0 : mints.map(normalizeMint)) !== null && _a !== void 0 ? _a : []; // eslint-disable-next-line react-hooks/exhaustive-deps }, [makeListMemoKey(mints)]); }; /** * Uses and loads a series of mints as {@link Token}s. * @param mints * @returns */ export const useTokens = (mints) => { const { network } = useSolana(); const { fetchKeys } = useSail(); const normalizedMints = useNormalizedMints(mints); return useQueries(normalizedMints.map((mint) => { return makeTokenQuery({ network, address: mint, fetchKeys, }); })); }; /** * Uses and loads a series of mints as {@link Token}s using a batched call. * @param mints * @returns */ export const useBatchedTokens = (mints) => { const { network } = useSolana(); const { fetchKeys } = useSail(); const normalizedMints = useNormalizedMints(mints); return useQuery(makeBatchedTokensQuery({ network, addresses: normalizedMints, fetchKeys, })); }; /** * Uses and loads a single token. * * @param mint * @returns */ export const useToken = (mintRaw) => { const mint = usePubkey(mintRaw); const { network } = useSolana(); const { fetchKeys } = useSail(); const normalizedMint = useMemo(() => mapSome(mint, normalizeMint), [mint]); return useQuery(makeTokenQuery({ network, address: normalizedMint, fetchKeys, })); }; //# sourceMappingURL=useToken.js.map