Skip to main content

USDT0 Routes

USDT0 routes use the USDT0 bridge — a LayerZero OFT — to extend Harbor's swap surface across chains where Harbor doesn't have a native deposit vault.

A USDT0 route is composed of two legs:

  • The USDT0 leg: USDT moves between a source and destination chain over LayerZero.
  • The Harbor leg: a swap on the Harbor CLOB.

The legs run in either order, giving two integration directions:

DirectionExampleFirst legSecond leg
USDT0 → HarborARB.USDT → BTC.BTCUSDT0 (ARB.USDT → ETH.USDT)Harbor (ETH.USDT → BTC.BTC)
Harbor → USDT0BTC.BTC → ARB.USDTHarbor (BTC.BTC → ETH.USDT)USDT0 (ETH.USDT → ARB.USDT)

The two directions differ in what the user signs. In USDT0 → Harbor the user broadcasts an OFT send() on the source chain. In Harbor → USDT0 the user broadcasts a standard Harbor deposit and the USDT0 leg is executed by Harbor.

Supported routes

At launch, USDT0 routes are paired with BTC.BTC on the Harbor side.

RouteUSDT0 legSource chain type
ARB.USDT ↔ BTC.BTCARB.USDT ↔ ETH.USDTEVM
TRON.USDT ↔ BTC.BTCTRON.USDT ↔ ETH.USDTTRON
Authoritative source

Supported routes change as new chains and pairs are onboarded. Use the Swap API to discover routes — a successful quote means the route is live.

Architecture

1
Source signature
User signs OFT.send() on the source chain
2
USDT0 bridge
LayerZero delivers the message to the Composer on Ethereum
3
Harbor swap
Composer deposits to the router; CLOB executes the swap
4
Destination
Outbound delivered to the destination address

The user signs one transaction on the source chain. LayerZero delivers the message to the Composer on Ethereum, which calls depositWithExpiry on the Harbor router with a Harbor swap memo. From that point the flow is identical to a standard Harbor swap.

Contracts

ContractRoleAddress
ComposerReceives the LayerZero message on Ethereum and calls depositWithExpiry on the Harbor Router (USDT0 → Harbor)0x3Bd27233114b374BEC0BBBF17F81bEF7bafcE01A
Harbor ExecutorSigns OFT.send() from Ethereum to bridge USDT to the destination chain after the Harbor leg fills (Harbor → USDT0)0x9E3F3F7F3B619D33d0195ea7a51cDe1128D204be
USDT0 OFTsSource and destination OFT contracts on each supported chainUSDT0 deployments

Requesting a quote

Both directions use the standard Swap API GET /swap/v1/quote endpoint with the same parameter set described in Cross-chain Swaps.

sourceAddress is required for USDT0 → Harbor quotes. Without it the response omits inboundTx and the integrator has no way to construct the OFT call.

curl -G "https://quote.harbor.xyz/swap/v1/quote" \
--data-urlencode "fromAsset=ARB.USDT" \
--data-urlencode "toAsset=BTC.BTC" \
--data-urlencode "amount=10000000000" \
--data-urlencode "destination=bc1q6csj53wdrzyzextj2syljvhh0vhynyrx904gp8" \
--data-urlencode "toleranceBps=300" \
--data-urlencode "sourceAddress=0xb38e8c17e38363af6ebdcb3dae12e0243582891d"

The response carries an extra inboundTx field describing the unsigned source-chain OFT call:

{
"expectedAmountOut": "29006947",
"fees": { "asset": "BTC.BTC", "outbound": "684", "liquidity": "23225", "total": "23909", "totalBps": "8" },
"expiry": "1768843381",
"memo": "=:b:bc1q6csj53wdrzyzextj2syljvhh0vhynyrx904gp8:29006947/300",
"inboundTx": {
"chain": "ARB",
"evmTx": {
"to": "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92", // Source OFT
"from": "0xb38e8c17e38363af6ebdcb3dae12e0243582891d",
"data": "0xc7c7f5b3...", // send() calldata
"value": "243000000000000", // LayerZero native fee (wei)
"gas": "0x0",
"functionName": "send",
"functionSignature": "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)"
}
},
"route": {
"legs": [
{ "venue": "usdt0", "fromAsset": "ARB.USDT", "toAsset": "ETH.USDT", "fees": [...] },
{ "venue": "harbor", "fromAsset": "ETH.USDT", "toAsset": "BTC.BTC", "fees": [...] }
]
}
}

For TRON sources, inboundTx.evmTx is replaced with inboundTx.tronTx:

{
"inboundTx": {
"chain": "TRON",
"tronTx": {
"contractAddress": "TFG4wBaDQ8sHWWP1ACeSGnoNR6RRzevLPt",
"ownerAddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"functionSelector": "send((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),(uint256,uint256),address)",
"parameter": "0x...",
"callValue": "0",
"feeLimit": "30000000",
},
},
}

Try it out

Loading quote tool...

Decode the calldata

Before signing, verify what the OFT contract will actually do by decoding the send() calldata returned in the quote. Paste the calldata or the full inboundTx JSON.

Loading decoder...

Broadcasting the source transaction

1. Approve USDT for the OFT (one-time per wallet)

const usdt = new ethers.Contract(USDT_ADDRESS, ERC20_ABI, signer);
await usdt.approve(quote.inboundTx.evmTx.to, ethers.constants.MaxUint256);

2. Broadcast the OFT send()

Use the unsigned tx returned in inboundTx.evmTx verbatim — the calldata, the recipient, and the native value are coupled and must be submitted as one unit.

const tx = await signer.sendTransaction({
to: quote.inboundTx.evmTx.to,
data: quote.inboundTx.evmTx.data,
value: quote.inboundTx.evmTx.value,
});
const receipt = await tx.wait(1);

For TRON, use TronWeb's triggerSmartContract with the matching fields from inboundTx.tronTx.

Do not modify the calldata

The calldata encodes the destination Composer, the LayerZero options, the slippage-protected MinAmountLD, and the Harbor swap memo. If any of these change, the route will no longer settle. If you need to change the amount, destination, or tolerance — request a new quote.

Monitoring

Use the source-chain transaction hash as the swapId for status polling:

curl "https://quote.harbor.xyz/swap/v1/tx/${TX_HASH}"

The status field cycles through queued → processing → complete (or refunded / failed). When the status reaches complete, outboundTxs[0] contains the destination-chain transaction.

Additional cross-chain observability for USDT0 routes:

LayerWhat to watchUsed in
Source receiptConfirmation of your signed txBoth directions
LayerZero Scanhttps://layerzeroscan.com/tx/{txHash} — bridge delivery, typically 30–180sBoth directions
Composer eventComposedDeposit(guid, srcEid, vault, amountLD, expiration, memo) on the destination ComposerUSDT0 → Harbor
Router eventDeposit(to, asset, amount, memo) on the Harbor RouterUSDT0 → Harbor
Swap APIGET /swap/v1/tx/{swapId} — Harbor leg lifecycle and outbound txBoth directions

For USDT0 → Harbor, Composer and Router events fire before the swap appears in the Swap API (the API is keyed off the Router Deposit). Subscribing to those events gives a faster signal than API polling.

For Harbor → USDT0, the LayerZero Scan link is the most accurate signal that the destination delivery is in flight after the Swap API reports complete.