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

# Flex Facilitator

> Run a Flex facilitator: validate authorizations, manage holds, and settle on-chain.

The facilitator is the operator of a Flex deployment. It accepts client-signed authorizations from middleware, validates them, holds the reserved capacity in memory, and submits + finalizes pending settlements on-chain. This page describes the operator-side primitives.

The full implementation lives in `@faremeter/payment-solana/flex/facilitator` and `@faremeter/flex-solana/facilitator`. For the protocol-level model, see [Flex Concepts](/flex/concepts).

## Two layers of API

| Layer               | Package                                      | Use it for                                                                |
| ------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| **Plug-in**         | `@faremeter/payment-solana/flex/facilitator` | A drop-in `FacilitatorHandler` that integrates with Faremeter middleware. |
| **Building blocks** | `@faremeter/flex-solana/facilitator`         | Hold manager, escrow accounting, split merging — for custom integrations. |

If you're standing up a Faremeter facilitator service that already speaks x402, start with the plug-in. If you're embedding Flex into a non-Faremeter system, use the building blocks directly.

## The plug-in: `createFacilitatorHandler`

```typescript theme={null}
import { createSolanaRpc } from "@solana/kit";
import { createKeyPairSignerFromBytes } from "@solana/kit";
import { createFacilitatorHandler } from "@faremeter/payment-solana/flex/facilitator";
import { address } from "@solana/kit";

const rpc = createSolanaRpc(rpcURL);
const facilitatorSigner = await createKeyPairSignerFromBytes(facilitatorBytes);

const handler = await createFacilitatorHandler(
  "devnet",
  rpc,
  facilitatorSigner,
  {
    supportedMints: [address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")],
    defaultSplits: [
      { recipient: "<merchant-token-account>", bps: 9500 },
      { recipient: "<facilitator-fee-token-account>", bps: 500 },
    ],
    maxSubmitRetries: 30,
    submitRetryDelayMs: 1000,
    minGracePeriodSlots: 150n,
    flushIntervalMs: 5000,
  },
);
```

