Cross-chain Swaps
Harbor provides a cross-chain swapping product enabling Web3 developers to offer users native asset swaps with strong price execution. The system operates through an Exchange subnet featuring a performant CLOB (Central Limit Order Book) where institutional market makers fulfill orders.
Developers utilize the Swaps API to request quotes and monitor execution stages.
Swap Lifecycle
All swaps follow a consistent four-phase process:
Execute a Swap
For the complete API specification, see the Swap API Reference.
- 1. Get Quote
- 2. Deposit
- 3. Check Status
- 4. Complete
Request a quote from the Swaps API to get pricing and deposit instructions.
Endpoint: GET https://quote.harbor.xyz/swap/v1/quote
| Parameter | Type | Required | Description |
|---|---|---|---|
fromAsset | string | Yes | Source asset (e.g., BTC.BTC) |
toAsset | string | Yes | Destination asset (e.g., ETH.ETH) |
amount | string | Yes | Amount in 1e8 decimals |
destination | string | Yes | Recipient address on destination chain |
toleranceBps | number | No | Slippage tolerance in basis points (default: 100 = 1%) |
Example Request:
curl -X GET "https://quote.harbor.xyz/swap/v1/quote?\
fromAsset=BTC.BTC&\
toAsset=ETH.ETH&\
amount=10000000&\
destination=0x742d35Cc6634C0532925a3b844Bc9e7595f8FbDc&\
toleranceBps=100"
Example Response:
{
"inboundAddress": "bc1q6csj53wdrzyzextj2syljvhh0vhynyrx904gp8",
"inboundConfirmationBlocks": "1",
"inboundConfirmationSeconds": "600",
"fees": {
"asset": "ETH.ETH",
"affiliate": "0",
"outbound": "684",
"liquidity": "23225",
"total": "23909",
"totalBps": "8"
},
"expiry": "1768843381",
"warning": "Do not cache this response. Do not send funds after the expiry.",
"notes": "First output should be to inbound_address, second output should be change back to self, third output should be OP_RETURN, limited to 80 bytes. Do not send below the dust threshold. Do not use exotic spend scripts, locks or address formats.",
"dustThreshold": "10000",
"recommendedMinAmountIn": "0",
"recommendedGasRate": "3",
"gasRateUnits": "satsperbyte",
"expectedAmountOut": "29006947",
"totalSwapSeconds": "620"
}
Construct and broadcast a deposit transaction to the source blockchain.
Carefully review the Deposit Transaction specifications to prevent fund loss.
Bitcoin Deposits
Create a transaction with two outputs:
| VOUT | Purpose | Value |
|---|---|---|
| 0 | Vault deposit | Swap amount |
| 1 | OP_RETURN memo | 0 (data only) |
VOUT 0: <amount> to <inboundAddress>
VOUT 1: OP_RETURN <memo>
Example memo: =:ETH.ETH:0x742d35Cc6634C0532925a3b844Bc9e7595f8FbDc
EVM Deposits
Call the Router contract's depositWithExpiry function:
function depositWithExpiry(
address vault,
address asset,
uint256 amount,
string memory memo,
uint256 expiry
) external payable;
Example (ethers.js):
const router = new ethers.Contract(ROUTER_ADDRESS, ROUTER_ABI, signer);
await router.depositWithExpiry(
quote.inboundAddress, // vault
ETH_ADDRESS, // asset (0x0 for native)
ethers.parseEther("1"), // amount
quote.memo, // memo from quote
quote.expiry, // expiration timestamp
);
See Router Contract for complete EVM integration details.
Poll the status endpoint to track swap progress.
Endpoint: GET https://quote.harbor.xyz/swap/v1/tx/{swapId}
| Status | Description |
|---|---|
pending | Deposit observed, awaiting confirmation |
processing | Order being executed on Exchange subnet |
completed | Swap successful, outbound transaction sent |
refunded | Swap failed, funds returned to sender |
Example Request:
curl -X GET "https://quote.harbor.xyz/swap/v1/tx/5BC3C1506F87DC1B4A0C3CFFBB05BB3A4DEF3EDA93626AC55231EC9A4A72B748"
Example Response:
{
"swapId": "5BC3C1506F87DC1B4A0C3CFFBB05BB3A4DEF3EDA93626AC55231EC9A4A72B748",
"status": "processing",
"memo": "=:b:bc1qy6a8nmnw75fks66rkyvwwa9fd62lnmrz3m87y4:342813/300:sk/t:5/50",
"fromAsset": "ETH.ETH",
"fromAmount": "10000000",
"toAsset": "BTC.BTC",
"fees": [
{
"type": "liquidity",
"bps": "8",
"asset": "BTC.BTC",
"amount": "276",
"feeAcountType": "fee",
"feeAccountLabel": "fee"
}
],
"inboundTx": {
"id": "5BC3C1506F87DC1B4A0C3CFFBB05BB3A4DEF3EDA93626AC55231EC9A4A72B748",
"chain": "ETH",
"fromAddress": "0x2e561008bd76f4a99c6739b9935efb849792af35",
"toAddress": "0x00dE4d8658ab249627286fCf9f920B3078aEa4fe",
"asset": "ETH.ETH",
"amount": "10000000",
"gasAsset": "ETH.ETH",
"gasAmount": "241",
"memo": "=:b:bc1qy6a8nmnw75fks66rkyvwwa9fd62lnmrz3m87y4:342813/300:sk/t:5/50"
},
"swapOrders": [
{
"id": "479",
"swapId": "332",
"symbol": "eth.eth-btc.btc",
"side": "sell",
"qty": "10000000",
"price": "3453000",
"status": "filled",
"type": "limit",
"tif": "aon",
"filledQty": "10000000",
"priceQty": "345400",
"avgPrice": "0",
"filledAt": "2026-01-19 18:39:29.956493 +0000 UTC",
"createdAt": "2026-01-19 18:39:29.956285 +0000 UTC",
"updatedAt": "2026-01-19 18:39:29.956493 +0000 UTC"
}
]
}
Polling Example:
async function waitForCompletion(swapId, maxAttempts = 60) {
for (let i = 0; i < maxAttempts; i++) {
const res = await fetch(`https://quote.harbor.xyz/swap/v1/tx/${swapId}`);
const data = await res.json();
if (data.status === "completed" || data.status === "refunded") {
return data;
}
await new Promise((r) => setTimeout(r, 10000)); // Wait 10s
}
throw new Error("Swap timeout");
}
When status is completed, the response includes outbound transaction details:
{
"swapId": "5BC3C1506F87DC1B4A0C3CFFBB05BB3A4DEF3EDA93626AC55231EC9A4A72B748",
"status": "completed",
"memo": "=:b:bc1qy6a8nmnw75fks66rkyvwwa9fd62lnmrz3m87y4:342813/300:sk/t:5/50",
"fromAsset": "ETH.ETH",
"fromAmount": "10000000",
"toAsset": "BTC.BTC",
"fees": [...],
"inboundTx": {...},
"swapOrders": [...],
"outboundTx": {
"id": "A1B2C3D4E5F6...",
"chain": "BTC",
"toAddress": "bc1qy6a8nmnw75fks66rkyvwwa9fd62lnmrz3m87y4",
"asset": "BTC.BTC",
"amount": "343228"
}
}
The outboundTx object contains the destination chain transaction. Use the id to verify receipt on the destination blockchain.
Try It Out
Use this interactive tool to test the quote API:
Swap Types
Harbor supports multiple order execution strategies to accommodate different swap requirements:
Single-book AON Walk
The full swap amount is placed as a single All-or-Nothing order starting at quote price, walking down by tolerance_bps periodically until filled. AON orders must be filled completely in a single match—no partial fills allowed.
Multi-book AON Walk
Coming SoonThe full swap amount is placed as a multi-book All-or-Nothing order starting at quote price, walking down towards tolerance_bps periodically. AON ensures complete fills while multi-book routing improves execution across multiple liquidity sources.
TWAP
Coming SoonTime-Weighted Average Price execution. The swap is broken into multiple smaller orders spread over a user-defined time period, enabling larger swaps with reduced market impact.
Next Steps
- Review Deposit Transactions for transaction formatting
- Understand Refunds behavior
- Learn about the Router Contract for EVM chains