Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.1tx.fi/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Use this cookbook when you want to integrate directly with the deployed contracts instead of relying on transaction-building endpoints. This gives you full control over:
  • token approvals,
  • router call parameters,
  • wallet UX,
  • and transaction submission.
The examples below use viem, which is also the library used in the current frontend stack.

Prerequisites

npm install viem
You will also need:
  • the target router address,
  • the source-chain USDC address,
  • the instrument ID,
  • and a connected wallet client.
See Contract Addresses for the deployed addresses.

Shared Setup

import {
  createPublicClient,
  createWalletClient,
  custom,
  http,
  parseUnits,
} from 'viem';
import { base, arbitrum } from 'viem/chains';

const ERC20_ABI = [
  {
    name: 'approve',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'spender', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ name: '', type: 'bool' }],
  },
] as const;

const ROUTER_ABI = [
  {
    name: 'buy',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'instrumentId', type: 'bytes32' },
      { name: 'amount', type: 'uint256' },
      { name: 'minDepositedAmount', type: 'uint256' },
      { name: 'fastTransfer', type: 'bool' },
      { name: 'maxFee', type: 'uint256' },
      { name: 'referralFeeBps', type: 'uint16' },
      { name: 'referralWallet', type: 'address' },
    ],
    outputs: [{ name: 'depositedAmount', type: 'uint256' }],
  },
  {
    name: 'sell',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'instrumentId', type: 'bytes32' },
      { name: 'yieldTokenAmount', type: 'uint256' },
      { name: 'minOutputAmount', type: 'uint256' },
      { name: 'referralFeeBps', type: 'uint16' },
      { name: 'referralWallet', type: 'address' },
    ],
    outputs: [{ name: 'outputAmount', type: 'uint256' }],
  },
] as const;

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';

const walletClient = createWalletClient({
  chain: base,
  transport: custom(window.ethereum),
});

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

Recipe 1: Same-Chain Buy on Base

const account = '0xYourWalletAddress';
const router = '0xbFdd5bEdC0cB9B8795A93C2a1fB634012C8F99bC';
const usdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const instrumentId = '0x00002105c053a3e1290845e12a3eea14926472ce7f15da324cdf0700056fc04b';

const amount = parseUnits('1000', 6);
const minDepositedAmount = 0n;

const approvalHash = await walletClient.writeContract({
  account,
  chain: base,
  address: usdc,
  abi: ERC20_ABI,
  functionName: 'approve',
  args: [router, amount],
});

await publicClient.waitForTransactionReceipt({ hash: approvalHash });

const buyHash = await walletClient.writeContract({
  account,
  chain: base,
  address: router,
  abi: ROUTER_ABI,
  functionName: 'buy',
  // buy(instrumentId, amount, minDepositedAmount, fastTransfer, maxFee, referralFeeBps, referralWallet)
  args: [instrumentId, amount, minDepositedAmount, false, 0n, 0, ZERO_ADDRESS],
});

console.log('buy tx:', buyHash);
For same-asset deposits such as Base Aave USDC, minDepositedAmount = 0n is usually acceptable because there is no internal token conversion.

Charging a referral fee

Set referralFeeBps (1–500, i.e. 0.01%–5%) and a non-zero referralWallet to route a portion of the user’s USDC to a referrer before the deposit. Fees above 500 bps are rejected by the router.
// Example: 25 bps (0.25%) referral fee for a partner integration
args: [
  instrumentId,
  amount,
  minDepositedAmount,
  false,
  0n,
  25,                            // referralFeeBps
  '0xYourReferralWallet',        // referralWallet
],

Recipe 2: Cross-Chain Buy From Base To Arbitrum

When the instrument lives on another supported chain, the same buy() entrypoint routes into the bridge path automatically.
const account = '0xYourWalletAddress';
const router = '0xbFdd5bEdC0cB9B8795A93C2a1fB634012C8F99bC';
const usdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

