# ZKDepth documentation > ZKDepth is a liquidity venue on Robinhood Chain (chain id 4663) where everything you can do in public, you can also do from a private balance: Uniswap v3 and v4 liquidity management, ZKDepth's own pools, ERC-4626 vaults, perps, launches and a swap router. An agent uses it with its own wallet. ZKDepth never asks for a private key, a seed phrase or a session key. The chapters of https://zkdepth.com/docs as plain text, in reading order. The index is https://zkdepth.com/llms.txt. --- # Introduction Source: https://zkdepth.com/docs/overview · Get started Everything you can do in public on Robinhood Chain, you can also do from a private balance. ZKDepth is a liquidity venue on Robinhood Chain (id 4663). Everything you can do in public, you can also do from a private balance: swap, provide liquidity, earn, trade perps and join launches. Every action still settles on chain. What it stops showing is who acted. In brief: - A private balance is a set of notes in one shared tree. Your browser finds them in the chain logs and proves you own them, so no server sees which ones are yours. - Spending always needs the owner's proof. There is no admin path to a note. - The immediate lane hides who acted, and the amount stays visible. The sealed batch lane also hides each order's size. [What each lane shows](https://zkdepth.com/docs/lanes) has the full table. - Your deposit and your withdrawal are always public. - No contract is upgradeable, and no admin function can withdraw, move or freeze what you hold. ## What you can do | You want to | Read | | --- | --- | | Hold a balance only you can open | [Private balance and notes](https://zkdepth.com/docs/private-balance) | | Swap, provide liquidity, earn or trade a launch from it | [Immediate lane](https://zkdepth.com/docs/immediate-lane) | | Trade without showing your order size | [Sealed batches](https://zkdepth.com/docs/sealed-batches) | | Trade perps | [On-chain perps](https://zkdepth.com/docs/perps), [Private perps](https://zkdepth.com/docs/private-perps), [Perps on Lighter](https://zkdepth.com/docs/lighter) | | Find your way around the app | [Using the app](https://zkdepth.com/docs/app-guide) | | Swap or set a limit in public | [Swaps and routing](https://zkdepth.com/docs/swaps), [Limit orders](https://zkdepth.com/docs/range-orders) | | Earn without managing a range | [Vaults](https://zkdepth.com/docs/vaults), [Hedged vaults](https://zkdepth.com/docs/hedged-vaults), [Structured notes](https://zkdepth.com/docs/structured) | | Provide liquidity yourself | [ZKDepth pools](https://zkdepth.com/docs/pools), [One-token deposits](https://zkdepth.com/docs/zap), [Position automation](https://zkdepth.com/docs/keeper) | | Launch a token | [Launchpad](https://zkdepth.com/docs/launch), [Launch pools](https://zkdepth.com/docs/launch-pools) | | See what it costs | [Fees and referrals](https://zkdepth.com/docs/fees) | ## Rules that hold everywhere **One chain.** Every contract is deployed on Robinhood Chain (4663), and the app sends transactions there only. **Your keys.** Your private balance keys come from one signature in your wallet, in your browser, and stay in memory in the tab. [Keys and backup](https://zkdepth.com/docs/keys) explains them. **Immutable contracts.** Contracts cannot be upgraded. The private pool's owner can only stop new deposits; spending and withdrawals stay open. The venue's owner can pause swaps and new liquidity, never withdrawals, and tune fees and limits inside caps written into the contracts. [Who can do what](https://zkdepth.com/docs/roles) lists every key. **One custody contract per token.** A blocklist hit on one stock token freezes only that token's notes. **$ZKD.** Protocol fees become ETH, the ETH buys $ZKD, and the $ZKD is burned, each step by a call anyone can make. See [Buyback and burn](https://zkdepth.com/docs/protocol). Where it lives: - `contracts/src/`: every contract, none upgradeable - `circuits/`: the proofs a private action runs on - `packages/zk/`: keys, notes, tree and proving in the browser --- # Using the app Source: https://zkdepth.com/docs/app-guide · Get started Each page in a few lines: what it is for and what it can ask you to sign. This chapter goes through the app page by page: what each page is for and what it can ask you to sign. In brief: - Every public write is simulated first, and if the simulation reverts your wallet never opens. A deposit into the private balance is the exception: it goes straight to your wallet. - Approvals are for the exact amount, never unlimited. - A write needs a connected wallet, Robinhood Chain (4663) and deployed contracts. A disabled button says which one is missing. - Your wallet signs Lighter orders as messages, not transactions. - A private action is a proof your browser builds and a relayer sends. Your wallet only signs the key request and public deposits. ## Signing in You connect a browser wallet, MetaMask, Rainbow or WalletConnect, or sign in with email, which creates an embedded wallet. On any other network the bar shows a Wrong network button that switches you to Robinhood Chain. ## How a write is sent 1. Check: The app checks the wallet, the network and that the contract is deployed, and stops with the reason if one is missing. 2. Simulate: It runs the call as an eth_call. A revert stops here, with the reason in a toast, before your wallet opens. 3. Confirm: Your wallet asks you to confirm. 4. Send: Once the transaction has a hash, a toast links to it in the explorer. 5. Refresh: The affected data reloads over the next 10 seconds, so the indexer has time to catch up. Approvals come first when a token needs one. Permit2 is used only for the Uniswap Universal Router and the v4 position manager, and its allowance expires after 30 minutes. Slippage defaults to 50 bps for a spot trade and 100 bps for a zap. Above 500 bps you have to tick an acknowledgement first. ## The pages | Page | What it is for | What it can ask you to sign | | --- | --- | --- | | Markets | Every market with its price, depth and funding | Nothing | | Swap | One token for another, routed across pools, in public or from a private balance | An approval and a swap | | Trade | The terminal: chart, order book, spot, limit and perp tickets, and range entry. The on-chain perps ticket has a private side | Orders as messages, swaps and zaps as transactions | | Private | Your private balance: keys, holdings, deposit, send, withdraw, sealed orders, activity and backup | The key request, and a deposit with its approval | | Liquidity | Liquidity managed for you: pick a market, hand a range to the keeper, or use a vault | A zap, a keeper enrollment or a deposit | | Earn | Vaults and notes side by side, with a private side on LP, DLMM and perps vaults | A deposit or a withdrawal | | Launch | Every launch, closest to graduating first. Create one at `/launch/new`, and trade each on its own page, in public or from a private balance | A launch, a buy, a sell or a graduation | | Token vaults | At `/launch/vaults`: move an aged token with a deep pool into a managed vault, and trade pre-market perps | `track`, `graduate`, `createMarket` and perp trades | | Pons tokens | At `/launch/pons`: tokens from Pons, tradable here and ready for a ZKDepth pool | A trade, or opening a pool | | Portfolio | Positions, orders, vaults, wallet, referrals and activity for any address, and a section on your private balance | Closes, claims and referral registration, on your own wallet only | | Pools | Every ZKDepth pool, and its page with bins and positions. A DLMM pool's page has a private side | Add, remove and bin orders | | Stocks | Stock tokens with their sessions, feeds and pools | Nothing | | Lounge | Who is active, and how the points stand | A follow, as a message | | Ideas | Trades people sign their name to | An idea, as a message, and a mirror of someone else's trade | | Agents | Connect your own agent, and watch its wallet | Nothing | | Alerts | Rules on prices and positions, in the browser or on Telegram | Linking Telegram, as a message | | Protocol | Where fees go, with a button for each public call | `flush`, `convert`, `buyback`, `poke` | | Planner, Sizing | Plan a position before you open it | Nothing | | Ranges | Three LP ranges on one real market side by side, with draggable bands, measured against its real hourly closes | Nothing | | Backtest | Hold, an LP range and a hedge run through real hourly closes on any market. 60D and 90D are ETH, BTC and SOL only | Nothing | ## The private side `/private` is a main nav item, and the green Deposit action in the top bar goes there. A first visit offers `Set up keys`, where your wallet signs one fixed typed-data request shown on the page, only on click, or `Restore from backup`, an encrypted file plus its passphrase with no wallet needed. Keys stay in memory in the tab. `Lock this tab` forgets them, and a reload asks again. See [Keys and backup](https://zkdepth.com/docs/keys). After that the page shows your private balance in USD and the set size, which is every note in the tree. Below are: - Holdings by asset, with bin shares, vault shares and position notes. - Deposit in green, send to a `zkd1` address, and withdraw, with `Use a fresh address` and a linkability reading that says hard, somewhat easy or easy to link, with reasons. - Sealed orders with a randomised suggested claim time. A claim or refund opens a drawer with the same private foot as any spend. - Activity, your private address, backup download, your viewing key (shown on click) and disclosure: a proof over up to 8 chosen notes, downloaded as a file. ### Tickets with a private side | Where | From a private balance | Worth knowing | | --- | --- | --- | | Swap | `Immediate`, and `Sealed batch` when the pair has a market | The sealed ticket shows the epoch countdown, each side's order count and state, and a warning when a tranche is thin | | DLMM pool page | Add one token at a time with bins, shape and an optional active bin. Remove per bin | One proof and one transaction per bin | | v4 pool panel | Nothing: its private side says why, and links to swap and liquidity | A range position stays public, because it is two tokens in one position | | Launch | Buy and sell. A launch whose opener came from `SealedMarkets.createLaunchOpener` takes sealed buys on its page while it waits | Inside the protected window each buy has its own wallet limit and pays the protection fee. A claim of launch tokens goes from a fresh address you choose, until a relayer prices them | | Earn | Deposit and redeem on LP vaults and DLMM vaults | A stock vault takes two tokens and has no private lane. The perps vault takes private deposits, and its shares leave through a plain withdrawal to a fresh address, because of the exit delay | | Trade | The on-chain perps ticket, with position notes in the Positions tab. Manage opens a drawer with Close, Reduce, Add size, Margin and Hand over | Lighter stays public | | Portfolio | A section on your private balance | | Public stays the default until a private balance is open and holds something. ### A private action The private side of a ticket shows the note it uses, one sentence on what the action reveals, the relayer fee, the 0.10 % fee and one primary action. Proof progress shows real steps: read the pool logs in the browser, load the proving key, build the proof in this browser, send it through the relayer, done. A failed price check says the note stayed unspent. ### The relayer The relayer's URL is the only private-lane server your browser talks to, and a fee quote shows before anything is sent. It refuses adapters and tokens outside the manifest by default. If it refuses a route the ticket shows why, and if it does not answer the ticket says so. Either way it offers a send from a fresh address derived from your keys, which needs a little ETH and pays no relayer fee. That send is always your explicit choice and never uses your connected wallet. Merkle paths are built in the browser. Logs come through a same-origin RPC route with server-held keys, which serves pool and sealed lane logs only. ### Proving The wasm and the proving key load only for the circuit an action needs, are cached in the browser and are checked against a manifest's SHA-256. A deployment whose manifest has development keys refuses to run private lanes anywhere but a local chain. ## Perp orders Every perp action on Lighter (set up, place, cancel) is a message you sign, with a fresh nonce and a timestamp. To trade there, deposit USDG to Lighter from your wallet first. The app then asks for three signatures that register an order key with your Lighter account. The key can place and cancel orders and cannot withdraw. See [Perps on Lighter](https://zkdepth.com/docs/lighter). Perps on ZKDepth's own engine are ordinary transactions. See [On-chain perps](https://zkdepth.com/docs/perps), and [Private perps](https://zkdepth.com/docs/private-perps) for the same ticket from a private balance. ## Data labels "Live" appears only while a source actually streams. Otherwise a page says when it last read a figure, or that the source is unavailable. When the API does not answer, pages show what they can read from the chain and offer Try again. ## The tour Take the tour, in the bar's More menu, opens a walk through the main pages. It moves between pages for you. Esc ends it, the arrow keys step through it, and it never opens by itself. --- # What is in ZKDepth Source: https://zkdepth.com/docs/whats-new · Get started The product as it stands today, area by area. This page describes ZKDepth as it stands today. It changes when the product does, and it has no history. ## Private balance Deposit USDG, a stock token or ETH into one shared note tree at `/private`, then move it with proofs built in your browser: private transfer, withdrawal, and actions through adapters. Keys come from one wallet signature of a fixed request and stay in memory in the tab, with an optional encrypted backup. [Private balance and notes](https://zkdepth.com/docs/private-balance) starts here. ## Private lanes Swap, DLMM pools, launches, Earn and on-chain perps have tickets with a public or private switch. The immediate lane settles a private swap, a bin move, a vault deposit or a launch trade in one transaction. Sealed batches hide each order's size and cross at one rate per side, with at least 5 orders a side by default and a total that shows only in the transaction that trades it. Private perps hold a position as a note, with the owner hidden and the size and price public. A v4 range position and a stock vault stay public, as each takes two tokens at once. [What each lane shows](https://zkdepth.com/docs/lanes) has the table. ## Public venue DLMM pairs with single-sided bins and a composition fee, DAMM pools with dynamic fees, stock pools with session-aware fees, a Router across ZKDepth pools, public Uniswap v4 pools and Pons, and limit orders. [ZKDepth pools](https://zkdepth.com/docs/pools) and [Swaps and routing](https://zkdepth.com/docs/swaps) cover them. ## Perps An on-chain engine against a USDG vault with 35 launch markets: ETH and 34 stock tokens on Chainlink feeds. Lighter stays as the public perp and hedge venue. [On-chain perps](https://zkdepth.com/docs/perps) has the parameters. ## Earn and LP tools LP vaults, hedged vaults and structured notes hold positions for you. One-token deposits, range orders and position automation help you run your own. ## Launches A segmented bonding curve that graduates into a DLMM or DAMM pool, with a protection window, creator vesting and a developer API. A launch can open through a sealed batch that stays open for at least 10 minutes, and its curve takes private buys and sells. [Launchpad](https://zkdepth.com/docs/launch) has the defaults. ## Where it runs [Contract addresses](https://zkdepth.com/docs/contracts) lists what this deployment runs on, from its manifest. --- # Lounge and Ideas Source: https://zkdepth.com/docs/lounge · Get started The activity feed, points, follows, signed ideas and mirroring. The Lounge (`/lounge`) is ZKDepth's community view: who is active, what they just did and how the points stand. Everything it shows is on chain, signed by a wallet or read from the points ledger, and every address links to its profile. In brief: - The live feed lists new ideas, vault deposits and withdrawals, new ZKDepth pools, graduations and filled limit orders, newest first. - Points are an off-chain ledger, not $ZKD, and nothing settles them: 1 point per dollar of volume and 5 per dollar of fees earned. - An idea is a position published with the argument for it, signed by the author's wallet. - Mirroring returns a transaction for your own wallet to send. It executes nothing. - A follow is a message your wallet signs, with no gas. Follows are public. ## Live activity `GET /lounge/feed` merges the newest 40 rows of each source and keeps the newest 120 lines. The page asks every 15 seconds while the tab is open, and one build answers every request for 10 seconds. Amounts are priced as the points ledger prices them, and a token nothing prices shows its amount alone. ZKDepth's own contracts are never named as the one who acted. **Everyone / Following** narrows the feed to the addresses you follow, and the kind pills (Ideas, Vaults, Launches, Limit orders) narrow it further. If one source does not answer, the others are still listed and the page says so. ## Points Volume counts double for 7 days after you post an idea. Each ledger row comes from one on-chain event, priced in USD when it is booked: - **Volume:** a swap through the ZKDepth Router, a pre-market perp open or voluntary close, and a Lighter fill that paid ZKDepth's fee. - **Fees earned:** keeper harvests, range order fills, ZKDepth vault harvests (split across holders), and the referral share a referrer earns. A job books new events every 5 minutes. `GET /points/leaderboard` ranks every address with points, and addresses on equal points share a rank. The ranking is rebuilt at most once a minute. ## Ideas An idea publishes a position with its argument and running result. `POST /theses` verifies an EIP-712 signature over `Thesis { positionRef, side, text, ts }`, where `positionRef` names a v3 position, a perp or a vault, `side` is long, short or LP, and `text` is at most 500 characters. `ts` must be within 300 s of the server clock, so one signature makes one post. Top ideas this week are those posted in the last 7 days with the best result so far. The Ideas leaderboard ranks authors by realised fees plus realised PnL across their indexed positions. ## Mirroring `POST /theses/:id/mirror` opens the same kind of position at your size, and executes nothing itself. It returns one of: - `zapMint` calldata for a v3 position, with the author's pair, fee and ticks, 100 bps of slippage and a 20-minute deadline; - a `deposit` for a vault, which the web hands to Earn to size; - given a size and a live Lighter mark, a sized market order for a perp, which goes through the [signer](https://zkdepth.com/docs/lighter) under your own signature. Your wallet signs and sends it. ## Follows and profiles A follow is a public edge from one address to another. Every change is one `POST /follows` carrying an EIP-712 signature over `FollowUpdate { account, follow, unfollow, nonce, ts }`, so your wallet shows exactly whom you follow. It costs no gas and moves no funds. Parameters: POST /follows - `follow + unfollow` (allowed: 1 to 100 addresses): Per update. One address cannot be in both. - `following` (allowed: up to 500 addresses): Per address, after the update. - `ts` (allowed: ±300 s): From the API clock. - `nonce` (allowed: 16 to 64 bytes): Random, used once per address. `/lounge/u/
` shows one address: its ideas with their results, its track record, its points and rank, how many addresses follow it and how many it has referred, and its recent lines in the feed. Profiles are kept out of search engines. Where it lives: - `apps/api/src/lounge/feed.ts`: the feed sources, prices and the 10-second build - `apps/api/src/points.ts`: the ledger and what earns points - `apps/api/src/theses/service.ts`: posting an idea and building a mirror --- # Private balance and notes Source: https://zkdepth.com/docs/private-balance · Private balance How a balance becomes notes in one shared tree, and the three ways a note moves. A private balance is a set of notes only you can open. Every token is a note asset in one shared tree, so every deposit adds to the same anonymity set. In brief: - A note is a commitment on chain. Only its owner can open it or spend it. - Your deposit and your withdrawal are public: the address and the amount show. A spend in between does not name the note it came from. - Spending always needs the owner's proof. There is no admin path to a note. - Your browser rebuilds the tree from chain logs, so no server sees which commitment is yours. - Each token sits in its own custody contract. ## In the app `/private` is the Private item in the nav, and the green Deposit action in the top bar goes there. On a first visit it offers `Set up keys` or `Restore from backup`. See [Keys and backup](https://zkdepth.com/docs/keys). After that it shows: - Your private balance in USD, and the set size: every note in the tree. - Holdings by asset, with bin shares, vault shares and position notes. - Deposit, send to a `zkd1` address, and withdraw. A withdrawal offers `Use a fresh address` and a linkability reading that says hard, somewhat easy or easy to link, with reasons. - Sealed orders with claim and refund, and a randomised suggested claim time. - Activity, your private address, backup download, viewing key, disclosure and `Lock this tab`. Swap, pools, launches, Earn, perps and Portfolio have private tickets of their own. [Using the app](https://zkdepth.com/docs/app-guide) lists them. ## What a note is ```text commitment = Poseidon(amount, assetId, ownerBlind) ownerBlind = Poseidon(pk, blinding) pk = Poseidon(sk) nullifier = Poseidon(commitment, leafIndex, nk) assetId = keccak256(token, tokenId) >> 8 ``` Stablecoins, stock tokens, launch tokens, ERC-4626 shares, LP vault shares, DLMM bin shares (`tokenId` is the bin) and perp positions (`tokenId` is a random share id) all move through one circuit. Private LP, private yield and private perps are actions on a note, not separate systems. ## Three ways a note moves Diagram: How a note moves. Your keys and proofs stay in the browser. The chain sees a spent nullifier and a new commitment. **`deposit`** pulls the tokens, measures what arrived and computes the commitment on chain. It needs no proof, and a note can never claim more than arrived, so a fee-on-transfer token simply makes a smaller note. `depositETH` wraps ETH to WETH. Bin shares and positions cannot be deposited: they enter through `act`. **`transact`** covers private transfer and withdrawal. It is a join-split with 4 inputs and 3 outputs. The proof binds the recipient, the relayer, the fee, the ciphertexts and a deadline, and a relayer must be the address that sends the transaction. A private transfer shows a spend and the relayer's fee, and no amount and no recipient. A fee paid in the note's own token shows that token, which is why a fee in private WETH comes first. **`act`** spends a note into an adapter you choose, bound in the proof. The adapter runs the venue call and returns tokens to custody, and the pool measures each output and writes a new note. There is no adapter allowlist: an adapter only receives what a valid proof from the note's owner sends it. The one exception is the sealed lane, which the pool refuses as an adapter, because its route belongs to its own settlement. And an action that leaves the spent token on its adapter reverts, since an adapter holds nothing between calls. [Immediate lane](https://zkdepth.com/docs/immediate-lane) covers this. The client refuses a withdrawal or a full-tree exit to one of the pool's own contracts. One proof moves one asset, and it can pay its relayer from a second one, private WETH. If an amount needs more notes than fit, a self-transfer merges them first: one more proof and one more transaction. ## The tree One incremental Poseidon tree of depth 26 holds every note. A proof can reference any of the latest 65,536 tree states, so a proof built a moment ago still verifies after other people's transactions land. Spending a note publishes its nullifier. Nothing on chain links a nullifier to its commitment, and a nullifier spends once. Deposits and action outputs stop when the tree is three quarters full, which leaves room for change notes. Every open sealed order holds back two more leaves for its claim or refund, and nothing else can use them. `exitFullTree` withdraws when the tree has no room left for a full join-split beside those. `treeFull()` says when that point is reached, and `openOrders()` counts the open orders. ## Your keys Your wallet signs one fixed typed-data request, and your keys derive from that signature. It costs nothing and sends no transaction. Sign it only on the ZKDepth app: a signature of this request is your keys, for good. There is no way to rotate them short of moving your notes to a new balance. The app forgets them on `Lock this tab`, on a reload and when you switch wallets, and the wallet asks again next time. See [Keys and backup](https://zkdepth.com/docs/keys). ## Custody `AssetCustody` is one contract per token, created by anyone through CREATE2 on first use. It tracks what is owed per asset and measures its balance on every move. A hostile token or adapter can only reach what its own notes hold. ## Yield on a private balance ERC-4626 shares can sit in a note like any other token. Deposit a note into a vault through `VaultAdapter`, and the shares come back as a note that keeps earning. LP vaults and DLMM vaults go in and out this way. The perps vault takes private deposits, and its shares leave through a plain withdrawal to a fresh address, because its exit takes two steps and a delay. See [Immediate lane](https://zkdepth.com/docs/immediate-lane). ## What the owner can do The pool is immutable. Its owner can stop `deposit` and nothing else: spending, withdrawals and actions stay open whatever the owner does. A change ships as a new pool, and people move across themselves. ## The setup Proofs are Groth16 on BN254, and the verifiers are set immutably at deploy. Phase 1 uses a public Powers of Tau file. Phase 2 takes three contributions across two separate machines, run by the owner, and a public drand beacon finalises each circuit. Its round is named ahead of the last contribution, so nobody contributing can have seen it. The transcript and hashes are published. The deploy refuses a development verifier on chain 4663. The app loads the wasm and the proving key only for the circuit an action needs, caches them in the browser and checks them against a manifest's SHA-256. A deployment whose manifest has development keys refuses to run private lanes anywhere but a local chain. Where it lives: - `contracts/src/shielded/ShieldedPool.sol`: the tree, nullifiers, deposit, transact and act - `contracts/src/shielded/AssetCustody.sol`: one clone per token - `circuits/src/transaction.circom`: the join-split --- # What each lane shows on chain Source: https://zkdepth.com/docs/lanes · Private balance The table to read before you trust a private action: what is hidden and what stays visible. Every private action still settles on chain. This table says what each lane stops showing and what stays visible. | On chain | Immediate | Sealed batch | Perps | | --- | --- | --- | --- | | Who acted | Hidden | Hidden | Hidden | | Your order size | Visible | Hidden | Visible | | Market | Visible | Visible | Visible | | Price | Visible | One rate per side | Visible | | Batch totals, order count, net routed | Not a batch | Visible | Not a batch | | Your deposit, your withdrawal | Visible | Visible | Visible | ## How to read it **Immediate.** A swap, a bin move, a vault deposit or a launch trade from a note. Nobody can tell which wallet acted, and the trade itself is public like any other trade: the market, the amount and the price. See [Immediate lane](https://zkdepth.com/docs/immediate-lane). **Sealed batch.** Your order joins a batch for one market. A side's order count shows as orders land. Its total, the net routed and its one rate show together, in the transaction that settles the batch. An amount stays hidden from everyone except all 3 keepers together. See [Sealed batches](https://zkdepth.com/docs/sealed-batches). **Perps.** A position note hides the owner. Size and price stay public because the vault's solvency depends on them. See [Private perps](https://zkdepth.com/docs/private-perps). Every ticket says in one sentence what its action reveals, before you send it. ## What no lane hides Your deposit and your withdrawal are public: the address and the amount show. Depositing 1,000 USDG and withdrawing 1,000 USDG a minute later to a known wallet links the two for anyone who looks. The client reads a withdrawal before you make it, using the amount, the timing and the destination, says it is hard, somewhat easy or easy to link, and says why. A relayer's fee is public too. Paid in the note's own token, it shows that token. ## How much the set hides Privacy comes from the other notes in the tree. Early on there are few, so an amount or a time can stand out. The app shows the live size of the set, with no invented activity. A batch with few orders reveals more, so a side needs at least `minOrders` orders (default 5) to seal, and otherwise waits and refunds. Someone holding all but one of a tranche's orders can still read the last from the total: see [Sealed batches](https://zkdepth.com/docs/sealed-batches). ## Who can read what - **Anyone:** everything in the table. - **Sealed batch keepers:** all 3 keepers together could read per-order amounts, but not who placed them. They cannot move funds. - **A relayer:** the proof it submits and what that proof makes public, the same as the chain, and the IP address the request came from. Not your wallet. The relayer here writes no request log and holds an IP only as a salted hash for its rate limit, and anyone can run their own. - **The site's RPC route:** the chain reads your browser makes, and a raw transaction sent from a fresh address. Its logs are the pool's and the sealed lane's only, the same for everyone. Your browser builds its Merkle paths itself, so no server learns which commitment is yours. - **Someone you give your viewing key:** every note of the balance, its amounts and which are spent. They cannot spend. - **Someone you choose:** the `disclose` circuit proves up to 8 notes to them, with their amounts. See [Keys and backup](https://zkdepth.com/docs/keys). --- # Immediate lane Source: https://zkdepth.com/docs/immediate-lane · Private balance One proof, one transaction: swap, provide liquidity and earn from a note. The owner is hidden, the amount is visible. The immediate lane spends a note straight into a venue call, settled in one transaction. The owner is hidden and the amount is visible. There is no reserve step and no settle step. In brief: - One proof, one transaction. If the price check fails, it reverts and your note stays unspent. - The output comes back as a new note in the same transaction. - One proof moves one asset. That shapes bin liquidity and rules out a two-token position. - A relayer submits the proof. If it does not answer, you can send it from a fresh address with no relayer fee. - Fee: 0.10 % on what leaves the pool, plus the relayer's quote. See [Fees](https://zkdepth.com/docs/fees). ## One action, step by step 1. Open your keys: Your wallet signs one fixed request once, and your browser derives your keys from it. See Keys and backup. 2. Read the logs: The browser reads pool logs, decrypts your notes and rebuilds the tree, so it builds the Merkle path itself. 3. Prove: A web worker builds the proof in this browser. The transaction circuit has about 32,000 constraints. The proof binds the adapter, the call data, each output's token, minimum and owner, the relayer, its fee and a deadline. 4. Submit: A relayer simulates the call, sends the transaction and is paid from the note. A fee quote shows before anything is sent. 5. Settle: The pool spends the note and hands the tokens to the adapter. The adapter runs the venue call and sends the output to custody. The pool measures it and writes the new note. ## Adapters | Adapter | What it does | | --- | --- | | `SwapAdapter` | A private swap through the Router, across ZKDepth pools, public v4 pools and Pons curves, or down a Uniswap v3 path, where the deep WETH/USDG liquidity is | | `DlmmAdapter` | Bins in and out of a DLMM pair. The position is bin shares held as notes | | `VaultAdapter` | Deposit into and redeem from an ERC-4626 vault, and in and out of a DLMM vault through the venue's zap. The shares are held as notes, so they keep earning | | `LaunchAdapter` | Buys and sells on a launch's bonding curve | | `PerpAdapter` | Positions on the perps engine, each held as one note. See [Private perps](https://zkdepth.com/docs/private-perps) | | `EthExitAdapter` | Exits a WETH note as native ETH, so a fresh address can pay for its own gas | The adapter is your choice, bound in the proof. The pool has no allowlist, and the web app lists the known adapters. An adapter holds nothing between calls: an action that leaves the spent token on it reverts, and anything sent to one any other way goes to the next action in that token, so never send tokens to an adapter. The client refuses a withdrawal to any of the pool's own contracts. A relayer is more careful than the pool, as a hostile token or contract could waste its gas. By default it touches only what it can vouch for: the adapters and tokens of the deployment, launches of the launchpad, pairs of the venue's factory, and whatever its operator adds through `RELAYER_ADAPTERS` and `RELAYER_TOKENS`. So a route through a Pons curve or a hook it does not know is refused. An operator lifts a list by setting it to `any`. When a relayer refuses, the ticket shows why and offers the send from a fresh address. ## What one proof can move **Adding to bins.** One token per action: X into the bins above the active one, Y into those below, so both sides take two actions. An action covers up to 15 bins, in a shape you pick. You can also put some in the active bin, which pays the composition fee when the mix differs. Each bin comes out as its own note. **Removing from bins.** One proof and one transaction per bin. Both tokens come out as notes. Swap fees stay in the bins and come out with the burn, so there is no separate fee claim. **Vaults.** An LP vault takes one asset and pays it back. A DLMM vault takes one token through the zap, which swaps part of it into the other, and pays both tokens out. A stock vault takes two tokens and has no private lane. The perps vault has no synchronous exit, so a redeem reverts and the note stays unspent: its shares leave through a plain withdrawal to a fresh address, and the exit runs from there. **Range positions.** A v4 range is two tokens in one position, so it stays public. From a private balance, use a swap and bin liquidity. ## Launches The curve's wallet limit counts the recipient. Inside the protected window each private buy goes through a fresh recipient, so the limit applies per buy and the protection fee is paid every time. Past the window, tokens go straight to custody. A buy past the close is cut to fit, and the rest comes back as a note of the quote token. A buy that closes the curve also graduates it. Sells stay open until graduation. See [Launchpad](https://zkdepth.com/docs/launch). ## Paying the relayer The fee comes from a private WETH note when the balance holds one, which keeps the token out of the fee. Otherwise it comes from the note's own token, if the relayer takes that token: WETH and USDG by default, and any other its operator lists. Bin shares and positions pay a relayer only from private WETH, so a balance that relays them keeps a small WETH note. Otherwise the spend goes from a fresh address with no relayer fee. A relayer limits requests per IP and attempts per note, and caps the gas of one transaction, at 12M by default. ## A fresh address If the relayer does not answer or will not take a spend, the ticket says so and offers a send from a fresh address derived from your keys. It needs a little ETH for gas and pays no relayer fee. You choose it each time: nothing falls back to it by itself, and it never uses your connected wallet. Funding it from your wallet links the two, so fund it with a private withdrawal of WETH as native ETH where you can. ## When it fails A failed minimum reverts the whole transaction, so the note stays unspent and you can try again, and the ticket says so. The relayer simulates before it sends, at the gas price it will pay. If a proof of a note reverts anyway, it waits an hour before it sends for that note again. A proof stops being valid 15 minutes after it was built, so a relayer cannot sit on one, and the note stays locked in your browser until then. Nothing in the SDK leaves a trade unprotected by default. A vault or launch action takes its minimum from a quote, and a perp's limit comes from the oracle's price and `slippageBps`, 100 by default. To trade with no limit, say so with `limitPrice: 0n`. With no oracle price to go by, a perp call throws. ## What the chain shows A spent nullifier, the new commitments, the adapter and the trade itself: the token and amount going in, the route and its minimum in the call data, and each output's token and amount. The link back to your wallet is gone. [What each lane shows](https://zkdepth.com/docs/lanes) has the table. Where it lives: - `contracts/src/shielded/interfaces/IActionAdapter.sol`: the adapter interface - `contracts/src/shielded/adapters/SwapAdapter.sol`: one adapter, in full - `packages/zk/src/lanes.ts`: every private action from the browser - `apps/relayer/src/app.ts`: quotes and submission --- # Sealed batches Source: https://zkdepth.com/docs/sealed-batches · Private balance Orders that cross in a batch at one rate per side, with each order size hidden. A sealed batch collects orders for one market and crosses them at one rate per side. Hidden: who placed each order and how much each order was. Visible: the market, the number of orders per side, each side's total, the net routed and one rate per side. In brief: - An order's amount stays hidden from everyone except all 3 keepers together, and they do not see who placed it. - A side needs at least `minOrders` orders to seal, 5 by default and never fewer. A thin batch waits up to `maxWait`, 600 seconds by default, then voids and refunds. - A side shows its order count as orders land. Its total shows only in the transaction that trades it, after the seal and its price window. - Whoever holds all but one of a tranche's orders can read the last from the total. - Opposing totals cross at a reference price. Only the net goes through the public Router. - Claim and refund need nobody's permission. - Fee: 0.10 %, out of the batch. See [Fees](https://zkdepth.com/docs/fees). ## The parameters A market's terms are fixed when it is added. The owner adds markets inside these bounds. Parameters - `epochLength` (allowed: 30 - 3,600 s; default: 60): A side can seal at each epoch end - `minOrders` (allowed: 5 - 64 orders; default: 5): Distinct orders a side needs per tranche - `maxWait` (allowed: one epoch - 3,600 s; default: 600): From a tranche's first order until a thin one voids - `priceWindow` (allowed: at least 10 s; default: 30): The reference is the price as of this long after the seal - `settleWindow` (allowed: 30 - 600 s; default: 120): From the seal until an unsettled batch voids - `bandBps` (allowed: 10 - 300 bps; default: 50): How far a quote or the routed net may sit from the reference Every market has 3 keepers, and all 3 must post: 3 of 3. ## How a batch runs Diagram: One sealed batch. Each order’s amount stays hidden. Order counts, each side’s total, the net and one rate per side are public. A void batch refunds every order as a new note. 1. Order: The sealed_order proof spends your notes and creates an order commitment and a Pedersen commitment to the amount on Baby JubJub. Additive shares of the amount and its blinding are encrypted to the 3 keepers, and the circuit proves all of it. No amount appears in calldata. The contract adds your commitment to the tranche collecting when the order lands, so a slow proof only joins a later epoch. A memo to your viewing key lets your keys find the order again. 2. Seal: A side seals at the end of an epoch once it holds at least minOrders distinct orders. With fewer it rolls over into the next epoch. So no side's total ever shows with fewer than minOrders orders behind it, and a thin side holds up nothing. 3. Price: The reference is the price as of priceWindow seconds after the seal: a TWAP over exactly those seconds from a deep Uniswap v3 pool, or the Chainlink round in force at that time for stock tokens, in the equity session. Nothing can reveal before then, so no order saw its price and no keeper's timing changes it. A small quote on the market's own route must agree with it inside the band, both ways. 4. Reveal and settle: Each keeper posts the sum of its shares, both sides of a batch in one call. The last post checks that the total opens the aggregate, so keepers can stall but cannot lie. That same post makes the total public in TrancheRevealed and settles the batch, or it reverts and shows nothing. Two sides sealed by the same epoch end settle together. Opposing totals cross at the reference, and the net goes through the Router on the market's fixed route, inside the band. A net too big for the route trades in part, trying up to 8 sizes, each half the last, and the rest comes back to that side in kind. Each side gets one rate, after the 0.10 % fee, and every claim rounds down. 5. Claim: The sealed_claim proof shows your order sits in the tranche, publishes its nullifier and turns it into notes at the public rate, without revealing the amount. A relayer sends it and takes its fee from what you claim. Nobody's permission is needed. An order has no limit price of its own. It takes its side's rate, and the band is what bounds that rate. ## When a batch fails A side that stays under `minOrders` for `maxWait` is void, and its total never shows. So is a sealed batch that has not settled within `settleWindow` of its seal, because a keeper stalled, the price check failed or no route took the net with nothing to cross. A last post that cannot settle reverts and shows nothing, and the keepers try again until the window ends: only time voids. The `sealed_refund` proof returns your input as a new note. You do not need anyone's permission and no keeper can stop it: if the time is up and nobody has voided the batch, the refund does. A void batch pays no 0.10 %. A full tree cannot lock an order. Every open order holds back two leaves of the tree for its claim or refund, and nothing else can use them. When only those are left, notes leave through `exitFullTree`. ## What a batch hides, and from whom - An amount stays hidden from everyone except all 3 keepers together. A single share is a random field element. - A side's order count is public from its first order. Its total stays hidden until the transaction that trades it, and for a thin side it never shows. - The net routed and the rate per side are public. A batch hides an amount among the other orders of its tranche, and only among the honest ones. The total is public. **A party holding all but one of a tranche's orders learns the last order's amount, by taking its own from the total.** No minimum can stop that while totals are public. What the minimum does is raise the price. To read one order out of a default market, someone must place the other four themselves, pay a relayer for each, and hope nobody else joins. A tranche that never reaches five orders shows no total and refunds at `maxWait`, ten minutes by default. In a quiet market, look at how many orders a tranche holds before you join it, and split a large order across epochs. The sealed ticket warns you when a tranche is thin. Who placed an order stays hidden either way. ## Sending a claim or refund yourself A claim pays its relayer in what it claims, and a refund in what it refunds. A relayer takes only the fee tokens it lists, so a launch's first buyers, for one, have no relayer for their claim until one prices launch tokens. Whoever sends the transaction is then public, and if that wallet funded your deposit, you are. So nothing here falls back to your wallet by itself. The app tells you, and you choose a fresh address: withdraw a WETH note to it as native ETH for the gas, then send the claim from it. It never uses the connected wallet. In the SDK, `sealedRoute(order, 'claim' | 'refund')` says which it will be, `sealedClaim` and `sealedRefund` throw `SelfSubmitRequiredError` unless you pass `selfSubmit`, which names the address, and `sealedOrder` takes it too. ## Keepers Keepers run as separate services. Each reads the public order logs, decrypts its own shares and keeps a running sum per tranche. They cannot move funds and they cannot change a total. They can stall, and then the batch voids and refunds. All 3 together could read per-order amounts, but not who placed them. At launch one operator runs all 3, and a new market can name other operators. A market's keeper keys never change, and its encrypted shares stay in the logs for good, so whoever ever holds all of a market's secrets can read every past order's amount in it. Keys rotate by market: the owner adds a new market with new keeper addresses and keys, and stops the old one taking orders. Each keeper keeps the old secret until the old market's last tranche is done, then destroys it. A keeper's share secret stays on its own machine, and never in the wallet or server that holds other keys. ## Markets `SealedMarkets` is the owner's market list. The owner adds markets inside the bounds above and can stop one taking new orders. It holds no funds, and nothing the owner does later reaches an order already placed. `SealedBatches` has no owner. Each market sets its smallest order per side and its fixed route. A market on a stock token takes orders in the equity session only. ## In the app Swap shows `Immediate` and `Sealed batch` when the pair has a market. The sealed ticket shows the epoch countdown, each side's order count and state, the terms above, and a warning when a tranche is thin. Your orders are on `/private`, with a randomised suggested claim time, so claims do not all land right after a batch. A claim or refund opens a drawer with the same private foot as any spend. ## Launches A launch can open through a sealed batch, so its first buyers share one price. `SealedMarkets.createLaunchOpener` makes the opener, `LaunchFactory.createLaunch` names it with an `openDelay`, and the opener's `bind` adds a buy-only market whose one fill is `BondingCurve.allocate`. The create page sends all three from one button. Only the batch's own settlement can make that fill, and an order is the only way into it. The launch takes sealed buys on its page while it waits. The batch stays open for at least 10 minutes, and it seals the settle window plus 30 seconds before the launch would open, so the delay must leave all of that when `bind` runs: 12.5 minutes on the default windows. The fee comes off the quote token before the fill, the tokens come back as notes, and whatever the curve's opening cap leaves comes back in the quote token. If the batch fails, the launch opens by itself and the buyers take their refunds. See [Launchpad](https://zkdepth.com/docs/launch). Where it lives: - `contracts/src/shielded/SealedBatches.sol`: orders, reveal, settlement, claim and refund - `contracts/src/shielded/SealedMarkets.sol`: the market list, its bounds and the reference price - `contracts/script/shielded/ShieldedWiring.sol`: the default parameters - `apps/keeper/src/keeper.ts`: one keeper --- # Private perps Source: https://zkdepth.com/docs/private-perps · Private balance Position notes on the on-chain engine: the owner is hidden, size and price stay public. Private perps hold a position on the on-chain engine as a note. The owner is hidden. Size and price stay public, because the vault's solvency depends on them. In brief: - Same engine, same markets and same parameters as [On-chain perps](https://zkdepth.com/docs/perps). - No circuits of their own: `PerpAdapter` and the join-split circuit do it. - A position is one share in custody, with a random share id you pick. - Margin comes from a note, and proceeds go straight back to custody as a new note. - Each trade carries a limit, from the oracle's price by default. - A position pays the 0.10 % once, on the margin going in, plus the engine's own fees and the relayer's quote. See [Fees](https://zkdepth.com/docs/fees). ## How it works The engine does not care whether a position's owner is a person or a contract. It makes no callbacks, and every payout names a receiver, so an adapter can send proceeds straight to custody. `PerpAdapter` owns every position on the engine and exposes each one as a single share, held in custody the way a DLMM bin share is. So a position is a note, the join-split circuit proves who owns it, and it can move to another key with a private transfer. A proof binds its outputs before anything runs, and the engine numbers positions as they open. So a note cannot name the engine's id. Its share id is random and chosen by the note's owner, and `positionOf` maps it to the position. | Action | Spends | Comes back | | --- | --- | --- | | Open | A USDG note, all of it margin | The position note | | Add margin | Any USDG note | Nothing but change. It can only help whoever holds the position | | Increase | The position note | The position note | | Decrease | The position note | The position note and a USDG note | | Close | The position note | A USDG note | | `claimLiquidated` | The position note | A USDG note with what a liquidation left | | `emergencyClose` | The position note | A USDG note with the margin, once a market's feed has died | | Hand over | The position note | A position note for another key, by private transfer. Nothing on the engine changes | Only the share released in that very action can be acted on. More margin and more size together take two proofs. An open, an increase, a close or a change of size takes its limit from the oracle's price and `slippageBps`, 100 by default, unless you pass a `limitPrice`. `limitPrice: 0n` says no limit. With no oracle price to go by, the call throws. ## Fees A share of 1 is too small for a protocol fee to round above zero. So a position pays the 0.10 % once, on the margin going in, at the open and on any margin you add. Close, decrease, increase, the claims and a hand-over pay no 0.10 %. The engine's open and close fees apply as on any position. A position note pays a relayer only from private WETH, so keep a small WETH note. Otherwise the spend goes from a fresh address with no relayer fee. ## In the app The on-chain perps ticket on Trade has a public or private switch, and position notes sit in the Positions tab. Manage on a note opens a drawer with Close, Reduce, Add size, Margin and Hand over. A liquidated note offers its claim, and a dead market its emergency close. Lighter stays public. ## What the chain shows The market, the direction, the size, the entry, the margin and every later change, as with any position on the engine. `ShareOpened` ties the share id to the engine's position, so anyone can follow one position through its changes. What is missing is the wallet behind it. [What each lane shows](https://zkdepth.com/docs/lanes) has the table. ## Keeping it working Liquidation, funding and closed-market rules are the engine's, so a private position behaves like a public one. Nothing in the adapter can block a liquidation, and what it leaves waits for the share's holder. A private position that is close to liquidation needs a proof to add margin, and your browser has to build it, so keep some room. ## Leaving without an action A position note released to any address but the adapter settles on the spot, the way a bin share pays its tokens: the position closes at the oracle, or takes its margin out of a dead market, or claims what a liquidation left, and that address gets the payout. That is the exit for a plain withdrawal and for a tree too full to hold an action's leaves. ## Funding Lighter privately Lighter stays as the public perp and hedge venue. A private withdrawal can fund it from a fresh address, so your Lighter account does not lead back to your main wallet. See [Perps on Lighter](https://zkdepth.com/docs/lighter). Where it lives: - `contracts/src/shielded/adapters/PerpAdapter.sol`: shares, operations and settling on release - `contracts/src/perps/PerpEngine.sol`: the engine it runs on - `packages/zk/src/lanes.ts`: privatePerpOpen and the rest, from the browser --- # Keys and backup Source: https://zkdepth.com/docs/keys · Private balance The one signature your keys come from, what each key does, and how to back them up. Your private balance has its own keys. They come from one signature in your wallet, in your browser, and no server ever sees them. In brief: - Signing the key request costs nothing and sends no transaction. Sign it only on the ZKDepth app. - The same wallet gives the same keys on any device, as long as it signs the same request the same way. - Keys stay in memory in the tab. `Lock this tab` forgets them, and a reload asks again. - An encrypted backup is optional, and with its passphrase it opens the balance without the wallet. - Nobody can recover your notes for you. There is no admin path to a note. ## Opening your keys `/private` asks only when you click. - **`Set up keys`.** Your wallet signs one fixed typed-data request, shown on the page. The first time on a device it asks twice, to check the signature is stable. - **`Restore from backup`.** An encrypted file plus its passphrase. No wallet is needed. Your keys stay in memory in the tab. `Lock this tab` forgets them, as does a reload or a switch to another wallet, and the wallet asks again next time. The notes your browser has found are cached on the device, encrypted under a key from the same root, so the next scan only reads new logs. ## Where the keys come from The signature goes through a key derivation function into a 32-byte root, and every key comes from that root. The request has a domain and names no chain and no contract, so a wallet shows whose it is and the keys hold across networks. It never changes, because anything else would give new keys and an empty balance. A signature of this request is your keys, for good. There is no way to rotate them short of moving your notes to a new balance, so sign it only on the ZKDepth app. Most wallets sign the same request the same way every time. The client checks this and refuses a wallet that does not, because its keys could never be derived again. ## What each key does | Key | What it does | | --- | --- | | Spending key `sk` | Proves you own a note. Whoever holds it can spend your notes | | Nullifier key `nk` | Derives the nullifier that marks your note as spent | | Viewing key | Finds your notes and their amounts, and recognises the spent ones. It cannot spend | | Fresh address keys | One-time addresses: withdrawals that should not link back to your wallet, and gas for a send with no relayer | Your private address starts with `zkd1`. It is what others send to. Your viewing key starts with `zkdview1`, and the app shows it only on a click. Whoever holds it can read every note of this balance, so share it only with someone who should. ## Finding your notes Each note from a deposit, a transfer or a sealed batch carries a ciphertext encrypted to your viewing key. Your browser scans pool logs and tries to open each one, with a one-byte view tag to skip most of them. An action's output has no ciphertext: your keys recompute its blinding from the action's first nullifier. Your browser rebuilds the tree from the same logs, so a new device needs only your keys and the chain. ## Backup The backup is one encrypted file: the key root under a passphrase you choose (PBKDF2 with 600,000 rounds, then AES-GCM). Pick a strong passphrase: the file is only as hard to open as it is. The passphrase is never sent anywhere. Losing your wallet with no backup loses the balance. Nobody can restore it, and that is what no admin path means. ## Showing a note to someone The `disclose` circuit proves up to 8 chosen notes belong to one spending key, with their amounts and, for spent ones, the nullifier each published. It reveals nothing else about the key or its other notes. It runs off chain, only you can start it, and the app downloads the proof as a file. A prover can leave a spend out, so a disclosure shows ownership of these notes and never proves what someone holds now. Where it lives: - `packages/zk/src/keys.ts`: the key request and the derivation - `packages/zk/src/vault.ts`: the encrypted backup - `circuits/src/disclose.circom`: selective disclosure --- # Swaps and routing Source: https://zkdepth.com/docs/swaps · Trade How one swap is split into hops across ZKDepth pools, public v4 pools and Pons, and how a route is chosen. A swap trades any token for any other through the ZKDepth `Router`, which chains up to three hops across ZKDepth pools, public Uniswap v4 pools and Pons launches in one transaction. The Router charges no fee of its own, so you pay only the fees of the pools you cross. In brief: - A swap is one `Router.swapExactIn` call. It reverts unless the final output reaches your minimum. - The app quotes every candidate route live and ranks them by output. - Price impact is flagged from 1 %, must be accepted from 5 % and refuses the swap from 15 %. - An ERC-20 input needs one exact-amount approval to the Router. ETH goes in as the transaction value. ## A route is a list of hops | Kind | Venue | Direction flag | | --- | --- | --- | | `0` | any Uniswap v4 pool | `zeroForOne`: currency0 goes in | | `1` | a ZKDepth DLMM pair | `swapForY`: token X goes in | | `2` | a Pons bonding curve | `isBuy`: the pair token goes in | | `3` | WETH | wraps or unwraps, and does not count toward the three | Each hop must start in the token the previous one ended in, and ETH and WETH count as one asset. A v4 hop must consume its whole input, and a Pons hop works only before the token graduates. Your minimum, checked once at the end, is the only price bound in a swap. The Router swaps through any hop it is given. The app builds hops only from ZKDepth's own pools, hookless public v4 pools and pools the Pons factory created. ## How the app picks a route 1. Gather the pools: ZKDepth's pools, the Pons venue of either token, and public v4 pools that hold in-range liquidity. Hookless only, because a hook can run arbitrary code during a swap. 2. Build candidates: Every pool holding both tokens, and every two-hop route through USDG, ETH or a Pons pair token, up to 40. 3. Quote and rank: `quoteExactIn` runs each candidate through an `eth_call` every 15 s, and the routes rank by output. The best route through a ZKDepth DLMM pair moves to the top unless it pays 15 % or more below the leader. 4. Measure the impact: The leading route's rate against a quote for 1/1000 of the amount on the same hops, so the pool fees cancel out. ## Before you sign The minimum you sign is the leading quote less your slippage. The page shows it with the rate, the price impact and up to four other routes. Parameters: Thresholds the app applies - `Slippage` (allowed: 0 - 50 %; default: 0.5 %): Becomes minAmountOut. Above 5 % needs an acknowledgement. - `Impact warning` (allowed: fixed; default: 1 %): Impact shown as a warning. - `Impact confirm` (allowed: fixed; default: 5 %): You must accept the impact to swap. - `Impact block` (allowed: fixed; default: 15 %): The swap is refused. - `Deadline` (allowed: fixed; default: 20 min): Passed to swapExactIn. 10 min in the trade terminal. WETH to ETH on its own skips the Router: the page unwraps it with WETH's own `withdraw`, with no approval. The same swap from a private balance runs in the [immediate lane](https://zkdepth.com/docs/immediate-lane). ## Buy and Sell in the trade terminal The terminal normally trades the asset's Uniswap pool through the Universal Router: approve, Permit2, swap, with ZKDepth's 25 bps fee on the output. When a live Router quote pays more than that estimate, the trade goes through the ZKDepth Router instead, with one exact approval and no 25 bps fee. The terminal does not apply the Swap page's impact gates, so use the Swap page for a large trade. Where it lives: - `contracts/src/venue/periphery/Router.sol`: swapExactIn, quoteExactIn, routeTokens - `packages/agent/src/route/route.ts`: hop encoding, candidates, ranking, price impact - `apps/web/lib/amm/useSwapRoutes.ts`: the live quote loop behind every page --- # Limit orders Source: https://zkdepth.com/docs/range-orders · Trade A one-sided position that sells as the price crosses it and earns swap fees while it waits. A ZKDepth limit order sells one token for another once the price reaches a level you choose, and earns swap fees while it waits. It is a narrow Uniswap v3 position placed just beyond the current price, held by the `RangeOrders` contract. In brief: - A sell order sits above the price, a buy order below it. The app makes the band one tick spacing wide. - An order can be closed only after the price has crossed the whole band. Until then you may cancel at any time. - Whoever closes a crossed order keeps 1 % of the converted amount. The protocol takes 10 % of the swap fees the order earned, never of the principal. - Payouts are always ERC-20, so you receive WETH, not ETH. Orders do not expire and cannot be edited or transferred. ## Setting the price You choose a side, a limit price and an amount in the Limit mode of the ticket on the Trade page. A sell puts the asset into a band above the market, and a buy puts the other token into a band below it. The order converts as the price walks across the band, so your average price lies between its two edges: about 0.1 % apart on a 0.05 % pool, 0.6 % on a 0.30 % pool and 2 % on a 1 % pool. The button stays locked while the pool's own price and the API's differ by more than 100 bps. Only v3 pools are supported. DLMM pairs have a second kind of order that rests in a single bin, see [ZKDepth pools](https://zkdepth.com/docs/pools). `place` takes ETH when the sold token is WETH, or pulls the token after an exact-amount approval. The contract mints the position to itself, so a fill needs no approval from you. Your claim is the order's `owner` field, which cannot change. ## When an order is done A sell-token0 order is fully converted once the tick reaches `tickUpper`. A sell-token1 order is fully converted once the tick drops strictly below `tickLower`. `fillable(orderId)` answers this, and a partly crossed band is not fillable. Until `fill` runs, the order is ordinary liquidity. If the price crosses the band and returns through it, the pool converts the order back into the token you were selling, and it waits for the next cross. One the price never reaches stays open, earning fees, until you cancel it. ## Closing an order | | `fill` | `cancel` | | --- | --- | --- | | Who | anyone, once `fillable` is true | the owner, any time while open | | Caller's bounty | 1 % of the converted principal | none | | Protocol | 10 % of the fees earned | 10 % of the fees earned | | You receive | everything else | the position as it stands | The protocol's share goes to the `FeeRouter`, booked to you so that your referrer earns 20 % of it. A closed order cannot reopen. In practice ZKDepth's keeper bot closes orders. Every minute by default it reads `fillable` for every open order and sends `fill` only when the bounty is at least three times the estimated gas, so a small order may wait. You can close a crossed order yourself: `fill` brings the 1 % back to you. A filler may push the price through the band and then close the order. That trade pays the position fees on the way and can only happen at or beyond your price. The proceeds always go to the owner. Where it lives: - `contracts/src/RangeOrders.sol`: place, fillable, fill, cancel and the fee split - `apps/web/lib/trade/limit.ts`: limitBand and earnsPerDayUsd - `apps/api/src/bots/keeper.ts`: sweepRangeOrders: fillable in one batch and the bounty in wei --- # On-chain perps Source: https://zkdepth.com/docs/perps · Trade Oracle-priced perps against a USDG vault: 35 markets, their tiers, fees, funding and the LP exit delay. ZKDepth's own perps are oracle-priced and peer to pool. You hold an isolated position with USDG margin at Chainlink prices, against `PerpVault`, the USDG vault that takes the other side. In brief: - 35 launch markets: ETH and 34 stock tokens with live Chainlink feeds. - 0.10 % to open and 0.10 % to close, split 80 % to the vault's LPs and 20 % to the protocol. - Profit is capped at 50 % of entry notional, and that amount is locked in the vault from the moment you open. - While a price is not live, nothing that needs a price can happen. - LPs exit in two steps, 1 hour apart. ## Markets and tiers Tiers set leverage and caps by how hard an asset gaps over a closed market. Every open interest cap is the lower of a share of the vault and a dollar amount, so a market stays in proportion to what the vault holds. | Tier | Max leverage | Maintenance margin | Liquidation fee | Open interest per side | | --- | --- | --- | --- | --- | | ETH | 20x | 2.5 % | 1 % | 30 % of the vault, up to $5M | | Index: SPY, QQQ | 10x | 5 % | 1 % | 20 % of the vault, up to $2M | | Large: other stock tokens | 5x | 10 % | 1 % | 10 % of the vault, up to $1M | | Volatile | 3x | 15 % | 1.5 % | 5 % of the vault, up to $250K | The volatile tier is PLTR, COIN, MSTR, SNDK, RKLB, CRWV, NBIS, CRCL, GME, IONQ, CLSK, RGTI, SPCX and USAR. SGOV has no market: a T-bill ETF barely moves, so its perp would only collect carry from the LPs. The vault must hold USDG before any position can open, since open interest is capped by what it holds. ## What a trade costs | Item | Value | | --- | --- | | Open fee, close fee | 0.10 % of notional each | | Early close fee | Up to 0.20 % more right after an increase, decaying to zero over 30 minutes | | Price impact | Grows with the skew, up to 1 % | | Funding, skew part | Up to 100 % APR. The heavier side pays | | Funding, base part | 5 % APR, paid by both sides to the vault | | Liquidation fee | Half to the keeper (at least 0.50 USDG), half to the insurance fund | | Minimum margin | 10 USDG | The funding scale differs by tier: $1M of skew reaches the cap on ETH, $500K on the index tier, $250K on the large tier and $100K on the volatile tier. The impact scale is $100M, $50M, $20M and $5M. ## Profit cap and reserves A position can be paid at most 50 % of its entry notional. That amount is locked as a reserve in the vault while the position exists, so a winner's capped profit is always in the vault as cash. Past the cap, more price move earns nothing. One position can reserve at most 10 % of the vault, and all positions together at most 80 %. ## Oracle and market hours Feeds are Chainlink only. Stock markets follow the session their feeds run on, from 20:00 New York time on the evening before a trading day to 16:00 on the last trading day of a run, minus listed US market holidays. ETH never closes. The feeds update on a 0.5 % deviation, so three things price that latency in: - **Entry guard.** For 90 seconds after a fill, a new oracle round that goes against you re-bases your entry to that round. - **Early close fee.** It makes cycling a position around each round cost more than the round can pay. - **Caps.** Open interest and reserves bound what one mispriced fill can take. ## When a market is closed While the price is not live, there is no open, no increase, no close and no liquidation, in either direction. You can still add margin, and funding does not charge. The position trades again at the first new round after the session reopens. Reduce-only and delisting stop new size only. If a feed dies for good, `emergencyClose` returns the margin at no profit or loss after 14 days. No role exists that can touch a position. ## Providing liquidity `PerpVault` is an ERC-4626 vault. Its share price is its cash, plus or minus what traders stand to win or lose right now, with the premium a skew has paid counted as theirs, since walking the skew back pays it back. Shares trade around that mid at a spread equal to the oracle's uncertainty times the vault's net exposure: 0.5 % per unit while a market is open, and 3 % to 20 % by tier while it is closed. A vault with a flat book has no spread. Deposits are immediate. Exiting takes two steps: `requestExit`, then `claimExit` after 1 hour, which pays the worse of the price at the request and the price at the claim. A request cannot be cancelled and earns nothing while it waits, and its owner can point it at a new receiver with `setExitReceiver`. Shares are transferable, so a share can sit in a private note. ## What the owner can change The owner can change a live market's parameters and add markets, each within hard bounds in `PerpMarketRegistry` (for example leverage up to 50x, exit delay from 15 minutes to 7 days). Feeds, market hours, maintenance margin and the liquidation fee are fixed when a market is added. Nobody can touch the vault's money or a position. Where it lives: - `contracts/src/perps/PerpEngine.sol`: positions, fills and liquidations - `contracts/src/perps/PerpVault.sol`: the LP vault and the exit - `contracts/script/perps/PerpWiring.sol`: the launch values above --- # Perps on Lighter Source: https://zkdepth.com/docs/lighter · Trade Perps on Lighter through a per-user order key that can place orders and has no way to withdraw. Lighter is ZKDepth's public perp and hedge venue: the perps on the Trade page and the shorts behind the [hedged vaults](https://zkdepth.com/docs/hedged-vaults) trade there. To give you orders without a wallet popup for each one, a ZKDepth service called the signer holds an order key for your Lighter account. In brief: - The order key can place and cancel orders and set leverage. Binding a new key and withdrawing both need your wallet, and the signer has no code that submits a withdrawal. - The signed price must sit within 1,000 bps of Lighter's current mark, one order is capped at $250,000 of notional, and nothing is signed when there is no mark. - A market order goes to Lighter as an immediate-or-cancel limit order at the mark plus or minus 1 %. - Every write carries an EIP-712 signature from your wallet, or from a tab key under a trading session of at most 24 h. ## Who holds which key Your wallet authorises a Lighter API key once. The API checks each write's signature, forwards it and holds no key. The signer stores your order key encrypted and accepts calls only from the API. ## Connect your Lighter account 1. Open a Lighter account: Lighter creates your account with its first deposit, at least 1 USDG through Lighter's bridge. The USDG never passes through ZKDepth, and Lighter credits it a few minutes later. A smart-contract wallet is refused, because Lighter cannot verify its signature on the key change. 2. Request a key: The Bind order key button signs a request. The signer generates one keypair per address, stores it encrypted, and returns the text your wallet must sign. 3. Sign and bind: Your wallet signs that text, and the signer submits the key change to Lighter. ## How an order is priced The signer signs an order only if every check passes: 1. The address is onboarded and its key is bound. 2. Lighter has a current mark for the market. 3. A limit order uses your price and rests for up to 28 days. A market order uses the mark plus or minus the requested slippage, never under 100 bps. A request over 1,000 bps is refused, not clamped. 4. That price is within 1,000 bps of the mark, whichever order type produced it. 5. The size meets the market's minimum unless the order is reduce-only, and size times mark is at most $250,000. The app sends no slippage value, so every market order from the Perp form is priced at 1 %. The form's liquidation price is ZKDepth's cross-margin estimate, not Lighter's own figure. The band limits each order, not how many are sent. ## Exit orders A stop loss or take profit is Lighter's own conditional order, and it triggers with your browser closed. Every exit is reduce-only, triggered by Lighter's mark price, executes no more than 1 % beyond the trigger, and expires after 28 days. With a new entry, exits are sized to what the entry actually filled. A gap or a thin book can leave part of a position open after a trigger. ## Signed requests Every write to `/perp/*` carries a one-time nonce, a timestamp within 300 s and an EIP-712 signature over a hash of the request body, so a signature fits exactly one body. `/perp/withdraw-intent` returns an unsigned description for your wallet to sign and submit. ## Trading sessions Signing every order in your wallet is the default. Turn on **Trade without confirming each order** and pick 1 hour, 8 hours or 24 hours. The browser generates a key for this tab, and your wallet signs one grant for it, naming `order` and `cancel` and an end time. Until the session ends, anyone with access to this tab can place and cancel perp orders for your wallet inside the signer's band and cap. A session can never withdraw or bind a key. **End session** revokes the grant at the API. Closing the tab discards the key, and the grant lapses at its expiry. ## ZKDepth fee mode Your wallet chooses between two modes. Zero-fee mode charges nothing. ZKDepth fee mode adds 1 bp on taker fills through Lighter's partner program. It needs your account on Lighter's Plus tier and your approval of ZKDepth's fee account, and those fills earn [points](https://zkdepth.com/docs/lounge). The owner switches it on with `PERP_FEE_ENABLED`. ## Prices and funding Marks, volume, open interest and funding for 58 markets come from Lighter through the API, cached for 15 s. Where Lighter has no mark, other public price sources fill the gap. Lighter settles funding every hour, and at a positive rate longs pay shorts. The funding chart annualises the settled rate as simple interest (rate × 8,760). ZKDepth's own on-chain engine is in [Perps](https://zkdepth.com/docs/perps). Where it lives: - `services/signer/app/main.py`: onboard, bind, order and its checks - `services/signer/app/config.py`: the floor, ceiling, band and notional cap - `apps/api/src/routes/perp.ts`: the /perp/* routes and the signed envelope --- # Vaults Source: https://zkdepth.com/docs/vaults · Earn One-token Uniswap vaults and the DLMM and stock vaults: pricing, the price check, harvest and rebalance. A ZKDepth vault takes your tokens, runs a liquidity position for you and gives you shares of it. This chapter covers `LPVault`, one Uniswap v3 position that takes a single token, and `DlmmVault` and `StockVault`, which sit on ZKDepth's own pools. In brief: - `LPVault` is an ERC-4626 vault: you bring one token and get shares priced at the pool's spot price, with the other leg valued net of the pool fee. - Every deposit, withdrawal and rebalance first checks that spot sits within 300 bps of a mean over at least 10 min of pool history. - `harvest` sends 10 % of the collected fees to the protocol and 0.5 % to the caller, and compounds the rest. - DLMM and stock vaults take both tokens as an exact slice of what they hold, and their withdrawals work even while the protocol is paused. ## Choosing a vault | | `LPVault` | `DlmmVault` | `StockVault` | | --- | --- | --- | --- | | Position | one Uniswap v3 range | bins around the active one | a base and a limit range in a stock pool | | You bring | one token | both tokens, or one through the zap | both tokens | | Re-centred by | anyone | the factory's keeper or the owner | the factory's keeper or the owner | | Fee to ZKDepth | 10 % of harvested fees | none of its own | none of its own | | Created by | anyone | the protocol owner | the protocol owner | [Hedged vaults](https://zkdepth.com/docs/hedged-vaults) and [structured notes](https://zkdepth.com/docs/structured) are built on `LPVault`. ## Depositing and withdrawing `deposit(assets, receiver)` mints shares with three more decimals than the asset. The vault adds its idle balance to the position once that balance is worth at least 0.5 % of `totalAssets()`, and until then it still counts in the share price. `totalAssets()` values the idle tokens and what the position would return if closed now, and leaves out uncollected trading fees, so a harvest can only add value. `withdraw` and `redeem` burn your shares and pull liquidity out. If the vault still holds too little `asset`, it sells the other leg with a floor of the spot quote less `maxSlippageBps`. If that is not enough, the call reverts `Shortfall` rather than pay less than the preview. A withdrawal takes no fee. ## The price check Spot is the price an attacker can move inside a block. So `LPVault` runs `TwapGuard.check(pool)` before every deposit, withdrawal and rebalance: - The pool's observation ring must reach back at least 10 min, or the call reverts `TwapWindowTooShort`. - Spot must sit within 300 bps of the mean over 30 min, or over the history there is, or the call reverts `PriceDeviation`. After a jump of more than 3 %, these calls revert until the mean catches up. `VaultFactory` grows each pool's ring to 60 slots at creation. The check is skipped while a vault has no shares, so the first depositor can get in. ## Harvest and rebalance Anyone may make either call. `harvest()` collects every fee the position has earned and runs no price check, because it moves no principal. `rebalance()` mints a new position centred on the current tick, once the tick is past a range edge by the hysteresis. ZKDepth's keeper bot sends `harvest` for each deployed vault every 6 h. Parameters: Seed vault policy - `widthTicks` (allowed: multiple of the spacing; default: 2 x 2231): Full range width, about ±25 %. - `hysteresisTicks` (allowed: 0 or more; default: 10 spacings): How far past an edge the tick must sit before a rebalance. - `minInterval` (allowed: seconds; default: 6 h): Least time between two harvests, and between two rebalances. - `maxSlippageBps` (allowed: 0 - 9999; default: 100): Floor on every swap, against a spot quote. `VaultFactory.createV3Vault` is open to anyone, so the app shows only the vaults in the deployment manifest and those [graduation](https://zkdepth.com/docs/graduation) created. The deploy creates `ZKDepth ETH Vault` and `ZKDepth USDG Vault` on the WETH/USDG 0.05 % pool. ## Vaults on ZKDepth pools `DlmmVault` and `StockVault` never swap and charge no fee of their own. Deposits and rebalances stop while the protocol is paused, and withdrawals never do. Only the factory's keeper or the protocol owner can rebalance either, at most every 5 min. A `DlmmVault` is due a rebalance once the active bin is more than half the half-width from the centre, and ZKDepth's bot waits until that has held for 120 s. `DlmmVaultZap` turns one token, ETH included, into a deposit, and a withdrawal back into one token. A `StockVault` centres a base range on the oracle price, wider while the market is closed. What the base cannot take waits in a one-sided limit range next to it. Where it lives: - `contracts/src/LPVault.sol`: totalAssets, deposit, withdraw, harvest, rebalance - `contracts/src/libraries/TwapGuard.sol`: the price check and its constants - `contracts/src/venue/vaults/DlmmVault.sol`: the DLMM vault: deposit, withdraw, rebalance, shapes --- # Hedged vaults Source: https://zkdepth.com/docs/hedged-vaults · Earn An LP vault with a Lighter short: the reporting and funding keys, and when it pauses. A liquidity position is long the token it holds. `HedgedLPVault` is an [`LPVault`](https://zkdepth.com/docs/vaults) paired with a short perp on Lighter, sized to the position's delta, so you keep the fee income and shed most of the price exposure. In brief: - Two seed vaults, `ZKDepth NVDA Hedged` and `ZKDepth SPY Hedged`, each on the deepest WETH pool for its token. - The hedger key reports the hedge's value. The funder key moves margin to Lighter, at most 10 % of the vault's on-chain assets per call and 25 % per day. - The margin sits in a Lighter account owned by the vault's funder key, and that key can withdraw it. - A report that moves the hedge's equity by more than 15 %, or no report for 1 h, pauses the vault. While paused, deposits and withdrawals both revert. - An exit larger than the vault's on-chain holdings reverts until the funder brings margin back. ## How the hedge is counted The chain cannot read Lighter, so the vault takes the hedge's value from reports: ```text totalAssets() = LP position + idle balances + equityUsd converted to WETH through Chainlink ETH/USD ``` If the feed reverts, answers zero or less, or has not updated for 25 h, the hedge counts as zero rather than reverting, so exits do not depend on the oracle. ## Who does what | Key | Can | Cannot | | --- | --- | --- | | Hedger (hot, on the API server) | call `reportHedge` | move any funds | | Funder (cold, one per vault) | call `fundHedge` and `returnHedge`; withdraw the margin on Lighter | make the vault pay any address but the fixed Lighter deposit contract | | Signer's order key | open, close and resize the perp | withdraw from the account | ## Where the margin lives Trading a Lighter account needs an API key bound by a signature from the account's owner, and Lighter has no accounts owned by a contract. So `fundHedge` credits margin to the Lighter account of the vault's funder, an ordinary wallet key the operator holds offline. The [signer](https://zkdepth.com/docs/lighter) holds that account's order key, the hedger trades and reports it, and `fundingConfig().funderKey` names it on chain. No two vaults share an account. The caps bound what can leave for Lighter, measured against the position and idle balance, never against reported equity. They do not bound what happens to margin once it is there. The app takes no deposits into a hedged vault that has never been reported on. ## Moving margin `fundHedge(assetAmount)` is refused while the vault is paused. It sells WETH for USDG behind a TWAP check, with a floor of the TWAP quote less `maxSlippageBps`, and deposits at least 1 USDG through Lighter's bridge. `returnHedge(usdgAmount)` works even while the vault is paused. The funder withdraws USDG on Lighter with its own key and calls it, and the vault sells the USDG for WETH behind the same floor. USDG sent straight to a vault is not counted and cannot be moved again. Margin takes minutes to cross, so it is counted wherever it is. Both calls move the stored equity in the same transaction, and each report counts the Lighter account's equity, deposits not yet credited, withdrawals not yet paid out, and the USDG and WETH in the funder's wallet. A piece that moves during a read is counted twice for one report, so the share price can read briefly high and never low. ## The hedger bot The hedger runs every 30 s. It sizes the short to the position's balance of the risk asset, and acts when a report is due (every 15 min) or when drift passes 200 bps of the target. It sends at most one market order per tick, between $50 and $25,000. Buys shrink the short and are always reduce-only. Once the hedges have lost $2,500 in a UTC day it places no more orders that day, and reports continue. ## When the vault pauses A report from the hedger with a fresh `asOf` is always stored, even one that trips the band. The band compares it with the stored equity: the last report plus USDG funded less USDG returned since. A move of more than 15 % pauses the vault, and the next in-band report clears it. Staleness needs no transaction: the vault is paused the moment its last report is older than 1 h. The band limits each report, not a series of them, so reports inside it can move equity step by step. A paused SPY Hedged also stops the [Protected SPY note](https://zkdepth.com/docs/structured)'s entries and exits, because 15 % of the note sits in it. Where it lives: - `contracts/src/HedgedLPVault.sol`: fundHedge, returnHedge, reportHedge, paused - `contracts/src/libraries/HedgeFunding.sol`: the TWAP-floored swaps and the Lighter deposit - `apps/api/src/bots/hedger.ts`: one tick: read, size, trade, report --- # Structured notes Source: https://zkdepth.com/docs/structured · Earn Income and Protected: two dated payoffs built from an LP sleeve, a perp sleeve or T-bills. A structured note is a vault with an end date. `StructuredVault` splits each deposit between an [`LPVault`](https://zkdepth.com/docs/vaults) and a second sleeve, and after maturity it unwinds everything back to its asset. In brief: - Two seed notes in WETH with a 90-day term. `ZKDepth Protected SPY` is open. `ZKDepth Income NVDA` has a cap of zero, so it takes no deposits and the app does not list it. - Leaving before maturity costs 50 bps, and the previews already show the amount after the fee. After maturity there is no fee. - The Protected floor at the start of the term is about 0.859 per unit deposited, measured in WETH. ## Two modes, one split `sleeveBps` is the share of every deposit that goes to the mode's own sleeve. The rest goes to the other one. | Note | Mode | LP sleeve | Second sleeve | | --- | --- | --- | --- | | `ZKDepth Income NVDA` | `Income` | 80 % into NVDA Hedged | 20 % idle WETH, backing a long the hedger reports | | `ZKDepth Protected SPY` | `Protected` | 15 % into SPY Hedged | 85 % SGOV, a T-bill token, from the deepest WETH/SGOV pool | Both sit on [hedged vaults](https://zkdepth.com/docs/hedged-vaults), so the LP vault's price check and pause apply to the note's money too: while SPY Hedged is paused, the Protected note's deposits and withdrawals revert. `totalAssets()` is the idle asset, plus the note's LP shares, plus the second sleeve: SGOV at the pool's TWAP net of its fee, or the reported sleeve equity. ## The payoff at maturity The chart draws both payoffs per unit deposited, against `s`, the underlying's price at maturity as a multiple of its price at deposit. (An interactive payoff chart is on the web page.) ```text income = sleeve × (√s + feeYield) + (1 − sleeve) × min(s, 1.5) protected = sleeve × (1 + sgovYield) + (1 − sleeve) × √s ``` Both value the LP sleeve as `√s`, what a full-range position is worth after a price move of `s`. In `Income`, the long stops gaining above 1.5x. In `Protected`, the first term does not depend on `s`, which is where the floor comes from: `0.85 × (1 + 0.045 × 90 / 365)`, about 0.859. The floor is arithmetic on an SGOV sleeve sold through one Uniswap pool. It is measured in WETH, not dollars, and it leaves out the LP sleeve, which falls with `√s`. Note: The chart is a model The chart adds a 6 % fee yield to Income and gives Protected a full year of SGOV yield at 4.5 %. A 90-day note earns about 1.1 %, hence the lower floor above. ## Leaving the note You can withdraw at any time. Before maturity, 50 bps goes to the `FeeRouter`, because an early exit unwinds part of a sleeve at that minute's prices. The ERC-4626 views include the fee, so `previewRedeem` shows what you will be paid. A partial exit pays from idle asset first, then redeems LP shares and, on a `Protected` note, sells SGOV. If your shares are the whole supply, the note unwinds everything and pays you the rest. `settle()` unwinds everything once the date has passed, and anyone can call it, once. It can revert while the LP vault is paused or the SGOV pool is off its mean, and can be retried. After settlement the note is a pile of WETH, and no exit depends on the SGOV pool or the LP vault again. ## The SGOV pool A `Protected` note buys, sells and values SGOV through one Uniswap pool, so every deposit, every withdrawal while the note holds SGOV, and every settlement first runs [the price check](https://zkdepth.com/docs/vaults#the-price-check) on it. Each swap has a floor 1 % under the TWAP quote, and a sale below it reverts. While the pool is off its mean the check stops the sale and the exit with it. ## Reporting an Income sleeve The hedger reports an `Income` sleeve with `reportSleeve`, under the [hedged vault's gate](https://zkdepth.com/docs/hedged-vaults#when-the-vault-pauses): a 15 % jump or 1 h of silence pauses the note, and a paused note takes no deposits and pays no exits. On a `Protected` note the call reverts, so the hedger key holds no pause over it. Where it lives: - `contracts/src/StructuredVault.sol`: deposit, withdraw, settle, reportSleeve, payoffCurve - `packages/core/src/payoff.ts`: incomePayoff and protectedPayoff - `contracts/script/ZKDepthWiring.sol`: the splits, the term and the two seed notes --- # ZKDepth pools Source: https://zkdepth.com/docs/pools · Liquidity DAMM, DLMM and stock pools: how each prices a swap, what it charges, and how bin orders rest. ZKDepth pools are the venue ZKDepth runs itself on Robinhood Chain. DAMM and stock pools are Uniswap v4 pools behind a ZKDepth hook, and DLMM pairs keep liquidity in fixed-price bins. In brief: - Every swap fee is split: 20 % goes to the fee collector and the rest to liquidity providers. The owner can raise the share to 50 %. - A DAMM fee is a 5-100 bps base plus a surcharge for recent price movement, capped at 500 bps outside an anti-snipe window. - A stock pool must end each swap inside a Chainlink band, unless the swap moved the price toward the oracle. - Only you can take your liquidity out. The venue pause stops swaps and deposits, never a withdrawal. ## Choosing a pool type | Pool | Price moves | Fee per swap | Good for | | --- | --- | --- | --- | | DAMM | along v4 ticks | base fee, optional anti-snipe schedule, volatility surcharge | new tokens, volatile pairs | | DLMM | bin to bin, fixed inside a bin | bin-step preset plus a volatility term | shaped liquidity, limit orders | | Stock | v4 ticks inside an oracle band | set by the market session | tokenized stocks | Anyone can open a pool or provide to one, in one permissionless transaction from the Pools page. The protocol's part of the fee goes to [buyback and burn](https://zkdepth.com/docs/protocol). ## DAMM pools The fee has three parts: 1. **Schedule.** `baseFeeBps` (5 to 100), or during an anti-snipe window a fee that falls in a straight line from `snipeStartFeeBps` (up to 5,000) to the base over `snipeSeconds` (up to 3,600). 2. **Surcharge.** It grows with the square of the ticks recent swaps moved the price, and decays to zero over 600 s. At a `variableFeeControl` of 10,000, 100 ticks of movement adds 10 bps. 3. **Cap.** 500 bps, or the schedule itself while it is higher. ## DLMM pairs Bin `id` trades at `(1 + binStep / 10,000)^(id − 2^23)` of token Y per token X. A swap fills at exactly one bin's price until the bin is empty, then moves to the next bin with liquidity. There is one pair per token pair and bin step, ERC-20 only. Bin steps of 1, 5, 10, 25, 50 and 100 bps have base fees of 2, 5, 10, 20, 40 and 80 bps. A variable fee grows with how far, in bins, the price has recently travelled, and the pair caps the total at 10 %. `DlmmPositionNFT.mint` gives you one ERC-721 over a range of bins, with a Spot, Curve or Bid-Ask shape. A deposit reverts if the active bin has moved more than `ceil(50 / binStep)` bins, if less than 99.5 % of what you typed lands, or after the deadline. A deposit into the active bin with a different X/Y mix pays the swap fee on the part that amounts to a swap. `decrease` and `burn` take minimum amounts and do not check the pause. ## Stock pools A stock pool pairs an `AssetRegistry` asset with USDG (priced at $1), ETH or WETH (priced by Chainlink ETH/USD), and starts at the oracle price. These are the deploy values, tunable per asset by the owner up to a 1,000 bps fee. | Session | When | Fee | Band | | --- | --- | --- | --- | | Open | Monday to Friday, 13:30-20:00 UTC, except holidays | 30 bps | ± 200 bps | | Closed | any other time | 150 bps | ± 50 bps | | Stale | a feed is over 25 h old | 500 bps | ± 50 bps | After a swap the price must sit inside the band or be strictly closer to the oracle than before, so anyone can pull a drifted pool back. While the feed is stale only such swaps pass. Outside the session the oracle stops moving, so a pool can sit far from where the stock reopens. ## Limit orders in a bin `DlmmLimitOrders.place` rests your tokens in one bin past the active one: X above it to sell, Y below it to buy. Orders in the same bin and side share a batch, fill at that bin's exact price and earn swap fees. The contract charges no fee. Once the price has crossed the whole bin, anyone may `execute` the batch, and `claim` executes a filled batch itself. If the price crosses back first, the order trades back into the token you started with. Before execution, `cancel` returns whatever the bin holds for you. The owner's powers over these pools are in [Who can do what](https://zkdepth.com/docs/roles). Where it lives: - `contracts/src/venue/hooks/DammHook.sol`: createPool, the schedule, the surcharge and the cap - `contracts/src/venue/hooks/StockHook.sol`: the band and the toward-oracle rule - `contracts/src/venue/dlmm/DlmmPair.sol`: bins, swap, mint, burn and the fee --- # One-token deposits Source: https://zkdepth.com/docs/zap · Liquidity Enter a range with a single token: the swap it makes for you, the checks on it, and the refund. A concentrated position needs both tokens of a pair, in a ratio set by where the price sits inside your range. A one-token deposit does the balancing for you: you send one token or ETH, and ZKDepth's zap sells the share your range does not want, mints the position to your wallet and sends back the rest, all in one transaction. In brief: - The zap charges 25 bps of what you send. The pool's own fee applies to the part that is swapped. - One slippage figure (default 100 bps) limits the swap's output and the mint amounts. A separate floor, `minLiquidity`, is the least liquidity you accept. - Everything is priced at the pool's spot price, not a TWAP. - The contract keeps no balance between calls and has no owner. ## One transaction, start to finish You choose the token, the amount and a range in the Liquidity mode of the ticket on the Trade page. It works with Uniswap v3 pools (`ZKDepthZapV3`) and v4 pools (`ZKDepthZapV4`). 1. Take the input and the fee: An ERC-20 is pulled after an exact-amount approval. 25 bps goes to the `FeeRouter`, booked against your address so that a referrer you have bound earns 20 % of it. 2. Size and swap: The contract reads the price from `slot0` and sells the excess token on the pool you are entering, with a minimum output `maxSlippageBps` below the spot quote. 3. Mint to you: The position manager mints the balanced amounts to `recipient`. Less liquidity than `minLiquidity` reverts. 4. Return the rest: The contract sends its balance of both tokens to the caller. A real swap moves the price a little, so a small remainder is normal. `ZKDepthZapV3.zapIncrease` adds to a v3 position you already hold. `previewZap` returns the exact swap amount `zapMint` would use. ## Picking a range | Shape | Band around the current price | | --- | --- | | `SPOT` | 5 % below to 5 % above | | `CURVE` | 25 % below to 25 % above | | `WIDE` | 50 % below to 50 % above | | Custom | the prices you type or drag | For a tokenized stock, presets double both edges while the NYSE is closed, because the token keeps trading on chain while its reference price stands still. Custom ranges are never widened. ## The three checks on your deposit - **Swap output.** The swap must fill at or above the spot quote less `maxSlippageBps`. - **Mint amounts.** The mint must stay within `maxSlippageBps` of the balanced amounts. This tightens as the range narrows, so a large deposit into a narrow range can revert at the default. Widen the range, deposit less or raise the slippage. - **Liquidity floor.** The app sets `minLiquidity` to the previewed liquidity less your slippage. The first two are computed from `slot0` when your transaction runs, so only `minLiquidity` is fixed when you sign, and the zap has no TWAP guard. The app will not build the transaction until the pool's price agrees with the quoted price within your slippage. If you call the contract directly, derive `minLiquidity` from a price you trust. The app's presets are 50, 100 and 200 bps. Above 500 bps you must acknowledge it, and both zaps refuse more than 5,000 bps. The app sets the deadline 600 s ahead. ## Cost You pay 25 bps of the input and nothing on the way out. The `FeeRouter` passes 20 % of it to your referrer if you have bound a code, and the rest goes toward the [$ZKD buyback](https://zkdepth.com/docs/protocol). The pool charges its usual fee on the swapped part only: on a 0.30 % pool, a deposit that swaps half its input pays about 0.15 % of the input to the pool. ## On Uniswap v4 `ZKDepthZapV4.zapMint` gives the same result through v4's plumbing. The pool is named by its full `PoolKey`, and `hookData` is passed to the hooks. ETH is accepted only when one side of the pool is native ETH. `approveOnce(token)` sets a standing approval to Permit2, which anyone may call. There is no `zapIncrease` on v4. Where it lives: - `contracts/src/ZKDepthZapV3.sol`: zapMint, zapIncrease, previewZap - `contracts/src/ZKDepthZapV4.sol`: zapMint, approveOnce and the unlock callback - `packages/core/src/shapes.ts`: the presets and the after-hours widening --- # Position automation Source: https://zkdepth.com/docs/keeper · Liquidity Let anyone harvest or re-centre your position under limits only you set, for a bounty you choose. A concentrated position earns only while the price is inside its range, and its fees sit in the position until someone collects them. `PositionKeeper` lets anyone do both chores on a Uniswap v3 position you own, under rules you set, for a reward you choose, and the NFT never leaves your wallet. In brief: - The keeper acts through an operator approval you can revoke at any time. The contract has no owner. - Every cut comes from earned fees: 10 % to the protocol and your chosen bounty (at most 300 bps) to the caller. Principal is never cut. - A rebalance runs only when spot is within 3 % of the 30-minute TWAP, and only if the round trip keeps your value within your slippage at that TWAP. - If the NFT changes hands, the enrolment stops working, and the new owner enrols again. ## Enrolling a position On Portfolio, the Automate drawer does both steps. 1. **Approve.** `setApprovalForAll(positionKeeper, true)` on the position manager. This covers every v3 position you own, and the keeper only touches enrolled ids. 2. **Enrol.** `enroll(tokenId, policy)` from the NFT's owner. The first action can come no sooner than `minInterval` later. `updatePolicy` swaps the rules without resetting the clock, and `unenroll` stops all actions at once. Revoking the approval also stops the keeper. ## Your rules Parameters: Policy, with the Automate drawer's defaults - `widthTicks` (allowed: a multiple of the spacing ticks; default: 1200): Total width of the range a rebalance mints. - `hysteresisTicks` (allowed: 0 or more ticks; default: 120): How far beyond the range edge the tick must be before a rebalance. - `minInterval` (allowed: 0 or more seconds; default: 3600): Time since enrolment or the last action before the next one. - `maxSlippageBps` (allowed: 0 - 9,999 (app: 1 - 500) bps; default: 100): Bounds the swap, the mint ratio and the rebalance round trip. - `compound` (allowed: true or false; default: true): Harvest puts your share back into the position, or pays it to you. - `bountyBps` (allowed: 0 - 300 bps of fees; default: 50): Paid to whoever calls harvest or rebalance. Only `minInterval` stops a caller from harvesting crumbs for bounties, so choose an interval that makes each harvest worth having. ## Collecting fees `harvest` collects every fee the position has earned and none of its liquidity. Each token is split on its own: 10 % to the `FeeRouter`, booked to you so that your referrer earns 20 % of it, `bountyBps` to the caller, and the rest is yours. With `compound` on, your share goes back into the position. A harvest has no TWAP check, because only one interval's fees are at stake. ## Re-centring a position `rebalance` needs the tick outside the range by the hysteresis. 1. Unwind: All liquidity is removed and the old NFT burned. The fee portion is split as in a harvest, and the principal is not. 2. Swap and mint to you: A new range centred on the current tick. The excess side is sold on the same pool, and the position is minted with you as owner. 3. Value after: What was minted and sent back, at the same TWAP, must be at least the value before less `maxSlippageBps`, or the whole transaction reverts. 4. Move the enrolment: Your policy is copied to the new token id with a fresh clock. ## Price protection on a rebalance - **Before anything moves**, the TWAP guard refuses while spot is more than 3 % from the 30-minute mean, or with under 10 minutes of oracle history. - **During the swap**, the router refuses to fill below the spot quote less `maxSlippageBps`. - **After the mint**, the value check compares the round trip at the TWAP. Inside the 3 % band a wrong spot can still cost up to half the gap. After a genuine move, a position can take up to half an hour to qualify. A narrow range may not mint inside a tight tolerance, and then every rebalance reverts: widen `widthTicks` or raise `maxSlippageBps`. ## Who calls the keeper Anyone can, and `shouldHarvest` and `shouldRebalance` answer without gas. In practice ZKDepth's keeper bot does. Every minute by default it reads both views for every active enrolment, and sends only when the bounty is at least three times the estimated gas. Both actions compare the live `ownerOf` with the enrolment and revert on a mismatch. Where it lives: - `contracts/src/PositionKeeper.sol`: enroll, harvest, rebalance and the two should views - `contracts/src/libraries/TwapGuard.sol`: the window, the minimum history and the band - `apps/api/src/bots/keeper.ts`: sweepEnrollments: the views in one batch and the bounty in wei --- # Launchpad Source: https://zkdepth.com/docs/launch · Launch A segmented bonding curve that graduates into a ZKDepth pool, with its real defaults. The launchpad deploys a token, its bonding curve and its future pool in one transaction, for anyone. When the curve sells out, anyone can graduate it, and the pool opens in that same transaction. In brief: - 1B tokens: 70 % on the curve, 25 % for the pool, 5 % for the creator. - The USDG template closes at 11,500 USDG in the reserve. - Trade fee 1 %: creator 30 %, pool 20 %, protocol 50 %. - Buys pay up to 20 % extra in the first 5 minutes, and one wallet can buy at most 2 % of the curve in that time. - No creation fee by default. - A launch trades from a private balance too, and a launch can open through a sealed batch. ## In the app - **Launch** (`/launch`) lists every launch, the ones closest to graduating first. With no launches yet, it shows what a launch does on the default template, read from the registry. - **Create a launch** (`/launch/new`) takes a template from the registry, a venue, an opener or none, who gets the creator's share and a logo if you want one, then sends `LaunchFactory.createLaunch`. A logo is its own step on the button: one signature uploads the file, and the launch's `metadataUri` points at it. Without one, the launch shows its first letter. Once the transaction lands, the page moves to the new launch. - **One launch** (`/launch/[id]`, by curve or token address) shows the curve and its price, trades and holders, where it stands, the protection window while it runs, and the ticket. Once graduated, the ticket gives way to the pool it moved into, and the creator sees the pool's fees there, to collect and claim. The opener is always named. Two older roads sit beside it. [Token vaults](https://zkdepth.com/docs/graduation), at `/launch/vaults`, move any aged token with a deep pool into a managed vault. [Pons tokens](https://zkdepth.com/docs/launch-pools), at `/launch/pons`, are tokens from the Pons launchpad. ## From curve to pool Diagram: From curve to pool. The venue exists from the first second, locked at the price the curve closes on. The curve is made of straight price segments. Its reserve is a pure function of the tokens sold and is tracked, never read from a balance, so a donation cannot move the curve or the threshold. It closes at exactly its last token: a buy past that is cut to fit and pays only for what it gets, and the closing buy also graduates. Sells stay open until graduation, so a launch can always be sold back. The pool is created at launch, locked at the price the curve closes on. Until graduation it takes no mint, no swap and no price move, whatever is sent to it. So the seed price comes from the curve and cannot be sandwiched. ## The defaults A launch copies its template when it is created, so a later template change never reaches a live curve. | | USDG template | WETH template | | --- | --- | --- | | Supply | 1,000,000,000 | 1,000,000,000 | | On the curve | 70 % | 70 % | | For the pool | 25 % | 25 % | | For the creator | 5 % | 5 % | | Start price | $0.000002 | 0.0000000007 WETH | | Closing price | $0.00005 | 0.0000000175 WETH | | Reserve at close | 11,500 USDG | 4.0075 WETH | | Fully diluted at close | $50,000 | 17.5 WETH | The WETH template is sized for ETH near $2,900. | Setting | Default | | --- | --- | | Trade fee | 1 %, on the quote side | | Fee split | Creator 30 %, pool seed 20 %, protocol 50 % | | Protection fee | 20 % extra on buys at the first second, falling to zero over 5 minutes | | Wallet limit | 2 % of the curve per recipient during protection | | Creator vesting | 30 day cliff, 180 days in total, from graduation | | Pool position fees | Creator 50 %, protocol 50 % | | Opening allocation | At most 20 % of the curve | | Creation fee | 0, fixed at deploy | ## The protection window From the first second, buys pay an extra fee that falls linearly to zero, and each recipient has a limit. It runs on timestamps. No identity is checked: bots can split wallets, and they pay the fee on every one. The extra fee goes to the pool's seed, never to the creator, so a creator sniping their own launch pays it like anyone else, through an opener or not. Sells pay the base fee only. ## Graduation The creator picks the venue per launch: a DLMM pair (bin step 100) or a DAMM pool (tick spacing 60, base fee 1 %, an anti-snipe fee from 5 % over 5 minutes). Anyone can call `graduate`. It takes the reserve and the pool's 25 % into the venue at the close price, puts the position in `LPAllocationEscrow`, starts the creator's vesting and opens trading. The escrow has no removal path; it only collects fees. Anyone can collect them, which credits the creator and the protocol at the launch's split, and `claim` pays each side. ## Private buys and sells The ticket on a launch's page has a private side. `LaunchAdapter` buys and sells on the curve from a note, in one transaction, and pays the curve's fees plus the 0.10 % on what leaves the pool. Who traded stays hidden. The launch, the size and the price are public. The wallet limit counts the recipient. Inside the protection window each private buy goes through a fresh recipient, so the limit applies per buy and the protection fee is paid every time. Past the window the tokens go straight to custody. See [Immediate lane](https://zkdepth.com/docs/immediate-lane#launches). ## Openers A launch can name an opener, which gets one fill before the public opens. Every launch page names it. The fill starts the protection window for everyone else. If the opener never makes it, the launch opens by itself when its delay ends. - An opener from `SealedMarkets.createLaunchOpener` opens through a sealed batch, so the first buyers share one price and the tokens come back as notes. Its fill pays the base fee, skips the wallet limit and the protection fee, and can reach `maxAllocationBps` of the curve, 20 % by default. Anyone can join the batch until it seals, it stays open for at least ten minutes, and the launch takes sealed buys on its page while it waits. On the create page, pick "Sealed batch": one button sends the three transactions it takes. See [Sealed batches](https://zkdepth.com/docs/sealed-batches#launches). - Any other opener, such as the creator's own wallet, pays the whole protection fee and stops at the wallet limit. It buys a moment's head start and nothing more. A sealed claim pays its relayer in what it claims, and a relayer takes no launch token until it prices one. So a launch's claims go from a fresh address you choose. See [Sending a claim or refund yourself](https://zkdepth.com/docs/sealed-batches#sending-a-claim-or-refund-yourself). ## Limits on templates The registry's owner can write templates inside hard bounds and do nothing else. For example: trade fee 0.10 % to 3 %, pool allocation 5 % to 50 %, creator allocation at most 20 %, protection fee at most 30 % over at most 1 hour, graduation threshold 1,000 to 1,000,000 USDG or 0.25 to 500 WETH. ## For developers A developer account belongs to a wallet, which signs for its API keys. A key can list launches in a branded catalog on its own domain. `GET /launches` lists launches. A launch points at a hosted logo with `logo:` in its `metadataUri`: `POST /logos` takes the file with the wallet's signature over its hash, and `POST /dev/logos` takes it with a key. Where it lives: - `contracts/src/launchpad/LaunchDefaults.sol`: the templates above - `contracts/src/launchpad/BondingCurve.sol`: the curve, fees and protection - `contracts/src/launchpad/GraduationManager.sol`: graduation --- # Launch pools and pooled buying Source: https://zkdepth.com/docs/launch-pools · Launch A token and its locked pool in one transaction, pooled buying before launch, and Pons tokens. `LaunchPools` creates a new ERC-20 together with its own ZKDepth pool in one transaction, so no one can hold or trade the token before its market exists. These are contracts anyone can call; the app's own flow is the [Launchpad](https://zkdepth.com/docs/launch). A launch vault lets a group buy at launch for one price, and any Pons token can get a ZKDepth pool next to it. In brief: - One call deploys a fixed-supply token (no owner, no mint, no transfer tax), creates its ETH/token DAMM pool and seeds it, one-sided. - The seeded liquidity is locked for good. Its LP fees go to a fee recipient, the creator at first. - Every launch has an anti-snipe window: the fee starts at up to 5,000 bps and falls linearly to the base fee over up to 3,600 s. - A launch vault has no owner and charges no fee. If nobody launches within 3 days after deposits close, every depositor can take the whole deposit back. ## What one launch does 1. Deploy the token: A plain 18-decimal ERC-20 whose whole supply is minted once. 2. Create and seed the pool: A DAMM pool of native ETH against the token, tick spacing 60, opened at your start price. The seed holds only the token and sells it to buyers at every higher price. 3. Buy, if you sent ETH: The ETH buys the token as the pool's first swap, so nobody can trade ahead of it. 4. Hand out the rest: Tokens bought and any ETH paid back go to you. The supply the pool did not take goes to the creator you named. The token's address is unknown until the launch lands, so nobody can create the pool first. A launch cannot go through while the venue is paused. Parameters: What a launch sets - `Supply` (allowed: up to 10^15; default: 1,000,000,000): The whole supply, minted once. - `Seed` (allowed: up to the supply; default: 100 %): How much the pool can sell. - `Start` (allowed: multiple of 60 ticks; default: 10 ETH market cap): The price of the first token sold. - `Base fee` (allowed: 5 - 100 bps; default: 100): The fee after the anti-snipe window. - `Anti-snipe` (allowed: up to 5,000 bps, 1 - 3,600 s; default: 2,500 bps over 600 s): The fee at the first second, and how long it takes to fall. - `Volatility control` (allowed: 0 or more; default: 10,000): Scales the surcharge. 0 turns it off. ## The launch buy and the launch fees A buy inside the launch transaction pays the full start fee. But the launch position is then the pool's only liquidity, so `LaunchPools` pays the LP part back to the buyer. Only the protocol share stays charged, 20 % of the fee at deploy: 500 bps of the buy with the form's defaults. `LaunchPools` holds the seeded position and has no function that removes it. `collectFees(id)` is open to anyone and pays the LP fees to the launch's fee recipient, even while the venue is paused. `setFeeRecipient(id, to)` lets the current recipient, and only them, hand the fees on. ## Pooled buying with a launch vault Everyone's ETH goes into one first buy, so every depositor pays the same average price. 1. Open: `LaunchVault.open(p, depositEnd)` stores the launch parameters for good. `depositEnd` must be at most 7 days away, and the form defaults to 24 h. 2. Deposit: Until `depositEnd` anyone can deposit ETH and withdraw any part of their own deposit. 3. Launch: From `depositEnd` until 3 days after, anyone can call `launch(id)`, which spends the vault's whole balance as the first buy. 4. Claim, or refund: After a launch, each depositor claims a share of the tokens and of the ETH paid back. If nobody launched in time, each refunds the whole deposit. The unseeded supply and the position's fees go to the creator the opener named. A depositor's one cost is the protocol share of the start fee. The buy has no price limit, so the more ETH the vault holds, the higher up the curve it buys. ## Pons tokens Pons is a third-party launchpad on Robinhood Chain, and its tokens are at `/launch/pons`. A Pons token trades on a bonding curve until it graduates, and then in a Uniswap v4 pool. The [Router](https://zkdepth.com/docs/swaps) takes either as a hop, under your minimum output and deadline. Open a ZKDepth pool calls `DammHook.createPool` for the token against its pair token, at a start price prefilled from the live Pons price. Defaults: 100 bps base fee, 2,000 bps anti-snipe fee falling over 600 s, tick spacing 200. The pool starts empty, and only one per tick spacing can exist. Curve trades pay the Pons curve fee and the creator tax, and buys also pay a snipe tax while one applies. ZKDepth does not list or vet tokens. The bonding-curve launchpad is a separate product, see [Launch](https://zkdepth.com/docs/launch). Where it lives: - `contracts/src/venue/launch/LaunchPools.sol`: launch, preview, the launch position and its fees - `contracts/src/venue/launch/LaunchVault.sol`: open, deposit, withdraw, launch, claim, refund - `contracts/src/venue/periphery/PonsAdapter.sol`: the Pons curve hop --- # Token vaults and pre-market perps Source: https://zkdepth.com/docs/graduation · Launch How an aged token earns a managed vault, and perps on a virtual AMM for tokens no venue lists. A token that already trades can get two more things on ZKDepth, on the Token vaults page at `/launch/vaults`: a managed vault over its deepest pool, and a perpetual market before any order book lists it. This is a separate road from the [launchpad](https://zkdepth.com/docs/launch), where a curve graduates into its pool. Neither contract has an owner or an allow-list, and `Graduated` and `MarketCreated` mean only that someone paid gas. In brief: - A token gets its vault when two on-chain facts hold: at least 3 days since someone called `track`, and at least `10 ether` of liquidity, averaged over the last 30 min across its token/WETH pools. - A pre-market perp takes USDG, isolated per position, at 1x to 3x. It charges 30 bps of notional on entry and on exit, and caps open interest at 5x the market's virtual depth. - A winning close can be paid less than it earned: first cut by any outstanding bad debt, then limited to the USDG the contract holds free, with the rest owed to you as a deferred claim. ## Opening a vault `track(token)` needs a token/WETH pool on one of the four Uniswap v3 fee tiers. It grows each pool's observation ring to 60 slots and starts the clock. `graduate(token)` then checks: 1. No vault yet: A token gets one vault, and it cannot be removed. 2. Old enough: At least 3 days since `track`. 3. Deep enough, over time: For each tier, the pool's harmonic-mean in-range liquidity over the last 30 min, summed to at least `MIN_LIQUIDITY`. Liquidity added in the same block weighs nothing. 4. Vault deployed: On the single deepest pool, as a clone with no admin. The vault has the policy of the deploy's own [vaults](https://zkdepth.com/docs/vaults): WETH deposits, a ±25 % range, a 6 h interval, 100 bps of slippage, and compounding. It starts empty. ## The Seasoned screen Seasoned is a screener label: a pool at least 3 days old, a market cap above $1,000,000, and at least 100 trades in 24 h. No contract reads it. ZKDepth indexes its own pools and not the third party's, so `/launch/vaults` shows the clock and the depth from the chain, and market cap and trades as unknown. A clock starts with `track`, which anyone can send from that page or any wallet. Each minute the graduator bot sends `graduate` for a token on the clock once 3 days have passed. You can make either call for any token. ## Pre-market perps `PreMarketPerp` is a constant-product virtual AMM with no liquidity providers and no counterparty. The contract's USDG balance pays every winner. Parameters: PreMarketPerp constants - `Leverage` (allowed: 1 - 3 x): Whole numbers only. - `Taker fee` (allowed: 30 bps): Of notional, on entry and on exit. A third goes to the insurance fund. - `Maintenance` (allowed: 625 bps): Of notional, at the mark and at the index. - `Liquidation bounty` (allowed: 100 bps): Of the remaining margin. The insurance fund gets the rest. - `Market depth` (allowed: 10,000 or more USDG): Set by the creator. Open interest is capped at 5x it. - `Funding` (allowed: ±1 % an hour): One poke charges at most 24 hours. ### Two prices The **index** is a 30-minute mean tick of the token/WETH pool, times Chainlink ETH/USD. The **mark** is the curve's own price, and every fill uses the curve. Funding is the capped gap between the two, and longs pay when the mark has run above the index. A liquidation needs a position below maintenance at both prices, so pushing the mark cannot liquidate anyone by itself. While the index is unreadable, liquidations wait and `close` keeps working. ### A position's life `createMarket(token, fee, virtualLiquidityUsd)` is open to anyone, and the curve is seeded at the index. `open` takes the entry fee out of your collateral first, so a 3x open is about 3.03x on what remains, and a limit price bounds the fill. A 3x long is liquidatable roughly 28.6 % below entry, a 3x short roughly 25.2 % above. `close` is for the position's trader only. `liquidate` is open to anyone, and the liquidated trader keeps nothing. ### Bad debt and late profits A close or liquidation that ends below zero is bad debt. The insurance fund pays first, and anything left becomes `deficit`, which the next profitable closes pay out of their profit, never their collateral. The contract pays out only free USDG: its balance minus all open collateral and the insurance fund. A profit above that is recorded in `deferred[trader]`, and `claimDeferred()` pays as much as is free at the time, first come, first served. Where it lives: - `contracts/src/LaunchPipeline.sol`: track, graduate, canGraduate - `contracts/src/PreMarketPerp.sol`: createMarket, open, close, liquidate, pokeFunding, claimDeferred - `apps/api/src/bots/graduator.ts`: the bot that graduates --- # Fees and referrals Source: https://zkdepth.com/docs/fees · Protocol What each action costs, the relayer quote, where the money goes, and the referral share. This chapter lists what ZKDepth charges and where it goes. Private lanes first, then the public venue. In brief: - Deposits and private transfers are free. - Private actions, withdrawals and sealed batches cost 0.10 %. A private position pays it once, on the margin going in. - A relayer charges its own quote, paid from the note. - Managed positions pay 10 % of the fees they earn and nothing on principal. - If you were referred, your referrer earns 20 % of every FeeRouter fee you generate. ## Private lanes | Action | Fee | | --- | --- | | Deposit | Free | | Private transfer | Free | | Private action (swap, bins, vault, launch, perps) | 0.10 % | | Withdrawal | 0.10 % | | Sealed batch | 0.10 %, out of the batch | | Relayer | The relayer's quote, paid from the note | The 0.10 % is set at deploy and cannot change after that, and the pool caps it at 1 %. It applies to what leaves the pool, so the relayer's own fee carries it too. A private transfer through a relayer pays the relayer and 0.10 % of that fee, and nothing on the amount sent. A private position is one share, too small for the fee to round above zero. So it pays the 0.10 % once, on the margin going in, at the open and on any margin you add, and nothing on the way out. A sealed batch takes the 0.10 % from what each side gets, before the rate is set. A launch's batch takes it from the quote token before the fill. A void batch pays none. Venue costs still apply: a private swap pays the pools' fees, a private launch trade pays the curve's, and private perps pay the engine's. ### The relayer's quote A quote is the gas of the transaction, priced in the fee token, plus the relayer's margin. It carries a 60 second expiry, and when the proof arrives the relayer checks the fee against the gas cost at that moment. Your browser checks a quote against the gas price it reads itself before it builds the proof, and shows it before anything is sent. Anyone can run a relayer, so the quote is theirs and not ZKDepth's. The fee token is private WETH when the balance holds some, which keeps the token out of the fee. Otherwise it is the note's own token, if the relayer takes it: WETH and USDG by default, and any other its operator lists. Bin shares and positions pay only from private WETH. A sealed claim pays in what it claims and a refund in what it refunds, so a claim of launch tokens has no relayer until one prices them. You can skip the quote by choosing to send from a fresh address, which needs a little ETH. ## Public venue | Action | Fee | Taken from | | --- | --- | --- | | Swap in a ZKDepth pool | The pool's fee, 20 % of which is the protocol share | Your input | | Swap through the ZKDepth Router | The pools' fees, nothing more | Your input | | Spot trade on the Uniswap path | 25 bps | The output | | Zap into a Uniswap range | 25 bps | The input | | Keeper harvest or rebalance | 10 % | The fees collected | | Range order fill or cancel | 10 % | The fees the order earned | | LP or hedged vault harvest or rebalance | 10 % | The fees collected | | Structured note exit before maturity | 50 bps | The amount withdrawn | | On-chain perps open and close | 10 bps each, plus an early close fee | The margin | | Pre-market perp open, close or liquidation | 30 bps | The notional | | Launchpad trade | 1 % | The quote side | A deposit into a vault, a withdrawal from an LP, hedged, DLMM or stock vault, and a DLMM limit order pay no ZKDepth fee. Pool fees depend on the pool. A DAMM pool charges 5 to 100 bps plus a volatility surcharge, at most 500 bps, and a new pool may add an anti-snipe fee that falls to the base fee within an hour. Stock pools charge 30 bps while the market is open, 150 bps while it is closed and 500 bps when the feed is stale. A DLMM pair charges a base fee plus a variable fee, capped at 10 %. See [ZKDepth pools](https://zkdepth.com/docs/pools). Pons and Lighter charge their own fees. ## Fees come out of earnings When a keeper, a vault or a range order closes or re-centres a Uniswap position, the contract charges only on what it collected beyond the principal. A position that has earned nothing pays nothing. Inside one split the order is fixed: the protocol share, then the caller's bounty, then everything left to the owner. ## Where the money goes Diagram: From fee to burn. From the FeeCollector on, each arrow is a call anyone can make. Pool shares and private lane fees go straight to the FeeCollector. Every other fee goes through `FeeRouter`, a contract with no owner: 20 % to the payer's referrer, if any, and the rest flushed to the FeeCollector. From there it becomes ETH, then a buyback and burn of $ZKD. See [Buyback and burn](https://zkdepth.com/docs/protocol). ## Referrals `ReferralRegistry` holds two write-once links and has no owner. - **Your code.** Register 1 to 8 letters or digits on Portfolio. An address owns one code for life. - **Your referrer.** Opening a `?ref=CODE` link offers a one-click bind if you have no referrer yet. A binding never changes, and you cannot refer yourself. - **Earnings.** A referrer earns 20 % of the FeeRouter fees their wallets generate, out of the protocol's share, and claims them per token from Portfolio. Vaults and notes name themselves as the user, so their fees never pay a referrer. A private action has no wallet to name, so it pays no referrer either. ## Bounties A bounty pays the gas of a public call nobody else is paid to make. | Call | Bounty | Paid from | | --- | --- | --- | | `PositionKeeper.harvest`, `rebalance` | The owner's choice, at most 3 % | The fees collected, after the 10 % | | `LPVault.harvest`, `rebalance` | 0.5 % | The fees collected, after the 10 % | | `RangeOrders.fill` | 1 % | The order's output | | `PreMarketPerp.liquidate` | 1 % | The margin left | | `PerpEngine` liquidation | Half the liquidation fee, at least 0.50 USDG | The liquidation fee | ## Points Points are an off-chain ledger: 1 point per dollar of volume and 5 per dollar of fees earned, and volume counts double for 7 days after you post an idea. See [Lounge and Ideas](https://zkdepth.com/docs/lounge). Where it lives: - `contracts/src/FeeRouter.sol`: take, claim and flush - `contracts/src/ReferralRegistry.sol`: register and bind - `contracts/src/shielded/ShieldedPool.sol`: the private lane fee --- # Buyback and burn Source: https://zkdepth.com/docs/protocol · Protocol Fees become ETH, ETH buys $ZKD, $ZKD is burned: each step, its guard and who can call it. ZKDepth has one use for its protocol revenue: buying $ZKD and burning it. A fee collector swaps fees to ETH, and `BuybackV2` spends the ETH on $ZKD and sends it to `0x…dEaD`. In brief: - ZKDepth pools send 20 % of each swap fee to the fee collector. Other fees pass through the `FeeRouter`, which first credits 20 % to the payer's referrer, if any. - Every step is a public call, and the [Protocol page](https://zkdepth.com/protocol) has a button for each. - One `buyback()` spends at most 0.05 ETH, runs at most once per block and pays its caller 0.5 %. - A buyback will not buy while $ZKD trades above a 10 % band over a slow reference price. A falling price never blocks it. ## The path of one fee A ZKDepth pool swap pays its protocol share straight to the fee collector. Every other fee is booked in the `FeeRouter` until someone calls `flush(token)`, see [Fees and referrals](https://zkdepth.com/docs/fees). The collector turns what it holds into ETH for `BuybackV2`, and `buyback()` spends that ETH on $ZKD in the ETH/ZKD Uniswap v4 pool, which pays the tokens directly to `0x…dEaD`. Diagram: From fee to burn. From the FeeCollector on, each arrow is a call anyone can make. ## Converting fees to ETH The owner gives each fee token a route: Router hops ending in native ETH, a cap per call, and an optional oracle floor. Then anyone can call `convert`. With the floor on, the output must be at most 3 % below the `AssetRegistry` feed's price, and a stale feed reverts. WETH needs no route: `unwrapWeth()` unwraps it 1:1, and `forwardEth()` passes ETH fees on. A token without a route waits in the collector. `FeeCollectorV2` has the same conversion logic plus `burnZKDepth()`, which sends its whole $ZKD balance to `0x…dEaD`. It can never sell or sweep $ZKD. The pools pay it once the owner calls `ProtocolConfig.setFeeCollector`. The `FeeRouter`'s treasury stays the first `FeeCollector`, and both send ETH to the same `BuybackV2`. ## Buying and burning `buyback()` takes `BuybackV2`'s ETH balance, including any ETH sent with the call, up to 0.05 ETH. It keeps 0.5 % for the caller and spends the rest. Unspent ETH waits for the next call. $ZKD has no Chainlink feed, so `BuybackV2` checks the pool against a reference price that follows it slowly. - The reference moves at most 30 % per hour since its last update, and at most 10 % per update. With no time elapsed it cannot move, so a pump inside one block does not shift it. - `buyback()` catches the reference up, then reverts `PriceAboveBand` if $ZKD is above the band. - The swap stops at the tighter of the band edge and 10 % above its own starting price. A reverted buyback also undoes its catch-up, so `poke()` moves the reference on its own and pays nothing. After `PriceAboveBand`: poke, wait, retry. `BuybackV2` needs one owner step: `configure` names the $ZKD pool, once. Until then `buyback()` and `poke()` revert `NotConfigured`, and converted ETH waits in `BuybackV2`, which has no other way to send it. ## Burned and circulating supply A burn is a transfer to `0x…dEaD`, so circulating supply is `totalSupply()` less `balanceOf(0x…dEaD)`, checkable by anyone. `BuybackV2` also counts `totalEthSpent` and `totalBurned`. ZKDepth marks any other token called ZKD as not the official one. ## What the owner controls | Contract | The owner can | Limit | | --- | --- | --- | | `ProtocolConfig` | set the protocol share, name another collector | share at most 50 % | | Collectors | set routes and `maxSlippageBps`, name another buyback | slippage at most 20 % | | Collectors | `sweep` a token | only a token with no route, never ETH | | `BuybackV2` | set the per-call cap, the reward, the guard | reward at most 5 %, band 1 to 2,000 bps, drift at most 10,000 bps per hour | So the owner can redirect revenue that has not reached `BuybackV2`. It cannot take ETH from `BuybackV2`, and none of these contracts reaches a user's position or deposit. Every other key is in [Who can do what](https://zkdepth.com/docs/roles). Where it lives: - `contracts/src/venue/periphery/FeeCollectorV2.sol`: routes, the oracle floor, convert, burnZKDepth - `contracts/src/venue/periphery/BuybackV2.sol`: buyback, poke, the reference and the band - `contracts/script/venue/VenueWiring.sol`: deploy values and ownership --- # Who can do what Source: https://zkdepth.com/docs/roles · Protocol Every key and role, what it can do and the limits on it. This chapter lists every key that can do something the public cannot, and the limit the code puts on it. No contract is behind a proxy, so none can be upgraded. In brief: - No owner call can take a liquidity provider's tokens, touch a position or spend a note, and no pause blocks a withdrawal. - The hedger key can move a hedged vault's share price through its reports. - A hedged vault's funder key owns its Lighter account and can withdraw the margin there. - The signer's order keys can trade, and the signer has no code that withdraws. - Relayers and sealed batch keepers cannot move funds. They can refuse or stall, and a stalled batch refunds. ## Keys at a glance | Key | Kept | Can | Limit | | --- | --- | --- | --- | | Owner | cold | the admin calls below | hard caps, two-step transfer | | Hedger | hot, on the API server | hedge and sleeve reports | 15 % band, 1 h staleness, moves no funds | | Keeper | hot, may be the hedger's key | rebalance DLMM and stock vaults | only when due, at most every 5 min, never with a swap | | Funder, one per hedged vault | cold | `fundHedge`, `returnHedge`, its Lighter account | 10 % per call and 25 % per day, to one deposit contract | | Order keys | encrypted in the signer | place, change and cancel Lighter orders, set leverage | 1,000 bps price band, $250,000 per order, no withdrawal | | Relayer | hot, a gas key | send a proof or drop it, and only for what it lists | the proof binds the recipient, the relayer and the fee | | Sealed batch keepers, 3 per market | hot, a gas key and a share secret each | post share sums, void a stale tranche | a total must open the aggregate and settle in the same transaction, and they direct no funds | | Deployer | used once | nothing after the hand-over | the owner must accept | `DeployRobinhood` refuses to run if the hedger or keeper is the deploying key, or the owner is unless `OWNER_IS_DEPLOYER=1` is set. It also refuses if the owner is a hot key, if a funder is the owner or a hot key, or if the two hedged vaults share a funder. ## The owner - **`ProtocolConfig`.** Pause the venue. Set the protocol share of every pool swap fee, at most 50 %. Name the fee collector. Set DLMM presets, for new pairs only. Create DLMM and stock vaults, name their keeper and rebalance them. - **`AssetRegistry`.** List or delist a stock, choose its Chainlink feed and heartbeat, its fees (at most 1,000 bps), its band, the session hours and holidays. - **Fee collectors.** Set conversion routes, a slippage bound of at most 20 %, and where ETH goes. Sweep a token with no route. `FeeCollectorV2` can never sweep $ZKD. - **`BuybackV2`.** `configure` the pool once. Set the per-call cap, a reward of at most 5 %, and the price guard. It cannot take the ETH the contract holds. - **`PerpMarketRegistry`.** Add a market, tune it inside hard bounds, set it reduce-only or delist it, stop new opens, and list a market holiday with notice. It cannot touch a position, a margin or the money in the vault or insurance fund, change a feed, raise a maintenance margin or stop a close. - **`LaunchConfigRegistry`.** Write launch templates inside hard bounds. A change only affects later launches, and nothing here reaches a live curve. `setSealedOpeners` names the sealed lane's opener list once, at deploy. - **`ShieldedPool`.** `setDepositsOpen`, and nothing else. Spending, withdrawals and private actions stay open, and the fee is fixed at deploy. - **`SealedMarkets`.** Add a market inside hard bounds, name its keepers, stop it taking new orders, and set the market a launch's batch copies. A market's terms never change once added. It cannot reach an order or move funds: claims and refunds stay open. The pause stops swaps and liquidity adds on DAMM and stock pools, DLMM swaps, deposits and new pairs, deposits and rebalances in DLMM and stock vaults, and new DLMM limit orders. Two owner calls still reach providers indirectly: a wrong feed moves a stock pool's band, and delisting an asset stops swaps in its pools until it is listed again. ## Contracts with no owner The vaults and `VaultFactory`, both zaps, `RangeOrders`, `PositionKeeper`, `LaunchPipeline`, `PreMarketPerp`, `FeeRouter` and `ReferralRegistry` have no owner and no pause switch. Nor do the Router, the DLMM limit orders, `LaunchPools`, `LaunchVault`, `LaunchFactory`, `GraduationManager`, the perps engine outside its registry, the private action adapters and `SealedBatches`. A vault pays out only to whoever redeems its shares, and its cap and policy are set once. Referral links are write-once. `LaunchPools` and `LPAllocationEscrow` hold launch liquidity with no function that removes it. ## The hedger and the funder A hedged vault cannot see its Lighter position, so it believes the hedger's `reportHedge`. The band limits each report, not a series of them, so reports inside it can walk the price. The same key can also pause a vault's exits by reporting a jump or by going quiet. `fundHedge` can only post USDG to the one Lighter deposit contract fixed at creation, credited to the funder's own account. Whoever holds the funder key can withdraw whatever margin is on Lighter. See [Where the margin lives](https://zkdepth.com/docs/hedged-vaults#where-the-margin-lives). ## Order keys and trading sessions The signer signs nothing without a current mark and applies its [order checks](https://zkdepth.com/docs/lighter#how-an-order-is-priced) to every order type. A trading session lets a key in one browser tab sign orders and cancels for at most 24 h, and it can never withdraw or bind a key. ## Relayers and sealed batch keepers A relayer submits a proof and takes a fee from the note. The proof binds the recipient, relayer and fee, so a relayer can only send it or drop it. Anyone can run one, and each keeps its own list of what it will spend gas on: by default the adapters and tokens of the deployment, launches of the launchpad and pairs of the venue's factory. The pool has no adapter allowlist, because an adapter only receives what a valid proof from the note's owner sends it. If a relayer does not answer or refuses, you can choose to send the spend from a fresh address. A sealed batch keeper holds a share of each order's amount and publishes only the sum. The contract checks the total, so keepers can stall but cannot lie. If they stall the batch is void, and each order can take its input back as a new note. All 3 together could read per-order amounts, not who placed them. At launch one operator runs all 3, and keys rotate by adding a new market. See [Sealed batches](https://zkdepth.com/docs/sealed-batches). Where it lives: - `contracts/script/DeployRobinhood.s.sol`: the role checks and every environment variable - `contracts/src/venue/core/ProtocolConfig.sol`: the venue owner, the pause and the protocol share - `contracts/src/perps/PerpMarketRegistry.sol`: the perps owner and its hard bounds - `contracts/src/shielded/SealedMarkets.sol`: the sealed market owner and its hard bounds --- # Contract addresses Source: https://zkdepth.com/docs/contracts · Protocol Every contract this deployment runs on, read from its manifest. This table reads the deployment manifest this site ships with, so it can never drift from the deploy. A contract that is not deployed is not listed. ## Deployed contracts Core: - FeeRouter: 0xeE46AB1789beE2047029f25d80D0ADbA35b1Bb8D · https://zkdepth.com/abi/FeeRouter.json - ReferralRegistry: 0x40793d25ac3CE56dd7357bbA3e3595f5e9a829E4 · https://zkdepth.com/abi/ReferralRegistry.json - ZKDepthZapV3: 0x303bbc9cCE2dd7d71FDbB74471cAF16E5a7176c3 · https://zkdepth.com/abi/ZKDepthZapV3.json - ZKDepthZapV4: 0xD19db77734239f3517708649e86971f03d042426 · https://zkdepth.com/abi/ZKDepthZapV4.json - RangeOrders: 0xf79C6Be6EEf1164B8cFFf875fCA12d90456a8864 · https://zkdepth.com/abi/RangeOrders.json - PositionKeeper: 0xCa209b78C5Ada1627D513995bB4D7F24FC5da870 · https://zkdepth.com/abi/PositionKeeper.json - VaultFactory: 0xbB9e3f4D061c7ECf36b18318e531d51FD639AcdD · https://zkdepth.com/abi/VaultFactory.json - LaunchPipeline: 0xF5362e748c735A23a61DC74047108641ae3B31C0 · https://zkdepth.com/abi/LaunchPipeline.json - PreMarketPerp: 0xBa3E3F184aB312ac25B61236217Ea5D1694B6de7 · https://zkdepth.com/abi/PreMarketPerp.json Vaults: - ETH Vault: 0x725AaA9F9FA0F877fFD67b941E78633447015FfF · https://zkdepth.com/abi/LPVault.json - Income NVDA: 0xbD5532E94cDcc190D6e777eE22959AB443459DAd · https://zkdepth.com/abi/StructuredVault.json - NVDA Hedged: 0xA8A78a311D5CC2BedDF80160698F5aF453701102 · https://zkdepth.com/abi/HedgedLPVault.json - Protected SPY: 0x551bB59213CE106D2C58AA2Bd017E12E91e6074B · https://zkdepth.com/abi/StructuredVault.json - SPY Hedged: 0x1f55991F1Ff09505e36047f286A7771c99da48E0 · https://zkdepth.com/abi/HedgedLPVault.json - USDG Vault: 0x8FC464E4B11Fca3F1c09990AEA6bDC1AdC423eCD · https://zkdepth.com/abi/LPVault.json ZKDepth pools and router: - ProtocolConfig: 0x7C6fbD2cC16eCcEa1e4c16CaFbea5Fb52B1E24e1 · https://zkdepth.com/abi/ProtocolConfig.json - AssetRegistry: 0x7905c474D7195CAf402be4d9d00f09DC0eab1915 · https://zkdepth.com/abi/AssetRegistry.json - DammHook: 0x2bb0693c5C1ACe6083DC364B2853fcAaD31268CC · https://zkdepth.com/abi/DammHook.json - StockHook: 0x5069523b2A6E917cfAc24aec5af378cF477028CC · https://zkdepth.com/abi/StockHook.json - DlmmFactory: 0x027A0dc162561E5783825e109C99663Af23330e1 · https://zkdepth.com/abi/DlmmFactory.json - DlmmPositionNFT: 0x79bd8c774DdD259C868EC1972b5A4eae1C19b573 · https://zkdepth.com/abi/DlmmPositionNFT.json - Router: 0x9D8C2B906131B3950f5c33d28947199893847409 · https://zkdepth.com/abi/Router.json - FeeCollector: 0xb93F12E6424988364E3a6E9598599eD0475913cb · https://zkdepth.com/abi/FeeCollector.json - BuybackV2: 0x491c7d83081FCea71F0FEd40a79E8Af0FF6Dff49 · https://zkdepth.com/abi/BuybackV2.json - DlmmLimitOrders: 0x07B609140Cd0f80D6Ed5F33e8E2cC8e23c7F10Bc · https://zkdepth.com/abi/DlmmLimitOrders.json - DlmmVaultFactory: 0xA0F9A61024B52CC52dA2b7d0530C70C44123884e · https://zkdepth.com/abi/DlmmVaultFactory.json - DlmmVaultZap: 0x01312544de35A00064358B5E4c6239FB2C264716 · https://zkdepth.com/abi/DlmmVaultZap.json - LaunchPools: 0xd6c5A2B0BBd5287e7BEe204b1fdC90E87CAad13e · https://zkdepth.com/abi/LaunchPools.json - LaunchVault: 0x598fF86820B0AfEE6217F58Da586b023ad8aF008 · https://zkdepth.com/abi/LaunchVault.json - StockVaultFactory: 0xafFdD9eCDd0B19da505D4bb845290841c12971e8 · https://zkdepth.com/abi/StockVaultFactory.json Perps: - PerpEngine: 0x36eaC44FD1F6b659B2eD39A62A8661A6dba7c0a4 · https://zkdepth.com/abi/PerpEngine.json - PerpVault: 0x6fdec61c563cC0B2D5d61a5aF6c5708B18FBE762 · https://zkdepth.com/abi/PerpVault.json - PerpLens: 0x00A88393F74F91C07a9e866a4aaC94489F1E42BC · https://zkdepth.com/abi/PerpLens.json - PerpMarketRegistry: 0x104A224dD5845906d6be13Ac2357eBf62A966930 · https://zkdepth.com/abi/PerpMarketRegistry.json - PerpOracle: 0xfB0F7B196AD23A16f797Feb8f78121f5Dc8ACC24 · https://zkdepth.com/abi/PerpOracle.json - FundingModule: 0x2dA8CCC62ca7dA92fdCedc66dF171Ed402c3DF06 · https://zkdepth.com/abi/FundingModule.json - InsuranceFund: 0xB3507B737D73FdD866c282900b6C58B01268cE62 · https://zkdepth.com/abi/InsuranceFund.json Launchpad: - LaunchConfigRegistry: 0xA91fA7Bf5434938271583197c11ccB78B30Dadc1 · https://zkdepth.com/abi/LaunchConfigRegistry.json - LaunchFactory: 0xC6EeF40233f54BC7c6ED2954e2C2DB916c70d56c · https://zkdepth.com/abi/LaunchFactory.json - GraduationManager: 0x0445728AFB0eDb9270D1C4862b534A85226F0685 · https://zkdepth.com/abi/GraduationManager.json - LPAllocationEscrow: 0x73be4A0fD405Aa1162DAac687c49A10570633F5E · https://zkdepth.com/abi/LPAllocationEscrow.json - CreatorVesting: 0x2C9c9AD29c0935CCb9d76B435C6b964Fe7F05E25 · https://zkdepth.com/abi/CreatorVesting.json - DlmmGraduationAdapter: 0xB11364B60e9c52B8232af812a1A26E625e8A2944 · https://zkdepth.com/abi/DlmmGraduationAdapter.json - DammGraduationAdapter: 0xab87dCeb1913497DE8cb0484500d81185a98068a · https://zkdepth.com/abi/DammGraduationAdapter.json - BondingCurve: 0x1505570f132EE6F7b2f6690c2d1d19ec6DD73d1B · https://zkdepth.com/abi/BondingCurve.json Private balance: - ShieldedPool: 0x890BC80050a561200cfDDB3092B23C19c54CA3CF · https://zkdepth.com/abi/ShieldedPool.json - AssetCustody: 0xD83Ba2Dd76ac3498B289afFAeC83d902Bb9FfB76 · https://zkdepth.com/abi/AssetCustody.json - EthExitAdapter: 0x6c44E67623001051717f39A63B22C4D0297Dcc57 · https://zkdepth.com/abi/EthExitAdapter.json - SwapAdapter: 0xC3A28633571B28F3C19b1C1d4fF1D76cdD078C48 · https://zkdepth.com/abi/SwapAdapter.json - DlmmAdapter: 0x919D8e2c39A4CDF180Da0628044D2E13C3534B53 · https://zkdepth.com/abi/DlmmAdapter.json - VaultAdapter: 0x4074d74c41B6B9d6e0925C1e2CF16E22ed390a83 · https://zkdepth.com/abi/VaultAdapter.json - LaunchAdapter: 0xab79CEE31959a6f68352d332f85fbB8Dd7a93998 · https://zkdepth.com/abi/LaunchAdapter.json - PerpAdapter: 0x81F9646EF3dA09655a4E4D4E57aB527aaD2e74dD · https://zkdepth.com/abi/PerpAdapter.json Sealed batches: - SealedBatches: 0x69Ae7b6614758d67a47ee9bF8Cf61f526DF9261B · https://zkdepth.com/abi/SealedBatches.json - SealedMarkets: 0x6067B78F4CB3107014cB9F171634d8C6BFd88973 · https://zkdepth.com/abi/SealedMarkets.json Vault implementations (every vault clone delegates to one): - HedgedLPVault implementation: 0xeFfc276E1e6c51A356f0Dd35D87595c5812Fb7A3 · https://zkdepth.com/abi/HedgedLPVault.json - LPVault implementation: 0x9c133c0B5B9F33AC784194971C21f48d39021927 · https://zkdepth.com/abi/LPVault.json - StructuredVault implementation: 0x24aeD04933240AEbeCe585D4e73AE3e1BE20b15e · https://zkdepth.com/abi/StructuredVault.json ## For integrators The same addresses are at `/deployments/robinhood.json`, the chain's own constants (Uniswap, Permit2, WETH, USDG, stock tokens, Chainlink feeds) at `/deployments/robinhood-external.json`, and one ABI per contract at `/abi/.json`, listed in `/abi/index.json`. Sources are verified on Sourcify from the first deploy. --- # Architecture Source: https://zkdepth.com/docs/architecture · Reference The contracts and services, which one calls which, and how a write travels from the app to the chain. This chapter is the map for engineers: the contract families ZKDepth deploys, the programs around them, and the two paths data takes. In brief: - Nothing in `contracts/src` is behind a proxy, and nothing can be upgraded. - Your browser sends your public transactions itself, through one hook that simulates each call before your wallet opens. A private action is a proof your browser builds and a relayer sends. - Pages read from the API. The browser never queries the indexer. - The maths every page shows is written once, in `@zkdepth/core`. ABIs, addresses and the manifest are written once, in `@zkdepth/abi`. ## The map Diagram: The system. The browser signs and proves. Services read, relay and keep things moving. The browser, the API and its bots, the indexer, the signer, the relayer and the sealed batch keepers sit off chain, and all of them reach the contracts through the Robinhood Chain RPC. Addresses are in [Contracts](https://zkdepth.com/docs/contracts). ## Contract families | Family | What it holds | Chapter | | --- | --- | --- | | Position tools | `ZKDepthZapV3`, `ZKDepthZapV4`, `RangeOrders`, `PositionKeeper` | [Zap](https://zkdepth.com/docs/zap), [Limit orders](https://zkdepth.com/docs/range-orders), [Keeper](https://zkdepth.com/docs/keeper) | | Vaults | `VaultFactory`, `LPVault`, `HedgedLPVault`, `StructuredVault` | [Vaults](https://zkdepth.com/docs/vaults), [Hedged](https://zkdepth.com/docs/hedged-vaults), [Notes](https://zkdepth.com/docs/structured) | | Graduation | `LaunchPipeline`, `PreMarketPerp` | [Token vaults](https://zkdepth.com/docs/graduation) | | Public venue | `ProtocolConfig`, `AssetRegistry`, `DammHook`, `StockHook`, DLMM, `Router`, pool vaults, `LaunchPools` | [Pools](https://zkdepth.com/docs/pools), [Swaps](https://zkdepth.com/docs/swaps), [Launch pools](https://zkdepth.com/docs/launch-pools) | | Fees | `FeeRouter`, `ReferralRegistry`, fee collectors, `BuybackV2` | [Fees](https://zkdepth.com/docs/fees), [Buyback](https://zkdepth.com/docs/protocol) | | Perps engine | `PerpEngine`, `PerpVault`, `FundingModule`, `InsuranceFund`, `PerpOracle`, `PerpMarketRegistry` | [Perps](https://zkdepth.com/docs/perps) | | Launchpad | `LaunchFactory`, `BondingCurve`, `GraduationManager`, `LPAllocationEscrow`, `LaunchConfigRegistry` | [Launch](https://zkdepth.com/docs/launch) | | Shielded core | `ShieldedPool`, `AssetCustody`, `TransactionVerifier`, `SwapAdapter`, `DlmmAdapter`, `VaultAdapter`, `LaunchAdapter`, `PerpAdapter`, `EthExitAdapter` | [Private balance](https://zkdepth.com/docs/private-balance), [Immediate lane](https://zkdepth.com/docs/immediate-lane), [Private perps](https://zkdepth.com/docs/private-perps) | | Sealed batches | `SealedBatches`, `SealedMarkets`, a `SealedLaunchOpener` per launch that uses one, the order, claim and refund verifiers | [Sealed batches](https://zkdepth.com/docs/sealed-batches) | The families meet at fees: `FeeRouter`'s treasury is the venue's `FeeCollector`, so whatever referrers do not earn joins the pools' protocol fees on the way to the [$ZKD buyback](https://zkdepth.com/docs/protocol). Who can call what is in [Who can do what](https://zkdepth.com/docs/roles). ## The programs off chain | Program | Code | Job | Holds | | --- | --- | --- | --- | | Web app | `apps/web` | every page and every user transaction | nothing, your wallet signs | | API | `apps/api` | the routes pages read, the perp proxy, ideas and points | the signer's shared secret | | Bots | `apps/api/src/bots` | upkeep transactions | one hot bot key | | Indexer | `apps/indexer` | logs into tables, served to the API alone | nothing | | Signer | `services/signer` | Lighter onboarding and order signing | Lighter order keys, encrypted | | Relayer | `apps/relayer` | submits private proofs for a quoted fee | a gas key | | Sealed batch keepers | `apps/keeper`, three of them | read order logs, post share sums, try a post again if it could not settle, void stale tranches | a gas key and a share secret each | [Indexer, API and bots](https://zkdepth.com/docs/offchain) covers the first four, [Perps on Lighter](https://zkdepth.com/docs/lighter) the signer and [Sealed batches](https://zkdepth.com/docs/sealed-batches) the keepers. The relayer's URL is the only private-lane server the browser talks to. The browser reads pool and sealed lane logs through a same-origin RPC route whose keys stay on the server, and builds Merkle paths itself. The API's `GET /shielded/stats` answers aggregates only: set size, total per asset and action counts. No route takes a commitment or an address. ## A write, from click to block Every button that changes chain state calls `useWrite().run(...)` in `apps/web/lib/tx.ts`. 1. Gate: The hook checks for a connected wallet, chain 4663 and a deployment manifest. 2. Simulate: An `eth_call` from your address. A revert stops here, and your wallet never opens. 3. Sign and send: Your wallet signs the simulated request and sends it through the RPC. 4. Refresh: The queries the write affects are refetched as the indexer sees the new logs. A deposit into the private balance is the one exception: the `@zkdepth/zk` client sends it to your wallet with no simulation first. For anything a transaction must get exactly right, the browser reads the chain directly. A private action loads the proving key for its circuit, proves in a browser worker, and a relayer or a fresh address submits it. The API's POST routes store a signed idea, forward a signed perp request to the signer, or return a plan for your wallet to send. ## A read, from chain to page A page hook in `apps/web/lib/data.ts` calls a typed fetcher in `apps/web/lib/api.ts`. The API answers from the indexer, from chain reads batched through Multicall3, from market data or from its database, and a cached route keeps its answer for 15 to 60 s. It maps its own model to the exact shape `api.ts` declares, and a test fails if either side renames a field. The indexer is fed by the chain alone. ## Shared packages - **`@zkdepth/core`** is pure TypeScript: tick and zap maths, fee APR, hedge sizing, note payoffs, DLMM bin maths, slippage. A number on a card and the number a bot acts on come from the same function. - **`@zkdepth/abi`** holds the compiler ABIs, the chain's fixed addresses and the manifest type. The manifest is the one file that says where a deployment lives. - **`@zkdepth/agent`** holds the routes, plans and tools agents and the app share. - **`@zkdepth/zk`** holds keys, notes, the tree, proving workers, scanning and every private action. ## Deploying the stack `DeployRobinhood.s.sol` and `DeployLocal.s.sol` both call `deployAll`, which deploys the venue, the fee contracts, the position tools, the vaults, the perps engine and the launchpad, creates the seed vaults, then proposes the owner for the admin contracts. Until the owner calls `acceptOwnership`, the deployer still holds them. The deploy then adds the private balance, its adapters and the sealed lane, on the verifiers it is given, and names the lane's opener list to the launchpad. The owner adds sealed markets after, with the operators' keeper keys. `scripts/dev-local.sh` runs the same deploy on a local fork, on development proving keys. Where it lives: - `contracts/script/ZKDepthWiring.sol`: deployAll, the seed vaults and writeManifest - `apps/web/lib/tx.ts`: useWrite, the one write path - `packages/abi/src/manifest.ts`: the manifest type and loadManifest --- # Indexer, API and bots Source: https://zkdepth.com/docs/offchain · Reference Events into tables, the routes the app reads, and the bots that keep positions, hedges and pools current. Three programs sit between the chain and the page. The indexer turns contract logs into tables, the API turns those tables, live chain reads and market data into the JSON every page reads, and the bots send the routine transactions that keep positions, hedges and pools current. In brief: - The indexer (Ponder, `:42080`) reads ZKDepth, Uniswap position manager and Pons logs. The API is its only reader. - The API (Fastify, `:8840`) maps every answer to the exact types the web declares. No route sends a transaction for you. - Every bot send is simulated first, and a send paid by a bounty goes out only when the bounty is at least 3 times its gas cost. - Only `reportHedge` and the rebalance of DLMM and stock vaults need the bot's key. Every other bot action is a public function. ## The indexer `apps/indexer` writes one handler per event into Postgres and serves the tables as GraphQL. It follows every ZKDepth contract in the manifest, the Uniswap v4 PoolManager for pools on ZKDepth's hooks, the two Uniswap position managers and the Pons factory. It does not follow the third party's pools, so a range in one shows its value and pending fees from the chain, and no time in range. Volume is counted on the quote side only, so a pushed pool price cannot inflate it. Gate traffic on `/ready`, which turns 200 once the backfill is done. Production needs an RPC with archive state, because handlers read positions at the block of the event. Each build gets its own schema, so a new deploy backfills beside the old one. ## The API `apps/api` is one Fastify image with three roles. `api` serves the routes, `worker` runs the bots and jobs, and `agent` serves the agent tools over MCP and REST with no database, signer secret or hot key. | Routes | Serves | Cache | | --- | --- | --- | | `/radar`, `/markets`, `/candles`, `/book`, `/funding/history` | Markets, Trade, charts | 15 s, 60 s for candles | | `/portfolio/:address` | Portfolio | none | | `/vaults`, `/amm/vaults` | Earn | 30 s, 15 s | | `/pools`, `/stocks`, `/protocol`, `/pons`, `/launch` | Pools, Stocks, Protocol, Launch | 15 to 30 s | | `/perp/*` | Trade, through the signer | none | | `/theses`, `/points`, `/referral`, `/follows`, `/lounge/feed` | Ideas and Lounge | feed 10 s | A test fails when a field in the web's types is added, removed or renamed on either side. `MarketData` is one cached view of every symbol. Lighter REST is the source of truth, with its WebSocket stream filling gaps, and other public sources fill in where Lighter has no mark. `/perp/*` forwards to the signer, with each call signed by a shared secret over a timestamp, a one-time nonce, the path and the body. The API never holds an order key, and there is no withdraw route. Every write shares a budget of 30 a minute per route group and client IP, answered with 429 and `Retry-After`. Reads are not limited, and `/contact` has its own 5 a minute. ## The bots Bots start only when `BOTS`, `BOT_PRIVATE_KEY` and `MANIFEST_PATH` are all set. A worker holds a Postgres lease, so a second container stays on standby. Every write simulates first: a revert is the usual result and costs nothing. A target is muted for 5 min after a success and 15 min after a failure. | Bot | Every | Sends | | --- | --- | --- | | `keeper` | 60 s | `harvest` and `rebalance` on enrolled positions, `fill` on crossed range orders, `harvest` on manifest vaults every 6 h | | `hedger` | 30 s | hedge orders on Lighter, and `reportHedge` every 15 min | | `graduator` | 60 s | `graduate` for tokens past their 3 day clock | | `dlmmVaultRebalancer` | 15 s | `rebalance` once a DLMM vault's drift has held for 120 s | | `stockVaultRebalancer` | 15 s | `rebalance` when `rebalanceStatus()` says ready | | `limitOrderSettler` | 15 s | `executeMany` for up to 25 filled DLMM books | | `buybackPoker` | 15 s | `poke` when the reference is 1 % off the pool and 600 s old | | `feeSweeper` | 10 min | `flush`, `convert`, `unwrapWeth`, `forwardEth`, `burnZKDepth`, `buyback` | | `spotFeeBooker` | 5 min | `take` for 25 bps fees from the Uniswap path | | `perpKeeper` | 10 s | `liquidate` on the perps engine, and `pokeFunding` at most every 15 min | Anyone can send what a bot sends, except `reportHedge`, which only a vault's hedger may call, and the DLMM and stock vault rebalances, which only their factory's keeper may call. Neither role can move funds out of a vault. The worker also runs jobs that need no key. Points accrual reads indexed events every 5 min into the [points ledger](https://zkdepth.com/docs/lounge), one row per event. ## The local stack `scripts/dev-local.sh` runs the contracts, indexer, API and web on a fork, with a paper perp venue (`VENUE=sim`) that production refuses. Where it lives: - `apps/indexer/ponder.schema.ts`: every table and column - `apps/api/src/config.ts`: every environment variable - `apps/api/src/bots/chain.ts`: simulateAndSend and the gas rule --- # Agents and integrations Source: https://zkdepth.com/docs/agents · Reference How an AI agent or a script uses ZKDepth with its own wallet: the calls, the approvals, the minimums, and where the addresses and ABIs live. An agent, whether an AI agent, a bot or a script, can use every pool, vault and swap the app offers by calling ZKDepth's contracts with its own wallet. To set one up step by step, use [Connect your agent](https://zkdepth.com/agents). In brief: - Every ZKDepth contract is non-upgradeable, with no admin function over user positions. An address and its ABI never change under you. - Approve the exact amount, pass a minimum that is not zero wherever the call takes one, and simulate before you sign. - An agent trades ZKDepth's own perps on `PerpEngine` with its own wallet, and perps on Lighter directly, with its own key. - A private balance takes a public deposit from the agent's wallet. Spending from it takes a proof built from the note's keys. ## Where everything lives | Resource | Path on zkdepth.com | What it holds | | --- | --- | --- | | Agent index | `/llms.txt` | The rules, every contract with its ABI link, every chapter with its summary | | Full docs | `/llms-full.txt` | Every chapter as plain text | | Deployment manifest | `/deployments/robinhood.json` | Chain id, deploy block, every ZKDepth contract, the seed vaults by name | | Chain constants | `/deployments/robinhood-external.json` | Uniswap, Permit2, WETH, USDG, stock tokens, Chainlink feeds, Multicall3 | | ABIs | `/abi/index.json`, `/abi/.json` | One JSON ABI per ZKDepth contract | ## The loop Read addresses, ABIs and state. Encode the call with every minimum and deadline it takes. Check the addresses and simulate. Sign and send with the agent's own key, and wait for the receipt. Then read the receipt's events (`Swapped`, `ZapMint`, `Deposit`) rather than trusting the simulation's numbers. ## What an agent can call | Goal | Call | Approval first | Bound you set | | --- | --- | --- | --- | | Swap | `Router.swapExactIn(hops, amountIn, minAmountOut, to, deadline)` | Exact `amountIn` to the Router. ETH goes as `msg.value` | `minAmountOut`, `deadline` | | One-token liquidity | `ZKDepthZapV3.zapMint(params)` or `ZKDepthZapV4.zapMint(params)` | Exact `amountIn` to the zap | `minLiquidity`, `maxSlippageBps` (at most 5,000), `deadline` | | Remove v3 liquidity | `NonfungiblePositionManager.decreaseLiquidity`, then `collect` | None | `amount0Min`, `amount1Min`, `deadline` | | LP, hedged or structured vault | `deposit(assets, receiver)`, `redeem(shares, receiver, owner)` | Exact `assets` to the vault | None on chain: read the previews first | | DLMM vault | `DlmmVaultZap.zapIn(params)`, `zapOut(params)` | Input token, or shares for `zapOut`, to the zap | `minSwapOut` and `minShares`, or `minOut`; `deadline` | | Stock vault | `StockVault.deposit(amount0Max, amount1Max, minShares, to, deadline)`, `withdraw(shares, to, amount0Min, amount1Min, deadline)` | Both tokens to the vault | `minShares`, the two minimums, `deadline` | | Perp | `PerpEngine.open(marketId, isLong, margin, size, limitPrice, deadline)`, `close(id, receiver, limitPrice, deadline)` | Exact USDG margin to the engine | `limitPrice`, `deadline` | | Perp vault | `PerpVault.deposit(assets, receiver)`, `requestExit(shares, receiver)`, then `claimExit(id)` after 1 hour | Exact `assets` to the vault | None on chain: read the previews first | | Private deposit | `ShieldedPool.deposit(token, 0, amount, ownerBlind, ciphertext)`, or `depositETH(ownerBlind, ciphertext)` with ETH as `msg.value`. No proof | Exact `amount` to the pool | None on chain: the pool measures what arrives | | Private spend | `ShieldedPool.transact(proof, inputs, ext)`, or `act(proof, inputs, ext, adapter, data, outs)`. Needs the note owner's proof | None: tokens leave custody | The proof binds each out's `minAmount`, the relayer, its fee and a `deadline` | | Sealed order | `SealedBatches.placeOrder(proof, inputs, ext)`, then `claim` or `refund`. Needs the note owner's proof | None: tokens leave custody | The proof binds the fee and a `deadline`. An order has no limit price: the market's band bounds its rate. A side needs 5 orders by default, or it refunds after 600 s | ### Routes through the Router A route is up to three swap hops, each a `kind` and its ABI-encoded `data`. The hops must chain token to token, and ETH and WETH count as one token. | `kind` | Hop | `data` | | --- | --- | --- | | 0 | A Uniswap v4 pool, ZKDepth's DAMM and stock pools included | `abi.encode(PoolKey, bool zeroForOne, bytes hookData)` | | 1 | A ZKDepth DLMM pair | `abi.encode(address pair, bool swapForY)` | | 2 | A Pons bonding curve | `abi.encode(address curve, bool isBuy)` | | 3 | Wrap or unwrap ETH; does not count toward the three | empty | Quote a route with `quoteExactIn(hops, amountIn)` through `eth_call`. It reverts with the result, so it must never be sent as a transaction. See [Swaps and routing](https://zkdepth.com/docs/swaps). ### A private balance A deposit is a plain call from the agent's wallet. `ownerBlind` and the 142-byte `ciphertext` come from the keys that will own the note, and a blind nobody can open loses the deposit, so build them with the client. `tokenId` must be zero: bin shares and perp positions enter through `act`. Everything after that takes a proof. `transact` is a private transfer or a withdrawal. `act` spends a note into an adapter, which runs the venue call and returns the output to custody as new notes. `SealedBatches` takes orders, claims and refunds. One proof moves one asset. Never send tokens to an adapter: what lands on one goes to the next action in that token. | Manifest key | Contract | What it does | | --- | --- | --- | | `shielded.pool` | `ShieldedPool` | `deposit`, `depositETH`, `transact`, `act`, `exitFullTree`. `treeFull()` and `openOrders()` read the tree's room | | `shielded.swapAdapter` | `SwapAdapter` | A route through the Router, or a Uniswap v3 path | | `shielded.dlmmAdapter` | `DlmmAdapter` | One token into a pair's bins, and one bin at a time back out as both tokens | | `shielded.vaultAdapter` | `VaultAdapter` | ERC-4626 deposit and redeem, DLMM vault in and out | | `shielded.launchAdapter` | `LaunchAdapter` | A buy or a sell on a bonding curve | | `shielded.perpAdapter` | `PerpAdapter` | Open, add margin, increase, decrease, close, `claimLiquidated`, `emergencyClose` | | `shielded.ethExitAdapter` | `EthExitAdapter` | A WETH note out as native ETH | | `sealed.batches` | `SealedBatches` | `placeOrder`, `claim`, `refund`, `voidTranche`. Keepers call `postShares`, and the last post settles | | `sealed.markets` | `SealedMarkets` | The market list and each market's terms | The client in `packages/zk` (`@zkdepth/zk`) does all of it in the agent's own process: `createZKDepthClient({ chain, signTypedData })` derives the keys from the wallet's signature of one fixed typed-data request (`KEY_TYPED_DATA`), then `deposit`, `transfer`, `withdraw`, `privateSwap`, `privateAddLiquidity`, `privateVaultDeposit`, `privateLaunchBuy`, `privatePerpOpen`, `sealedOrder` and the rest scan the logs, prove and submit. With no `relayerUrl` an immediate spend goes from its own `walletClient` with no relayer fee, and that wallet shows as the sender, so give it a fresh address: `freshAddressKey(index)`. A sealed order, claim or refund never does that by itself: `sealedRoute(order, 'claim' | 'refund')` says whether a relayer will send it, and otherwise it throws `SelfSubmitRequiredError` until you pass `selfSubmit` with a fresh wallet. `sealedDepth(marketId, side)` says how thin a tranche is, and `forgetKeys()` drops the keys from the client. A perp call takes `slippageBps`, 100 by default against the oracle's price, or a `limitPrice`, where `0n` says no limit. With no oracle price it throws. `withdraw` and `exitFullTree` refuse the pool's own contracts. A relayer submits only for the adapters and tokens it lists, by default those of the deployment. See [Private balance](https://zkdepth.com/docs/private-balance), [Immediate lane](https://zkdepth.com/docs/immediate-lane) and [What each lane shows](https://zkdepth.com/docs/lanes). ## The rules 1. **Your own wallet.** Never ask for, accept or send a private key, a seed phrase or a session key: ZKDepth never needs one. 2. **Check every address.** Before signing, check the transaction's `to` and every approval's spender against `/deployments/robinhood.json` or `/deployments/robinhood-external.json`. 3. **Exact approvals.** Approve the exact amount of the next call, to the contract that pulls it, never an unlimited amount. 4. **A real minimum.** Wherever a function takes a minimum (`minAmountOut`, `minLiquidity`, `minShares`, `amount0Min`, `amount1Min`), pass one that is not zero, with a deadline a few minutes out. An ERC-4626 vault deposit or redeem takes none: read `previewDeposit` or `previewRedeem` just before signing and send straight away. 5. **Simulate before signing.** `eth_call`, or one `eth_simulateV1` request for an approval and the call after it. 6. **Vault decimals.** LP, hedged and structured vault shares have 3 more decimals than their asset: read the vault's own `decimals()`. 7. **Perps.** ZKDepth's own perps are contract calls from your own wallet: `PerpEngine`, with a `limitPrice` and a `deadline`. See [Perps](https://zkdepth.com/docs/perps). Lighter orders are signed by ZKDepth's signer for people using the app: to trade on Lighter, use Lighter directly, with your own key. 8. **Private balance.** A deposit is a public call from your own wallet. A spend (`ShieldedPool.transact` or `act`, or a `SealedBatches` order, claim or refund) takes a proof built from the note's keys, in your own process. Those keys never leave it. Never send tokens to an adapter. A zap's `maxSlippageBps` is measured inside the same transaction, so derive `minLiquidity` from `previewZap` at a price you trust. Vaults can refuse: deposits and withdrawals revert while the pool's spot price is more than 3 % from its 30-minute average, and a hedged vault pauses both when its last report is over an hour old or its equity jumped more than 15 %. ## Simulating a sequence An approval and the call that uses it are two transactions, and a plain `eth_call` of the second fails until the first is mined. `eth_simulateV1` runs calls in order inside one simulated block, and the public RPC answers it. ```json { "method": "eth_simulateV1", "params": [{ "blockStateCalls": [{ "calls": [ { "from": "", "to": "", "data": "" }, { "from": "", "to": "", "data": "" } ] }], "validation": false }, "latest"] } ``` A status of `0x1` on both means the pair would succeed against the current block. ## Agent starter kit The [agent starter kit](https://zkdepth.com/examples/agent-quickstart/README.md) is plain TypeScript with viem as its only dependency. It reads the published files, quotes native ETH for USDG, plans a USDG Vault deposit and simulates the calls in one request. It signs only with `--send`, one transaction at a time, with its own key, and Robinhood Chain also needs `--mainnet`. ## Referrals for agent builders Register a code once with `ReferralRegistry.register(bytes8 code)`. A user who calls `bind(code)` is tied to you for life, and 20 % of every `FeeRouter` fee their activity pays goes to you, out of the protocol fee and not out of the user's trade. A binding can never be changed, so never bind a user who has not asked for it. See [Fees and referrals](https://zkdepth.com/docs/fees). ## Reading data and rate limits Everything an agent needs before it signs is a view call, and Multicall3 batches them. The public RPC is rate-limited and keeps only recent state, so give an agent that reads a lot its own RPC. The files on this site are static, so read them when the agent starts. The app's API is unversioned, and it limits writes to 30 a minute per route group and IP address, answering `429` with `Retry-After`. ## App flows The `zkdepth_build_*` tools build public calls only. A private spend goes through the client above. `PositionKeeper` needs `setApprovalForAll`, which covers every position the wallet holds, see [Position automation](https://zkdepth.com/docs/keeper). Launches and range orders have their own rules: read [Launch](https://zkdepth.com/docs/launch), [Launch pools](https://zkdepth.com/docs/launch-pools) and [Limit orders](https://zkdepth.com/docs/range-orders) before calling those contracts. Where it lives: - `apps/web/lib/agents/llms.ts`: buildLlmsTxt and buildLlmsFullTxt - `contracts/src/venue/periphery/Router.sol`: swapExactIn, quoteExactIn and the hop kinds - `apps/web/lib/trade/approvals.ts`: exact approvals, as the app does them --- # Glossary Source: https://zkdepth.com/docs/glossary · Reference The terms these docs use, each in a sentence or two. The words below come up across these docs. Each entry says what the term means in ZKDepth and points to the chapter that explains it. ## Trading **Carry.** What a hedged position earns per year before price moves: the pool's fee APR plus the perp funding a short receives (hourly funding × 8,760). ZKDepth flags 8 % or more as worth watching and 20 % or more as strong. See [Using the app](https://zkdepth.com/docs/app-guide). **Funding.** The periodic payment between longs and shorts that keeps a perp's price near its index. Positive funding means longs pay shorts. See [Perps on Lighter](https://zkdepth.com/docs/lighter). **Hedge.** A liquidity position paired with a perp short sized to the position's delta, so price moves on the two legs roughly cancel and the fees remain. See [Hedged vaults](https://zkdepth.com/docs/hedged-vaults). **Idea.** A position published with the argument for it and its running PnL. Mirroring one opens the same kind of position at your own size. See [Lounge and Ideas](https://zkdepth.com/docs/lounge). **Limit order.** A one-sided liquidity position placed entirely above or below the price. It converts as the price crosses it and earns swap fees while it waits. See [Limit orders](https://zkdepth.com/docs/range-orders). **Order key.** A Lighter API key the signer service holds for you. It can place and cancel orders on your Lighter account and has no way to withdraw. See [Perps on Lighter](https://zkdepth.com/docs/lighter). **Price impact.** How much worse a route's rate is than the rate for a tiny trade on the same hops. See [Swaps and routing](https://zkdepth.com/docs/swaps). **Slippage.** The most the output may fall short of the quote before the transaction reverts. You set it per trade, and the app turns it into a minimum amount out. ## Liquidity **Anti-snipe fee.** A fee that starts high when a pool opens and falls in a straight line to the base fee, within one hour at most. See [ZKDepth pools](https://zkdepth.com/docs/pools). **Bin, bin step and active bin.** A DLMM pair splits its price range into discrete bins. The bin step is the price distance between neighbours, 1 to 100 bps. The active bin is the one that holds both tokens, where swaps fill. See [ZKDepth pools](https://zkdepth.com/docs/pools). **DAMM.** A Uniswap v4 pool on ZKDepth's `DammHook`, whose fee is a base rate plus a surcharge that rises with recent price movement. See [ZKDepth pools](https://zkdepth.com/docs/pools). **DLMM.** ZKDepth's pair where liquidity sits in discrete price bins, each trading at one exact price. See [ZKDepth pools](https://zkdepth.com/docs/pools). **Harvest.** Collecting a position's earned swap fees, and on a vault or an automated position, adding them back as liquidity. A harvest pays 10 % of the fees to the protocol, never any principal. See [Position automation](https://zkdepth.com/docs/keeper). **Range.** The price interval a concentrated position is active in, written as two ticks. Outside it the position holds one token and earns nothing. See [One-token deposits](https://zkdepth.com/docs/zap). **Rebalance.** Closing a position that has drifted out of range and opening a new one centred on the current price. See [Position automation](https://zkdepth.com/docs/keeper). **Stock pool.** A v4 pool on `StockHook` for a tokenized stock. Every swap must leave the price inside a band around the Chainlink price, and the band and fee follow the market session. See [ZKDepth pools](https://zkdepth.com/docs/pools). **Zap.** One transaction that takes a single token, swaps the part the range does not need, mints the position and refunds the rest, for 25 bps of the input. See [One-token deposits](https://zkdepth.com/docs/zap). ## Vaults and notes **Equity jump and stale report.** A hedge report that moves equity by more than 15 %, or a last report older than one hour. Either pauses a hedged vault's deposits and withdrawals. See [Hedged vaults](https://zkdepth.com/docs/hedged-vaults). **ERC-4626.** The standard interface for tokenized vaults: you deposit an asset and receive shares whose value follows the vault's total assets. **Sleeve.** One part of a structured note's capital. A Protected note puts 85 % into SGOV, a T-bill token. See [Structured notes](https://zkdepth.com/docs/structured). **TWAP guard.** The check run before value moves at a pool's price: spot must be within 3 % of its 30-minute average, over at least 10 minutes of history. See [Vaults](https://zkdepth.com/docs/vaults#the-price-check). ## Private balance **Note.** A private balance entry: an amount of one asset, held as a commitment in the shielded pool's tree. Any token, ERC-4626 share, LP vault share, DLMM bin share or perp position can be a note. See [Private balance](https://zkdepth.com/docs/private-balance). **Commitment.** The hash stored on chain in place of a note: `Poseidon(amount, assetId, ownerBlind)`. For a deposit the pool computes it from the amount that actually arrived. **Nullifier.** The value published when a note is spent: `Poseidon(commitment, leafIndex, nk)`. The pool stores it so a note spends once, and it does not reveal which commitment was spent. **Viewing key.** A key derived from a fixed request your wallet signs. It finds your notes when the client scans the pool's events and recognises the spent ones, and it cannot spend them. See [Keys](https://zkdepth.com/docs/keys). **Relayer.** A service that submits your proof and takes its quoted fee from the note, in private WETH or in the note's own token if it takes that token. Anyone can run one, the proof binds its address and fee, and by default it spends gas only on adapters and tokens of the deployment. See [Lanes](https://zkdepth.com/docs/lanes). **Fresh address.** A one-time address derived from your keys: a withdrawal target that does not link back to your wallet, or the sender of a spend with no relayer fee, which needs a little ETH. See [Keys](https://zkdepth.com/docs/keys). **Position note.** A perp position held as one share in custody, with a random share id its owner picks. See [Private perps](https://zkdepth.com/docs/private-perps). **Adapter.** A contract a private action spends a note into. It runs the venue call and returns tokens to custody. You choose it and your proof binds it, so there is no allowlist. See [Immediate lane](https://zkdepth.com/docs/immediate-lane). **Immediate lane.** A private action that settles in one transaction. A failed minimum reverts the whole transaction, so the note stays unspent. See [Immediate lane](https://zkdepth.com/docs/immediate-lane). **Sealed batch.** A lane that gathers orders by epoch, crosses opposing totals at a reference price and routes only the net. It hides who placed each order and how much each was. The market, each side's order count and total, and the net stay visible. See [Sealed batches](https://zkdepth.com/docs/sealed-batches). **Tranche.** One side of a sealed market's run of orders. It collects until an epoch ends with at least `minOrders` in it, and voids if it never gets there within `maxWait`. See [Sealed batches](https://zkdepth.com/docs/sealed-batches). **Keeper (sealed batch).** One of the 3 services that each hold a share of every order's amount and publish only the sum. Keepers cannot move funds. All 3 together could read per-order amounts, not who placed them. See [Sealed batches](https://zkdepth.com/docs/sealed-batches). **Anonymity set.** The notes an action could have come from. All notes share one tree, so every asset adds to the same set. The app calls its live size the set size. See [Private balance](https://zkdepth.com/docs/private-balance). ## Launch **Graduation.** Giving a tracked token a managed vault once it is at least three days old and its pools hold enough liquidity. Anyone can call it. See [Token vaults and pre-market perps](https://zkdepth.com/docs/graduation). **Opener.** Whoever a launch names to make its one fill before the public opens. Only one from `SealedMarkets.createLaunchOpener` fills past the wallet limit at the base fee. Any other pays the whole protection fee and stops at the wallet limit. See [Launchpad](https://zkdepth.com/docs/launch). **Launch vault.** A vault that collects ETH before a token exists and spends all of it as the new pool's first swap, so every depositor pays the same average price. See [Launch pools](https://zkdepth.com/docs/launch-pools). **Pons.** A token launchpad on Robinhood Chain. ZKDepth can route swaps through a Pons bonding curve and give a Pons token a ZKDepth pool. See [Launch pools](https://zkdepth.com/docs/launch-pools). **Pre-market perp.** A perp on a virtual AMM for a token no venue lists yet: USDG margin, up to 3x, hourly funding. See [Token vaults and pre-market perps](https://zkdepth.com/docs/graduation). **Seasoned.** A screener label for a token whose pool is at least three days old, whose market cap is above $1 million, and which traded at least 100 times in 24 hours. It is set off chain and no contract checks it. See [Token vaults and pre-market perps](https://zkdepth.com/docs/graduation). ## Protocol **Bounty.** A payment to whoever calls a permissionless function, such as a harvest, a limit-order fill or a liquidation. See [Fees and referrals](https://zkdepth.com/docs/fees). **Keeper.** Any account that calls ZKDepth's permissionless maintenance functions for the bounty. ZKDepth runs one as a bot. See [Indexer, API and bots](https://zkdepth.com/docs/offchain). **$ZKD.** ZKDepth's token. Protocol fees are converted to ETH, which buys $ZKD and sends it to the burn address. See [Buyback and burn](https://zkdepth.com/docs/protocol). **Protocol share.** The part of every ZKDepth pool's swap fee that goes to the fee collector: 20 % at deploy, never more than 50 %. See [ZKDepth pools](https://zkdepth.com/docs/pools). **Referral share.** 20 % of every fee that passes through the `FeeRouter`, credited to the payer's referrer when one is bound. See [Fees and referrals](https://zkdepth.com/docs/fees). **USDG.** The dollar stablecoin ZKDepth uses as its main quote asset on Robinhood Chain, and the margin of its perps. --- # Credits Source: https://zkdepth.com/docs/credits · Reference The open-source work the app ships with. ZKDepth ships with open-source work from other people. This page credits it. ## Charts Charts are drawn with [TradingView Lightweight Charts](https://www.tradingview.com/lightweight-charts/), made by [TradingView](https://www.tradingview.com/). Copyright TradingView, Inc., Apache License 2.0. ## Type and scene The typeface is Inter Tight, SIL Open Font License 1.1. The landing's scene runs on three.js, MIT License. ## Marks Every token, venue and wallet mark belongs to its owner and is used to name that entity. `apps/web/public/logos/SOURCES.md` records where each one came from.