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

# Facilitator

> Running your own x402 payment facilitator with @faremeter/facilitator.

A facilitator verifies and settles payments on-chain. By default, faremeter uses the Corbits facilitator (`https://facilitator.corbits.dev`). If you need to run your own, use `@faremeter/facilitator`.

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

## When to run your own

* You need to support custom payment schemes not available on the Corbits facilitator.
* You want full control over payment verification and settlement.
* You are running in an environment that cannot reach the Corbits facilitator.

For most development and production use cases, the Corbits facilitator is sufficient.

<Note>
  `adaptSettleResponseV2ToV1Legacy` is deprecated. Use `adaptSettleResponseV2ToV1` for spec-compliant v1 output. See the [Facilitator API reference](/api/facilitator.src) for details on version adapters.
</Note>

## Basic setup

```typescript theme={null}
import { Hono } from "hono"
import { createFacilitatorRoutes } from "@faremeter/facilitator"

const handlers = [
  // Chain-specific handlers
]

const app = new Hono()
app.route("/", createFacilitatorRoutes({ handlers }))

export { app }
```

`createFacilitatorRoutes` returns a Hono router with the standard facilitator endpoints: `/accepts`, `/verify`, `/settle`, and `/supported`.

## Creating handlers

Each chain has its own `createFacilitatorHandler` function that returns a `FacilitatorHandler`. Install the payment package for the chain you want to support.

### Solana handler

```bash theme={null}
pnpm add @faremeter/payment-solana @solana/kit @solana/web3.js @solana-program/node-helpers
```

```typescript theme={null}
import { createFacilitatorHandler } from "@faremeter/payment-solana/exact"
import { createSolanaRpc } from "@solana/kit"
import { getKeypairFromFile } from "@solana-program/node-helpers"
import { PublicKey } from "@solana/web3.js"

const rpc = createSolanaRpc(process.env.SOLANA_RPC_URL!)
const feePayerKeypair = await getKeypairFromFile(process.env.SOLANA_PAYER_KEYPAIR!)
const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") // USDC

const solanaHandler = await createFacilitatorHandler(
  "devnet",
  rpc,
  feePayerKeypair,
  mint,
)
```

**Signature:** `createFacilitatorHandler(network, rpc, feePayerKeypair, mint, config?)`

The optional `config` parameter accepts:

| Option                              | Type                 | Default  | Description                                                |
| ----------------------------------- | -------------------- | -------- | ---------------------------------------------------------- |
| `maxRetries`                        | `number`             | `30`     | Maximum retries for transaction submission.                |
| `retryDelayMs`                      | `number`             | `1000`   | Delay between retries in milliseconds.                     |
| `maxPriorityFee`                    | `number`             | `100000` | Maximum priority fee in lamports.                          |
| `maxTransactionAge`                 | `number`             | `150`    | Maximum age of a transaction (in slots) before it expires. |
| `features.enableSettlementAccounts` | `boolean`            | --       | Enable settlement account support.                         |
| `features.enableDuplicateCheck`     | `boolean`            | --       | Prevent duplicate transaction submission.                  |
| `hooks`                             | `FacilitatorHooks[]` | --       | Lifecycle hooks for `afterVerify` and `afterSettle`.       |

```typescript theme={null}
const solanaHandler = await createFacilitatorHandler(
  "devnet",
  rpc,
  feePayerKeypair,
  mint,
  {
    maxRetries: 30,
    retryDelayMs: 1000,
    features: { enableDuplicateCheck: true },
    hooks: [
      {
        afterSettle: async ({ response, requirements, payment, logger }) => {
          logger.info("Settlement complete", { transaction: response.transaction })
        },
      },
    ],
  },
)
```

### EVM handler

```bash theme={null}
pnpm add @faremeter/payment-evm viem
```

```typescript theme={null}
import { createFacilitatorHandler } from "@faremeter/payment-evm/exact"

const evmHandler = await createFacilitatorHandler(
  { id: 84532, name: "base-sepolia", rpcUrls: { default: { http: [process.env.EVM_RPC_URL!] } } },
  process.env.EVM_PRIVATE_KEY!,
  "USDC",
)
```

