Skip to main content

Overview

The BOB Gateway SDK makes it easy to bring Bitcoin onramp functionality directly into your app. This guide walks through the complete integration process. We recommend using the API directly, but you may use our SDK for convenience.

Installation

npm install @gobob/bob-sdk viem

Initialize the SDK

Import the GatewayApiClient (exported as GatewaySDK) and create an instance:
import { GatewaySDK } from '@gobob/bob-sdk';
import { bob, bobSepolia } from 'viem/chains';

// Mainnet
const gatewaySDK = new GatewaySDK(bob.id);

// Testnet
const gatewaySDKTestnet = new GatewaySDK(bobSepolia.id);

Get Available Routes

Fetch all supported routes to show users their options:
const routes = await gatewaySDK.getRoutes();

// Routes include information about:
// - Source and destination chains
// - Supported tokens
// - Available bridges
// - Fee structures

Get a Quote

Request a quote for the user’s desired transaction:
import { parseBtc } from '@gobob/bob-sdk';
import { parseEther } from 'viem';

const quote = await gatewaySDK.getQuote({
  fromChain: 'bitcoin',
  fromToken: '0x0000000000000000000000000000000000000000',
  fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
  toChain: 'bob',
  toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
  toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
  amount: parseBtc("0.1"), // 0.1 BTC
  gasRefill: parseEther("0.00001"), // Optional ETH gas refill
});
Token parameters (fromToken, toToken) must be 0x-prefixed hex addresses, not symbols. Use getRoutes() to find supported token addresses. For BTC, use the zero address 0x0000000000000000000000000000000000000000.
Display quote fields like fees and estimated time to give users transparency about the transaction. See the section below for how to access these fields.

Understanding Quote Types

The getQuote response is a discriminated union — access fields through the appropriate key:
const quote = await gatewaySDK.getQuote({ /* ... */ });

if ('onramp' in quote) {
  // Bitcoin → BOB onramp
  console.log('Input:', quote.onramp.inputAmount);
  console.log('Fees:', quote.onramp.feeBreakdown);
  console.log('ETA:', quote.onramp.estimatedTimeInSecs, 'seconds');
} else if ('offramp' in quote) {
  // BOB → Bitcoin offramp
  console.log('Input:', quote.offramp.inputAmount);
  console.log('Fees:', quote.offramp.feeBreakdown);
  console.log('ETA:', quote.offramp.estimatedTimeInSecs, 'seconds');
} else if ('layerZero' in quote) {
  // Cross-chain via LayerZero
  console.log('Input:', quote.layerZero.inputAmount);
  console.log('Fees:', quote.layerZero.fees);
  console.log('ETA:', quote.layerZero.estimatedTimeInSecs, 'seconds');
}
You don’t need to handle all quote types — the response type matches your fromChain/toChain parameters. For example, fromChain: 'bitcoin' with toChain: 'bob' always returns an onramp quote.

Execute the Quote

Execute the quote by having the user sign the Bitcoin transaction:
import { createPublicClient, createWalletClient, http, zeroAddress } from 'viem';
import { useAppKitProvider, useAppKitAccount } from '@reown/appkit/react';
import type { BitcoinConnector } from "@reown/appkit-adapter-bitcoin";
import { ReownWalletAdapter } from '@gobob/bob-sdk';
import { bob } from 'viem/chains';

// Setup viem clients
const publicClient = createPublicClient({
  chain: bob,
  transport: http(),
});

const walletClient = createWalletClient({
  chain: bob,
  transport: http(),
  account: zeroAddress, // Replace with connected account
});

// Get Bitcoin wallet provider
const { walletProvider } = useAppKitProvider<BitcoinConnector>('bip122');
const { address: btcAddress } = useAppKitAccount();

// Execute the quote
const txId = await gatewaySDK.executeQuote({
  quote,
  walletClient,
  publicClient,
  btcSigner: new ReownWalletAdapter(walletProvider, btcAddress),
});

console.log('Transaction ID:', txId);
For detailed wallet integration options including sats-wagmi, Dynamic.xyz, and more, see the Bitcoin Wallets guide.

Monitor Orders

Get the user’s pending and completed orders:
const orders = await gatewaySDK.getOrders(userEvmAddress);

// Display orders in a table or list
orders.forEach(order => {
  console.log(`Source: ${order.srcInfo.amount} ${order.srcInfo.token} (${order.srcInfo.chain})`);
  console.log(`Destination: ${order.dstInfo.amount} ${order.dstInfo.token} (${order.dstInfo.chain})`);

  // Check order status (discriminated union)
  if (typeof order.status === 'string') {
    // Simple status: 'success' or 'refunded'
    console.log(`Status: ${order.status}`);
  } else if ('inProgress' in order.status) {
    console.log('Status: in progress');
    if (order.status.inProgress.bumpFeeTx) {
      console.log('Fee bump transaction available');
    }
    if (order.status.inProgress.refundTx) {
      console.log('Refund transaction available');
    }
  } else if ('failed' in order.status) {
    console.log('Status: failed');
    if (order.status.failed.refundTx) {
      console.log('Refund transaction available');
    }
  }
});

Offramp Features

For offramp (BOB → Bitcoin) transactions, the get-orders API now returns EVM transactions that need to be submitted when actions are required:
If Bitcoin network fees increase after order placement, the order will include a bumpFeeTx that can be submitted to speed up confirmation:
const orders = await gatewaySDK.getOrders(userEvmAddress);

// Find order with bump fee transaction
const orderNeedingBump = orders.find(order =>
  typeof order.status === 'object'
  && 'inProgress' in order.status
  && order.status.inProgress.bumpFeeTx
);

if (orderNeedingBump && typeof orderNeedingBump.status === 'object' && 'inProgress' in orderNeedingBump.status) {
  const bumpFeeTx = orderNeedingBump.status.inProgress.bumpFeeTx!;
  // Submit the bump fee transaction
  const hash = await walletClient.sendTransaction({
    to: bumpFeeTx.to,
    data: bumpFeeTx.data,
    value: bumpFeeTx.value,
  });

  await publicClient.waitForTransactionReceipt({ hash });
}
Only works if the original transaction hasn’t been confirmed yet.
If an order gets stuck or needs to be cancelled, the order will include a refundTx to unlock the locked assets:
const orders = await gatewaySDK.getOrders(userEvmAddress);

// Find order with refund transaction available
const orderNeedingRefund = orders.find(order =>
  typeof order.status === 'object'
  && (('inProgress' in order.status && order.status.inProgress.refundTx)
    || ('failed' in order.status && order.status.failed.refundTx))
);

if (orderNeedingRefund && typeof orderNeedingRefund.status === 'object') {
  const refundTx = 'failed' in orderNeedingRefund.status
    ? orderNeedingRefund.status.failed.refundTx!
    : orderNeedingRefund.status.inProgress.refundTx!;
  // Submit the refund transaction
  const hash = await walletClient.sendTransaction({
    to: refundTx.to,
    data: refundTx.data,
    value: refundTx.value,
  });

  await publicClient.waitForTransactionReceipt({ hash });
}
This action is irreversible. Once refunded, the order cannot be resumed.

Monetization

Gateway supports affiliate fees out of the box. You earn a fee on every transaction through your integration: by providing your unique affiliate ID:
const quote = await gatewaySDK.getQuote({
  // ... other params
  affiliateId: '550e8400-e29b-41d4-a716-446655440000', // Your unique UUID
});
Your affiliate ID:
  • Must be a valid UUID (universally unique identifier)
  • Tracks all transactions through your integration
  • Automatically associates earned fees with your account
  • Contact the BOB team to register your affiliate ID and set up fee structure

Next Steps