@saberhq/sail
Version:
Account caching and batched loading for React-based Solana applications.
206 lines • 7.46 kB
JavaScript
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