Axiomatic
Privacy & Confidential Transfers

Architecture

Contract relationships, data flow diagrams, and deployment order for the privacy system.

Contract Relationships

flowchart TB
  subgraph privacy["Privacy Package"]
    CL["ConfidentialLedger<br/>(UUPS Upgradeable)"]
    MT["IncrementalMerkleTree<br/>(depth 32, Poseidon)"]
    NS["Nullifier Set<br/>(mapping uint256 → bool)"]
    VF["Halo2KZGVerifier<br/>(BN254 pairing verifier)"]
    PH["PoseidonBN254<br/>(P128Pow5T3)"]
  end

  subgraph wallets["Wallets Package"]
    SF["ShieldedTransferFacet<br/>(Diamond facet)"]
  end

  subgraph external["External"]
    EP["EntryPoint (ERC-4337)"]
    PM["AxiomaticPaymaster"]
    BN["Bundler"]
  end

  SF -->|"shield / transfer / unshield"| CL
  CL --> MT
  CL --> NS
  CL -->|"verifyProof"| VF
  CL -->|"hash"| PH
  MT -->|"hash"| PH

  BN -->|"UserOp"| EP
  EP -->|"execute()"| SF
  PM -->|"sponsor gas"| EP

Data Flow — Shield Operation

sequenceDiagram
  participant User
  participant API as /api/privacy/shield
  participant Wallet as Smart Account
  participant Facet as ShieldedTransferFacet
  participant CL as ConfidentialLedger
  participant MT as MerkleTree

  User->>API: POST {entityId, token, amount}
  API->>API: Derive key set, compute commitment
  API->>API: Encrypt note under IVK
  API->>Wallet: UserOp (shieldETH or shieldTokens)
  Wallet->>Facet: delegatecall
  Facet->>CL: shield(token, amount, commitment, scanTag, ephemeralPubKey, encryptedNote)
  CL->>MT: insertLeaf(commitment)
  MT-->>CL: leafIndex
  CL-->>CL: emit NoteCreated(commitment, leafIndex, scanTag, ephemeralPubKey, encryptedNote)
  CL-->>Facet: success
  Facet-->>Wallet: success
  API-->>User: {userOpHash, noteCommitment}

Data Flow — Private Transfer

sequenceDiagram
  participant User
  participant API as /api/privacy/prove-transfer
  participant Prover as Native Prover (Halo2-KZG)
  participant Relay as /api/relay/transfer
  participant Bundler
  participant Wallet as Relay Smart Account
  participant CL as ConfidentialLedger
  participant VF as Halo2KZGVerifier

  User->>API: POST {entityId, recipient, token, amount}
  API->>API: Select input notes, fetch Merkle paths
  API->>Prover: Generate JoinSplit proof (witness)
  Prover-->>API: proof, public inputs
  API-->>User: proof bundle
  User->>Relay: POST {proof, root, nullifiers, commitments, ...}
  Relay->>Bundler: Submit UserOp (anonymous relay account)
  Bundler->>Wallet: execute()
  Wallet->>CL: transfer(proof, root, nullifiers, commitments, ...)
  CL->>VF: verifyProof(proof, instances)
  VF-->>CL: valid
  CL->>CL: Record nullifiers, insert new commitments
  CL-->>Wallet: success

Data Flow — Private DeFi Call (Swap / Earn)

sequenceDiagram
  participant User
  participant WalletUI as Wallet App
  participant Prover as Browser Prover (private_call)
  participant Bundler
  participant SA as Smart Account
  participant CL as ConfidentialLedger
  participant Adapter as Private Adapter (UniswapV4 / ERC4626)

  User->>WalletUI: Submit private swap/deposit/withdraw
  WalletUI->>Prover: Build witness + prove(private_call)
  Prover-->>WalletUI: proof + public inputs
  WalletUI->>Bundler: UserOp(privateContractCall)
  Bundler->>SA: execute()
  SA->>CL: privateContractCall(...)
  CL->>Adapter: external call(adapterCalldata)
  Adapter-->>CL: token output
  CL-->>SA: success

Private DeFi calls use protocol adapters:

  • PrivateUniswapV4Adapter for swaps
  • PrivateERC4626Adapter for vault deposit/withdraw

