Superbridge
Start typing to search...

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