**Signature:** `createFacilitatorHandler(chain, privateKey, assetNameOrInfo, opts?)`

| Parameter         | Type                      | Description                                                                  |
| ----------------- | ------------------------- | ---------------------------------------------------------------------------- |
| `chain`           | `ChainInfoWithRPC`        | Object with `id`, `name`, and `rpcUrls: { default: { http: [url] } }`.       |
| `privateKey`      | `string`                  | Hex-encoded private key for transaction submission.                          |
| `assetNameOrInfo` | `AssetNameOrContractInfo` | Asset name (e.g., `"USDC"`) or contract info object.                         |
| `opts`            | `object`                  | Optional. `network` (`KnownX402Network`) and `transport` (viem `Transport`). |

## Multi-chain configuration

Wire handlers for each chain together and pass them to `createFacilitatorRoutes`:

```typescript theme={null}
import { Hono } from "hono"
import { createFacilitatorRoutes } from "@faremeter/facilitator"
import { createFacilitatorHandler as createSolanaHandler } from "@faremeter/payment-solana/exact"
import { createFacilitatorHandler as createEvmHandler } from "@faremeter/payment-evm/exact"
import { createSolanaRpc } from "@solana/kit"
import { getKeypairFromFile } from "@solana-program/node-helpers"
import { PublicKey } from "@solana/web3.js"

// Solana
const rpc = createSolanaRpc(process.env.SOLANA_RPC_URL!)
const feePayerKeypair = await getKeypairFromFile(process.env.SOLANA_PAYER_KEYPAIR!)
const solanaMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")

// EVM
const chain = { id: 84532, name: "base-sepolia", rpcUrls: { default: { http: [process.env.EVM_RPC_URL!] } } }

const handlers = [
  await createSolanaHandler("devnet", rpc, feePayerKeypair, solanaMint),
  await createEvmHandler(chain, process.env.EVM_PRIVATE_KEY!, "USDC"),
]

const app = new Hono()
app.route("/", createFacilitatorRoutes({ handlers }))

export { app }
```

Each handler implements the `FacilitatorHandler` interface:

```typescript theme={null}
import type { FacilitatorHandler, GetRequirementsArgs } from "@faremeter/types/facilitator"

interface FacilitatorHandler {
  capabilities?: HandlerCapabilities
  schemes?: string[] // x402 schemes this handler can settle
  getSupported?: () => Promise<x402SupportedKind>[]
  getSigners?: () => Promise<Record<string, string[]>>
  getRequirements: (args: GetRequirementsArgs) => Promise<x402PaymentRequirements[]>
  handleVerify?: (requirements, payment) => Promise<x402VerifyResponse | null>
  handleSettle: (requirements, payment) => Promise<x402SettleResponse | null>
}
```

Handlers return `null` for payment schemes they do not support, allowing the facilitator to route to the correct handler.

The optional `capabilities` field declares the networks and assets a handler supports, and the separate optional `schemes` field declares the x402 schemes the handler can settle. The middleware uses both to route `ResourcePricing` entries to the right handler without calling it. While optional on the interface, both are effectively required for in-process usage — the middleware skips handlers that don't declare them on its dispatch path.

## Using handlers in-process

The same handlers you pass to `createFacilitatorRoutes` can be passed directly to the middleware via `x402Handlers`, eliminating the need for a separate facilitator process. See the [Middleware Overview](/server/middleware-overview) for details.

If you have an existing remote facilitator and want to use it through the in-process interface, wrap it with `createHTTPFacilitatorHandler`:

```typescript theme={null}
import { createHTTPFacilitatorHandler } from "@faremeter/middleware/http-handler"

const handler = createHTTPFacilitatorHandler("https://facilitator.corbits.dev", {
  capabilities: { networks: ["solana:devnet"], assets: ["USDC"] },
})
```

## Lifecycle hooks

Hooks let you run custom logic after the facilitator verifies or settles a payment. Use them for logging, analytics, webhooks, or downstream side effects.

Pass an array of hook objects to the handler's `hooks` option. Each object can define `afterVerify`, `afterSettle`, or both:

```typescript theme={null}
const handler = await createFacilitatorHandler(
  "devnet",
  rpc,
  feePayerKeypair,
  mint,
  {
    hooks: [
      {
        afterVerify: async ({ requirements, response, payment, logger }) => {
          logger.info("Payment verified", {
            scheme: requirements.scheme,
            isValid: response.isValid,
          })
        },
        afterSettle: async ({ response, requirements, payment, logger }) => {
          logger.info("Payment settled", { transaction: response.transaction })
          // Send a webhook, update a database, trigger fulfillment, etc.
        },
      },
    ],
  },
)
```

### Hook parameters

Both hooks receive a context object:

| Parameter      | Available in                 | Description                                                                                                                                                              |
| -------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `requirements` | `afterVerify`, `afterSettle` | The matched payment requirements.                                                                                                                                        |
| `payment`      | `afterVerify`, `afterSettle` | The decoded payment payload from the client.                                                                                                                             |
| `logger`       | `afterVerify`, `afterSettle` | A structured logger instance from `@faremeter/logs`.                                                                                                                     |
| `response`     | `afterVerify`, `afterSettle` | The operation result. In `afterVerify`: `{ isValid, payer?, invalidReason? }`. In `afterSettle`: `{ success, transaction, network, payer?, errorReason?, extensions? }`. |

### Multiple hooks

You can pass multiple hook objects. They run in order:

```typescript theme={null}
hooks: [
  { afterSettle: async (ctx) => { /* logging */ } },
  { afterSettle: async (ctx) => { /* webhook */ } },
]
```

### Hook behavior

Hooks can optionally **replace the response** by returning a new value. If a hook returns a non-`undefined` value, that value becomes the response passed to subsequent hooks and returned to the client.

<Warning>
  Hook errors are **not** caught by the facilitator — a throwing hook will cause the request to fail with a 500 error, even though the payment may have already been verified or settled on-chain. Ensure your hooks handle their own errors internally if you want settlement to proceed regardless.
</Warning>

## Settlement accounts

The `enableSettlementAccounts` feature (Solana only) enables an intermediate settlement account flow. When enabled, payments are first settled into a facilitator-controlled settlement account rather than directly to the merchant's `payTo` address. This is useful when the facilitator needs to hold funds temporarily before distributing them — for example, to support refunds, escrow patterns, or fee splitting.

```typescript theme={null}
const solanaHandler = await createFacilitatorHandler(
  "devnet",
  rpc,
  feePayerKeypair,
  mint,
  {
    features: { enableSettlementAccounts: true },
  },
)
```

<Note>
  Settlement accounts are an advanced feature. For most integrations, leave this disabled and payments will settle directly to the `payTo` address.
</Note>

## Environment configuration

Facilitator handlers typically need:

* RPC endpoint URLs for each chain
* Private keys for transaction submission (the facilitator submits transactions on behalf of clients)
* Token contract addresses

Configure these via environment variables:

```bash theme={null}
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_PAYER_KEYPAIR=~/.config/solana/facilitator.json
EVM_RPC_URL=https://sepolia.base.org
EVM_PRIVATE_KEY=0x...
```

## Facilitator endpoints

| Endpoint     | Method | Purpose                                                            |
| ------------ | ------ | ------------------------------------------------------------------ |
| `/accepts`   | POST   | Enriches payment requirements with facilitator-specific fields.    |
| `/verify`    | POST   | Verifies a payment without settling.                               |
| `/settle`    | POST   | Verifies and settles a payment on-chain. Returns transaction hash. |
| `/supported` | GET    | Lists supported schemes, networks, and assets.                     |

## Further reading

* [Facilitators concept](/concepts/facilitators) -- How facilitators fit into the x402 flow.
* [Middleware Overview](/server/middleware-overview) -- How middleware connects to the facilitator.
