Documentation Index
Fetch the complete documentation index at: https://docs.x402.org/llms.txt
Use this file to discover all available pages before exploring further.
The batch-settlement scheme is for high-throughput payments where many requests are authorized individually and redeemed later in batches. The EVM implementation uses stateless unidirectional payment channels: a buyer deposits funds into onchain escrow once, then signs off-chain cumulative vouchers for each request. The seller verifies vouchers quickly and redeems them onchain later in batches.
Use batch-settlement for repeated paid API calls, usage-metered endpoints, and other workloads where many small payments would be too expensive or slow to settle one-by-one.
How It Works
- Deposit: the client opens or tops up a channel by depositing ERC-20 funds into escrow. Deposits use EIP-3009 or Permit2 and are submitted by the facilitator.
- Voucher: each paid request includes a signed cumulative voucher for the channel’s total claimable amount.
- Verify: the server verifies the voucher and serves the response without waiting for an onchain transfer.
- Claim: the server’s channel manager periodically claims the latest vouchers from many channels in one transaction.
- Settle: claimed funds are swept to the receiver in a separate settle transaction.
- Refund: idle channels can be cooperatively refunded after outstanding vouchers are claimed.
The route price is still a per-request maximum. For dynamic pricing, the server can charge less than that maximum with settlement overrides.
Server Setup
Register the scheme with a resource server, protect routes with scheme: "batch-settlement", configure server-side channel storage, and run a channel manager to claim, settle, and refund channels.
import { HTTPFacilitatorClient } from "@x402/core/server";
import { BatchSettlementEvmScheme, FileChannelStorage } from "@x402/evm/batch-settlement/server";
import { paymentMiddleware, setSettlementOverrides, x402ResourceServer } from "@x402/express";
const network = "eip155:84532" as const;
const facilitatorClient = new HTTPFacilitatorClient({ url: process.env.FACILITATOR_URL! });
const batchScheme = new BatchSettlementEvmScheme(receiverAddress, {
receiverAuthorizerSigner,
withdrawDelay: 86400,
storage: new FileChannelStorage({ directory: "./channels" }),
});
const resourceServer = new x402ResourceServer(facilitatorClient)
.register(network, batchScheme);
const channelManager = batchScheme.createChannelManager(facilitatorClient, network);
channelManager.start({
claimIntervalSecs: 60,
settleIntervalSecs: 300,
refundIntervalSecs: 3600,
maxClaimsPerBatch: 100,
});
app.use(paymentMiddleware({
"GET /weather": {
accepts: {
scheme: "batch-settlement",
price: "$0.01",
network,
payTo: receiverAddress,
},
description: "Weather data",
mimeType: "application/json",
},
}, resourceServer));
app.get("/weather", (req, res) => {
setSettlementOverrides(res, { amount: "50%" });
res.json({ report: { weather: "sunny", temperature: 70 } });
});
cfg := &batchedserver.BatchSettlementEvmSchemeServerConfig{
ReceiverAuthorizerSigner: receiverAuthorizerSigner,
WithdrawDelay: 86400,
Storage: batchedserver.NewFileChannelStorage(batchsettlement.FileChannelStorageOptions{
Directory: "./channels",
}),
}
scheme := batchedserver.NewBatchSettlementEvmScheme(receiverAddress, cfg)
manager := scheme.CreateChannelManager(facilitatorClient, x402.Network("eip155:84532"))
manager.Start(batchedserver.AutoSettlementConfig{
ClaimIntervalSecs: 60,
SettleIntervalSecs: 300,
RefundIntervalSecs: 3600,
MaxClaimsPerBatch: 100,
})
routes := x402http.RoutesConfig{
"GET /weather": {
Accepts: x402http.PaymentOptions{
{
Scheme: batchsettlement.SchemeBatched,
Price: "$0.01",
Network: x402.Network("eip155:84532"),
PayTo: receiverAddress,
},
},
Description: "Weather data",
MimeType: "application/json",
},
}
mux.HandleFunc("GET /weather", func(w http.ResponseWriter, r *http.Request) {
nethttpmw.SetSettlementOverrides(w, &x402.SettlementOverrides{Amount: "50%"})
_ = json.NewEncoder(w).Encode(map[string]any{
"report": map[string]any{"weather": "sunny", "temperature": 70},
})
})
Server Storage
Server storage keeps the latest voucher and channel session state that the channel manager uses to claim, settle, and refund later. In-memory storage is useful for local demos, but production servers should configure durable storage so outstanding vouchers survive restarts.
Use file storage for single-process deployments. For serverless or multi-instance deployments, use Redis/Valkey storage so channel updates are shared atomically across processes. See the Next.js batch-settlement Redis example for a full withX402 setup with RedisChannelStorage and cron-based claim/settle routes.
Client Setup
Register BatchSettlementEvmScheme for eip155:*. The client SDK handles deposits, voucher signing, channel recovery, and corrective 402 resync.
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { toClientEvmSigner } from "@x402/evm";
import { BatchSettlementEvmScheme } from "@x402/evm/batch-settlement/client";
import { createPublicClient, http } from "viem";
import { baseSepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
const signer = toClientEvmSigner(account, publicClient);
const batchScheme = new BatchSettlementEvmScheme(signer, {
depositPolicy: { depositMultiplier: 5 },
});
const client = new x402Client();
client.register("eip155:*", batchScheme);
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const response = await fetchWithPayment("https://api.example.com/weather");
import (
x402 "github.com/x402-foundation/x402/go"
x402http "github.com/x402-foundation/x402/go/http"
"github.com/ethereum/go-ethereum/ethclient"
batchedclient "github.com/x402-foundation/x402/go/mechanisms/evm/batch-settlement/client"
evmsigners "github.com/x402-foundation/x402/go/signers/evm"
)
ethClient, err := ethclient.Dial("https://sepolia.base.org")
if err != nil {
log.Fatal(err)
}
evmSigner, err := evmsigners.NewClientSignerFromPrivateKeyWithClient(os.Getenv("EVM_PRIVATE_KEY"), ethClient)
if err != nil {
log.Fatal(err)
}
batchScheme := batchedclient.NewBatchSettlementEvmScheme(evmSigner, &batchedclient.BatchSettlementEvmSchemeOptions{
DepositMultiplier: 5,
})
x402Client := x402.Newx402Client().
Register("eip155:*", batchScheme)
httpClient := x402http.WrapHTTPClientWithPayment(
http.DefaultClient,
x402http.Newx402HTTPClient(x402Client),
)
Deposit Policy
The deposit policy controls how much the client deposits when a channel needs funding or top-up.
| Field | Description |
|---|
depositMultiplier / DepositMultiplier | Deposits amount x multiplier for the advertised per-request maximum. The default is 5; the minimum is 3. |
depositStrategy / DepositStrategy | Optional callback for caps, dynamic deposits, or opting out of automatic top-up. |
const maxDeposit = 1_000_000n;
const batchScheme = new BatchSettlementEvmScheme(signer, {
depositPolicy: { depositMultiplier: 5 },
depositStrategy: ({ depositAmount }) => {
const amount = BigInt(depositAmount);
return amount > maxDeposit ? maxDeposit : undefined;
},
});
batchScheme := batchedclient.NewBatchSettlementEvmScheme(evmSigner, &batchedclient.BatchSettlementEvmSchemeOptions{
DepositMultiplier: 5,
DepositStrategy: func(_ context.Context, c batchedclient.DepositStrategyContext) (batchedclient.DepositStrategyResult, error) {
capped, _ := new(big.Int).SetString("1000000", 10)
proposed, _ := new(big.Int).SetString(c.DepositAmount, 10)
if proposed.Cmp(capped) > 0 {
return batchedclient.DepositStrategyResult{Amount: capped.String()}, nil
}
return batchedclient.DepositStrategyResult{}, nil
},
})
Voucher Signer Delegation
By default, vouchers are signed by the payer key. For higher-throughput clients, especially smart wallets using EIP-1271, delegate voucher signing to a dedicated EOA. The delegated address is committed into the channel as payerAuthorizer, which lets the facilitator verify vouchers with ECDSA recovery instead of an onchain smart-wallet signature check.
const voucherSigner = toClientEvmSigner(
privateKeyToAccount(process.env.EVM_VOUCHER_SIGNER_PRIVATE_KEY as `0x${string}`),
);
const batchScheme = new BatchSettlementEvmScheme(signer, {
voucherSigner,
});
voucherSigner, err := evmsigners.NewClientSignerFromPrivateKey(os.Getenv("EVM_VOUCHER_SIGNER_PRIVATE_KEY"))
if err != nil {
log.Fatal(err)
}
batchScheme := batchedclient.NewBatchSettlementEvmScheme(evmSigner, &batchedclient.BatchSettlementEvmSchemeOptions{
VoucherSigner: voucherSigner,
})
Client Persistence and Refunds
Client channel state is stored in memory by default. Persistent client storage is optional because the SDK can recover channel state through corrective 402 responses and onchain state on the next paid request. Long-lived clients can still persist state to avoid that recovery round trip after restarts.
import { FileClientChannelStorage } from "@x402/evm/batch-settlement/client";
const batchScheme = new BatchSettlementEvmScheme(signer, {
storage: new FileClientChannelStorage({ directory: "./channels" }),
});
await batchScheme.refund("https://api.example.com/weather");
await batchScheme.refund("https://api.example.com/weather", { amount: "1000000" });
import "github.com/x402-foundation/x402/go/mechanisms/evm/batch-settlement"
batchScheme := batchedclient.NewBatchSettlementEvmScheme(evmSigner, &batchedclient.BatchSettlementEvmSchemeOptions{
Storage: batchedclient.NewFileClientChannelStorage(batchsettlement.FileChannelStorageOptions{
Directory: "./channels",
}),
})
settle, err := batchScheme.Refund(ctx, "https://api.example.com/weather", &batchedclient.RefundOptions{
Amount: "1000000",
})
Receiver Authorizer
Every channel commits to a receiverAuthorizer, which signs claim and refund authorizations.
| Strategy | When to use it |
|---|
Self-managed receiverAuthorizerSigner | Recommended for production. Channels survive facilitator changes because any facilitator can relay your signed claims and refunds. |
| Facilitator-delegated authorizer | Simpler to run. If you switch facilitators, claim and refund old channels before opening new ones. |
Settlement Policy
Choose claim, settle, and refund intervals based on throughput and gas cost:
- Claim often enough that outstanding vouchers are claimed before a payer’s timed withdrawal can finalize after
withdrawDelay.
- Settle less often when gas savings matter more than cash-flow latency.
- Refund idle channels to return unclaimed balance to payers.
Set withdrawDelay greater than your claim cadence plus an operational safety margin.
Examples
Specs
See Also