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

# Direct Contract Cookbook

> Call 1tx contracts directly with viem for buy and sell flows

## 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

```bash theme={null}
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](/contracts/addresses) for the deployed addresses.

## Shared Setup

```typescript theme={null}
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

```typescript theme={null}
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.

```typescript theme={null}
// 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.

```typescript theme={null}
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.

```typescript theme={null}
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));
  }
}
```

<Note>
  Direct contract callers do not need to register an operation with the backend. The relay starts from the bridge event webhook.
</Note>

## Recipe 3: Sell Directly Through the Router

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="Contract Addresses" icon="map-pin" href="/contracts/addresses">
    Router, bridge, receiver, and token addresses on each supported chain
  </Card>

  <Card title="API Cookbook" icon="book-open" href="/integration/api-cookbook">
    API-assisted execution flow with bundle building and relay monitoring
  </Card>
</CardGroup>
