> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uzolabs.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge USDT out

> Send USDT from BOT Chain to another network by approving the bridge and calling deposit. Rehearse on testnet, then send on mainnet.

This guide shows you how to send USDT from BOT Chain to another network from a script: check the live limits, approve the bridge router, and call `deposit`. You rehearse on testnet first, then run the same script on mainnet.

<Warning>
  **The mainnet run moves real USDT.** A transfer can't be cancelled once the deposit lands.

  * Run the whole guide on testnet before you touch mainnet.
  * On mainnet, send a small amount first, just above the minimum, and confirm it arrives before you send more.
  * Check the recipient address is one you control **on the destination chain**. The bridge doesn't check it for you.
  * Read the limits and fee on the day you send. They live in the contracts and can change.
  * For Tron, use the official app at [bridge.botchain.ai](https://bridge.botchain.ai/). How the router expects a Tron address to be encoded isn't documented, so this script doesn't support it.
</Warning>

## What you'll build

A viem script that sends 11 USDT from BOT Chain to your own address. By default it bridges from BOT Chain testnet to BSC testnet. With `NETWORK=mainnet` it bridges from BOT Chain mainnet to BNB Smart Chain.

11 USDT is just above the 10 USDT minimum read on 2026-10-02 on both networks. The fee is 1 USDT at that size, so 10 USDT arrives.

## Prerequisites

* The `bot-defi` project from [Wrap BOT](/guides/defi/tokens/wrap-bot), with a testnet key in `.env`.
* At least 11 testnet USDT and a little tBOT for gas. Testnet USDT can't be minted by the public, so swap tBOT for it on [BDEX V2](/guides/defi/swaps/bdex-v2).
* For the mainnet run, a separate **mainnet** wallet holding the USDT you want to send plus a little BOT for gas.
* You've read [Limits and fees](/guides/bridge/limits-and-fees) and the [Bridge overview](/guides/bridge/overview).

## Steps

<Steps>
  <Step title="Add the recipient to .env">
    Add these lines to your testnet `.env`. On testnet you can send to your own address, since the same key controls it on BSC testnet.

    ```bash .env theme={"dark"}
    RECIPIENT=0xyour_address_on_the_destination_chain
    AMOUNT=11
    ```
  </Step>

  <Step title="Write the script">
    ```ts bridge-out.ts theme={"dark"}
    import { createPublicClient, createWalletClient, erc20Abi, formatUnits, getAddress, http, parseAbi, parseUnits } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { botChain, botChainTestnet } from "@uzolabs/sdk/chains";
    import { botBridgeAbi, getAddresses, USDT_RESOURCE_ID } from "@uzolabs/sdk/contracts";

    // Testnet by default. NETWORK=mainnet moves real USDT: read the warnings on this page first.
    const isMainnet = process.env.NETWORK === "mainnet";
    const chain = isMainnet ? botChain : botChainTestnet;
    const account = privateKeyToAccount(process.env.BOT_PRIVATE_KEY as `0x${string}`);
    const publicClient = createPublicClient({ chain, transport: http() });
    const walletClient = createWalletClient({ account, chain, transport: http() });
    const { usdt, bridgeRouter } = getAddresses(chain.id);
    const explorer = chain.blockExplorers.default.url;

    // BNB Smart Chain on mainnet, BSC testnet on testnet. For Ethereum use 1n, or 11155111n (Sepolia) on testnet.
    const destinationChainId = isMainnet ? 56n : 97n;
    const recipient = getAddress(process.env.RECIPIENT as string); // Your address on the destination chain
    const amount = parseUnits(process.env.AMOUNT ?? "11", 6); // USDT on BOT Chain has 6 decimals

    // 1. Check the bridge is open and the amount is inside the live limits.
    const bridgeAbi = parseAbi(["function chainAndTokenFee(uint256 destChainId, bytes32 resourceId) view returns (uint256)"]);
    const [paused, minFee, minUsd, maxUsd, bridge] = await Promise.all([
      publicClient.readContract({ address: bridgeRouter, abi: botBridgeAbi, functionName: "getBridgePause" }),
      publicClient.readContract({ address: bridgeRouter, abi: botBridgeAbi, functionName: "getMinFee" }),
      publicClient.readContract({ address: bridgeRouter, abi: botBridgeAbi, functionName: "getMinAmountUsd" }),
      publicClient.readContract({ address: bridgeRouter, abi: botBridgeAbi, functionName: "getMaxAmountUsd" }),
      publicClient.readContract({ address: bridgeRouter, abi: botBridgeAbi, functionName: "Bridge" }),
    ]);
    if (paused) throw new Error("The bridge is paused");
    if (amount < parseUnits(minUsd.toString(), 6) || amount > parseUnits(maxUsd.toString(), 6)) {
      throw new Error(`Amount must be between ${minUsd} and ${maxUsd} USDT`);
    }

    // 2. Estimate what arrives: the fee is a percentage with a minimum.
    const feeBps = await publicClient.readContract({ address: bridge, abi: bridgeAbi, functionName: "chainAndTokenFee", args: [destinationChainId, USDT_RESOURCE_ID] });
    const percentFee = (amount * feeBps) / 10_000n;
    const fee = percentFee > parseUnits(minFee.toString(), 6) ? percentFee : parseUnits(minFee.toString(), 6);
    console.log(`Sending ${formatUnits(amount, 6)} USDT, fee about ${formatUnits(fee, 6)}, arrives about ${formatUnits(amount - fee, 6)}`);

    // 3. Approve the router for exactly this amount.
    const approveHash = await walletClient.writeContract({ address: usdt, abi: erc20Abi, functionName: "approve", args: [bridgeRouter, amount] });
    await publicClient.waitForTransactionReceipt({ hash: approveHash });
    console.log(`Approved: ${explorer}/tx/${approveHash}`);

    // 4. Simulate, then deposit.
    const { request } = await publicClient.simulateContract({
      account,
      address: bridgeRouter,
      abi: botBridgeAbi,
      functionName: "deposit",
      args: [destinationChainId, USDT_RESOURCE_ID, recipient, amount],
    });
    const hash = await walletClient.writeContract(request);
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    console.log(`Deposit ${receipt.status}: ${explorer}/tx/${hash}`);
    ```
  </Step>

  <Step title="Understand each part">
    * **The checks** stop the script before it spends anything if the bridge is paused or the amount is outside the limits. The limits are whole USDT, so they're converted to base units first.
    * **The fee estimate** uses the same rule as [Limits and fees](/guides/bridge/limits-and-fees#work-out-the-fee). The fee comes out of the amount, so the recipient gets `amount - fee`.
    * **The approval** is for exactly `amount`. The router pulls the full amount from your wallet during `deposit`. Avoid unlimited approvals.
    * **`deposit(destinationChainId, resourceId, recipient, amount)`** takes the amount in BOT Chain units (6 decimals) for every destination, including BNB Smart Chain. The function is payable, but you send no BOT with it for USDT.
    * **`simulateContract`** runs the call first, so a revert costs nothing.
    * **`NETWORK`** picks the chain, addresses and destination together, so a testnet run can never reach a mainnet contract.
  </Step>

  <Step title="Run it on testnet">
    ```bash theme={"dark"}
    npx tsx --env-file=.env bridge-out.ts
    ```
  </Step>

  <Step title="Run it on mainnet">
    Only after the testnet run has arrived. Create a separate env file so you never send mainnet transactions by accident:

    ```bash .env.mainnet theme={"dark"}
    BOT_PRIVATE_KEY=0xyour_mainnet_private_key
    RECIPIENT=0xyour_address_on_the_destination_chain
    AMOUNT=11
    NETWORK=mainnet
    ```

    Add `.env.mainnet` to `.gitignore`. Never commit or share it. Then run:

    ```bash theme={"dark"}
    npx tsx --env-file=.env.mainnet bridge-out.ts
    ```
  </Step>
</Steps>

## Verify it worked

Output from a testnet run on 2026-10-02:

```text Output theme={"dark"}
Sending 11 USDT, fee about 1, arrives about 10
Approved: https://scan.bohr.life/tx/0xef5a42ee5e4aa6de4cc8fac9bec367005d651f06778dd7d93d22262c81a8d866
Deposit success: https://scan.bohr.life/tx/0xd3a1267282d34778bf47a875538964aac3aaea549018c6f2293a67c0fdc9513e
```

Then:

1. Copy the deposit transaction hash and follow it with [Track a bridge transfer](/guides/bridge/tracking-transfers). Use `botChain` in that script for a mainnet transfer.
2. When the status is `delivered`, check the recipient's USDT balance on [BscScan testnet](https://testnet.bscscan.com) or [BscScan](https://bscscan.com). The amount shows with 18 decimals there.

In the run above, the tracking script reported `delivered` within a minute, and the recipient's balance on BSC testnet went up by `10000000000000000000`, which is 10 USDT with 18 decimals. In an earlier run the same day, the gateway on BSC testnet released the USDT 38 seconds after the deposit block. Mainnet timing isn't documented.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Amount must be between ...">
    Your `AMOUNT` is outside the live limits. Read them with the script in [Limits and fees](/guides/bridge/limits-and-fees).
  </Accordion>

  <Accordion title="The simulation reverts">
    Check the approval succeeded, your USDT balance covers the amount, and the destination chain ID is supported on the network you're using. Testnet supports 97, 11155111 and 3448148188. Mainnet supports 56, 1 and 728126428.
  </Accordion>

  <Accordion title="The transfer stays pending">
    See [Track a bridge transfer](/guides/bridge/tracking-transfers#troubleshooting). If it doesn't move, contact BOT Chain with the deposit hash.
  </Accordion>

  <Accordion title="I sent to the wrong address">
    A delivered transfer can't be reversed. Always test with a small amount first.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Track a transfer" icon="map-pin" href="/guides/bridge/tracking-transfers">
    Follow your deposit to arrival.
  </Card>

  <Card title="Contract addresses" icon="file-code" href="/reference/contract-addresses#bridge-gateways-on-other-chains">
    Routers, gateways and the resource ID.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.