Concepts Deep Dive

x402 Protocol

x402 is an HTTP-native payment protocol based on the 402 Payment Required HTTP status code. It adds structured payment information to the 402 response so clients can pay programmatically.

The 402 Response

When an AI service requires payment, it returns:

http
HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "1500000",
      "payTo": "0xServiceProviderAddress",
      "maxTimeoutSeconds": 60
    }
  ],
  "resource": {
    "url": "https://api.example.com/chat",
    "description": "AI chat completion",
    "mimeType": "application/json"
  }
}

Key fields:

  • x402Version: 2 — protocol version (must be 2)
  • scheme: "exact" — pay the exact amount specified
  • network: "eip155:8453" — CAIP-2 identifier for Base blockchain
  • asset — USDC token contract address on Base
  • amount: "1500000" — amount in smallest units (USDC has 6 decimals, so this is $1.50)
  • payTo — the service provider's receiving address
  • maxTimeoutSeconds — how long the service will wait for payment settlement

The Payment Headers

After signing, the client retries the original request with payment headers:

http
GET /chat HTTP/1.1
Host: api.example.com
X-Payment: <base64-encoded payment payload>
X-Payment-Signature: 0x<65-byte ECDSA signature in hex>

The X-Payment header contains a base64-encoded JSON payload with the authorization details (from, to, value, nonce, validity window).

The X-Payment-Signature header contains the EIP-712 signature in Ethereum format: 0x + R (32 bytes) + S (32 bytes) + V (1 byte).

Protocol Flow

Agent                    AI Service               Viatika
  │                          │                       │
  │── GET /resource ────────>│                       │
  │<── 402 + x402 payload ──│                       │
  │                          │                       │
  │── POST /sign-payment ──────────────────────────>│
  │                          │     Policy check ✓    │
  │                          │     Budget check ✓    │
  │                          │     EIP-712 sign      │
  │<── signature + auth ───────────────────────────│
  │                          │                       │
  │── GET /resource ────────>│                       │
  │   + X-Payment headers    │                       │
  │                          │── verify sig          │
  │                          │── submit on-chain     │
  │<── 200 + response ──────│                       │

Credit System

Units

UnitValueExample
1 credit$0.001 USD
1,000 credits$1.00 USDSmall API call
1,500 credits$1.50 USDamount: "1500000" in x402 (USDC micro-units)
10,000 credits$10.00 USDTypical daily budget

How Credits Work

  1. Organization purchases credits — via Stripe (recurring or one-time)
  2. Credits are pooled at the organization level in a single account
  3. When an x402 payment is signed, credits are consumed from the organization's account
  4. The platform wallet pays on-chain — Viatika's centralized wallet actually sends the USDC
  5. Credits track the liability — the organization owes Viatika for payments made on their behalf

Credit ↔ USDC Conversion

USDC uses 6 decimal places. The conversion:

USDC micro-units = credits × 1000
  (because 1 credit = $0.001 = 1000 USDC micro-units)

Examples:
  1500 credits = 1,500,000 USDC micro-units = $1.50
  50 credits   = 50,000 USDC micro-units    = $0.05
  10000 credits = 10,000,000 USDC micro-units = $10.00

Balance Tracking

Credits are tracked with optimistic locking in PostgreSQL:

  • balance field on the credit account (integer, in credits)
  • Every consumption creates a credit_transaction record with negative amount
  • Every purchase creates a credit_transaction record with positive amount
  • Daily reconciliation job verifies sum of transactions matches balance

Policy Engine

Every payment request goes through the policy engine before signing. The engine runs in under 10ms using Redis for hot-path checks.

Evaluation Order

1. Whitelist/Blacklist check (Redis)
   → Is this provider allowed?
   
2. Budget check (Redis Lua script, atomic)
   → Does the entity have budget remaining?
   → Atomically decrements budget if allowed
   
3. Policy rules (Cedar SDK)
   → Do IAM-style policies permit this action?
   → Checks amount limits, time windows, etc.
   
4. Approval check (PostgreSQL)
   → Does this require manual approval?

If any check fails, the payment is denied immediately. No signature is created.

Budget Policies

Budgets are per-entity (user, team, or swarm) and per-period:

PeriodDescriptionBudget Key (Redis)
hourlyResets every hourbudget:user:{id}:hourly:2025-01-15T12
dailyResets at midnight UTCbudget:user:{id}:daily:2025-01-15
weeklyResets Monday UTCbudget:user:{id}:weekly:2025-W03
monthlyResets 1st of monthbudget:user:{id}:monthly:2025-01
quarterlyResets per quarterbudget:user:{id}:quarterly:2025-Q1

