toChain to a different chain. Settlement is asynchronous, so the status step polls. Pick a chain below; the tabs stay in sync across Setup, Steps, End-to-end, and API usage.
Setup
- EVM
- Solana
- Bitcoin
Bridge USDC from Arbitrum to Base.
import { DZapClient, Services, ApprovalModes, TxnStatus } 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 = '50000000'; // 50 USDC (6 decimals)
Bridge USDC from Solana to Base.
toChain is an EVM chain and recipient is an EVM address. Needs @solana/web3.js alongside the SDK.import { DZapClient } from '@dzapio/sdk';
import { Connection, VersionedTransaction } from '@solana/web3.js';
const dzap = DZapClient.getInstance();
const connection = new Connection('https://api.mainnet-beta.solana.com');
// your Solana wallet adapter (wallet-standard, Keypair, etc.)
const account = wallet.publicKey.toBase58();
const recipient = '0x46b24b781f9Ac1344e594A313671e5CDb1459646'; // EVM address on Base
const USDC_SOL = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const AMOUNT = '50000000'; // 50 USDC (6 decimals)
Bridge native BTC to an EVM chain. Bitcoin is bridge-only (no same-chain swap) and UTXO-based, so there are no approvals: the build returns a PSBT you sign, then broadcast.
import { DZapClient } from '@dzapio/sdk';
const dzap = DZapClient.getInstance();
const account = 'bc1q...';
const publicKey = '02...'; // compressed pubkey for `account`
const recipient = '0x46b24b781f9Ac1344e594A313671e5CDb1459646'; // EVM address on Base
const BTC = '<native BTC address, from GET /v1/chains>';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const AMOUNT = '100000'; // 0.001 BTC (satoshis)
Steps
- EVM
- Solana
- Bitcoin
1
Quote
Quote across bridge providers. Setting
toChain to a different chain than fromChain is what makes this a bridge.const quotes = await dzap.getTradeQuotes({
fromChain: 42161,
account: account.address,
data: [{
srcToken: USDC_ARB,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453, // Base, different chain = bridge
slippage: 1,
}],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
const best = pair.quoteRates?.[source];
console.log(`Best: ${best?.providerDetails.name}, ~${best?.duration}s`);
To bias toward speed or a specific bridge, use
filter: 'fastest' or provider allow/deny lists, see advanced quote fields.2
Approve
Approval is on the source chain only: the bridge handles disbursement on the destination side.
AutoPermit keeps it gasless where the token allows.const { data } = await dzap.getAllowance({
chainId: 42161,
sender: account.address,
tokens: [{ address: USDC_ARB, amount: AMOUNT }],
service: Services.trade,
mode: ApprovalModes.AutoPermit,
});
const entry = data[USDC_ARB];
if (entry.type !== 'eip2612' && entry.allowance < BigInt(AMOUNT)) {
await dzap.approve({
chainId: 42161,
signer: walletClient,
sender: account.address,
tokens: [{ address: USDC_ARB, amount: AMOUNT }],
service: Services.trade,
mode: ApprovalModes.AutoPermit,
});
}
Other approval flows (Permit2, EIP-2612, gasless permits) are in Check allowance and Approval mechanisms.
3
Build & send
trade() builds the bridge transaction, then signs and sends it in one call. refundee and recipient matter more here than on a same-chain swap.const request = {
fromChain: 42161,
sender: account.address,
refundee: account.address, // funds return here if the destination leg fails
gasless: false,
data: [{
srcToken: USDC_ARB,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453,
protocol: source,
recipient: account.address, // can be a different address on Base
slippage: 1,
}],
};
const result = await dzap.trade({ request, signer: walletClient });
console.log(`Sent: ${result.txnHash}`);
trade() builds and sends in one call. Call buildTradeTxn first only to preview the transaction or reuse it via txnData. Use a refundee you control on the source chain, and set recipient to the address that should receive funds on the destination. Full request shape: Execute trade.4
Status
Cross-chain settlement is asynchronous: poll
getTradeTxnStatus until it reaches a terminal state.const status = await dzap.getTradeTxnStatus({
txHash: result.txnHash!,
chainId: 42161, // source chain, as a number
});
console.log(status.status); // PENDING → COMPLETED (or FAILED / REFUNDED)
Terminal states are
COMPLETED, FAILED, PARTIAL, and REFUNDED. See Track trade status for a ready-made polling helper.1
Quote
Same call as EVM, with Solana’s chain ID (
7565164); toChain stays the EVM destination. The response is keyed by pair: read recommendedSource (or bestReturnSource).const quotes = await dzap.getTradeQuotes({
fromChain: 7565164,
account,
data: [{
srcToken: USDC_SOL,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453, // Base, different chain = bridge
slippage: 1,
}],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
const best = pair.quoteRates?.[source];
console.log(`Best: ${best?.providerDetails.name}, ~${best?.duration}s`);
2
Build
No approval on Solana. Build returns a base64 Solana transaction in
transaction.data.const built = await dzap.buildTradeTxn({
fromChain: 7565164,
sender: account,
refundee: account, // funds return here on Solana if the destination leg fails
gasless: false,
data: [{
srcToken: USDC_SOL,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453,
protocol: source,
recipient,
slippage: 1,
}],
});
3
Sign & send
Deserialize the built transaction, sign it with your Solana wallet, then hand it back to DZap to broadcast.
broadcastTradeTx returns the on-chain txnHash and routes Jito trades through the Jito block engine for you.const tx = VersionedTransaction.deserialize(Buffer.from(built.transaction.data, 'base64'));
const signed = await wallet.signTransaction(tx); // your Solana wallet adapter (wallet-standard, Keypair, etc.)
const result = await dzap.broadcastTradeTx({
txId: built.txId,
chainId: 7565164,
txData: Buffer.from(signed.serialize()).toString('base64'),
});
Prefer to submit it yourself? Send the signed transaction over your own RPC instead, then track by that signature:
const sig = await connection.sendRawTransaction(signed.serialize()); await connection.confirmTransaction(sig, 'confirmed');. See Broadcast.4
Status
Cross-chain settlement is async, poll to a terminal state.
const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 7565164 });
console.log(status.status); // PENDING to COMPLETED (or FAILED / REFUNDED)
1
Quote
Bitcoin’s chain ID is
1000 and amounts are in satoshis. The response is keyed by pair: read recommendedSource (or bestReturnSource).const quotes = await dzap.getTradeQuotes({
fromChain: 1000,
account,
data: [{
srcToken: BTC,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453, // Base, different chain = bridge
slippage: 1,
}],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
const best = pair.quoteRates?.[source];
console.log(`Best: ${best?.providerDetails.name}, ~${best?.duration}s`);
2
Build
Pass
publicKey (Bitcoin only). The response carries a PSBT in transaction: { inputs, outputs, feeRate }.const built = await dzap.buildTradeTxn({
fromChain: 1000,
sender: account,
refundee: account,
gasless: false,
publicKey, // required for Bitcoin
data: [{
srcToken: BTC,
destToken: USDC_BASE,
amount: AMOUNT,
toChain: 8453,
protocol: source,
recipient,
slippage: 1,
}],
});
3
Sign & broadcast
Assemble and sign the PSBT from
built.transaction with your Bitcoin wallet (signPsbt, bitcoinjs-lib, etc.), then hand the signed transaction to DZap to broadcast.// built.transaction holds the PSBT ({ inputs, outputs, feeRate }); sign it with your BTC wallet to get the raw signed tx
const result = await dzap.broadcastTradeTx({
txId: built.txId,
chainId: 1000,
txData: signedTxHex, // your signed Bitcoin transaction
});
4
Status
Poll with the returned
txnHash until it reaches a terminal state (cross-chain settlement is asynchronous).const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 1000 });
console.log(status.status);
End-to-end
- EVM
- Solana
- Bitcoin
import { DZapClient, Services, ApprovalModes, TxnStatus } 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 = '50000000'; // 50 USDC
// 1. Quote
const quotes = await dzap.getTradeQuotes({
fromChain: 42161,
account: account.address,
data: [{ srcToken: USDC_ARB, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, slippage: 1 }],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
// 2. Approve (source chain only)
const { data } = await dzap.getAllowance({
chainId: 42161,
sender: account.address,
tokens: [{ address: USDC_ARB, amount: AMOUNT }],
service: Services.trade,
mode: ApprovalModes.AutoPermit,
});
const entry = data[USDC_ARB];
if (entry.type !== 'eip2612' && entry.allowance < BigInt(AMOUNT)) {
await dzap.approve({
chainId: 42161,
signer: walletClient,
sender: account.address,
tokens: [{ address: USDC_ARB, amount: AMOUNT }],
service: Services.trade,
mode: ApprovalModes.AutoPermit,
});
}
// 3. Build + send
const request = {
fromChain: 42161,
sender: account.address,
refundee: account.address,
gasless: false,
data: [{ srcToken: USDC_ARB, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, protocol: source, recipient: account.address, slippage: 1 }],
};
const result = await dzap.trade({ request, signer: walletClient });
if (result.status !== TxnStatus.success) throw new Error(result.errorMsg ?? 'bridge failed');
// 4. Poll until settled (cross-chain is async)
for (let i = 0; i < 60; i++) {
const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 42161 });
if (['COMPLETED', 'PARTIAL', 'FAILED', 'REFUNDED'].includes(status.status)) {
console.log('settled:', status.status);
break;
}
await new Promise((r) => setTimeout(r, 5000));
}
import { DZapClient } from '@dzapio/sdk';
import { Connection, VersionedTransaction } from '@solana/web3.js';
const dzap = DZapClient.getInstance();
const connection = new Connection('https://api.mainnet-beta.solana.com');
// your Solana wallet adapter (wallet-standard, Keypair, etc.)
const account = wallet.publicKey.toBase58();
const recipient = '0x46b24b781f9Ac1344e594A313671e5CDb1459646'; // EVM address on Base
const USDC_SOL = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const AMOUNT = '50000000'; // 50 USDC
// 1. Quote
const quotes = await dzap.getTradeQuotes({
fromChain: 7565164,
account,
data: [{ srcToken: USDC_SOL, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, slippage: 1 }],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
// 2. Build (no approval on Solana)
const built = await dzap.buildTradeTxn({
fromChain: 7565164,
sender: account,
refundee: account,
gasless: false,
data: [{ srcToken: USDC_SOL, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, protocol: source, recipient, slippage: 1 }],
});
// 3. Sign + broadcast via DZap
const tx = VersionedTransaction.deserialize(Buffer.from(built.transaction.data, 'base64'));
const signed = await wallet.signTransaction(tx);
const result = await dzap.broadcastTradeTx({
txId: built.txId,
chainId: 7565164,
txData: Buffer.from(signed.serialize()).toString('base64'),
});
// 4. Poll until settled (cross-chain is async)
for (let i = 0; i < 60; i++) {
const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 7565164 });
if (['COMPLETED', 'PARTIAL', 'FAILED', 'REFUNDED'].includes(status.status)) {
console.log('settled:', status.status);
break;
}
await new Promise((r) => setTimeout(r, 5000));
}
import { DZapClient } from '@dzapio/sdk';
const dzap = DZapClient.getInstance();
const account = 'bc1q...';
const publicKey = '02...'; // compressed pubkey for `account`
const recipient = '0x46b24b781f9Ac1344e594A313671e5CDb1459646'; // EVM address on Base
const BTC = '<native BTC address, from GET /v1/chains>';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const AMOUNT = '100000'; // 0.001 BTC (satoshis)
// 1. Quote
const quotes = await dzap.getTradeQuotes({
fromChain: 1000,
account,
data: [{ srcToken: BTC, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, slippage: 1 }],
});
const pair = quotes[Object.keys(quotes)[0]];
const source = pair.recommendedSource ?? pair.bestReturnSource;
// 2. Build (pass publicKey; response carries a PSBT)
const built = await dzap.buildTradeTxn({
fromChain: 1000,
sender: account,
refundee: account,
gasless: false,
publicKey,
data: [{ srcToken: BTC, destToken: USDC_BASE, amount: AMOUNT, toChain: 8453, protocol: source, recipient, slippage: 1 }],
});
// 3. Sign the PSBT (built.transaction) with your BTC wallet, then broadcast the raw signed tx
const result = await dzap.broadcastTradeTx({ txId: built.txId, chainId: 1000, txData: signedTxHex });
// 4. Poll until settled (cross-chain is async)
for (let i = 0; i < 60; i++) {
const status = await dzap.getTradeTxnStatus({ txHash: result.txnHash!, chainId: 1000 });
if (['COMPLETED', 'PARTIAL', 'FAILED', 'REFUNDED'].includes(status.status)) {
console.log('settled:', status.status);
break;
}
await new Promise((r) => setTimeout(r, 5000));
}
API usage
The same flow over REST. Non-EVM chains build a chain-specific transaction you sign locally, then submit via Broadcast.- EVM
- Solana
- Bitcoin
curl -X POST https://api.dzap.io/v1/quotes \
-H "Content-Type: application/json" \
-d '{
"fromChain": 42161,
"data": [{
"amount": "50000000",
"srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"slippage": 1
}],
"account": "0xUser"
}'
curl -X POST https://api.dzap.io/v1/buildTx \
-H "Content-Type: application/json" \
-d '{
"sender": "0xUser",
"refundee": "0xUser",
"fromChain": 42161,
"gasless": false,
"data": [{
"amount": "50000000",
"srcToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"protocol": "<recommendedSource from quote>",
"recipient": "0xUser",
"slippage": 1
}]
}'
curl "https://api.dzap.io/v1/status?txHash=0xabc...&chainId=42161"
transaction from the build response with your own signer. Full reference: Quote, Build Tx, Status.curl -X POST https://api.dzap.io/v1/quotes \
-H "Content-Type: application/json" \
-d '{
"fromChain": 7565164,
"data": [{
"amount": "50000000",
"srcToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"slippage": 1
}],
"account": "8AsEhwyveydfzqnuUTjoCYjpxydpKPUXLqgyKdTPWV8v"
}'
curl -X POST https://api.dzap.io/v1/buildTx \
-H "Content-Type: application/json" \
-d '{
"sender": "8AsEhwyveydfzqnuUTjoCYjpxydpKPUXLqgyKdTPWV8v",
"refundee": "8AsEhwyveydfzqnuUTjoCYjpxydpKPUXLqgyKdTPWV8v",
"fromChain": 7565164,
"gasless": false,
"data": [{
"amount": "50000000",
"srcToken": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"protocol": "<recommendedSource from quote>",
"recipient": "0x46b24b781f9Ac1344e594A313671e5CDb1459646",
"slippage": 1
}]
}'
# sign the base64 transaction from the build response, then submit it
curl -X POST https://api.dzap.io/v1/broadcast \
-H "Content-Type: application/json" \
-d '{
"txId": "<txId from build>",
"chainId": 7565164,
"txData": "<signed transaction>"
}'
curl "https://api.dzap.io/v1/status?txHash=<signature>&chainId=7565164"
curl -X POST https://api.dzap.io/v1/quotes \
-H "Content-Type: application/json" \
-d '{
"fromChain": 1000,
"data": [{
"amount": "100000",
"srcToken": "<native BTC, from GET /v1/chains>",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"slippage": 1
}],
"account": "bc1q..."
}'
curl -X POST https://api.dzap.io/v1/buildTx \
-H "Content-Type: application/json" \
-d '{
"sender": "bc1q...",
"refundee": "bc1q...",
"fromChain": 1000,
"gasless": false,
"publicKey": "02...",
"data": [{
"amount": "100000",
"srcToken": "<native BTC, from GET /v1/chains>",
"destToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"toChain": 8453,
"protocol": "<recommendedSource from quote>",
"recipient": "0x46b24b781f9Ac1344e594A313671e5CDb1459646",
"slippage": 1
}]
}'
# build returns a PSBT; sign it with your BTC wallet, then submit the raw signed tx
curl -X POST https://api.dzap.io/v1/broadcast \
-H "Content-Type: application/json" \
-d '{
"txId": "<txId from build>",
"chainId": 1000,
"txData": "<signed transaction>"
}'
curl "https://api.dzap.io/v1/status?txHash=<txnHash>&chainId=1000"
publicKey on the build; the response carries a PSBT to sign before broadcasting. Full reference: Quote, Build Tx, Broadcast, Status.Bridge quirks
refundeematters. If the destination leg fails, funds return torefundeeon the source chain. Use a wallet you control.recipientcan differ fromsender. Useful when paying on someone else’s behalf, they receive on the destination chain.- Settlement time varies. Across is seconds; CCTP is ~13 minutes; others vary by route. Read
best.durationfrom the quote. - Approval is source-chain only. No extra approval on the destination side; the bridge contract handles disbursement.