Bridge tokens to another chain with the DZap SDK, from EVM, Solana, or Bitcoin.
Move tokens from one chain to another. It works like a swap with toChain set to a different chain: DZap finds a route and builds the transaction on the source chain, and you sign it, send it, and track it until the tokens arrive. Pick EVM, Solana, or Bitcoin in any tab, and the rest of the page switches with it.
This example bridges 100 USDC from Arbitrum to your own address on Base, signing with walletClient, a viem wallet client built from the private key in PRIVATE_KEY. Amounts are strings in the token’s smallest unit, and no API key is needed.
import { DZapClient, Services, ApprovalModes, TxnStatus, type TradeStatusResponse } from '@dzapio/sdk';import { createWalletClient, http } from 'viem';import { arbitrum } from 'viem/chains';import { privateKeyToAccount } from 'viem/accounts';const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() });const dzap = DZapClient.getInstance();const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';const AMOUNT = '100000000'; // 100 USDC (6 decimals)
This example bridges 100 USDC from Solana to your wallet on Base, set in recipient. It signs with the keypair in SOLANA_SECRET_KEY using @solana/web3.js, which you install alongside the SDK. Amounts are strings in the token’s smallest unit, and no API key is needed.
import { DZapClient, TxnStatus, type TradeStatusResponse } from '@dzapio/sdk';import { Keypair, VersionedTransaction } from '@solana/web3.js';const dzap = DZapClient.getInstance();const keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.SOLANA_SECRET_KEY!)));const account = keypair.publicKey.toBase58() as `0x${string}`; // SDK types expect hex; base58 worksconst recipient = '0xYourBaseAddress'; // your wallet on Baseconst USDC_SOL = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';const AMOUNT = '100000000'; // 100 USDC (6 decimals)
This example bridges 0.001 BTC to USDC on Base. Set account and publicKey to your Bitcoin address and its public key, and recipient to your wallet on Base; your Bitcoin wallet signs the transaction. Amounts are strings in satoshis, and no API key is needed.
import { DZapClient, TxnStatus, type TradeStatusResponse } from '@dzapio/sdk';const dzap = DZapClient.getInstance();const account = 'bc1q...' as `0x${string}`; // your Bitcoin address; SDK types expect hexconst publicKey = '02...'; // compressed pubkey for `account`const recipient = '0xYourBaseAddress'; // your wallet on Baseconst BTC = '0x0000000000000000000000000000000000000000'; // native BTCconst USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';const AMOUNT = '100000'; // 0.001 BTC (satoshis)
getTradeQuotes asks the bridge providers DZap supports for a price; a toChain different from fromChain makes it a bridge. pair.quoteRates lists every route, recommendedSource names DZap’s pick, and each route’s duration is its estimated time in seconds. slippage: 1 accepts up to 1% less than quoted.
The DZap router on Arbitrum needs permission to move your USDC. getAllowance reads the current allowance, and if it’s too low, approve() approves exactly the bridge amount. Nothing needs approving on Base.
trade() builds the transaction for the recommended route, simulates it, then signs and sends it on Arbitrum. If the simulation fails, nothing is sent and result says why. recipient receives the USDC on Base, and refundee gets it back on Arbitrum if the bridge refunds.
The USDC reaches Base only after the bridge finishes, so poll getTradeTxnStatus with the Arbitrum transaction hash until the status leaves PENDING; COMPLETED means it arrived. DZap answers 404 until it sees the transaction, and 429 once the keyless limit of 5 requests a minute is used up, so the catch skips only those two and rethrows anything else. The loop gives up after 30 minutes, and the check after it throws unless the status is COMPLETED.
let status: TradeStatusResponse | undefined;for (let i = 0; i < 120; i++) { // gives up after 30 minutes await new Promise((r) => setTimeout(r, 15_000)); try { status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 42161 }); } catch (e: any) { if (![404, 429].includes(e.response?.status)) throw e; } if (status && status.status !== 'PENDING') break;}if (status?.status !== 'COMPLETED') throw new Error(`Not completed: ${status?.status ?? 'no status'}`);console.log(status.status); // 'COMPLETED'
1
Quote
getTradeQuotes asks the bridge providers DZap supports for a price from Solana (chain ID 7565164 in DZap) to Base. pair.quoteRates lists every route, recommendedSource names DZap’s pick, and each route’s duration is its estimated time in seconds. slippage: 1 accepts up to 1% less than quoted.
No approval on Solana. buildTradeTxn builds the transaction for the recommended route and simulates it, throwing if it would fail. It returns the unsigned transaction, base64-encoded in data, and a txId for sending it. recipient receives the USDC on Base, and refundee gets it back on Solana if the bridge refunds.
Sign the transaction with your keypair and hand it to broadcastTradeTx, which submits it and returns its txnHash. Do it right away: the transaction expires about a minute after it’s built.
The USDC reaches Base only after the bridge finishes, so poll getTradeTxnStatus with the txnHash until the status leaves PENDING; COMPLETED means it arrived. DZap answers 404 until it sees the transaction, and 429 once the keyless limit of 5 requests a minute is used up, so the catch skips only those two and rethrows anything else. The loop gives up after 30 minutes, and the check after it throws unless the status is COMPLETED.
let status: TradeStatusResponse | undefined;for (let i = 0; i < 120; i++) { // gives up after 30 minutes await new Promise((r) => setTimeout(r, 15_000)); try { status = await dzap.getTradeTxnStatus({ txHash: result.txnHash, chainId: 7565164 }); } catch (e: any) { if (![404, 429].includes(e.response?.status)) throw e; } if (status && status.status !== 'PENDING') break;}if (status?.status !== 'COMPLETED') throw new Error(`Not completed: ${status?.status ?? 'no status'}`);console.log(status.status); // 'COMPLETED'
1
Quote
getTradeQuotes asks the bridge providers DZap supports for a price from Bitcoin (chain ID 1000 in DZap) to Base. pair.quoteRates lists every route, recommendedSource names DZap’s pick, and each route’s duration is its estimated time in seconds; routes from Bitcoin take 10 minutes or more. slippage: 1 accepts up to 1% less than quoted.
No approval on Bitcoin. buildTradeTxn builds the transaction for the recommended route and requires your publicKey. It returns an unsigned PSBT (partially signed Bitcoin transaction), base64-encoded in data, and a txId for sending it. recipient receives the USDC on Base, and refundee gets the BTC back if the bridge refunds.
Sign and finalize the PSBT with your Bitcoin wallet, then pass the transaction hex to broadcastTradeTx, which submits it and returns its txnHash.
// sign the base64 PSBT in built.data with your Bitcoin wallet, then finalize it into signedTxHexconst result = await dzap.broadcastTradeTx({ txId: built.txId, chainId: 1000, txData: signedTxHex, // your signed Bitcoin transaction, as hex});if (result.status !== TxnStatus.success) throw new Error(result.message);
4
Status
The USDC reaches Base only after the bridge finishes, so poll getTradeTxnStatus with the txnHash until the status leaves PENDING; COMPLETED means it arrived. DZap answers 404 until it sees the transaction, and 429 once the keyless limit of 5 requests a minute is used up, so the catch skips only those two and rethrows anything else. The loop gives up after 30 minutes, and the check after it throws unless the status is COMPLETED.
let status: TradeStatusResponse | undefined;for (let i = 0; i < 120; i++) { // gives up after 30 minutes await new Promise((r) => setTimeout(r, 15_000)); try { status = await dzap.getTradeTxnStatus({ txHash: result.txnHash, chainId: 1000 }); } catch (e: any) { if (![404, 429].includes(e.response?.status)) throw e; } if (status && status.status !== 'PENDING') break;}if (status?.status !== 'COMPLETED') throw new Error(`Not completed: ${status?.status ?? 'no status'}`);console.log(status.status); // 'COMPLETED'
Approve the DZap router for the USDC first, then sign and send the transaction from the build response with your own wallet. Full reference: Quote, Build Tx, Status.
# sign the base64 PSBT from the build response, then submit the finalized transaction as hexcurl -X POST https://api.dzap.io/v1/broadcast \ -H "Content-Type: application/json" \ -d '{ "txId": "<txId from build>", "chainId": 1000, "txData": "<signed transaction hex>" }'
publicKey is required on the build. Sign the base64 PSBT in data, then broadcast the finalized transaction as hex. Full reference: Quote, Build Tx, Broadcast, Status.
No route: pair.quoteRates is empty; pair.unavailableRoutes says why each provider declined.
The build fails: trade() returns an error and buildTradeTxn throws. The error’s action says what to fix; TRY_ANOTHER_ROUTE means building with another route from pair.quoteRates.
expired from broadcastTradeTx on Solana: build and sign again right away.
Not completed: no status: DZap never returned a status, usually because the transaction failed on-chain. Check the hash in a block explorer.
HTTP 429: without an API key, the trade API allows 5 requests a minute in total, shared by every endpoint, and retryAfter says how many seconds to wait. The status loop skips 429s and polls again.