> ## Documentation Index
> Fetch the complete documentation index at: https://distributedcrafts.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

## What's new in V3

<Info>
  This guide targets the V3 API. V1 and V2 endpoints remain reachable but are superseded — the SDK calls `/v3` under the hood. Upgrading an existing integration? See the [Migration Guide](/gateway/migration).
</Info>

<Warning>
  **The V1 API will be deprecated at the end of July 2026** and shut down afterwards. Migrate any V1 integration to V3 before that date — see the [Migration Guide](/gateway/migration). V2 remains supported.
</Warning>

V3 builds on V2 with multi-chain support and richer settlement data:

* **Non-EVM chains (Tron & Solana)** — quotes and orders now span EVM, Tron, and Solana as well as Bitcoin. Addresses and token identifiers are `0x…` on EVM chains and Base58 on Tron/Solana. `executeQuote` signs and broadcasts the right transaction shape for each chain; if you build transactions yourself, `createOrder` returns a tagged union you dispatch on (`type: 'evm' | 'tron' | 'solana'`).
* **`ownerAddress` (required) and `refundAddress`** — `getQuote` now takes an `ownerAddress` (the EVM owner / refund-claimant, also used for order lookup) and an optional Bitcoin `refundAddress` for onramps. Routes that need an owner but don't get one fail with `MISSING_OWNER_ADDRESS`.
* **USD on order info** — `srcInfo`, `dstInfo`, settled transfers (`receivedTokens` / `refundedTokens`), and `pendingBtcPayment` now each carry an optional `usd` value (valued at order time). In V2, `usd` was only on quote amounts and fee-breakdown lines.
* **Affiliate fees on `tokenSwap`** — `tokenSwap` quotes now expose a resolved single `affiliate` (`{ address, bps }`), charged by the aggregator on the source chain. Aggregators allow only one partner fee per swap, so passing more than one affiliate returns `TOO_MANY_AFFILIATES`. `onramp`/`offramp` keep the V2 multi-recipient `affiliates` list ([details](#monetization-affiliate-fees)).
* **Removed parameters** — `gasRefill`, `strategyAddress`/`strategyTarget`, and `strategyMessage` are no longer supported and are rejected by the gateway.
* **Paginated `getOrders`** — `getOrders` accepts `{ userAddress, cursor?, limit? }` and returns `{ orders, nextCursor }` (carried over from V2). Pass back `nextCursor` to fetch the next page; a null/missing value means you've reached the end ([details](#monitor-orders)).
* **Discriminated order status** — `success` / `refunded` are objects carrying `receivedTokens` / `refundedTokens` (each entry has `chain`, `token`, `amount`, the settlement `txHash`, and now `usd`); `status.inProgress.pendingBtcPayment` exposes the gateway's outgoing Bitcoin `{ txid, amount, usd }` on in-progress X-to-BTC orders. Read the destination `txHash` from the status payload — `dstInfo` doesn't carry one.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @gobob/bob-sdk viem
  ```

  ```bash yarn theme={null}
  yarn add @gobob/bob-sdk viem
  ```

  ```bash pnpm theme={null}
  pnpm add @gobob/bob-sdk viem
  ```
</CodeGroup>

## Initialize the SDK

Import the `GatewayApiClient` (exported as `GatewaySDK`) and create an instance. The constructor takes an optional options object:

```typescript theme={null}
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

<Card title="Partner Onboarding" icon="rocket" href="/gateway/onboarding">
  Get an API key and go live — full flow and contact details
</Card>

Once you have a key, pass it in the options object:

```typescript theme={null}
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 V3 request.

## Get Available Routes

Fetch all supported routes to show users their options:

```typescript theme={null}
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:

<CodeGroup>
  ```typescript BTC to BOB theme={null}
  import { parseBtc } from '@gobob/bob-sdk';

  const quote = await gatewaySDK.getQuote({
    fromChain: 'bitcoin',
    fromToken: '0x0000000000000000000000000000000000000000',
    fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
    toChain: 'bob',
    toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
    toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
    ownerAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c', // EVM owner / refund-claimant
    amount: parseBtc("0.1"), // 0.1 BTC
  });
  ```

  ```typescript With Affiliate Fees theme={null}
  const quote = await gatewaySDK.getQuote({
    fromChain: 'bitcoin',
    fromToken: '0x0000000000000000000000000000000000000000',
    fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
    toChain: 'bob',
    toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
    toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
    ownerAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
    amount: parseBtc("0.1"),
    // onramp/offramp accept multiple recipients; tokenSwap accepts exactly one
    affiliates: [{ address: '0xYourAddress', bps: 50 }], // 0.50% to one recipient
  });
  ```

  ```typescript Token Swap theme={null}
  // V3 supports cross-chain token swaps via the tokenSwap route.
  const quote = await gatewaySDK.getQuote({
    fromChain: 'bob',
    fromToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c', // source token on BOB
    toChain: 'bob',
    toToken: '0x...', // destination token
    fromUserAddress: '0x...',
    toUserAddress: '0x...',
    ownerAddress: '0x...',
    amount: '1000000', // amount in smallest unit
  });
  ```

  ```typescript To Solana / Tron theme={null}
  // Destination addresses and tokens on Tron/Solana are Base58, not 0x-hex.
  const quote = await gatewaySDK.getQuote({
    fromChain: 'bitcoin',
    fromToken: '0x0000000000000000000000000000000000000000',
    fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
    toChain: 'solana',
    toToken: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC mint (Base58)
    toUserAddress: '7xKX...9aBc', // Base58 wallet address
    ownerAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c', // EVM owner for lookup
    amount: parseBtc("0.1"),
  });
  ```
</CodeGroup>

<Warning>
  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`.
</Warning>

<Tip>
  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.
</Tip>

### Understanding Quote Types

The `getQuote` response is a discriminated union — access fields through the appropriate key:

```typescript theme={null}
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 (V3)
  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');
  // V3: 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}`);
  }
}
```

<Tip>
  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.
</Tip>

## Execute the Quote

Execute the quote by having the user sign the Bitcoin transaction:

<CodeGroup>
  ```typescript Reown AppKit theme={null}
  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);
  ```

  ```typescript OKX Wallet theme={null}
  import { OkxWalletAdapter } from '@gobob/bob-sdk';

  // Assuming you have window.okxwallet available
  const txId = await gatewaySDK.executeQuote({
    quote,
    walletClient,
    publicClient,
    btcSigner: new OkxWalletAdapter(window.okxwallet),
  });
  ```

  ```typescript Custom Adapter theme={null}
  import { BitcoinSigner } from '@gobob/bob-sdk';

  // Implement the BitcoinSigner interface
  class CustomWalletAdapter implements BitcoinSigner {
    async sendBitcoin(params: {
      from: string;
      to: string;
      value: string;
      opReturn?: string;
    }): Promise<string> {
      // Your wallet integration logic here
      // Return signed transaction hex
    }
    
    // OR implement signAllInputs for PSBT-based wallets
    async signAllInputs(psbtHex: string): Promise<string> {
      // Sign PSBT and return hex
    }
  }

  const txId = await gatewaySDK.executeQuote({
    quote,
    walletClient,
    publicClient,
    btcSigner: new CustomWalletAdapter(),
  });
  ```
</CodeGroup>

<Note>
  For detailed wallet integration options including Reown AppKit, sats-wagmi, Dynamic.xyz, and more, see the [Bitcoin Wallets](/gateway/wallets) guide.
</Note>

## Monitor Orders

Fetch a page of the user's pending and completed orders. `getOrders` returns `{ orders, nextCursor }` — pass `nextCursor` back to walk subsequent pages:

```typescript theme={null}
const { orders, nextCursor } = await gatewaySDK.getOrders({
  userAddress: userEvmAddress,
  limit: 20, // optional; omit to use the gateway default
});

