Gateway SDK
Step-by-step guide to integrate BOB Gateway SDK into your application
Overview
The BOB Gateway SDK makes it easy to bring native BTC swaps 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.
V4 integration
The SDK calls /v4 under the hood. See the Migration Guide for the API and generated-type changes.
- Pass a
refundAddresson the source chain for every quote you intend to execute. It is optional for a price-only quote, but order creation rejects a quote without one withMISSING_REFUND_ADDRESS. The SDK forwards the address you provide; it does not infer it fromfromUserAddress. - Remove
ownerAddressfrom quote parameters. It is no longer part of the V4 request. - Bitcoin signers must return signed raw transaction hex. The SDK submits it to Gateway for validation and broadcasting. Offramp and token-swap transactions are indexed from the source chain without
register-tx. executeQuotesupports Bitcoin signing and viem-compatible EVM/Tron clients. Solana source transactions require the API and a Solana signer; the SDK does not sign SolanaVersionedTransactionpayloads. Solana can still be a destination for supported routes.
Quotes retain affiliate fees, optional USD amounts, and price impact. Orders retain pagination and discriminated settlement status. Use getRoutes() to discover current chains and tokens.
Installation
npm install @gobob/bob-sdk viemInitialize the SDK
Import the GatewayApiClient (exported as GatewaySDK) and create an instance. The constructor takes an optional options object:
import { GatewaySDK, STAGING_GATEWAY_BASE_URL } from '@gobob/bob-sdk';
// Mainnet (default)
const gatewaySDK = new GatewaySDK();
// Staging
const gatewaySDKStaging = new GatewaySDK({ basePath: STAGING_GATEWAY_BASE_URL });Authentication
API keys are optional — the API is reachable without authentication. A key unlocks:
- Analytics dashboard — your orders, volume, and affiliate earnings
- Higher rate limits than keyless usage
Partner Onboarding
Get an API key and go live — full flow and contact details
Once you have a key, pass it in the options object:
import { GatewaySDK } from '@gobob/bob-sdk';
const gatewaySDK = new GatewaySDK({ apiKey: 'your-api-key' });The API key must be exactly 32 characters long. When provided, the SDK will include it in the Authorization header as a Bearer token (Authorization: Bearer <api-key>). If you're calling the API directly, set the same header on every V4 request.
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 structuresGet a Quote
Omit maxSlippage to let Gateway recommend a slippage tolerance for the route. The SDK leaves the API's slippage parameter unset and returns the resolved value in the quote. To set your own tolerance, supply basis points, for example maxSlippage: 300 for 3%. The SDK no longer applies a fixed 3% default; see the migration notes and recommendation example. Always review the quote's resolved slippage before execution.
Request a quote for the user's desired transaction:
import { parseBtc } from '@gobob/bob-sdk';
const quote = await gatewaySDK.getQuote({
fromChain: 'bitcoin',
fromToken: '0x0000000000000000000000000000000000000000',
fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
refundAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d', // Bitcoin source-chain refund
toChain: 'bob',
toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
amount: parseBtc("0.1"), // 0.1 BTC
});On EVM chains, token parameters (fromToken, toToken) must be 0x-prefixed hex addresses, not symbols; on Tron/Solana they are Base58 identifiers. 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.
Request a Slippage Recommendation
Omit maxSlippage to request a quote with Gateway's recommended tolerance. You can inspect the recommendation without creating an order:
import { GatewaySDK, getInnerQuote, parseBtc } from '@gobob/bob-sdk';
const gateway = new GatewaySDK();
const quote = await gateway.getQuote({
fromChain: 'bitcoin',
fromToken: '0x0000000000000000000000000000000000000000',
toChain: 'bob',
toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
amount: parseBtc('0.1'),
// Omit maxSlippage to request Gateway's recommendation.
});
const slippageBps = Number(getInnerQuote(quote).slippage);
console.log(`Recommended slippage: ${slippageBps} bps (${slippageBps / 100}%)`);The equivalent API request leaves out the slippage query parameter entirely:
curl --get 'https://gateway-api-mainnet.gobob.xyz/v4/get-quote' \
--data-urlencode 'srcChain=bitcoin' \
--data-urlencode 'dstChain=bob' \
--data-urlencode 'srcToken=0x0000000000000000000000000000000000000000' \
--data-urlencode 'dstToken=0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' \
--data-urlencode 'recipient=0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c' \
--data-urlencode 'amount=10000000'Read onramp.slippage, offramp.slippage, or tokenSwap.slippage in the response. The onramp value is a string and the other variants use numbers; all are in basis points.
Show the resolved tolerance to the user before execution. These examples request a quote only: include a source-chain refundAddress in a fresh quote before creating an order. If the user accepts the tolerance, pass that returned quote unchanged to create-order. If they want a different limit, request a new quote with an explicit slippage / maxSlippage; do not edit the returned quote.
V3 still requires slippage. Older SDK versions send a fixed 3% when maxSlippage is omitted; upgrade to use this behavior, or call the V4 API directly. See the migration notes.
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) {
// BTC to BOB/EVM
console.log('Input:', quote.onramp.inputAmount); // GatewayTokenAmountV2 (has optional .usd)
console.log('Fees:', quote.onramp.feeBreakdown); // each line is GatewayTokenAmountV2
console.log('Price impact:', quote.onramp.priceImpact); // optional, fraction e.g. "-0.05"
console.log('ETA:', quote.onramp.estimatedTimeInSecs, 'seconds');
} else if ('offramp' in quote) {
// EVM to BTC
console.log('Input:', quote.offramp.inputAmount);
console.log('Fees:', quote.offramp.feeBreakdown);
console.log('Price impact:', quote.offramp.priceImpact);
console.log('ETA:', quote.offramp.estimatedTimeInSecs, 'seconds');
} else if ('tokenSwap' in quote) {
// Cross-chain token swap
console.log('Input:', quote.tokenSwap.inputAmount);
console.log('Fees:', quote.tokenSwap.fees);
console.log('Price impact:', quote.tokenSwap.priceImpact);
console.log('ETA:', quote.tokenSwap.estimatedTimeInSecs, 'seconds');
// The resolved single affiliate (null for fee-free swaps)
if (quote.tokenSwap.affiliate) {
const { address, bps } = quote.tokenSwap.affiliate;
console.log(`Affiliate: ${bps} bps to ${address}`);
}
}You don't need to handle all quote types — the response type matches your fromChain/toChain parameters. fromChain: 'bitcoin' with toChain: 'bob' always returns an onramp quote; a token-to-token pair (including to Tron/Solana) returns a tokenSwap quote.
Execute the Quote
Execute the quote by having the user sign the Bitcoin transaction. executeQuote returns { order, tx }; keep the order ID to monitor settlement. Without btcSigner, it returns an onramp order with no tx so the user can pay its Bitcoin deposit address externally. A source-chain refundAddress is still required.
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 { order, tx } = await gatewaySDK.executeQuote({
quote,
walletClient,
publicClient,
btcSigner: new ReownWalletAdapter(walletProvider, btcAddress),
});
console.log('Order:', order, 'Transaction ID:', tx);For detailed wallet integration options including Reown AppKit, sats-wagmi, Dynamic.xyz, and more, see the Bitcoin Wallets guide.
Monitor Orders
Fetch a page of the user's pending and completed orders. getOrders returns { orders, nextCursor } — pass nextCursor back to walk subsequent pages:
const { orders, nextCursor } = await gatewaySDK.getOrders({
userAddress: userEvmAddress,
limit: 20, // optional; omit to use the gateway default
});
orders.forEach(order => {
// srcInfo/dstInfo carry an optional `usd` value alongside the amount
console.log(`Source: ${order.srcInfo.amount} ${order.srcInfo.token} (${order.srcInfo.chain})${order.srcInfo.usd ? ` ~$${order.srcInfo.usd}` : ''}`);
console.log(`Destination (estimated): ${order.dstInfo.amount} ${order.dstInfo.token} (${order.dstInfo.chain})`);
// Order status is always a discriminated object — no bare strings
if ('inProgress' in order.status) {
console.log('Status: in progress');
if (order.status.inProgress.pendingBtcPayment) {
const { txid, amount } = order.status.inProgress.pendingBtcPayment;
console.log(`Pending BTC payout: ${amount} sats (txid: ${txid})`);
}
// Note: `refundTx` is always null — Gateway settles refunds itself
} else if ('failed' in order.status) {
console.log('Status: failed');
} else if ('success' in order.status) {
console.log('Status: success');
// Settled token transfers (with on-chain txHash, and optional `usd`) are on the status payload
for (const t of order.status.success.receivedTokens) {
console.log(`Received ${t.amount} ${t.token} on ${t.chain} (tx ${t.txHash})${t.usd ? ` ~$${t.usd}` : ''}`);
}
} else if ('refunded' in order.status) {
console.log('Status: refunded');
for (const t of order.status.refunded.refundedTokens) {
console.log(`Refunded ${t.amount} ${t.token} on ${t.chain} (tx ${t.txHash})`);
}
}
});Link users to the Gateway Explorer
If you don't want to build your own tracking UI, every order has a public status page on the Gateway Explorer that you can link users to directly:
https://gateway-explorer.gobob.xyz/order/<ORDER_ID>Example: gateway-explorer.gobob.xyz/order/71a070e7-...
Use the order ID returned when the order is created. The page shows live status, amounts, and transaction hashes for both sides of the swap — useful as a "track your swap" link in confirmation screens, order history, or notification emails.
Paginating through all orders
nextCursor is null/absent once you've reached the last page:
let cursor: string | undefined;
do {
const page = await gatewaySDK.getOrders({
userAddress: userEvmAddress,
limit: 50,
cursor,
});
// ...handle page.orders
cursor = page.nextCursor ?? undefined;
} while (cursor);order.dstInfo.amount is the estimated output recorded when the order was created. The settled amount and destination txHash are reported on status.success.receivedTokens (or status.refunded.refundedTokens) once the order resolves.
X to BTC Order Features
For X-to-BTC (BOB → Bitcoin) orders, getOrders surfaces status fields you can act on while the order is still in progress:
While the gateway is settling an X-to-BTC order, status.inProgress.pendingBtcPayment carries the outgoing Bitcoin transaction { txid, amount }. Use it to show the user a "payout in flight" state and link to a block explorer:
const { orders } = await gatewaySDK.getOrders({ userAddress: userEvmAddress });
const inFlight = orders.find(order =>
'inProgress' in order.status && order.status.inProgress.pendingBtcPayment
);
if (inFlight && 'inProgress' in inFlight.status) {
const { txid, amount } = inFlight.status.inProgress.pendingBtcPayment!;
console.log(`Gateway is sending ${amount} sats — track it at https://mempool.space/tx/${txid}`);
}Gateway no longer exposes a bumpFeeTx EVM transaction — it manages fee bumps internally for the BTC payout it broadcasts.
Gateway settles every refund itself, in both directions — there is no refund transaction for you or your users to submit. What you can do is surface the outcome, which arrives as the refunded status:
const { orders } = await gatewaySDK.getOrders({ userAddress: userEvmAddress });
for (const order of orders) {
if (!('refunded' in order.status)) continue;
for (const t of order.status.refunded.refundedTokens) {
// A BTC refund reports `chain: 'bitcoin'` and a Bitcoin txid in `txHash`
console.log(`Refunded ${t.amount} ${t.token} on ${t.chain} (tx ${t.txHash})`);
}
}status.inProgress.refundTx and status.failed.refundTx are always null — the fields are retained only so the response shape is unchanged for existing integrators. X-to-BTC refunds are owner-operated on the offramp registry and cannot be triggered by a user or an integrator. See Refunds.
Monetization (Affiliate Fees)
Gateway supports affiliate fees out of the box. You set them per-quote via the SDK's affiliates parameter — an array of { address, bps } pairs. How they're charged depends on the route:
onramp/offramp— fees are deducted at settlement and paid out in USDT on Ethereum to the recipient addresses you specify, regardless of the route. These routes accept multiple recipients per quote.tokenSwap— the fee is charged by the aggregator (Bungee/Velora) on the source chain. Aggregators allow only one partner fee per swap, so atokenSwapquote accepts exactly one affiliate. Passing more than one returnsTOO_MANY_AFFILIATES.
1 bps = 0.01%, so 50 means 0.50%. For the full fee model, see Fees.
Single recipient
const quote = await gatewaySDK.getQuote({
// ... other params
affiliates: [{ address: '0xYourAddress', bps: 50 }], // 0.50% to one recipient
});Split fees across multiple recipients
onramp and offramp quotes let you split affiliate fees across multiple recipients in a single quote — useful for revenue splits between an aggregator and an underlying integrator, referral programs, or multi-party agreements. (tokenSwap accepts only one affiliate.)
const quote = await gatewaySDK.getQuote({
// ... other params
affiliates: [
{ address: '0xPartnerA', bps: 50 }, // 0.50%
{ address: '0xPartnerB', bps: 25 }, // 0.25%
],
});Format and rules
- Comma-separated
<address>:<bps>pairs, no spaces. - Each address must be a valid EVM address.
- Each
bpsMUST be greater than0. - Omit
affiliatesor pass an empty array for no affiliate fees. tokenSwapaccepts at most one affiliate; more than one returnsTOO_MANY_AFFILIATES.- The gateway enforces caps on recipient count and total bps. Routes that don't support affiliate fees return error code
AFFILIATE_FEES_NOT_SUPPORTED_FOR_ROUTE— handle this by retrying the quote withaffiliatesomitted, or surfacing the error to the user.
Reading resolved fees from the quote
onramp and offramp quotes include a resolved affiliates array — each entry has the recipient address and the computed fee amount (with optional USD value). Use it to surface the affiliate split to the user:
const quote = await gatewaySDK.getQuote({ /* ... */ });
const onramp = 'onramp' in quote ? quote.onramp : null;
if (onramp?.affiliates?.length) {
for (const a of onramp.affiliates) {
console.log(
`${a.address} earns ${a.fee.amount} ${a.fee.address}` +
(a.fee.usd ? ` (~$${a.fee.usd})` : '')
);
}
}tokenSwap quotes instead expose a single resolved affiliate ({ address, bps }, or null for a fee-free swap):
const tokenSwap = 'tokenSwap' in quote ? quote.tokenSwap : null;
if (tokenSwap?.affiliate) {
const { address, bps } = tokenSwap.affiliate;
console.log(`${address} earns ${bps} bps on the source chain`);
}Raw API equivalent
For integrators not using the SDK, pass the same pairs to the V4 quote endpoint as the affiliates query parameter:
GET /v4/get-quote?...&affiliates=0xPartnerA:50,0xPartnerB:25URL-encode the comma if your client doesn't allow raw commas in query strings.
Track your orders and earnings
With an API key you get a partner dashboard on the Gateway Explorer:
https://gateway-explorer.gobob.xyz/affiliate/<API_KEY>It shows your integration's orders, volume, and — if you charge affiliate fees — your accumulated earnings. For ecosystem-wide activity, see the public Dune dashboard.