An agent, whether an AI agent, a bot or a script, can use every pool, vault and swap the app offers by calling ZKDepth's contracts with its own wallet. To set one up step by step, use Connect your agent.
Where everything lives
| Resource | Path on zkdepth.com | What it holds |
|---|---|---|
| Agent index | /llms.txt | The rules, every contract with its ABI link, every chapter with its summary |
| Full docs | /llms-full.txt | Every chapter as plain text |
| Deployment manifest | /deployments/robinhood.json | Chain id, deploy block, every ZKDepth contract, the seed vaults by name |
| Chain constants | /deployments/robinhood-external.json | Uniswap, Permit2, WETH, USDG, stock tokens, Chainlink feeds, Multicall3 |
| ABIs | /abi/index.json, /abi/<Name>.json | One JSON ABI per ZKDepth contract |
The loop
Read addresses, ABIs and state. Encode the call with every minimum and deadline it takes. Check the addresses and simulate. Sign and send with the agent's own key, and wait for the receipt. Then read the receipt's events (Swapped, ZapMint, Deposit) rather than trusting the simulation's numbers.
What an agent can call
| Goal | Call | Approval first | Bound you set |
|---|---|---|---|
| Swap | Router.swapExactIn(hops, amountIn, minAmountOut, to, deadline) | Exact amountIn to the Router. ETH goes as msg.value | minAmountOut, deadline |
| One-token liquidity | ZKDepthZapV3.zapMint(params) or ZKDepthZapV4.zapMint(params) | Exact amountIn to the zap | minLiquidity, maxSlippageBps (at most 5,000), deadline |
| Remove v3 liquidity | NonfungiblePositionManager.decreaseLiquidity, then collect | None | amount0Min, amount1Min, deadline |
| LP, hedged or structured vault | deposit(assets, receiver), redeem(shares, receiver, owner) | Exact assets to the vault | None on chain: read the previews first |
| DLMM vault | DlmmVaultZap.zapIn(params), zapOut(params) | Input token, or shares for zapOut, to the zap | minSwapOut and minShares, or minOut; deadline |
| Stock vault | StockVault.deposit(amount0Max, amount1Max, minShares, to, deadline), withdraw(shares, to, amount0Min, amount1Min, deadline) | Both tokens to the vault | minShares, the two minimums, deadline |
| Perp | PerpEngine.open(marketId, isLong, margin, size, limitPrice, deadline), close(id, receiver, limitPrice, deadline) | Exact USDG margin to the engine | limitPrice, deadline |
| Perp vault | PerpVault.deposit(assets, receiver), requestExit(shares, receiver), then claimExit(id) after 1 hour | Exact assets to the vault | None on chain: read the previews first |
| Private deposit | ShieldedPool.deposit(token, 0, amount, ownerBlind, ciphertext), or depositETH(ownerBlind, ciphertext) with ETH as msg.value. No proof | Exact amount to the pool | None on chain: the pool measures what arrives |
| Private spend | ShieldedPool.transact(proof, inputs, ext), or act(proof, inputs, ext, adapter, data, outs). Needs the note owner's proof | None: tokens leave custody | The proof binds each out's minAmount, the relayer, its fee and a deadline |
| Sealed order | SealedBatches.placeOrder(proof, inputs, ext), then claim or refund. Needs the note owner's proof | None: tokens leave custody | The proof binds the fee and a deadline. An order has no limit price: the market's band bounds its rate. A side needs 5 orders by default, or it refunds after 600 s |
Routes through the Router
A route is up to three swap hops, each a kind and its ABI-encoded data. The hops must chain token to token, and ETH and WETH count as one token.
kind | Hop | data |
|---|---|---|
| 0 | A Uniswap v4 pool, ZKDepth's DAMM and stock pools included | abi.encode(PoolKey, bool zeroForOne, bytes hookData) |
| 1 | A ZKDepth DLMM pair | abi.encode(address pair, bool swapForY) |
| 2 | A Pons bonding curve | abi.encode(address curve, bool isBuy) |
| 3 | Wrap or unwrap ETH; does not count toward the three | empty |
Quote a route with quoteExactIn(hops, amountIn) through eth_call. It reverts with the result, so it must never be sent as a transaction. See Swaps and routing.
A private balance
A deposit is a plain call from the agent's wallet. ownerBlind and the 142-byte ciphertext come from the keys that will own the note, and a blind nobody can open loses the deposit, so build them with the client. tokenId must be zero: bin shares and perp positions enter through act.
Everything after that takes a proof. transact is a private transfer or a withdrawal. act spends a note into an adapter, which runs the venue call and returns the output to custody as new notes. SealedBatches takes orders, claims and refunds. One proof moves one asset. Never send tokens to an adapter: what lands on one goes to the next action in that token.
| Manifest key | Contract | What it does |
|---|---|---|
shielded.pool | ShieldedPool | deposit, depositETH, transact, act, exitFullTree. treeFull() and openOrders() read the tree's room |
shielded.swapAdapter | SwapAdapter | A route through the Router, or a Uniswap v3 path |
shielded.dlmmAdapter | DlmmAdapter | One token into a pair's bins, and one bin at a time back out as both tokens |
shielded.vaultAdapter | VaultAdapter | ERC-4626 deposit and redeem, DLMM vault in and out |
shielded.launchAdapter | LaunchAdapter | A buy or a sell on a bonding curve |
shielded.perpAdapter | PerpAdapter | Open, add margin, increase, decrease, close, claimLiquidated, emergencyClose |
shielded.ethExitAdapter | EthExitAdapter | A WETH note out as native ETH |
sealed.batches | SealedBatches | placeOrder, claim, refund, voidTranche. Keepers call postShares, and the last post settles |
sealed.markets | SealedMarkets | The market list and each market's terms |
The client in packages/zk (@zkdepth/zk) does all of it in the agent's own process: createZKDepthClient({ chain, signTypedData }) derives the keys from the wallet's signature of one fixed typed-data request (KEY_TYPED_DATA), then deposit, transfer, withdraw, privateSwap, privateAddLiquidity, privateVaultDeposit, privateLaunchBuy, privatePerpOpen, sealedOrder and the rest scan the logs, prove and submit. With no relayerUrl an immediate spend goes from its own walletClient with no relayer fee, and that wallet shows as the sender, so give it a fresh address: freshAddressKey(index). A sealed order, claim or refund never does that by itself: sealedRoute(order, 'claim' | 'refund') says whether a relayer will send it, and otherwise it throws SelfSubmitRequiredError until you pass selfSubmit with a fresh wallet. sealedDepth(marketId, side) says how thin a tranche is, and forgetKeys() drops the keys from the client.
A perp call takes slippageBps, 100 by default against the oracle's price, or a limitPrice, where 0n says no limit. With no oracle price it throws. withdraw and exitFullTree refuse the pool's own contracts. A relayer submits only for the adapters and tokens it lists, by default those of the deployment. See Private balance, Immediate lane and What each lane shows.
The rules
- Your own wallet. Never ask for, accept or send a private key, a seed phrase or a session key: ZKDepth never needs one.
- Check every address. Before signing, check the transaction's
toand every approval's spender against/deployments/robinhood.jsonor/deployments/robinhood-external.json. - Exact approvals. Approve the exact amount of the next call, to the contract that pulls it, never an unlimited amount.
- A real minimum. Wherever a function takes a minimum (
minAmountOut,minLiquidity,minShares,amount0Min,amount1Min), pass one that is not zero, with a deadline a few minutes out. An ERC-4626 vault deposit or redeem takes none: readpreviewDepositorpreviewRedeemjust before signing and send straight away. - Simulate before signing.
eth_call, or oneeth_simulateV1request for an approval and the call after it. - Vault decimals. LP, hedged and structured vault shares have 3 more decimals than their asset: read the vault's own
decimals(). - Perps. ZKDepth's own perps are contract calls from your own wallet:
PerpEngine, with alimitPriceand adeadline. See Perps. Lighter orders are signed by ZKDepth's signer for people using the app: to trade on Lighter, use Lighter directly, with your own key. - Private balance. A deposit is a public call from your own wallet. A spend (
ShieldedPool.transactoract, or aSealedBatchesorder, claim or refund) takes a proof built from the note's keys, in your own process. Those keys never leave it. Never send tokens to an adapter.
A zap's maxSlippageBps is measured inside the same transaction, so derive minLiquidity from previewZap at a price you trust. Vaults can refuse: deposits and withdrawals revert while the pool's spot price is more than 3 % from its 30-minute average, and a hedged vault pauses both when its last report is over an hour old or its equity jumped more than 15 %.
Simulating a sequence
An approval and the call that uses it are two transactions, and a plain eth_call of the second fails until the first is mined. eth_simulateV1 runs calls in order inside one simulated block, and the public RPC answers it.
{ "method": "eth_simulateV1", "params": [{ "blockStateCalls": [{ "calls": [
{ "from": "<agent>", "to": "<USDG>", "data": "<approve(vault, amount)>" },
{ "from": "<agent>", "to": "<USDG Vault>", "data": "<deposit(amount, agent)>" }
] }], "validation": false }, "latest"] }
A status of 0x1 on both means the pair would succeed against the current block.
Agent starter kit
The agent starter kit is plain TypeScript with viem as its only dependency. It reads the published files, quotes native ETH for USDG, plans a USDG Vault deposit and simulates the calls in one request. It signs only with --send, one transaction at a time, with its own key, and Robinhood Chain also needs --mainnet.
Referrals for agent builders
Register a code once with ReferralRegistry.register(bytes8 code). A user who calls bind(code) is tied to you for life, and 20 % of every FeeRouter fee their activity pays goes to you, out of the protocol fee and not out of the user's trade. A binding can never be changed, so never bind a user who has not asked for it. See Fees and referrals.
Reading data and rate limits
Everything an agent needs before it signs is a view call, and Multicall3 batches them. The public RPC is rate-limited and keeps only recent state, so give an agent that reads a lot its own RPC. The files on this site are static, so read them when the agent starts. The app's API is unversioned, and it limits writes to 30 a minute per route group and IP address, answering 429 with Retry-After.
App flows
The zkdepth_build_* tools build public calls only. A private spend goes through the client above. PositionKeeper needs setApprovalForAll, which covers every position the wallet holds, see Position automation. Launches and range orders have their own rules: read Launch, Launch pools and Limit orders before calling those contracts.
apps/web/lib/agents/llms.tsbuildLlmsTxt and buildLlmsFullTxtcontracts/src/venue/periphery/Router.solswapExactIn, quoteExactIn and the hop kindsapps/web/lib/trade/approvals.tsexact approvals, as the app does them