Skip to content

How zBase works (diagrams)

The whole system in pictures. If you read nothing else, read this page.

The one-picture version

Your agent pays an API. Normally that payment is a public receipt linking your wallet → the API. zBase breaks that link: you deposit once into a shared pool, and every payment is funded through a fresh single-use wallet drawn from the pool with a zero-knowledge proof. The seller is paid normally and can't tell zBase was involved.

flowchart LR
    A[Your wallet] -->|deposit USDC once| P[(Privacy Pool<br/>many depositors)]
    P -.->|ZK withdrawal<br/>no link to you| E[Fresh single-use<br/>payer wallet]
    E -->|standard x402 payment| S[Seller API]
    S -->|data 200| G[Your agent]
    style P fill:#e7eef8,stroke:#365f9f,color:#18191b
    style E fill:#eef1f4,stroke:#9aa0a6,color:#18191b

The public chain sees "a deposit" and, later, "some fresh wallet paid a seller." It can't join the two — the ZK proof hides which deposit funded the payer, and the anonymity set is the crowd you hide in.

A payment, step by step

sequenceDiagram
    participant Agent
    participant zBase as zBase facilitator
    participant Pool as Privacy Pool (chain)
    participant Seller as Seller (CDP)

    Agent->>Seller: GET /endpoint (no payment)
    Seller-->>Agent: 402 Payment Required (accepts: exact, $X USDC)
    Agent->>zBase: settle-x402 (note + change-note secrets)
    Note over zBase: build + sign EIP-3009 header<br/>for a deterministic payer EOA
    zBase->>Pool: reserve nullifier, then ZK withdraw $X → payer EOA
    Pool-->>zBase: funded (payer holds $X)
    Note over zBase: finalize (store header) — idempotent on the note
    zBase-->>Agent: settled: true + X-PAYMENT header + change note
    Agent->>Seller: GET /endpoint + X-PAYMENT
    Seller-->>Agent: 200 + data

The note is spent once. If the response is lost, a retry replays the same payment (idempotent on the note) instead of paying twice — see the tri-state below.

The two gates (why privacy + compliance coexist)

zBase runs two independent gates. Break one and a payment still isn't unmasked.

flowchart TB
    D[Deposit] --> G1{Gate 1: ASP<br/>who MAY withdraw}
    G1 -->|label in approved set<br/>excludes known-illicit| W[ZK withdrawal]
    W --> G2{Gate 2: Stealth<br/>who RECEIVES}
    G2 -->|fresh address<br/>only the payee can find| R[Seller paid]
    style G1 fill:#e4f0e6,stroke:#4a7a52,color:#18191b
    style G2 fill:#e5edf7,stroke:#365f9f,color:#18191b

Gate 1 (ASP / Association Sets) proves your funds are not in the set of known-illicit deposits — compliance without revealing which deposit is yours. Gate 2 (stealth addresses) hides the recipient. This is why zBase is a Privacy Pool, not a blind mixer.

The result you must handle (money-safety)

Every payment resolves to exactly one of three states. Never treat "unknown" as "unspent."

flowchart TD
    Start[payAndFetch] --> Q{settled?}
    Q -->|delivered: paid=true, 2xx| OK[Use the data<br/>persist change note]
    Q -->|throws: PROVEN unspent| RETRY[Note untouched —<br/>safe to retry same note]
    Q -->|uncertain: paid=false, uncertain=true| UNC[Note MAY be spent —<br/>retry THIS call idempotently,<br/>never pay from another note]
    style OK fill:#e4f0e6,stroke:#4a7a52,color:#18191b
    style RETRY fill:#e5edf7,stroke:#365f9f,color:#18191b
    style UNC fill:#f6efe0,stroke:#b08a3e,color:#18191b

Full contract: Developer Guides.

Where this sits vs the alternatives

flowchart LR
    subgraph zBase
      Z1[Pooled ZK anonymity set] --- Z2[ASP compliance] --- Z3[standard x402 seller]
    end
    subgraph Others
      O1[CDP: public link] 
      O2[Dexter Shield: 1-hop forward, timing-linkable]
      O3[Mixer: unlinkable but no compliance]
    end

See How zBase compares for the full breakdown, including Dexter Shield.