SDK · @t3rn/sdk

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.

bash
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.

tsx
// 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.

typescript
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.

typescript
// 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.

typescript
// 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.

typescript
// 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 }),
});
More from the build hub

Chain configs, advanced routing, custom executors, error codes, and changelog.

Build hub →