Commit graph

454 commits

Author SHA1 Message Date
Local Dev
399372f923 chore(wallet): release 0.1.3
Published to silentmode.st/apps/deviant/releases/ + mirrored on Sia at
bns://deviant.x/wallet/releases/. Running 0.1.2 clients will see the update
banner on next launch (CapacitorHttp bypass on native, same-origin fetch
on the Sia-hosted hub).

APK:    deviant-wallet-0.1.3.apk  (32.18 MB)
SHA256: 0945f8d6fe40ec565b4c47fc59d005fee087ebcd11f8ad463b512d701c72f822

Also fixes the Sia-upload stage path in release.sh — Git Bash resolved
`$(dirname /tmp/...)` to a path node's statSync couldn't open on Windows.
Staging now goes under shells/app/downloads/ which exists on both
platforms.

See snapshots/0278-wallet-0.1.3-released.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 21:27:40 +03:00
Local Dev
cdf197e751 docs(snapshots): 0282 chipnet A-H full FT redeem + consolidation to H
Operation ran today: swept every FT holding across A-H + gmx
(backed MBAs + non-state NBAs) back to BCH via implicit fungible-token
burn, consolidated into wallet H. 9 txs total, 51 covenant-state NFTs
preserved. H now at 2.6967 BCH (up from 1.86).

Also kept two new audit helpers from the operation:
  scripts/scan-all-wallets-ft.mjs    one-shot inventory across every
                                     .chipnet-keys-*.json
  scripts/inspect-h-utxos.mjs        classifies H's UTXOs
                                     (bch-only / ft / nft / hybrid)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 21:26:44 +03:00
Local Dev
23714aac43 feat(wallet-ia): flat per-asset list + single-wallet shortcut + scoped filter
Three refinements to the Main → Wallet detail → Activity flow.

AssetsPane rewritten as a flat per-asset list:
  - One row per asset — BCH, every held MBA, every held NBA, every unknown
    token — each independently sized and tappable.
  - Colour-coded by kind via a left stripe + dot: green BCH, acid MBA,
    purple NBA (unknown tokens get the NBA colour with reduced opacity).
  - Small "MBA" / "NBA" chip in the asset name so the kind reads without
    needing the stripe colour alone.
  - Any row tap routes through onOpenActivity(kind, categoryId?) — BCH
    opens the BCH tab, a specific MBA/NBA opens its kind tab filtered to
    that category.
  - The old 3-category accordion (BCH / Backed Assets / Non-backed Assets
    with inline expanders) is gone — it hid individual assets behind a
    tap and gave MBA/NBA the same visual weight regardless of how many
    tokens were in each.

Single-wallet shortcut on the main tab:
  - When aggregate.rows.length (or fallback wallets.length) is ≤ 1, App.tsx
    renders WalletDetailScreen directly on the main tab instead of
    MainScreen → wallet-list → tap → WalletDetail. The back arrow is
    omitted (onBack prop unset) since there's no wallet-list to go back to.
  - Multi-wallet users still see the hero + Wallets|Activity list and the
    wallet-tap push.

Activity screen: category filter only applies on the originating tab:
  - If the user taps a specific MBA token card, Activity opens with
    (tab=MBA, categoryFilter=<cat>) — only MBA txs carrying that category
    show on the MBA tab.
  - Swiping to BCH or NBA now shows the FULL kind, not an empty filtered
    view. The hint row ("Filtered to <cat>… · swipe for all") appears
    only on the originating tab.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 21:11:08 +03:00
Local Dev
187850ab6c feat(wallet-ia): wallet detail screen + swipeable Activity tabs + centred FAB
Main → Wallet detail → Activity, per user spec.

