Documentation

Private payments
for web3

Peek-a-boo is privacy infrastructure for web3 payments and AI agents. Shielded tokens, stealth addresses, zero-knowledge proofs, note encryption, viewing keys, and compliance proofs — across Bittensor, Base, Ethereum, and more.

01 Overview

When transactions happen on public blockchains, every payment, transfer, and routing decision is visible. Anyone can track who paid whom, how much, and when — whether it's a human wallet or an AI agent.

Peek-a-boo solves this with a privacy layer that works across chains. Users deposit tokens into a shielded pool, generate ZK proofs to withdraw without linkability, and use stealth addresses so recipients receive payment without revealing a persistent identity. The protocol is live on Bittensor EVM and supports Ethereum, BSC, Polygon, and Arbitrum via the Railgun adapter.

Core Guarantee

No observer can link a deposit to a withdrawal, or determine who paid whom. The ZK proof proves you had funds in the pool—without revealing which deposit was yours.

ZK-SNARKs (Groth16) Poseidon Merkle Trees ERC-5564 Stealth UTXO Model Bittensor EVM

02 Quickstart

Peek-a-boo is a monorepo built with npm workspaces and Turborepo. All packages build to ESM + CJS + DTS via tsup.

Terminal # Clone and install git clone <repo-url> cd peek-a-boo npm install # Build everything npm run build # Run all 523 tests npm run test # Build a specific package npx turbo run build --filter=@peekaboopay/core # Compile smart contracts cd packages/contracts && npx hardhat compile # Build ZK circuits cd packages/contracts && npm run circuits:build
ToolVersionPurpose
Node.jsv24.11.1Runtime
npm11.6.2Package manager
TurborepolatestMonorepo build orchestration
tsuplatestLibrary bundler (ESM + CJS + DTS)
VitestlatestTest runner
HardhatlatestSmart contract toolchain
Circom2.1.9ZK circuit compiler
snarkjslatestGroth16 proving system

03 Architecture

The protocol is organized as a layered stack. Types at the base, core privacy primitives in the middle, chain adapters on top, and an SDK that composes everything into a unified API.

@peekaboopay/types
@peekaboopay/core
@peekaboopay/sdk
@peekaboopay/mcp-server
@peekaboopay/types
@peekaboopay/contracts
@peekaboopay/adapter-bittensor
@peekaboopay/types
@peekaboopay/adapter-railgun

Storage Abstraction

Every core module uses constructor-injected storage adapters. In-memory implementations ship as defaults. SQLite adapters are opt-in via the @peekaboopay/storage-sqlite package—core has zero SQLite dependency.

TypeScript // Default: in-memory (no persistence) const engine = createEngine({ }) // Opt-in: SQLite persistence import { createSqliteStores } from '@peekaboopay/storage-sqlite' const stores = createSqliteStores('./privacy.db') const engine = createEngine({ stores })

Cryptography

All cryptographic operations use @noble/curves and @noble/hashes—pure JavaScript with no native bindings. The library stays fully platform-agnostic: no node:crypto, no WASM, no system dependencies.


04 Policy Engine

The policy engine enforces spend constraints on agent transactions. Rules are evaluated in priority order—the first matching rule determines the outcome.

Rule TypeDescription
Spend LimitPer-transaction and cumulative limits with configurable time windows
Time BoundsRestrict transactions to specific time ranges with timezone support
WhitelistAllow only specific destination addresses
DenylistBlock specific destination addresses
Chain RestrictionLimit which chains an agent can transact on

Rules are composable and priority-ordered. An agent session might have a per-tx limit of 1 TAO, a daily cumulative limit of 10 TAO, and a whitelist restricting payments to verified miners only.


05 Session Manager

Sessions scope an agent's access to the privacy layer. Each session has a lifecycle:

Created
Active
Expired / Terminated

Sessions auto-expire on access check if their TTL has elapsed. A background cleanup sweep removes stale sessions. Policy rules are attached per-session, so different agents (or different tasks within one agent) can have different spending constraints.


06 Shielded Treasury

The treasury manages a UTXO note set representing shielded balances. Each note is a commitment that can only be spent once—double-spend prevention uses nullifiers.

