SDK quickstart
The TypeScript SDK wraps every pricer-api call and wagmi write into a clean, typed surface. Install, submit an order, handle errors, and stream live events — everything you need to ship a cross-chain product end-to-end.
Install
One package, zero peer dependencies beyond ethers v6.
pnpm add @t3rn/sdk
Submit an order
Three lines to go cross-chain. The SDK resolves gas, selects a route, and attaches the crosschain proof automatically.
import { t3rn } from '@t3rn/sdk';
// 1. Configure once — reads PRIVATE_KEY from env
const client = t3rn.create({ signer: process.env.PRIVATE_KEY });
// 2. Submit a cross-chain order
const order = await client.submit({
from: 'ethereum', // source chain slug
to: 'base', // destination chain slug
asset: 'USDC', // token symbol
amount: 100, // human-readable amount
});
// 3. Wait for settlement — crosschain proof attaches automatically
const receipt = await order.settled();
console.log('Filled by solver:', receipt.solver);
console.log('On-chain proof:', receipt.proofTx);Quote before you commit
Fetch a firm quote and inspect fees before signing. Quotes are valid for 30 seconds.
// Get a quote before committing
const quote = await client.quote({
from: 'ethereum', to: 'base', asset: 'ETH', amount: 1.25,
});
console.log('Expected output:', quote.amountOut);
console.log('Solver fee:', quote.fee);
console.log('Estimated time:', quote.estimatedMs, 'ms');
// Submit only if the quote looks good
if (quote.amountOut > 1.23) {
const order = await client.submit(quote);
}Subscribe to the genome stream
Receive live cross-chain events. Useful for dashboards, solvers, and analytics.
import { t3rn } from '@t3rn/sdk';
const stream = t3rn.genomeStream({ filter: 'order' });
stream.on('event', (event) => {
console.log('New intent:', event.id, event.asset, event.amount);
});
stream.on('disconnect', () => {
// SDK auto-reconnects with exponential backoff
});
// Pause when tab is hidden to save resources
document.addEventListener('visibilitychange', () => {
document.hidden ? stream.pause() : stream.resume();
});Drive the SDK from a wallet
In a browser app, pass a wagmi WalletClient as the signer. The SDK uses it for approvals, signatures, and the on-chain submit tx.
// In a Next.js / React app, drive the SDK from the user's wallet via wagmi.
import { useAccount, useWalletClient } from 'wagmi';
import { t3rn } from '@t3rn/sdk';
export function BridgeButton() {
const { address } = useAccount();
const { data: wallet } = useWalletClient();
async function send() {
if (!wallet || !address) return;
const client = t3rn.create({ signer: wallet });
const order = await client.submit({
from: 'arbitrum',
to: 'base',
asset: 'USDC',
amount: 100,
recipient: address, // defaults to signer.address
});
const receipt = await order.settled();
console.log('done:', receipt.fillTx);
}
return <button onClick={send}>Bridge 100 USDC →</button>;
}Handle errors that matter
Every recoverable failure throws a typed T3rnError. Switch on err.code instead of parsing messages — codes are stable across SDK versions.
import { t3rn, T3rnError } from '@t3rn/sdk';
try {
const order = await client.submit({
from: 'ethereum', to: 'base', asset: 'USDC', amount: 100,
});
const receipt = await order.settled();
} catch (err) {
if (!(err instanceof T3rnError)) throw err;
switch (err.code) {
case 'INSUFFICIENT_LIQUIDITY':
// No solver can fill at the requested route + amount.
// Try a smaller amount or a different destination chain.
break;
case 'SLIPPAGE_EXCEEDED':
// Market moved between quote and fill. Re-quote and retry.
break;
case 'REFUNDED':
// Order was published but no solver bid. Funds are back on src chain.
console.log('Refund tx:', err.refundTx);
break;
case 'SIGNATURE_REJECTED':
// User declined in their wallet. Surface a friendly retry CTA.
break;
default:
// Network errors, RPC failures, etc. Safe to retry.
}
}Supported chains and assets
Use the chain slug (not the chainId) wherever the SDK takes a from/to. Asset symbols are case-insensitive. Mainnet chains route by default; flip to devnet via the network option below.
// Chains the SDK can route between today.
// Use the slug as 'from' / 'to' on submit() and quote() calls.
import { CHAINS } from '@t3rn/sdk';
console.log(CHAINS);
// {
// ethereum: { id: 1, name: 'Ethereum', type: 'mainnet' },
// arbitrum: { id: 42161, name: 'Arbitrum', type: 'mainnet' },
// base: { id: 8453, name: 'Base', type: 'mainnet' },
// optimism: { id: 10, name: 'OP', type: 'mainnet' },
// polygon: { id: 137, name: 'Polygon', type: 'mainnet' },
// bsc: { id: 56, name: 'BNB', type: 'mainnet' },
// linea: { id: 59144, name: 'Linea', type: 'mainnet' },
// scroll: { id: 534352,name: 'Scroll', type: 'mainnet' },
// blast: { id: 81457, name: 'Blast', type: 'mainnet' },
// monad: { id: 143, name: 'Monad', type: 'testnet' },
// ...
// }
// Supported assets on a specific chain:
const arbitrumAssets = await client.assets('arbitrum');
// ['ETH', 'WETH', 'USDC', 'USDT', 'WBTC', 'TRN']Type reference
The shapes you'll touch most. Full types ship in @t3rn/sdk/types and are re-exported from the root for convenience.
// Minimal type surface — full types ship in @t3rn/sdk/types.
type ChainSlug = 'ethereum' | 'arbitrum' | 'base' | 'optimism' | 'polygon' | string;
type AssetSymbol = 'ETH' | 'WETH' | 'USDC' | 'USDT' | 'WBTC' | 'TRN' | string;
interface Quote {
from: ChainSlug;
to: ChainSlug;
asset: AssetSymbol;
amountIn: number;
amountOut: number;
fee: number; // protocol fee in source asset
estimatedMs: number; // p50 fill latency
expiresAt: number; // unix ms — re-quote after this
}
interface Order {
id: `0x${string}`; // 32-byte order hash
status: 'pending' | 'bid' | 'filled' | 'refunded';
settled(): Promise<Receipt>;
cancel(): Promise<void>;
}
interface Receipt {
orderId: `0x${string}`;
solver: `0x${string}`;
amountOut: number;
fillTx: `0x${string}`;
proofTx: `0x${string}`; // crosschain inclusion proof
filledAt: number;
}Test on devnet first
taifoon-devnet runs the same execution layer with throwaway funds. Submit, fail, retry, and inspect receipts without touching mainnet capital.
// Point the SDK at taifoon-devnet for safe end-to-end testing.
// Devnet uses the same intent flow as mainnet — same code, throwaway funds.
import { t3rn } from '@t3rn/sdk';
const client = t3rn.create({
signer: process.env.DEVNET_KEY,
network: 'devnet', // taifoon-devnet, chainId 36927
// Optional override — defaults to https://api.taifoon.dev
apiUrl: 'https://api.taifoon.dev',
});
// Faucet endpoint — drops 1 test ETH per call, 1 per address per hour.
await fetch('https://api.taifoon.dev/faucet', {
method: 'POST',
body: JSON.stringify({ address: client.address }),
});Chain configs, advanced routing, custom executors, error codes, and changelog.