# How Nullbook works: a guide for buyers and sellers

Nullbook is an over-the-counter (OTC) trading app: a seller and a buyer agree on a price directly. **Markets** uses Birdeye to show what tokens are trading for on Solana mainnet. **Private deals** lets two named traders negotiate and exchange a **mainnet SPL token for SOL**. The market price is a reference; the price that matters is the one both traders approve in their deal.

The exchange uses the existing Helius Solana Privacy Rings program. Neither trader sends their side to Nullbook for safekeeping. Both sign the same Solana transaction, so the token and SOL move together or neither transfer takes effect. Nullbook does not need to deploy its own contract. **Registration, deposits, swaps and withdrawals use real mainnet funds.**

[Make your first trade](#make-your-first-trade) · [Read a deal status](#what-the-deal-status-means) · [Recover funds](#if-a-trader-leaves-or-you-lose-access) · [See what was tested](#what-has-been-verified)

## Where to go in the app

| If you want to… | Open | What you will find |
| --- | --- | --- |
| Research a token | **Markets** → **Search token**, then the token page | Mainnet market data and price comparisons. A listing alone does not make a mint eligible for a private deal. Use **Check mint** on the invitation to verify it. |
| Find buyers or bid on a listed token | **Marketplace** (`/solana/marketplace`) | Anyone can browse. Registered traders can post listings, bid privately and open their selected deal from **My activity**. |
| Invite a buyer, respond to an offer, or check a receipt | **Private deals** (`/solana/deals`) | Your encrypted deal inbox. **Your deals** shows what you are buying or selling and the current revision. |
| Deposit, read shielded balances, or withdraw | **Private wallet** inside **Private deals** | The mainnet wallet you unlock with your backup. The separate **Wallets** page looks up public balances. |
| Look for a past private trade | Reopen the room under **Your deals** | A settled room links to the finalized mainnet transaction. **Offers**, **Positions**, and **History** belong to the separate public-offer workflow; they are not the record of your encrypted negotiation. |

## Which wallet or address do I use?

Each trader uses **two wallets**. Mixing them up can leave you looking at the right deal with the wrong balance.

| Item | What it does | When you use it |
| --- | --- | --- |
| **Connected Solana wallet** (via Privy) | Identifies you to Nullbook and authorizes access to your inbox. | **Share this address with the seller** if you are the buyer. You sign a login challenge and an inbox registration message; neither spends your trading funds. |
| **Mainnet private wallet** (from your encrypted backup) | Owns your funds and signs deposits, exchanges, and withdrawals. Its **Public mainnet address** and its **private balance** are two different places to hold assets under the same wallet. | **Fund your own displayed public mainnet address.** Keep some public SOL there for fees; deposit the amount to trade into the private balance. |
| **Token mint** | Identifies the particular token to sell. Tokens can share a name or ticker. | Enter the agreed **mainnet mint** in the offer and check that same address before signing. A mint address is not a destination wallet. |

For your first mainnet deal, create a **fresh mainnet backup** and register a mainnet inbox. A backup or room from another network cannot be reused; switching networks does not move the old balance.

Your backup and passphrase belong together: the file is encrypted, and the passphrase alone cannot recreate it. Restoring your Privy login does not restore this separate wallet. Keep the file and passphrase somewhere you can recover them, and do not send either to the counterparty.

## Make your first trade

### 1. Both traders: set up the wallet and inbox

1. Open **Private deals** and select **Connect a Solana wallet**. Use your own connected account and private-wallet backup. To practice the screens without moving money, use the [isolated browser check](../tests/private-deals-browser/README.md).
2. In **Private wallet**, choose **Create backup**, enter and confirm a passphrase of at least 12 characters, then choose **Generate encrypted backup** and **Download encrypted backup**. Select the downloaded file, enter the passphrase, and choose **Unlock mainnet wallet**. This checks that you can actually reopen the wallet before funding it.
3. Choose **Open encrypted inbox**, complete the wallet challenge if prompted, then choose **Register inbox** on first use. This binds your connected account to your inbox and private-wallet owner. Both traders must do this before the seller can invite the buyer.
4. Send **mainnet SOL** to your own displayed **Public mainnet address** for registration and network fees. In the wallet panel choose **Register on mainnet**, review it, and confirm the registration. **Register inbox** opens the negotiation channel; **Register on mainnet** publishes the wallet’s public shielded keys so it can receive private payments. Signing up for one does not complete the other.

### 2a. Find a buyer on the marketplace

1. **Seller:** open **Marketplace → Post a listing**. Enter the token mint, choose **Open bids** or **Sealed auction**, and set the deadline. Publishing makes the mint, your connected seller address, format, deadlines and total bid count public. It does not publish a sell quantity, reserve tokens or charge a listing fee. Use **Copy listing link** to share it.
2. **Buyer:** open the listing, unlock your registered inbox, and enter the token quantity in its smallest units and the **total** amount of SOL you offer. For a six-decimal token, `1500000` means 1.5 tokens. **Send encrypted bid** signs and encrypts the proposal locally. Only the seller and that bidder can read the terms. You can withdraw a pending bid. **My activity** lets you return to it.
3. **Seller:** under **Open bids** you can review offers as they arrive. In a **Sealed auction**, the service withholds all bids from you until the deadline; this timing relies on the service. No highest bid wins automatically. You can decline bids, close without choosing, or **Review bid → Choose this bid**. Choosing closes the listing to other buyers and opens one private room; other bidders cannot settle against that listing.
4. **In the room:** the seller can accept the buyer’s proposal or counter its quantity or price. The token mint stays fixed. The deal expires 24 hours after the bidding deadline, regardless of when the bid was chosen. Continue to funding and signing below. A match is not a sale: neither bidding, selection nor accepting terms moves money.

Each listing leads to at most one deal. To negotiate with more than one buyer for separate lots, use separate listings.

### 2b. Or invite a buyer you already know

Under **Invite a buyer**, enter the buyer’s **connected** address, the **Mainnet token mint**, the **Token quantity (smallest units)**, and the **Total price (mainnet SOL)**. The total price is for the entire quantity. Choose **Send encrypted invitation**. The buyer can find it under **Your deals**; **Copy invitation link** also lets you point them to the room. Possessing that link does not grant access.

Use **Check mint** to read the mainnet token’s decimals and privacy registration and preview the number of tokens your base units represent. This lookup is optional while negotiating; deposit and settlement recheck eligibility before signing. The app accepts registered classic SPL mints and plain Token-2022 mints without extensions. A mint’s freeze authority, when present, is shown separately.

For example, if the mint has six decimal places, selling **1.5 tokens** for **0.02 mainnet SOL total** means entering `1500000` for the token quantity and `0.02` for the price. Check the mint’s decimals first: the same integer means something different for a zero-decimal token.

The buyer checks the mint, quantity, price, fee, payout addresses, and expiry. Choose **Accept these terms** to agree, or **Counteroffer** to propose a different mint, quantity, or total price. After a counter, only the recipient of that new revision can accept or counter again. The original 24-hour invitation deadline stays the same. **No tokens or SOL move when someone accepts.**

### 3. Both traders: fund your own side

The seller needs the agreed token; the buyer needs the quoted SOL plus any desk fee. First make the asset available on the **public side of your own mainnet wallet**. Then use **Deposit or withdraw on mainnet** to deposit it:

- **Seller:** enter the mainnet mint and the token amount in base units. The browser requires a token whose mainnet privacy interface already exists.
- **Buyer:** enter `SOL` and the amount in **lamports**. Unlike the invitation’s price field, the wallet deposit field takes whole base units: for `0.02 SOL`, enter `20000000`. Leave additional public SOL available for network fees.

Select **Review public deposit**, check the asset and amount, then **Confirm mainnet deposit**. A deposit publicly reveals the funding movement. After confirmation, choose **Read balances** and look for the amount marked **(private)**. In the accepted room, each person selects **Verify my funding**. This checks the balance; it does not reserve it. Spending those funds elsewhere can still make the deal fail.

### 4. Both traders: verify and sign; either trader: submit

Once both sides have verified funding, each chooses **Prepare my side**. Each wallet prepares proof of its proposed private transfer. After both proofs arrive, the **buyer** selects **Open signing window**.

Each trader reviews the current revision, then selects **Verify payouts and sign**. The wallet checks that its incoming payment matches the agreement, checks any configured desk fee, and simulates the complete exchange before signing. One trader’s signature cannot authorize a standalone payment to the other. A signature for one transaction cannot be reused for a changed payout or a new signing window.

Once both have signed, **either trader** can select **Submit signed exchange**. The buyer pays the network fee for that transaction regardless of who presses Submit. If finalization takes time, select **Check settlement onchain**. Look for **Settled** and **View finalized mainnet transaction**. A signature or a submission notice on its own is not proof the trade landed.

## What the deal status means

| Room status | What has happened | What to do next |
| --- | --- | --- |
| **In negotiation** | An invitation or counteroffer is waiting for the other person. | The recipient reviews this revision and accepts or counters. |
| **Accepted** | Both agreed to the terms. Neither payment has been made by accepting. | Each trader deposits the required asset and verifies funding. |
| **Preparing exchange** | Both marked their sides ready. A proof or signature may still be missing. | Follow the action for your side; the buyer opens the signing window. |
| **Awaiting settlement** | Both approved the same transaction. It may not yet have been submitted or finalized. | Submit it if needed, or check the mainnet receipt if submission was attempted. |
| **Settled** | Nullbook verified a successful, finalized receipt matching both approvals. | Open the receipt and read your updated private balance. |
| **Closed** | A participant closed the room before any settlement packet or approval was recorded. | No exchange took place through that room. Any unspent deposits remain in the owners’ wallets. |
| **Expired** | The invitation deadline passed before both approvals were recorded. | Review any prior signing activity before making a fresh invitation. A room already awaiting settlement can still have its receipt checked after the deadline. |

## Fees and visibility

By default a private deal has **no desk fee**. If a fee recipient is configured, the buyer pays **0.5% of the SOL price on top**, rounded down to a whole lamport. For a quote of 0.02 SOL, that means the buyer needs 0.0201 SOL in private funds; the seller still receives 0.02 SOL. The fee and recipient appear in the review. Public SOL for network fees and account registration is separate from that private amount.

| Who | What they can see |
| --- | --- |
| Anyone browsing Marketplace | The listing metadata and total number of bids submitted, including bids later withdrawn or declined. The public listing does not show buyer identities, quantities or prices. Direct invitations do not appear there. |
| The two traders | The negotiated terms, each other’s account and payout addresses, and their own private balances. With a positive desk fee, the proof used to verify it can also expose the buyer’s change on that side of the transaction to the seller. |
| Nullbook’s coordinator | Who bid on which listing and when; the two accounts in a selected room, their registered mainnet owners, revisions, status, approved transaction digest and final signature. Bid terms, counteroffers and signing packets arrive encrypted. |
| A public chain observer | Signers and timing of the exchange, plus assets, amounts, and addresses on public deposits and withdrawals. Nearby funding movements can give clues about a trade even when its offer and private outputs are encrypted. |
| The Helius prover | Private inputs needed to generate a proof are processed inside an attested enclave. The wallet checks the signed mainnet policy before using it. |

A trader can reuse their private wallet across rooms. That makes one backup useful for recovery, but the public signing address can link activity across deals. Nullbook does not promise anonymity or generate a different wallet for every trade.

## If a trader leaves or you lose access

- **Before either side sends a settlement packet:** either party can use **Close deal without trading** if the button is available. Closing the room neither moves funds nor withdraws an earlier deposit.
- **After preparation or signing begins:** closing the page or locking the wallet does not revoke a signature already shared. If neither side has approved yet, the buyer can **Refresh signing window**. After an approval, do not start a replacement trade on the assumption the first one failed. Check the original receipt when both approvals are available; resolve any submitted transaction and wait out its signing window before reusing the funds.
- **To recover an unspent balance:** choose **Unlock from backup**, reopen the original file, and select **Read balances**. Under **Deposit or withdraw on mainnet**, enter the asset, base-unit amount, and intended public mainnet withdrawal address (blank defaults to the restored wallet’s public address). Choose **Review public withdrawal** and **Confirm mainnet withdrawal**. Your counterparty does not sign. The destination and amount become public.
- **If Nullbook is unavailable:** use the [standalone recovery instructions](mainnet-operations.md#recover-a-private-wallet-without-the-app). The recovery tool uses your backup, passphrase, your own Helius access, and some public mainnet SOL for fees. It does not need Privy, the room, or the other trader. It still needs the underlying mainnet services.

| Problem | First thing to check |
| --- | --- |
| Seller cannot invite the buyer | Ask the buyer for their **connected** address and confirm they completed **Register inbox**. Sending a room link does not register an account. |
| The room will not decrypt after reconnecting | Use the same connected account and the backup originally registered to it. Creating a new backup creates a different wallet. |
| Public SOL is visible but funding verification fails | **Read balances** and check the **private** entry for the exact asset. A public balance pays fees but does not satisfy the private trade. |
| Funding looked sufficient but preparation fails | A balance may have changed, or the usable funds may be split into too many pieces. The current proof builder can use at most eight eligible unspent private notes per trader. Check the mint, registration, and current balance before retrying. |
| A submission timed out | Reopen the room, check the original signature on mainnet, and use **Check settlement onchain**. Do not make a second payment just because the first response was lost. |

## What has been verified

The [mainnet operator check](mainnet-operations.md#check-your-connection-without-moving-funds) verifies connectivity and the attested prover without moving funds. The [browser tests](../tests/private-deals-browser/README.md) cover direct invitations and a marketplace sale with two competing buyers. They verify private bids, exactly one selected room, a counteroffer and acceptance using isolated signers. Neither check is a funded mainnet exchange.

## Set up the app

For local installation, database setup, and Privy configuration, start with the [project README](../README.md#run-locally). The web app needs `NEXT_PUBLIC_PRIVY_APP_ID`; the API needs Helius mainnet privacy access through `PRIVACY_MAINNET_HELIUS_KEY` or a configured Helius RPC project key. Keep provider credentials on the server. Leave `SOLANA_PROGRAM_ID` empty: it belongs to the legacy public-offer program, not the private exchange. The optional web build setting `NEXT_PUBLIC_PRIVACY_MAINNET_FEE_OWNER` selects a registered mainnet fee recipient.

For the browser/API boundary, stored fields, transaction checks, and code map, see the [developer reference](private-deals-technical.md). The [privacy research notes](otc-privacy.md) explain where the Rings OTC workflow and Helius protocol fit into the design.
