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),
});