Integration examples
Read backing through the gateway, paginate discovery, and reuse the application’s quote and transaction validators.
Integrations should select the intended chain, discover the configured contracts, read authoritative state, and validate quotes before opening the wallet. The application’s frontend/lib modules are source-level integration references; they are not a published, versioned npm SDK.
Read a basket at one block
This Node.js example uses viem in an application with that dependency installed. Save it as read-basket.mjs. It sends only read requests through the public API gateway and checks membership in the configured community/native registries.
import { createPublicClient, getAddress, http, parseAbi } from 'viem';
const apiOrigin = process.env.API_ORIGIN ?? 'http://localhost:8787';
const expectedChain = Number(process.env.CHAIN_ID ?? '46630');
if (![4663, 46630].includes(expectedChain)) throw new Error('Unsupported chain.');
if (!process.argv[2]) throw new Error('Pass a basket address.');
const basket = getAddress(process.argv[2]);
const response = await fetch(`${apiOrigin}/v1/config`);
if (!response.ok) throw new Error('Configuration unavailable.');
const config = await response.json();
if (config.chainId !== expectedChain || !config.ready) {
throw new Error('API network mismatch or RPC unavailable.');
}
const client = createPublicClient({
ccipRead: false,
transport: http(`${apiOrigin}/rpc`, { retryCount: 0 }),
});
if (await client.getChainId() !== expectedChain) throw new Error('RPC mismatch.');
const blockNumber = await client.getBlockNumber();
const registryAbi = parseAbi(['function isBasket(address) view returns (bool)']);
const registries = [config.factoryAddress, config.nativeFactoryAddress]
.filter(Boolean).map((address) => getAddress(address));
const membership = await Promise.all(registries.map((address) =>
client.readContract({
address, abi: registryAbi, functionName: 'isBasket',
args: [basket], blockNumber,
}),
));
if (!membership.some(Boolean)) throw new Error('Not in configured community registries.');
const abi = parseAbi([
'function getTokens() view returns (address[])',
'function getReserves() view returns (uint256[])',
'function totalSupply() view returns (uint256)',
'function closed() view returns (bool)',
]);
const read = (functionName) => client.readContract({
address: basket, abi, functionName, blockNumber,
});
const [tokens, reserves, totalSupply, closed] = await Promise.all([
read('getTokens'), read('getReserves'), read('totalSupply'), read('closed'),
]);
if (tokens.length !== reserves.length) throw new Error('Invalid backing shape.');
console.log(JSON.stringify({
chainId: expectedChain,
basket,
blockNumber: blockNumber.toString(),
totalSupply: totalSupply.toString(),
closed,
assets: tokens.map((token, i) => ({ token, activeReserve: reserves[i].toString() })),
}, null, 2));
Set BASKET_ADDRESS to a verified basket address from the intended deployment, then run:
API_ORIGIN=http://localhost:8787 CHAIN_ID=46630 \
node read-basket.mjs "$BASKET_ADDRESS"
All reads share one block. If the provider cannot serve state at its advertised head, retry with an explicitly chosen available block rather than mixing snapshots. The output uses raw quantities; inspect each token’s decimals separately before formatting. Active reserves exclude pending claims and unsolicited surplus.
For a managed basket, use the separate managed registry and its appropriate interface. Do not silently treat managed membership as native community-router eligibility.
Paginate discovery without losing freshness information
This browser-compatible helper visits the indexed creation pages and deduplicates by address. Its bounded page count makes partial results explicit.
export async function discoverBaskets(apiOrigin, maxPages = 10) {
const found = new Map();
let cursor = null;
let freshness = null;
for (let page = 0; page < maxPages; page++) {
const url = new URL('/v1/baskets', apiOrigin);
url.searchParams.set('limit', '20');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url);
const result = await response.json();
if (!response.ok) throw new Error(result.error ?? 'Discovery failed.');
for (const basket of result.baskets) {
found.set(basket.address.toLowerCase(), basket);
}
freshness = { indexedBlock: result.indexedBlock, status: result.status };
cursor = result.nextCursor;
if (!cursor) break;
}
return { baskets: [...found.values()], freshness, nextCursor: cursor };
}
If nextCursor remains non-null, the result is incomplete. Do not label it a complete portfolio. A reorganization can alter earlier pages; periodically refresh from page one. The single-address route /v1/baskets/:address is useful for retrieving an indexed creation receipt without scanning the collection.
Prepare an ETH buy using application modules
Within frontend/lib, a TypeScript integration can reuse the actual quote validator and execution helper:
import { parseEther, type Address } from 'viem';
import { publicClient, vaultAbi } from './protocol.ts';
import {
quoteNativeTrade, executeNativeTrade,
type NativeTradeQuote, type TradingWallet,
} from './native-trading.ts';
export async function previewEthBuy(
basket: Address, account: Address, ethBudget: string,
) {
const tokens = await publicClient.readContract({
address: basket, abi: vaultAbi, functionName: 'getTokens',
});
return quoteNativeTrade({
basket, account, tokens: [...tokens], side: 'buy', amount: parseEther(ethBudget),
});
}
// Invoke from the user's confirm action after presenting the quote.
export async function confirmEthBuy(
quote: NativeTradeQuote,
wallet: TradingWallet,
assertFormUnchanged: () => void,
onProgress: (message: string) => void,
) {
return executeNativeTrade(quote, wallet, onProgress, assertFormUnchanged);
}
The wallet adapter must implement the source WalletActions contract and check expectedAccount in its signing/submission methods. Supply getSnapshot from the current wallet session so account/network changes cannot leave stale properties. The callback must reject a changed basket, budget, or direction.
The quote helper sends only { basket, side, amount } to the HTTP endpoint. It validates and attaches the local account/token intent. The execution helper reloads settings, checks exact contract previews and expiry, simulates, sends the bounded ETH value, and verifies the receipt event. It also preserves pending-transaction recovery. Present netShares as the buyer’s expected shares and keep gas separate from the ETH budget.
Choose the correct flow
| Desired integration | Source functions/modules |
|---|---|
| Atomic native launch | quoteNativeLaunch, validateNativeQuote, launchNativeBasket in native-launch.ts |
| Proportional ETH buy/sell | quoteNativeTrade, validateNativeTradeQuote, executeNativeTrade in native-trading.ts |
| Full-range liquidity | quoteLiquidity, validateLiquidityQuote, executeLiquidity in liquidity.ts |
| ETH secondary pool trade | quotePoolTrade, validatePoolTradeQuote, executePoolTrade in liquidity.ts |
| ERC-20 secondary pool trade | quoteTokenPoolTrade, validateTokenPoolTradeQuote, executeTokenPoolTrade in token-pool-trading.ts |
| Signed metadata | uploadMetadata in protocol.ts, or the standalone metadata example |
| In-kind issuance/claims | Exported contract ABIs plus previews, approveTokens, and transact |
A proportional buy acquires constituents and mints backed shares; a pool buy transfers shares that already exist. Compare the expected receiver output and input limits, and keep each route’s approval spender, deadline, and receipt event attached to that route.
For direct in-kind integrations, call previewMint or previewRedeem, preserve token order, and submit meaningful maximum/minimum quantities and a finite deadline. Fetch claim IDs from receipts and use independent per-token claims. Never infer transaction completion from a quote response or the appearance of a token in discovery.
Test your integration
Use the existing local integration scripts and route-specific unit tests described in local development. Include account/chain changes, quote expiry during approval, provider failure, receipt timeout, low-decimal token rounding, and an unavailable route in application-level validation. Public-network execution additionally depends on the selected deployment and current venue state.