Documentation

Caligo, end to end.

Caligo is an Orchard-style shielded pool on Robinhood. You get a public ERC-20 balance and a private one, so you can hold and move CALIGO without putting a balance, an amount, or a counterparty on-chain. Everything below is the full reference, for users and for the people running relayers.

Live on Robinhood mainnet. Every transfer is private, and every proof is verified on-chain by the same UltraHonk verifiers that run in your browser. You hold your keys and your notes — no admin can mint, freeze, or seize.

Introduction

On a public chain, every balance, counterparty, and payment time is permanent and searchable. Caligo keeps the public token for liquidity and interoperability, then moves your real position into a shielded pool: a set of encrypted notes. The chain stops narrating your financial life.

There are four core moves:

  • Shield — turn public CALIGO into a private note.
  • Send — pay another user with no amount or parties on-chain.
  • Unshield — exit to a fresh address through a relayer.
  • Earn — receive anonymity-mining rewards already shielded.

Every proof is generated in your browser; your spending key never leaves the device. The same UltraHonk verifiers that ran in your browser check the proof on-chain.

Public and private: your two sides

If you take one thing from this page, take this: Caligo gives you two balances and two addresses. Keeping them apart is what makes privacy work. If your private side were derived from your public wallet, anyone could link the two, and the privacy would be gone.

Public sidePrivate side
Where it livesyour MetaMask accountthe shielded pool (notes)
Addressyour 0x… walletyour Caligo address npk:encPk
Derived fromMetaMaskyour 12-word recovery phrase
On-chain visibilitybalance + history publicnothing visible
Holdspublic CALIGO + gas (ETH)your shielded balance

How value moves between the two

  • Shield — move public CALIGO into the pool (public → private).
  • Send — pay another Caligo address inside the pool (private → private).
  • Unshield — exit to any public 0x address (private → public).

Your Caligo address — the long npk:encPk string on the Receive page — is what you share to get paid privately. It is derived from your recovery phrase, not from MetaMask, and it never holds a balance on-chain: it's simply how a payer addresses an encrypted note to you. Your MetaMask address is your ordinary public account; it holds public CALIGO and pays gas, and it's the kind of address you give as the destination of an unshield.

Connecting, changing, or disconnecting MetaMask never changes your Caligo address — that depends only on your 12-word recovery phrase. The two addresses are deliberately unlinkable.

Getting started

Caligo runs on Robinhood mainnet. You'll need a browser wallet (MetaMask or another injected wallet) and a little Robinhood ETH for gas.

1 · Connect your wallet

Open the app and connect. It switches you to Robinhood automatically. This is your public account.

2 · Create your shielded wallet

The app generates a 12-word recovery phrase — your private wallet, separate from MetaMask. Write it down and keep it safe: it is the only way to restore your shielded funds on another device or after clearing your browser. Optionally set a password to encrypt the key at rest in this browser.

3 · Shield some CALIGO

On the Shield tab, enter an amount and confirm. Your public CALIGO becomes a private note in the pool; the proof is built in your browser. Watching your public address afterwards reveals nothing about the shielded balance.

4 · Receive and send privately

Open Receive to get your Caligo address; hand a fresh diversified address to each payer so your incomes can't be correlated. To pay someone, use their Caligo address on the Send tab.

5 · Unshield when you want to go public

Unshield to any 0x address — through a relayer, so the exit isn't linked to you and you pay no gas.

Back up your phrase first. Clearing your browser without the recovery phrase means the shielded funds can never be spent. The phrase is the only recovery path.

Core concepts

Notes

A note is the unit of private value: a tuple of (value, npk_d, rho) holding the amount, a diversified owner key, and a one-time secret. When you shield, your CALIGO becomes a note, and its commitment Poseidon2(value, npk_d, rho) goes into the pool's Merkle tree. The commitment is public. Everything it hides is not.

The commitment tree

All commitments live in one incremental Merkle tree, depth 24, up to ~16.7M leaves, hashed with Poseidon2. That one tree is the shared anonymity set everyone hides in. Each note also emits an ECIES-encrypted copy on-chain that only the recipient, or someone holding a viewing key, can read.

