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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
- /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>
- 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>
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>
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>
- 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>
'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>
'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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>