iceValidation: string;
cumulativeSpend: string;
}
class SecureAgentClient {
private wallet: ethers.Wallet;
private policyEngine: PolicyEngine;
private ledger: BudgetLedger;
private idempotencyStore: Map<string, string>;
constructor(wallet: ethers.Wallet, policyEngine: PolicyEngine, ledger: BudgetLedger) {
this.wallet = wallet;
this.policyEngine = policyEngine;
this.ledger = ledger;
this.idempotencyStore = new Map();
}
async executePaidRequest(
url: string,
context: PolicyContext
): Promise<Response> {
const requestId = req_${Date.now()}_${Math.random().toString(36).slice(2)};
const auditEvent: AuditEvent = {
requestId,
resourceCategory: this.categorizeResource(url),
termsHash: '',
policyDecision: 'deny',
signatureHash: '',
facilitatorResult: '',
serviceValidation: '',
cumulativeSpend: ''
};
try {
// Step 1: Initial Request
const initialResponse = await fetch(url, {
headers: { 'X-Request-ID': requestId }
});
if (initialResponse.status !== 402) {
return initialResponse;
}
// Step 2: Parse Terms
const termsHeader = initialResponse.headers.get('PAYMENT-REQUIRED');
if (!termsHeader) {
throw new Error('Missing PAYMENT-REQUIRED header');
}
const terms: PaymentTerms = JSON.parse(termsHeader);
auditEvent.termsHash = this.hashPayload(terms);
// Step 3: Policy Pause
const policyCheck = await this.policyEngine.evaluate(terms, context);
auditEvent.policyDecision = policyCheck.allowed ? 'allow' : 'deny';
auditEvent.policyReason = policyCheck.reason;
if (!policyCheck.allowed) {
throw new PolicyViolationError(policyCheck.reason);
}
// Step 4: Secure Signing
const idempotencyKey = this.generateIdempotencyKey(requestId, terms);
const signaturePayload = this.buildSignaturePayload(terms, idempotencyKey);
const signature = await this.wallet.signMessage(signaturePayload);
auditEvent.signatureHash = this.hashPayload(signature);
// Step 5: Signed Retry
const retryResponse = await fetch(url, {
headers: {
'X-Request-ID': requestId,
'PAYMENT-SIGNATURE': signature,
'X-Idempotency-Key': idempotencyKey
}
});
// Step 6: Facilitator & Service Validation
const facilitatorResult = retryResponse.headers.get('PAYMENT-RESPONSE');
auditEvent.facilitatorResult = facilitatorResult || 'unknown';
if (retryResponse.ok) {
const responseBody = await retryResponse.json();
const validation = this.validateServiceBody(responseBody, terms);
auditEvent.serviceValidation = validation.status;
// Step 7: Ledger Update
await this.ledger.recordSpend(context.taskId, terms.amount);
auditEvent.cumulativeSpend = await this.ledger.getCumulativeSpend(context.taskId);
}
return retryResponse;
} catch (error) {
// Log audit event even on failure
await this.logAuditEvent(auditEvent);
throw error;
} finally {
await this.logAuditEvent(auditEvent);
}
}
private categorizeResource(url: string): string {
// Redact sensitive paths; return category only
const path = new URL(url).pathname;
if (path.includes('/report/')) return 'paid_report';
if (path.includes('/model/')) return 'inference';
return 'generic_api';
}
private hashPayload(payload: object | string): string {
const data = typeof payload === 'string' ? payload : JSON.stringify(payload);
return sha256:${createHash('sha256').update(data).digest('hex').slice(0, 16)};
}
private buildSignaturePayload(terms: PaymentTerms, idempotencyKey: string): string {
// Bind signature to resource and idempotency to prevent replay
return JSON.stringify({
...terms,
idempotencyKey,
timestamp: Date.now()
});
}
private generateIdempotencyKey(requestId: string, terms: PaymentTerms): string {
const keySource = ${requestId}:${terms.resource}:${terms.amount};
return idm_${createHash('sha256').update(keySource).digest('hex').slice(0, 12)};
}
private validateServiceBody(body: any, terms: PaymentTerms): { status: string } {
// Validate schema, version, and freshness
const checks = [];
if (body.schemaVersion) checks.push('schema_pass');
if (body.timestamp && Date.now() - body.timestamp < 60000) checks.push('freshness_pass');
if (body.modelVersion === terms.resource) checks.push('version_match');
return { status: checks.join(' ') || 'validation_failed' };
}
private async logAuditEvent(event: AuditEvent): Promise<void> {
// Secure logging: never store raw signatures or sensitive URLs
console.log(JSON.stringify(event));
}
}
class PolicyEngine {
async evaluate(terms: PaymentTerms, context: PolicyContext): Promise<{ allowed: boolean; reason: string }> {
if (!context.allowedNetworks.includes(terms.network)) {
return { allowed: false, reason: 'network_not_allowed' };
}
if (!context.allowlistedResources.some(r => terms.resource.startsWith(r))) {
return { allowed: false, reason: 'resource_not_allowlisted' };
}
const currentSpend = await this.getCurrentSpend(context.taskId);
if (parseFloat(currentSpend) + parseFloat(terms.amount) > parseFloat(context.taskBudget)) {
return { allowed: false, reason: 'budget_exceeded' };
}
if (new Date(terms.expiresAt) < new Date()) {
return { allowed: false, reason: 'terms_expired' };
}
return { allowed: true, reason: 'policy_pass' };
}
private async getCurrentSpend(taskId: string): Promise<string> {
// Implementation depends on ledger storage
return '0.00';
}
}
class BudgetLedger {
async recordSpend(taskId: string, amount: string): Promise<void> {
// Atomic update to prevent race conditions
}
async getCumulativeSpend(taskId: string): Promise<string> {
return '0.00';
}
}
class PolicyViolationError extends Error {
constructor(reason: string) {
super(Policy violation: ${reason});
}
}
#### Architecture Rationale
* **Policy Pause:** The `PolicyEngine` runs between the 402 challenge and the signature generation. This ensures the agent never signs a transaction that violates budget, network, or resource constraints. The protocol cannot enforce these rules; the runtime must.
* **Resource-Bound Signatures:** The `buildSignaturePayload` function includes the resource path and an idempotency key. This prevents an attacker or a misconfigured agent from replaying a valid signature against a different endpoint.
* **Secure Logging:** The `AuditEvent` stores hashes of terms and signatures, not raw values. URLs are categorized to prevent metadata leaks. This maintains auditability without exposing sensitive agent intent or payment credentials.
* **Idempotency:** The `X-Idempotency-Key` header is derived from the request ID and payment terms. This allows the server to detect duplicate retries caused by network timeouts, preventing double-charging.
* **Service Validation:** Payment success does not guarantee service quality. The `validateServiceBody` function checks schema, freshness, and version independently. This decouples financial settlement from service reliability.
### Pitfall Guide
#### 1. Blind Signing
**Explanation:** The agent automatically signs the payment challenge without evaluating the terms against current constraints. This can lead to spending on unauthorized resources or exceeding budgets.
**Fix:** Implement a mandatory policy pause. The runtime must evaluate `PaymentTerms` against allowlists, budgets, and network restrictions before invoking the wallet signer.
#### 2. Replay Vulnerabilities
**Explanation:** A signed payment payload is reused against a different resource or after expiration. If the signature is not bound to the specific resource and time window, the agent delegates excessive authority.
**Fix:** Bind the signature to the resource path, amount, and expiration. Include an idempotency key in the signed payload. The server must verify these bindings during settlement.
#### 3. Metadata Leakage
**Explanation:** Logging raw URLs or payment terms exposes agent intent. For example, logging `GET /v1/report/merger-draft` reveals sensitive business activity.
**Fix:** Sanitize logs by categorizing resources and hashing sensitive payloads. Store `resource_category` and `terms_hash` instead of raw paths and signatures.
#### 4. Facilitator Fallacy
**Explanation:** Assuming that a successful facilitator result implies the service response is valid. The facilitator only verifies payment settlement; it does not validate the AI model output or data quality.
**Fix:** Implement a separate service validation layer. Check response schema, model version, and freshness regardless of payment status.
#### 5. Idempotency Gaps
**Explanation:** Network timeouts after sending the signed retry cause the agent to retry blindly, potentially resulting in duplicate payments.
**Fix:** Use idempotency keys derived from request and payment details. Ensure the server supports idempotent retries and returns the same result or a duplicate rejection for repeated keys.
#### 6. Budget Drift
**Explanation:** Per-request policy checks pass, but the cumulative spend exceeds the task budget due to many small transactions.
**Fix:** Maintain a global budget ledger that tracks cumulative spend. The policy engine must check the ledger before allowing each payment.
#### 7. Stale Terms Usage
**Explanation:** The agent retries a payment using terms that have expired, causing settlement failures or inconsistent behavior.
**Fix:** Validate the `expiresAt` field in the policy engine. Reject terms that are close to expiration or already expired. Implement a mechanism to fetch fresh terms if needed.
### Production Bundle
#### Action Checklist
- [ ] **Implement Policy Pause:** Add a governance checkpoint between the 402 challenge and signature generation to enforce budget, network, and resource constraints.
- [ ] **Bind Signatures:** Ensure payment signatures include the resource path, amount, expiration, and idempotency key to prevent replay attacks.
- [ ] **Sanitize Logs:** Replace raw URLs and payment payloads with categorized resources and cryptographic hashes in audit logs.
- [ ] **Add Idempotency:** Generate idempotency keys for signed retries and ensure the server handles duplicate requests safely.
- [ ] **Validate Service Output:** Implement schema, version, and freshness checks for the response body, independent of payment status.
- [ ] **Maintain Budget Ledger:** Track cumulative spend per task and enforce global budget limits in the policy engine.
- [ ] **Test Replay Scenarios:** Verify behavior with expired terms, wrong resources, duplicate retries, and facilitator timeouts.
- [ ] **Handle Facilitator Timeouts:** Implement reconciliation logic for unknown settlement states to prevent blind retries.
#### Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
| :--- | :--- | :--- | :--- |
| **Human-Driven API Usage** | API Keys / OAuth | Simpler authentication; no need for per-request payment negotiation. | Low overhead; fixed costs. |
| **High-Volume Agent Tasks** | x402 with Hardened Runtime | Enables micro-payments with strict policy control and auditability. | Higher implementation cost; precise spend control. |
| **Subscription-Based Access** | Pre-paid Credits | Avoids per-request latency; suitable for predictable usage patterns. | Upfront cost; risk of unused credits. |
| **Multi-Agent Collaboration** | x402 with Shared Ledger | Allows multiple agents to transact while tracking collective spend. | Complex ledger management; shared risk. |
#### Configuration Template
Use this YAML configuration to define policy rules for the x402 runtime. This template supports allowlisting, budget limits, and network restrictions.
```yaml
policy:
version: "1.0"
defaults:
max_amount_per_request: "0.05"
allowed_networks:
- "base"
- "ethereum"
require_expiration: true
max_expiration_seconds: 300
tasks:
- id: "research_report_generation"
budget: "1.00"
currency: "USDC"
allowlisted_resources:
- "/v1/research/"
- "/v1/analysis/"
max_requests_per_hour: 100
- id: "model_inference"
budget: "0.50"
currency: "USDC"
allowlisted_resources:
- "/v1/model/"
max_amount_per_request: "0.01"
allowed_networks:
- "base"
audit:
log_level: "info"
redact_urls: true
store_terms_hash: true
store_signature_hash: true
Quick Start Guide
- Initialize Wallet and Client: Set up a secure wallet for the agent and instantiate the
SecureAgentClient with the policy engine and budget ledger.
# Example setup command
npm install @coinbase/x402 ethers
- Configure Policy Rules: Define your policy rules using the configuration template. Specify allowed networks, resource allowlists, and budget limits.
- Implement Ledger Storage: Set up a persistent storage backend for the budget ledger to track cumulative spend across requests.
- Run Replay Tests: Execute the replay test suite to verify behavior with expired terms, wrong resources, and duplicate retries.
- Deploy and Monitor: Deploy the hardened client and monitor audit logs for policy violations, budget drift, and service validation failures.
This guide provides the technical foundation for implementing x402 in production AI agent systems. By focusing on policy enforcement, replay protection, and secure auditing, developers can enable autonomous payments while maintaining strict control over agent behavior and financial risk.