If adapter execution fails, ConfidentialLedger now bubbles adapter revert bytes through a typed error so wallet decoding can show actionable diagnostics instead of a generic revert.

ECDH Key Agreement Model

Note encryption uses X25519 ECDH (Elliptic Curve Diffie-Hellman) for per-note key agreement:

  • The recipient's X25519 public key is derived from their IVK and published in the stealth meta-address
  • Each sender generates a fresh ephemeral X25519 keypair (r, R) per note
  • Shared secret: x25519(r, recipientX25519Pub) — only the recipient (with IVK-derived private key) can compute it
  • Note key: HKDF-SHA256(sharedSecret, "axiomatic.note.v2")

This ensures inter-sender privacy: even if Sender A is compromised, they cannot decrypt Sender B's notes to the same recipient.

Server-Side IVK for Cron Scanning

The IVK (Incoming Viewing Key) is stored server-side to enable cron-based scanning of incoming notes:

  • Rationale: Journal entries and note data must be available in the web app for reconciliation and reporting. Client-only scanning would require the wallet to be open for events to be processed.
  • Security: The IVK is read-only — it cannot spend notes or derive the spending key. It is encrypted at rest under the entity's data encryption key (DEK).
  • Scope: The scanning cron (/api/cron/scan-incoming-notes) uses the IVK to trial-decrypt notes and classify transfer types (deposits, transfers in/out, withdrawals).

Wallet Receive UX Model

Receive now uses a segmented three-mode layout:

  • Crypto: stealth meta-address (st:), QR, and sharing actions
  • Bank: KYC/Plaid + ACH deposit details
  • Request: contact-first private payment request generation

The same axiomatic://send?... request format is used for both QR and deep-link prefills.

Scheduled Payment Execution Semantics

Bills scheduling now enforces destination-aware behavior:

  • Bank contacts: Auto (bank) execution path remains active
  • Wallet contacts: Reminder (wallet) only in this release (no unattended execution)

This avoids silent/unsafe execution for private wallet-to-wallet flows before policy-constrained delegation is fully enabled.

Scoped Session-Key Scaffold (Phase 2)

A feature-flagged policy scaffold is staged in wallet settings for:

  • recurring bank payouts,
  • recurring private wallet transfers,
  • auto-shield policy presets.

Policies encode strict constraints (method allowlist, token caps, amount ceilings, time windows, and recipient limits) before autonomous execution is enabled by default.

Scan Tag Protocol

Scan tags are 4-byte fingerprints that enable efficient server-side filtering without trial decryption:

  • Derivation: keccak256(recipientX25519Pub)[0:4]
  • On-chain: The NoteCreated event includes scanTag as an indexed parameter, allowing the Goldsky indexer and scanning cron to filter events with a simple WHERE scan_tag = $1 query
  • Privacy trade-off: All notes to the same recipient share the same scan tag (linkability), but the tag does not reveal the recipient's identity or public key. Collision probability is ~1 in 4 billion.

See Scan Tags for full details.

Key Types

TypeSolidityDescription
Commitmentuint256Poseidon(ownerPubkey, Poseidon(tokenHash, amount, salt))
Nullifieruint256Poseidon(spendingKey, commitment)
Merkle Rootuint256Root of the depth-32 Poseidon Merkle tree
Tokenaddressaddress(0) for native ETH, ERC-20 address otherwise

Deployment Order

flowchart LR
  P["1. PoseidonBN254"] --> V["2. Halo2KZGVerifier"]
  V --> CL["3. ConfidentialLedger(verifier, hasher)"]
  CL --> SF["4. Add ShieldedTransferFacet to Diamonds"]
  SF --> INIT["5. ShieldedTransfer_init(admin, ledgerAddress)"]

Environment Variables

After deployment, set these in the platform .env:

VariableDescription
CONFIDENTIAL_LEDGERConfidentialLedger address (shared across Base Sepolia and Base mainnet)
WALLET_ADDRESS_BASE_SEPOLIARelay smart account address (Base Sepolia)
WALLET_ADDRESS_BASERelay smart account address (Base mainnet)
BUNDLER_URL_BASE_SEPOLIAERC-4337 bundler RPC URL (Base Sepolia)
BUNDLER_URL_BASEERC-4337 bundler RPC URL (Base mainnet)
ENTRYPOINT_ADDRESSERC-4337 EntryPoint contract address

On this page