Superbridge
Start typing to search...

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