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 contract

Batch 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)
batch-example.tstypescript
// 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

  1. Decode the base64 Payment header from the client
  2. Check scheme is fhe-confidential-v1
  3. Check chain ID matches
  4. Check nonce is fresh (not replayed)
  5. Verify ConfidentialTransfer event in the transfer TX receipt (from + to match)
  6. Verify PaymentVerified event in the verifier TX receipt (minPrice >= required)
  7. Check block confirmations >= minimum
  8. 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

ErrorWhen
NonceAlreadyUsedNonce has already been recorded
ZeroMinPriceminPrice parameter is 0
ZeroRequestCountBatch requestCount is 0
BatchOverflowrequestCount * pricePerRequest overflows
UntrustedCallerCallback not from trusted token
ZeroAddressServer address is zero
NonceCancellationFailedCaller is not nonce owner