Skip to main content

Overview

Use this cookbook when you want 1tx to handle:
  • instrument discovery,
  • quote generation,
  • source-chain selection,
  • calldata generation,
  • and cross-chain relay tracking.
This is the recommended integration path for most apps.

Prerequisites

  • an API key for discovery and monitoring endpoints,
  • a bearer token for transaction-building endpoints,
  • a wallet connection in your frontend,
  • a way to send EVM transactions, such as viem or wagmi.

Recipe 1: Build and Execute a Same-Chain Buy

Step 1: Fetch the instrument

const API_BASE = 'https://api.1tx.fi/api/v1';

const instrumentsResponse = await fetch(`${API_BASE}/instruments`, {
  headers: {
    'X-API-Key': apiKey,
  },
});

const instruments = await instrumentsResponse.json();
const instrument = instruments.data.find(
  (item: { symbol: string; chainId: number }) =>
    item.symbol === 'USDC' && item.chainId === 8453,
);

Step 2: Build the transaction bundle

const bundleResponse = await fetch(`${API_BASE}/transactions/buy`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${jwt}`,
    'X-API-Key': apiKey,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    userAddress,
    instrumentId: instrument.instrumentId,
    amountUsdc: '1000.00',
    slippageBps: 50,
  }),
});

const bundle = await bundleResponse.json();

Step 3: Execute the returned transactions in order

import { createWalletClient, custom, parseEther } from 'viem';
import { base } from 'viem/chains';

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

for (const tx of bundle.transactions) {
  const hash = await walletClient.sendTransaction({
    account: userAddress,
    chain: base,
    to: tx.to,
    data: tx.data,
    value: BigInt(tx.value),
  });

  console.log(`sent ${tx.type}`, hash);
}
The bundle may contain either:
  • a single router transaction, or
  • an approval followed by the router transaction.
An approval is only included when current allowance is insufficient. If it is present, it must be mined before the deposit transaction is sent.

Recipe 2: Build and Monitor a Cross-Chain Buy

For cross-chain buys, the source-chain router transaction is still built through POST /transactions/buy, but completion is tracked through the CCTP API.

Step 1: Build the bundle

const bundleResponse = await fetch(`${API_BASE}/transactions/buy`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${jwt}`,
    'X-API-Key': apiKey,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    userAddress,
    instrumentId: '0x0000a4b194d4938ed6aab5bdbac7ca4b622f3639b1bca1b8b9c3271403d3b1b5',
    amountUsdc: '2.20',
  }),
});

const bundle = await bundleResponse.json();

if (!bundle.isCrossChain) {
  throw new Error('expected a cross-chain route');
}

Step 2: Execute the source-chain transactions

let sourceTxHash: `0x${string}` | null = null;

for (const tx of bundle.transactions) {
  const hash = await walletClient.sendTransaction({
    account: userAddress,
    to: tx.to,
    data: tx.data,
    value: BigInt(tx.value),
  });

  sourceTxHash = hash;
}
The final transaction in the bundle is the router buy() transaction. Use that hash as the source transaction hash to monitor.

Step 3: Poll relay status by source tx hash

type RelayStatus = {
  jobId: string;
  status: 'pending' | 'waiting_attestation' | 'redeeming' | 'success' | 'failed';
  sourceTxHash: string;
  sourceChainId: number;
  destinationChainId: number;
  destinationTxHash?: string;
  error?: string;
};

async function waitForCrossChainCompletion(sourceTxHash: string): Promise<RelayStatus> {
  while (true) {
    const response = await fetch(`${API_BASE}/cctp/relay/tx/${sourceTxHash}`, {
      headers: {
        'X-API-Key': apiKey,
      },
    });

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

    if (!response.ok) {
      throw new Error(`relay lookup failed: ${response.status}`);
    }

    const relay = (await response.json()) as RelayStatus;

    if (relay.status === 'success') {
      return relay;
    }

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

    await new Promise((resolve) => setTimeout(resolve, 10000));
  }
}

Step 4: Show destination completion

const relay = await waitForCrossChainCompletion(sourceTxHash!);
console.log('destination tx:', relay.destinationTxHash);
A brief 404 Job not found immediately after source-chain confirmation is expected. The relay job is created asynchronously when the bridge event webhook is ingested.

Recipe 3: Estimate CCTP Fees Before Building

If you want to display cross-chain transfer mode estimates before submitting a buy, use the CCTP fee endpoint.
const feeResponse = await fetch(`${API_BASE}/cctp/fee/6/3`, {
  headers: {
    'X-API-Key': apiKey,
  },
});

const fees = await feeResponse.json();
console.log(fees.fastFeeBps, fees.standardFeeBps);
Use GET /cctp/config to map chain IDs to Circle domains.

Common Pitfalls

  • Missing bearer auth on POST /transactions/buy or POST /transactions/sell
  • Sending transactions out of order
  • Treating an early 404 from /cctp/relay/tx/:sourceTxHash as a permanent failure
  • Reusing an expired bundle after expiresAt
  • Formatting human-readable amounts incorrectly before calling the API

Next Steps

Transactions API

Full request and response shapes for transaction bundles

CCTP API

Relay monitoring and fee estimation endpoints