# 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: