feat(asm-sequencer): settings-UI toggle in both surfaces (wallet committed; Potidaea drafted)

Beta wallet (shells/app/src/screens/SettingsScreen.tsx)
  New "ASM sequencer relay" group between Network and Token definitions.
  URL input + live reachability probe (useEffect fires on endpoint change,
  shows checking → Reachable / Unreachable). Persists via localStorage key
  asm-sequencer.endpoint through the sequencerRelay module. Default empty =
  trustless tier-4 retry stays in charge.

Server (scripts/asm-sequencer/server.mjs)
  Permissive CORS (Access-Control-Allow-Origin: *, methods GET/POST/OPTIONS,
  content-type header, 10 min max-age) + explicit OPTIONS preflight handler.
  Production deploys tighten this at the reverse proxy; the dev daemon needs
  CORS so any wallet/dApp origin can probe /health and POST /claim.

Potidaea (design/potidaea/{app.html, src/main.ts})
  Drafted locally but NOT committed — those files carry the DEA asset-side
  session's uncommitted network-toggle work on the same lines. My wiring
  (wireSequencerSettings + Section 4 render in paintWalletMenu) will rebase
  cleanly once that session commits. Browser-verified against the live
  chipnet daemon: ✓ reachable chip, saved to LS, menu stays open while
  typing, ⚠ unreachable chip for 127.0.0.1:9999.

Snapshot 0280 documents the arc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Local Dev 2026-10-02 20:40:59 +02:00
parent 07fd344299
commit a014f6636f
4 changed files with 168 additions and 4 deletions

View file