Nullifiers

To spend a note you reveal its nullifier Poseidon2(sk, rho), a one-time tag derived from your spending key and the note's secret. The pool keeps a list of spent nullifiers, so a note spends exactly once and double-spends can't happen. And the nullifier can't be traced back to the note's commitment.

Diversified addresses

One spending key produces many unlinkable receive addresses (ZEC-style diversification): npk_d = Poseidon2(sk, d). Hand a fresh one to each sender; the single key still finds them all when it scans.

Shield

Shielding deposits public CALIGO into the pool and hands you back a private note. The app builds a deposit witness (two dummy inputs, one real output plus change), proves it in your browser, then runs approve and transact from your connected wallet.

# CLI equivalent node scripts/wallet.mjs deposit 100000000000000000000 # 100 CALIGO node scripts/wallet.mjs scan node scripts/wallet.mjs balance

After shielding, watching your public address reveals nothing about the shielded balance. The deposit binds to the pool's current root, so it stays valid as the tree grows.

Send

A private send moves value note → note with an external amount of zero. The proof shows value balances; the amount and both parties stay off-chain. The recipient's output is encrypted to their address, so they discover it by scanning with their viewing key. Your change returns to you as a fresh note.

node scripts/wallet.mjs transfer 30000000000000000000 <recipient-caligo-address> "memo"

The recipient address is the long Caligo address (npk:encPk) from their Receive page, not a plain 0x wallet. A fixed-length 512-byte memo rides along with the note, so even its presence and length stay hidden in the on-chain ciphertext.

Unshield

Unshielding withdraws a note to a public 0x address. A zk proof shows you own an input note and that value balances; the nullifier burns it once. The proof binds the recipient, the relayer, and the fee, so nothing can be redirected in transit.

To stay unlinked, a relayer submits the withdrawal for you (see Run a relayer) instead of your own wallet, so the exit address has no on-chain link to you and pays no gas. You can self-pay if you prefer, but that ties the exit back to your wallet.

# self-paid (links the exit to your wallet) node scripts/wallet.mjs withdraw 20000000000000000000 <to-0x-address> # gasless via a relayer (unlinked) RELAYER_URL=https://relay.caligo.cash node scripts/wallet.mjs withdraw 20000000000000000000 <to-0x-address>

Earn — anonymity mining

Anonymity mining pays you for keeping value shielded. It works spend-on-claim: you prove in zk that you've held a note since an old epoch (which binds its age), consume it, and reissue a continuation note worth principal + reward, all without revealing which note it was. Since the note has to be unspent to claim and gets consumed by claiming, the reward tracks how long you actually held.

  • Reward ≤ rate × value × age, capped per claim.
  • The 55% AnonymityHolding emission bucket is the system-wide ceiling.
  • Rewards are minted into the pool as fresh private notes — earning never posts to a public scoreboard.

What privacy you get — and its limits

The privacy here is real, but it's conditional, and it gets stronger the more people use the pool. We'd rather under-promise than oversell, so here's the honest version: inside the pool, Caligo hides how much you hold and who pays whom. What it can't do is erase the patterns you create with amounts and timing, or hand you a crowd to hide in before many people have shielded.

Protected (inside the pool)

  • Amounts — note values live inside commitments; the chain never shows your balance.
  • Ownership — a spend reveals a nullifier that can't be linked back to its note or deposit.
  • Sender ↔ recipient — a private send reveals no amount and no parties; payment and change are indistinguishable.
  • Receive addresses — diversified addresses are mutually unlinkable.
  • Memos — fixed-length and encrypted, so their presence and length aren't observable on-chain.