// Arbitrum Morpho / Clearstar USDC Reactor instrument
const instrumentId = '0x0000a4b194d4938ed6aab5bdbac7ca4b622f3639b1bca1b8b9c3271403d3b1b5';

const amount = parseUnits('2.20', 6);
const minDepositedAmount = 0n;

const approvalHash = await walletClient.writeContract({
  account,
  chain: base,
  address: usdc,
  abi: ERC20_ABI,
  functionName: 'approve',
  args: [router, amount],
});

await publicClient.waitForTransactionReceipt({ hash: approvalHash });

const sourceTxHash = await walletClient.writeContract({
  account,
  chain: base,
  address: router,
  abi: ROUTER_ABI,
  functionName: 'buy',
  args: [instrumentId, amount, minDepositedAmount, false, 0n, 0, ZERO_ADDRESS],
});

console.log('source tx:', sourceTxHash);

Monitor cross-chain completion

The destination-side redeem is completed asynchronously after attestation. Poll the relay status endpoint using the source tx hash.
async function pollRelay(sourceTxHash: string) {
  while (true) {
    const response = await fetch(
      `https://api.1tx.fi/api/v1/cctp/relay/tx/${sourceTxHash}`,
      {
        headers: {
          'X-API-Key': apiKey,
        },
      },
    );

    if (response.status === 404) {
      await new Promise((resolve) => setTimeout(resolve, 5000));
      continue;
    }

    const relay = await response.json();
    if (relay.status === 'success') {
      console.log('destination tx:', relay.destinationTxHash);
      return relay;
    }

    if (relay.status === 'failed') {
      throw new Error(relay.error || 'relay failed');
    }

    await new Promise((resolve) => setTimeout(resolve, 10000));
  }
}
Direct contract callers do not need to register an operation with the backend. The relay starts from the bridge event webhook.

Recipe 3: Sell Directly Through the Router

const account = '0xYourWalletAddress';
const router = '0xbFdd5bEdC0cB9B8795A93C2a1fB634012C8F99bC';
const yieldToken = '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB';
const instrumentId = '0x00002105c053a3e1290845e12a3eea14926472ce7f15da324cdf0700056fc04b';

const yieldTokenAmount = parseUnits('500', 18);
const minOutputAmount = 0n;

const approvalHash = await walletClient.writeContract({
  account,
  chain: base,
  address: yieldToken,
  abi: ERC20_ABI,
  functionName: 'approve',
  args: [router, yieldTokenAmount],
});

await publicClient.waitForTransactionReceipt({ hash: approvalHash });

const sellHash = await walletClient.writeContract({
  account,
  chain: base,
  address: router,
  abi: ROUTER_ABI,
  functionName: 'sell',
  // sell(instrumentId, yieldTokenAmount, minOutputAmount, referralFeeBps, referralWallet)
  args: [instrumentId, yieldTokenAmount, minOutputAmount, 0, ZERO_ADDRESS],
});

console.log('sell tx:', sellHash);
sell() applies the referral fee on the stable currency output after the lending withdrawal and optional swap back to USDC.

When To Use Direct Contracts vs The Transaction API

Use direct contract calls when you want:
  • fully custom wallet UX,
  • explicit control over approvals and router params,
  • to avoid backend execution helpers for calldata generation.
Use the Transactions API when you want:
  • automatic source-chain selection,
  • quote normalization and slippage handling,
  • ready-to-sign transaction bundles,
  • less integration logic in your client.

Common Pitfalls

  • forgetting that amount for USDC uses 6 decimals
  • not waiting for approval confirmation before sending buy() or sell()
  • assuming cross-chain buys finish in the same transaction as the source-chain buy()
  • hardcoding outdated addresses instead of checking the deployment docs
  • using the wrong chain for the router call when the instrument lives elsewhere

Next Steps

Contract Addresses

Router, bridge, receiver, and token addresses on each supported chain

API Cookbook

API-assisted execution flow with bundle building and relay monitoring