@ -175,13 +175,29 @@ async function readBody(req) {
return raw ? JSON.parse(raw) : {};
}
// Permissive CORS for a dev relay — any wallet/dApp origin can probe + claim.
// A production deploy behind nginx/caddy can tighten this via the reverse
// proxy (Access-Control-Allow-Origin) rather than touching the server.
const CORS_HEADERS = {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET, POST, OPTIONS',
'access-control-allow-headers': 'content-type',
'access-control-max-age': '600',
};
function send(res, status, body) {
const data = JSON.stringify(body);
res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data) });
res.writeHead(status, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data), ...CORS_HEADERS });
res.end(data);
}
const server = createServer(async (req, res) => {
// CORS preflight — Chrome fires OPTIONS before any non-simple POST.
if (req.method === 'OPTIONS') {
res.writeHead(204, CORS_HEADERS);
res.end();
return;
}
try {
if (req.method === 'GET' && req.url === '/health') {
send(res, 200, { ok: true, ...state.snapshot() });

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import type { Network, CoreSession } from '../core/wallet';
import { TokenImportModal } from '../components/TokenImportModal';
import type { TokenTierA } from '@core/token/types.js';
@ -7,6 +7,7 @@ import { BUILD, isNetworkAllowed, FUSE_ENABLED } from '../buildInfo';
import type { DevWallet } from '../devWallets';
import type { MultiKeyStore, WalletSummary, SeedSummary } from '../keystore/MultiKeyStore';
import { ImportFromKeystoreFlow } from './ImportFromKeystoreFlow';
import { getSequencerEndpoint, setSequencerEndpoint, sequencerReachable } from '../asm-core/sequencerRelay';
// Settings — reachable from the topbar gear (not a bottom tab). Security (seed +
// PIN/biometric + lock), Privacy (CashFusion toggles), Network (shared with the
@ -68,6 +69,28 @@ export function SettingsScreen({
() => (session ? session.registry.listTierA() as TokenTierA[] : []),
);
// ASM sequencer relay — opt-in tier-2 path for same-target reservoir races.
// Null endpoint = disabled, which is the default. See
// asm-core/sequencerRelay.ts for the probe-first-then-commit dispatch, and
// wiki/asm/zero-contention-protocol.md for the trust model.
const [seqEndpoint, setSeqEndpoint] = useState<string>(() => getSequencerEndpoint() ?? '');
const [seqProbe, setSeqProbe] = useState<'idle' | 'checking' | 'ok' | 'fail'>('idle');
const seqEnabled = seqEndpoint.trim().length > 0;
useEffect(() => {
if (!seqEnabled) { setSeqProbe('idle'); return; }
let cancelled = false;
setSeqProbe('checking');
sequencerReachable(seqEndpoint.trim()).then((ok) => {
if (!cancelled) setSeqProbe(ok ? 'ok' : 'fail');
});
return () => { cancelled = true; };
}, [seqEndpoint, seqEnabled]);
const commitSeqEndpoint = (next: string) => {
const trimmed = next.trim();
setSeqEndpoint(trimmed);
setSequencerEndpoint(trimmed.length > 0 ? trimmed : null);
};
const handleImported = (token: TokenTierA) => {
setRegisteredTokens((prev) => {
const next = prev.filter((t) => t.categoryId !== token.categoryId);
@ -199,6 +222,51 @@ export function SettingsScreen({
</div>
</div>
{/* ASM sequencer relay — tier-2 low-latency path for same-target reservoir races.
Default off (empty endpoint = no relay = tier-4 wallet retry stays in charge).
See wiki/asm/zero-contention-protocol.md and snapshot 0279 for the trust model. */}
<div className="group">
<div className="group-label">ASM sequencer relay</div>
<div className="card">
<SRow
icon="⟶"
title="Relay endpoint"
sub={
seqEnabled
? seqProbe === 'checking'
? 'Checking reachability…'
: seqProbe === 'ok'
? 'Reachable — ASM claims will try the relay first, local retry if it fails'
: seqProbe === 'fail'
? 'Unreachable right now — ASM claims will use local retry'
: 'Endpoint set — not yet probed'
: 'Not set — ASM claims use the local client-side retry (tier-4)'
}
right={
<input
type="url"
inputMode="url"
placeholder="https://sequencer.example"
value={seqEndpoint}
onChange={(e) => setSeqEndpoint(e.currentTarget.value)}
onBlur={(e) => commitSeqEndpoint(e.currentTarget.value)}
aria-label="Sequencer relay endpoint URL"
style={{
width: 180, padding: '4px 8px', fontSize: 12,
border: '1px solid rgba(255,255,255,0.12)', borderRadius: 4,
background: 'transparent', color: 'inherit',
}}
/>
}
/>
</div>
<div className="empty" style={{ marginTop: 9 }}>
Opt-in. The relay orders claims into one reservoir chain for lower latency; it never holds
your keys and the covenant enforces the swap math either way. Leave empty to keep the
trustless retry path.
</div>
</div>
{/* Token Definitions — persistent registry */}
<div className="group">
<div className="group-label">Token definitions</div>

View file

@ -0,0 +1,78 @@
---
domain: asm
summary: "Settings-UI toggle for the ASM sequencer relay in both surfaces. Beta wallet gets a new 'ASM sequencer relay' group in SettingsScreen with URL input + live reachability probe. Potidaea's dynamic walletMenu gains the same input + status chip (drafted, not yet committed — needs the DEA session's co-mingled main.ts/app.html changes to land first). Server gained permissive CORS so browser probes succeed. Browser-verified reachable↔unreachable transitions against a live chipnet daemon."
tags: asm, sequencer, tier-2, zero-contention, settings-ui, wallet-wire, potidaea, cors
---
# Snapshot 0280 — ASM sequencer relay settings-UI toggle
**Date:** 2026-10-02
**Track:** shells/app/src/screens/SettingsScreen.tsx · design/potidaea/{app.html, src/main.ts} (local only) · scripts/asm-sequencer/server.mjs
**Status:** ✅ beta wallet committed + pushed · ⏳ Potidaea wiring drafted locally, blocked on co-mingled DEA-session commit · ✅ server CORS committed · ✅ browser-verified
Follow-up to [0279](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md). Exposes `sequencerRelay.ts`'s `getSequencerEndpoint` / `setSequencerEndpoint` through a user-facing control in each surface, plus a live `/health` reachability probe.
## What ships in this commit
### Beta wallet — `shells/app/src/screens/SettingsScreen.tsx`
New group between "Network" and "Token definitions":
```
ASM sequencer relay
┌────────────────────────────────────────────────────┐
│ ⟶ Relay endpoint [ https://sequencer.example ] │
│ Not set — ASM claims use the local client-side │
│ retry (tier-4) │
└────────────────────────────────────────────────────┘
Opt-in. The relay orders claims into one reservoir chain for
lower latency; it never holds your keys and the covenant
enforces the swap math either way. Leave empty to keep the
trustless retry path.
```
Live `useEffect` probe fires on every endpoint change: `checking…` → `Reachable — ASM claims will try the relay first, local retry if it fails` on a 200 `/health`, or `Unreachable right now — ASM claims will use local retry` on failure. tsc clean on all my files (pre-existing errors in `ExchangePanel.tsx` / `SwapPanel.tsx` / `UniformFiatScreen.tsx` are untouched).
### Server — `scripts/asm-sequencer/server.mjs`
Added permissive CORS headers (`Access-Control-Allow-Origin: *`, methods `GET, POST, OPTIONS`, headers `content-type`, 10 min max-age) + `OPTIONS` preflight handler. A production deploy behind nginx tightens this at the proxy layer; the dev daemon needs it so browser fetches from any wallet/dApp origin succeed.
### Potidaea — DRAFTED, uncommitted
The dApp's `walletMenu` is **dynamically rebuilt** by `paintWalletMenu` in `design/potidaea/src/main.ts` each time it opens (there's no static HTML to attach the input to). My wiring injects Section 4 — "ASM relay" — into the renderer's `items` array and attaches `wireSequencerSettings()` after each repaint. `wireSequencerSettings()` loads the saved endpoint, binds blur/change commit handlers, live-probes reachability, and stops click-propagation so typing inside the input doesn't trigger the mockup's document-level `closeAll`.
The TS compiles and the dApp builds. **Not committed** because `main.ts` and `app.html` carry the DEA asset-side session's in-flight network-toggle work on the same lines; staging them would commit someone else's uncommitted work. When the DEA session lands its commit, my Potidaea wiring rebases cleanly on top (my hunks are additive and don't overlap theirs).
## Browser verification
Spawned the daemon on 127.0.0.1:8710 (chipnet, COORD=h, TOKEN=asm2), reloaded Potidaea dev-server at localhost:5185, opened walletMenu, pasted `http://127.0.0.1:8710` into the input:
- `Access-Control-Allow-Origin: *` header present in `/health` response.
- Status chip transitioned from `off` → `checking…` → `✓ reachable` (ok class).
- `localStorage['asm-sequencer.endpoint']` persisted the URL.
- Menu stayed open while typing (click-propagation stop confirmed).
- Entering an unreachable URL (`127.0.0.1:9999`) produced `⚠ unreachable` (warn class).
## Why Potidaea needs the dynamic injection
The walletMenu `hdrmenu` in `app.html` is a stub that `paintWalletMenu` fully replaces on every open with per-wallet rows (`Wallet 1 ✎ ⧉ ✕`, Attach-another, Session). Any static HTML in the stub gets wiped. The settings sub-block has to live in the renderer, not the HTML. This is a change in approach from my first draft (which added static HTML) and is already encoded in the local main.ts work.
## Trust posture reminder
Nothing about this toggle changes the trust model from [0279](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md): default disabled (empty endpoint = trustless tier-4 retry), opt-in only, the covenant enforces swap math regardless of relay path. Users keep their keys locally in both the coord-funded `/claim` and the user-funded `/tip` + `/submit` flows.
## Files committed in this snapshot
- [`scripts/asm-sequencer/server.mjs`](../scripts/asm-sequencer/server.mjs) — permissive CORS + OPTIONS preflight
- [`shells/app/src/screens/SettingsScreen.tsx`](../shells/app/src/screens/SettingsScreen.tsx) — new "ASM sequencer relay" group with live probe
## Files drafted but not committed (co-mingled with DEA session)
- `design/potidaea/src/main.ts` — adds `wireSequencerSettings` + Section 4 renderer in `paintWalletMenu` + import of `sequencerRelay` helpers
- `design/potidaea/app.html` — no net change (my earlier static block was removed in favor of the dynamic render)
## Cross-references
- [0272](0272-wallet-asmcore-optimistic-chain-retry.md), [0275](0275-asm-forced-race-retry-proof-chipnet.md), [0277](0277-potidaea-orbital-swap-retry-adaptation.md) — tier-4 retry wire + proofs
- [0278](0278-asm-sequencer-service-drafted-and-proved.md), [0279](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md) — tier-2 sequencer daemon + chipnet proofs
- `wiki/asm/zero-contention-protocol.md` — handoff doc

View file

@ -10,7 +10,7 @@
> Duplicate snapshot numbers exist (0018, 0019, 0022, 0027, 0053, 0054, 0057, 0062, 0083, 0084,
> 0144, 0145, 0153, 0164) — they are distinct files, disambiguated below by slug.
_322 snapshots · 322 tagged · 8 domains · regenerate after adding one._
_323 snapshots · 323 tagged · 8 domains · regenerate after adding one._
## Quick-find — all snapshots (Ctrl-F by №, domain, tag, or word)
@ -338,6 +338,7 @@ _322 snapshots · 322 tagged · 8 domains · regenerate after adding one._
| [0277](0277-potidaea-orbital-swap-retry-adaptation.md) | asm | asm potidaea orbital-swap retry zero-contention chipnet wallet-parity | Potidaea dApp (navigate.st/bns/potidaea.asm) adopts the tier-4 client-side optimistic chain + retry pattern shipped in the beta wallet AsmCore (snapshot 0272): claimViaWc / claimViaLocalSigner / claimViaLocalSignerShare wrapped with silent re-broadcast on txn-mempool-conflict, retry-status banner in pendingSheet, resolved-via-N badge on doneSheet. tsc clean, dApp built. |
| [0278](0278-asm-sequencer-service-drafted-and-proved.md) | asm | asm potidaea orbital-swap sequencer tier-2 zero-contention chipnet proof daemon relay | Tier-2 ASM sequencer service drafted as a coordinator-funded HTTP relay + tested end-to-end on chipnet. 5 simultaneous /claim requests → 5/5 accepted with the daemon-assigned order forming a single reservoir chain (each tx spent the previous tx's reservoir output). Complements the tier-4 wallet retry from 0272/0275/0277 by adding the low-latency relay path with the wallet retry as fallback. |
| [0279](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md) | asm | asm sequencer tier-2 zero-contention chipnet systemd user-funded wallet-wire potidaea proof | Three-part completion of the tier-2 sequencer arc: (1) systemd unit + deploy README for running the daemon as a long-lived service; (2) user-funded /tip + /submit endpoints that remove the 'relay holds funds' trust step, chipnet-proved 5/5 (users signed their own BCH inputs, mean 3 attempts via 409-stale retry); (3) wallet-side sequencerRelay.ts client in both shells/app and design/potidaea with probe-first-then-commit race-free dispatch. Plus a chipnet-fund-from-h.mjs helper that topped up A/C/E/F/G from wallet H. |
| [0280](0280-asm-sequencer-settings-ui-toggle.md) | asm | asm sequencer tier-2 zero-contention settings-ui wallet-wire potidaea cors | Settings-UI toggle for the ASM sequencer relay in both surfaces. Beta wallet gets a new 'ASM sequencer relay' group in SettingsScreen with URL input + live reachability probe. Potidaea's dynamic walletMenu gains the same input + status chip (drafted, not yet committed — needs the DEA session's co-mingled main.ts/app.html changes to land first). Server gained permissive CORS so browser probes succeed. Browser-verified reachable↔unreachable transitions against a live chipnet daemon. |
## Wallet UI (shells/app) `(103)`
@ -445,7 +446,7 @@ _322 snapshots · 322 tagged · 8 domains · regenerate after adding one._
- [0272-wallet-asmcore-optimistic-chain-retry](0272-wallet-asmcore-optimistic-chain-retry.md) — AsmCore beta wires the tier-4 client-side optimistic chain + retry pattern — swap/mint/swap-all now silently re-plan against the mempool-inclusive reservoir tip and re-broadcast on `txn-mempool-conflict`, resolving the same-target reservoir race client-side (chipnet-measured 1/6 → N/N on the naïve path) · _wallet, asm, orbital-swap, potidaea, agora, chipnet, retry, zero-contention_
- [0276-potidaea-chart-tab-plan](0276-potidaea-chart-tab-plan.md) — Design for a Potidaea Chart tab — historical price chart of any asset in USD, powered by a lean off-chain oracle history + on-chain covenant rate for MBAs · _potidaea, ui, chart, oracle, price-history, plan_
## ASM / Orbital Swap / Potidaea / Anthemus `(44)`
## ASM / Orbital Swap / Potidaea / Anthemus `(45)`
- [0002-swap-core](0002-swap-core.md) — swap core landed · _swap_
- [0005-q4-handoff](0005-q4-handoff.md) — Q4 answer + chipnet validation plan, pre-switch handoff
@ -491,6 +492,7 @@ _322 snapshots · 322 tagged · 8 domains · regenerate after adding one._
- [0277-potidaea-orbital-swap-retry-adaptation](0277-potidaea-orbital-swap-retry-adaptation.md) — Potidaea dApp (navigate.st/bns/potidaea.asm) adopts the tier-4 client-side optimistic chain + retry pattern shipped in the beta wallet AsmCore (snapshot 0272): claimViaWc / claimViaLocalSigner / claimViaLocalSignerShare wrapped with silent re-broadcast on txn-mempool-conflict, retry-status banner in pendingSheet, resolved-via-N badge on doneSheet. tsc clean, dApp built. · _asm, potidaea, orbital-swap, retry, zero-contention, chipnet, wallet-parity_
- [0278-asm-sequencer-service-drafted-and-proved](0278-asm-sequencer-service-drafted-and-proved.md) — Tier-2 ASM sequencer service drafted as a coordinator-funded HTTP relay + tested end-to-end on chipnet. 5 simultaneous /claim requests → 5/5 accepted with the daemon-assigned order forming a single reservoir chain (each tx spent the previous tx's reservoir output). Complements the tier-4 wallet retry from 0272/0275/0277 by adding the low-latency relay path with the wallet retry as fallback. · _asm, potidaea, orbital-swap, sequencer, tier-2, zero-contention, chipnet, proof, daemon, relay_
- [0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md) — Three-part completion of the tier-2 sequencer arc: (1) systemd unit + deploy README for running the daemon as a long-lived service; (2) user-funded /tip + /submit endpoints that remove the 'relay holds funds' trust step, chipnet-proved 5/5 (users signed their own BCH inputs, mean 3 attempts via 409-stale retry); (3) wallet-side sequencerRelay.ts client in both shells/app and design/potidaea with probe-first-then-commit race-free dispatch. Plus a chipnet-fund-from-h.mjs helper that topped up A/C/E/F/G from wallet H. · _asm, sequencer, tier-2, zero-contention, chipnet, systemd, user-funded, wallet-wire, potidaea, proof_
- [0280-asm-sequencer-settings-ui-toggle](0280-asm-sequencer-settings-ui-toggle.md) — Settings-UI toggle for the ASM sequencer relay in both surfaces. Beta wallet gets a new 'ASM sequencer relay' group in SettingsScreen with URL input + live reachability probe. Potidaea's dynamic walletMenu gains the same input + status chip (drafted, not yet committed — needs the DEA session's co-mingled main.ts/app.html changes to land first). Server gained permissive CORS so browser probes succeed. Browser-verified reachable↔unreachable transitions against a live chipnet daemon. · _asm, sequencer, tier-2, zero-contention, settings-ui, wallet-wire, potidaea, cors_
## FIATm / BCHm / MBA (backed currency) `(58)`