# Orb — Metropolis submission checklist

Prepared on October 2, 2026. This is a work list, not a statement that every
item has been completed. No push, publication or GitHub visibility change was
performed when creating this checklist.

## Deadline, sources and priority

The terms sent by the founder, version 3.0 of September 3, 2026, indicate
**October 13, 2026, 23:59 ET**, equivalent to **October 14, 00:59 in São
Paulo**. Check the current version and the countdown on the dashboard before
treating this deadline as definitive. Our internal goal: deliver by **October
12** and reserve the 13th for corrections, without waiting for the last minutes.

Sources: the provided terms, sections 2.4–2.5, 4, 5.2, 7.2 and 9; Orb's local
code and reports; the [Metropolis page](https://monad.xyz/developers/hackathons/metropolis);
the [Rise In page](https://www.risein.com/monad/monad-metropolis-hackathon).
The official page could not be read by the search tool in this review. Rise In
presents the four tracks and the October 13 submission date, but that does not
replace the current terms and the dashboard. We do not treat prizes or
sponsor-specific requirements as confirmed in this checklist.

**P0:** mandatory for a valid submission and a trustworthy demonstration.
**P1:** improves the application. **P2:** commercial production, without
pretending that it already exists.

## What is already prepared in the local checkout

- [x] MIT license at the root and third-party attribution index.
- [x] The README identifies the previous Órbita codebase and discloses the use
  of Codex, Qwen and Qoder.
- [x] Documentation distinguishes existing features, proposals and trust boundaries.
- [x] The local flow for portable community, mission, meetup, approval, payout
  and independent verification is implemented; EN/PT changelog updated.
- [x] The two-wallet journey on local contracts, with simulated Privy sessions,
  passed the tests; fake receipts and receipts from another wallet were rejected.
- [x] Two demonstration EOA wallets funded by the faucet completed 16 public
  transactions on testnet: an invite, a net reward of 9.5 ORB and two swaps on
  the demonstration AMM. See [receipts and limits](orb-testnet-rehearsal-2026-10-02.md).
  Real Privy login, Perpl/Kuru fills and IPFS availability remain pending.
- [x] Targeted tests for permissions, mobile, TypeScript, production build, HTTP
  security and independent-client integrity passed.
- [x] Full local round on October 2: 50 isolated suites, 20 browser journeys and
  30 HTTP/build gate steps approved. One Passport navigation test was updated and
  rerun. See [release candidate report](orb-release-candidate-2026-10-02.md).
- [x] Targeted search for credential patterns: no matches across 883 text files
  and 58 commits. It does not cover every secret format or binary file and does
  not revoke previously exposed credentials.

These items do not mean the latest changes are deployed, an independent audit, a
run with two real Privy accounts or exhaustive testing on every device. The
attribution index still needs its final rights/licenses review.

## P0 / 1 — get the application right

- [ ] Check the current terms, deadline/time zone and every field required on the
  dashboard.
- [ ] Check the team registration, eligibility, primary contact and the data
  requested by the organizer. The provided terms allow 1–5 members and one project
  in a single track; do not infer individual eligibility from this list.
- [ ] Choose **Social, Attention & Culture** as the recommended primary track:
  community is the product; trading, games and contributions support that experience.
- [ ] Settle on a clear promise: "The community meets, does something useful,
  receives a funded reward and takes its identity and proof outside the platform."
- [ ] Explain the initial audience: community/project organizers and their
  members, especially groups that run tests, play and discuss markets.
- [ ] Explain why Monad: coordination and payments in contracts, reviewed actions
  inside the app and records verifiable by another client. Show real measured cost
  and time, without presenting two browsers as a 10,000 TPS test.
- [ ] Define sponsors based on the current requirements and on demonstrable
  integration. Document the APIs/models actually used. `openrouter/free` does not
  guarantee Tencent; having Privy/Perpl/Kuru in the code does not by itself prove
  a specific bounty.

## P0 / 2 — close the testnet release

- [ ] Choose the candidate commit/build and review the local changes, including
  those made by other agents. Freeze new features during validation.
- [x] Run the full gates for the local candidate and log failures/fixes:
  completed on October 2. Later functional changes need the relevant
  verifications; acceptance of the published version remains pending.
- [ ] Rotate previously exposed credentials and revoke old venue keys. Verify that
  secrets live only on the server and that their scopes are limited.
- [ ] Take a consistent backup of Orb, prepare a rollback and publish the release
  candidate only to the **Orb** service/domain and database, separate from Órbita.
- [ ] Apply the Web3 migrations, update the manifests and check the runtimes/chain
  10143. Configure the Privy origins, Perpl relay, RPC and alert monitor of the
  same release.
- [ ] Choose which Arena is demonstrated. If it includes Control v4 onchain,
  publish and validate its corresponding contracts; the local tests did not
  migrate the existing league. Practice must not be described as every move
  verified onchain.
- [ ] Test the public link in a clean window, login, faucets/test balances and
  navigation paths. Check the limits of free services before recording.
- [ ] Actually import/pin the capsule CAR and verify availability through another
  client/gateway. Having a CID without retention is not guaranteed availability.

## P0 / 3 — run with two demonstration accounts

Both accounts may be created and controlled by the founder. We do not need to
recruit customers to demonstrate that the technology works. Privy login/signing
and the testnet transactions must genuinely work in the chosen journeys; the
mocks already tested stay identified as simulation. This validation is our
internal technical criterion, not a requirement from the terms to have two real
customers.

- [ ] Use two demo Privy accounts and two distinct Orb Wallets on **testnet
  10143**. Record wallet binding, logout/login, top-up and recovery without
  exposing keys.
- [ ] One user joins the community; both test chat, call and permissions. Check
  microphone/reconnection on the devices that will appear in the demonstration.
- [ ] Ask the AI for a trade, answer about margin, edit the terms and confirm that
  it keeps the context. The second user does not receive the private conversation
  or the margin.
- [ ] Execute a testnet Spot, verify the receipt/balance and share the card. The
  second member checks the proof and prepares their own quote/amount.
- [ ] Execute a testnet perp if it goes into the video or the bounty: distinguish
  acknowledgement from fill and check positions/SL/TP on the venue itself.
- [ ] Fund a mission → schedule a meetup → submit a public contribution → approve
  with the sponsor → withdraw with the participant. Check the net value and the
  5% fee.
- [ ] Create/link a public identity → sign a capsule → pin → register the CID. A
  member without authority cannot replace the identity or publish as owner.
- [ ] Extract the independent client to another host/location and verify identity,
  capsule and payout with requests to the Orb API blocked **in that browser**.
- [ ] Verify cancellation, insufficient balance, account switch, wrong wallet,
  RPC/AI failure, invalid receipt and top-up during submission. An unknown result
  must not trigger an automatic resend. Do not take production down to test the
  verifier.
- [ ] Play Arena with several people and confirm objectives, end and result. Only
  demonstrate an onchain prize/action when the published version actually records
  it.
- [ ] Keep consented evidence: build, chain, contract addresses, hashes, balances
  and observed times. Distinguish simulated tests, local network and testnet.

Scripts: [contribution and independence](orb-hackathon-rehearsal.md),
[two-person trading](orb-two-person-demo.md) and
[the limits of each claim](orb-product-claims.md).

## P0 / 4 — GitHub and documentation

- [ ] Review the source and history for secrets, personal data, dumps, unlicensed
  media and internal files. Separate experiments stay out of the submission.
- [ ] Commit/push the reviewed version and make the repository public according to
  the rules. Do not change visibility or publish private content without reviewing
  the package. Preserve the real history, without fabricating development dates.
- [ ] Validate the README on a clean install performed by someone else: clone,
  versions, installation, `.env.example`, migrations, build and run, without
  operator secrets.
- [ ] Include architecture and stack; state what runs on Oracle, on Monad, on
  Privy, on IPFS/XMTP and on the trading/AI providers. Include the origin of the
  Órbita codebase.
- [ ] Publish contract addresses, chain ID, artifacts, compiler/configuration,
  instructions and reference transactions. Verify the source on the explorer where
  feasible.
- [ ] Finalize attributions/licenses for code, images, fonts and music. Identify
  resources built during the build window and disclose the AI tools actually used.
- [x] Update the whitepaper of the submitted release: v0.4 (October 5) adds the
  metered assistant, the five interface languages and their coverage gate, the
  abuse limits and the testnet-compliance finding. `npm run whitepaper:build`
  regenerated `public/whitepaper/Orb-Whitepaper.pdf` at 15 pages, one per section;
  the build fails if a section overflows its page.
- [x] Every document is English now. The Órbita legacy record, this checklist, the
  demo video script, the chat-media notes and the Oracle runbook were translated
  rather than deleted, so the pre-existing-foundation disclosure of §4.1.4 stays
  intact, and `npm run check:docs:language` keeps it that way.
- [ ] Update the in-app changelog for the submitted release: its newest entry is
  still September 30 and does not mention the October work.
- [ ] Open GitHub, the demo, the docs and the explorer while logged out and check
  every link.

## P0 / 5 — record the demonstration and submit

- [ ] Record a public video of **at most three minutes**, with the version
  actually working. Use consented accounts and licensed music/art.
- [ ] Show the user and the problem; community/AI/card; contribution and payout;
  verification outside Orb; Arena; the reason for Monad and the revenue model.
- [ ] Time cuts may condense the flow, but must not simulate execution, balance,
  fill, latency or a live response. Record the current UI, without old mockups.
- [ ] Fill in description, category, team, GitHub, demo, links and integrations.
  Confirm the real fields on the dashboard; this list does not invent the form.
- [ ] Register only the bounties that are met and include specific evidence for
  each one.
- [ ] Submit by the internal goal, **October 12**, and keep the dashboard
  confirmation. Reopen the project and verify that the links and fields were saved.
- [ ] Reserve the 13th to fix links/problems. The version at the deadline is the
  one judged; keep the demo and repository accessible during the evaluation and in
  accordance with the terms.

Suggested script for 180 seconds — adapt it to the journey that passed on testnet:

| Time | Scene | Evidence |
| --- | --- | --- |
| 0–15 s | Who uses Orb and why | A community with one concrete activity |
| 15–40 s | Joining and finding people | Login/Orb Wallet and the community meetup |
| 40–75 s | AI trade and shared card | Reviewed draft, real result and another member |
| 75–110 s | Contribution → approval → payout | Mission funding and the participant's receipt/balance |
| 110–140 s | Outside the platform | Capsule and payout verified without the Orb API |
| 140–165 s | Arena in a group | Current gameplay and the result of the version shown |
| 165–180 s | Why Monad and how to sustain Orb | Measured cost/time; fee and Pro as a pilot |

It is not necessary to show every tab to prove that the product works.

## P1 — make the application stronger

User research is optional for the submission. Demo accounts are not customers and
their actions will not be presented as real adoption, retention or revenue.

- [ ] Observe at least ten new users from three communities, with consent, without
  guiding every click. These are pilot targets, not numbers already obtained.
- [ ] Fix the three biggest obstacles observed and repeat the same flow.
- [ ] Measure onboarding, first participation, mission completion, return to the
  next meetup and organizers who want to repeat. Record the denominators.
- [ ] Listen to an organizer, a contributor and a trader: what did Orb solve that
  they do today in several places? What would bring them back? What would they pay
  for?
- [ ] Close out a revenue summary: an explicit fee on approved work in the pilot,
  Pro/coordination tools as a hypothesis; infrastructure and support costs. Test
  tokens, locks and treasury are not commercial revenue nor proven profit.
- [ ] Prepare current screenshots, logo/brandkit and a short pitch in English.
- [ ] Rehearse questions about trust, hosted dependencies, Passport, IPFS,
  custody/recovery, fees, retention and why Monad. Answer with real evidence.

## P2 — before accepting real value

- [ ] Independent review of wallets, contracts, permissions and E2EE.
- [ ] Backup restored, rollback executed, monitoring/alerts verified and Oracle
  capacity tested with representative load.
- [ ] Physical compatibility and accessibility; a plan for incidents and support.
- [ ] Commercial/regulatory review of token, payments and terms; treasury controls
  and mainnet launch as separate decisions.
- [x] Product decision taken October 5: replacing Kuru Spot with a swap aggregator
  plus a disclosed swap fee is deliberately **post-hackathon**. No aggregator with
  verifiable Monad testnet liquidity could be confirmed, Kuru is the wired and
  tested Monad-native venue, and a swap fee would need either an Orb router
  contract (a new custody surface; today the trading key never leaves the browser)
  or an aggregator referral programme. Conditions if it is ever built: verifiable
  liquidity on the target network, the fee shown before signing, and no Orb custody.

These commercial requirements are not a claim that the hackathon demands a formal
audit. Submitting a testnet prototype does not authorize calling it invulnerable
or audited commercial production.

## Internal criterion for ready to submit

**P0 complete**, published build identified, two demo accounts passed the chosen
journeys, reproducible proofs/receipts, a clean install, public source with
license/attributions, an accessible video ≤3 min and a saved submission
confirmation. P1 strengthens the product and the application; the number of
features does not guarantee a win.

The provided terms distribute the main evaluation across five 20% criteria:
quality/completeness, technical excellence, Monad integration, fit to the
problem/track and innovation/impact. The best use of time is closing out this
evidence.

## October 4 update — what has changed since this checklist

**Confirmed track: 03 — Social, Attention & Culture.** Orb's primary user is a
community member, not a trader; trading is a function inside a social product.
Keep a single track (§2.5).

**New evidence, measured on this date:**

- `npm run onchain:test` publishes the history, the manifest and a shared channel
  as **ciphertext inside `Archived` events** and recovers everything with only a
  wallet, a recovery secret and an RPC — no index, no block host, no gateway, no
  Orb API, no browser storage and zero HTTP requests. Readable text never goes
  into an event.
- Compression before encrypting: **20,595 bytes of plaintext → 4,771 bytes of
  encrypted block (4.3×)** on varied text, measured by `chat:scale:test`.
  Ciphertext does not compress, so this is the only place where the ratio exists.
- Full batching at the contract ceiling: **5 transactions → 2, and 846,064 →
  723,025 gas (-14.5%)**. The floor is ~39.5 gas per byte of ciphertext;
  scheduling does not remove that.
- Recovery by **passphrase** that does not depend on the wallet signing the same
  way forever; the suite produces a second valid signature from the same key,
  shows the old factor failing and the new one working.
- A keeper with a storage preflight and a **disk guard**: it refuses to write
  below `ORB_CHAT_MIN_FREE_BYTES` (2 GiB default), because on a host where web,
  proxy and media share the filesystem, ENOSPC takes everything down.

**Operational note — one host, one disk:**

- Web, proxy and media share a single volume, so a full disk stops all of them.
  Disk headroom is an operator task kept in private notes, not in this repository;
  what the code does about it is below: uploads are refused under `ORB_MIN_FREE_BYTES`
  (1 GiB default), encrypted blocks under `ORB_CHAT_MIN_FREE_BYTES` (2 GiB), and
  accounts without a verified wallet have small upload allowances.
- **Build off the host.** A production build on the same disk that serves the site
  can exhaust it; build elsewhere and ship the release.
- The published release **now contains** the independence work. Re-measured
  October 5: `/proofs`, `/independent`, the chat manifests and
  `orb-independent.zip` all return 200, and the live `bundle-manifest.json` is
  identical to the working tree (313 files, 0 differing digests). The public origin
  is the **October 4** build, so everything committed after it is still local-only:
  the metered assistant, the `.nad` removal, the five-language interface and its
  gate, the per-IP anonymous limits, whitepaper v0.4 and the two documentation
  checks. The demonstration video must be recorded against a build that contains
  the work it shows - the local preview does, the published site does not yet.
- Release cleanup needs care: releases can share one dependency installation through
  symlinks. Map what each release links to before pruning an old one.

**Bounties — watch out for Tencent's:** the form shows *Kepler Plan by
Tencent* asking for a link. Orb's inference runtime uses **OpenRouter free**, not
a guaranteed Hunyuan model (the README already says so). Selecting that bounty
without running Hunyuan would be a false representation (§8.2/§10). Do not select
it, or genuinely integrate it first.

**Still depends on the founder alone:** public repository + pushing the local
commits, recording the videos (technical ≤3 min and pitch ≤2 min), the final
choice on the form and submitting. None of this is delegable without credentials.

## October 5 update — the mainnet question, answered by the terms

**It does not have to be mainnet.** The terms the founder sent say twice, in
plain words, that testnet is acceptable:

- §4.1.3, *Monad Integration*: "Clear explanation of how the project leverages
  Monad; Contract addresses (if applicable); and **Deployment to Monad mainnet or
  testnet**."
- §9.2, *Monad Integration Requirements*: "All projects must: Provide contract
  addresses or transaction hashes; **Use Monad mainnet or testnet**; Clearly
  document blockchain integration purpose; Demonstrate why Monad's capabilities
  are utilized."

§3.3 repeats the same thing ("a working product deployed on Monad", without
qualifying the network). Therefore Orb on **testnet 10143** meets the requirement
as written, and a form field or dashboard header showing another chain ID (for
example 143) is not a requirement that every project live on mainnet — it is
dashboard metadata. It is worth checking on the form whether there is a mandatory
chain ID field and filling it in with **10143**.

Migrating to mainnet would be counterproductive nine days from the deadline: the
deployed contracts are fixed to 10143, every attestation epoch and every
encrypted archive root already written there would be orphaned, and mainnet would
put real value behind keeper keys that today are unfunded test accounts. It would
be a re-deploy plus a re-audit, not a configuration change.

**Addresses and hashes already published** (the P0/4 item about publishing
addresses): the public manifests carry chain ID, addresses and deployment
transaction hashes — `public/token/deployment.monad-testnet.json` (ORB
`0x439D1DA1a1A1cf438EcDABbb29c1412E7250a9F0`, council, missions),
`public/independent/deployment.json` (registry
`0x0f40156299Fb43979DB0F602b5287bab41Df4B59`, missions
`0x24d8c98A601270c26B07457EC8Aa22F4BaDB8595`, with the two deploy transactions),
`public/independent/chat*.deployment.json`, `public/economy/deployment.monad-testnet.json`
and the artifacts in `public/arena/*.json`. `docs/orb-testnet.md` also lists MON,
USDC, WMON and Perpl's AUSD collateral. What remains open on that item is source
verification on the explorer and publishing the new manifests on the live site,
which today returns 404 for them.

**What the terms require and the repository already delivers**, checked file by
file: an OSI license at the root (`LICENSE`, MIT — §4.1.1 and §7.2), attribution
of external code (`THIRD_PARTY_NOTICES.md` — §4.1.1 and §7.3), installation
instructions in the README (§4.1.1), identification of the pre-existing Órbita
codebase and disclosure of the Codex, Qwen and Qoder AI tools in the README
(§4.1.4, which requires that disclosure explicitly), and project/architecture/
stack/setup documentation (§4.1.5) in `docs/`. Do not mark as complete what
depends on publication: §4.1.1 asks for "commit history covering the build
window" in a **public** repository, and the history is still local.

**Two limits from the terms worth remembering before recording:** §5.3 forbids
including private keys, credentials or third-party personal data in the
submission (`node scripts/check-submission-secrets.mjs --history` remains the gate
before any push), and §9.4 requires the video to show the product **in real
operation**, not a mockup or a slide, with visible blockchain interaction — which
reinforces the corrected note above: record against a build that contains what the
video claims. The public origin now serves the independence work, but it is still
the October 4 build, so today's changes are only on the local preview.

## October 9 update — built, measured, and what only the founder can do

**Built since October 8, all local and uncommitted.**

- Upload security pack: a per-address daily budget, magic-byte checks, a disk floor, `sharp` 0.35.5.
- Open Ledger: a community's text history on Monad, private (sealed to a key only the owner holds) or public.
  Contract, keeper that journals every transaction, owner panel, reader command, an Open Ledger tab in the
  independent client, a published protocol, and a benchmark. See [docs/orb-ledger.md](orb-ledger.md).
- Holder Pro quadruples a ledger's daily write allowance; whitepaper v0.5 (what ORB is for, the ledger).
- Judge mode: `/judges`, a five-minute path and a live reading of the chain. See [docs/orb-judge-guide.md](orb-judge-guide.md).
- NFT profile pictures: the mark shows only while the verifying wallet still holds the token, and the picker
  lists a wallet's tokens in one collection without an indexer key.
- README: architecture, stack, why Monad, and the 15 contract addresses (each has code on Monad testnet).
- Open Ledger reader as a page: integrity checks first, then the community laid out like the chat it came from,
  search, a shareable link, downloads, and a "Saved on Monad" chip for members. Judge mode links the demo community.
- Opt-in tips on feed posts (wallet to wallet, counted after the chain confirms them): [docs/orb-tips.md](orb-tips.md).
- Discord import: DiscordChatExporter JSON into read-only channels, then onto Monad: [docs/orb-discord-import.md](orb-discord-import.md).
- Faster first load: the translation catalog is fetched only for Spanish, French and Chinese, and the screens are
  fetched when first opened (home JavaScript 1.84 MB to 1.24 MB over the wire).

**Track and bounties.** Track 03, Social, Attention & Culture; a submission competes in one track only (§2.5).
The community is the product and trading is a function inside it, which is what Track 03's wording allows.
Bounties are optional and scored mostly on the sponsor's own requirement, so each one is chosen on the
dashboard only after reading it. [docs/bounties.md](bounties.md) lists what the repository can evidence and
what it cannot; Mera, Qwen, Kimi and Hunyuan are not selectable honestly.

**Checks run on October 9.**

- `npm run check:production`: 58 of 58 offline suites, including those added for the ledger, the keeper, NFT
  ownership, the collection lookup, the judge page, tips and the Discord import.
- `npm run check:production:ui`: 23 of 23 browser suites with the real components, three of them new (tips, the
  importer, the archive page). Two older suites had gone stale and were corrected: the passport suite's fixture now
  stands in for the split `api` module, and the social suite's live-room fixture carries the members the home screen
  reads.
- The Open Ledger contract was deployed to Monad testnet and rehearsed there: the sample community was saved by the real
  keeper, read back complete with the consent verified, and stopped (four transactions, see "A rehearsal on the real chain"
  in [docs/orb-ledger.md](orb-ledger.md)).
- `tsc`, `next build`, `npm run i18n:test` (every string answered in five languages), and the documentation
  reference and language gates.
- `node scripts/check-submission-secrets.mjs --history`: 1,048 files and 87 commits, no findings.
- `npm run ledger:bench` on 100,000 messages: see the measured table in [docs/orb-ledger.md](orb-ledger.md). It also
  showed that a 100,000-message history takes weeks at the default daily allowance, which the owner's panel
  now says.
- The audit found and fixed: free accounts could have spent the scribe's daily fee ceiling on empty ledgers (a
  cap per owner and per Orb), a slow RPC could have held the feed (the NFT check now waits a moment and lets the
  read finish by itself), and a roster of thousands was rebuilt on every keeper pass.

**Only the founder can do these, in this order.**

1. Apply migrations 0052 to 0056 to the production database and run `npm install`.
2. Rotate the Privy app secret and the OpenRouter key; the public documents said they had been disclosed.
3. The Open Ledger is deployed (`0xA6DE00A98b0f3a191992D1099d48a62905b64eD0`, 9 October 2026, from the testnet deployer account). Set `ORB_LEDGER_CONTRACT`,
   `ORB_LEDGER_SCRIBE`, `ORB_ATTESTATION_CONTRACT` and `ORB_JUDGE_INVITE` on the server (the first two are already in
   `.env.arena.local`), decide whether the keeper gets its own scribe key (see "Where it is deployed" in
   [docs/orb-ledger.md](orb-ledger.md)), and start both keepers
   with `--publish` (`npm run attestation:keeper:publish`, `npm run ledger:keeper:publish`). The two keepers have
   systemd units that keep their keys apart from the web process: see "Keepers" in `deploy/orb-oracle/README.md`.
4. A demonstration mission is already open on the Missions contract (mission 2, 5 slots of 2 test ORB, until 6 November
   2026; [transaction](https://testnet.monadscan.com/tx/0x8b0db0e8bc9eda249bac05d64ca86be3aff1f55f75f8fc349fcd53a9d91fd981)), and a public demo ledger exists on chain (`public/ledger/demo.json`), so the judge page has
   something to show even before this step. Still to do on the live host: create the demo community, bring in the sample
   history (**Use a sample** in its settings), turn on a public ledger, and set `ORB_JUDGE_INVITE`.
5. Rebuild the independent client so it embeds the ledger address (`npm run independent:build`; done locally on 9 October, redo it on the release host if that is where the client is built).
6. Deploy this build to the public host, then open `/judges?operator` and fix what it says is missing.
7. If the Perpl or Kuru bounty is selected, make a real testnet order or swap and keep the transaction hash.
8. Make the repository public and push, record the video (three minutes at most, real operation, visible chain
   interaction, §9.4), put its link in the README, and select the bounties.

**What to say plainly in the video and the README.** The Open Ledger's keeper reads the text before sealing it,
so private means private from the chain and from every reader without the code, not from the operator. A
deletion marks a message removed but cannot erase what an earlier entry already holds, and in a public ledger
that text stays readable. The filter that removes keys and contact details is a safety net. A long history
reaches the chain at the daily allowance, which can take weeks.

## October 9 update, evening — the demo on the real chain, the video and the text to paste

**On Monad testnet, sent from the testnet deployer account** (its key stays in the ignored `.env.arena.local`, and no script prints it):

- A public demo ledger, `Orb Builders (demo)`, `0xe6e2…3028` on the Open Ledger contract: the real keeper wrote the community's description
  and the 28 messages of the invented sample, then one message written live while the video was recorded
  ([entry 3](https://testnet.monadscan.com/tx/0xd5ac3e0eea926a6feb7d38052da7b51d67a91b509d6777aa1551afea312a496b), block 69602865). Every transaction is listed in
  [docs/orb-ledger.md](orb-ledger.md#the-public-demo-ledger); the record the judge page reads is `public/ledger/demo.json`. A first demo
  ledger made earlier the same day was replaced and stopped (the same document says why).
- A demonstration mission on the Missions contract (mission 2, 5 slots of 2 test ORB, open until 6 November 2026), so `/judges` has
  an open mission through the judging window.

**The video** is `outputs/demo-v3/orb-demo.mp4` (2:34, 1080p; `orb-demo-without-import.mp4`, 2:11, leaves out the Discord import). It shows the
Open Ledger path in depth and then the feed, missions, token and Council screens; the form may also ask for a separate pitch video of up to two
minutes, which this is not (a script is in [docs/orb-demo-video-script.md](orb-demo-video-script.md)). A synthetic voice reads the script, and the recorder draws only the
captions, two cards and three panels that echo real output (the keeper's own text, the RPC's receipt, a refused connection);
[docs/orb-demo-video-script.md](orb-demo-video-script.md) says what is real and what is prepared. It still has to be uploaded somewhere
public, and its link put in the README and in the form.

**The text to paste** into the form is in [docs/orb-submission-text.md](orb-submission-text.md).

**Found and fixed while recording.** The owner's Open Ledger panel ticked every channel that could be saved instead of the ones being
saved, so a community could look as if it saved a channel it did not (a test now covers it). The recorder's own rehearsal also showed that
the keeper compares addresses in lowercase and leaves a ledger alone otherwise. The first live take showed a third thing: a ledger tells
channels apart by id, and the recorder's disposable copy of the community had different ids, so the live message appeared under a
second `#general`. That take's ledger was replaced by a clean one and stopped, and the recorder now gives the copy the ledger's channel
ids and can resume a take without a second transaction. A rehearsal or a take against the real production build found all of them;
no test had.

**Still only the founder's** (this updates items 1, 3, 4 and 8 of the October 9 list):

1. Deploy this build to the public host: the live origin is still the October 4 build. Apply migrations 0052 to 0056 and run `npm install` first.
2. Set `ORB_LEDGER_CONTRACT`, `ORB_LEDGER_SCRIBE`, `ORB_ATTESTATION_CONTRACT` and `ORB_JUDGE_INVITE`, start both keepers with `--publish`, and give the Open Ledger
   keeper its own key before a real community turns the ledger on (the demo ledger's scribe is the shared testnet deployer key).
3. Make a demo community on that host: **Use a sample** in its settings, then a public ledger (the host's own copy, not the recorder's disposable one).
4. Upload the video, put its link in the README and in the form, and fill the form from [docs/orb-submission-text.md](orb-submission-text.md).
5. Rotate the credentials the public documents say were disclosed, run `node scripts/check-submission-secrets.mjs --history`, push, and make the repository public.
6. Top up the deployer account from the faucet: it holds about 0.58 test MON after this work (an entry costs about 0.007), enough for the demo but not for many communities.

## October 10 update — apps, Council v2, the trade desk and one English

**On Monad testnet, sent from the testnet deployer account** (two deployments, both read back and compared with the published build before anything else touched them):

- **Orb Bot Registry** `0x4dc0f7105D872273492f8Ac486e78e7A28Ed3d54`: who registered an app, a hash of its manifest, the scopes a community accepted and an
  optional one-time price in ORB (95% to the developer, 5% to Orb, in code). [docs/orb-bot-registry.md](orb-bot-registry.md).
- **Orb Council v2** `0x00C23Dc1c24a67e35d929146e9cFB8aFd54ec82F`, replacing `0xDA0FC8B2…fbc7` before it held a single proposal: a voice is the square root of the ORB locked,
  with anti-sniping, withdrawal before the first vote and a spam flag. [docs/orb-council.md](orb-council.md).

**What changed in the product** (all in the working tree, none of it committed or deployed): a Discord-style apps platform (listings, invite links, a directory, long-poll or
webhook delivery, commands, an SDK, `/developers` and `/apps/<id>`); the registry's client, server gate and screens; the assistant as a conversation with streaming answers and market
reads that cost no model call; the trade desk's chart overlays; keyboard play and quieter polling in the practice Arena; English as the canonical language of the API, the commands and the
server's messages, with Portuguese a translation; the whitepaper at v0.6; and the revenue lines written down in [docs/orb-product-loop.md](orb-product-loop.md#how-the-pieces-pay-for-each-other).

**Verified on this tree:** `npm run check:production` 66/66, `node scripts/check-production-http.mjs` 31/31 on a fresh production build, `node scripts/check-production-ui.mjs` 26/26, `tsc`,
`npm run i18n:test` (3,172 strings in three languages), `check:docs:language`, `check:docs:references` and `check:source:language`.

**Found and fixed on the way.** Creating a practice Arena match before a rival had joined crashed the screen, because the rival's base is on the map from the start and its team is not
listed yet (the screen test for it is `scripts/test-arena-practice-ui.mjs`). A passport gate named the kind of proof in Portuguese in an English sentence. The public pages I added had no
nonce CSP because `proxy.ts` lists the routes it covers, and `test-production-security` caught it.

**Still only the founder's, in addition to the list above:**

1. Apply migrations 0057 (the voice stage, another change), 0060 and 0061 before deploying this build.
2. Register one app on the registry from the Apps tab of a community (about 0.017 test MON from that account's Orb Wallet) so the judge page's *App registry* row is green and a judge can read a real record.
3. The video's Council scene predates Council v2 (see [docs/orb-demo-video-script.md](orb-demo-video-script.md) for how to record only that scene again).
4. The deployer account holds about 0.19 test MON after these two deployments: top it up before any further deployment.
5. The voice stage's messages (`lib/stage.ts`) and the permission labels (`lib/permissions.ts`, the role editor) are still Portuguese: they belong to the change that is writing them, and `scripts/test-source-language.mjs` lists them as pending.

## October 12 update — the cut that goes in, and the order the rest has to happen in

**The video is final:** `outputs/demo-v3/orb-demo.mp4` — **2:42**, 1080p25, H.264 + AAC, with `subtitles.srt` beside it. It supersedes the
descriptions of October 9 and 10 above. The submitted cut leaves the Discord import out and adds two beats to the ledger path: the **voice stage**
(a second browser joins, is seated in the audience with its microphone zeroed by the server, raises a hand, and the host admits it from the queue)
and **`/developers`** (the bot API, the scopes a community accepts, the SDK, the registry's own record on testnet). The entry on chain is still
entry 3 of the demo ledger: the new beats were recorded with `--resume --live`, which touches nothing on chain.

**Found and fixed while making it.** The database the take was resumed on predated the stage migration, so every read returned 500 with
`no such column: r.stage`; migrations 0057, 0060 and 0061 were applied to it. A take that ends by closing a browser used to carry about 25 seconds
of the last frame into the cut, because the recorder's locator for the request queue stopped matching once the count disappeared; the button is now
held by its prefix, and an open request list can be closed again (`components/voice-stage.tsx`). The in-app changelog dated the voice stage
October 9; it shipped on October 11.

**What is left, in the order it has to happen — all of it the founder's:**

1. Push the five local commits and make the repository public, after `node scripts/check-submission-secrets.mjs --history`.
2. Deploy this build to the public host: the live origin is still the October 4 build, so everything the video shows is not yet on the public URL.
   `npm install`, migrations 0052 to 0061, `npm run independent:build`, then `ORB_LEDGER_CONTRACT`, `ORB_LEDGER_SCRIBE`,
   `ORB_ATTESTATION_CONTRACT` and `ORB_JUDGE_INVITE`, and both keepers with `--publish`. Build off the serving disk.
3. Upload the video, put its link in the README and in the form. Nothing links to it yet.
4. Rotate the credentials the public documents say were disclosed, run `node scripts/check-submission-secrets.mjs --history`, and make the repository public.
5. Fill the form from [orb-submission-text.md](orb-submission-text.md): track **03 — Social, Attention & Culture**, chain **10143**, and only the bounties that are
   genuinely met — not Tencent's, because inference runs on OpenRouter free.
6. Top up the testnet deployer (about 0.19 test MON) before any further deployment.
7. Optional, and only after 2: register one app on the registry from a community's Apps tab, so `/developers` and the judge page show a real
   record instead of `APPS REGISTERED 0`.

## October 12, later — the host now serves this build

**Deployed and verified from outside.** `/srv/orb/current` is `releases/20261012-stage-v034`, built on the box (npm ci, build, migrate, prune).
Migrations **0052 to 0061** were applied to `/srv/orb/data/orb.db` after a copy to `orb.before-20261012-stage.db`, which was then opened and checked
(`PRAGMA integrity_check: ok`, 4 communities, 6 members, 10 rooms, 5 messages). `orb`, `caddy`, `orbita`, `livekit` and `orb-intent-monitor` are
active; `/`, `/proofs`, `/developers`, `/judges`, `/independent`, `/missions`, `/council` and `/token` answer 200, and the API still creates an
anonymous visitor session and refuses a member action without one. Rollback is one symlink back to `20261004-independence-v033` plus a restart.

**A runbook bug, found and fixed.** `deploy/orb-oracle/build-release.sh` never built the recovery kit, and Next only serves what was in `public/`
when it built — so `/independent/orb-independent.zip` was 404 on the public host, including on the October 4 release that judges may have visited.
The script now runs `npm run independent:build` before `npm run build`; the kit is live at 7,555,935 bytes.

**Public values now in `/etc/orb/orb.env`:** `ORB_LEDGER_CONTRACT`, `ORB_LEDGER_SCRIBE` and `ORB_ATTESTATION_CONTRACT`. Disk went from 7.1 GB free to
4.1 GB; the October 4 release and its `node_modules` were left in place, so the rollback still works.

**What the deploy did not solve, and needs a person with the credentials:**

- `ORB_JUDGE_INVITE` is still unset (`/api/orb/demo` answers `{"demo":null}`), because none of the four communities on this host is a demonstration:
  they are named "Orb" and hold between 0 and 5 messages. Make one on the live host — rename it, **Bring your Discord history** with the sample, turn
  on a public ledger — then put its invite code in the env and restart.
- **The Open Ledger keeper now runs on the host.** `orb-ledger-keeper.service` is enabled and active in `--publish --loop` mode, reading
  `/etc/orb/ledger-keeper.env` (root-only, 0600) and the web environment for the contract and scribe. Its first dry run on the box reported the
  expected `opened 0`: nothing is queued until a community turns a ledger on. Note what that means for custody — the scribe key installed here is the
  **shared testnet deployer key**, the same one that signed the demo ledger; the README says a real deployment should give the keeper its own key, and
  that is still owed before any community beyond the demonstration uses the ledger.
- The Privy app secret and the OpenRouter key are still the ones the documents say were disclosed.

## October 12, later still — a passport label, and a disk that ran out

**The host now serves `releases/20261012-passport-v035`.** Built the same way, with `node_modules` symlinked to the previous release instead of
installing another 2.5 GB, and only the build-time dev packages added. `orb` is active, `/proofs` and the recovery kit answer 200, and the
October 12 stage release stays one symlink away as rollback.

**What v035 fixes.** The Passport panel compared epochs by id and called both anchored epochs `DIVERGED`. The host's database restarted its
attestation chain after the September anchors, so its epoch 1 and 2 share a number and nothing else with the ones on chain — the windows differ by
about eight days. `DIVERGED` says Orb changed a proof the registry already holds; that is not what happened, and the panel now says
**"another ledger"** and shows which window each side holds. Verified by running the live API payload through the shipped comparison: epoch 1 and 2
`unaligned`, epoch 3 `waiting to anchor`. The comparison lives in `lib/attestation-epoch-state.ts` with all seven states covered by
`scripts/test-attestation-ledger.mjs`.

**The disk nearly took the host down.** Building the release filled the root volume to **145 MB free**, with Orb, Órbita, Caddy and LiveKit sharing
it — the condition where an ENOSPC stops all four. Reclaimed **3.1 GB** without deleting a release or a byte of data: `.next/cache` in every release
(1.5 GB of build cache, regenerated on the next build), the npm caches (557 MB), the staging tarballs in `/tmp`, and the journal vacuumed to 40 MB.
Back at **3.3 GB free (89%)** with all six services active. Two things follow: a build on this box costs roughly 3 GB, so the free space has to be
checked before one and not after; and the two oldest releases still hold about 2.5 GB of `node_modules` each, which is where the margin would come
from — but only after mapping what symlinks into what, because releases have shared a dependency tree before.

## October 13 — the deploy broke wallet connection for about half an hour, and nothing complained

**What happened.** The two October 12 releases were built by calling `npm ci` and `npm run build` directly instead of running
`deploy/orb-oracle/build-release.sh`, which sources `/etc/orb/orb.env` first. `NEXT_PUBLIC_PRIVY_APP_ID` is inlined into the client bundle at build
time, so the deployed bundle carried no Privy app id: every wallet surface said "Wallet connection is not configured" — the founder noticed it on
`/council`, not any check of mine. The server was healthy by every measure I had taken: service active, all routes 200, no journal errors.

**How it was proved and fixed.** Grepped the built chunks for the app id the server's own env file holds: release `20261004-independence-v033` had it,
`20261012-stage-v034` had zero occurrences, so the regression was mine and not pre-existing. `20261012-passport-v035` was rebuilt with the env sourced
and now has it in `static/chunks/1vmx_t7njxxiz.js`, fetched and verified **over the public URL** rather than from the disk. `/council` 200, `orb`
active, 3.0 GB free.

**The lesson, written into the runbook.** A green route check does not prove a client-side configuration reached the browser. `deploy/orb-oracle/README.md`
now says to build through the script and to verify a deployment by fetching a built chunk from outside and grepping it for the public id. Any
future deploy should get that check before it is called done.

## October 13, later — the demonstration community is on chain, and one click gets a judge in

**The community exists on the live host.** `Orb Builders (demo)`, server `5c08b13d-73a5-41fa-8bdd-9d2c4bd1cc19`, invite
`02b5f8caf80c4f9fb0a193efe032b5d8`, owned by a profile named "Orb team" that was created for this purpose. It carries the invented history
(**Bring your Discord history** → **Use a sample**), and its Open Ledger was turned on in **public** mode with the consent signed by that profile's
embedded wallet. The keeper on the box wrote it: ledger `0xd034c246557cab19564a8e8f02e2c700aefe468f3128c43b5016cd5583fae31e`, open transaction
`0x531b2bf5…a9537`, then entries `0x040f7291…8d14a5` and `0xec17c16e…1df179` — **28 messages, 5,706 bytes, 0.062 test MON of gas**, all three
transactions confirmed.

**Read back with no Orb server.** `node scripts/read-ledger.mjs --ledger 0xd034c2…e31e` against the public RPC and nothing else — no account, no
database, no host — reported `Complete and intact · 2 entries · public · consent verified` and wrote a 41-line transcript of the community into the
gitignored `work/` directory. That is the claim the submission makes, demonstrated rather than described.

**`ORB_JUDGE_INVITE` is set.** Appended to `/etc/orb/orb.env` (previous copy kept as `orb.env.before-judge-invite-20261013`) and `orb` restarted:
`/api/orb/demo` now answers with the community name and invite, so the home screen offers one-click entry to someone with no community.

**Two things cost an hour each and are worth writing down.**

- **One Orb Wallet per Privy account.** `orb_testnet_app_wallets` keys `privy_user` as unique, and a *fresh browser context* logging into an existing
  Privy account creates a *new* Orb profile rather than reusing the old one. The first profile to open the Wallet tab claims the embedded wallet, and
  every later profile of that account then fails with "This Orb wallet is already assigned to another profile" — which surfaces in the Open Ledger
  panel as "Set up your Orb wallet first", pointing at the wrong thing entirely. Two funded testnet wallets are stranded that way on this host, and two
  empty communities named `Orb Builders (demo)` (`63486481…`, `e8962f7c…`) are left over from the attempts. The rule that works: login, wallet binding,
  community, import and ledger in **one** pass on **one** persistent browser profile.
- **Privy asks before it signs.** Every embedded-wallet signature raises a "Sign message" modal with a **Sign and continue** button. Until it is
  clicked the signature promise neither resolves nor rejects, so the panel keeps its button disabled with no error text and looks broken. Automation has
  to click it; a person will never notice, because it is the approval screen doing its job.

**Still owed, in order:** test MON in `0x5ed9CE4c…b7769` (the wallet the demonstration profile can actually spend from) so the Privy and Perpl
recordings can send anything; the two recordings cut to under two minutes; the repository made public; and the Privy app secret plus the OpenRouter
key rotated, which the documents already say were disclosed.

## October 13, evening — the funded wallet found a broken transfer, and it was never the wallet

With test MON in place, the first real transfer from Orb Wallet failed on the public host: the RPC refused the broadcast with *"Exceeds transaction
gas limit"*. Decoding the raw transaction the wallet had been told to send gave `gasLimit = 237,149,237,300`, while the quote on screen said 77,204
and the block gas limit is 150,000,000. `237,149,237,300` is `0x3737323034` — the characters `7`, `7`, `2`, `0`, `4` in ASCII. The server quotes gas
as **decimal strings**, `checkedTransferTransaction` in `lib/orb-wallet-client.ts` handed them straight to Privy's `sendTransaction`, and viem treats
a non-`0x` string as text rather than a quantity. Every ERC-20 and MON transfer from the wallet was affected; the AUSD and ORB claims survived
because those call sites pass bigints.

The fix is at the boundary that already validates the intent: it now returns `value`, `gasLimit` and `gasPrice` as hex quantities, and
`scripts/test-orb-wallet-client.mjs` asserts each of them matches `/^0x[0-9a-f]+$/` so a decimal string cannot reach a wallet again.

**What it says about the checks so far.** This path had been exercised with an injected wallet, which forgives decimal strings, and never end to end
with the embedded wallet that Orb actually tells people to use. A member-facing action was broken on the public host and no suite noticed.

## October 13, evening again — the assistant answered in the interface's language, not the member's

Asked in English on a Portuguese interface, the trading assistant replied in Portuguese. The model's own sentence was fine — the system prompt
already says *"Reply concisely in the same language the user wrote"* — but everything the **server** says on the assistant's behalf came from
`body.locale`, which is the app's language switcher: the out-of-scope denial ("Sou o assistente de trading do Orb…"), each progress line
("Lendo o mercado de BTC…") and the line under a drafted ticket. A member who wrote English got an assistant that spoke Portuguese around an
English answer.

`lib/trade-assistant.ts` now decides that with `speakLocale(message, locale)`: Portuguese markers outweigh English ones at equal counts (the
English words on the list are short and ambiguous, and "funding do BTC" means Portuguese), an English marker wins alone, and a message with no
signal at all — a bare "10", "Tell me a joke" — keeps the language the person chose for the app. `scripts/test-assistant-scope.mjs` covers both
mismatches, which is where the two bugs in the first version of that rule came from: `explic\w*` never matched "Explique", and an ungrouped
alternation in the word-boundary pattern let one side of each pair match anywhere in the message.

**Interface labels still follow the app language.** That part was right and is unchanged: only what the assistant *says* follows what the member
wrote.

## October 13, late afternoon — two walls the demonstration walked into, both real

**The faucet grants exactly what the lock costs.** `CLAIM()` gives 100 ORB once per address (`claimed(address) -> bool`, on chain), and the Holder Pro
licence locks exactly `LICENSE_AMOUNT = 100 ORB`. So a member who spends *any* of the claim before locking can never reach the lock, and on testnet
there is no second source of ORB for that address. The demonstration wallet is at 99 ORB right now for precisely that reason: the Privy video shows it
sending 1.0 ORB, and that transfer is what made Holder Pro unreachable. Either the faucet grants more than the lock (150 would leave room to spend),
or the lock amount drops below the claim. Both are contract constants and neither can change tonight.

**A returning Privy account can strand its own wallet.** The onboarding account used for the Privy video claimed its 100 ORB on chain without trouble,
then its transfer failed with "This Orb wallet is already assigned to another profile." The browser's session had resolved to a *new* Orb person
(`bbba82aa`) while the wallet binding belonged to the person created at login (`c09554fa`) — the same one-wallet-per-Privy-id rule from earlier today,
now hit by revisiting an account in the same browser profile rather than by a new profile. The guard is correct (it refuses to let one profile spend
another's wallet) but there is no path back: the account holds 100 ORB it cannot move, because the app will not bind the wallet to the person it is
currently talking to. What the product should do is recognise the Privy user already has an Orb profile and offer to switch to it, the way the
login-wallet conflict dialog already does for external identities.

Neither wall blocks the submission. The assistant allowance is five reviews per wallet per day, resetting at 00:00 UTC (21:00 Brasília), which is
before the deadline, so the ticket scene is recorded after the reset rather than unlocked with a licence the demonstration cannot buy.

## October 13, evening third — the judge page lied about the assistant, twice

**"The AI assistant is inactive" while the assistant was answering members.** `lib/judge-status.ts` tested `OPENROUTER_API_KEY`, a variable the
product never reads — the client is configured through `HUNYUAN_API_BASE_URL` and `HUNYUAN_API_KEY`, which point at OpenRouter. The host has the
latter two, so the page warned every judge who opened it. It now checks the same credentials `hunyuanConfigured()` checks, and
`scripts/test-judge-status.mjs` fails if a key the product ignores is enough to call the assistant active.

**The reply language was left to the model's judgement.** `speakLocale` fixed what Orb itself says, but the instruction to the model was
*"reply in the same language the user wrote"*, and the free model ignored it often enough that "prepare a BTC market long" came back in Portuguese.
The prompt now states the language for the turn — "Reply in English, the language the member wrote in. Do not answer in Portuguese." — decided by the
same detection, and the suite asserts both variants of the instruction actually sent.

Both went out as `20261013-assistanttruth-v038`. The host's own status endpoint now reads *"Wallet login is configured; the AI assistant is active."*
and the root disk is back to 2.7 GB free after clearing `.next/cache` from the installed releases — build cache, regenerated on the next build, never
read at runtime.

## October 13, night — the site moved to joinorb.online

The purchased domain now serves Orb. `/etc/orb/orb.env` and line 15 of `orb.service` both say
`ORBITA_PUBLIC_ORIGIN=https://joinorb.online`; they had to be changed together, because the unit file
pinned the old sslip name while the env file carried the domain. The env file won in practice (read
from `/proc/<pid>/environ`), but two sources that disagree are a restart away from silently flipping
the canonical origin back and 403ing every write from the domain. Backups taken before each change:
`orb.env.before-domain`, `orb.service.before-domain`, `Caddyfile.before-joinorb`,
`Caddyfile.before-cloudflare-ip`.

**No rebuild was needed.** `perpTradingSocketUrl` builds the relay from `window.location.origin` and
only consults `NEXT_PUBLIC_PERPL_WS_RELAY_ORIGIN` when the page is on localhost, so the installed
bundle is origin-agnostic; the one build-time variable this deployment has, `NEXT_PUBLIC_PRIVY_APP_ID`,
did not change. Checked from outside instead: `wss://joinorb.online/perpl/trading` answers
**101 Switching Protocols** with `Origin: https://joinorb.online`, **403** with no Origin at all, the
sslip host still upgrades, and a `http://localhost:3112` tab is accepted by the domain's matcher, so
local development keeps a working relay. `caddy validate` reported "Valid configuration" before the
reload, and all four services came back `active`.

**Cloudflare sits in front of the domain and that broke an assumption in the rate limiter.** Every
response carries `Cf-Ray`, so `{remote_host}` as seen by Caddy is a Cloudflare edge address shared by
all visitors. `rateLimitAnonymous` keys its budget on the client address — 30 new profiles per hour
per IP — which would have let the first thirty visitors spend the allowance for every judge after
them. The two domain blocks now read the client address from `Cf-Connecting-Ip`, and only when the
peer really is a Cloudflare address (a `remote_ip` matcher over the published ranges), so a caller
that reaches Caddy directly cannot name its own IP. Proved functionally: a fresh visitor on the
domain still receives `__Host-orbita_session` and a person row, which only happens if the address the
app reads passes `isIP()`.

**Privy accepts the new origin without a dashboard change.** Signing in on
`https://joinorb.online` with the demo login wallet completed, and Privy's own approval modal read
`joinorb.online wants you to sign in with your Ethereum account: 0x1866…`.

**The wall that remains is the session cookie, not the wallet.** `__Host-orbita_session` is
host-scoped by definition, so the browser that owns the demo community arrives on the domain as a
fresh "Visitante" and finds its embedded wallet already bound to the person on the other host:
`/api/orb/wallet/account` answers 409. The recovery the product does have — the conflict panel's
**Sign in to that account**, which posts `action:'switch'` and repoints the browser at the person who
owns the wallet — is only reached when a *link* of an external login wallet returns 409 with a
conflict object. The embedded-wallet auto-setup reports the same collision without that object, so no
panel appears and the account is stranded again. Two endpoints, one event, two shapes: that is the
defect, and it is the same wall written up earlier tonight from the other side.

**What a judge can do on the domain, measured.** Open `/`, click "Join the demo community" (a link
carrying the invite and a suggested guest name, which pre-fills the join dialog so entering is one
click), and land in **Orb Builders (demo)** with `general`, `shipped`, `help`, the `Lounge` voice room
and the trade channel; the header shows the **Saved on Monad** chip; the perpetual desk renders live
venue data (NEAR $5.0738 −3.26%, BTC $83,404.4, mark and oracle side by side, labelled Live). The
only non-2xx for a visitor with no wallet is `401 /api/orb/wallet`.

**The assistant tells a walletless visitor the wrong reason.** A disabled composer said "OpenRouter
free AI is waiting for server credentials" on a host that has them — the running process carries
`HUNYUAN_API_BASE_URL=https://openrouter.ai/api/v1` and a 73-character key. `configured` starts false
in `use-trade-intents.ts` and is only set by a successful desk response, while
`POST /api/orb/assistant` requires an Orb Wallet before it looks at credentials at all, so for that
visitor the sentence describes neither the state nor the cause. The fix is to report the reason the
request actually failed instead of defaulting to the credentials sentence.