UTXO Model

Each deposit creates a note (commitment = Poseidon(nullifier, secret)). Spending a note reveals its nullifierHash but not the original commitment—breaking the link between deposits and withdrawals.

The treasury supports multi-token balances, greedy UTXO selection for optimal transaction construction, and filtered transaction history with timestamps.


07 Stealth Addresses

Stealth addresses let miners receive payments without revealing a persistent public address. The protocol implements ERC-5564 using ECDH on secp256k1.

How it works

The sender generates a one-time address from the miner's meta-address. The miner scans on-chain announcements using their viewing key to detect payments—a viewTag enables fast filtering so miners only need to attempt full derivation on ~1/256 of all announcements.

Flow Sender Chain Miner | | | |-- generate(metaAddr) ------->| Announcement event | | |--- scan(viewKey) ----------->| | | | | | viewTag match? derive key | | | |

08 Smart Contracts

Five Solidity contracts handle the on-chain privacy operations. All contracts are ready for deployment on any EVM-compatible chain.

ContractPurpose
ShieldedPoolPrivacy pool with Poseidon Merkle tree, Groth16 ZK verification for withdrawals
StealthAnnouncerERC-5564 announcement registry for stealth address discovery
IncrementalMerkleTreeGas-efficient Merkle tree library with Poseidon hashing
PoseidonHasherOn-chain Poseidon hash wrapper (matches circomlib)
Groth16VerifierAuto-generated ZK proof verifier from snarkjs

Deposit & Withdraw Flow

Deposit TAO
Insert Commitment
Generate ZK Proof
Verify & Withdraw

Deposits insert a commitment into the Poseidon Merkle tree. Withdrawals require a Groth16 proof demonstrating knowledge of the pre-image without revealing which deposit is being spent. A 30-root history buffer prevents race conditions between concurrent deposits and withdrawals.

Deploy Order

Deployment PoseidonT3 // External library (deployed separately)Groth16Verifier // ZK proof verificationShieldedPool(levels, verifierAddr) // 20 levels production, 5 levels testStealthAnnouncer // Independent — ERC-5564 events

09 ZK Circuits

The withdrawal circuit is written in Circom and compiled to a Groth16 proving system. It follows the Tornado Cash pattern, upgraded from keccak256 to Poseidon hashing for ZK efficiency.

Circuit Design

Circom // Commitment construction commitment = Poseidon(nullifier, secret) // Nullifier hash (revealed on spend) nullifierHash = Poseidon(nullifier) // Public inputs (visible on-chain) root, nullifierHash, recipient, amount // Private inputs (known only to prover) nullifier, secret, pathElements[], pathIndices[]
Anti-Front-Running

The recipient and amount are bound to the proof via a square constraint trick. A front-runner cannot change the destination or amount without invalidating the proof.


10 Bittensor EVM

Bittensor's Subtensor EVM is the primary deployment target. The adapter supports both mainnet and testnet.

NetworkChain IDRPCCurrency
Mainnet964lite.chain.opentensor.aiTAO (18 decimals)
Testnet945test.chain.opentensor.aiTAO (18 decimals)

The BittensorAdapter implements the PrivacyBackend interface—the same interface used by the Railgun adapter. Config widening via BittensorAdapterConfig extends BackendConfig allows chain-specific options without breaking the shared interface.

Deployed Contracts (Chain 964)

ContractAddress
PoseidonT30x389B7E7d332d83157a580ead27884aa0CAB14815
Groth16Verifier0x0CD5A7D426ED71D8d1e216FEEADBC2F6574D053D
ShieldedPool0xaf243B3bFc7D4cbD0e58fA175876fF51f7097f59
StealthAnnouncer0xF6b3223aC0107e2bd64A982e0212C0b0751c269B

Protocol fee: 0.5% on deposits and withdrawals.


11 Railgun Adapter

The Railgun adapter provides shielded ERC-20 transactions via the Railgun SDK's ZK-SNARK infrastructure. It uses a provider abstraction pattern—all SDK calls are isolated behind a single interface, making the adapter fully testable with mocks.

