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