orders.forEach(order => {
  // V3: srcInfo/dstInfo now 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})`);
    }
    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');
    }
  } else if ('success' in order.status) {
    console.log('Status: success');
    // Settled token transfers (with on-chain txHash, and V3 `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:

```text theme={null}
https://gateway-explorer.gobob.xyz/order/<ORDER_ID>
```

Example: [gateway-explorer.gobob.xyz/order/71a070e7-...](https://gateway-explorer.gobob.xyz/order/71a070e7-7ff5-45c9-8f60-135894ebed11)

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:

```typescript theme={null}
let cursor: string | undefined;
do {
  const page = await gatewaySDK.getOrders({
    userAddress: userEvmAddress,
    limit: 50,
    cursor,
  });
  // ...handle page.orders
  cursor = page.nextCursor ?? undefined;
} while (cursor);
```

<Tip>
  `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.
</Tip>

## 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:

<AccordionGroup>
  <Accordion icon="clock" title="Track the Pending BTC Payout">
    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:

    ```typescript theme={null}
    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}`);
    }
    ```

    <Note>
      Gateway no longer exposes a `bumpFeeTx` EVM transaction — it manages fee bumps internally for the BTC payout it broadcasts.
    </Note>
  </Accordion>

  <Accordion icon="unlock" title="Refund Stuck Orders">
    If an order gets stuck or needs to be cancelled, the order will include a `refundTx` to unlock the locked assets:

    ```typescript theme={null}
    const { orders } = await gatewaySDK.getOrders({ userAddress: userEvmAddress });

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

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

      await publicClient.waitForTransactionReceipt({ hash });
    }
    ```

    <Warning>
      This action is irreversible. Once refunded, the order cannot be resumed.
    </Warning>
  </Accordion>
</AccordionGroup>

## 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 a `tokenSwap` quote accepts exactly one affiliate. Passing more than one returns `TOO_MANY_AFFILIATES`.

`1 bps = 0.01%`, so `50` means `0.50%`. For the full fee model, see [Fees](/gateway/fees).

### Single recipient

```typescript theme={null}
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.)

```typescript theme={null}
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 `bps` MUST be greater than `0`.
* Omit `affiliates` or pass an empty array for no affiliate fees.
* `tokenSwap` accepts at most one affiliate; more than one returns `TOO_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 with `affiliates` omitted, 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:

```typescript theme={null}
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):

```typescript theme={null}
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 V3 quote endpoint as the `affiliates` query parameter:

```bash theme={null}
GET /v3/get-quote?...&affiliates=0xPartnerA:50,0xPartnerB:25
```

URL-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:

```text theme={null}
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](https://dune.com/bob_collective/gateway).

## Next Steps

<CardGroup cols={2}>
  <Card title="Build DeFi Strategies" icon="layer-group" href="/gateway/strategies">
    Create 1-click DeFi actions like staking and lending
  </Card>

  <Card title="Bitcoin Wallets" icon="wallet" href="/gateway/wallets">
    Detailed guide on wallet integrations
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/overview">
    Complete API documentation
  </Card>

  <Card title="Example Code" icon="github" href="https://github.com/bob-collective/bob/tree/master/sdk/examples">
    View complete examples on GitHub
  </Card>
</CardGroup>
