Reference
Examples
End-to-end examples of common bridging flows
End-to-end examples of common bridging flows using the Superbridge API with viem.
Bridge ETH from Ethereum to Base
This example fetches a route, signs and sends the initiating transaction, then polls for completion.
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet } from 'viem/chains';
const API_BASE = 'https://api.superbridge.app';
const API_KEY = 'your-api-key';
const account = privateKeyToAccount('0x...');
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
// 1. Get a route
const routesResponse = await fetch(`${API_BASE}/v1/routes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
fromChainKey: 'eth',
toChainKey: 'base',
fromTokenAddress: '0x0000000000000000000000000000000000000000',
toTokenAddress: '0x0000000000000000000000000000000000000000',
amount: parseUnits('0.01', 18).toString(),
sender: account.address,
recipient: account.address,
}),
});
const { results } = await routesResponse.json();
// 2. Pick the first quote (check for initiatingTransaction to distinguish from errors)
const quote = results.find((r) => r.result.initiatingTransaction)?.result;
if (!quote) throw new Error('No quote available');
// 3. Send the initiating transaction
const hash = await walletClient.sendTransaction({
to: quote.initiatingTransaction.to,
data: quote.initiatingTransaction.data,
value: BigInt(quote.initiatingTransaction.value),
});
console.log('Transaction sent:', hash);
// 4. Wait for the transaction to be confirmed
await publicClient.waitForTransactionReceipt({ hash });
// 5. Poll activity until the bridge is complete
async function pollUntilComplete() {
while (true) {
const activityResponse = await fetch(
`${API_BASE}/v1/activity?evmAddress=${account.address}`,
{ headers: { 'x-api-key': API_KEY } },
);
const activities = await activityResponse.json();
// Find our bridge by matching the initiating tx hash
const bridge = activities.find((a) =>
a.steps.some(
(s) =>
s.type === 'transaction' &&
s.transactionType === 'done' &&
s.confirmation?.transactionHash === hash,
),
);
if (!bridge) {
// Transaction not indexed yet, wait and retry
await new Promise((r) => setTimeout(r, 5000));
continue;
}
// Check if all steps are done
const allDone = bridge.steps.every((s) =>
s.type === 'transaction'
? s.transactionType === 'done' || s.transactionType === 'auto'
: s.type === 'wait'
? s.waitType === 'done'
: true,
);
if (allDone) {
console.log('Bridge complete!');
return bridge;
}
// Check for a step that needs signing
const readyStep = bridge.steps.find(
(s) => s.type === 'transaction' && s.transactionType === 'ready',
);
if (readyStep) {
// Get the step transaction data
const stepTxResponse = await fetch(
`${API_BASE}/v1/get_step_transaction`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
id: bridge.id,
action: readyStep.action,
provider: bridge.provider.name,
submitter: account.address,
}),
},
);
const stepTx = await stepTxResponse.json();
const stepHash = await walletClient.sendTransaction({
to: stepTx.to,
data: stepTx.data,
value: BigInt(stepTx.value),
});
await publicClient.waitForTransactionReceipt({ hash: stepHash });
console.log('Step transaction sent:', stepHash);
}
// Wait using nextCheckTimestamp
const delay = bridge.nextCheckTimestamp
? Math.max(bridge.nextCheckTimestamp - Date.now(), 2000)
: 5000;
await new Promise((r) => setTimeout(r, delay));
}
}
await pollUntilComplete();Handling approvals (ERC-20 bridging)
When bridging ERC-20 tokens, the route may include approval transactions that must be submitted before the initiating transaction.
// After getting a quote...
// Check if token approval is needed
if (quote.tokenApproval) {
// Some tokens (e.g. USDT) require revoking existing allowance first
if (quote.revokeTokenApproval) {
const revokeHash = await walletClient.sendTransaction({
to: quote.revokeTokenApproval.tx.to,
data: quote.revokeTokenApproval.tx.data,
});
await publicClient.waitForTransactionReceipt({ hash: revokeHash });
}
const approveHash = await walletClient.sendTransaction({
to: quote.tokenApproval.tx.to,
data: quote.tokenApproval.tx.data,
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });
}
// Check if gas token approval is needed (custom gas token rollups)
if (quote.gasTokenApproval) {
const gasApproveHash = await walletClient.sendTransaction({
to: quote.gasTokenApproval.tx.to,
data: quote.gasTokenApproval.tx.data,
});
await publicClient.waitForTransactionReceipt({ hash: gasApproveHash });
}
// Now send the initiating transaction
const hash = await walletClient.sendTransaction({
to: quote.initiatingTransaction.to,
data: quote.initiatingTransaction.data,
value: BigInt(quote.initiatingTransaction.value),
});