Not protected (know these)

  • Amount correlation — the big one. A distinctive deposit and a later withdrawal of a similar amount can be linked by anyone watching the chain. Inherent to any variable-amount pool. Reduce it: use round amounts, split across notes, leave change shielded.
  • Timing. Deposit-then-quick-withdraw of the same value is linkable. Reduce it: let value sit in the pool.
  • The public boundary. Your funding address is visible on deposit, and the recipient on a withdrawal if you don't use a relayer. Always exit through a relayer.
  • Anonymity-set size. Your privacy is only as strong as the number of plausible notes your spend could be — an adoption property, not a code property. Early on the set is small; the 55% mining bucket exists to grow it.
  • Action type. Deposit / send / withdraw are distinguishable by design (the signed external amount). Only amounts and parties are hidden, not the kind of action.
  • Network metadata. IP address and RPC query patterns are out of scope for the protocol. Use a VPN/Tor if that's in your threat model.

Tips to maximize your privacy

  • Prefer round amounts; avoid one-of-a-kind values.
  • Leave change shielded and let value age in the pool before withdrawing.
  • Always withdraw via a relayer.
  • Hand a fresh diversified address to each sender.
  • Don't deposit and withdraw the same amount back-to-back.
Caligo is Orchard-inspired, not Zcash: it borrows the concepts (shielded notes, nullifiers, a commitment tree, viewing keys, diversified addresses) but uses EVM-practical cryptography (Noir/UltraHonk + Poseidon2). This isn't unconditional anonymity. The protocol gives you the tools; using them well is on you.

Compliance & disclosure

Caligo is neutral by default: the full commitment tree is itself a valid association set, so an honest user is never blocked and needs no ASP. There is no admin backdoor, no freeze, no seize.

  • Association sets (Privacy Pools). An Association Set Provider can publish a Merkle root of commitments it considers clean. At withdrawal you can optionally prove your note is in both the full tree and a registered clean subset — proof-of-innocence, without revealing which note. Multiple ASPs can coexist; relayers and exchanges choose which they trust.
  • Viewing keys. Hand an auditor or exchange an incoming/outgoing viewing key for full, user-controlled disclosure — read-only, never spend power. Export it from the app's Settings.

Proving

Caligo uses Noir circuits and the UltraHonk proof system (Barretenberg) over the BN254 curve. There are two circuits: transact (a single 2-in / 2-out transaction that covers deposit, transfer, and withdrawal via a signed external amount) and anon_claim (anonymity mining).

  • Witnesses and proofs are built in your browser — the spending key never touches a server.
  • The exact same verifier contracts check the proof on-chain.
  • UltraHonk uses a universal SRS, so there is no per-circuit trusted-setup ceremony.

Proofs are bound to the chain id (and, for transfers/withdrawals, to recipient/relayer/fee via an ext_data_hash), and the pool only accepts its own recent roots — so a proof cannot be replayed on another chain or pool.

Wallet & keys

Spending key & recovery phrase

Your spending key is derived from a 12-word recovery phrase (BIP-39). Write the phrase down: it restores your wallet on any device, and your notes re-sync from the chain when you scan. The key itself never leaves the browser.

Encrypted keystore

Optionally protect the key at rest with a password (scrypt → AES-256-GCM). This guards against someone who gets into your browser. It complements the recovery phrase; it doesn't replace it.

Backup matters. Clearing the browser without the recovery phrase means the shielded funds can never be spent. The phrase is the only recovery path.

Viewing keys

An incoming viewing key lets a third party see the notes you received (amount + memo) without spend power. An outgoing viewing key reveals the notes you sent. Both are user-controlled — selective disclosure, not a backdoor.

Emission

CALIGO has no hard cap; supply is governed by a disinflationary halving curve plus a perpetual tail. Emission accrues per epoch and is split by fixed, immutable weights across three buckets:

BucketShareFor
Anonymity holding55%users who keep value shielded
Relayer ops15%relayers / operators, by work
Treasury30%audits, liquidity, integrations, proving infra

Each bucket can only mint up to what time has unlocked for it, which rate-limits every reward manager. Reference parameters: base 144,000 CALIGO/epoch, halving ~every 2 years, perpetual tail 4,800/epoch, genesis 3,000,000. There is no LLM/useful-work pillar — unverifiable on-chain, so it is treasury-funded if it ever matters.

