Mist documentation

Your guide
to Mist.

Understand your private balance, make your first trade, and know exactly what stays public.

Get started
01 / The essentials

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.

02 / Your first steps

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.

  1. 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.
  2. Download your recovery file. Keep it somewhere safe and the passphrase somewhere else. Mist asks you to confirm before your first deposit.
  3. Connect your wallet to deposit. It's the wallet you deposit from, and the only place withdrawals can go. Trading doesn't need it.
  4. 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.
  5. Withdraw when you're done. Mist checks whether the amount or timing would point back to one of your trades before you confirm.
03 / Understand the boundaries

Privacy model

Mist doesn't hide that trades happen. It takes your wallet out of them.

What each Mist action makes public and keeps private
ActionPublicNot included
DepositYour wallet, the asset, the amountThe keys that read and spend the note
TradeSolver, assets, amounts, proofYour wallet, who owns the notes
WithdrawYour wallet, the asset, the amountThe keys that read and spend the note
Privacy is more than what's onchain. Quote relays can see the pair, size and timing of a request. Your RPC provider sees what you read and send. Amounts and timing can still give you away: withdrawing the exact output of a trade right after making it is easy to match. Round amounts and a little patience go a long way.
04 / Keep control

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.

05 / Entering the pool

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.
06 / Simple by design

Fees

0.5% of each action's output. Depositing, trading and withdrawing each pay it once. The rate is fixed in the pool.

A full cyclePrices held constant
  1. 01Starting balance$1,000.00
  2. 02After deposit$995.00−0.5%
  3. 03After one trade≈ $990.03−0.5%
  4. 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.

07 / Choose your network

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.

08 / Under the surface

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.

09 / Good to know

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.

10 / Developers

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.

Node.js 22–24Robinhood Chain · 4663Gas in ETH
11 / Solver quickstart

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.

Before you startUse Node.js 22–24 and a local SDK checkout with its workspace dependencies installed using pnpm. Run the commands below from that checkout’s root. Keep your operator directory on persistent storage, outside version control.
  1. Create an operator directoryThe CLI generates separate operator and Nostr keys in mist-operator/.env, with file permissions set to 0600. It prints the public operator address and refuses to overwrite existing keys.
    Initialize the solverShell
    node packages/solver/src/cli.js init ./mist-operator
  2. Configure the RPC and fund the operatorSet RH_RPC_URL in 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.
  3. 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.
    Deploy the solver contractShell
    node packages/solver/src/cli.js deploy-contract ./mist-operator
  4. 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.
    Check the solverShell
    node packages/solver/src/cli.js doctor ./mist-operator
    Start the solverShell
    node 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.

12 / Operator reference

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_URL

Primary RPC for Robinhood Chain, chain ID 4663. Replace the generated public endpoint with your own for sustained traffic.

RH_SECONDARY_RPC_URL

Optional second RPC. Startup checks every configured endpoint against the network and contracts.

SOLVER_PRIVATE_KEY

Generated operator key. Signs quotes, pays settlement gas and receives the contract’s surplus.

SOLVER_NOSTR_PRIVATE_KEY

Separate generated key for your relay identity. It must differ from the operator key.

NOSTR_RELAYS

Comma-separated secure WebSocket relay URLs. Use at least two reachable relays.

OPENOCEAN_API_KEY

Optional API key for the reference solver’s route provider.

Routing and settlement settingsEnvironment
# 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.json

100 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 / stopped

Process lifecycle. A stopped process is no longer listening for new requests.

quote / quote_failed

An offer was sent, or a request could not be quoted. Check the reason for missing routes, minimum-output or gas-limit failures.

execution / execution_failed

A settlement was confirmed or failed. Successful entries include a transaction hash and block number.

transport_error

A relay message or connection failed. Check relay reachability and the reported reason.

13 / Application integration

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.

Amounts are integer base unitsUse 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.

Create and save a recovery fileJavaScript
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.

Deposit and tradeJavaScript
// 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.

Restore and withdrawJavaScript
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
}
14 / SDK reference

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

AssetNotEnabledError

Choose a deposit-enabled asset. No deposit was sent.

DepositApprovalError

Handle the screening decision before retrying; a rejected or unavailable approval does not submit a deposit.

QuoteExpiredError

Request a fresh offer. The SDK also rejects offers too close to expiry to finish proving.

UnreviewedAssetError

Review the buy asset before explicitly opting in with acceptUnreviewedAsset.

TransactionRevertedError

Inspect the transactionHash and sync before retrying.

ArtifactIntegrityError

Restore the matching proving files and manifest. Do not bypass the integrity check.

Keep protocol identifiers and artifacts intactApplication branding does not change signed message formats, domain separators, contract ABIs or recovery schemas. Use the SDK’s canonical encoders and manifest-verified proving files. Renaming these values breaks compatibility with existing keys and contracts.

Make it your own.

Your keys. Your balance. Your next trade.

Open Mist