UNPKG

@lifi/composer-sdk

Version:

Public Composer SDK for building and submitting flows

123 lines (111 loc) 4.46 kB
import type { ChainSummary } from '../discovery.js'; import type { ComposeSdk } from '../sdk.js'; /** * The slice of {@link ComposeSdk} these helpers need. Depending on the one * capability rather than the whole SDK keeps them testable with a two-line * fake, and documents that nothing here touches the compile path. */ export type SupportedChainsSource = Pick<ComposeSdk, 'getSupportedChains'>; /** * A memoised view of the supported-chain list. */ export interface SupportedChainsCache { /** Supported chains, ascending by `chainId`. Fetched once, then served from memory. */ readonly get: () => Promise<readonly ChainSummary[]>; /** * Resolves when `chainId` is supported, rejects with an actionable message * naming the alternatives when it is not. */ readonly assertSupported: (chainId: number) => Promise<void>; /** * Display label for `chainId`, or `undefined` when the deployment does not * support it. Served from the same cached fetch as {@link get}. */ readonly nameOf: (chainId: number) => Promise<string | undefined>; /** Drops the memoised list so the next {@link get} refetches. */ readonly refresh: () => void; } /** * Wraps `sdk.getSupportedChains()` in a cache so a long-lived process pays for * one round trip instead of one per check. * * The SDK deliberately does not cache the list — the backend's chain registry * refreshes periodically, so the right lifetime is the caller's to choose. * Chains are added rarely, so caching for the life of a request handler (or * refreshing on a timer via {@link SupportedChainsCache.refresh}) is usually * right. * * Demonstrates: * - `sdk.getSupportedChains()`, the convenience accessor over `GET /chains` * - Preflighting a chain so an unsupported one fails locally with a message * naming the alternatives, instead of costing a server round trip * - Turning the per-chain `name` into display labels without a second lookup * or a hardcoded chain-name table * - Memoising the in-flight promise, not just the settled value, so concurrent * first callers share one request */ export const createSupportedChainsCache = ( sdk: SupportedChainsSource, ): SupportedChainsCache => { let inFlight: Promise<readonly ChainSummary[]> | undefined; const get = (): Promise<readonly ChainSummary[]> => { // A rejected fetch must not be memoised, or one transient network blip // would poison every later lookup. inFlight ??= sdk.getSupportedChains().catch((err: unknown) => { inFlight = undefined; throw err; }); return inFlight; }; const find = async (chainId: number): Promise<ChainSummary | undefined> => (await get()).find((chain) => chain.chainId === chainId); return { get, assertSupported: async (chainId: number): Promise<void> => { if (await find(chainId)) return; const chains = await get(); throw new Error( `Chain ${chainId} is not supported by this Compose deployment. ` + `Supported chains: ${chains .map((chain) => `${chain.name} (${chain.chainId})`) .join(', ')}`, ); }, nameOf: async (chainId: number): Promise<string | undefined> => (await find(chainId))?.name, refresh: (): void => { inFlight = undefined; }, }; }; /** Ethereum mainnet — supported by every deployment. */ const MAINNET = 1; /** Not an EVM chain ID any deployment serves; stands in for a bad input. */ const UNSUPPORTED = 999_999; /** * Harness entry point: prints the live chain list with names, then shows the * guard accepting a supported chain and rejecting an unsupported one. */ export const runSupportedChainsExample = async ( sdk: SupportedChainsSource, ): Promise<void> => { const chains = createSupportedChainsCache(sdk); const supported = await chains.get(); console.log(`Supported chains (${supported.length}):`); for (const { chainId, name } of supported) { console.log(` ${chainId}\t${name}`); } await chains.assertSupported(MAINNET); // Served from cache: no second round trip. console.log( `\n${await chains.nameOf(MAINNET)} (${MAINNET}) is supported — safe to build a request.`, ); await chains .assertSupported(UNSUPPORTED) .then(() => { throw new Error(`expected chain ${UNSUPPORTED} to be rejected`); }) .catch((err: unknown) => { console.log(`\nGuard rejected as expected: ${(err as Error).message}`); }); };