Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
404 changes: 404 additions & 0 deletions DEFERRED_ARCHITECTURE.md

Large diffs are not rendered by default.

707 changes: 707 additions & 0 deletions DEFERRED_ARCHITECTURE_V2.md

Large diffs are not rendered by default.

247 changes: 247 additions & 0 deletions specs/extensions/deferred-voucher-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# Extension: `deferred-voucher-store`

## Summary

The `deferred-voucher-store` extension enables voucher storage and retrieval for the `deferred` payment scheme. It provides a pluggable interface that allows servers to choose where vouchers are stored - locally or delegated to a facilitator.

This is a **Server ↔ Client** extension with optional **Facilitator** involvement for storage.

**Key Design Principle:** The extension defines WHAT operations are needed (store/retrieve vouchers). The server chooses WHERE storage happens by selecting an appropriate backend implementation. This addresses facilitator portability concerns - servers can store vouchers locally and switch facilitators without losing voucher state.

---

## PaymentRequired

Server advertises support and current voucher state:

```json
{
"extensions": {
"deferred-voucher-store": {
"info": {
"type": "aggregation",
"voucher": {
"id": "0x...",
"nonce": 5,
"valueAggregate": "5000000",
"buyer": "0x...",
"seller": "0x...",
"asset": "0x...",
"escrow": "0x...",
"timestamp": 1703123456,
"chainId": 84532
},
"signature": "0x...",
"account": {
"balance": "10000000",
"assetAllowance": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"assetPermitNonce": "0"
}
},
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"voucher": {
"type": "object",
"properties": {
"id": { "type": "string" },
"nonce": { "type": "integer" },
"valueAggregate": { "type": "string" },
"buyer": { "type": "string" },
"seller": { "type": "string" },
"asset": { "type": "string" },
"escrow": { "type": "string" },
"timestamp": { "type": "integer" },
"chainId": { "type": "integer" }
},
"required": ["id", "nonce", "valueAggregate", "buyer", "seller", "asset", "escrow", "timestamp", "chainId"]
},
"signature": { "type": "string" }
},
"required": ["voucher", "signature"]
}
}
}
}
```

---

## `info` Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | Response type: `"aggregation"` (existing voucher) or `"initial"` (no prior voucher) |
| `voucher` | object | No | Current voucher state (omitted when `type: "initial"`) |
| `signature` | string | No | Signature for current voucher (omitted when `type: "initial"`) |
| `account` | object | Yes | Buyer's escrow account information |

### `type` Values

- **`initial`**: No existing voucher for this buyer-seller-asset combination. Client creates a new voucher with `nonce: 0`.
- **`aggregation`**: Existing voucher found. Client increments `nonce` and adds payment amount to `valueAggregate`.

### `voucher` Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique identifier for this buyer-seller pair (bytes32 hex) |
| `nonce` | integer | Current nonce (client increments by 1) |
| `valueAggregate` | string | Total accumulated value (client adds payment amount) |
| `buyer` | string | Buyer address |
| `seller` | string | Seller/payTo address |
| `asset` | string | ERC-20 token contract address |
| `escrow` | string | Escrow contract address |
| `timestamp` | integer | Unix timestamp of last aggregation |
| `chainId` | integer | Network chain ID |

### `account` Fields

| Field | Type | Description |
|-------|------|-------------|
| `balance` | string | Buyer's current escrow balance |
| `assetAllowance` | string | Token allowance for escrow contract |
| `assetPermitNonce` | string | Current EIP-2612 permit nonce |

---

## PaymentPayload

Client echoes the extension with updated voucher:

```json
{
"extensions": {
"deferred-voucher-store": {
"voucher": {
"id": "0x...",
"nonce": 6,
"valueAggregate": "6000000",
"buyer": "0x...",
"seller": "0x...",
"asset": "0x...",
"escrow": "0x...",
"timestamp": 1703123500,
"chainId": 84532
},
"signature": "0x..."
}
}
}
```

The client:
1. Increments `nonce` by 1
2. Adds payment amount to `valueAggregate`
3. Updates `timestamp` to current time
4. Signs the voucher using EIP-712

---

## Server Behavior

### On PaymentRequired

1. Read buyer address from `payer-identifier` header (if available)
2. Query voucher store for existing voucher
3. Return `type: "initial"` or `type: "aggregation"` with current state
4. Include account balance information for client validation

### On Payment Verification

1. Validate voucher fields match expected values
2. Verify `nonce == previousNonce + 1`
3. Verify `valueAggregate >= previousValueAggregate + paymentAmount`
4. Forward to facilitator for signature verification and escrow balance check

### On Settlement Success

1. Store the new voucher and signature in the voucher store
2. This voucher becomes the baseline for the next aggregation

---

## Facilitator Support

Facilitators MAY advertise voucher storage capability:

```json
// GET /supported
{
"kinds": [...],
"extensions": ["deferred-voucher-store"]
}
```

When a facilitator supports this extension, servers MAY delegate storage operations to the facilitator instead of storing vouchers locally.

---

## VoucherStore Interface

Implementations MUST provide these operations:

```typescript
interface VoucherStore {
// Store a new/aggregated voucher after successful settlement
storeVoucher(voucher: Voucher, signature: string): Promise<void>;

// Get latest voucher for aggregation (by buyer-seller-asset)
getLatestVoucher(
buyer: string,
seller: string,
asset: string
): Promise<{ voucher: Voucher; signature: string } | null>;

// Get account balance information
getAccountInfo(
buyer: string,
seller: string,
asset: string,
escrow: string,
chainId: number
): Promise<AccountInfo>;
}
```

### Implementation Options

**Option A: Server stores locally (portable)**

```typescript
const extension = createDeferredVoucherStoreExtension({
store: new ServerVoucherStore(database)
});
```

**Option B: Server delegates to facilitator**

```typescript
const extension = createDeferredVoucherStoreExtension({
store: new FacilitatorVoucherStore(facilitatorClient)
});
```

The server chooses the backend. The extension interface remains the same.

---

## Security Considerations

- **Voucher Signatures**: Vouchers are EIP-712 signed by the buyer. The facilitator verifies signatures before approving payments.
- **Nonce Ordering**: Strict `nonce == previousNonce + 1` prevents replay and ensures ordering.
- **Escrow Balance**: The facilitator checks that escrow balance covers `valueAggregate` before verification succeeds.
- **Storage Integrity**: If using local storage, servers MUST ensure voucher data is not corrupted or lost. Lost vouchers cannot be reconstructed.
- **Facilitator Portability**: Servers storing vouchers locally can switch facilitators without losing state. Servers delegating storage to a facilitator are dependent on that facilitator's availability.

---

## Parallel Request Limitations

The current nonce-based design does not support parallel requests well. Each payment must wait for the previous voucher to be stored before the next nonce can be used.

Future versions may address this with:
- Nonce ranges (client reserves a range of nonces)
- Multiple voucher IDs per buyer-seller pair

73 changes: 73 additions & 0 deletions specs/extensions/payer-identifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Extension: `payer-identifier`

## Summary

The `payer-identifier` extension allows clients to provide **unauthenticated identification** to servers when initially requesting access to x402-protected resources. Servers can use this claimed identity to customize the payment requirements accordingly, saving one roundtrip between client and server.

This is a **Server ↔ Client** extension. The Facilitator is not involved in the identification flow.

**Important**: This extension provides identification, not authentication. The client may not control the claimed address. Servers must treat this as untrusted input and never grant access based solely on this header. For authenticated identification, use [sign-in-with-x](./sign-in-with-x.md).

---

## PaymentRequired

Server advertises support:

```json
{
"extensions": {
"payer-identifier": {
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
"minLength": 16,
"maxLength": 128,
"pattern": "^[a-zA-Z0-9_:-]+$"
}
}
}
}
```

The identifier is expected to be a string of 16-128 characters (alphanumeric, hyphens, underscores, colons).

---

## Client Request

The Client sends their identifier in the `PAYER-IDENTIFIER` HTTP header.

```http
GET /weather HTTP/1.1
Host: api.example.com
PAYER-IDENTIFIER: 0x857b06519E91e3A54538791bDbb0E22373e36b66
```

---

## Server Behavior

When the Server receives a request with the `PAYER-IDENTIFIER` header:

1. **Parse**: Extract the identifier from the header
2. **Validate**: Verify the format matches expected length and characters
3. **Act**: Adjust response as required

**Servers MAY:**
- Enrich `PaymentRequired` based on the claimed identity, using only publicly available data (pricing, available schemes, balances, payment history)
- Log the claimed identifier for correlation

**Servers MUST NOT:**
- Grant access to protected resources based solely on this header
- Return off-chain private data based solely on this header
- Modify any state (balances, records) without a signed payment

---

## Security Considerations

- **Unauthenticated**: The header is an unauthenticated claim. Anyone can send any address. Servers MUST NOT grant access or modify state based solely on this header.
- **Public Data Only**: Servers should only use this header to look up publicly available data. Off-chain private data should not be returned based solely on this header.
- **Information Leakage**: Sending the header reveals the client's address before any payment. Clients should only send this header to trusted servers.

13 changes: 13 additions & 0 deletions specs/schemes/deferred/scheme_deferred.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading