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

# Fetch Wrapper

> Lower-level fetch wrapper with pluggable payment handlers and full control.

`@faremeter/fetch` wraps the global `fetch` with automatic 402 payment handling. Unlike `@faremeter/rides`, you configure handlers and options explicitly.

```bash theme={null}
pnpm add @faremeter/fetch
```

## Basic usage

```typescript theme={null}
import { Keypair, PublicKey } from "@solana/web3.js"
import { wrap } from "@faremeter/fetch"
import { createPaymentHandler } from "@faremeter/payment-solana/exact"
import { createLocalWallet } from "@faremeter/wallet-solana"
import { lookupKnownSPLToken } from "@faremeter/info/solana"

const keypair = Keypair.fromSecretKey(new Uint8Array(keypairBytes))
const wallet = await createLocalWallet("devnet", keypair)

const splTokenInfo = lookupKnownSPLToken("devnet", "USDC")
if (!splTokenInfo) throw new Error("Unknown SPL token")
const mint = new PublicKey(splTokenInfo.address)
const handler = createPaymentHandler(wallet, mint)

const fetchWithPayment = wrap(fetch, {
  handlers: [handler],
})

const response = await fetchWithPayment("https://api.example.com/resource")
```

## `wrap`

```typescript theme={null}
import { wrap } from "@faremeter/fetch"

function wrap(phase2Fetch: typeof fetch, options: WrapOpts): typeof fetch
```

Returns a new `fetch` function that intercepts 402 responses and handles payment automatically.

### `WrapOpts`

| Option                 | Type                                                   | Default                | Description                                                         |
| ---------------------- | ------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------- |
| `handlers`             | `PaymentHandler[]`                                     | Required               | Payment handlers to use for 402 responses.                          |
| `payerChooser`         | `(execers: PaymentExecer[]) => Promise<PaymentExecer>` | `chooseFirstAvailable` | Selects which payer to use when multiple can fulfill a requirement. |
| `phase1Fetch`          | `typeof fetch`                                         | Same as `phase2Fetch`  | Fetch function for the initial request (before payment).            |
| `retryCount`           | `number`                                               | `2`                    | Number of retries after payment (3 total attempts).                 |
| `initialRetryDelay`    | `number`                                               | `100`                  | Starting backoff delay in milliseconds.                             |
| `returnPaymentFailure` | `boolean`                                              | `false`                | Return the 402 response instead of throwing on payment failure.     |

## Phase 1 and Phase 2 fetch

The wrapper uses two fetch calls per payment:

1. **Phase 1**: The initial request that discovers the 402 response.
2. **Phase 2**: The retry with the `X-PAYMENT` header attached.

By default, both use the same `fetch` function. You can provide a separate `phase1Fetch` if the initial request needs different configuration (e.g., different headers or timeouts).

## Payer selection

When multiple handlers can fulfill a payment requirement, the `payerChooser` function selects one.

```typescript theme={null}
import { wrap, chooseFirstAvailable } from "@faremeter/fetch"

const wrappedFetch = wrap(fetch, {
  handlers: [solanaHandler, evmHandler],
  payerChooser: chooseFirstAvailable,
})
```

`chooseFirstAvailable` is the default. It picks the first handler that returns a `PaymentExecer`.

### Custom payer chooser

```typescript theme={null}
import type { PaymentExecer } from "@faremeter/types/client"

const preferSolana = async (execers: PaymentExecer[]) => {
  const solana = execers.find((e) => e.requirements.network.startsWith("solana"))
  return solana ?? execers[0]
}

const wrappedFetch = wrap(fetch, {
  handlers: [solanaHandler, evmHandler],
  payerChooser: preferSolana,
})
```

## Error handling

If all payment attempts fail, `wrap` throws a `WrappedFetchError`:

```typescript theme={null}
import { WrappedFetchError } from "@faremeter/fetch"
import { getLogger } from "@faremeter/logs"

const logger = await getLogger(["my-app"])

try {
  const response = await wrappedFetch("https://api.example.com/resource")
} catch (error) {
  if (error instanceof WrappedFetchError) {
    logger.error(`Payment failed: ${error.message}, status: ${error.response.status}`)
  }
}
```

Alternatively, set `returnPaymentFailure: true` to receive the 402 response instead of throwing:

```typescript theme={null}
const wrappedFetch = wrap(fetch, {
  handlers: [handler],
  returnPaymentFailure: true,
})

const response = await wrappedFetch("https://api.example.com/resource")
if (response.status === 402) {
  // Payment failed, handle accordingly
}
```

## Retry behavior

Failed payment attempts are retried with exponential backoff:

* Default: 2 retries (3 total attempts)
* Backoff starts at 100ms and doubles each retry
* Only retries when the 402 payment flow fails, not on other HTTP errors

## Testing with mocks

`@faremeter/fetch` exports a `mock` namespace for testing payment flows without real network calls.

### `responseFeeder`

```typescript theme={null}
import { mock } from "@faremeter/fetch"
```

`responseFeeder(responses)` takes an array of `Response` objects or fetch-like functions and returns a mock `fetch`. Each call to the returned function consumes the next item in the array.

### Basic test pattern

Queue a 402 response followed by a success response to simulate a full payment flow:

```typescript theme={null}
import { mock, wrap } from "@faremeter/fetch"

const mockFetch = mock.responseFeeder([
  // Phase 1: server returns 402
  new Response(JSON.stringify({ requirements: [/* ... */] }), {
    status: 402,
    headers: { "Content-Type": "application/json" },
  }),
  // Phase 2: server accepts payment header
  new Response(JSON.stringify({ data: "paid content" }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  }),
])

const wrappedFetch = wrap(mockFetch, {
  handlers: [testHandler],
})

const response = await wrappedFetch("https://api.example.com/resource")
// response.status === 200
```

### Separate phase 1 and phase 2 mocks

Use `phase1Fetch` to mock each phase independently:

```typescript theme={null}
const phase1Mock = mock.responseFeeder([
  new Response(JSON.stringify({ requirements: [/* ... */] }), { status: 402 }),
])

const phase2Mock = mock.responseFeeder([
  new Response("OK", { status: 200 }),
])

const wrappedFetch = wrap(phase2Mock, {
  handlers: [testHandler],
  phase1Fetch: phase1Mock,
})
```

### Using fetch-like functions

Instead of static `Response` objects, pass functions for dynamic responses:

```typescript theme={null}
const mockFetch = mock.responseFeeder([
  async (input: RequestInfo | URL, init?: RequestInit) => {
    const url = input.toString()
    if (url.includes("/paid")) {
      return new Response(null, { status: 402 })
    }
    return new Response("free", { status: 200 })
  },
])
```

## Further reading

* [Rides SDK](/client/rides-sdk) -- The simpler high-level alternative.
* [Payment Handlers](/client/payment-handlers) -- Setting up handlers for specific chains.
* [Payment Handlers concept](/concepts/payment-handlers) -- How the handler plugin interface works.
