Dark pools.
A dark pool lets you buy a coin on the launchpad, or on its PancakeSwap pair after graduation, from inside the shielded pool, so the wallet that owns the money never appears on the trade. The chain still sees that a buy happened, which coin, how much BNB, and the address that now holds the tokens: a one-off dark vault. It does not see who. Selling, harvesting and any leftover BNB go straight back into the shielded pool as notes, again without your wallet on the transaction.
There is no new circuit, no new trusted setup and no change to any deployed contract. A dark-pool buy is an ordinary transact withdrawal from the same pool everyone else uses, so it hides among every other note in the tree, and the trade itself goes through the Launchpad curve or the Pancake router exactly as a wallet would. The only new pieces are a small factory, DarkPool, and the vault it clones per order, DarkVault. Neither has an admin: the factory answers to nobody, and a vault obeys only its own owner key (see Keys).
You trade from the Dark pool tab of a coin page once you have derived your shielded key on Move BNB. Your positions across all coins are listed under your notes there.
How a buy works
The trick is a counterfactual vault. Your browser builds an order (coin, BNB amount, minimum tokens out, a fresh owner key, an optional deadline and a random nonce) and asks the factory where the vault for that exact order would live: a CREATE2 address, salt = hash of the order. Nothing exists there yet. You then prove a normal withdrawal of that BNB amount (plus the relayer fee) to that address. Only DarkPool can put code there, and it will only do so by executing that exact order, so the relayer, or anyone, can fill it, but nobody can change it: coin, amount, slippage, owner key and deadline are all bound into the vault address, which is bound into extDataHash, which is bound by the proof.
- One proof. Your browser proves the unshield to the predicted vault address, with the relayer's address and fee baked in, like any relayed unshield.
- The relayer fills. It calls
DarkPool.transactAndFill(proof, extData, order)from its own wallet. The pool pays the vault address and pays the relayer its fee; the factory deploys the vault, initialises it with your owner key and the coin, and the vault buys: on the curve while the coin is on the curve, through the PancakeSwap router after graduation. - Everything or nothing. If the buy fails (the slippage check, for example) the whole transaction reverts, your notes stay unspent and you simply prove again.
The tokens stay in the vault. The vault is yours: it only acts on a signature from its owner key, and that key is derived from your shielded key (see Keys).
Selling, harvesting and shielding back
Everything that leaves a vault goes back into the shielded pool as a note, never to a wallet. Sell → pool sells the tokens (curve or router), Harvest → pool burns them for the coin's roots, and Shield BNB returns BNB sitting in the vault (a buy that could not execute, for instance). Each pays the proceeds into the pool with depositFor, minus the relayer fee, so you never need a proof or a wallet for the way back: your browser signs a message with the vault's owner key and the relayer submits it. The UI pays every vault's proceeds to your own shielded pubKey.
The proceeds note has a public, odd amount: the DepositFor event shows the exact BNB the vault paid in. Do not unshield exactly that amount to a wallet, it would match the vault's payout and so the buy. Let it sit, send it privately, or unshield in standard sizes to a fresh address through the relayer, as Standard sizes explains.
If a buy does not finish
The pool binds your proof to the order, not to whoever sends it. If someone who sees the proof (the relayer itself, or a mempool watcher) submits the bare withdrawal before the fill, your BNB arrives at the vault address with no vault there yet. Nothing is lost: that address is the CREATE2 image of your order, only DarkPool.fill(order) can put code there, and your browser keeps every order it has handed to the relayer until the vault exists. The panel then shows the buy as not finished with a Finish this buy button: the relayer sends the plain fill (no proof, no fee, it was already paid when the withdrawal landed) and the vault buys, or, if the order has expired, keeps the BNB for you to shield back. The same button covers a relayer that crashed between the two steps. Keep the browser profile you traded from, or derive the key again in the same one: the pending list lives there.
A signed vault action (sell, harvest, shield) stays valid until its deadline, ten minutes, even if the relayer fails to send it. If you need to change your mind within that window, wait for the deadline to pass before signing a replacement, or the earlier message could still land first.
What is hidden
- The wallet, and the shielded key, behind a buy, a sell, a harvest or a shield-back. The only EOA on those transactions is the relayer's. The way back still names your pubKey, see below.
- Which note paid for a buy, and what happens to the proceeds after they become a note: the note itself (amount, pubKey, blinding) is public in the
DepositForevent, but spending it is not. - Any link between two dark vaults of the same person until they pay out. Every vault has its own fresh owner key, so two buys cannot be tied together by their owners, only by timing or amount guesses. Once a vault sells, harvests or shields back, the pubKey it pays into is public, and the panel pays every vault into your own pubKey: from then on all of your paid-out vaults are publicly one owner.
What stays public
- Every buy: the coin, the BNB amount (it is an unshield), the vault address, the tokens it received, and the time.
- Every sell, harvest and shield-back: the vault, the amount, and the
DepositForpubKey it paid into. The UI uses your own shielded pubKey, so all of your vaults' proceeds land on one pubKey, and every vault that has paid out is publicly tied to every other one that has. That pubKey is a Poseidon image, not a wallet, but it is the same pubKey for everything you do here. If a wallet ever pays into it (a Harvest shielded on a coin page, a Claim shielded holder reward, or registering a donation cause with it), that wallet is publicly tied to the pubKey, and through it to every dark vault that paid out, before or after. Keep dark-pool trading on a shielded key that no wallet has ever paid into (derive it from a wallet you use for nothing else), or accept that link. - The amount and the coin of a buy. Hiding them would need a multi-asset circuit and a new ceremony: later.
- The relayer learns your IP address, the order and, on the way back, the pubKey you pay into; it does not receive your wallet. It can decline, delay until your deadline, or trade ahead of you within your slippage, like anyone who sees the transaction in the mempool. It cannot redirect or alter anything.
- Timing and sizes. The buy is a public unshield of the BNB amount at a public time. A shield of the same amount shortly before, standard size or not, is the obvious match while the pool is quiet; so is a set of buys adding up to what you just shielded. Buy in the standard sizes (0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5 and 10 BNB), not the size you shielded, and let other activity pass first; anything under 0.01 BNB is always unique. The panel offers the sizes and warns on other amounts, as Move BNB does. Your privacy depends on how much the pool is used.
What the relayer can and cannot do
The relayer is mandatory for dark-pool buys: a transactAndFill sent from your own wallet would put your wallet on the trade and defeat the purpose, so there is no wallet fallback. If the relayer is unavailable, the panel says so and the buttons are disabled. For a buy, the relayer can only submit the proof you gave it: the recipient, amount, fee and order are fixed by extDataHash. For a sell, harvest or shield-back it can only submit the message you signed: the call data, the fee, the deadline and a sequential nonce are all under your signature, so it cannot replay, alter or reorder them. In both cases it is paid the quoted fee out of the transaction itself, and beyond declining or delaying (an order is valid for 30 minutes, a signed vault call for 10) it can only do what any mempool watcher can: trade ahead of you within the slippage you set, so keep slippage tight. It learns your IP address, the order and, on vault calls, the pubKey you pay into; it never receives your wallet or your shielded private key, though the same server also serves this site's read RPC, so the operator could correlate your IP with the wallet reads of the same browser session; use a different network or session if that matters to you. There is one site relayer today; a relayer network is later.
Keys and recovery
Each vault has an owner key, a plain secp256k1 key derived in your browser from your shielded key and the vault's index (keccak256(shieldedKey, "zkBNB dark vault v1", index)). It is derived on demand and never stored. A vault acts only on that key: either the owner calls it directly, or anyone submits a relay(data, fee, deadline, signature) carrying the owner's EIP-712 signature over exactly that call, that fee, that deadline and the vault's current nonce. The vault pays the submitter the fee from its own balance and refuses to be entered any other way: relay, initialize and the factory's own fill cannot be smuggled inside a relayed call.
Because every owner key comes from your shielded key, your shielded key is all you need to recover every vault: the app re-derives the owner addresses and scans the factory's VaultCreated events for them. Anyone who can sign with the wallet you derived your shielded key from can do the same, so the warning on the shielded pool applies here too.
Later
- Hiding the amount or the coin of a buy (needs a multi-asset circuit and a new ceremony).
- Claiming holder rewards from a vault in the UI. The vault has an
execescape hatch that already allows it by hand. - A relayer network. Today there is one site relayer.
Addresses
| BNB Smart Chain | Address | |
|---|---|---|
| DarkPool (factory) | 0xade2c2E6F0edB8bc8d19ECc7BBcA466A9f60e3AF | |
| DarkVault (implementation every vault clones) | 0x6cbf0C7478956FB738E8129e960Ff4eEf4383605 |