ModuleResponsibility
WalletManagerEngine initialization, wallet create/load
TransactionBuilderShield / unshield / transfer assembly
ProofServiceGroth16 proof generation & verification
BalanceScannerShielded balance queries
ChainMapChainId ↔ NetworkName mapping

Supported chains: Ethereum (1), BSC (56), Polygon (137), Arbitrum (42161).


12 Base L2

Base L2 uses our own ShieldedPool contracts — the same architecture as Bittensor, deployed natively on Base.

ContractAddress
PoseidonT30x95C9521932F9Ed6bBF907b5e950C4BC7656d1439
Groth16Verifier0xAC50E112F95fbf97bDAc64F5E0Ad1fcfe3a252be
ShieldedPool0xe01Aba8855c83f2A70eE0A0D7401F8B7DB289C86
StealthAnnouncer0x1251E4E0B9c55406427aBef555dB580Da90D8A12

Chain ID 8453. Gas costs ~$0.01 per transaction. Protocol fee: 0.5%.


13 Package Map

20 packages total. 523 tests, all passing.

PackageDescriptionStatus
@peekaboopay/typesShared type definitions (9 modules) Live
@peekaboopay/corePrivacy engine, policy, sessions, treasury, stealth 65 tests
@peekaboopay/cryptoCryptographic primitives, note encryption, key derivation 48 tests
@peekaboopay/storage-sqliteSQLite persistence adapters 41 tests
@peekaboopay/contractsSolidity contracts + ZK circuits 41 tests
@peekaboopay/adapter-bittensorBittensor Subtensor EVM adapter 40 tests
@peekaboopay/adapter-railgunRailgun privacy backend 74 tests
@peekaboopay/x402Shielded x402 payment scheme 21 tests
@peekaboopay/sdkUniversal developer-facing API Wired
@peekaboopay/mcp-serverMCP server for agent tools Wired
@peekaboopay/settlementL1/L2 providers, gasless relay Stub
@peekaboopay/adapter-ethereumNative Ethereum L2 privacy Stub
@peekaboopay/adapter-aztecAztec private execution Stub
@peekaboopay/agent-crewaiCrewAI tool wrappers Stub
@peekaboopay/agent-langchainLangChain tool wrappers Stub

14 SDK & MCP Server

The @peekaboopay/sdk provides a universal privacy interface wired to all chain adapters. Methods call real backends — shield, unshield, transfer, prove, and balance queries all work end-to-end. The @peekaboopay/mcp-server exposes these as MCP tools with a CLI entry point: npx peekaboopay-mcp.

Agent Adapters

Framework-specific wrappers for CrewAI and LangChain are scaffolded in @peekaboopay/agent-crewai and @peekaboopay/agent-langchain. These translate SDK calls into framework-native tool interfaces.


15 SDK Reference

The PASClient class is the primary developer-facing interface. It wraps a PrivacyBackend and exposes high-level operations for transactions, identity, and policy management.

Constructor & Lifecycle

TypeScript import { PASClient } from '@peekaboopay/sdk' const client = new PASClient(backend) // Connect with agent config await client.connect({ agentId: 'agent-001', privacyLevel: 'FULL', allowedOperations: ['pay', 'receive'], ttl: 3600 }) // Disconnect when done await client.disconnect()

Transaction API

MethodParamsReturns
pay(params){ recipient, amount, token, memo? }PASResult<PaymentReceipt>
receive(params){ token, singleUse? }PASResult<ReceiveAddress>
swap(params){ fromToken, toToken, amount, slippageBps? }PASResult<SwapReceipt>
bridge(params){ token, amount, fromChain, toChain }PASResult<BridgeReceipt>

Identity API

MethodParamsReturns
prove(id, disclosure)credentialId: string, disclosure: DisclosureRequestPASResult<Proof>
credential(cred)Credential { type, claims, issuer }PASResult<string> (ID)
disclose(attrs)attributes: string[]PASResult<Proof>

Policy API

TypeScript await client.setPolicy([ { type: 'spend_limit', token: 'TAO', maxAmount: '1000000000000000000', period: 'daily' }, { type: 'whitelist', addresses: ['0x...'], mode: 'allow' }, { type: 'chain_restriction', allowedChains: [964, 945] }, ])