MainScreen no longer renders the per-wallet asset breakdown. The donut +
hero + Wallets|Activity segmented pane are the whole main surface. Tapping
a wallet row switches into it (if it's not already active) and pushes the
new WalletDetailScreen.

WalletDetailScreen (new): per-wallet hero + BCH / Backed / Non-backed cards.
Reuses the AssetsPane component now extracted from MainScreen. Every card
header taps through to the Activity screen pre-tabbed to the matching asset
kind; an inline token row taps through with its categoryId as a filter.

ActivityScreen (restructured): three swipeable tabs — BCH | Backed | Non-backed.
HistoryItem classification uses session.registry.listTierA() to decide MBA
vs NBA; a BCH-only move lands in BCH. Horizontal swipe past 60 px (or 20%
of width) snaps to the next tab; vertical drag > 30 px cancels so page
scroll wins. End resistance at both ends. New initialKind + categoryFilter
props so WalletDetail can open the right tab + category directly.

Also fixes the central FAB sitting off-centre:
  - tabbar gives every column `flex: 1 1 0` (5 equal widths)
  - FAB wrapped in a `.fab-col` div so its fixed-circle pill stays dead
    centre regardless of neighbouring labels' intrinsic widths
  - "Agora" is now the same column width as every other tab

Removed from MainScreen: legacy inline AssetsPane function (dead since the
extract), unused glyphFor + mbaHeld/nbaHeld/unknownHeld memos,
onViewAgora prop (migrated to WalletDetail where the cards live).

Added shells/app/.claude/launch.json for the dev-server preview launcher.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 21:02:42 +03:00
Local Dev
63cbdb8230 feat(wallet): auto-update fixes — native CORS bypass, Settings check, release notes
Three pre-0.1.3 fixes to make the auto-update loop testable and useful
end-to-end on native. Diagnosed in the browser after shipping 0.1.2: the
WebView origin (https://localhost under Capacitor's androidScheme) couldn't
reach the manifest at silentmode.st, so the native banner never surfaced.

useReleaseCheck.ts changes:
  - Use CapacitorHttp on native to bypass WebView CORS (OkHttp path), fall
    back to fetch() in the browser so the service worker still sees it.
  - Expose checkNow() + an UpdateCheckState discriminated union so Settings
    can trigger a forced re-check and distinguish idle / checking /
    available / current / error.
  - versionCode as tiebreaker when semver matches — a hot-fix that forgot
    to bump package.json still ranks higher.
  - Throttle tightened from 6 h to 1 h.

UpdateBanner.tsx:
  - "what's new" expander renders manifest.notes (preserving linebreaks).
    Dismiss now moves to local React state instead of DOM manipulation.

CheckForUpdatesRow.tsx (new):
  - Settings › About row that bypasses both the throttle AND the
    dismissed-version flag, with inline feedback for every state.

release.sh:
  - Also uploads manifest to the Sia bucket (bns/deviant.x/wallet/releases/)
    so the Sia-hosted download hub keeps showing live SHA / size / date.
    Also stages a copy at silentmode.st/apps/deviant/wallet/releases/ for
    the silentmode mirror of the same hub.
  - SKIP_SIA=1 opt-out; DEVIANT_SIA_UPLOAD env for a non-default Argus path.

server-cors-fix.sh (new):
  - Idempotent one-shot that adds `Access-Control-Allow-Origin: *` to all
    JSON manifests under /apps/*/releases/ via a new angie location block.
    Includes config-test guard + rollback on failure. Mirrors the pattern
    already shipping on dl.silentmode.st. Run once on silentmode VPS to
    give the Sia-hosted hub + any other cross-origin consumer a working
    plain-fetch path (CapacitorHttp bypass stays in place as defence in
    depth).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 19:05:31 +03:00
Local Dev
600f4e7873 feat(deviant.x): /wallet/ download landing page
Dedicated hub at deviant.x/wallet/ (Sia-hosted, mirrored on
silentmode.st/apps/deviant/wallet/) with APK + PWA install cards, live
manifest fetch for SHA-256 + size + published date, "what's in it" summary,
and a chipnet warning.

Picked /wallet over /dl for SEO (keyword alignment with "deviant wallet"
searches), content-hub potential (room for /wallet/ios/, /wallet/docs/),
and consistency with the rest of the site's word paths (/potidaea/,
/artemision/, /katalogos/).

APK + manifest stay at silentmode.st (fast HTTP, 32 MB per release is
expensive on Sia); the download page uses absolute URLs so clicks resolve
the same whether the HTML was served from Sia or silentmode.st.

Template preserved at Parameters/webfiles/deviant_wallet_download.html so
future updates have a tracked source.

See snapshots/0277-wallet-download-page-deviant-x-wallet.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 16:36:47 +03:00
Local Dev
0a9c768001 feat(wallet): 0.1.2 — BNS Send, multisig cosign inbox, Aegis 600-coin cap
Three Aegis/Theseus-aligned additions in one release.

BNS resolution (bns://ariadne.x/dev/):
  shells/app/src/bns/resolver.ts resolves `alice.bch`-style names by racing
  the local Ariadne's Thread against the public silentmode.st indexer, with
  CashAddr + browse-target pickers. SendFlow auto-resolves BNS-shaped
  recipients and shows `alice.bch → bchtest:q…` on the review step.

Multisig cosign inbox (bns://aegis.x/docs/multisig/):
  New Settings › Multisig cosign screen wraps the existing cosign.ts +
  cosign-relay.ts library in a UI. User adds wallet descriptors
  (chain / m / xpubs), the screen opens a Nostr NIP-59 kind-1059 session
  per descriptor and lists inbound requests from the other devices.
  Decline sends an aegis.cosign.v1 `declined` message back so the asker
  isn't left waiting on silence. Signing requires the multisig keystore
  kind (parallel branch) and is intentionally disabled with that note.
  Adds nostr-tools@2.25.2 as a dynamic import.

Aegis 600-coin cap (bns://aegis.x/docs/consolidation/):
  sweep.ts picks up MAX_INPUTS_PER_TX = 600 (~89 KB at 149 B/input, under
  the 100 KB relay limit). buildSweepPlan sorts (address × utxo) pairs by
  value descending, keeps the top 600, and reshapes each ScannedAddress for
  the pass. SweepPlan carries inputCount + leftBehindCount + leftBehindSats;
  ConsolidateFlow + SweepFlow show "Leaves behind N coins · M BCH" with a
  one-liner telling the user to re-run until the remaining coins fall below
  the 149-bit break-even. Matches Aegis's spec verbatim.

Also bumps to 0.1.2 (versionCode 3) and publishes the APK + manifest to
silentmode.st/apps/deviant/releases/ so running 0.1.1 clients will see the
UpdateBanner tomorrow.

See snapshots/0276-wallet-bns-multisig-consolidate-cap.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 11:13:05 +03:00
Local Dev
d068c0c6ce feat(wallet): auto-update on new release + published deviant.x channel
Running wallets now check `silentmode.st/apps/deviant/releases/latest.json` on
launch (6 h throttle, no polling) and surface a non-blocking "Update available"
banner when a newer version is published. Native (Capacitor Android) opens the
APK URL in the system browser; PWA hard-reloads so the service worker swaps
revisions. Dismissal is per-version — once a user says "Later" on 0.1.1 they
won't see the strip again until 0.1.2 lands.

Release automation lives in `shells/app/release.sh`: it builds the web bundle +
APK, computes SHA-256 + byte length, writes `latest.json` with the current UTC
timestamp, uploads both to the canonical (`apps/deviant/releases/`) and legacy
(`deviant/releases/`) paths, and refreshes the `deviant-wallet-latest.apk`
symlink.

Already published for 0.1.1:
  https://silentmode.st/apps/deviant/releases/deviant-wallet-0.1.1.apk
  sha256 48525afea1c67d89d073637b2d39cd93630bbbe056e69e94cc673ba77aa32f0b

Also gitignores shells/app/downloads/ (APK + screenshot staging).

See snapshots/0275-wallet-auto-update-published.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 02:48:26 +03:00
Local Dev
d64d519297 chore(wallet): release 0.1.1
First release under the policy agreed 2026-10-23: patch-only semver
bumps (0.1.1, 0.1.2, …), new release only when several
updates/fixes have piled up.

What piled since 0.1.0-beta.1:
- Mainnet in the network picker (f78c5bd4)
- Fiat 3-step deposit + Withdraw tabs (503f458)
- Anthemus dual asset pickers + honest exec state (63ae6cd)
- In-app QR camera (a5474b1) + the camera/notifications permissions
  with proactive launch-time prompt (a7c0a0e)
- Brand icon from bns://deviant.x (0a0ee52) + splash regen
- Three-tab Agora (My Assets | Potidaea | Anthemus) made visible via
  rebuild + redeploy
- ADR-0057 metadata stack end-to-end:
  • NFT-commitment emission (6e57e08)
  • Sia-backed primary BCMR URL + server mirror (74a15d5, 8bd292d)
  • BCMR consumer in the shell (6f97946)
  • Artemision wizard opt-in (f9be2bd)
- Main-screen IA (8e45fd1): legend trimmed, ~ moved left, Assets|Wallets
  gate retired
- Dedicated AddWalletFlow wizard replacing the full-Settings dump
  (0a67246)
- Beta banner retired (0a67246)
- Aegis cosign transport + ADR-0056 scaffolding (f1ea4d6)

Version bumped in three places that must agree:
- src/buildInfo.ts (BUILD.version = '0.1.1')
- package.json (0.1.1)
- android/app/build.gradle (versionCode 2 / versionName '0.1.1')

CashFusion stays gated (FUSE_ENABLED = false). Deferred — real fusion
backend wire-up is weeks of work and isn't on the current roadmap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 02:37:08 +03:00
Local Dev
0a672469fd feat(wallet-main): dedicated AddWalletFlow + retire the beta banner
Two user-directed changes:

1. "+ Add / Import wallet" now opens a step-by-step wizard, not the
   whole Settings page.

   NEW shells/app/src/screens/AddWalletFlow.tsx — modal flow modelled
   on SendFlow/SweepFlow with a 2-step shape:
     Step 1: pick one of four paths (new account on existing seed ·
             import seed phrase · import WIF · bulk import encrypted
             keystore)
     Step 2: enter the one thing that path needs + the app PIN
   The four paths map 1:1 to the existing MultiKeyStore verbs
   (addHdWallet / importSeed / importWif / ImportFromKeystoreFlow) —
   zero keystore API churn.

   NEW `add-wallet` kind on the PushedScreen union + case in
   renderPushed; the trigger button routes here when walletStore is
   available, falls back to the old onManageWallets (Settings route)
   when the device is on the single-session CapacitorKeyStore path.

   MainScreen.tsx: new onAddWallet prop threaded through WalletsPane
   to the "+ Add / Import wallet" button; onManageWallets kept for the
   gear-icon / "Manage" paths. ImportSummary type exported from
   ImportFromKeystoreFlow so AddWalletFlow can type its bulk callback.

   New CSS in shell.css for .aw-path tiles + .aw-seed select rows.

2. Retire the persistent beta banner.

   BetaBanner.tsx now returns null unconditionally. Users explicitly
   asked for the "BETA · chipnet · test coins only" strip to go — it
   was glued to every screen since the mainnet picker was unlocked,
   and the warning now lives inside Settings → Build Info instead.
   Component kept as a passthrough so a future "danger mode" strip
   can reuse the same mount point without touching App.tsx.

Verified live on silentmode.st/apps/deviant/ (bundle main-CTTCotYS.js):
- aw-path styles present
- add-wallet PushedScreen case present
- beta-banner class gone, warning text gone

tsc clean. Deployed to prod.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 02:22:43 +03:00
Local Dev
8e45fd1080 feat(wallet-main): trim legend copy, right-align numbers, drop Assets|Wallets gate
Three user-directed main-screen IA cleanups (2026-10-23):

1. Legend copy — strip the "· backed" / "· non-backed" tails from the
   MBA + NBA rows. The acronyms are self-contained; the longer form was
   duplicating context that already lives on the Backed Assets accordion
   below.

2. Right-align the number column across all three legend rows. The old
   NBA row rendered as "~ 0.000" with the "~" sitting inside .val, which
   pushed the digits left of the MBA/BCH columns above. The marker is
   now a sibling of .val (new .approx class) so it takes the
   margin-left:auto push, and .val sits snug next to it — numbers
   visually right-align whether or not the row is market-estimated.

3. Retire the Assets|Wallets segmented gate at the top of Main. Users
   wanted the asset rows (BCH / Backed / Non-backed) to be always-on
   content, not something behind a tab. Both AssetsPane and WalletsPane
   now render unconditionally; the Wallets|Activity sub-toggle inside
   WalletsPane becomes the only segmented bar on the page.
   - `Pane` type retired
   - `pane` state + `setPane` callers removed
   - `onViewWallets` call-site in AssetsPane became a no-op (the Wallets
     section is already on-screen below)

Verified live on silentmode.st/apps/deviant/ (bundle main-CXiApcxo.js):
- "MBA · backed" and "NBA · non-backed" removed from Main legend
- .approx class present (bundle search confirms)
- subtab class gone (Assets|Wallets gate removed)
- The one remaining "MBA · backed" string is in the Artemision wizard's
  Backing dropdown — correct to leave, it's a different context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-23 00:28:33 +03:00
Local Dev
f9be2bd593 feat(wallet-metadata): Artemision opts into the ADR-0057 commitment on every MBA genesis
The wizard already classifies the user's intent via mint/supply/backing
axes (DEA / PLA / MCA / MTA) — now it also threads that `assetClass`
into the `GenesisIntent` so the planner can emit the 40-byte metadata
commitment on the minting NFT. Zero new UI copy; from this commit on,
every non-adjustable-fee MBA minted through Artemision carries a fully-
on-chain, offline-resolvable (ticker + decimals + class) record on its
minting-baton NFT.

- shells/app/src/screens/ArtemisionScreen.tsx:
  • Import classifyAssetClass from @core/token/assetClass
  • Compute mbaAssetClass = classifyAssetClass({backing:'mba', mint, supply})
    at plan time
  • Pass it to planGenesis as `assetClass`, mutually exclusive with
    `adjustableFee` (that path uses the commitment for live feeBps —
    the planner's own guard refuses the overlay; the UI mirrors it
    at the call site for clarity)

- wiki/decisions.md: ADR-0057 "UI opt-in path" section updated from
  "deferred" → "SHIPPED", and the NBA case called out as a follow-on
  because ADR-0054 (capped NBA cap-as-consensus) skips the minting NFT
  entirely for capped assets so the commitment has no carrier.

Regression:
- Full Vite build clean (shells/app)
- 47/47 core tests (test/unit/wallet/metadata-commitment + genesis)
- 31/31 shells/app BCMR-resolver tests
- tsc clean on shells/app

With steps 1 + 2 + 3 + UI opt-in all shipped, the metadata stack is
end-to-end for MBAs: Artemision wizard → planner emits NFT commitment +
BCMR OP_RETURN → BCMR uploaded to Sia via /api/bcmr-upload → consumer
in shells/app resolves it on first balance load → ticker/name shown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 22:52:01 +03:00
Local Dev
74a15d592e feat(bcmr): Sia-backed primary URL — ADR-0057 step 2 fully shipped
Server-side mirror hook on bns-gateway landed, so the "primary vs mirror"
split from the previous commit collapses: the primary URL is now itself
Sia-backed via the BNS gateway. One URL in the OP_RETURN, zero HTTPS
single-point-of-failure, every genesis's metadata survives a silentmode.st
outage because any bns-gateway (Theseus, Ariadne, self-hosted) resolves
the same bytes from Sia and the on-chain SHA-256 confirms integrity.

Server (deployed to silentmode:/opt/bns-gateway/public-gateway.mjs,
service restarted + active):
- NEW /api/bcmr-upload/<network>/<categoryId>.json handler modelled on
  the existing /api/icon-upload sibling. POST only, 32 KB cap, strict
  path validation (chipnet|mainnet + 64 lowercase hex), parse-and-
  discard JSON validity check, PUT to Sia at
  bns/deviant.x/bcmr/<network>/<categoryId>.json.
- Returns { url, sha256, bytes, path }. No auth: on-chain sha256 is the
  trust root, so a wrong-hash upload is simply ignored by every reader.
- Backup written: public-gateway.mjs.bak-bcmr-<timestamp>.

End-to-end verified (browser → gateway → Sia → gateway → browser):
- POST 200, Sia PUT success, response carries the public URL
- GET from the public URL returns 200, 483 bytes
- Client SHA-256 == response.sha256 == fetched-bytes SHA-256
- Negative paths: wrong network/short/upper-case categoryId → 400,
  GET → 405, invalid JSON → 400 with parser detail

Client (design/potidaea/src/bcmr.ts + genesis.ts):
- bcmrPublicUrl now returns
  https://navigate.st/bns/deviant.x/bcmr/<network>/<categoryId>.json —
  the Sia-backed primary. Replaces the never-working silentmode.st/bcmr
  URL that was 404'ing in production.
- bcmrSiaUrl kept as an alias of bcmrPublicUrl so step 2's earlier
  opt-in callers still compile.
- Genesis emitter: dropped the VITE_BCMR_SIA_MIRROR flag — the primary
  is Sia-backed now, no gating needed. Encoder stays variadic so future
  mirrors (IPFS pin, alternate gateway) can be added without API churn.

ADR-0057 updated: step 2 marked SHIPPED with the gateway + Sia +
end-to-end verification notes.

Potidaea tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 22:21:55 +03:00
Local Dev
8bd292d3a1 feat(bcmr): variadic multi-URL + Sia mirror helper (ADR-0057 step 2)
Encoder changes to prepare the BCMR OP_RETURN to carry two URLs so a
silentmode.st outage no longer takes our token metadata with it. Client
side is ready; the server-side Sia mirror (POST to /api/bcmr-upload
also writing the Sia bucket bns/bcmr/<network>/) still TODO — the opt-
in flag below keeps current deploys unchanged until that lands.

- design/potidaea/src/bcmr.ts
  • bcmrOpReturnChunks(bcmr, ...urls): now variadic. Returns [marker,
    hashHex, ...urls]. Throws when called with zero URLs.
  • bcmrSiaUrl(categoryId, net): forward-compatible Sia-mirror URL
    (https://navigate.st/bns/bcmr.deviant.x/<network>/<catId>.json).
    Resolves whichever bytes the gateway's s3 record returns from the
    Sia bucket. Harmless to emit now: a wallet that fetches before the
    mirror is live sees a 404 and falls through to the primary URL —
    the ADR-0057 step 3 consumer we just shipped handles multi-URL
    fallback cleanly.

- design/potidaea/src/genesis.ts
  • Reads VITE_BCMR_SIA_MIRROR=1 at build time; when set, pushes BOTH
    the primary and the Sia URL into the OP_RETURN. Default off so no
    existing deploy's behaviour changes.

- wiki/decisions.md
  • ADR-0057 "Deferred" section updated: step 2 marked client-ready;
    server-side mirror hook called out as the remaining task.

Runtime sanity-check with the real encoder:
  single-url: [BCMR, 0x…, primaryUrl]                      (3 chunks)
  two-url:    [BCMR, 0x…, primaryUrl, siaUrl]              (4 chunks)
  three-url:  variadic works end-to-end
  zero-url:   correctly refused with explanatory message

Potidaea tsc clean. No regressions in callers (bcmrOpReturnChunks's
old (bcmr, url) call site is a strict subset of the new (bcmr, ...urls)
signature).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 21:30:55 +03:00
Local Dev
6f9794693f feat(wallet-metadata): BCMR consumer — unknown tokens resolve to real tickers
Implements step 3 of ADR-0057's three-layer metadata split. The Deviant
shell already emits CHIP-2023-04 BCMR OP_RETURNs on genesis (via
design/potidaea/src/bcmr.ts) but never READ them back, which is why
every token outside the three hardcoded chipnet seeds (ASM, ASM2, CAP)
rendered as "Unknown token (abcd…ef12)". Now any BCH ecosystem token
with a BCMR anchor resolves to its real ticker + name + decimals
automatically on first discovery.

NEW shells/app/src/token/bcmr-resolver.ts:
- resolveBcmr(categoryId, provider, opts?): fetch genesis tx (categoryId
  == genesis txid per CashTokens) → scan outputs for BCMR OP_RETURN →
  try each URL in on-chain push order → verify SHA-256 matches the
  on-chain anchor → parse CHIP-2023-04 JSON → return name/symbol/
  decimals/icon.
- findBcmrAnchor + parseBcmrOpReturn: minimal BCH tx + script parser
  hardened against adversarial scripts (truncated pushes, non-push
  opcodes, OP_PUSHDATA4, wrong markers, hostile URL schemes).
- parseBcmrJson: tolerant extraction of the CHIP identity (handles
  multi-revision identities by ISO-sort, clamps nonsense decimals to 0,
  requires name + symbol).
- bcmrToTokenTierA: translates to the TokenTierA shape the registry
  expects. backing: 'token' (naming only; actual MBA peg verification
  stays with the classifier).
- In-memory cache: positive = session-forever (BCMR anchor is immutable
  modulo authchain, which we don't follow yet — see ADR-0057 deferred),
  negative = 10 min TTL so transient fetch errors don't thrash.
- Bounded fetch (≤32 KB) + explicit SHA-256 refuse so a hostile host
  cannot force a large download or substitute bytes.

NEW shells/app/src/token/useBcmrResolve.ts:
- React hook: for each unknown token in the current balance, kick a
  background resolve exactly once per session. On success inject into
  session.registry.add(..., 'replace'). The AgoraScreen's existing
  tierAToDetail path takes over on the next render — no display logic
  changes needed.

NEW shells/app/test/bcmr-resolver.test.ts: 31/31 green.
- OP_RETURN parser rejects: non-OP_RETURN, wrong marker, wrong-length
  marker, non-32B hash push, truncated pushes, OP_PUSHDATA4, non-push
  opcodes, javascript:/file:/ URLs.
- Finder picks the BCMR output among noise + non-BCMR outputs.
- JSON parser handles multi-revision identities, missing/nonsense
  decimals, missing name/symbol.
- End-to-end: hash match on correct bytes, URL fallback on mismatch,
  all-URLs-failed path, size cap, memoisation (second call → zero
  additional fetches).

shells/app/src/App.tsx: single-line wire-up —
  `useBcmrResolve(session, balance?.unknownTokens);`

DEFERRED (next sessions, documented in ADR-0057):
- Authchain following: a BCMR update after genesis is NOT currently
  picked up; the first-resolved anchor is canonical for the session.
- Step 2: Sia mirror in the BCMR OP_RETURN emitter so our own tokens
  resolve even when silentmode.st is dark.
- UI opt-in (one-liner in ArtemisionScreen.tsx) to thread assetClass
  into GenesisIntent so new MBAs get the ADR-0057 commitment too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 21:10:43 +03:00
Local Dev
6e57e08884 feat(wallet-metadata): NFT-commitment metadata on genesis — ticker + decimals + class fully on-chain
Implements step 1 of ADR-0057's three-layer metadata split. The 40-byte
minting-NFT commitment, previously set to '' on every genesis, now carries
the irreducible-minimum identity so a wallet with no network at all can
show "ASM (DEA, 2 dp)" for an on-chain amount — no BCMR URL fetch, no
local registry lookup.

Binary layout (pinned per ADR-0057 §1):
    byte  0:    magic   = 0x44 ('D')
    byte  1:    version = 0x01
    byte  2:    decimals (uint8, 0..18)
    byte  3:    assetClass byte (ASSET_CLASS_BYTE table)
    bytes 4..15: ticker, 12 UTF-8 bytes right-padded with 0x00 (ASCII only)
    bytes 16..39: reserved zero (24 B for future flags)

Disambiguation vs AdjustableFeeCapped (ADR-0011) which already uses the
commitment for live feeBps: length (40 vs 1-2) + magic byte. The genesis
planner enforces mutual exclusion at write time — assetClass is honoured
only when adjustableFee is NOT set.

Changes:
- NEW src/wallet/metadata-commitment.ts: pure encoder/decoder + format
  constants, no deps outside src/token/assetClass. Decoder returns null
  for anything not a well-formed metadata commitment (empty, short,
  wrong-magic, wrong-version) so callers can fall through to BCMR / local
  registry / "Unknown".
- NEW test/unit/wallet/metadata-commitment.test.ts: 32/32 pass. Round-
  trips every asset class, pins the exact byte layout, exercises every
  rejection case including the AdjustableFee-ScriptNum ambiguity.
- src/wallet/genesis.ts: GenesisIntent gains optional `assetClass`;
  prepareGenesis populates GenesisPlan.metadataCommitmentHex when the
  caller provides it AND adjustableFee is not set. Encoder failures
  (ticker too long, non-ASCII, decimals out of range) become INFO
  warnings on the plan — never block genesis.
- src/wallet/wallet.ts: createAsset picks the NFT shape by precedence:
  (1) adjustable-fee ScriptNum commitment, (2) ADR-0057 metadata
  commitment, (3) empty default (prior behaviour).
- wiki/decisions.md: ADR-0057 appended — the three-layer split, the
  binary layout, the AdjustableFee disambiguation rule, and what steps
  2 (Sia BCMR mirror) and 3 (BCMR consumer in shells/app) still owe.

Backward-compatible by construction: callers that don't set assetClass
get the exact prior commitment: '' default. 47/47 tests green (existing
genesis.test + new metadata-commitment.test). Core tsc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 21:00:32 +03:00
Local Dev
549e906802 docs(snapshots): 0281 Potidaea balance-flash fix
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 19:31:36 +03:00
Local Dev
a1fffce620 fix(potidaea): drop pending-swap overlay + fix idle-wipe balance race
Removed 251 lines of structurally fragile code that was causing the
"0.82 BCH flash → empty wallet" race on page load inside Theseus
Navigator.

Root cause: the pending-swap optimistic overlay (`pendingSwaps[]`,
`loadPendingSwapsFromStorage`, `reapPendingSwaps`, etc.) persisted a
pre-swap BCH snapshot across page lifetimes via localStorage. On
reload, the overlay rode on top of a stale electrum snapshot and
painted the "lifted" balance (0.82), then the hard-timeout branch or
the next retry's reap flipped it to zero. Compounded by `wc.on('idle')`
auto-wiping balances when no local signer was present — in Theseus
the user has an Aegis attachment, not a WIF, so an idle emit from
the initial WC state would nuke a painted balance.

Changes:

* Deleted the entire pending-swap overlay layer in main.ts:
  - interface PendingSwap, pendingSwaps[], PENDING_SWAPS_KEY
  - loadPendingSwapsFromStorage, savePendingSwapsToStorage
  - reapPendingSwaps, reconcilePendingViaTxid
  - applyOptimisticSwapDelta, registerPendingSwap
  - currentBchSatsFromAssets, PENDING_HARD_TIMEOUT_MS
  - overlay application in paintBalance (lines 493-499, 521-533)
  - broadcast-handler call-site

  Rationale: the swap flow still refreshes twice (at 2.5s + 8s) after
  broadcast; chipnet Fulcrum's mempool indexing is reliably caught by
  that window in practice. A manual Refresh button on the chip menu
  covers the long-tail miss. The overlay's cost (stale-overlay flash,
  localStorage persistence, two-call reconcile loop, bigint
  serialization) outweighed its benefit.

* paintBalance now just divides by 1e8 and writes to the chip; no
  overlay math, no bonus accounting, no reap.

* refreshBalance no longer awaits reconcilePendingViaTxid before
  fetching UTXOs — removes a per-call RPC round-trip.

* wc.on('idle') no longer auto-wipes balances. The initial state is
  idle; a transient disconnect of a WC relay shouldn't nuke the UI.
  The explicit disconnect path (menu X on the WC row) now routes
  through a wrapper that zeros only when the last wallet goes.

* Boot order: zeroAllBalances runs FIRST (before renderChip and all
  the wire-up calls), not after. The old order had a small window
  where a stale fixture balance could flash. The redundant second
  zeroAllBalances call later in boot is removed.

* onSignerChanged's refresh retries collapsed from 4 (0ms, 700ms,
  2500ms, 6000ms) to 2 (0ms, 2500ms). The retry storm was defensive
  cruft from before the shared Electrum connect-any pool.

* Broadcast handler's post-swap retries collapsed from 3 (1.5s, 5s,
  12s) to 2 (2.5s, 8s). Same reasoning.

Build: typecheck clean, bundle 902 → 900 KB, main.ts 3763 → 3512
lines (-251). Deployed to Sia at bns/potidaea/ for
navigate.st/bns/potidaea.asm/ serving.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 19:30:21 +03:00
Local Dev
f1ea4d6aa8 feat(wallet-multisig): adopt Aegis cosign transport + scaffold signing layer
Implements the aegis.cosign.v1 protocol specified at bns://aegis.x/multisig/
and lays the typed foundation for the signing layer that will build on it.

TRANSPORT (B — this commit, fully working):

- src/multisig/cosign.ts: HKDF-SHA256 channel derivation over canonical
  {p, chain, network, m, xpubs[sorted]} JSON; AES-256-GCM envelope with
  channel id authenticated as AAD; three message kinds (request / signed /
  declined); 24-hour replay guard; validator rejects messages failing any
  check without discriminating the reason (so an attacker learns nothing
  about which guard caught them).
- src/multisig/cosign-relay.ts: Nostr NIP-59 gift-wrap (kind 1059)
  transport. Fresh per-session Nostr signing key so relays cannot link
  conversations. SINGLE filter in subscribeMany per the spec's §05 warning
  (array form silently matches nothing while publishing succeeds).
- test/multisig-cosign.test.ts: 17/17 — reproduces the spec's §08 vector
  byte-for-byte (channel id 8cca5fb6…, envelope round-trip with the fixed
  all-0x2a IV, plaintext {kind:request, at, txId, from, note:null, pst}).
  Also covers tamper/replay rejection and the end-to-end session verbs.
- MPL-2.0 to match the Aegis reference code; any wallet speaking the spec
  can co-sign with Deviant and vice versa.

SIGNING LAYER (A — scaffolding only; next session's build):

- src/multisig/types.ts: MultisigComposition, MultisigWalletFields, the
  wire DeviantPst JSON shape, CosignerIdentity, and bip48CosignPath. These
  are the stable contract the covenant, the libauth bridge, and the UI
  will all build against.
- Script form picked per-wallet at creation: `covenant` default
  (DeviantMultiSig.cash — unlimited m-of-n, first-class CashTokens) or
  `native` opt-in (BCH OP_CHECKMULTISIG P2SH-20, 15-of-20 cap).
- Wire PST = Deviant JSON aligned with Aegis's example shape, internal
  signing on libauth's partial primitives.
- Keystore model = new `multisig` kind in MultiKeyStore beside
  `independent` + `hd-account`; BIP-48 branch (m/48'/145'/N'/2'/0/0) for
  each cosigning identity.

ADR-0056 appended to wiki/decisions.md with the full rationale, the
decisions locked, and the open questions the next session must settle
(DeviantMultiSig.cash covenant draft + chipnet validation; libauth PST
signing adapter; UI flow; keystore migration).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 18:14:08 +03:00
Local Dev
9412836f02 feat(potidaea): sign-in via Theseus ID / Aegis wallet (client-side)
Detects the two session-wide preloads Theseus Navigator installs on every
page and surfaces matching attach-menu entries:

  window.theseusId  — "Sign in with Theseus ID"
                       signIn({projectId:'potidaea', nonce}) returns a
                       project-scoped tid:q… account + BIP-137 message.
                       Identity-only, no spend authority. Phase 1.

  window.bcnr       — "Attach Aegis wallet (in Theseus)"
                       requestAccount() returns the user's cashaddr.
                       Subscribed as a read-only attachment today; full
                       swap-signing lights up when the Aegis addon ships
                       the bch.signTransaction handler specced in the
                       SilentMode-repo DESIGN-aegis-bch-signtx.md.

New files:
  design/potidaea/src/theseusHost.ts  typed bridges + feature detection
                                      (hasTheseusId, hasAegis,
                                      hasAegisSignTransaction) + call
                                      wrappers (signInWithTheseusId,
                                      requestAegisAccount,
                                      aegisSignTransaction).

Changes:
  design/potidaea/src/main.ts  imports theseusHost, conditionally renders
                               two new menu rows in the wallet dropdown
                               (hidden in non-Theseus browsers), wires
                               click handlers, mini-toast feedback.

The attach-Aegis row's sub-label distinguishes between
  "in Theseus - swap-ready"            when hasAegisSignTransaction()
  "in Theseus - balance only (swap     when the preload exists but the
   via WC until Theseus update ships)"  signTx handler does not.

Follow-up (SilentMode repo, separate session): implement the three files
specced in DESIGN-aegis-bch-signtx.md - Aegis addon handler, bcnr-preload
bridge, main.js IPC route. The dApp side is in place to use them the
moment they land.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 14:27:01 +03:00
Local Dev
cdfaa95647 site(potidaea): title size parity + cyan Potidaea + uniform-blue Liquidity
- Restore h1.hero-title to clamp(38px, 6vw, 84px) so Potidaea matches
  Artemision / Katalogos / Anthemus / Agora (I had shrunk it last
  turn — correction).
- 'Potidaea' wordmark now takes the cyan 'Liquidity' glow (#3ec7ea)
  — unifies the title with the liquidity concept it owns.
- Collapse the two-tone 'Ocean Deep' blue + 'Liquidity' cyan split in
  the hero-sub into a single 'Ocean Deep Liquidity' deep-blue phrase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 14:16:09 +03:00
Local Dev
67d9554363 site: shrink Potidaea hero title + Launch links on BNS instead of navigate.st
- potidaea hero-title: clamp(38px, 6vw, 84px) → clamp(32px, 4.5vw, 56px).
  Max dropped from 84px → 56px so the wordmark fits with the hero-sub
  on a single screen without dominating the composition.
- Every 'Launch app →' / 'Launch Potidaea →' / 'Wallet' link now uses
  bns://potidaea.asm/app.html instead of hard-coding the navigate.st
  gateway. On a BNS-aware browser (Theseus, bcnr relays, etc.) the
  link stays inside the BNS resolver instead of jumping to a specific
  gateway host.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:32:05 +03:00
Local Dev
178ac1a791 site(potidaea): CTA = 'Launch Potidaea' (drop Orbital Swap wording)
Hero + mid-page CTAs now say 'Launch Potidaea →' instead of 'Launch
Orbital Swap →'. Footer line matches: Potidaea · Automated Swap
Mechanism on Bitcoin Cash · chipnet beta.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:27:29 +03:00
Local Dev
a5474b11aa fix(wallet-android): make in-app QR camera actually show through
Three issues broke the previous commit's "in-app camera" shipping as a
dark non-fullscreen rectangle:

1. WebView was opaque. The @capacitor-mlkit/barcode-scanning plugin's
   startScan() renders a native camera preview UNDER the WebView, but on
   Android the WebView paints an opaque background by default — nothing
   below it ever shows. MainActivity.java now sets
   bridge.getWebView().setBackgroundColor(Color.TRANSPARENT) on create.
   The HTML/CSS keeps painting its own backgrounds, so this is only
   visible when the HTML side ALSO goes transparent (during a scan).

2. The .phone wrapper has an opaque dark-blue gradient
   (linear-gradient(180deg, rgba(19,26,42,.55), rgba(10,15,28,.55))) that
   stayed painted on top of the (now-transparent) WebView, blocking the
   preview. The scanner CSS now null-chains the whole stack
   (html → body → #root → .app-root → .phone) via background: transparent
   + background-image: none + border: 0 while body.barcode-scanner-active
   is set.

3. Modal was pinned to the .phone 420px-max column — on a 1080×2400
   phone the scan frame + Cancel sat inside a narrow central strip while
   the native preview filled the whole screen. The modal now escapes
   with position: fixed; inset: 0; width: 100vw; height: 100dvh. The
   scan frame is centred at min(72vw, 320px), buttons sit at the bottom
   with a backdrop-blur chip so they stay legible against bright scenes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:25:47 +03:00
Local Dev
3fddd71acc site: Anthemus opens as a landing + sub-nav floats free (no box)
- /anthemus/index.html was a 41-line meta-refresh redirect to the
  wallet kiosk that never got the wallet up, so Anthemus tab appeared
  broken. Replace it with the full landing content (previously only
  reachable at /anthemus/about/), with ../ paths adjusted for the new
  depth. The nav aria-current now sits on /anthemus/ itself.
- shared-header.css: drop body { padding-right: 200px } so the
  sub-nav never shifts main-page body text. Strip the sub-nav
  background / border / border-radius / box-shadow on both the
  desktop right-side rail and the mobile top-band — links float
  directly on the dark sky. Mobile keeps a soft blur-only backdrop
  so links stay readable when content scrolls under them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:20:20 +03:00
Local Dev
8c214c89cb site(potidaea): hero-sub — Bitcoin Cash in BCH green, 'rate' singular
- Split 'CashScripts' back into 'Cash Scripts' (two words) and wrap
  'Bitcoin Cash' in a span.bch so it renders in BCH's native brand
  green (#0ac18e) — the existing .hero-sub .bch style covers it.
- 'operates at fixed rate' (singular, matches the rate-is-a-constant
  framing — one rate per asset covenant).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:18:06 +03:00
Local Dev
0bc3697d5a site(potidaea): Bitcoin Cash in native BCH green, Scripts separate
Hero-sub rewrite:
  'Zero-Counterparty Ocean Deep Liquidity Automated Swap Mechanism on
   Bitcoin Cash Scripts that operates at fixed rates by consensus. No
   order book, no liquidity pool, no maker, no slippage. The covenant
   is the engine.'

- Drop 'via' and 'based' — reads as a compound noun chain
- 'CashScripts' split to 'Cash Scripts' (two words)
- 'operate' → 'operates'
- New .hero-sub .bch rule paints 'Bitcoin Cash' in #0ac18e, the
  bitcoincash.org brand green, with a soft matching glow

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 03:15:28 +03:00
Local Dev
7a6cb43e71 fix(brand): add trailing-slash guard so Theseus loads brand/mark/*.png
Symptom: visiting potidaea.asm/brand in Theseus shows the Potidaea trident
PNG as a broken image (alt text visible). The 5 SVG explorations below
still render because they're inline SVG, not external files.

Cause: Theseus serves BNS URLs as the user typed them. When the URL has no
trailing slash — potidaea.asm/brand instead of potidaea.asm/brand/ —
<base href="./"> anchors the relative resolution at '/' (parent of '/brand'
treated as a filename) rather than at '/brand/'. Images with src="mark/..."
then resolve to '/mark/...' which 404s.

The BNS gateway at navigate.st/bns/ already normalizes with a 302 that adds
the slash, and nginx-served paths at silentmode do the same. Theseus does
not. So the issue only shows under native BNS browsing.

Fix: inline <script> at the TOP of <head> (runs before any <img>, <link>, or
further <base> is parsed by the preloader) that redirects to the slash
version when the pathname is exactly '/brand'. No-op under the gateway
(pathname is '/bns/...' there) or when the slash is already present.

Applied to both pages — Deviant brand had the same latent bug, same guard
closes it.

Verified in-browser via gateway: location.href resolves to .../brand/,
trident PNG naturalWidth=1373 (loaded). Theseus verification requires a
reload from the user side; the script's logic is trivial and side-effect-
free.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 02:01:46 +03:00
Local Dev
04a157dcac site: swap body font to Ubuntu to match bitcoincash.org
- shared-theme.css: universal [data-theme='signal'] *{} switches from
  'DM Sans' to 'Ubuntu' (DM Sans kept as a graceful fallback). Mono
  labels + Fraunces-leaning surfaces are overridden later by more
  specific selectors and are unaffected.
- shared-header.css: 'Ubuntu, DM Sans, ...' in the sub-nav links / CTA
  / top-anchor stack.
- Every subpage + main landing: Google Fonts <link> gets
  'Ubuntu:wght@300;400;500;700' added, DM+Sans kept for fallback and
  DM+Mono stays for the main landing's mono labels.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:53:11 +03:00
Local Dev
0501c68a96 site(potidaea): tweak hero-sub — Zero-Counterparty leads, drop 'Swaps'
'Zero-Counterparty Ocean Deep Liquidity via Automated Swap Mechanism
based on Bitcoin CashScripts that operate at fixed rates by consensus.
No order book, no liquidity pool, no maker, no slippage. The covenant
is the engine.'

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:50:17 +03:00
Local Dev
bf62a12b5c site(potidaea): rework hero-sub — lead with Ocean Deep Liquidity Swaps
'Ocean Deep Liquidity Swaps via Automated Swap Mechanism based on
Bitcoin CashScripts that operate at fixed rates by consensus.
Zero-Counterparty, no order book, no liquidity pool, no maker, no
slippage. The covenant is the engine.'

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:49:22 +03:00
Local Dev
69778d0795 site: fix sub-nav stickiness + drop eyebrow pills
- shared-header.css: on <=900px flip the sub-nav from sticky to
  position:fixed (sticky was broken by body overflow-x:hidden on the
  subpages, letting the band scroll away). CTA keeps the right side
  via order:2. body:has(> .sub-nav) gets 112px top padding to clear
  both bars on narrow viewports.
- Drop the eyebrow pill (<div class='eyebrow'>...dot...</div>) from
  every subpage: potidaea, artemision, katalogos, agora, anthemus/about.
  The h1 now carries the surface identity on its own.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:48:41 +03:00
Local Dev
a45d3ebca5 feat(wallet-android): in-app QR scanner instead of Google Code Scanner
The scanner was calling BarcodeScanner.scan(), which hands off to the
Google Code Scanner module — a separate system activity that opens
outside the WebView with Google-branded chrome. On Android it looked
like leaving the Deviant app to use Google Camera, and the Deviant
scan-frame + Cancel button were invisible because the entire overlay
was replaced by Google's UI.

QrScanner.tsx now uses startScan() + the barcodesScanned listener. The
plugin renders the live camera preview UNDER the WebView; a body class
(`.barcode-scanner-active`) hides the React app and selectively un-hides
the scanner modal (`.barcode-scanner-modal` opt-out), so the user sees
the Deviant-styled scan frame + Cancel button on top of the native
preview — in-app, no handoff.

- Rear camera (LensFacing.Back) by default — better focus on close QRs
- Full teardown on close/unmount: stopScan + listener detach + body
  class removal, each wrapped so any single failure can't strand the
  user with a transparent app behind a dead camera
- CAMERA permission still requested at app launch (prior commit); this
  commit re-checks it inside the scanner so a revoke-from-Settings
  surfaces as a readable error instead of a silent black screen

shell.css: appends the visibility/background rules that implement the
transparent-webview trick, scoped to body.barcode-scanner-active so no
other screen is affected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:41:00 +03:00
Local Dev
9f70b6d929 feat(potidaea-brand): add Potidaea-specific brand page + revert Deviant cross-contamination
Previous commit (f7529ef) made two mistakes: served the Deviant brand page
under potidaea.asm/brand/ (treating Potidaea as if it had no brand of its
own), and injected a Potidaea 'Products' section into the Deviant brand
page (bleeding the product into the parent-brand kit).

This commit fixes both:

1. New site/potidaea/brand/ — Potidaea-specific brand exploration page
   based on the design the user authored (claude.ai/artifact/Xe1YF34x...).
   Shows five SVG mark directions (Trident-Orbit, Wave Rings, Ψ Monogram,
   Nautilus Spiral, Amphora) and the current rendered concept (neon-cyan
   3D trident with an upward-arrow prong) featured prominently at the top
   of the grid. Uses the oceanic tide-blue accent + Fraunces display +
   JetBrains mono palette the artifact proposed.

   Content: index.html + mark/potidaea-trident-neon.png (raster, 1373x784).

2. Reverted the 'Products — Potidaea' section from site/deviant/brand/
   index.html. Deviant brand page stays Deviant-only; Potidaea brand page
   stays Potidaea-only. The two brands have separate identities.

Deploy state (not in this commit — Sia uploads):
  - potidaea.asm/brand/      — now serves Potidaea brand page (correct)
  - deviant.x/brand/         — serves Deviant brand page (unchanged, correct)
  - potidaea.asm/branding/   — redirects to /brand/ (same stub, now resolves
                              to the Potidaea page as intended)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:34:28 +03:00
Local Dev
a7c0a0eea5 feat(wallet-android): camera + notification permissions, prompted at launch
The APK had neither declared, so Settings → Apps → Deviant → Permissions
was effectively empty. First-time users would hit a dead end on "Scan QR"
and never receive a system notification for incoming WalletConnect /
WizardConnect sign requests.

Manifest additions:
- android.permission.CAMERA (for @capacitor-mlkit/barcode-scanning)
- android.hardware.camera + .front as uses-feature required=false
  (declares front-camera support without blocking cameraless installs)
- android.permission.POST_NOTIFICATIONS (required API 33+)

App.tsx: new one-shot startup effect on native platforms that calls
BarcodeScanner.requestPermissions() + Notification.requestPermission()
once per install (tracked via localStorage key
`deviant.native-perms.requested`). Android requires user consent via the
system dialog — this triggers both dialogs on first launch so Settings
shows them as granted from the start, instead of the user hitting the
dead-end later.

capacitor.build.gradle + capacitor.settings.gradle regenerated by
cap sync alongside this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:32:00 +03:00
Local Dev
0a0ee52750 feat(wallet-android): brand icon from bns://deviant.x + splash regen
Replaces the generic Capacitor placeholder launcher icon with the
official Deviant mark fetched from bns://deviant.x/brand/icon/icon-512.svg
(the same mark the site uses). Adaptive-icon layers split cleanly for
Android 8+ masks:

- icon-foreground = orbital ring + crescent-D glyph + acid-yellow
  orbital dot (scaled x2.5 from the 512 BNS source so the D reads
  bigger on-launcher without clipping on circular masks)
- icon-background = dark sky radial gradient + faint starfield specks
- icon.svg = full composition for the legacy ic_launcher.png

Sources live at shells/app/assets/{icon,icon-foreground,icon-background}.svg
— a future icon re-roll is one `./node_modules/.bin/capacitor-assets
generate --android` away.

@capacitor/assets regenerated the five mipmap densities (ldpi through
xxxhdpi) + the splash screens (portrait/landscape, light/dark).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:31:40 +03:00
Local Dev
4ab864d8e0 site: footer brand link on every page + Potidaea hero-sub tweak
- Add '/brand/' link in each page footer so the gallery is reachable
  from the site (previously unlinked). Main landing gets it in
  .footer-meta (new .footer-meta a style). Potidaea + Anthemus/about
  gain it in .foot-links. Artemision/Katalogos/Agora append it after
  the deviant.x/<page> label.
- Potidaea hero-sub text: 'Zero-Counterparty Ocean Deep Liquidity
  Swaps based via Automated Swap Mechanism on Bitcoin CashScripts
  that operates at fixed rates by consensus. No order book, no
  liquidity pool, no maker, no slippage. The covenant is the engine.'

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 01:19:41 +03:00
Local Dev
f7529ef77b chore(brand): mirror silentmode brand/ assets into the repo + publish to potidaea.asm
Prior state: the brand assets (Deviant mark + wordmark, favicon, social cards,
palette doc, voice doc, PNG exports) lived ONLY at silentmode:/opt/silent-mode/
site/deviant/brand/ — no git history, no Sia fallback, no backup. Deleting
silentmode would have lost them.

This commit pulls the full 35-file tree into site/deviant/brand/ so the repo
is the source of truth going forward.

Simultaneously (not in this commit — deploy step): uploaded the same tree
to Sia at bns/potidaea/branding/, so the branding page is now also live at:

  https://navigate.st/bns/potidaea.asm/branding/   (via BNS gateway)
  https://navigate.st/bns/potidaea.asm/branding/social/
  https://navigate.st/bns/potidaea.asm/branding/logo/deviant-mark.svg

Alongside the existing deviant.x publication:

  https://navigate.st/deviant/brand/
  https://deviant.x/brand/   (BNS canonical)

Both routes serve the same 'Deviant brand' page because Potidaea is a product
of the Deviant project — showing the parent brand under the product URL is
the honest framing. A Potidaea-specific brand page can land later if we
decide the product deserves its own visual identity surface.

Deploy path for future edits — same two-leg pattern as docs/, with the
Potidaea quirk that its Sia prefix is bns/potidaea/ (not bns/potidaea.asm/,
per potidaea.asm's on-chain s3 record):

  scp -r site/deviant/brand/ silentmode:/opt/silent-mode/site/deviant/
  cd D:/Dev/SilentMode
  node Argus/src/lib/sia-upload.js D:/Dev/Deviant/site/deviant/brand bns/deviant.x/brand
  node Argus/src/lib/sia-upload.js D:/Dev/Deviant/site/deviant/brand bns/potidaea/branding

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-22 00:48:54 +03:00
Local Dev
1ca645f680 feat(faucet): add chipnet.bch.ninja — sats + CashToken genesis in one flow
New BCH chipnet faucet by BCH Ninja. Superior to tbch.googol.cash for
this project: single flow for both sats top-up AND CashToken genesis
(fungible FT + NFT with immutable / mutable / minting capability +
optional hex commitment), token-aware addresses (bchtest:z/r), no
captcha (client-side bot check instead), limit 0.1% of available
balance per request.

Not embeddable — the faucet declares CSP frame-ancestors 'none'
explicitly, so we don't attempt the iframe. Opens in a new tab via
the standard faucet-item blocked flow. tbch.googol.cash kept as the
sats-only fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-11 18:49:14 +02:00
Local Dev
cfeb0e2504 docs(asm): experimental MBA-adjacent covenants catalogue
Names each of the 14 .cash files in contracts/ that are NOT the four
shipping MBA templates (CoinMBA / ShareMBA / OracleMBA / DynamicMBA)
and groups them into five honest categories so future sessions don't
misread the catalogue:

  1. Attack demos — the T-001 verify-module test artifacts
     (OpenMintFT_Backdoor / _Escalating / _NoPeg / _Whitelist).
     DELIBERATELY malicious; serve the wallet-side D-1/D-2/D-3 defense
     matrix on chipnet.

  2. Variants of live families (future-promotable):
     OracleMBAFloor, OracleMBA_DraFee, TieredMintOracleFT, ExpiringMBA

  3. Specialty MBAs: ManagedMintFT (MCA/MTA stablecoin+bond template),
     OraclePricedVault (AnyHedge-style value lock)

  4. Platform/infrastructure: DeviantPlatformMBA (consensus-enforced
     platform-fee version of CoinMBA)

  5. Market covenants held BY MBAs: BinaryShareVaultMBA,
     ParimutuelToteOpenMBA

  (NBA sibling for disambiguation): OpenMintNBA — the unbacked family.

Each entry carries a one-paragraph honest summary (not a sales pitch),
links to the ADR/snapshot of record where relevant, and the file names
the "promotion checklist" sequence a candidate must pass before earning
a reserved templateId slot.

Preempts the "we only have 2 MBAs" misread — the live chipnet deploys
(ASM, ASM2) are two SEEDS of ONE template (CoinMBA), while the full
.cash catalogue has 14+ MBA-adjacent entries doing distinct things.

Index entry added to wiki/asm/index.md so the domain router surfaces it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:24:44 +02:00
Local Dev
5465029321 feat(mainnet-infra): oracle client + Katalogos deploy script + design doc
Three artifacts the mainnet launch needs, scoped tightly:

design/potidaea/src/oracleClient.ts
  Replaces the baked chipnet dev signing key for mainnet. fetchOracleAttestation()
  pulls a fresh AnyHedge-format signed BCH/USD message from oracles.cash
  (General Protocols' production BCH oracle network — the same signer AnyHedge
  uses), verifies the Schnorr signature client-side against the pinned pubkey
  before handing it to the covenant. Chipnet keeps its publish.ts baked dev
  key (dev environment, no economic bearing). Mainnet pubkey slot is
  INTENTIONALLY empty — any call hard-throws until deploy, which kills the
  'chipnet-sponsor-key-classifier-block' concern for mainnet builds.

scripts/mainnet-katalogos-deploy.mjs
  One-shot broadcast of KatalogosRegistryV1 on BCH mainnet. Refuses to run
  unless .mainnet-katalogos-config.json supplies real (non-placeholder)
  values for feeCategoryId / oraclePubKeyHex / treasuryLB / deaRateSats.
  Interactive 'MAINNET DEPLOY' confirmation gate (or MAINNET_KATALOGOS_YES=1
  for CI). Writes MainnetTests/.mainnet-katalogos.json with the resulting
  (txid, registryCategoryId, contractAddress) and prints paste-ready config
  for KATALOGOS_VERSIONS.mainnet in shells/app/src/core/katalogos.ts.

wiki/infra/oracle-service.md
  Design doc explaining the Path-A choice (consume oracles.cash) vs
  Path-B (self-hosted oracle, future option), the rotation policy
  (operator-driven; we redeploy on their pubkey change), and the
  "do NOT sign messages locally on mainnet" guardrails. Links from
  publish.ts's dev-key constant and the Katalogos deploy script.

Depends on: feat(network) [5f852b9] — oracleClient.ts imports currentNetwork
from the network module.

Follow-up (requires real BCH + external coordination):
  - set the oracles.cash BCH/USD pubkey in oracleClient.ts
  - mint a mainnet DEA category (feeCategoryId prereq)
  - run scripts/mainnet-katalogos-deploy.mjs with the real config
  - paste the result into KATALOGOS_VERSIONS.mainnet
  - refactor publish.ts to consume fetchOracleAttestation() on mainnet

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:21:13 +02:00
Local Dev
e66a8d6c8a fix(wc): pairing diagnostics + 45s visible-timeout
Aegis (and anything built on @wizardconnect/dapp 0.2.x) can pair on its
own side and never advance ours — the dapp library's handleWalletReady
silently swallows invalid session payloads at dapp-connection-manager.js:436:

    if (!isHdwalletv1Session(sessionData)) {
      console.error("[wizardconnect/dapp] Invalid hdwalletv1 session data:", sessionData);
      return;   // ← no 'walletready', no 'disconnect' event — just a stuck chip
    }

When this fires, nothing in our chip updates. Previously the user watched
"connecting…" forever with no clue why.

Two additions:

Diagnostic ring buffer (last 40 relay messages):
  manager.on('messagereceived') / ('messagesent') / ('reconnecting') →
  push {t, dir, action, raw} into wc.diag, exposed as window.wcDiag.
  After a failed pairing attempt the user can paste
    JSON.stringify(window.wcDiag, (_,v) => typeof v === 'bigint' ? v.toString() : v, 2)
  from the browser console, which — combined with the console.error the
  dapp lib prints — tells us precisely which of the three silent-failure
  modes hit: session-shape rejected, protocol mismatch, or no inbound at
  all (relay dead).

Visible 45-second timeout:
  If snap.phase is still 'awaiting-scan' 45s after connect(), snap flips
  to 'error' with a message pointing the user at window.wcDiag. Any
  inbound message resets the timer (so a slow-reconnect but alive wallet
  doesn't false-trigger). cleanup() + walletready both clear the timer.

Zero behavior change in the happy path — the diagnostic listeners are
purely observational, and the timeout only fires when the dapp lib's
silent-return bug would have left us hung.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:20:51 +02:00
Local Dev
a7e501087b fix(potidaea): mock-balance flash + 'Potidaea · ASM' title
Two UX fixes on the same two files:

Title (both landing + app):
  'Potidaea · Swap on Bitcoin Cash' → 'Potidaea · ASM on Bitcoin Cash'
  'Potidaea · Swap'                → 'Potidaea · ASM'
Reflects the product-naming lock: the brand is the ASM (Automated Swap
Mechanism) engine, not a generic "swap" app. Also what the dynamic route-
title map in main.ts was already using.

Mock-balance flash (app.html):
  The inline-JS ASSETS array shipped with non-zero mock bals (BCH 0.8092,
  ASM 5000, ASM2 800, CAP 200, GMX 60, CHE 150) and the chip template +
  swap-leg text carried the same hardcoded 0.8092 figures. On page load,
  the inline JS fired renderBalances() with those mock numbers, then
  ~500 ms later main.ts's refreshBalance() overwrote with real electrum
  data — producing a visible "0.8 → 0" flash for any 0-balance wallet.

  Fix: zero every initial bal in ASSETS, replace the hardcoded chip +
  leg text with '…' placeholders, zero PAIR_BALS for the Anthemus swap
  view. Chip gets id="chipBch" so main.ts's first paintBalance writes
  into it directly.

Verified in-browser at localhost:5185/app.html:
  t=  7.1s  chip="…"           ASSETS.BCH.bal=0        (initial)
  t=  7.9s  chip="0.0101 BCH"  ASSETS.BCH.bal=0.01015  (real balance in)
  No 0.8092 ever appears.

Build size unchanged (~337 KB gzip).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:20:22 +02:00
Local Dev
9a24a19c94 feat(mba): rename covenants to CoinMBA / ShareMBA / OracleMBA + reserve DynamicMBA
Clean names that match the Mathematically Backed Asset framing (which came
first; the covenant source files were a later exploratory implementation
whose historical names outlived their usefulness). Rename is bytecode-
preserving — the chipnet-validated 52,998-swap proof carries over unchanged
because cashscript's `contract Name { }` is a source-level identifier that
doesn't enter the compiled script.

Source renames (git mv):
  contracts/DeviantOpenMintFT.cash  → contracts/CoinMBA.cash    (templateId 0x00)
  contracts/DeviantShareMBA.cash    → contracts/ShareMBA.cash   (templateId 0x01)
  contracts/DeviantOracleMBA.cash   → contracts/OracleMBA.cash  (templateId 0x02)

Deleted:
  contracts/ArtemisionMintFT.cash   (byte-identical duplicate of DeviantOpenMintFT;
                                     "Artemision" lives on only as the wizard UI
                                     concept, not a distinct template)

New:
  contracts/DynamicMBA.cash         (templateId 0x03 — reserved for the Dynamic
                                     Rate Asset covenant; scaffold only, rejects
                                     every call. DRA is distinct from FIATμ:
                                     FIATμ = dynamic-rate + oracle, DRA = pure
                                     state-driven, no oracle, reflective
                                     against a shared pool)

Compiled artifacts rebuilt via `cashc` and shipped to:
  - design/potidaea/src/artifacts/   (live client consumption)
  - site/deviant/docs/mba/artifacts/ (public download URLs for third-party
                                     wallet integration; also deployed to
                                     silentmode primary + Sia fallback)

Bytecode verification (sha256-of-bytecode):
  CoinMBA     == DeviantOpenMintFT  ✓ (6a9ae89b...)
  ShareMBA    == DeviantShareMBA    ✓
  OracleMBA   == DeviantOracleMBA   ✓

Client code updates (live surface only — historical scripts, snapshots,
knowledgebase etc. keep their original names since they reference specific
chipnet tx hashes that happened under those names):
  - design/potidaea/src/orbital.ts      — artifact imports
  - design/potidaea/src/genesis.ts      — artifact imports + Artemision deprecation note
  - design/potidaea/src/registry.ts     — artifact imports + dispatch doc + 0x03 note
  - design/potidaea/src/publish.ts      — TEMPLATE_* constants renamed + 0x03 added
  - design/potidaea/src/main.ts         — TEMPLATE_* usages renamed

Docs updates:
  - site/deviant/docs/mba/index.html         — templates table expanded to 4 rows,
                                              DRA vs FIATμ note added, dev-section
                                              code-reference lines updated
  - site/deviant/docs/mba/artifacts/index.html — one card per new artifact, DRA
                                                scaffold flagged "not deployable"

Follow-up (not in this commit): the chipnet reseed. Current ASM/ASM2
instances were minted under the DeviantOpenMintFT covenant address and
remain redeemable (bytecode is identical, addresses derive to the same
value). A fresh-start deploy that mints new CoinMBA instances under the
new identity is a separate step requiring real chipnet BCH funding +
Katalogos publish. ASM/ASM2 seed entries stay in orbital.ts SEED_MBAS
until that follow-up ships.

Also deferred: the ~15 experimental MBA-adjacent covenants
(DeviantOpenMintFT_Backdoor, _Escalating, _NoPeg, _Whitelist,
DeviantOracleMBAFloor, _DraFee, DeviantManagedMintFT,
DeviantTieredMintOracleFT, OraclePricedVault, ExpiringMBA,
DeviantPlatformMBA, etc.) stay in contracts/ at their current names —
none are user-facing templates, and their fate (archive, promote,
retire) deserves its own taxonomy session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-09 00:00:15 +02:00
Local Dev
6a58143966 feat(bcmr-integration): emit BCMR OP_RETURN on every MBA genesis + publish
Wires the bcmr.ts + bcmr-upload.ts modules (committed in f7ec956) into
the two surfaces where MBA identity enters the chain:

genesis.ts
  - New helper buildAndUploadBcmr(categoryId, args): builds the canonical
    JSON for an MBA via buildBcmrJson(), POSTs it to the gateway via
    uploadBcmr() (best-effort — on-chain OP_RETURN commits URL + hash
    regardless of upload success), returns the [magic, hash, url] chunks
    the caller passes to tb.addOpReturnOutput()
  - Called from both genesisArtemisionMBA (Static family, templateId 0x00)
    and genesisDeviantShareMBA (Share family, templateId 0x01); emits the
    DVMB template params inside extensions.deviant.dvmb so our stack can
    cross-check the covenant identity through both sidecars at once
  - GENESIS_MINER_FEE bumped 2000n → 3000n to cover the extra OP_RETURN
    output (~180B worst case: 4B BCMR magic + 32B hash + ~90B URL + push
    opcodes). Keeps every genesis above 1 sat/byte

publish.ts
  - Katalogos register() tx now emits a BCMR OP_RETURN alongside the
    existing DVMB OP_RETURN when metadata.ticker is present. Two
    OP_RETURN outputs in one tx is standard-policy-accepted on chipnet
    + mainnet BCHN; combined DVMB + BCMR data sits under the 220-byte
    datacarriersize ceiling.

End-to-end effect: every new MBA minted or registered through Potidaea
now carries both sidecars. Our stack reads DVMB; every general-purpose
BCH wallet (Cauldron, Selene, Zapit, Bitcoin.com, Cashonize, Paytaca)
reads BCMR and autoresolves the ticker/name/icon without any
coordination with us.

Depends on: feat(network) [5f852b9] + feat(broadcast) [b8ac497] +
feat(bcmr) [f7ec956]. The imports and the tb.build() + fanoutBroadcast
pattern pre-exist in both files from those commits; this one adds the
BCMR hunks only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-08 23:36:16 +02:00
Local Dev
b8ac497323 feat(broadcast): multi-source fan-out replacing single-endpoint cashscript send
Introduces design/potidaea/src/broadcast.ts and wires every tx-emitting
site in the Potidaea client through it. Replaces cashscript's
TransactionBuilder.send() — which hits ONE hardcoded electrum endpoint
(wss://chipnet.bch.ninja:50004) — with a parallel fan-out that pushes
the same signed tx to as many independent nodes as the pool allows.

broadcast.ts
  - fanoutBroadcast(rawTxHex) fires in parallel:
      (a) every pre-opened ElectrumClient the caller passed in
      (b) up to N (default 3) fresh ephemeral WSS connections drawn
          from endpointsFor(currentNetwork()), shuffled; closed after
      (c) every HTTPS JSON-RPC proxy in broadcastRpcFor(currentNetwork())
          — our own-node write path, uncoupled from third-party Fulcrums
  - Resolves on first success, aggregates rejections for diagnostics
  - txidOf(rawTxHex) computes txid client-side (double-SHA256 + display
    byte-order) so we trust our own hash, not whatever a server echoes
  - rpcBroadcast handles BCHN "already known" (-27) as success — a tx
    already in mempool is "safely propagated" from the caller's view

Mixed-content handling: ephemerals skip ws:// URLs when the page itself
is served over https:, since Chrome's loopback exception doesn't apply
to in-page requests from a secure origin.

Integration (all 6 tb.send() sites):
  orbital.ts
    - claimViaLocalSigner       (BCH → MBA, Static family)
    - claimViaLocalSignerShare  (BCH → MBA, Share family)
    - redeemViaLocalSigner      (MBA → BCH, any family)
  genesis.ts
    - genesisArtemisionMBA      (Static family genesis)
    - genesisDeviantShareMBA    (Share family genesis)
  publish.ts
    - publishViaLocalSigner     (KatalogosRegistryV1.register tx)

Shape at each site: tb.build() to get raw hex, then
  const { txid } = await fanoutBroadcast(rawTxHex)
Caller gets the exact same ClaimTxResult / GenesisResult / PublishResult
shape as before — swapping cashscript's send for our fan-out is purely
additive at the API layer.

Server-side companion: scratchpad/silentmode-broadcast-proxy/ (chipnet
RPC proxy bundle) + scratchpad/mainnet-broadcast-proxy/ (mainnet twin,
pending BCHN install per MAINNET-BCH-PLAYBOOK.md). The chipnet
broadcastRpcFor() URL (chipnet.silentmode.st/rpc/broadcast) starts
responding once that bundle lands on silentmode; until then the public
Fulcrums carry the broadcast.

Depends on: feat(network) — broadcast.ts imports endpointsFor +
currentNetwork from the network module.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-08 22:45:12 +02:00
Local Dev
5f852b945e feat(network): chipnet|mainnet state module + chain-aware wiring
Introduces design/potidaea/src/network.ts as the single source of truth
for which BCH chain the browser session is pointed at. Everything that
talks to the chain reads from it: endpoint pool, address encoding,
cashscript provider, catalog, WalletConnect chainId, topbar chip.

New module:
  network.ts
    - currentNetwork() / setNetwork() / onNetworkChange()
    - persisted in localStorage so a reload keeps the choice
    - addrPrefix(net), cashscriptNetwork(net), networkLabel(net) helpers

endpoints.ts
  - Split into per-chain CHIPNET_ENDPOINTS / MAINNET_ENDPOINTS plus
    endpointsFor(net) selector (mirror in CHIPNET_BROADCAST_RPC /
    MAINNET_BROADCAST_RPC for the broadcast-RPC pool that the broadcast
    commit will consume).
  - Chipnet pool widens to include the local-dev Fulcrum (slot 0, Vite
    DEV only), silentmode Fulcrum slot, and coinspectrum fallback.
  - Mainnet pool mirrors shells/app/src/core/spv/pool.ts public set.

Chain-aware integration (single-line swaps across many files):
  electrum.ts       — ElectrumClient.connectAny defaults to endpointsFor(currentNetwork())
  orbital.ts        — all four ElectrumNetworkProvider sites use cashscriptNetwork(currentNetwork());
                      KNOWN_MBAS → per-chain SEED_MBAS[Network] with refreshMbasForNetwork(net)
                      wipe-and-reseed, mainnet starts empty
  genesis.ts        — provider swap at both Artemision + Share genesis entry points
  publish.ts        — provider swap + mainnet guard throwing
                      PublishError('wrong-network') (KatalogosRegistryV1 is
                      chipnet-only until deployed; the chipnet dev oracle key
                      must never ship to mainnet)
  registry.ts       — provider swap in discoveredParamsToKnownMba
  signer-store.ts   — signer address encoding follows currentNetwork() so
                      the same WIF produces bchtest: on chipnet and
                      bitcoincash: on mainnet; createUserWallet() uses the
                      matching WIF flavor

shells/app/src/core/spv/pool.ts
  - Owns-slot wiring: dev Fulcrum ws://127.0.0.1:50003 (DEV only) +
    chipnet.silentmode.st:50012 own slot + coinspectrum fallback on
    chipnet; mainnet set unchanged from the existing public pool.

main.ts
  - window.setPotidaeaNetwork bridge invoked by app.html netMenu
  - onNetworkChange listener: close cached client, refresh catalog,
    re-derive signer, repaint chip + mainnet banner
  - Boot-time badge + banner sync (localStorage might hold "mainnet"
    from a prior session; corrects the HTML's static "chipnet" default)
  - WC import-flow prefix check uses addrPrefix() instead of hardcoded
    "bchtest:"

app.html
  - netMenu mainnet item un-disabled, tag now "read-only" with tooltip
  - netMenu click handler calls window.setPotidaeaNetwork(net)
    (no more "mainnet is coming" toast)

Verified in-browser: flipping to mainnet shows the view-only banner,
badge updates to "● mainnet", localStorage persists; flipping back
restores chipnet reads (live BCH balance via local Fulcrum).

Follow-up commits land: feat(broadcast) — fan-out module + wiring,
feat(bcmr-integration) — genesis/publish emission hooks that depend on
both this + the broadcast commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-08 22:42:49 +02:00
Local Dev
f7ec956d6e feat(bcmr): CHIP-2023-04 metadata registry client for MBAs
Two new modules the Potidaea client uses to emit BCMR sidecars alongside
every MBA genesis + Katalogos publish. CHIP-2023-04 is the interop standard
every general-purpose BCH wallet already reads (Cauldron, Selene, Zapit,
Bitcoin.com, Cashonize, Paytaca); emitting it means our MBAs show up with
correct ticker/name/icon in those wallets without any coordination.

bcmr.ts
  - BcmrRegistry JSON schema (v2 subset — tokens, uris.icon, extensions)
  - buildBcmrJson(): canonical identity payload per MBA, with our DVMB
    template params stashed in extensions.deviant.dvmb so our own stack
    can cross-check the covenant identity through both channels
  - canonicalize() + serializeBcmr(): JCS-style ordered serializer so
    the content hash is reproducible across publishers
  - bcmrContentHash(): single SHA-256 of canonical bytes (per CHIP spec)
  - bcmrPublicUrl(): per-network URL at silentmode.st/bcmr/
  - bcmrOpReturnChunks(): the [magic, hash, url] tuple cashscript's
    tb.addOpReturnOutput() takes directly

bcmr-upload.ts
  - uploadBcmr(): POST canonical bytes to navigate.st/api/bcmr-upload/.
    Mirrors the icon-upload contract (content-addressed by categoryId,
    gateway-side shape check, idempotent). Best-effort — the on-chain
    OP_RETURN commits the URL+hash regardless of upload success, so a
    later retry of the same canonical bytes is a no-op.

Wire format verified against the CHIP: OP_RETURN <'BCMR'> <sha256(json)>
<url>. Single SHA-256 (not double). URLs utf-8, multiple pushes allowed.

The genesis.ts / publish.ts integration hooks that CALL these modules are
tangled with the broadcast-fanout + network-awareness work from this
session (same lines, same imports) and will land together in a follow-up
commit. The two modules here are self-contained and build cleanly as-is.

Server-side bundle (bcmr-gateway.mjs sidecar + systemd unit + nginx snippet)
lives in the session scratchpad for deployment to silentmode.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-08 22:29:45 +02:00
Local Dev
11e83e2501 docs(mba): publish Mathematically Backed Asset explainer at deviant.x/docs/mba/
Hub at site/deviant/docs/index.html (lists MBA + draft stubs for Potidaea,
Anthemus, FIATμ, correspondent). MBA page covers the sound-money heritage
(PoW BCH as "metal", covenant bytecode as "stamp"), three template families
(Static/Share/Oracle), interaction model, trust guarantees + explicit
non-guarantees, BCMR + DVMB wallet-integration story.

Third-party integration section is Tier-1 / Tier-2 / Tier-3 with the full
claim + redeem tx shapes, so a wallet dev can transact MBAs without any
coordination with us. Covenant artifact JSONs published alongside at
docs/mba/artifacts/ (DeviantOpenMintFT, ArtemisionMintFT, DeviantShareMBA,
DeviantOracleMBA) with constructor args and a verify-covenant example.

Deployed via both legs: silentmode:/opt/silent-mode/site/deviant/docs/ and
Sia s3://bns/deviant.x/docs/. Reachable at navigate.st/deviant/docs/mba/,
navigate.st/bns/deviant.x/docs/mba/, and deviant.x/docs/mba/ (BNS canonical).

README adds the docs/ inventory and the two-leg deploy block for future
docs/<topic>/ pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-08 22:27:17 +02:00
Local Dev
a014f6636f 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>
2026-10-02 20:40:59 +02:00