Genesis allocation

The entire 3,000,000 CALIGO genesis supply is minted once to the treasury, which then funds the immutable liquidity and vesting locks below. Nothing else is minted at genesis — all later supply comes only from the emission schedule above.

AllocationCALIGOLock
Initial liquidity2,000,000single-sided DEX position — locked forever
Vesting750,000CaligoVesting — 1-year cliff, 4-year linear
Treasury reserve250,000Safe + Timelock — operating reserve

Vesting

The 750,000 CALIGO genesis allocation sits in CaligoVesting — an immutable, non-revocable linear lock with a cliff. Same ethos as the liquidity locker: no owner, no admin, no revoke, no upgrade, no pause. Funded once, the schedule then runs on its own forever.

ParameterValue
Cliff1 year — nothing releasable before it
Total schedule4 years, linear
At the cliff25% unlocks at once, then it streams to 100% at year 4
Beneficiaryfixed at deploy — release() is permissionless but always pays the beneficiary

The cliff starts at deployment. There is no way to accelerate, redirect, or claw back the schedule — a credible, public anti-dump lock.

Run a relayer

A relayer submits a user's withdrawal so the exit is paid and broadcast by the relayer, never the user's wallet — so there is no on-chain link between the user and the exit. The user proves the withdrawal in their browser and hands the proof to a relayer over HTTP; the relayer broadcasts it and fronts the gas.

Why relaying is trustless

The pool binds recipient, relayer, fee, chainid, root, amount, nullifiers and output commitments into the proof. On a withdraw, settlement does not use msg.sender — the pool pays the recipient and relayer from the proof-bound addresses. So a relayer is pure transport: it cannot redirect the recipient, change the amount, or inflate its fee; it cannot relay a deposit (that would pull tokens from itself, so the service refuses it). The worst it can do is censor (not submit), and the app falls back to another relayer or a self-paid exit.

Two layers: relaying vs the registry

Don't conflate them. Relaying is open to anyone — the pool accepts any relayer address bound in a proof, so you can self-host a relayer for your own exits with no permission and no bond. The registry + rewards are opt-in: register a CALIGO bond in the RelayerHub to be discovered by the app and eligible for the RelayerOps bucket. Because exits are free, the only profit incentive is the bucket, which needs a bond.

Quick start

You need Node ≥ 20 and a funded key with Robinhood ETH for gas.

cd relayer npm install cp .env.example .env # set RELAYER_PRIVATE_KEY (a dedicated hot key — keep only gas on it, never your deployer key) npm start # serves on :8788

You now have a working relayer. It dry-runs every request with staticCall before broadcasting, so invalid transactions (spent nullifier, unknown root, bad proof) are rejected cleanly instead of burning gas.

HTTP API

EndpointReturns
GET /info{ relayer, feeWei, chainId, pool }
POST /relay{ txHash } — after 1 confirmation
// POST /relay — request body { proof, // 0x… UltraHonk proof inputNullifiers: [nf0, nf1], outputCommitments:[cm0, cm1], extData: { root, associationRoot, extAmount, recipient, relayer, fee }, encOut0, encOut1 // encrypted note blobs (0x for none) }

The service validates that the proof names this relayer, refuses deposits (extAmount > 0), and requires the exit fee to meet its advertised floor before spending any gas.

Be discoverable + earn (register)

Registering puts your {address, endpoint} in the on-chain RelayerHub the app reads, and makes you eligible for rewards. It needs a bond ≥ MIN_BOND (currently 1000 CALIGO). Easiest is auto-register on boot:

# .env AUTO_REGISTER=true PUBLIC_URL=https://relayer.example.com # the URL the app reaches you at BOND_WEI= # empty = exactly MIN_BOND

On boot the service reads MIN_BOND, approves the bond, and calls registerRelayer(feeRecipient, endpoint, bond) — best-effort, never blocking serving. The app validates that your /info reports the same address you registered (anti-spoof), so point your endpoint at a server you control. The bond is withdrawable (there is no slashing); its anti-Sybil value is the lockup cost per identity.

