# Orb Wallet

> Current status reviewed September 29, 2026. See [the release index](README.md) for shipped versus planned features, network, wallet signing and deployment boundaries. Older dated experiments below are retained as history.

The Privy login is bound to a user-owned embedded Orb Wallet automatically after authentication. New Privy logins create the embedded wallet; an existing signed-in user without one gets one when opening Wallet. Existing external-wallet identities remain linked for legacy accounts. A Privy-only profile is bound by verified Privy user ID without requiring a separate external login wallet. The binding moves no funds and delegates no signing permissions.

The `orb_wallet_identities` table retains legacy external login identities. The testnet wallet table has unique bindings for the Orb profile, Privy user and spending address, plus outgoing transfer intents/history. The API verifies the embedded wallet belongs to the authenticated Privy user and rejects imported or non-user-signable spending wallets. Where no external identity exists, `identity_address` stores a `privy:<user-id>` marker; it is never treated as an onchain destination. Legacy identity records are preserved.

Wallet includes balances for MON and the configured tokens, a Monad testnet receive QR code, native/ERC-20 sends (including imported token contracts), withdrawal to the login wallet, outgoing transfer history with explorer links, and Privy's private-key export window. It is also the wallet that funds and signs the Perpl perpetual account steps. Full deposits and trading history is available through the explorer, not an internal transaction indexer.

Wallet also has an **Alerts** tab (`components/orb-alerts.tsx`, `app/api/orb/alerts/route.ts`, `lib/trade-alerts.ts`): a member names two assets, a side and a price, and Orb watches the venue's reference ratio for them. This is the same `orb_testnet_trade_intents` table the assistant's `create_intent` tool writes, so a condition set in chat and one set in the form are the same row with the same rules — including `alert_only`: **nothing here ever signs or submits**, and the panel says so beside the price source. Each row answers with the current ratio and how far the line is, or with "no price from the venue" when Perpl cannot price that market, and a crossed line whose evaluator pass has not run yet reads as "at the line · waiting for the evaluator" rather than as fired.

Above that tab sits **Markets you follow** (`components/orb-watchlist.tsx`, `app/api/orb/watch/route.ts`, `lib/watchlist.ts`, `orb_watch_markets` in migration 0036): up to twelve venues symbols a member keeps in their own panel, each row carrying the mark and the day read from Perpl at load time. It is deliberately a separate surface from alerts, because the two ask different things of a member: **following a price needs no bound wallet and signs nothing**, while a watched condition is a line held for a specific wallet. The route refuses a symbol the venue does not list, one profile can neither read nor release another's rows, and the row's bell only appears when that symbol is also one of Orb's own wallet tokens — so the panel never offers an alert it cannot honour. A bell hands the alert form the asset, the collateral and the price on screen; the member still chooses the side and saves. When Perpl cannot be reached the rows stay and say they have no price instead of borrowing one, and a market the venue later drops keeps its row labelled as unlisted — a preference is not a claim, so nothing here is ever presented as a number the network made.

A live quote cannot answer the one question that brings someone back — *what happened while I was not here* — so the panel keeps its own memory of that (`seen_at`, `seen_price`, migration 0037). Opening the tab is an `open`: it answers with the list **as it read before the visit**, showing each row's walk since the member last looked and a total of how many walked, and only then takes the new reference point. A background refresh is a quiet `list` and must never move that reference, or the walk would be re-baselined out from under nobody. Both columns stay nullable: a first visit has nothing to compare against and says so rather than reporting a comfortable zero, and the "worth a glance" threshold lives in `lib/watchlist.ts` (`WATCH_MOVED_PERCENT`) so the browser never re-decides what counted as movement.

Two limits are stated in the interface rather than assumed: an expired condition leaves the list (it is over, not pending), and if no evaluator process is running on the instance the tab reports `evaluator stopped` and names `npm run intents:monitor` — a watched line that can never be judged is worse than an honest one. An alert history is a notification log, not a performance record: nothing here proves a member would have traded, and no win rate should be inferred from it.

