How Mist works
Your public wallet sits at the edges. Your private balance lives in the middle, as encrypted notes only your keys can read.
Deposit
Move tokens from your wallet into Mist. The deposit is public. What lands in your private balance is an encrypted note.
Trade
Enter an amount. Independent solvers quote it, your browser proves you own the funds, and the pool settles the trade.
Withdraw
Turn notes back into tokens in the wallet you deposited from. The withdrawal is public and can't be sent anywhere else.
Getting started
Mist uses two wallets. Your private wallet lives in the browser and spends your notes; it's all you need to trade. Your usual wallet only comes in to deposit and to receive withdrawals.
- Set up your private wallet. Pick a passphrase of at least 12 characters. Mist creates your keys in the browser and encrypts them into a recovery file.
- Download your recovery file. Keep it somewhere safe and the passphrase somewhere else. Mist asks you to confirm before your first deposit.
- Connect your wallet to deposit. It's the wallet you deposit from, and the only place withdrawals can go. Trading doesn't need it.
- Deposit, then trade. Hold the button to confirm. Your browser builds the proof, a solver fills the trade, and the result lands in your private balance.
- Withdraw when you're done. Mist checks whether the amount or timing would point back to one of your trades before you confirm.
Privacy model
Mist doesn't hide that trades happen. It takes your wallet out of them.
| Action | Public | Not included |
|---|---|---|
| Deposit | Your wallet, the asset, the amount | The keys that read and spend the note |
| Trade | Solver, assets, amounts, proof | Your wallet, who owns the notes |
| Withdraw | Your wallet, the asset, the amount | The keys that read and spend the note |
Backups and recovery
Your private balance can only be opened with your recovery file and its passphrase. Nobody can reset them, including us.
Keep
The recovery file, the passphrase (stored separately), and access to the wallet you deposited from.
Restore
Open Mist, choose “Restore it”, pick the file and enter the passphrase. Your balance and history come back after a sync.
Keys unlock per browser tab. Close the tab or lock the wallet from the wallet menu and it locks again.
Deposit screening
New deposits go through a quick admission check before they enter the pool. The pool requires a short-lived signed approval for every deposit, including ones made outside this site.
- Only your public wallet address is checked. Your keys and notes stay on your device.
- Only deposits are checked. Trades, withdrawals, recovery and browsing never are.
- If the check can't approve a deposit, it stops before any funds move. If admission is paused, withdrawals keep working.
Fees
0.5% of each action's output. Depositing, trading and withdrawing each pay it once. The rate is fixed in the pool.
- 01Starting balance$1,000.00
- 02After deposit$995.00−0.5%
- 03After one trade≈ $990.03−0.5%
- 04After withdrawal≈ $985.07−0.5%
About 1.49% across a full cycle with prices unchanged. Network costs, solver spreads, slippage and token transfer taxes aren't included. Fees round down in the token's smallest unit.
Chains
Connect your Solana or Robinhood Chain wallet from the top of the app. Each chain has its own balances and transaction history.
One private wallet works on every chain: the same passphrase and the same recovery file. Each chain has its own pool, so balances stay separate and nothing moves between chains.
Protocol and security
Zero-knowledge proofs
Every note commits to its asset, amount, owner and pool inside a Merkle tree. When you trade, your browser creates a Groth16 proof that you own enough and that value is conserved, without putting the owner in the public inputs. Assets, exact amounts and nullifiers stay public; nullifiers stop the same note being spent twice. New notes are encrypted to you so you can always recover them.
An open solver auction
Changing an amount sends a quote request to independent solvers under a one-time key. Each one finds its own route and answers with a signed quote. The first valid quote shows right away, and a better one replaces it. There's no solver whitelist and no preferred venue.
A tight settlement boundary
Your proof fixes the solver and the exact amounts. During settlement the pool hands that solver only the proved sell amount; the solver has to return the full quoted buy amount before the call ends. If any check fails, the whole thing reverts.
Token policy
Deposits only accept assets the pool has enabled. Trades can pay out any token that isn't blocked. Reentrancy locks, balance checks and per-asset accounting protect the pool, but no protocol can make a malicious or worthless token safe to hold.
Tokens that tax transfers
The fee is taken from what each action actually produces. For a taxed deposit, the pool uses what it really received. A taxed withdrawal can land with less than requested, because the token itself takes a cut on the way out.
Questions
Does a solver ever hold my funds?
No. The pool holds the assets. A solver only receives the exact amount your proof allows, inside a single settlement that reverts if it doesn't pay back what it quoted.
Is my private wallet an onchain account?
No. It's a set of keys in your browser that can read and spend your notes. There's no private-wallet address onchain.
Can I withdraw to a different address?
No. Withdrawals only go back to the wallet that deposited. That keeps Mist from becoming a way to send funds privately to someone else.
Which tokens can I trade?
Trades can pay out any token that isn't blocked. Deposits accept the assets the pool has enabled.
What do I need to back up?
Your recovery file and its passphrase. You need both to restore your private balance, plus access to the wallet you deposited from to withdraw.
What does it cost?
0.5% of what each action produces: the deposit, the trade output, or the withdrawal. Network fees are on top and shown before you confirm.
Build on Mist
Run an independent solver, connect an application to the pool, or build your own trading experience with the Mist SDK.
These integration guides cover the EVM deployment on Robinhood Chain. The solver handles routing and settlement; the SDK handles private keys, notes, proofs and the client lifecycle.
Run a solver
A solver listens for quote requests over Nostr, finds a route and signs an offer. When a user accepts, their proof authorizes that exact trade. Your contract settles it atomically with the Mist pool. There is no solver registration or application process.
- Create an operator directoryThe CLI generates separate operator and Nostr keys in
mist-operator/.env, with file permissions set to0600. It prints the public operator address and refuses to overwrite existing keys.node packages/solver/src/cli.js init ./mist-operator - Configure the RPC and fund the operatorSet
RH_RPC_URLin the generated file to your Robinhood Chain endpoint. Send ETH on chain 4663 to the printed operator address for deployment and settlement gas. The health check requires at least 0.0005 ETH; leave additional room for deployment and ongoing transactions. Keep an offline backup of the generated keys. - Deploy your settlement contractThe CLI verifies the router’s pinned code, deploys the reference
OpenOceanSolver, and records its address in your operator configuration. Its router and operator are immutable. Deploy once for this operator.node packages/solver/src/cli.js deploy-contract ./mist-operator - Check the deployment, then start listeningThe read-only doctor checks the RPC, pool, contract ownership, router, routing API, gas balance and relays. Resolve failures before starting the process.
node packages/solver/src/cli.js doctor ./mist-operatornode packages/solver/src/cli.js start ./mist-operator
How the solver earns
The reference solver routes through OpenOcean and commits to a buy amount below the route’s expected output. After paying that amount into the pool, the settlement contract sends surplus tokens to the operator. Gas, failed transactions and price movement reduce the margin. Mist’s 0.5% protocol fee is deducted from the output credited to the user.
Your quote cannot change the user’s proved terms or redirect a withdrawal. If the route cannot pay the promised output, settlement reverts.
Configure and operate
Each command reads the operator directory’s .env. Process environment variables take precedence. Keep the generated contract setting intact; the deployment command fills it in for you.
RH_RPC_URLPrimary RPC for Robinhood Chain, chain ID 4663. Replace the generated public endpoint with your own for sustained traffic.
RH_SECONDARY_RPC_URLOptional second RPC. Startup checks every configured endpoint against the network and contracts.
SOLVER_PRIVATE_KEYGenerated operator key. Signs quotes, pays settlement gas and receives the contract’s surplus.
SOLVER_NOSTR_PRIVATE_KEYSeparate generated key for your relay identity. It must differ from the operator key.
NOSTR_RELAYSComma-separated secure WebSocket relay URLs. Use at least two reachable relays.
OPENOCEAN_API_KEYOptional API key for the reference solver’s route provider.
# Optional settings in mist-operator/.env
SOLVER_SLIPPAGE_BPS=100
SOLVER_QUOTE_SAFETY_BPS=25
SOLVER_MAX_GAS_PRICE_WEI=1000000000
SOLVER_CONFIRMATIONS=2
SOLVER_STATE_PATH=solver-data/quotes.json100 basis points is 1%. The reference defaults use 1% routing slippage and an additional 0.25% quote buffer. The gas cap above is 1 gwei. These are configurable limits, not guaranteed earnings.
Keep one active instance
Run one process per operator key. Preserve solver-data/quotes.json across restarts: it holds quote state and replay protection. A second process with separate state can conflict with the first.
Make restarts predictable
Use a process supervisor and persistent storage. Keep environment files out of images and logs. On upgrades, stop the old process, retain its keys and quote state, run doctor and restart.
Read the event stream
The process writes JSON logs to standard output. Track completed settlements, relay errors and the operator’s ETH balance.
started / stoppedProcess lifecycle. A stopped process is no longer listening for new requests.
quote / quote_failedAn offer was sent, or a request could not be quoted. Check the reason for missing routes, minimum-output or gas-limit failures.
execution / execution_failedA settlement was confirmed or failed. Successful entries include a transaction hash and block number.
transport_errorA relay message or connection failed. Check relay reachability and the reported reason.
Use the SDK
Initialize your SDK client with the Robinhood network configuration and a viem wallet client carrying the account that owns the notes. The examples accept that initialized client as mist. Use a dedicated RPC for sustained scanning and keep wallet signing credentials out of browser bundles.
bigint, not floating-point amounts. USDG has 6 decimals: 1_000_000n is 1 USDG. ETH and WETH have 18 decimals. Use token addresses from the selected network configuration.1. Back up before depositing
For a new private wallet, create keys and save the encrypted recovery file before moving funds. Use a passphrase of at least 12 characters and store it separately. For an existing wallet, restore its backup instead of creating new keys.
import { writeFile } from "node:fs/promises";
// For a new private wallet. Pass your initialized SDK client.
export async function createPrivateWallet(mist, passphrase) {
await mist.keys.create();
const backup = await mist.keys.backup(passphrase);
await writeFile("mist-recovery.json", backup, {
flag: "wx", // never overwrite an existing backup
mode: 0o600,
});
}2. Deposit, request an offer and trade
Deposits check asset policy and obtain a screening approval before submission. After the deposit, sync to discover the new note. Set a minimum buy amount for your trade; offers are sorted by output, and an empty result means no solver answered. The example below deposits 5 USDG and trades 1 USDG.
// minimumBuy is a gross minimum in the buy token's base units.
export async function depositAndTrade(mist, minimumBuy) {
const { USDG, WETH } = mist.network.assets;
// 5 USDG. The deposit credits 4.975 USDG after the fee.
await mist.deposit({ asset: USDG, amount: 5_000_000n });
await mist.sync();
const [best] = await mist.quote({
sell: USDG,
buy: WETH,
amount: 1_000_000n, // trade 1 USDG of the private balance
minimumBuy,
});
if (!best) throw new Error("No solver quote received");
// best.netBuyAmount is the amount after the protocol fee.
const receipt = await mist.trade(best);
await mist.sync();
return receipt;
}These calls submit real transactions. Display netBuyAmount when showing what the user receives. Use each offer once, request a new one after a trade attempt, and sync after settlement before reading balances.
3. Restore and withdraw
Restore with the same recovery file, passphrase and owning wallet, then sync. Withdrawals return to that wallet; there is no arbitrary recipient parameter. Close the client with mist.close() when your integration shuts down.
import { readFile } from "node:fs/promises";
export async function restorePrivateWallet(mist, passphrase) {
const backup = await readFile("mist-recovery.json", "utf8");
await mist.keys.restore(backup, passphrase);
await mist.sync();
return mist.balances();
}
export async function withdraw(mist, asset, amount) {
const receipt = await mist.withdraw({ asset, amount });
await mist.sync();
return receipt; // includes receivedAmount after the fee
}Methods and failure handling
keys.create()Create independent viewing and spending keys for a new private wallet.
keys.backup(passphrase)Return encrypted recovery JSON. Store the result before depositing.
keys.restore(json, passphrase)Restore a recovery file, then call sync() before spending.
sync(options?)Scan pool events, rebuild notes and reconcile spent nullifiers. Tune logChunkSize and concurrency for your RPC.
balances()Read the last synced private balances as { asset, amount } entries.
deposit({ asset, amount })Check asset policy, create the proof, obtain screening approval and submit the deposit. Returns noteAmount with the receipt.
quote({ sell, buy, amount })Collect verified offers, best first. An empty array means no solver answered.
trade(offer)Prove the selected offer, send it to its solver and wait for the settlement receipt.
withdraw({ asset, amount })Withdraw to the wallet that owns the notes. Returns receivedAmount after the fee.
close()Close relay connections and discard pending quote sessions on shutdown.
Understand an offer
buyAmount is the gross payment to the pool; netBuyAmount is the private note’s value after the fee. executor identifies the settlement contract. deadline is a Unix timestamp in seconds. Route estimates such as expectedBuyAmount and impactBps are informational.
Handle failures explicitly
AssetNotEnabledErrorChoose a deposit-enabled asset. No deposit was sent.
DepositApprovalErrorHandle the screening decision before retrying; a rejected or unavailable approval does not submit a deposit.
QuoteExpiredErrorRequest a fresh offer. The SDK also rejects offers too close to expiry to finish proving.
UnreviewedAssetErrorReview the buy asset before explicitly opting in with acceptUnreviewedAsset.
TransactionRevertedErrorInspect the transactionHash and sync before retrying.
ArtifactIntegrityErrorRestore the matching proving files and manifest. Do not bypass the integrity check.
Make it your own.
Your keys. Your balance. Your next trade.

