From 67d2b33050543f4d4f7537f0c86cd647ebb655fc Mon Sep 17 00:00:00 2001 From: MFrancis-dev Date: Tue, 10 Nov 2026 07:51:37 +0100 Subject: [PATCH] Pin the wallet to mainnet: NEAR Intents has no testnet (S7.14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-read live on 2026-11-10: hackathon Q-007 still says no testnet exists or is planned, and the vendor docs say the same. Every money path is mainnet-only, so the untested NEXT_PUBLIC_NEAR_NETWORK switch could only produce a wallet that signs in and then cannot pay or be paid. It also allowed a *.testnet payout address through validation for a delivery that cannot land. NEAR_NETWORK is now the literal "mainnet". The per-path audit is recorded in ARCHITECTURE.md ยง11, ESSENTIALS, README and LAUNCH ยง1. --- ARCHITECTURE.md | 30 ++++++++++++++++++++++++++++++ ARCHITECTURE_ESSENTIALS.md | 1 + LAUNCH.md | 4 ++++ README.md | 1 + ROADMAP.md | 4 +++- lib/ui/config.ts | 14 +++++--------- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 42c1db5..e96413f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -386,6 +386,36 @@ publicKey}`), and `verifyNep413` for the JSON/RAAT convention that no wallet pro - ๐Ÿ” still unverified: one real end-to-end login against a live wallet (the byte layout is the library's, the _dialog_ โ€” what a wallet shows the human โ€” is not testable offline). +### Answered: does Settle need testnet for anything? (S7.14, 2026-11-10) + +**No. No judged path can run on testnet, and one path would break silently if it did.** + +Re-read live on 2026-11-10: https://nearhackathon.com/HACKATHON.md still answers Q-007 "Testnet for +Intents? None exists or is planned", and the vendor's own docs say the same in two places ("There is no +testnet deployment and no plans for one. We recommend testing on NEAR mainnet using separate dev/test +NEAR accounts"), so the answer is not a stale snapshot. Feature by feature: + +- **1Click quotes, deposits and payouts** โ€” mainnet only; the live `/v0/tokens` lists 216 assets whose + `near` entries are all mainnet NEP-141 contracts (the one id containing "test" is a mainnet + sub-account, `test-token.highdome3013.near`, not a test network). This is the integration the track + judges, so Q-010 covers the entry: "Mainnet only where the integration is mainnet-only". +- **Wallet sign-in (NEP-413)** โ€” the standard is network-agnostic, but the wallet's network and the + server's `NEAR_RPC_URL` must agree. Mainnet wallet + testnet RPC fails the access-key read and refuses + the login; testnet wallet + mainnet RPC is the dangerous mix, because the address validator accepts + `alice.testnet`, 1Click would attempt a mainnet delivery to an account that does not exist there, and + the freelancer would not be paid. `lib/ui/config.ts` therefore no longer reads a network variable: + `NEAR_NETWORK` is the literal `"mainnet"`, and the only switch is demo vs real. +- **Storage check and the storage payer (S2.10)** โ€” the check reads the real USDC contract's bounds and + the real account's balance; on testnet it would read a test FT for an account that 1Click will never + pay. Free testnet NEAR (Q-008) buys nothing here, because the spend path is blocked on a funded mainnet + payer and an explicit human yes anyway. +- **Demo mode and dry-run quotes** โ€” already the no-funds paths: `pnpm demo` touches no chain, and a + `dry: true` quote is a real mainnet quote with no deposit address. Testnet is not a substitute for + either, and the e2e suite runs entirely in demo mode. +- **Hosted deployment** โ€” real mode against mainnet; production has no network variable set. +- **Prize payout account** โ€” the submission form's only network requirement is a **mainnet** NEAR + account. That is human-only and unchanged. + ### Answered: can the web process and the worker share one PGlite directory? (Phase 8 prep, 2026-11-08) **No, and the failure is silent.** Measured on `@electric-sql/pglite` 0.2.17 with two separate Node diff --git a/ARCHITECTURE_ESSENTIALS.md b/ARCHITECTURE_ESSENTIALS.md index b4444b0..bfb0e40 100644 --- a/ARCHITECTURE_ESSENTIALS.md +++ b/ARCHITECTURE_ESSENTIALS.md @@ -20,6 +20,7 @@ Separate repo from **Stack** (track 01). Never import Stack code; README names S - **SD-4** Payout assets: USDC on NEAR (default), Base, Arbitrum, Solana, Ethereum; USDT on Ethereum, Solana (ids: ARCHITECTURE ยง3.2) - **SD-5** Name: Settle - **SD-6** Invoice numbering: `INV-0001`, sequential **per user** (`invoices_user_number_uq`), at least 4 digits, grows past 9999 without truncating +- **Mainnet only (S7.14)** NEAR Intents has no testnet and none is planned (vendor docs + hackathon Q-007, re-read 2026-11-10), so `NEAR_NETWORK` in `lib/ui/config.ts` is the literal `"mainnet"`; do not reintroduce a network switch. Demo mode and `dry: true` quotes are the no-funds paths; testnet is used for nothing. The submission's prize-payout account is the one mainnet account the human supplies - **SD-7** Address checks use `@noble/hashes` (keccak256 โ†’ EIP-55, sha256 โ†’ base58-check) and `bs58` (Solana, Bitcoin). A chain with no validator is **rejected**, never accepted unchecked. **BTC is validated as a refund-only chain (2026-11-09, S1.7)**: bech32/bech32m and legacy mainnet addresses, never a payout chain ## ๐Ÿ” Verify before coding against diff --git a/LAUNCH.md b/LAUNCH.md index 275909b..853c026 100644 --- a/LAUNCH.md +++ b/LAUNCH.md @@ -33,6 +33,10 @@ Read from https://nearhackathon.com/HACKATHON.md on 2026-11-06. What it changes No rule changed in a way that invalidates work already merged. The two live consequences are: **the demo video is effectively required**, and **the payload must not gain a `website` field**. +**Re-read live on 2026-11-10 (S7.14):** Q-007 is unchanged, and the vendor's own docs agree โ€” +"There is no testnet deployment and no plans for one." No part of Settle uses testnet; the wallet is +pinned to mainnet and the no-funds paths are demo mode and `dry: true` quotes. See `ARCHITECTURE.md` ยง11. + --- ## 2. Demo video script (S10.1, #81) diff --git a/README.md b/README.md index de0b929..f4406a3 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ app/ (routes, pages) worker/ (two interval loops) - **Web app deployed; the worker is not.** The Next.js app is live on Vercel (https://settle-wine-alpha.vercel.app) in **real mode** against a hosted Postgres โ€” landing, NEAR-wallet sign-in, invoice create / list / dashboard / receipt โ€” and `GET /api/health` returns `{"status":"ok","demo":false,"database":"ok"}` from the running deployment, not only from a local boot. It was **re-deployed on 2026-11-09** from the merged tip โ€” the Vercel project is not connected to Git, so a merge does not deploy โ€” and the staleness probe in `DEPLOY.md` ยง8 went from 404 to 200. Loading the live URL in a real browser then found a hydration mismatch on the landing page (React #418): the server's HTML said wallet sign-in was unavailable while the browser's first render offered it, so React threw the markup away. Fixed here and pinned by the fifth e2e test, then deployed: the live page re-checked in Chromium at 1440 px and 390 px reports the sign-in button enabled, no page or console error, no failed request and no horizontal overflow. A screenshot of that page then caught the one defect no probe could: the hero receipt's amounts inherited the brand field's white text and rendered white-on-white, so the same e2e test now reads the amount's computed colour and fails it, and the fix (a `text-ink` on the card root) was proven by re-injecting the bug and watching the test go red first. A green `/api/health` detects neither problem, which is why the deploy notes now say to probe a route only the new artifact has and then read the first screen's console. It was **re-deployed once more on 2026-11-10** with S1.7: the challenge probe answers 200, `/api/health` is green, the landing page opens in Chromium with no console message, and the live `/api/tokens` carries `refundChain` and lists BTC as refund-only (`settleChain: null`) with no BTC asset among the payouts. The **same day** it was deployed again with S4.15, the dark landing redesign: the served HTML carries the new hero ("Invoice in dollars. Paid in any token."), the challenge probe answers 200, `/api/health` is green, the sign-in button is enabled after hydration, and Chromium at 1440 px and 390 px reports **no page or console error and no horizontal overflow**. Two things that URL does not do yet, stated plainly: **no live payment quote**, because the vendor `ONECLICK_JWT` (S0.8) is not in hand, so the pay page fails closed before it prices anything; and **no background worker**, because a serverless host cannot keep the long-lived 5 s poll / 60 s expire process. That second limit is exactly why `DEPLOY.md` ยง6 concluded that a two-process host is a paid decision โ€” Render's free-plan page says free plans do not support background workers, and usage-based hosts bill a worker that never idles. A full live payment therefore still needs a host that runs both processes plus the JWT. The offline evidence is unchanged: `pnpm demo`, the E2E browser journey โ€” which drives a **production build** (`pnpm build` + `pnpm start`, the same two commands a host runs) rather than `next dev` โ€” and 671 passing offline tests (672 total, one Postgres-only skip). `DEPLOY.md` also records two constraints that were measured, not assumed: PGlite is single-process โ€” two processes on one directory lost half their writes and killed one outright โ€” so a hosted Settle needs Postgres; and demo mode deliberately refuses an `https://` URL, so the hosted app runs **real mode** (decision `SD-8`) and the demo story lives in `pnpm demo` and the video. - **A real quote has never been driven offline, and since 2026-11-10 no quote is served without the partner JWT.** The minimum refusal itself is not a guess โ€” `try at least N` was provoked against the live API on 2026-11-09 with unauthenticated _dry_ quotes, which is what fixed the units it is rendered in (S0.13). Then, on **2026-11-10**, the same unauthenticated `POST /v0/quote` โ€” dry or not, on three routes including the USDCโ†’USDC call that had answered 200 the day before โ€” returned `400 Internal server error`, while the same body with a bogus bearer returned `401 Invalid token`. The token list still answers without credentials. So a real payment now needs the partner JWT (S0.8, #11) even for a preview, plus a funded signer, a running worker and a two-process host. +- **No testnet, because NEAR Intents has none.** Re-read live on 2026-11-10: the rules still answer Q-007 "None exists or is planned" and the vendor's docs say "There is no testnet deployment and no plans for one." Every money path is therefore mainnet โ€” the wallet selector is pinned to mainnet (`lib/ui/config.ts`), the storage check reads the real USDC contract, and the no-funds paths are `pnpm demo` and `dry: true` quotes. Testnet NEAR buys nothing here; the submission's prize-payout account is the one mainnet account the human supplies. The full per-path audit is `ARCHITECTURE.md` ยง11 (S7.14). - **Refund addresses are checked on five payout chains, plus Bitcoin.** `lib/core/address.ts` validates NEAR, EVM (EIP-55), Solana and, since S1.7, **BTC** โ€” base58-check P2PKH/P2SH and segwit bech32/bech32m, mainnet only. Bitcoin is a _refund-only_ chain: a client can pay from BTC and is refunded there, but Settle never pays out to it. Everything else a client might pay from is still refused with a reason rather than accepted unchecked; for TON and Tron that is a missing validator, and for chains that need a memo it is the missing memo field. The memo list itself is ours and flagged ๐Ÿ”, because 1Click does not publish which origins require one. Refusing errs safe: an address Settle cannot check is not one it will send money back to. - **A region block is passed through, not recognised.** The vendor's own wording reaches the client; Settle does not match it against strings it guessed. - **Rate limits are in-process.** Correct for one instance; N instances would each keep their own bucket. Stated in `ARCHITECTURE.md` ยง3.1 rather than hidden. diff --git a/ROADMAP.md b/ROADMAP.md index 704f12e..e3eee7a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -21,7 +21,7 @@ | 4. UI | Nov 7โ€“9 | 100% | โœ… done (S4.1โ€“S4.15; the E2E demo journey shipped with Phase 6, S4.9 is the app-wide identity, S4.10 designs the landing page, S4.11 keeps the wallet stack out of the first bundle, S4.12/13 are the Landbook-inspired redesign and the brand assets, S4.14 keeps a 24-decimal amount inside its quote column, S4.15 restyles the landing as a dark crypto-exchange hero, and one live wallet sign-in is still ๐Ÿ‘ค) | | 5. Worker | Nov 8 | 100% | โœ… done (S5.1โ€“S5.3; both loops run on the app's own deps and were run as a real process) | | 6. Demo mode & dry-run | Nov 9โ€“10 | 100% | โœ… done (S6.1โ€“S6.7; `pnpm demo` runs with no `.env`, and the Playwright journey runs in CI against a **production build** โ€” `pnpm demo:prod`, S6.7 โ€” so CI drives the artifact Phase 8 deploys) | -| 7. Hardening & edge cases | Nov 10โ€“11 | 100% | โœ… done (S7.1โ€“S7.13; four were already built and are now pinned by tests, #108 closed the bare-receipt-link dead-end, and S7.13/#140 โ€” found on the hosted URL, not by CI โ€” stopped the landing page's first render depending on `window`, with a fifth e2e test that opens `/` and refuses a hydration mismatch. ๐Ÿ” the memo-chain list and the region-block mapping are still ours โ€” S0.12/S0.13 came back from the vendor in 2026-11-09 and published neither) | +| 7. Hardening & edge cases | Nov 10โ€“11 | 100% | โœ… done (S7.1โ€“S7.14; four were already built and are now pinned by tests, #108 closed the bare-receipt-link dead-end, and S7.13/#140 โ€” found on the hosted URL, not by CI โ€” stopped the landing page's first render depending on `window`, with a fifth e2e test that opens `/` and refuses a hydration mismatch. S7.14 re-read testnet live, found no path that can use it, and pinned the wallet to mainnet. ๐Ÿ” the memo-chain list and the region-block mapping are still ours โ€” S0.12/S0.13 came back from the vendor in 2026-11-09 and published neither) | | 8. Deploy & real run | Nov 11 | 67% | in progress (**S8.1/S8.2/S8.3 โœ… 2026-11-08 โ€” the human chose Vercel and said deploy; the web app is live in real mode on https://settle-wine-alpha.vercel.app against a hosted Neon Postgres, `/api/health` green from the running deployment, no login wall. The serverless host cannot run the worker, so payments stay fail-closed on that URL and one real payment (S8.4 ๐Ÿ‘ค) still needs the partner JWT. **Re-deployed 2026-11-09** on the human's "follow your recommendation": the live build was ~5 merged PRs stale โ€” `GET /api/auth/challenge?intent=session` answered 404 โ€” and now answers 200 from `ab9d241`, with `/api/health`, `/` and `/demo` all 200 and no login wall. Verifying it in a real browser found S7.13/#140. **Re-deployed again 2026-11-10** with S1.7/#149: challenge probe 200, `/api/health` green, landing page clean in Chromium, and the live `/api/tokens` shows `refundChain` with BTC refund-only and absent from `payoutAssets`. S8.4 is blocked on a credential, not on code: measured 2026-11-10, **every unauthenticated `POST /v0/quote` now answers `400 Internal server error`** โ€” dry or not, three routes, including the USDCโ†’USDC dry call that returned 200 on 2026-11-09 โ€” while a bogus bearer gets `401 Invalid token` and `/v0/tokens` still answers 200**. **Re-deployed the same day with S4.15**: the live HTML serves the dark landing hero, the challenge probe is 200, `/api/health` is green, and Chromium at 1440 px and 390 px reports an enabled sign-in button, no page or console error and no horizontal overflow**) | | 9. README & public repo | Nov 11โ€“12 | 100% | โœ… done (S9.1โ€“S9.7; the fresh clone was run against the public URL and passes. The hosted demo URL is Phase 8, not this one) | | 10. Launch: video, post, submission | Nov 12โ€“13 | 57% | in progress (S10.1, S10.3, S10.5, S10.6 โœ… as drafts in `LAUNCH.md`; S10.2, S10.4, S10.7 are ๐Ÿ‘ค โ€” voice-over decision and upload, posting, submitting; three walkthrough cuts are recorded, the newest against the dark design) | @@ -173,6 +173,7 @@ Format: `- [task id] what blocks it ยท who can unblock ยท since date` - [x] **S7.11** [#108](https://github.com/MFrancis-dev/settle/issues/108) A bare `/receipt/` link resolves instead of dead-ending ยท 2026-11-08 (found verifying Phase 4 in a browser): copying the receipt URL out of the dashboard, or an email client trimming the query string, arrived at `/receipt/` with no `?attempt=` and got a page that could only say "this link is incomplete." The token is minted `unique` (ยง6), so it names exactly one attempt and the read needs no id. `findAttemptByReceiptToken` + `receiptByToken` reuse the same `buildReceipt` as the id-keyed route โ€” identical document, same "unknown โ†’ 404" so neither endpoint enumerates payments โ€” behind a new `GET /api/receipts?token=`, which `useReceipt` reaches when there is no attempt id; the app-generated link still carries `?attempt=`. No refund/payout address reaches the shared view. 6 route tests + 1 client test; suite **641 passing + 1 skipped**. - [x] **S7.12** [#109](https://github.com/MFrancis-dev/settle/issues/109) Freshness is the server's promise, not the wallet's ยท 2026-11-09: `lib/server/nonces.ts` was a **seen-set** โ€” `use(nonce)` said true the first time it saw any string โ€” so a sign-in body captured before it was ever submitted carried a nonce this process had never issued and verified for ever. Freshness was being promised by the caller. The store now has two verbs: `issue()` mints a 32-byte base64 challenge and stamps it with the injected clock; `use()` spends it once and refuses anything unissued, spent, or older than `CHALLENGE_TTL_MS` (5 min). A 10 000-entry cap evicts expired-then-oldest so the map cannot be filled by pre-minting. `GET /api/auth/challenge?intent=session|payout` hands them out, and three guards keep it from being anything else: it is unauthenticated because the nonce is not a credential (worthless without the account's own signature over it), it echoes no caller text (only `signInMessage(host)`, or `null` for a payout), and it is limited per caller by a new `CHALLENGE_LIMIT` (30/min) bucket on `deps.challengeLimits`. The `recipient` it returns is the server's own `PUBLIC_APP_URL` host, not the request's โ€” which let `recipientFromLocation()` go: the browser no longer guesses at a host the server has to agree with. `WalletProvider` asks _after_ the chooser closes, so the window is spent signing rather than sitting in a tab; the body builders take the nonce instead of minting one, and `verifySigned`'s call site is unchanged โ€” a never-issued nonce is the same `replayed` refusal with the same remedy (get another challenge). Demo sign-in issues its cookie directly and never reaches this path, so `pnpm demo` and the Playwright journey are untouched. 12 challenge-route tests + 9 store tests (was 8) + one never-issued refusal each in `POST /api/session` and `verifySigned`; suite **656 passing + 1 skipped**. - [x] **S7.13** [#140](https://github.com/MFrancis-dev/settle/issues/140) The landing page's first render must not depend on `window` ยท 2026-11-09, **found on the hosted deployment while verifying the redeploy, not by CI**: `WALLET_SUPPORTED` (`lib/ui/config.ts`) reads `typeof window`, and `WalletProvider` seeded its phase from it โ€” so the server sent `unsupported` ("Wallet sign-in unavailable", `disabled`) and the browser's first render sent `loading` ("Sign in with your wallet", spinner). React error **#418**: the server's markup is thrown away and the whole tree re-renders on the client, and the HTML an AI screener reads says sign-in is unavailable on a deployment where it works. The phase is now seeded `"loading"` on both sides and the effect resolves it after mount (`unsupported` only where the browser really has no `crypto.getRandomValues`), which is the only place `window` may be read. **Why CI never saw it:** the journey opened `/demo`, `/invoices`, `/pay/โ€ฆ` and `/receipt/โ€ฆ` but never `/`, and nothing listened for `pageerror`. The new fifth e2e test opens the landing page and asserts both halves โ€” the served HTML offers sign-in and does not deny it, and the browser throws nothing while hydrating it. It was run **red before the fix** (failed on the served HTML) and green after: **5 Playwright tests passing** against a fresh production build, unit suite unchanged at **664 passing + 1 skipped**. +- [x] **S7.14** [#157](https://github.com/MFrancis-dev/settle/issues/157) Settle needs NEAR testnet for nothing; pin the wallet network ยท 2026-11-10, on the human's "go through all the info from near": re-read the rules and the vendor docs live โ€” Q-007 still says "None exists or is planned", the docs say "There is no testnet deployment and no plans for one", and the live `/v0/tokens` (216 assets) lists only mainnet `near` contracts (the one "test" id is the mainnet sub-account `test-token.highdome3013.near`). So 1Click quotes/payouts, the storage check and the payer spend have no testnet counterpart; demo mode and `dry: true` quotes are already the no-funds paths; and a testnet wallet signing into a mainnet RPC would let `alice.testnet` through the address validator for a delivery that cannot land. Removed the untested `NEXT_PUBLIC_NEAR_NETWORK` switch: `lib/ui/config.ts` now exports the literal `"mainnet"`, so that mixed-network configuration cannot be built. Recorded in `ARCHITECTURE.md` ยง11, ESSENTIALS, README and LAUNCH ยง1. - Four of the original eleven turned out to be already built (S7.3 wholly, S7.7's public half, S7.8's signature/snapshot half, S7.10's log sites); this phase is the gaps plus the tests that pin them. Suite **656 passing + 1 skipped**, typecheck/lint clean. Not covered: no test drives the real 1Click minimum (it needs the partner JWT, S0.8), and the memo-chain list is ๐Ÿ” ours. ## Phase 8 โ€” Deploy & real run (Nov 11) @@ -225,6 +226,7 @@ Format: `- [task id] what blocks it ยท who can unblock ยท since date` One line per contribution, newest first: `YYYY-MM-DD ยท who/agent ยท what changed ยท tasks` +- 2026-11-10 ยท agent ยท Checked every NEAR-dependent path against live testnet availability and found that none can use it: the hackathon's Q-007 and the vendor docs both say NEAR Intents has no testnet and none is planned, the live token list has only mainnet `near` contracts, and the storage check reads the real USDC contract. **S7.14** ([#157](https://github.com/MFrancis-dev/settle/issues/157)) removes the untested `NEXT_PUBLIC_NEAR_NETWORK` switch โ€” `lib/ui/config.ts` now exports the literal `"mainnet"` โ€” which also closes the mixed-network footgun where a testnet wallet could sign in and register a `*.testnet` payout address that 1Click would try to pay on mainnet. Demo mode and `dry: true` quotes remain the no-funds paths; the per-path answer is in `ARCHITECTURE.md` ยง11 and the README limitation. - 2026-11-10 ยท agent ยท The human picked a different voice and asked for the real-payment slot. **Daniel (UK, rate 175)** replaces Samantha for all 15 lines (14 cues plus one for the real-payment segment), and the cut was re-recorded because `record-demo.mjs` paces every shot by its line's measured length. Added `assemble.mjs`, which builds the whole file from the raw captures, computes the cue offsets, mixes and loudness-normalises the narration, and โ€” when `recordings/real/real-payment.{mp4,mov,webm}` exists โ€” inserts a "One real payment" card plus the muted clip after the demo receipt with line n15 over it, shifting the phone and outro. The slot was proven with a 10 s stand-in (110.267 s โ†’ 122.767 s; n15 at 102.0 s), the stand-in was deleted, and the shipped cut is **110.267 s, 7.31 MB** with the first line starting over the brand card. Still ๐Ÿ‘ค: recording the real payment (S8.4), approving the synthesized voice, and uploading ยท S10.2 ([#82](https://github.com/MFrancis-dev/settle/issues/82)) - 2026-11-10 ยท agent ยท S10.1's script, recorded against the new design and voiced. The human asked for "a new video with voiceover" after the S4.15 restyle. The existing Playwright recorder was reused, so the footage is still demo-mode and honest. Two narration lines were rewritten and re-synthesized to match what is now on screen (the hero's own promise; one address, one exact amount, the fee named before anything is sent); captions and the cursor were moved from the old green to the new chrome because green is reserved for money that arrived. Added a 3.2 s brand intro card and a 5 s outro card rendered from `cards/cards.html`, re-paced the landing scroll by the second line's real spoken length, and scaled the phone segment to 1000 px high inside a framed dark field. Verification: every cue's mean level is โˆ’15.9 to โˆ’18.6 dB where speech should be and โˆ’91 dB in the intentional gaps; stream probe reports 1920ร—1080 at 30 fps, H.264 + AAC 48 kHz stereo, **103.333 s, 6.96 MB** at `/Users/allison/NEAR/recordings/settle-demo-walkthrough-voiced-2026-11-10.mp4`. Still ๐Ÿ‘ค: a human voice-over decision (or approval of the synthesized cut), the S8.4 real-payment shot and the upload ยท S10.2 ([#82](https://github.com/MFrancis-dev/settle/issues/82)) - 2026-11-10 ยท agent ยท Restyled the submission-facing landing on the human's "use this as a template" with a Dribbble crypto-exchange shot. Dribbble serves curl and WebFetch nothing, so the reference was read from its `og:image` in the browser: near-black ground, one magenta light shaft behind a centred hero, glass panels on hairline borders, pill CTAs, floating coin chips. **S4.15** ([#143](https://github.com/MFrancis-dev/settle/issues/143)): a `.night` scope re-declares the colour and radius tokens under a single class, so the page flips to that palette without a second stylesheet, and `.paper` restores the light ledger for the receipt; the new magenta is chrome only, while brand green keeps meaning money that arrived. Hero, glass stage around the real client-view demo, chain bar (Bitcoin is refund-only), steps, live token picker, three feature cards with mini product visuals, FAQ, closing CTA. Measured in Chromium: **0 horizontal overflow at 1440 px and 390 px** (`documentElement.scrollWidth` equals the viewport), the landing e2e contract still passes (server HTML offers sign-in, hydration throws no `pageerror`, the receipt amount's RGB sum stays under 600), **671 unit + 1 skip**, typecheck/lint/format clean. No phase percentage moves โ€” the redesign lands inside an already-complete phase ยท S4.15 (new, [#143](https://github.com/MFrancis-dev/settle/issues/143)) diff --git a/lib/ui/config.ts b/lib/ui/config.ts index ad74644..c8a8660 100644 --- a/lib/ui/config.ts +++ b/lib/ui/config.ts @@ -6,17 +6,13 @@ * component can reach a secret by importing the wrong module. */ -import { z } from "zod"; - /** - * The chain network the wallet modules connect to. wallet-selector requires one of its two literal - * ids; absent means the public one. Nothing in the UI prints this value. + * The chain network the wallet modules connect to. Pinned to mainnet: NEAR Intents has no testnet + * deployment and none is planned (vendor docs and hackathon Q-007, re-read 2026-11-10), so a wallet + * on any other network could sign in but never pay or be paid. Demo mode and dry-run quotes are the + * no-funds paths. Nothing in the UI prints this value. */ -const networkSchema = z.enum(["mainnet", "testnet"]).default("mainnet"); - -export const NEAR_NETWORK: "mainnet" | "testnet" = networkSchema.parse( - process.env.NEXT_PUBLIC_NEAR_NETWORK || undefined, -); +export const NEAR_NETWORK = "mainnet" as const; /** Wallet sessions and payouts are useless without a wallet, so the button degrades to disabled. */ export const WALLET_SUPPORTED =