Reference
Examples
End-to-end examples of common bridging flows using the SDK
End-to-end examples of common bridging flows using the Superbridge SDK with viem.
Bridge ETH from Ethereum to Base
This example fetches a route, signs and sends the initiating transaction, then polls for completion.
import { createClient, routeGuards, txGuards, activityStepGuards } from "@superbridge/sdk";
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
const sb = createClient({ apiKey: "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 routes = await sb.getRoutes({
fromChainKey: "eth",
toChainKey: "base",
fromTokenAddress: "0x0000000000000000000000000000000000000000",
toTokenAddress: "0x0000000000000000000000000000000000000000",
amount: parseUnits("0.01", 18).toString(),
sender: account.address,
recipient: account.address,
});
// 2. Find the first quote using SDK type guards
const quote = routes.results
.map((r) => r.result)
.find(routeGuards.isRouteQuote);
if (!quote) throw new Error("No quote available");
// 3. Narrow and send the initiating transaction
const { initiatingTransaction } = quote;
if (!txGuards.isEvmTx(initiatingTransaction)) {
throw new Error("Expected an EVM transaction");
}
const hash = await walletClient.sendTransaction({
to: initiatingTransaction.to,
data: initiatingTransaction.data,
value: BigInt(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 activities = await sb.getActivity({
evmAddress: account.address,
});
// Find our bridge by matching the initiating tx hash
const bridge = activities.find((a) =>
a.steps.some(
(s) =>
activityStepGuards.isTransactionStepDone(s) &&
s.confirmation?.transactionHash === hash
)
);
if (!bridge) {
await new Promise((r) => setTimeout(r, 5000));
continue;
}
// Check if all steps are done
const allDone = bridge.steps.every(
(s) =>
activityStepGuards.isTransactionStepDone(s) ||
activityStepGuards.isTransactionStepAuto(s) ||
activityStepGuards.isWaitStepDone(s) ||
activityStepGuards.isInfoStep(s)
);
if (allDone) {
console.log("Bridge complete!");
return bridge;
}
// Check for a step that needs signing
const readyStep = bridge.steps.find(
activityStepGuards.isTransactionStepReady
);
if (readyStep) {
// SDK doesn't have getStepTransaction yet, use fetch
const stepTxResponse = await fetch(
"https://api.superbridge.app/v1/get_step_transaction",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "your-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 quote may include approval transactions that must be submitted before the initiating transaction.
import { createClient, routeGuards } from "@superbridge/sdk";
const sb = createClient({ apiKey: "your-api-key" });
// After getting routes and finding a quote...
const quote = routes.results
.map((r) => r.result)
.find(routeGuards.isRouteQuote);
// 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),
});