The nav marks Carteira with a dot while a firing is newer than what this browser has read; opening Carteira is what clears it. The watermark lives in `localStorage`, so it is a convenience on the device in use, not a delivery guarantee — another tab or phone keeps its own reading point, and `alertsAwaiting` on the shell snapshot only answers for the member who owns those conditions.

The perpetual desk totals what the venue already sends instead of leaving it scattered per row: free margin, and the summed mark PnL of open positions computed with the same formula the table's own rows use, so the headline and the rows cannot disagree. While a position's market descriptor has not arrived the total reads `incomplete` rather than quietly undercounting, and the line names the venue as the source — these are Perpl's numbers, not an Orb performance record.

AUSD (`0xa9012a055bd4e0edff8ce09f960291c09d5322dc`) is now a known six-decimal testnet asset. The Wallet shows its onchain balance, lets the user send it, and links to the Perpl perpetual desk where AUSD is the collateral. This is **not** an AUSD spot market. The **Claim test AUSD** button calls the existing testnet faucet at `0xd236c18d274e54faccc3dd9dda4b27965a73ee6c` from the selected Orb Wallet after checking the chain, contract code, `token()` binding and a successful claim simulation. The user confirms the transaction and pays test MON for gas. Faucet cooldown and amount are controlled by the faucet contract, not by Orb. The browser retains a pending claim until its receipt is checked; an unknown signing outcome requires manual review before retry. No Orb server key mints AUSD.

Transfers are simulated before review and revalidated before sending. Integer token amounts, chain, recipient, encoded calldata and maximum network fee are checked before signing. An increased fee requires another review. For the verified non-imported embedded Orb Wallet, supported reviewed calls use per-call showWalletUIs=false: Orb's review is the explicit user action and there is no second transaction popup. Privy authentication, recovery or MFA can still require interaction. This is not unrestricted automatic transfer authority. No private keys pass through the Orb server.

Reviewed Council, token utilities, mission/economy calls, Perpl vault steps, AUSD faucet claims and ordinary Arena contract actions use a fixed gas limit and gas price, with a ceiling of 0.05 test MON per transaction and sufficient balance for the value plus gas. The legacy manual Arena deployment has a separate 0.35 test MON ceiling. Transfers and Spot retain their own displayed review budget and revalidation. An action that fails simulation, has an unexpected network or exceeds its fee limit is not passed to the wallet signer.

The client serializes sends between tabs using Web Locks, persists the submitting intent before invoking the SDK and saves the returned transaction hash. Unknown submissions block another withdrawal until reconciled. Recorded status comes from the Monad transaction/receipt and is not accepted from a client-provided success flag. This browser recovery journal contains no secrets.

Trading, Council and token utilities select the embedded Orb Wallet without falling back to an external or imported wallet. The AI and monitor have no transfer signer. Community League has a separate bounded, in-memory move session and gas sponsor; it cannot transfer or claim rewards. Perpl has a separately enrolled, expiring browser trade key. Neither is an unrestricted background wallet permission. Holder Pro activation groups the reviewed exact approval/revoke and lock into one user action; each network transaction still has a simulation, fee cap and receipt. ORB is a known asset, with a bounded faucet and a refundable Council stake. See orb-token-testnet.md and orb-council.md.

Validation: `node scripts/test-wallet-role.mjs`, `node scripts/test-orb-app-wallet.mjs`, `node scripts/test-orb-wallet-client.mjs`, `npm run intents:test` (conditions and the market list, against the local preview server), `npm run intents:test:ui` (both panels rendered in a browser with the venue stubbed), and `npm run build`. The local component harness (`work/test-wallet-ui.mjs`) exercises activation, receive QR, review/send, signer selection, mobile layout and interrupted-send recovery with mocked Privy and RPC. No real transfers were executed. Local preview: `http://localhost:3112/?view=wallet`.

Live data: the perpetual desk streams its own account, order and position state over the venue's trading socket, and the chart refreshes on its own request cadence. Neither touches the wallet's signing keys. The Orb Wallet address is the identity the perpetual account and its browser-generated API key belong to, so switching wallets means re-enrolling from that wallet.
