-
Notifications
You must be signed in to change notification settings - Fork 74
Description
Summary
Enable asynchronous payment settlement after data streaming completes to eliminate settlement latency from the critical path. Provide settlement status via a dedicated endpoint instead of blocking the response.
Context
Currently, x402 payment settlement happens synchronously before streaming data (see src/middleware/x402.ts:417-434). This blocks the response for up to 5 seconds (configurable via X_402_USDC_SETTLE_TIMEOUT_MS) while waiting for on-chain settlement confirmation.
The X-Payment-Response header contains base64-encoded SettleResponse:
{
success: boolean; // Whether settlement succeeded
transaction: string; // On-chain transaction hash
network: string; // Network name (e.g., "base-sepolia")
payer?: string; // Payer's address
errorReason?: string; // Error reason if failed
}This information is valuable for clients to verify payments on-chain, but requiring it before streaming adds significant latency.
Problem
Blocking settlement creates poor user experience:
- Users wait 0-5 seconds before data starts streaming
- Settlement timeout is the critical path for every paid request
- Network congestion or facilitator delays directly impact response time
- Cannot stream data until settlement completes
Proposed Solution
Architecture
-
Settlement ID Header - Return before streaming starts:
X-Payment-Settlement-Id: <uuid> X-Payment-Settlement-Status-Url: /ar-io/x402/settlement/<uuid> -
Immediate Streaming - Start streaming data immediately after verification
-
Background Settlement - Settle payment after streaming completes:
res.on('finish', async () => { const result = await paymentProcessor.settlePayment({...}); await db.storeSettlementResult(settlementId, result); });
-
Status Endpoint - Clients can check settlement status:
GET /ar-io/x402/settlement/:id Response: { settlementId: string; status: 'pending' | 'completed' | 'failed'; result?: { success: boolean; transaction: string; network: string; payer?: string; errorReason?: string; }; createdAt: string; completedAt?: string; }
Database Schema
CREATE TABLE IF NOT EXISTS payment_settlements (
settlement_id TEXT PRIMARY KEY,
payment_payload TEXT NOT NULL,
payment_requirements TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'failed')),
settlement_result TEXT,
created_at INTEGER NOT NULL,
completed_at INTEGER,
INDEX idx_status_created ON payment_settlements(status, created_at)
);Benefits
- Zero settlement latency for users - data streams immediately
- Same information available - clients can poll status endpoint
- Better than headers - JSON response instead of base64-encoded string
- Retry capability - clients can re-check status if needed
- Failure tracking - can implement settlement penalties (see Refactor X402 Payment and Rate Limiting from Middleware to Handler-Based Architecture #503)
- No timeout pressure - settlement can take as long as needed
Implementation Notes
- Requires database for settlement tracking
- Settlement status should expire after reasonable TTL (e.g., 24 hours)
- Status endpoint should be rate-limited
- Consider webhook callbacks for settlement completion (future enhancement)
Related Issues
- Refactor X402 Payment and Rate Limiting from Middleware to Handler-Based Architecture #503 - Refactor X402 Payment and Rate Limiting from Middleware to Handler-Based Architecture (prerequisite)
Acceptance Criteria
- Settlement ID generated and returned in headers before streaming
- Payment settlement happens asynchronously after streaming completes
- Status endpoint returns settlement results
- Database schema supports settlement tracking
- Settlement records expire after TTL
- Status endpoint is rate-limited
- E2E tests verify async settlement flow
- Documentation updated with new flow