Architecture

Architecture

A Soroban contract on testnet, a snarkjs circuit, three Node services, and this Next.js frontend. The private path runs entirely in the browser; the services hold only issuer-side state.

PoolPass system architecture

Contracts

RoleContract ID
PoolPassCBVARDGO…M7K2FV
Testnet USDC (SAC)CAX5YVXG…I4KP77
Pool tokenCBUXSNRS…VY2KWC
Poseidon parity gateCDBKFLR5…VIFUWD
Groth16 parity gateCALDSMCD…EI6UFX

PoolPass methods

contract interfaceinitialize(issuer, usdc_sac, pool_token, groth16_vk: Bytes, merkle_depth: u32, pool_name: String) -> Result<(), Error>
update_accredited_set(issuer, leaf_hashes: Vec<BytesN<32>>) -> Result<BytesN<32>, Error>
subscribe(investor, amount: i128, proof: Bytes, public_inputs: Vec<BytesN<32>>) -> Result<BytesN<32>, Error>
get_pool_info() -> Result<PoolInfo, Error>
advance_epoch(issuer) -> Result<u32, Error>

The issuer must authorize set updates and epoch advances. The investor must authorize subscribe and its nested Testnet USDC transfer.

Events

PoolPass uses the SDK 26 #[contractevent] API. The generated publish prepends the snake-case event-name symbol to the topic vector; non-topic fields are a canonical ScMap, named rather than positional. Reproduced verbatim from the integration contract:

event shapesRootUpdated { root, leaf_count, epoch(topic), timestamp }
  topics: Symbol("root_updated"), epoch: u32
  data:   { leaf_count: u32, root: BytesN<32>, timestamp: u64 }   // key order: leaf_count, root, timestamp

Subscribed { investor(topic), amount, nullifier, commitment, epoch(topic), timestamp }
  topics: Symbol("subscribed"), investor: Address, epoch: u32
  data:   { amount: i128, commitment: BytesN<32>, nullifier: BytesN<32>, timestamp: u64 }  // key order: amount, commitment, nullifier, timestamp

EpochAdvanced { epoch(topic), timestamp }
  topics: Symbol("epoch_advanced"), epoch: u32
  data:   { timestamp: u64 }

For RPC getEvents, filter by type: "contract", the PoolPass contract ID, and the encoded first topic via nativeToScVal(name, { type: "symbol" }).toXDR("base64").

Services

ServiceRole
apiFastify HTTP: /accredit, /faucet, /verify, /prove (fallback), /pool/demo.
indexerReads RPC getEvents, dedupes by event ID, persists to services/data/events.json.
demo-issuerHolds the issuer key; commits leaves and mints Testnet USDC.

Repository

layoutcontracts/        Soroban contracts (poolpass, mock token, gates)
circuits/         Circom circuit, artifacts, fixtures, VK
src/              shared crypto: poseidon, field, merkle, groth16 serializer
services/         api, indexer, demo-issuer
merkle-tools/     standalone tree + proof helpers
web/              this Next.js frontend
deployments.json  machine-readable source of truth