PrivacyBackend Interface

Every chain adapter implements this interface. Swap backends without changing application code.

TypeScript interface PrivacyBackend { readonly name: string readonly supportedChains: ChainId[] initialize(config: BackendConfig): Promise<void> shield(params: ShieldParams): Promise<PASResult<ShieldResult>> unshield(params: UnshieldParams): Promise<PASResult<UnshieldResult>> transfer(params: PrivateTransferParams): Promise<PASResult<TransferResult>> generateProof(params: ProofParams): Promise<PASResult<Proof>> verifyProof(proof: Proof): Promise<boolean> getShieldedBalance(token: TokenInfo, viewingKey: Hex): Promise<bigint> }

16 MCP Tools

The @peekaboopay/mcp-server exposes 10 tools via the Model Context Protocol. Any MCP-compatible agent runtime can call these directly—no SDK integration required.

Transaction Tools

ToolDescriptionInputs
pas_paySend a shielded payment—no on-chain link between sender and recipientrecipient, amount, token, memo?
pas_receiveGenerate a single-use stealth receive addresstoken, singleUse?
pas_swapPrivate token swap within the shielded poolfromToken, toToken, amount, slippageBps?
pas_bridgeBridge tokens privately between chainstoken, amount, fromChain, toChain

Treasury Tools

ToolDescriptionInputs
pas_get_balanceQuery shielded balance—computed locally, never exposed on-chaintoken
pas_shield_fundsMove funds from a public address into the shielded pooltoken, amount
pas_unshield_fundsWithdraw from the shielded pool to a public addresstoken, amount, recipient

Identity Tools

ToolDescriptionInputs
pas_proveGenerate a ZK proof for a credential without revealing underlying datacredentialId, attributes[]
pas_credential_storeStore a ZK-provable credential in the vaulttype, claims
pas_discloseSelectively reveal specific attributes for complianceattributes[]

MCP Resources

Read-only resources for querying protocol state without invoking tools.

URIDescription
pas://balance/{token}Current shielded balance for a specific token
pas://policiesCurrently active policy rules
pas://credentialsAvailable ZK-provable credentials
pas://sessionCurrent privacy session state
String Amounts

All MCP tools accept amounts as string (not number) to preserve precision with large token values. Use Wei-denominated strings for TAO and ERC-20 tokens.


17 Note Encryption

Deposit notes (nullifier + secret + amount) are encrypted using ECDH + AES-256-GCM before being stored or emitted on-chain. This enables cross-session and cross-device recovery — scan on-chain events with your viewing key to find and decrypt your shielded funds.

Encryption Flow

The sender generates an ephemeral secp256k1 keypair, performs ECDH with the recipient's viewing public key, derives an AES-256 cipher key via keccak256, and encrypts the 96-byte payload (nullifier + secret + amount). The encrypted note and ephemeral public key are stored on-chain.

API

FunctionDescription
encryptNote()Encrypt a deposit note for a recipient's viewing key
decryptNote()Decrypt using viewing private key
tryDecryptNote()Safe scan — returns null if key doesn't match

18 Viewing Keys

Peek-a-boo implements a hierarchical key system that separates spending authority from viewing authority. Share viewing keys with dashboards and auditors without risking your funds.

Key Hierarchy

Derivation Master Seed (32 bytes) ├── Spending Key // controls withdrawals — NEVER share │ └── Nullifier Key // derives nullifier hashes ├── Viewing Key // scans balances — safe for dashboards │ └── ECDH Key // stealth address derivation └── HMAC-SHA256 domain separation

API

FunctionDescription
deriveKeySet(seed)Derive full key hierarchy from 32-byte seed
generateKeySet()Generate a fresh random key set
exportViewingKey(keySet)Export viewing-only keys (no spending authority)
hasSpendingAuthority(keySet)Check if a key set can spend

19 Compliance Proofs

Proof of Innocence — prove your shielded funds did NOT originate from sanctioned addresses, without revealing which deposit is yours. Privacy meets compliance.

How It Works