Economics — paid by the bucket, not per-exit

Exits are free for users (FEE_WEI = 0 by default). Relayers are paid by the RelayerOps bucket (15% of emissions), per epoch, in proportion to verified work — not by per-exit fees. A permissioned scorer finalizes each epoch within the emission schedule and a per-relayer cap (default 20%, hard max 50%); then anyone calls claim(epoch, relayer) and the reward is minted to your feeRecipient. So there is no fee market and no cheapest-relayer race — the app picks one relayer at random per exit, which spreads the work and is a privacy win (no single relayer sees most exits).

Operations & security

  • Gas — keep the relayer key funded with ETH; the service estimates each relay and falls back to a 12M gas limit (a transact proof + Merkle insert costs ~7M).
  • One key, one process — submissions are serialized per key (one nonce lane); scale with more keys, not more processes on one key.
  • Key hygiene — the relayer key is hot (it signs every relay). Use a dedicated key; never reuse a mainnet/deployer key. It only needs ETH for gas + (optionally) the CALIGO bond.
  • You can't steal — the proof binds everything. The only abuse vectors are censorship and self-griefing (submitting reverting txs at your own gas cost); neither touches user funds.

Security & trust model

Caligo is built to be boring where it matters.

  • No admin minting. The token has no admin mint. Only the EmissionController can mint, and only up to the schedule. No owner switch. No arbitrary minting.
  • Immutable core. The pool and circuits aren't patched in place. New designs ship as new versions, never edits to live private state.
  • Proofs bound to context. Each proof is bound to the chain id; the pool checks its own roots; every note spends exactly once.
  • Backed reward notes. Shielded rewards are minted into the pool as real CALIGO before the note is inserted, so outstanding value is always fully backed.
  • Governance. Mint-governing roles transition to a Safe multisig + Timelock with a delayed handover, so no single hot key controls scheduled emission.
No backdoors. No admin mint, no freeze, no seize, and no upgradeable private state. What the contracts do today, they will always do — new designs ship as new versions, never as edits to live funds.

Contracts

Deployed on Robinhood Chain mainnet (chain id 4663). Every address below is verifiable on Blockscout.

ContractAddress
CaligoTokenpublished at launch
EmissionControllerpublished at launch
ShieldedPoolpublished at launch
TransactHonkVerifierpublished at launch
AnonClaimHonkVerifierpublished at launch
Poseidon2published at launch
AssociationSetRegistrypublished at launch
AnonymityMiningControllerpublished at launch
RelayerHubpublished at launch
RelayerRewardControllerpublished at launch

FAQ

Why do I have two addresses?

Your MetaMask 0x address is your public account (public CALIGO + gas). Your Caligo address (npk:encPk) is your private identity, derived from your 12-word recovery phrase and unlinkable to MetaMask. Share the Caligo one to receive privately; use a 0x address as the destination of an unshield.

Which address do I share to get paid?

Your Caligo address from the Receive page — the long npk:encPk string, never your plain 0x wallet. Hand a fresh diversified address to each payer so your incomes can't be correlated.

Is my balance ever visible?

No. Once shielded, the value lives as an encrypted note in a shared anonymity set. Your public address shows nothing about the shielded balance.

What if I clear my browser?

Restore from your 12-word recovery phrase on any device; your notes re-sync from the chain. Without the phrase, the funds cannot be recovered.

Do I pay gas or a fee to exit?

No — a relayer fronts the gas and is reimbursed from the RelayerOps emission bucket. Exits are free for the user.

Can the team mint or freeze my funds?

No admin mint exists, and the pool is immutable. Disclosure is opt-in via viewing keys; there is no hidden backdoor, freeze, or seize.

Should I use real money?

Caligo is live on Robinhood mainnet and CALIGO is real. Use normal on-chain precautions: keep your 12-word recovery phrase safe, verify contract addresses, and start with an amount you are comfortable testing.