Payment Verifier
On-chain nonce registry for x402 payment verification with batch support.
Overview
The X402PaymentVerifier is a thin nonce registry that records payment nonces with a minPrice commitment. Servers use this to verify that an agent committed to paying at least the required price, without knowing the actual encrypted transfer amount.
The contract is permissionless — any address can record a payment nonce. It also implements IERC7984Receiver for single-TX payment flows via confidentialTransferAndCall.
Recording a Payment
function recordPayment(
address server, // payment recipient
bytes32 nonce, // unique nonce (replay prevention)
uint64 minPrice // minimum price commitment
) external whenNotPaused
// Uses msg.sender as payer
// Reverts if nonce already used (NonceAlreadyUsed)
// Reverts if minPrice is 0 (ZeroMinPrice)
// Emits: PaymentVerified(payer, server, nonce, minPrice)The minPrice parameter is critical: the server checks that the committed price meets its required amount, even though the actual encrypted transfer is hidden.
Single-TX: Pay and Record
Instead of sending two separate transactions (transfer + record), agents can use payAndRecord or the callback-based confidentialTransferAndCall.
// Option 1: payAndRecord (requires operator approval)
function payAndRecord(
address token,
address server,
bytes32 nonce,
uint64 minPrice,
externalEuint64 encryptedAmount,
bytes calldata inputProof
) external whenNotPaused
// Transfers cUSDC from payer to server via operator mechanism
// Records nonce in same TX
// Emits: PayAndRecordCompleted(payer, server, nonce, token, minPrice)
// Option 2: confidentialTransferAndCall (callback)
// Agent calls cUSDC.confidentialTransferAndCall(verifier, amount, proof, data)
// Verifier receives callback via IERC7984Receiver:
function onConfidentialTransferReceived(
address operator,
address from,
euint64 amount,
bytes calldata data // abi.encode(server, nonce, minPrice)
) external returns (bytes4)
// Only callable by the trusted ConfidentialUSDC contractBatch Prepayment
Agents can prepay for multiple requests in a single transaction. The server grants credits and deducts them per-request without additional on-chain verification.
function recordBatchPayment(
address server,
bytes32 nonce,
uint32 requestCount, // number of prepaid requests
uint64 pricePerRequest // price per individual request
) external whenNotPaused
// Overflow-checked: requestCount * pricePerRequest must fit uint256
// Emits: BatchPaymentRecorded(payer, server, nonce, requestCount, pricePerRequest)// Prepay for 100 requests at 0.05 USDC each
const nonce = ethers.hexlify(ethers.randomBytes(32));
await verifier.recordBatchPayment(
serverAddress,
nonce,
100, // requestCount
50_000n // 0.05 USDC per request
);
// Server middleware tracks credits:
// - First request: verify on-chain, grant 100 credits
// - Requests 2-100: deduct credit, no on-chain check
// - Request 101: return 402 (credits exhausted)Nonce Management
// Check if nonce has been used
function usedNonces(bytes32 nonce) external view returns (bool)
// Get address that recorded a nonce
function nonceOwners(bytes32 nonce) external view returns (address)
// Cancel an unused nonce (only original recorder)
function cancelNonce(bytes32 nonce) external
// Emits: NonceCancelled(payer, nonce)Server Verification Flow
How the server verifies payment
- Decode the base64
Paymentheader from the client - Check scheme is
fhe-confidential-v1 - Check chain ID matches
- Check nonce is fresh (not replayed)
- Verify
ConfidentialTransferevent in the transfer TX receipt (from + to match) - Verify
PaymentVerifiedevent in the verifier TX receipt (minPrice >= required) - Check block confirmations >= minimum
- If all pass: grant access (200). If any fail: reject (402/403).
Events
event PaymentVerified(address indexed payer, address indexed server, bytes32 indexed nonce, uint64 minPrice);
event PayAndRecordCompleted(address indexed payer, address indexed server, bytes32 indexed nonce, address token, uint64 minPrice);
event BatchPaymentRecorded(address indexed payer, address indexed server, bytes32 indexed nonce, uint32 requestCount, uint64 pricePerRequest);
event NonceCancelled(address indexed payer, bytes32 indexed nonce);Custom Errors
| Error | When |
|---|---|
NonceAlreadyUsed | Nonce has already been recorded |
ZeroMinPrice | minPrice parameter is 0 |
ZeroRequestCount | Batch requestCount is 0 |
BatchOverflow | requestCount * pricePerRequest overflows |
UntrustedCaller | Callback not from trusted token |
ZeroAddress | Server address is zero |
NonceCancellationFailed | Caller is not nonce owner |