A sorted Poseidon Merkle tree of sanctioned addresses enables exclusion proofs. The user proves their deposit address falls between two adjacent sanctioned entries — demonstrating non-membership without revealing the address itself. An attestation hash binds the commitment, sanctioned set root, and timestamp via Poseidon hashing.

API

FunctionDescription
SanctionedSetSorted Poseidon Merkle tree of sanctioned addresses
generateComplianceAttestation()Generate attestation that deposit is not sanctioned
verifyComplianceAttestation()Verify an attestation's validity
Verifiable by Anyone

Compliance attestations are self-contained. Anyone can verify the exclusion proof and attestation hash without access to the depositor's identity or the full sanctioned set.


20 Lit Protocol

Decentralized key management via Lit Protocol. Agents sign transactions through Lit's threshold network — no raw private key is ever held locally. Keys are split across decentralized TEE nodes using Multi-Party Computation. More than 2/3 of nodes must combine their shares to sign.

Why Lit?

When an agent holds a raw private key in memory, a compromised process means stolen funds. Lit eliminates this — the full key never exists in any single location. Signing policies are enforced cryptographically by the network, not by SDK-level checks that can be bypassed.

PKP Signer (Drop-in Replacement)

createPKPSigner() returns an ethers.Signer backed by Lit's Programmable Key Pair. It's a drop-in replacement for ethers.Wallet — all existing adapter calls work unchanged.

TypeScript import { createLitClient, createPKPSigner } from '@peekaboopay/lit' // Connect to Lit network const litClient = await createLitClient({ network: 'datil' }) // Create signer — no raw key const signer = await createPKPSigner({ litClient, pkpPublicKey: '0x04...', authSig, provider, }) // Use exactly like ethers.Wallet

Wrapped Keys

Import existing private keys into Lit's encrypted storage. The key is encrypted with Lit's BLS key, stored in their infrastructure, and only decrypted inside a TEE during signing — then wiped from memory.

FunctionDescription
wrapPrivateKey()Import an existing key into Lit's encrypted storage
generateWrappedKey()Generate a new key entirely within Lit's TEE
signWithWrappedKey()Sign a transaction — key decrypted in TEE, wiped after

Programmable Policies

Lit Actions are JavaScript programs that run inside Lit's TEE before signing. They enforce policies that can't be bypassed by the agent — spending limits, recipient whitelists, chain restrictions, and cooldown periods are cryptographically guaranteed.

FunctionDescription
createPolicyAction(policy)Generate a Lit Action from a policy config
SHIELD_ACTIONPre-built: max deposit amount enforcement
UNSHIELD_ACTIONPre-built: daily limits + cooldown periods
TRANSFER_ACTIONPre-built: recipient whitelist enforcement
No Raw Keys

With Lit Protocol, the agent's private key never exists on any single machine. Threshold signatures across decentralized TEE nodes ensure that even a fully compromised agent process cannot steal funds.


21 Relayer Network

Meta-transaction relay for stealth addresses. When a stealth address receives shielded funds, it has no ETH/TAO for gas. Funding it from a known wallet would break privacy. The relayer solves this — the recipient signs an EIP-712 request, the relayer submits the transaction and pays gas, deducting a small fee from the withdrawal.

How It Works

Flow Stealth Address Relayer Chain | | | |-- signRelayRequest() ------->| | | (EIP-712 typed data) | | | |-- submitTransaction() -->| | | (pays gas) | | |<-- receipt ---------------| |<-- withdrawal (minus fee) ----| |

API

FunctionDescription
createRelayClient(config)Create a relayer instance with RPC + private key
signRelayRequest(signer, request)EIP-712 sign a relay request from the stealth address
submitRelayRequest(client, request)Relayer verifies signature, submits tx, deducts fee
verifyRelaySignature(request)Verify a signed relay request is valid
estimateRelayFee(client, request)Estimate gas cost + relay fee before submitting
buildWithdrawMetaTx(params)Build a relay request for ShieldedPool withdrawal
buildAnnounceMetaTx(params)Build a relay request for StealthAnnouncer
Security

