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

# Solana Batch Settlement

> Metered Solana payments with merchant-signed vouchers, public shared-lane settlement, and USDC payout.

Batch settlement lets a customer fund a reusable Solana channel and pay for multiple requests before the merchant claims and distributes the accumulated charges. It supports metered APIs, inference, and other services where the final charge is known after the work finishes.

<Note>
  PayAI's facilitator support is a public preview with bounded channel capacity. The shared lane requires no PayAI account or API key. Use an API key for paid usage, account attribution, or an assigned enterprise lane. The client and server implementation is available in the official upstream `@x402/core` and `@x402/svm` npm packages.
</Note>

## 1. Choose public or authenticated access

The public shared lane accepts facilitator `POST /verify` and `POST /settle` without an `Authorization` header. This includes deposits, claims, distributions, merchant seals, refunds, and exact-request recovery. Customers and merchants do not need a PayAI account to use that lane.

Authentication is optional. Create a key in the [merchant portal](https://merchant.payai.network) when you need paid usage beyond the free allowance, dashboard attribution, or an assigned enterprise lane, then follow the [authentication guide](/x402/facilitators/authentication). If an `Authorization` header is present, PayAI validates it; an invalid, expired, or revoked credential returns **401 Unauthorized** instead of silently falling back to anonymous access. An authenticated request refused by policy may return **403**. These are separate from the **402 Payment Required** challenge your service sends to its customer.

`GET /supported` is public. Read it without authentication:

```bash theme={null}
curl https://facilitator.payai.network/supported
```

Find the entry with `x402Version: 2`, `scheme: "batch-settlement"`, and your Solana network. Its `extra` contains the shared-lane `feePayer` and `batchPolicy`; it does not require an API key. Preserve this fee payer for the channel's lifetime. During an admission pause, the batch entry is removed from discovery while existing channels can still be paid out and closed using their saved terms.

Enterprise accounts should send their merchant API JWT when calling `/supported` to discover their assigned lane and on every later `/verify` and `/settle` request for that channel. Applications should use the returned address rather than hardcode signer indices. Anonymous requests are restricted to the shared lane; a supplied credential cannot silently downgrade there when validation fails. Batch and exact payments share each lane's fee-payer and queue capacity.

## 2. Prepare your USDC receiving account

The wallet in `payTo` must have its **USDC associated token account (ATA) on the selected network before customers open channels**. Creating it requires a one-time SOL rent deposit. The facilitator sponsors channel setup and transaction fees; it does not create the merchant's receiving ATA during settlement.

You can create the ATA yourself and fund its rent, or use the [merchant portal](https://merchant.payai.network). Link the wallet you will use as `payTo` and follow the token-account setup flow. The portal may sponsor the first linked Solana wallet's USDC ATA when eligible and within its daily allowance; manual creation currently defaults to **200 credits**. Check the portal's displayed price and eligibility. A separate voucher-signing operator does not replace the receiving wallet's ATA.

The free settlement allowance is not a signup grant of portal credits. Check your credit balance in the portal.

## 3. Install the upstream SDK

SVM batch settlement is included in the official upstream [`@x402/core`](https://www.npmjs.com/package/@x402/core) and [`@x402/svm`](https://www.npmjs.com/package/@x402/svm) packages starting with version 2.28.0. Pin both packages to the same release for a reproducible integration:

```bash theme={null}
npm install @x402/core@2.28.0 @x402/svm@2.28.0 @solana/kit@^5.5.1
```

Commit the resulting lockfile. The packages and SVM batch-settlement exports are maintained by the [x402 Foundation upstream repository](https://github.com/x402-foundation/x402); the [SVM batch-settlement specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/batch-settlement/scheme_batch_settlement_svm.md) is the protocol source of truth. PayAI operates the facilitator described in this guide but does not own or publish these SDK packages. Keep `@x402/core` and `@x402/svm` on the same version when upgrading.

Use `BatchSvmScheme` from `@x402/svm/batch-settlement/server` for the merchant, and the same-named class from `@x402/svm/batch-settlement/client` for the customer. For merchant-signed vouchers, configure the server's `operator` signer. The terms identify `voucherSigner: "server"` and the operator's public key.

The operator can authorize up to the channel's escrow, so every customer must trust it explicitly and cap the total deposit held under it:

```typescript theme={null}
import { BatchSvmScheme } from "@x402/svm/batch-settlement/client";

const batch = new BatchSvmScheme(payerSigner, {
  serverSignedChannelsPolicy: {
    allowedOperators: [process.env.MERCHANT_BATCH_OPERATOR!],
    maxDeposit: "$1",
  },
});

client.register("solana:*", batch);
client.registerPolicy(batch.paymentPolicy);
```

An unlisted operator is refused. When the same resource offers a client-signed alternative, the SDK falls back to it. `maxDeposit` caps the channel's total escrow, including top-ups; it is separate from PayAI's 100 USDC admission ceiling for each initial deposit.

The merchant should self-manage its receiver-authorizer key and pass it as `receiverAuthorizer` in the server scheme. The SDK advertises and binds that key when the channel opens, then uses it to authorize cooperative refunds and merchant seal. Keep both the operator and receiver-authorizer keys until every associated channel drains.

## 4. Set channel terms and durable storage

The public preview limits are below. Read `batchPolicy` from `/supported` for the active values; admission can pause when capacity or sponsorship funds are unavailable.

| Limit | Public preview |
| - | - |
| Asset | USDC on Solana mainnet |
| New channel deposit | 0.01–100 USDC, expressed as `10000`–`100000000` atomic units |
| Open or closing channels per merchant identity | 10. Authenticated usage is shared across the account's API keys; public usage is grouped by Solana receiving address. In-flight reservations count. |
| Deposit attempts, including new top-ups | 5 per minute per merchant identity and network; public traffic also has a 10/minute/IP anti-abuse ceiling |
| Withdrawal grace period | 900–86,400 seconds (15 minutes–24 hours) |
| Facilitator idle cleanup window | 72 hours (259,200 seconds) since the last facilitator touch |
| Service-wide sponsored channel capacity | 1,000, including open channels, closing channels and in-flight reservations |

The initial deposit limit applies when opening a new channel; it is not a lifetime channel-balance ceiling. A new top-up counts as a deposit attempt but does not consume another channel slot. Recovering an existing operation must not allocate another charge or channel. Ordinary metered requests use the merchant's off-chain channel state, so the deposit rate limit is not an inference/request throughput limit.

The 72-hour idle timer measures time since the last facilitator touch and is independent of the withdrawal grace period. A longer grace period increases how long the customer may wait for withdrawal after close begins. Keep merchant redemption frequent: choosing a 24-hour grace does not mean waiting 24 hours to pay the merchant.

Set the client SDK's `depositAmount` explicitly within these limits; its default may be only one request's price. `PaymentRequirements.amount` is the **maximum authorized charge for one request**, not the channel's deposit. Reserve that ceiling before work, then settle with the measured `amount` in the range zero through the ceiling. Serialize merchant-signed requests within each channel. Use multiple channels for parallel work until the upstream operation store supports ordered concurrent cumulative vouchers.

Implement the exported `ChannelStore` and `BatchOperationStore` contracts with shared Redis, PostgreSQL, or equivalent atomic storage. The included memory stores are for local tests only. A custom durable `BatchOperationStore` must retain the original reservation and persist the completed operation's optional `response` field; make those updates atomic. Without the stored response, completed-response replay is unavailable for that record. Keep the store durable, backed up, shared between replicas, and protected from eviction. Keep client channel and pending-request records durable too.

When a completed server-signed request is retried with the same stable request key, matching payment payload and unchanged charge ceiling, the SDK returns the stored x402 settlement response with `extra.replayed: true` and skips a second merchant-handler execution. This replay works after the original proof expires. A completed legacy record with no stored response, or a retry with a changed ceiling, returns `duplicate_settlement` instead of creating a new settlement.

The SDK does not store or replay the protected resource's response body. Your application must retain that result for each stable request key and return it when the request is retried. Commit application effects and the replayable resource result atomically where possible, or make the underlying work idempotent. Payment storage alone cannot provide exactly-once application effects.

The customer's reusable authorization is a bearer credential for the channel's lifetime. Protect it like a secret. The operator can sign cumulative charges up to the channel's funded balance; the signed voucher returned in the payment response provides evidence of a charge, but does not make the on-chain program enforce your application's pricing. Use an operator key with appropriate custody and rotation procedures, and retain it until its channels are drained.

## 5. Claim, distribute, and verify payout

Run the exported `BatchChannelManager` against the same durable store and facilitator client. The shared lane works without credentials; attach authentication when using an assigned enterprise lane or paid account usage. Configure its `rpcUrl` or `readPayoutWatermark` callback so it can reconcile the confirmed paid state before updating merchant bookkeeping. It claims saved vouchers, then distributes settled funds to the receiving ATA. Use a cadence comfortably inside the withdrawal grace period, such as 10–60 seconds, and alert on failed or delayed redemption. Preserve each channel's original network, asset, receiver, operator, fee-payer, and access mode.

A successful **claim is not merchant payment**. The subsequent **distribution** must succeed, and the merchant ATA's USDC balance must increase by the expected amount. Compare signed receipts, cumulative channel state, distribution transaction identities, and actual USDC receipts. Replayed operations must not be counted as new volume.

One channel per distribution request is supported. Use it when you need to attribute an on-chain USDC payout to one channel; aggregating several channels into one transaction yields a recipient-level token balance delta rather than a channel-level receipt.

A distribution request sweeps what is currently owed. An identical later request can therefore pay a newer claim; it is not an immutable payout ID. The request format does not require a payout idempotency key or payout watermarks. A recovered response returns the original transaction's actual `amount`, not zero. Deduplicate accounting by network, transaction, asset and recipient instead of summing successful HTTP responses. A response for an older payout must not mark a newer claim paid.

The public preview supports at most four channels per claim transaction. Configure `maxChannelsPerBatch` accordingly. For shutdown, flush claim and distribution work, initiate close, wait through the grace period, and verify the remaining customer balance is refunded and channel rent is reclaimed. Continue recovery workers while new admission is paused.

For new channels, advertise a merchant `receiverAuthorizer` in the payment terms and retain its signing key. The facilitator binds that key at the first deposit; a key supplied only in a later close request is not trusted. Configure the same signer as `BatchChannelManager.closeAuthorizer`. If a claim encounters a payer-initiated Closing channel, the manager submits its latest retained voucher as a merchant-authenticated `seal` during withdrawal grace. You can call `sealClosingChannel(channelId)` directly. The facilitator applies `settle_and_seal` and distributes in one transaction; verify the merchant USDC receipt and payer refund from that transaction. Channels opened before the key binding was stored cannot use merchant seal; keep ordinary claims current before any close.

PayAI's facilitator deployment routes confirmed cooperative seal payouts through `onDistributionConfirmed` and records cooperative-refund distributions. The facilitator persists the payout amount and callback marker across retries, and returns `settlement_pending` while durable accounting still needs to finish. Keep callback implementations idempotent by transaction.

A merchant outage can still lose revenue: PayAI does not retain unclaimed vouchers on the merchant's behalf, and idle cleanup only sees the on-chain settled watermark. Keep vouchers and claim intents durable, run the claim worker frequently, and alert when it falls behind. The 72-hour idle window does not extend the withdrawal grace of a channel already closing.

## Recovery and limit errors

| Response | Action |
| - | - |
| 401, invalid API-key reason | Remove the `Authorization` header to use the public shared lane, or refresh/fix the signed JWT for authenticated usage. |
| 400, `batch_initial_deposit_out_of_range` or `batch_withdraw_delay_out_of_range` | Adjust new-channel terms to the published policy and obtain a new customer signature. |
| 400, `batch_deposit_asset_not_supported` | Use the network's advertised USDC mint. |
| 429, `batch_account_channel_capacity_exhausted` | Reuse a funded channel or finish closing an existing one. |
| 429, `batch_channel_capacity_exhausted` or `batch_deposit_rate_limited` | Respect `Retry-After` and retry with backoff. |
| 403, `batch_account_admission_paused` | Contact PayAI; keep existing-channel recovery and payout running. |
| 503, `batch_new_channels_disabled` | New admission is paused. Continue recovery, claims, payouts, and close operations. |
| 503, `batch_signer_funding_headroom_exhausted` | Sponsorship is temporarily unavailable. Respect `Retry-After`; keep existing-channel payout, recovery and cleanup running. |
| `settlement_pending`, `duplicate_settlement`, or a lost HTTP response | Retain the exact payment payload and any transaction signature; reconcile by retrying that payload unchanged. A changed charge ceiling is a different request and cannot recover the completed response. |

Do not allocate another charge or build a replacement transaction merely because a response was lost. Retain the official SDK's original payment payload and retry it unchanged; PayAI's facilitator preserves pending transaction identity so the combined flow can reconcile the outcome after restart. A later cumulative watermark alone does not prove that a particular request succeeded. Keep recovery records and retry promptly. PayAI retains unresolved signatures and signed bytes without automatic expiry; completed results retain a 24-hour recovery window. Keep permanent receipts in your own storage. An expired blockhash plus missing transaction history does not authorize a replacement payment. Restore history or contact PayAI if the outcome remains unknown.

There is no operation-status lookup endpoint in this preview. Retrying the exact payment payload remains the transaction-reconciliation mechanism after a timeout. Reconcile payouts using the confirmed transaction signature, channel membership, cumulative watermark, and receiver USDC balance delta. A retry response alone is not a new payment.

## Preview deprecation policy

For a planned shutdown of new batch-channel admission, PayAI will give at least **30 days' notice**. Admission can be paused sooner for an incident or safety issue. For every channel admitted before admission stops, PayAI will continue claims, merchant payouts, customer refunds, closure, cleanup, and transaction recovery until the channel has drained. Keep the admitted channel terms, keys, and merchant voucher records until closure is verified.

Automatic cleanup can pay already-claimed funds during channel closure; it cannot claim vouchers held only by an offline merchant. Keep your redemption worker running inside the withdrawal grace period. PayAI records background payouts in the same transaction-deduplicated ledger. If a receiving ATA also receives the customer refund or treasury sweep, aggregate credits cannot reliably separate those transfers; that case requires reconciliation rather than being reported as a zero payout.

## Test before accepting customer traffic

Start with a small mainnet channel on the public shared lane, or with production credentials when testing an assigned lane. Exercise partial charges, concurrent requests, receipt verification, lost responses, process restart, payout to the actual receiving ATA, customer refund, and closure before you raise the deposit size. Confirm your ATA, wallet balances, durable stores, payout worker, and alerting before opening it to customers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.