diff --git a/specs/extensions/deferred-voucher-store.md b/specs/extensions/deferred-voucher-store.md new file mode 100644 index 0000000000..e828424fb6 --- /dev/null +++ b/specs/extensions/deferred-voucher-store.md @@ -0,0 +1,226 @@ +# Extension: `deferred-voucher-store` + +## Summary + +The `deferred-voucher-store` extension enables facilitators to store vouchers on behalf of resource servers. This simplifies server implementation by removing the need for voucher storage infrastructure while allowing servers to retain full control over when to initiate on-chain collection. + +This extension is only applicable to the `deferred` payment scheme. + +--- + +## Facilitator Advertisement + +Facilitators advertise this capability in their `/supported` response: + +```json +{ + "kinds": [ + { "scheme": "deferred", "network": "eip155:84532" } + ], + "extensions": ["deferred-voucher-store"] +} +``` + +--- + +## Voucher Management + +Servers opt into facilitator-managed voucher storage by setting `voucherStorage: facilitator` in `PaymentRequirements.extra`. Servers should check that the facilitator supports this extension before configuring it. + +```json +{ + "extra": { + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "name": "USDC", + "version": "2", + "voucherStorage": "facilitator" + } +} +``` + +The extension modifies behavior for some of the standard endpoints and adds additional ones to facilitate voucher management. + +### Modified Endpoint Behavior + +When this extension is active, the following standard endpoints behave differently: + +#### GET /deferred/buyers/:buyer + +Returns stored voucher data in addition to on-chain account data: + +**Response (with stored voucher):** +```json +{ + "balance": "10000000", + "assetAllowance": "5000000", + "assetPermitNonce": "0", + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 2, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." +} +``` + +Without this extension, `voucher` and `signature` fields would be absent from the response. + +#### POST /settle + +When `PaymentRequirements.extra.voucherStorage === "facilitator"`, the facilitator stores the voucher as part of settlement. + +### Additional Endpoints + +The extension provides endpoints for servers to query and retrieve stored vouchers. + +#### GET /deferred/vouchers + +Query vouchers with filters. + +**Query Parameters:** +- `buyer` (optional): Filter by buyer address +- `seller` (optional): Filter by seller address +- `asset` (optional): Filter by asset address +- `chainId` (optional): Filter by chain ID +- `escrow` (optional): Filter by escrow contract address + +**Example Request:** +``` +GET /deferred/vouchers?seller=0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D&chainId=84532 +``` + +**Response (200 OK):** +```json +{ + "vouchers": [ + { + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 5, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." + } + ] +} +``` + +#### GET /deferred/vouchers/:id + +Get all vouchers in a series by voucher ID. + +**Path Parameters:** +- `id`: Voucher ID (bytes32) + +**Query Parameters:** +- `chainId` (required): Chain ID +- `escrow` (required): Escrow contract address + +**Example Request:** +``` +GET /deferred/vouchers/0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc?chainId=84532&escrow=0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27 +``` + +**Response (200 OK):** +```json +{ + "vouchers": [ + { + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "1000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740670000, + "nonce": 1, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x1a2b3c4d..." + }, + { + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "3000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740672000, + "nonce": 2, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x2b3c4d5e..." + }, + { + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 3, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." + } + ] +} +``` + +#### GET /deferred/vouchers/:id/:nonce + +Get a specific voucher by ID and nonce. + +**Path Parameters:** +- `id`: Voucher ID (bytes32) +- `nonce`: Voucher nonce (uint256) + +**Query Parameters:** +- `chainId` (required): Chain ID +- `escrow` (required): Escrow contract address + +**Example Request:** +``` +GET /deferred/vouchers/0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc/3?chainId=84532&escrow=0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27 +``` + +**Response (200 OK):** +```json +{ + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 3, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." +} +``` + +**Response (404 Not Found):** +```json +{ + "error": "Voucher not found" +} +``` diff --git a/specs/schemes/deferred/scheme_deferred.md b/specs/schemes/deferred/scheme_deferred.md new file mode 100644 index 0000000000..f51692657d --- /dev/null +++ b/specs/schemes/deferred/scheme_deferred.md @@ -0,0 +1,13 @@ +# Scheme: `deferred` + +## Summary + +`deferred` is a scheme designed to support trust minimized micro-payments. Unlike the `exact` scheme, which requires a payment to be executed immediately and fully on-chain, `deferred` allows clients to issue signed vouchers (IOUs) off-chain, which can later be aggregated and redeemed by the seller. This scheme enables payments smaller than the minimum feasible on-chain transaction cost. + +`deferred` payment scheme requires the seller to store and manage the buyer's vouchers until their eventual on chain settlement. To simplify their setup sellers might choose to offload this task to trusted third parties providing these services, i.e facilitators. + +## Example Use Cases + +- AI agents or automated clients. +- Consuming an API requiring micro cent cost per request. +- Any case where payments are smaller than on-chain settlement costs. diff --git a/specs/schemes/deferred/scheme_deferred_evm.md b/specs/schemes/deferred/scheme_deferred_evm.md new file mode 100644 index 0000000000..6d547edae2 --- /dev/null +++ b/specs/schemes/deferred/scheme_deferred_evm.md @@ -0,0 +1,438 @@ +# Scheme: `deferred` on `EVM` + +## Summary + +The `deferred` scheme on EVM chains uses `EIP-712` signed vouchers to represent payment commitments from a buyer to a seller. Before issuing vouchers, the buyer deposits funds—denominated in a specific `ERC-20` token—into an on-chain escrow earmarked for the seller. Each voucher authorizes a payment against that escrow balance, and explicitly specifies the asset being used. +Sellers can collect and aggregate these signed messages over time, choosing when to redeem them on-chain and settling the total amount in a single transaction. The funds in the escrow contract are subject to a thawing period when withdrawing, this gives sellers guarantee they will be able to redeem in time. +Interactions with the escrow contract for the buyer (depositing, thawing and withdrawing funds) are all performed via signed authorizations to remove the need for gas and blockchain access. These authorizations are executed and translated into on-chain actions by the facilitator. +This design enables efficient, asset-flexible micropayments without incurring prohibitive gas costs for every interaction. + + +## Protocol sequencing + +The deferred scheme follows the standard x402 flow with a key difference: payments are stored off-chain as signed vouchers during the main resource request flow but collected on-chain later in a deferred way. + +### Resource Request Flow + +1. **Client** sends an HTTP request to a **resource server**, optionally including a `PAYER-IDENTIFIER` header to identify themselves. + +2. **Resource server** responds with `402 Payment Required`. The response follows the standard x402 v2 format with a `resource` object and `accepts` array. The `PaymentRequirements.extra` field includes scheme-specific static information (e.g., EIP-712 domain info) and, if the buyer identified themselves via `PAYER-IDENTIFIER`, buyer state including escrow balance and voucher history. + +3. **Client** creates a signed `voucher` based on the `PaymentRequirements` and embeds it into the `PaymentPayload`. The `PaymentPayload` follows x402 v2 format with `resource`, `accepted` (the selected `PaymentRequirements`), and `payload` (the scheme-specific data). The voucher can be an aggregation on top of a previous one, or a new one if there is no pre-existing history. Optionally, the client can include a signed `deposit` authorization for gasless escrow top-up. + +4. **Client** sends the HTTP request with the `PAYMENT-SIGNATURE` header containing the `PaymentPayload`. + +5. **Resource server** verifies the `PaymentPayload` is valid either via local verification or by POSTing the `PaymentPayload` and `PaymentRequirements` to the `/verify` endpoint of a `facilitator`. + +6. **Facilitator** verifies the payload and voucher are valid and returns a `VerifyResponse`. + +7. If valid, **resource server** performs the work to fulfill the request. + +8. **Resource server** settles the payment either locally or by POSTing the `PaymentPayload` and `PaymentRequirements` to the `/settle` endpoint of a `facilitator`. Note that the payment is not actually collected at this stage, the voucher is stored locally or at the facilitator for deferred on-chain collection. If the `PaymentPayload` included a `deposit` authorization, it is executed on-chain at this point. + +9. **Resource server** returns `200 OK` with the resource and a `PAYMENT-RESPONSE` header. + +### On-Chain Settlement Flow + +On-chain settlement is decoupled from the request flow and triggered at the seller's or facilitator's discretion: + +1. **Resource server or Facilitator** decides to settle vouchers based on a threshold, schedule, or manual trigger. + +2. **Resource server or Facilitator** collects by interacting with the escrow contract on the blockchain. This transfers payment from the buyer's escrow balance to the seller. + +## `PAYER-IDENTIFIER` header + +The `PAYER-IDENTIFIER` header allows buyers to provide **unauthenticated identification** to servers before signing any voucher or message. This enables sellers to customize the payment requirements based on pre-existing voucher history with the buyer—either to initiate a new voucher or to retrieve an existing one for further aggregation. + +Note that this header requires no proof of identity; the seller assumes the buyer is who they claim to be. This is not a problem since vouchers contain no private information and the payment flow will later require valid signatures, which an impostor won't be able to forge. + +The header contains the buyer's EVM address as a simple string: + +``` +PAYER-IDENTIFIER: 0x209693Bc6afc0C5328bA36FaF03C514EF312287C +``` + +The buyer needs to add this header when initially requesting access to a resource. Failing to provide the header will result in new vouchers being created on each interaction, defeating the purpose of the `deferred` scheme. + +## `PaymentRequirements` for `deferred` + +In addition to the standard x402 `PaymentRequirements` fields, the `deferred` scheme on EVM requires the following in the `extra` field: + +### Static Metadata + +- `escrow`: Address of the escrow contract (address) +- `name`: EIP-712 domain name for the asset (string) +- `version`: EIP-712 domain version for the asset (string) +- `voucherStorage`: Declares where the seller intends to store vouchers, either `"server"` or `"facilitator"` + +### Dynamic Per-Buyer Data + +When a `PAYER-IDENTIFIER` header is provided, the scheme injects buyer-specific state: + +- `account` (optional): Current escrow account details for the buyer-seller-asset tuple + - `balance`: Current escrow balance in atomic token units + - `assetAllowance`: Current token allowance for the escrow contract + - `assetPermitNonce`: Current permit nonce for the token contract +- `voucher` (optional): The latest voucher for this buyer-seller-asset tuple +- `signature` (optional): The signature of the latest voucher + +If `voucher` and `signature` are present, the client should aggregate onto the existing voucher. If absent, the client should create a new voucher. + +### Examples + +**New voucher (no existing state):** + +```json +{ + "extra": { + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "name": "USDC", + "version": "2", + "voucherStorage": "server" + } +} +``` + +**Aggregation (with existing voucher):** + +```json +{ + "extra": { + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "name": "USDC", + "version": "2", + "voucherStorage": "server", + "account": { + "balance": "5000000", + "assetAllowance": "115792089237", + "assetPermitNonce": "0" + }, + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "2000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 3, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b6c1d8e9c0f64f8724e5cfb8bfe9a3cdb1ad6e4a876f7d418e47e96b11a23346a1b0e60c8d3a4c4fd0150a244ab4b0e6d6c5fa4103f8fa8fd2870a3c81b" + } +} +``` + +## `PaymentPayload` `payload` Field + +The `payload` field of the `PaymentPayload` header must contain the following fields: + +- `signature`: The signature of the `EIP-712` voucher. +- `voucher`: parameters required to reconstruct the signed message for the operation. +- `deposit` (optional): A signed authorization allowing the facilitator to deposit funds into escrow on behalf of the buyer. This enables gasless deposits for new buyers or when additional funds are needed. + +### Voucher Fields + +- `id`: Unique identifier for the voucher (bytes32) +- `buyer`: Address of the payment initiator (address) +- `seller`: Address of the payment recipient (address) +- `valueAggregate`: Total outstanding amount in the voucher, monotonically increasing (uint256) +- `asset`: ERC-20 token address (address) +- `timestamp`: Last aggregation timestamp (uint64) +- `nonce`: Incremented with each aggregation (uint256) +- `escrow`: Address of the escrow contract (address) +- `chainId`: Network chain ID (uint256) + +Example: + +```json +{ + "signature": "0x3a2f7e3b6c1d8e9c0f64f8724e5cfb8bfe9a3cdb1ad6e4a876f7d418e47e96b11a23346a1b0e60c8d3a4c4fd0150a244ab4b0e6d6c5fa4103f8fa8fd2870a3c81b", + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "2000000000000000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "timestamp": 1740673000, + "nonce": 3, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + } +} +``` + +Full `PaymentPayload` header (aggregation scenario, without deposit): + +```json +{ + "x402Version": 2, + "resource": { + "url": "https://api.example.com/resource", + "description": "Example resource", + "mimeType": "application/json" + }, + "accepted": { + "scheme": "deferred", + "network": "eip155:84532", + "amount": "1000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "payTo": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "maxTimeoutSeconds": 60, + "extra": { + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "name": "USDC", + "version": "2", + "voucherStorage": "server", + "account": { + "balance": "5000000", + "assetAllowance": "115792089237", + "assetPermitNonce": "0" + }, + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "1000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "timestamp": 1740672000, + "nonce": 2, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b1c" + } + }, + "payload": { + "signature": "0x3a2f7e3b6c1d8e9c0f64f8724e5cfb8bfe9a3cdb1ad6e4a876f7d418e47e96b11a23346a1b0e60c8d3a4c4fd0150a244ab4b0e6d6c5fa4103f8fa8fd2870a3c81b", + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "2000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "timestamp": 1740673000, + "nonce": 3, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + } + }, + "extensions": {} +} +``` + +Note how the new voucher in `payload` has `nonce: 3` and `valueAggregate: "2000000"`, which is the previous voucher's `valueAggregate` plus the `accepted.amount`, and the `nonce` was incremented by 1. + +### Deposit Fields (optional) + +The `deposit` object enables gasless escrow deposits by allowing the facilitator to execute deposits on behalf of the buyer. This is particularly useful for first-time buyers or when escrow balance needs to be topped up. + +The object structure consists of two parts: + +- **`authorization`**: Authorizes the escrow contract to deposit a specific amount of tokens on behalf of the buyer for a specific seller. This is an EIP-712 signed message that the escrow contract verifies before accepting the deposit. + +- **`permit`** (optional): An EIP-2612 permit that grants the escrow contract approval to transfer the buyer's tokens. This is only needed if the buyer hasn't already approved the escrow contract to spend their tokens. If the buyer has sufficient allowance, the permit can be omitted. + +The facilitator executes the deposit by first calling `permit` (if provided) to set the token allowance, then calling the escrow's deposit function with the `authorization`. + +**`authorization` fields:** +- `buyer`: Address of the buyer authorizing the deposit (address) +- `seller`: Address of the seller receiving the escrow deposit (address) +- `asset`: ERC-20 token contract address (address) +- `amount`: Amount to deposit in atomic token units (uint256) +- `nonce`: Unique bytes32 for replay protection (bytes32) +- `expiry`: Authorization expiration timestamp (uint64) +- `signature`: EIP-712 signature of the authorization (bytes) + +**`permit` fields (optional):** +- `owner`: Token owner address (address) +- `spender`: Escrow contract address (address) +- `value`: Token amount to approve (uint256) +- `nonce`: Token contract nonce for the permit (uint256/bigint) +- `deadline`: Permit expiration timestamp (uint256) +- `domain`: Token's EIP-712 domain + - `name`: Token name (string) + - `version`: Token version (string) +- `signature`: EIP-2612 signature of the permit (bytes) + +Example `PaymentPayload` header with deposit: + +```json +{ + "x402Version": 2, + "resource": { + "url": "https://api.example.com/resource", + "description": "Example resource", + "mimeType": "application/json" + }, + "accepted": { + "scheme": "deferred", + "network": "eip155:84532", + "amount": "1000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "payTo": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "maxTimeoutSeconds": 60, + "extra": { + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "name": "USDC", + "version": "2", + "voucherStorage": "server", + "account": { + "balance": "0", + "assetAllowance": "0", + "assetPermitNonce": "0" + } + } + }, + "payload": { + "signature": "0x3a2f7e3b6c1d8e9c0f64f8724e5cfb8bfe9a3cdb1ad6e4a876f7d418e47e96b11a23346a1b0e60c8d3a4c4fd0150a244ab4b0e6d6c5fa4103f8fa8fd2870a3c81b", + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "1000000", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "timestamp": 1740673000, + "nonce": 1, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "deposit": { + "permit": { + "owner": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "spender": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "value": "5000000", + "nonce": "0", + "deadline": 1740759400, + "domain": { + "name": "USD Coin", + "version": "2" + }, + "signature": "0x8f9e2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f1b" + }, + "authorization": { + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "asset": "0x081827b8c3aa05287b5aa2bc3051fbe638f33152", + "amount": "5000000", + "nonce": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "expiry": 1740759400, + "signature": "0xbfdc3d0ae7663255972fdf5ce6dfc7556a5ac1da6768e4f4a942a2fa885737db5ddcb7385de4f4b6d483b97beb6a6103b46971f63905a063deb7b0cfc33473411b" + } + } + }, + "extensions": {} +} +``` + +## Verification + +The following steps are required to verify a `deferred` payment: + +1. **Signature validation**: Verify the EIP-712 signature is valid +2. **Payment requirements matching**: + - Verify `paymentPayload.accepted.scheme` is `"deferred"` + - Verify `paymentPayload.accepted.network` matches `paymentRequirements.network` + - Verify `paymentRequirements.payTo` matches `paymentPayload.payload.voucher.seller` + - Verify `paymentPayload.payload.voucher.asset` matches `paymentRequirements.asset` + - Verify `paymentPayload.payload.voucher.chainId` matches the chain specified by `paymentRequirements.network` +3. **Voucher aggregation validation** (if aggregating an existing voucher): + - Verify `nonce` equals the previous `nonce + 1` + - Verify `valueAggregate` is equal to the previous `valueAggregate + paymentRequirements.amount` + - Verify `timestamp` is greater than the previous `timestamp` + - Verify `buyer`, `seller`, `asset`, `escrow` and `chainId` all match the previous voucher values +4. **Amount validation**: + - Verify `paymentPayload.payload.voucher.valueAggregate` is enough to cover `paymentRequirements.amount` plus previous voucher value aggregate if it's an aggregate voucher +5. **Escrow balance check**: + - Verify the `buyer` has enough of the `asset` (ERC20 token) in the escrow to cover the valueAggregate in the `paymentPayload.payload.voucher` + - Verify `id` has not been already collected in the escrow, or if it has, that the new balance is greater than what was already paid (in which case the difference will be paid) +6. **Deposit validation** (if present): + - Verify the `paymentPayload.payload.deposit.authorization.signature` is a valid EIP-712 signature + - Verify `paymentPayload.payload.deposit.authorization.buyer` matches `paymentPayload.payload.voucher.buyer` + - Verify `paymentPayload.payload.deposit.authorization.seller` matches `paymentPayload.payload.voucher.seller` + - Verify `paymentPayload.payload.deposit.authorization.asset` matches `paymentPayload.payload.voucher.asset` + - Verify `paymentPayload.payload.deposit.authorization.expiry` has not passed + - Verify the nonce has not been used before by checking the escrow contract + - If `paymentPayload.payload.deposit.permit` is present: + - Verify the `paymentPayload.payload.deposit.permit.signature` is a valid EIP-2612 signature + - Verify the permit nonce is valid by checking the token contract + - Verify `permit.owner` matches the buyer + - Verify `permit.spender` matches the escrow contract address + - Verify `permit.value` is sufficient to cover the deposit amount + - Verify `permit.deadline` has not passed +7. **Transaction simulation** (optional but recommended): + - Simulate the voucher collection to ensure the transaction would succeed on-chain + +## Settlement + +Settlement in the `deferred` scheme occurs during the standard x402 `/settle` flow but does **not** transfer funds on-chain. Instead, it: + +1. **Executes deposit** (if `deposit` is included in the payment payload): The facilitator executes the deposit authorization to ensure the buyer has sufficient funds escrowed before storing the voucher. + +2. **Stores the voucher**: Depending on the `voucherStorage` configuration: + - `"server"`: The server stores the voucher locally. + - `"facilitator"`: The facilitator stores the voucher on behalf of the server (facilitator must support this through an extension). + +The voucher is now held for later on-chain collection. + +## Collection + +Collection is the on-chain settlement of vouchers, performed separately from the request flow. This is what actually transfers funds from the buyer's escrow balance to the seller. + +The facilitator calls the `collect` function on the escrow contract with the voucher and signature. This can be initiated by: +- **Seller request**: The seller explicitly requests collection of their vouchers +- **Automatic trigger**: The facilitator collects based on pre-agreed conditions (threshold, schedule, etc.) + +Multiple vouchers can be collected in a single transaction using the `collectMany` function, reducing gas costs. + +## Funds Recovery + +Buyers can recover unused funds from their escrow accounts through the **flush** mechanism. This enables gasless fund recovery by signing an authorization that the facilitator executes on their behalf. + +### How It Works + +1. **Buyer signs** a `FlushAuthorization` (EIP-712) +2. **Buyer submits** to facilitator's custom endpoint +3. **Facilitator executes** `flushWithAuthorization` on the escrow contract +4. **Contract performs** two operations: + - Withdraws any funds that have already completed thawing + - Starts thawing any remaining balance + +To fully recover funds, the buyer will need to flush at least twice. First time the thawing will be initiated, next one will withdraw the funds. + +## Limitations + +### Sequential Request Requirement + +The nonce-based voucher aggregation design requires sequential requests. Each new voucher must have `nonce = previous_nonce + 1`, meaning clients cannot issue multiple concurrent requests for the same buyer-seller-asset tuple. + +**Workaround:** Clients needing parallelism can use multiple voucher IDs (different `id` values) to maintain separate voucher series. + +## Appendix + +### A. Escrow Contract Specification + +The `deferred` scheme requires an on-chain escrow contract that: +- Holds buyer deposits earmarked for specific sellers +- Processes voucher collection +- Enforces thawing period for withdrawals +- Supports gasless operations via signed authorizations + +Full specification: [DeferredPaymentEscrow specification](./scheme_deferred_evm_escrow_contract.md) + +### B. Facilitator Custom Endpoints + +The `deferred` scheme requires facilitator endpoints beyond the standard x402 `/verify` and `/settle`: + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/deferred/buyers/:buyer` | GET | Query on-chain account data and voucher state | +| `/deferred/buyers/:buyer/flush` | POST | Execute gasless fund recovery for buyer | +| `/deferred/vouchers/collect` | POST | Submit vouchers for on-chain settlement | + +Full specification: [Deferred Facilitator specification](./scheme_deferred_evm_facilitator.md) + +### C. `deferred-voucher-store` (Facilitator Extension) + +Optional extension indicating the facilitator can store vouchers on behalf of servers. +Full specification: [`deferred-voucher-store` extension](../../extensions/deferred-voucher-store.md) \ No newline at end of file diff --git a/specs/schemes/deferred/scheme_deferred_evm_escrow_contract.md b/specs/schemes/deferred/scheme_deferred_evm_escrow_contract.md new file mode 100644 index 0000000000..ef81267705 --- /dev/null +++ b/specs/schemes/deferred/scheme_deferred_evm_escrow_contract.md @@ -0,0 +1,227 @@ + +# DeferredPaymentEscrow Contract Specification + +## Summary + +The `DeferredPaymentEscrow` contract enables micropayments using an escrow-based voucher system. Buyers deposit ERC-20 tokens into escrow accounts for specific sellers, then issue off-chain EIP-712 signed vouchers that sellers can redeem against those deposits. This approach allows for efficient aggregation of many small payments before on-chain settlement, while maintaining security through cryptographic signatures and time-bounded withdrawals. + +The contract is designed for scenarios where payments are frequent but small (micropayments), making individual on-chain transactions economically inefficient. It provides strong guarantees to both parties: buyers retain control over their deposited funds through a thawing mechanism, while sellers can collect payments immediately when vouchers are presented. + +## Contract Overview + +The contract manages deposits, withdrawals, and voucher redemption: + +- **Deposits**: Buyers deposit ERC-20 tokens for specific sellers +- **Vouchers**: Off-chain signed promises to pay that aggregate over time +- **Collection**: Sellers redeem vouchers against escrow balances +- **Withdrawal**: Buyers can withdraw unused funds after a thawing period +- **Authorizations**: EIP-712 signed operations for gasless interactions (designed for x402 Facilitators to be able to abstract escrow management actions from buyers) + +## Data Structures + +### EscrowAccount +```solidity +struct EscrowAccount { + uint256 balance; // Total deposited balance (includes thawing amount) + uint256 thawingAmount; // Amount currently thawing for withdrawal (subset of balance) + uint64 thawEndTime; // When thawing completes +} +``` + +**Important**: The `balance` field represents the total amount of tokens held in the escrow account, which includes any amount currently thawing. The `thawingAmount` is a subset of the `balance` that has been marked for withdrawal after the thawing period. Available funds for new thawing operations = `balance - thawingAmount`. + +### Voucher +```solidity +struct Voucher { + bytes32 id; // Unique identifier per buyer-seller pair + address buyer; // Payment initiator + address seller; // Payment recipient + uint256 valueAggregate; // Total accumulated amount (monotonically increasing) + address asset; // ERC-20 token address + uint64 timestamp; // Last aggregation timestamp + uint256 nonce; // Incremented with each aggregation + address escrow; // This contract's address + uint256 chainId; // Network chain ID +} +``` + +## Account Structure + +The contract uses a triple-nested mapping to organize escrow accounts: +``` +buyer → seller → asset → EscrowAccount +``` + +This structure ensures: +- Each buyer-seller pair has independent accounts +- Different assets (tokens) are tracked separately +- Clean separation of concerns between relationships + +## Payment Flow + +### 1. Deposit Phase +``` +Buyer → deposit(seller, asset, amount) → Escrow Contract + +OR + +Buyer → depositWithAuthorization(auth, signature) → Escrow Contract +``` + +### 2. Service & Voucher Phase +``` +Buyer ↔ Seller (off-chain interactions) +Buyer → signs Voucher(id, valueAggregate, ...) → Seller +``` + +### 3. Collection Phase +``` +Seller → collect(voucher, signature) → Escrow Contract +Escrow Contract → transfer(asset, amount) → Seller +``` + +### 4. Withdrawal Phase (if needed) +``` +Buyer → thaw(seller, asset, amount) → Escrow Contract +[wait THAWING_PERIOD] +Buyer → withdraw(seller, asset) → Escrow Contract + +OR + +Buyer → flushWithAuthorization(auth) → Escrow Contract +``` + +## Verification + +To verify a payment in the `deferred` scheme: + +1. **Signature Validation**: Verify the voucher signature using EIP-712 and ERC-1271 +2. **Contract Verification**: Ensure `voucher.escrow` matches the expected contract address +3. **Chain Verification**: Ensure `voucher.chainId` matches the current network +4. **Balance Check**: Verify escrow account has sufficient balance for collection +5. **Aggregation Validation**: Ensure `voucher.valueAggregate >= previous_collections` + +## Settlement + +Settlement occurs when sellers call the `collect` function: + +1. **Validation**: Contract validates voucher parameters and signature +2. **Amount Calculation**: Determines collectable amount based on: + - Total voucher value (`valueAggregate`) + - Previously collected amounts for this voucher ID + - Available balance in escrow account +3. **State Updates**: Records new collected amount and updates escrow balance +4. **Transfer**: Sends tokens directly to seller +5. **Events**: Emits collection events for off-chain tracking + +### Partial Collection + +If escrow balance is insufficient for the full voucher amount: +- Contract collects only the available amount +- Voucher remains valid for future collection of remaining amount +- Prevents voucher failures due to temporary fund shortages + +**Note for Sellers**: Before accepting a voucher off-chain, sellers should verify that the escrow account has sufficient balance to cover the voucher amount. This can be checked using `getOutstandingAndCollectableAmount(voucher)` which returns both the outstanding amount owed and the amount that can actually be collected immediately. + +## Withdrawal Protection + +The thawing mechanism protects sellers from sudden fund withdrawals: + +1. **Thaw Initiation**: Buyer calls `thaw(seller, asset, amount)` (calling `thaw()` multiple times will add to the thawing amount and reset the timer) +2. **Thawing Period**: Set at contract deployment (standard value is 1 day, though other escrow instances can be deployed with different thawing periods if needed) +3. **Seller Collection**: Sellers can still collect from full balance during thawing +4. **Withdrawal**: After thawing period, buyer can withdraw thawed amount +5. **Cancellation**: Buyers can cancel thawing at any time before completion + +## Authorization System + +### Gasless Operations + +The contract supports EIP-712 signed authorizations for gasless operations, designed for x402: + +### Deposit Authorization +Allows x402 Facilitators to execute deposits on behalf of buyers: +```solidity +struct DepositAuthorization { + address buyer; // Who is authorizing + address seller; // Recipient + address asset; // Token to deposit + uint256 amount; // Amount to deposit + bytes32 nonce; // Random bytes32 for replay protection + uint64 expiry; // Authorization expiration +} +``` + +### Flush Authorization +Enables x402 Facilitators to "flush" funds for buyers (withdraws any funds that have completed thawing, then starts thawing any remaining balance): +```solidity +struct FlushAuthorization { + address buyer; // Who is authorizing + address seller; // Specific account to flush + address asset; // Specific asset to flush + bytes32 nonce; // Random bytes32 for replay protection + uint64 expiry; // Authorization expiration +} +``` + +### Flush All Authorization +Allows batch withdrawal from all of a buyer's escrow accounts (performs flush operation on every account): +```solidity +struct FlushAllAuthorization { + address buyer; // Who is authorizing + bytes32 nonce; // Random bytes32 for replay protection + uint64 expiry; // Authorization expiration +} +``` + +## Contract Interface + +### Core Functions + +### Deposits +- `deposit(address seller, address asset, uint256 amount)` - Direct deposit +- `depositTo(address buyer, address seller, address asset, uint256 amount)` - Third-party deposit +- `depositMany(address asset, DepositInput[] deposits)` - Batch deposits +- `depositWithAuthorization(DepositAuthorization auth, bytes signature)` - Gasless deposit + +### Withdrawals +- `thaw(address seller, address asset, uint256 amount)` - Initiate withdrawal +- `cancelThaw(address seller, address asset)` - Cancel ongoing thaw +- `withdraw(address seller, address asset)` - Complete withdrawal +- `flushWithAuthorization(FlushAuthorization auth, bytes signature)` - Gasless specific flush +- `flushAllWithAuthorization(FlushAllAuthorization auth, bytes signature)` - Gasless batch flush + +### Collections +- `collect(Voucher voucher, bytes signature)` - Single voucher redemption +- `collectMany(SignedVoucher[] vouchers)` - Batch voucher redemption + +### View Functions +- `getAccount(address buyer, address seller, address asset)` → `EscrowAccount` - Get escrow account details +- `getAccountDetails(address buyer, address seller, address asset, bytes32[] voucherIds, uint256[] valueAggregates)` → `(uint256 balance, uint256 allowance, uint256 nonce)` - Get account details including available balance after accounting for pending vouchers, token allowance, and permit nonce. Returns: + - `balance`: Available escrow balance minus thawing amount and minus amounts needed for the provided voucher collections + - `allowance`: Current token allowance granted to the escrow contract + - `nonce`: Current EIP-2612 permit nonce for the buyer on the asset token contract +- `getVoucherCollected(address buyer, address seller, address asset, bytes32 voucherId)` → `uint256` - Get total amount already collected for this voucher ID +- `getOutstandingAndCollectableAmount(Voucher voucher)` → `(uint256 outstanding, uint256 collectable)` - Returns outstanding amount still owed and amount that can be collected immediately given current escrow balance +- `isVoucherSignatureValid(Voucher voucher, bytes signature)` → `bool` - Validate voucher signature +- `isDepositAuthorizationValid(DepositAuthorization auth, bytes signature)` → `bool` - Validate deposit authorization signature +- `isFlushAuthorizationValid(FlushAuthorization auth, bytes signature)` → `bool` - Validate flush authorization signature +- `isFlushAllAuthorizationValid(FlushAllAuthorization auth, bytes signature)` → `bool` - Validate flush all authorization signature + +### Constants +- `THAWING_PERIOD()` → `uint256` - Withdrawal thawing period (immutable, set at deployment) +- `MAX_THAWING_PERIOD()` → `uint256` - Maximum allowed thawing period (30 days) +- `DOMAIN_SEPARATOR()` → `bytes32` - EIP-712 domain separator + +## Appendix + +### Multi-Chain Deployment + +While each contract instance operates on a single chain, the design supports multi-chain deployments: +- Vouchers include `chainId` for chain-specific validation +- Contract will be deployed using Safe Singleton Factory for deterministic addresses across chains +- Cross-chain coordination must be handled at the application layer + +### Reference Implementation + +A reference implementation for this contract is provided with this repository, it can be found at [DeferredPaymentEscrow]() [TODO: update this when the implementation is ready] \ No newline at end of file diff --git a/specs/schemes/deferred/scheme_deferred_evm_facilitator.md b/specs/schemes/deferred/scheme_deferred_evm_facilitator.md new file mode 100644 index 0000000000..1af4b68c24 --- /dev/null +++ b/specs/schemes/deferred/scheme_deferred_evm_facilitator.md @@ -0,0 +1,172 @@ +# Deferred Facilitator Specification + +## Summary + +This specification defines the custom REST API endpoints that facilitators must implement to support the `deferred` payment scheme. These endpoints complement the standard x402 `/verify` and `/settle` endpoints. + +All endpoints are served under the facilitator's deferred scheme namespace: `${FACILITATOR_URL}/deferred/` + +## Authentication + +Read-only endpoints do not require authentication. Information retrieved by these endpoints is either publicly available on-chain or will be eventually. +Write endpoints rely on verification of signed messages rather than traditional authentication. See each endpoint for details. + +## Endpoints + +### GET /buyers/:buyer + +Retrieves on-chain account data for a specific buyer, including escrow balance, asset allowance, and permit nonce. If the facilitator supports the `deferred-voucher-store` extension, also returns the latest voucher for the buyer-seller-asset tuple. + +**Query Parameters:** +- `seller` (required): Seller address +- `asset` (required): Asset (token) address +- `escrow` (required): Escrow contract address +- `chainId` (required): Chain ID + +**Example Request:** +``` +GET /deferred/buyers/0x209693Bc6afc0C5328bA36FaF03C514EF312287C?seller=0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D&asset=0x036CbD53842c5426634e7929541eC2318f3dCF7e&escrow=0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27&chainId=84532 +``` + +**Response (200 OK):** +```json +{ + "balance": "10000000", + "assetAllowance": "5000000", + "assetPermitNonce": "0" +} +``` + +**Response (200 OK - with voucher, when facilitator supports `deferred-voucher-store` extension):** +```json +{ + "balance": "10000000", + "assetAllowance": "5000000", + "assetPermitNonce": "0", + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 2, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." +} +``` + +**Response (400 Bad Request):** +```json +{ + "error": "Invalid parameters" +} +``` + +### POST /buyers/:buyer/flush + +Executes gasless fund recovery for a buyer using a signed flush authorization. This operation: +1. Withdraws any funds that have completed their thawing period +2. Initiates thawing for any remaining balance + +**Path Parameters:** +- `buyer` (required): Buyer address + +**Request Body (Specific Flush):** +```json +{ + "authorization": { + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "nonce": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": 1740759400 + }, + "signature": "0xbfdc3d0a...", + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 +} +``` + +**Request Body (Flush All):** +```json +{ + "authorization": { + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "nonce": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": 1740759400 + }, + "signature": "0xbfdc3d0a...", + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 +} +``` + +**Response (200 OK):** +```json +{ + "success": true, + "transaction": "0xabc123...", + "network": "eip155:84532" +} +``` + +**Response (400 Bad Request):** +```json +{ + "success": false, + "errorReason": "reason", + "transaction": "", + "network": "eip155:84532" +} +``` + + +### POST /vouchers/collect + +Submits vouchers for on-chain settlement by calling the escrow contract's `collect` function. Accepts an array of vouchers to enable batch collection. + +**Request Body:** +```json +{ + "vouchers": [ + { + "voucher": { + "id": "0x9f8d3e4a2c7b9d04dcd11c9f4c2b22b0a6f87671e7b8c3a2ea95b5dbdf4040bc", + "buyer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", + "seller": "0xA1c7Bf3d421e8A54D39FbBE13f9f826E5B2C8e3D", + "valueAggregate": "5000000", + "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + "timestamp": 1740673000, + "nonce": 2, + "escrow": "0x7cB1A5A2a2C9e91B76914C0A7b7Fb3AefF3BCA27", + "chainId": 84532 + }, + "signature": "0x3a2f7e3b..." + } + ] +} +``` + +**Response (200 OK):** +```json +{ + "success": true, + "transaction": "0xabc123...", + "network": "eip155:84532", + "payer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C" +} +``` + +**Response (400 Bad Request):** +```json +{ + "success": false, + "errorReason": "", + "transaction": "", + "network": "eip155:84532", + "payer": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C" +} +``` diff --git a/specs/schemes/deferred/scheme_deferred_evm_voucher_store.md b/specs/schemes/deferred/scheme_deferred_evm_voucher_store.md new file mode 100644 index 0000000000..395e5ef335 --- /dev/null +++ b/specs/schemes/deferred/scheme_deferred_evm_voucher_store.md @@ -0,0 +1,167 @@ +# Voucher Store Specification + +## Summary + +The Voucher Store is a critical component of the x402 `deferred` payment scheme that manages the persistence and retrieval of signed payment vouchers and their settlement records. It serves as the data layer for sellers and facilitators to track off-chain payment obligations and their eventual on-chain settlements. + +This specification defines the interface and requirements for implementing a voucher store in the deferred EVM payment system, ensuring consistent behavior across different implementations (in-memory, database-backed, etc.). + +## Overview + +The voucher store manages three key concepts: + +1. **Vouchers**: EIP-712 signed payment commitments from buyers to sellers +2. **Voucher Series**: A sequence of vouchers sharing the same ID but with different nonces (representing aggregations) +3. **Collections**: Records of on-chain settlements for vouchers + +## Data Model + +### Voucher Structure + +A voucher contains the following fields: + +| Field | Type | Description | +|-------|------|-------------| +| `id` | bytes32 | Unique identifier for the voucher series | +| `buyer` | address | Address of the payment initiator | +| `seller` | address | Address of the payment recipient | +| `valueAggregate` | uint256 | Total accumulated amount (monotonically increasing) | +| `asset` | address | ERC-20 token contract address | +| `timestamp` | uint64 | Last aggregation timestamp | +| `nonce` | uint256 | Incremented with each aggregation | +| `escrow` | address | Address of the escrow contract | +| `chainId` | uint256 | Network chain ID | +| `signature` | bytes | EIP-712 signature of the voucher | + +### Collection Structure + +A collection record contains: + +| Field | Type | Description | +|-------|------|-------------| +| `voucherId` | bytes32 | The voucher series ID | +| `voucherNonce` | uint256 | The specific voucher nonce | +| `transactionHash` | bytes32 | On-chain settlement transaction hash | +| `collectedAmount` | uint256 | Amount actually collected on-chain | +| `asset` | address | ERC-20 token contract address | +| `chainId` | uint256 | Network chain ID | +| `collectedAt` | uint64 | Collection timestamp | + +## Core Operations + +### 1. Voucher Storage + +**Operation**: `storeVoucher(voucher)` + +**Purpose**: Persist a new signed voucher received from a buyer. + +**Requirements**: +- MUST reject duplicate vouchers (same id + nonce combination) +- MUST validate all required fields are present +- SHOULD validate signature format (but not cryptographic validity) +- MUST return error if storage fails + +**Use Case**: When a seller receives a new payment voucher from a buyer, either for a new series or an aggregation of an existing series. + +### 2. Voucher Retrieval + +#### Single Voucher Lookup + +**Operation**: `getVoucher(id, nonce?)` + +**Purpose**: Retrieve a specific voucher or the latest in a series. + +**Behavior**: +- When `nonce` provided: Return exact voucher matching (id, nonce) +- When `nonce` omitted: Return voucher with highest nonce for the given id +- Return `null` if no matching voucher exists + +**Use Case**: Get the details of a voucher. + +#### Series Retrieval + +**Operation**: `getVoucherSeries(id, pagination)` + +**Purpose**: Retrieve all vouchers in a series for audit or history tracking. + +**Requirements**: +- MUST return vouchers sorted by nonce (descending - newest first) +- MUST support pagination with configurable limit and offset +- MUST return empty array for non-existent series + +**Pagination Options**: +- `limit`: The maximum number of vouchers to return +- `offset`: The offset of the first voucher to return + +**Use Case**: Display payment history, audit trail, or analyze aggregation patterns. + +#### Query-Based Retrieval + +**Operation**: `getVouchers(query, pagination)` + +**Purpose**: Find vouchers matching specific criteria. + +**Query Options**: +- `buyer`: Filter by buyer address +- `seller`: Filter by seller address +- `latest`: If true, return only highest nonce per series + +**Pagination Options**: +- `limit`: The maximum number of vouchers to return +- `offset`: The offset of the first voucher to return + +**Sorting**: +- Primary: By nonce (descending) +- Secondary: By timestamp (descending) + +**Use Case**: Dashboard views, account reconciliation, payment analytics. + +### 3. Available Voucher Discovery + +**Operation**: `getAvailableVoucher(buyer, seller)` + +**Purpose**: Find the most suitable voucher for aggregation in a new payment. + +**Selection Algorithm**: +1. Filter vouchers matching exact buyer and seller +2. For each series, select the voucher with highest nonce +3. Among selected vouchers, return the one with most recent timestamp +4. Return `null` if no vouchers match + +**Use Case**: When a seller needs to determine which existing voucher to use to aggregate new payments from a returning buyer. + +### 4. Settlement Recording + +**Operation**: `settleVoucher(voucher, txHash, amount)` + +**Purpose**: Record that a voucher has been collected on-chain. + +**Requirements**: +- MUST store the settlement record +- MUST associate with correct voucher (id, nonce) +- MUST record actual collected amount (may differ from voucher amount) +- SHOULD allow multiple collections for same voucher (partial settlements) + +**Use Case**: After successful on-chain collection, record the settlement for reconciliation and tracking. + +### 5. Collection History + +**Operation**: `getVoucherCollections(query, pagination)` + +**Purpose**: Retrieve settlement history for vouchers. + +**Query Options**: +- `id`: Filter by voucher series ID +- `nonce`: Filter by specific nonce (requires id) + +**Pagination Options**: +- `limit`: The maximum number of vouchers to return +- `offset`: The offset of the first voucher to return + +**Use Case**: Reconcile on-chain settlements with off-chain vouchers, audit payment flows. + +## Appendix + +### Reference Implementation + +The `InMemoryVoucherStore` class in the x402 TypeScript package provides a reference implementation suitable for development and testing. Production implementations should follow the same interface while adding appropriate persistence, scaling, and security features. \ No newline at end of file