Relay requests use EIP-712 typed data signatures with nonce replay prevention and deadline enforcement. The relayer cannot modify the transaction — it can only submit what the stealth address holder signed. Maximum relay fee is capped at 5% (500 basis points).


22 Denomination Pools

Fixed-amount deposit pools following the Tornado Cash pattern. Every deposit in a pool is exactly the same amount — 0.1, 1, or 10 TAO. This creates much larger anonymity sets than variable-amount deposits, because all deposits and withdrawals look identical.

Why Fixed Denominations?

With variable amounts, an observer can correlate deposits and withdrawals by matching amounts. If you deposit 1.37 TAO and someone withdraws 1.37 TAO, that's a potential link. With fixed denominations, every deposit of 1 TAO looks identical to every other 1 TAO deposit — the anonymity set is everyone in the pool.

DenominatedPool.sol

A simplified ShieldedPool contract that enforces exact deposit amounts. deposit() requires msg.value == denomination. withdraw() always returns the denomination amount (minus the 0.5% protocol fee). No amount parameter needed — the contract knows exactly how much to send.

PoolDenominationUse Case
Pool A0.1 TAOSmall payments, micro-transactions
Pool B1 TAOStandard payments, inference fees
Pool C10 TAOLarger transfers, bulk operations

SDK Helpers

FunctionDescription
DENOMINATIONSStandard denomination constants in wei (TAO_01, TAO_1, TAO_10, ETH_01, ETH_1)
suggestDenomination(amount)Optimal split for any amount into fixed denominations
denominationLabel(amount, symbol)Human-readable label (e.g., "1 TAO")
isExactlyDenominatable(amount)Check if amount splits evenly with no remainder
totalDepositsNeeded(amount)Number of deposits needed for a given amount
Multi-Chain

DenominatedPool contracts deploy to any EVM chain. The same contract works on Bittensor (TAO pools) and Base L2 (ETH pools). All three denomination sizes share the same Groth16Verifier and PoseidonT3 library.


23 Nym Mixnet

Network-level privacy via the Nym mixnet. ZK proofs hide on-chain links and Lit Protocol protects keys — but RPC calls still leak IP addresses, timing, and metadata to providers and network observers. Nym closes this final gap by routing all blockchain traffic through a decentralized mixnet with packet mixing and dummy traffic.

Three Privacy Layers

LayerWhat's ProtectedHow
On-ChainWho paid whomZK proofs + stealth addresses + denomination pools
Key ManagementAgent's private keysLit Protocol threshold signing (no raw keys)
NetworkIP, timing, metadataNym mixnet routing (mixFetch + SOCKS5)

Two Modes

mixFetch — Drop-in replacement for fetch(). Routes HTTP RPC calls through the Nym mixnet. Best for browser-based and lightweight agents. Requires @nymproject/sdk.

SOCKS5 — Routes all TCP traffic through a local Nym SOCKS5 proxy. Best for Node.js and server-side agents. Requires the nym-socks5-client running locally.

Auto-detectmode: "auto" checks if the Nym SDK is available and uses mixFetch; otherwise falls back to SOCKS5.

Usage

TypeScript import { createNymProvider } from '@peekaboopay/nym' // All RPC calls now routed through Nym mixnet const nymProvider = await createNymProvider({ rpcUrl: 'https://lite.chain.opentensor.ai', chainId: 964, nym: { mode: 'auto' }, }) // Use with any adapter const provider = nymProvider.getEthersProvider()

API

FunctionDescription
createNymProvider(config)Create an ethers provider routed through Nym
createNymClient(config)Manage Nym mixnet client lifecycle
detectMode()Auto-detect mixFetch vs SOCKS5 availability
nymProvider.send(method, params)Send JSON-RPC call through mixnet
nymProvider.getInfo()Get connection info (mode, address, status)
nymProvider.disconnect()Disconnect from Nym network
Full-Stack Privacy

With Nym integrated, Peek-a-boo provides privacy at every layer: on-chain (ZK proofs), key management (Lit Protocol), and network (Nym mixnet). An agent using all three is untraceable — the blockchain can't link payments, no single node holds the key, and the network can't see who's making RPC calls.