Budget enforcement is atomic (Redis Lua script) — no race conditions even under concurrent requests.

Hierarchy

Budget checks cascade up the entity hierarchy:

Swarm "data-collector" (daily: 5000 credits)
  └─ Team "research" (monthly: 50000 credits)
      └─ Department "engineering" (quarterly: 500000 credits)

A payment from the swarm must pass all three budget checks.

Whitelist

Providers can be whitelisted at two levels:

  • Global: all entities in the organization can pay this provider
  • Entity-level: specific entity can pay this provider

Whitelists can include constraints:

  • Maximum single transaction amount
  • Daily/monthly spending caps for that provider
  • Effective date range

Denial Reasons

When a payment is denied, the response includes structured reasons:

json
{
  "approved": false,
  "denialReasons": [
    {
      "category": "budget-exceeded",
      "code": "DAILY_LIMIT",
      "message": "Daily budget of 5000 credits exceeded (current: 4800, requested: 1500)",
      "policyId": "budget-policy-uuid"
    }
  ]
}

Categories:

  • budget-exceeded — entity budget limit reached
  • not-whitelisted — provider not on approved list
  • rate-limit-exceeded — too many payments in time window
  • amount-exceeded — single payment exceeds maximum
  • provider-blocked — provider is on blacklist

EIP-712 Signing

Viatika signs payments using EIP-712 typed structured data. This is the Ethereum standard for signing human-readable data with domain separation.

What Gets Signed

An ERC-3009 TransferWithAuthorization message:

Domain:
  name: "USD Coin"
  version: "2"
  chainId: 8453 (Base)
  verifyingContract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (USDC on Base)

Message:
  from: 0xViatikaWallet (platform wallet address)
  to: 0xServiceProvider (payee address)
  value: 1500000 (amount in USDC micro-units)
  validAfter: 0 (immediately valid)
  validBefore: 1769917810 (expires ~5 minutes from signing)
  nonce: 0x<random 32 bytes> (replay protection)

Signing Process (inside Viatika)

  1. Load keystore: encrypted private key, decrypted with Scrypt (~10ms first time, cached after)
  2. Construct EIP-712 TypedData: domain separator + message hash
  3. Hash: Keccak256("\x19\x01" || domainSeparator || messageHash)
  4. ECDSA Sign: produce V (27 or 28), R (32 bytes), S (32 bytes)
  5. Return: signature components + authorization details

Total signing time: ~100ms

Security Properties

  • Time-bounded: signatures expire in ~5 minutes (validBefore)
  • Replay-protected: random 32-byte nonce; USDC contract rejects used nonces
  • Domain-separated: signature is only valid for USDC on the specific chain
  • Non-extractable: private key never leaves the encrypted keystore

Settlement Flow

After Viatika signs the payment and the agent retries the request:

1. Agent sends request with X-Payment + X-Payment-Signature headers
2. AI Service receives the headers
3. AI Service (or its facilitator) verifies:
   - Signature matches the authorization
   - ecrecover returns the `from` address
   - Nonce hasn't been used
   - Current time is within validAfter/validBefore window
4. AI Service calls USDC contract: transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s)
5. USDC contract executes on Base blockchain:
   - Verifies signature
   - Transfers USDC from Viatika's wallet to Service Provider
   - Emits Transfer event
6. Service processes the request and returns response

Key Points

  • Viatika never submits on-chain transactions. The service provider (or their facilitator) submits the signed authorization.
  • Settlement is asynchronous from signing. Viatika signs in ~150ms; on-chain settlement happens when the service submits.
  • Credits are consumed at signing time, not at settlement time. This prevents double-spending.
  • If settlement fails (e.g., nonce already used, insufficient USDC in wallet), that's between the service and the blockchain. The credits have already been consumed.

Transaction States

Viatika records every signed payment in blockchain_transactions:

StatusMeaning
signedSignature created, returned to client
submittedKnown to be submitted on-chain (if tracked)
pendingAwaiting confirmation
confirmedSettled on-chain with sufficient confirmations
failedOn-chain transaction failed
expiredvalidBefore passed without settlement

Platform Wallet

Viatika operates one platform wallet per blockchain (not per-organization). All organizations' payments are signed from the same wallet. This is by design:

  • Simpler key management and security
  • Single USDC balance to manage on-chain
  • Organizations are tracked via credits, not on-chain accounts
  • Key rotation happens at the platform level with zero impact on organizations