The returned `FlexFacilitator` is a `FacilitatorHandler` (so it slots into Faremeter's facilitator service) plus three extras:

* `flush()` -- drain settled in-memory holds to chain immediately.
* `getHoldManager()` -- inspect the in-memory hold state (debugging, metrics).
* `stop()` -- cancel the background flush interval.

The handler runs a periodic flush in the background (`flushIntervalMs`), so most operators never need to call `flush()` themselves.

### Verify and settle

The handler implements the standard Faremeter `verify` / `settle` lifecycle:

1. **`verify`** -- decodes the client's `FlexPaymentPayload`, checks the Ed25519 signature, fetches escrow + session-key state, runs `tryHold` on the in-memory hold manager, and returns success if the reservation took. **The hold initially reserves the full `maxAmount`**; `settle` reduces it later. Plan escrow capacity against the ceiling, not the expected actual cost.

2. **`settle`** -- the middleware reports the actual amount consumed. The handler updates the hold's `settleAmount` (must be `<=` the previously held `maxAmount`), marks it `settled`, and the next flush pushes it on-chain. The handler returns the x402 settle response immediately; on-chain confirmation is asynchronous.

3. **`flush` (background)** -- collects all `settled` holds, builds `submit_authorization` instructions in batches, signs them with the facilitator key, and tracks confirmation. Once submitted, holds become `submitted`. After the refund window closes, the handler builds `finalize` instructions to release funds.

`PERMANENT_SUBMIT_ERRORS` (expired authorizations, invalid signatures, expiry-too-far, etc.) are not retried — the hold is dropped and an error is logged. Transient errors retry up to `maxSubmitRetries` times with `submitRetryDelayMs` between attempts.

## Middleware integration: `createUptoHandler`

For variable-amount endpoints (the canonical Flex use case), `@faremeter/payment-solana/flex/hono` exposes `createUptoHandler`. It takes an `authorize` callback that decides the maximum amount for a given request, then a `handle` callback that does the work and reports the actual amount via a `settle` function.

```typescript theme={null}
import { Hono } from "hono";
import { createUptoHandler } from "@faremeter/payment-solana/flex/hono";

const app = new Hono();

app.post(
  "/inference",
  createUptoHandler({
    facilitatorURL: "https://facilitator.example.com",
    accepts: [
      {
        scheme: "@faremeter/flex",
        network: "solana-devnet",
        amount: "100000", // ceiling per request
        asset: "USDC",
        payTo: "<merchant-token-account>",
      },
    ],
    authorize: async (body) => {
      const tokensRequested = (body as { maxTokens: number }).maxTokens;
      return BigInt(tokensRequested * 10); // 10 micro-USDC per token
    },
    handle: async (body, settle) => {
      const result = await runInference(body);
      const cost = BigInt(result.tokensUsed * 10); // actual cost
      await settle(cost);
      return new Response(JSON.stringify(result), {
        headers: { "content-type": "application/json" },
      });
    },
  }),
);
```

This pattern is the natural fit for AI inference, streaming responses, metered APIs, and any other workflow where the cost is only known after the work is done.

## Building blocks: hold manager

Use `createHoldManager` directly if you're building outside the Faremeter middleware abstraction.

```typescript theme={null}
import { createHoldManager } from "@faremeter/flex-solana/facilitator";

const holds = createHoldManager();

const result = holds.tryHold(
  {
    escrow,
    mint,
    settleAmount: 50_000n,
    maxAmount: 100_000n,
    authorizationId,
    expiresAtSlot,
    sessionKeyAddress,
    sessionKeyPDA,
    vault,
    splits,
    signatureBytes,
    message,
    payer,
    validUntilSlot,
  },
  vaultBalance, // fetched from chain
  onChainCommitted, // sum of pending settlements for this escrow+mint
  onChainPendingCount, // current pending_count from the escrow account
);

if (!result.ok) {
  // Insufficient capacity, pending limit reached, or duplicate auth ID.
  // Reject the request to the middleware.
}
```

A hold moves through five states managed by the manager:

```
held -> settled -> submitting -> submitted -> finalizing
```

| State        | Meaning                                                                 |
| ------------ | ----------------------------------------------------------------------- |
| `held`       | Reserved in memory; merchant work has not yet completed.                |
| `settled`    | Actual amount known; ready to be flushed on-chain.                      |
| `submitting` | A `submit_authorization` transaction is in flight.                      |
| `submitted`  | Pending settlement exists on-chain; waiting for refund window to close. |
| `finalizing` | A `finalize` transaction is in flight to release funds to recipients.   |

The manager enforces two invariants when accepting new holds: the sum of all unsubmitted hold amounts (per escrow + mint) must fit in the vault balance minus on-chain commitments, and the total pending count (in-memory + on-chain) must stay below `MAX_PENDING_SETTLEMENTS = 16`.

## Building blocks: accounting

`fetchEscrowAccounting` is the snapshot you take from chain to feed the hold manager:

```typescript theme={null}
import { fetchEscrowAccounting } from "@faremeter/flex-solana/facilitator";

const snapshot = await fetchEscrowAccounting(rpc, escrowAddress, [
  mintA,
  mintB,
]);

snapshot.vaultBalances; // Map<mint, bigint>
snapshot.holds; // HoldEntry[] from on-chain pending settlements
snapshot.totalPendingByMint; // Map<mint, bigint>
snapshot.pendingCount; // bigint
snapshot.maxPending; // 16
snapshot.availableByMint; // vault - on-chain pending, per mint
snapshot.canSubmit; // false if at MAX_PENDING_SETTLEMENTS
```

Snapshots are cheap to take but should be cached briefly per escrow (the plug-in caches for `snapshotMaxAgeMs`, default 10 seconds) to avoid hammering RPC.

## Building blocks: split merging

When the merchant runs the same recipient through multiple split entries (for example, a base fee + a referral kick-back going to the same operator), `mergeSplits` collapses duplicates:

```typescript theme={null}
import { mergeSplits } from "@faremeter/flex-solana/facilitator";

const merged = mergeSplits([
  { recipient: alice, bps: 6000 },
  { recipient: bob, bps: 2500 },
  { recipient: alice, bps: 1500 }, // alice again
]);
// -> [{ recipient: alice, bps: 7500 }, { recipient: bob, bps: 2500 }]
```

This matters because the on-chain program rejects splits with duplicate recipients (`FLEX_ERROR__DUPLICATE_SPLIT_RECIPIENT`).

## Operational considerations

* **Activity tracking.** `last_activity_slot` is only updated by `submit_authorization` and `refund` (both require facilitator signatures). `finalize` is permissionless — even cranking it forever doesn't keep the deadman timer alive. Make sure your facilitator submits or refunds at least once per `deadman_timeout_slots`, even on quiet escrows.
* **Refund-window hygiene.** Honor refund requests from middleware (failed deliveries, disputes) by issuing `refund` instructions during the window. Once the window closes, `finalize` is the only remaining lever.
* **Pending-count headroom.** With the cap at 16, a busy escrow saturates fast. Submit and finalize aggressively so middleware verify calls don't get rejected for capacity.
* **Session-key grace period.** The facilitator should surface a documented submission SLA so clients can pick a `revocation_grace_period_slots` that's at least 2x the SLA. The default minimum the plug-in enforces is `150` slots (\~1 minute).

## Further reading

* [Concepts](/flex/concepts) -- the protocol-level model.
* [Program Reference](/flex/program-reference) -- on-chain instructions, accounts, and constraints.
* [API Reference](/api/flex-solana.src) -- the full `@faremeter/flex-solana` surface.
* [Facilitators](/concepts/facilitators) -- how facilitators fit into Faremeter overall.
