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

<Info>
  This guide targets the V2 API. V1 endpoints remain reachable but are deprecated.
</Info>

V2 adds:

* **Multi-affiliate fees** — split fees across multiple recipients per quote ([details](#monetization-affiliate-fees)).
* **`tokenSwap` route** — EVM-to-EVM token swaps, replacing the V1 `layerZero` variant.
* **USD values** — `GatewayTokenAmountV2` carries an optional `usd` field on every token amount and on each line of the V2 fee breakdowns.
* **Price impact** — V2 quotes expose `priceImpact` (fraction) and `priceImpactUsd` for display.
* **Paginated `getOrders`** — `getOrders` now accepts `{ userAddress, cursor?, limit? }` and returns `{ orders, nextCursor }` instead of a bare array. Pass back `nextCursor` to fetch the next page; a null/missing value means you've reached the end ([details](#monitor-orders)).
* **Order settlement details in status** — `success` and `refunded` are now objects carrying `receivedTokens` / `refundedTokens` (each entry has `chain`, `token`, `amount`, and the settlement `txHash`). In V1 these statuses were bare strings, and the destination `txHash` lived on `dstInfo`; in V2 `dstInfo` no longer carries a `txHash` — read it from the status payload instead.
* **`pendingBtcPayment` on X-to-BTC orders** — while an X-to-BTC order is in progress, `status.inProgress.pendingBtcPayment` exposes the gateway's outgoing Bitcoin `{ txid, amount }` so you can show the user the pending payout. This replaces the V1 `bumpFeeTx` field, which is no longer returned.

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

Gateway API keys are **optional**. All V2 endpoints are reachable without authentication; an API key unlocks higher rate limits and access to partner features.

To request a key, contact the BOB team. Once you have one, 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 V2 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';
  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
  });
  ```

  ```typescript With Affiliate Fees theme={null}
  const quote = await gatewaySDK.getQuote({
    fromChain: 'bitcoin',
    fromToken: '0x0000000000000000000000000000000000000000',
    fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
    toChain: 'bob',
    toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c',
    toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
    amount: parseBtc("0.1"),
    gasRefill: parseEther("0.00001"),
    // V2 affiliate fees: array of { address, bps } pairs
    affiliates: [{ address: '0xYourAddress', bps: 50 }], // 0.50% to one recipient
  });
  ```

  ```typescript EVM Token Swap theme={null}
  // V2 supports EVM-to-EVM 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...',
    amount: '1000000', // amount in smallest unit
  });
  ```
</CodeGroup>

<Warning>
  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`.
</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) {
  // EVM token swap (V2)
  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');
}
```

<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; an EVM-to-EVM pair 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 => {
  console.log(`Source: ${order.srcInfo.amount} ${order.srcInfo.token} (${order.srcInfo.chain})`);
  console.log(`Destination (estimated): ${order.dstInfo.amount} ${order.dstInfo.token} (${order.dstInfo.chain})`);

  // V2 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');
    // V2: settled token transfers (with on-chain txHash) 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})`);
    }
  } 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})`);
    }
  }
});
```

### 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 V2 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>
      V2 no longer exposes a `bumpFeeTx` EVM transaction — the gateway 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 can route a basis-point (bps) cut of each swap to one or more EVM recipient addresses, configured per-quote. V2 supports splitting fees across multiple recipients in a single quote.

### How it works

Affiliate fees are basis-point cuts of each swap, deducted at settlement and **paid out in USDT on Ethereum** to the recipient addresses you specify, regardless of the route. You set them per-quote via the SDK's `affiliates` parameter — an array of `{ address, bps }` pairs (the raw API serialises these as the comma-separated `affiliates` query parameter on `GET /v2/get-quote`).

`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

V2 lets 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.

```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.
* 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

The V2 quote response includes 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})` : '')
    );
  }
}
```

The `affiliates` field is present on `onramp` and `offramp` V2 quotes. `tokenSwap` quotes don't currently expose a resolved affiliates list.

### Raw API equivalent

For integrators not using the SDK, pass the same pairs to the V2 quote endpoint as the `affiliates` query parameter:

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

URL-encode the comma if your client doesn't allow raw commas in query strings.

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