diff --git a/design/potidaea/src/bcmr.ts b/design/potidaea/src/bcmr.ts index 1952235..133fb5c 100644 --- a/design/potidaea/src/bcmr.ts +++ b/design/potidaea/src/bcmr.ts @@ -164,35 +164,30 @@ export function bcmrContentHash(bcmr: BcmrRegistry): Uint8Array { } // ── The URL where wallets fetch the JSON ──────────────────────────────────── - -const BCMR_BASE_URL: Record = { - chipnet: 'https://silentmode.st/bcmr/chipnet/', - mainnet: 'https://silentmode.st/bcmr/', -}; +// +// ADR-0057 step 2 is now LIVE on the gateway (POST → bns-gateway at +// `/api/bcmr-upload//.json` writes to Sia bucket +// `bns/deviant.x/bcmr//.json`; the same path is served +// at `navigate.st/bns/deviant.x/bcmr//.json` through +// the BNS gateway, which reads it back from Sia). The primary URL below IS +// the Sia-backed one — no mirror flag needed; the gateway + Sia storage are +// the resilience layer. If silentmode.st goes dark, any public bns-gateway +// (Theseus, Ariadne, a self-hosted one) still resolves the same bytes from +// Sia, and the on-chain sha256 anchor confirms integrity. /** The public URL at which a wallet can GET the BCMR JSON for `categoryId`. - * Kept stable per network — this is what goes into the OP_RETURN as the - * PRIMARY URL. */ + * Kept stable per network — this is what goes into the OP_RETURN. */ export function bcmrPublicUrl(categoryId: string, net: Network = currentNetwork()): string { - return BCMR_BASE_URL[net] + categoryId.toLowerCase() + '.json'; + return `https://navigate.st/bns/deviant.x/bcmr/${net}/${categoryId.toLowerCase()}.json`; } -/** - * ADR-0057 step 2 — Sia-mirror URL. The BNS gateway resolves `bcmr.deviant.x` - * via an `s3` record pointing at the Sia bucket `bns/bcmr//`. Any - * wallet with Theseus or Ariadne hits the same bytes via a different host + - * storage backend, so a silentmode.st outage no longer takes the metadata - * with it. - * - * Forward-compatible: adding this URL to the on-chain push today is harmless - * even before the server-side mirror lands. Wallets try URLs in order; the - * primary resolves first in the happy path, and the Sia URL becomes active - * the moment the mirror writes bytes whose SHA-256 matches the on-chain - * anchor. A wallet seeing an inactive Sia URL simply moves to the next. - */ +/** Legacy alias of `bcmrPublicUrl`. The ADR-0057 step 2 "primary + Sia + * mirror" distinction collapsed once the primary itself became Sia-backed + * (the gateway stores to Sia on upload). Kept as a no-op alias so any + * caller that opted-in to the mirror URL still compiles. Prefer + * `bcmrPublicUrl` for new code. */ export function bcmrSiaUrl(categoryId: string, net: Network = currentNetwork()): string { - const sub = net === 'chipnet' ? 'chipnet/' : ''; - return `https://navigate.st/bns/bcmr.deviant.x/${sub}${categoryId.toLowerCase()}.json`; + return bcmrPublicUrl(categoryId, net); } // ── OP_RETURN encoder ────────────────────────────────────────────────────── diff --git a/design/potidaea/src/genesis.ts b/design/potidaea/src/genesis.ts index ca15c61..21fb66b 100644 --- a/design/potidaea/src/genesis.ts +++ b/design/potidaea/src/genesis.ts @@ -27,7 +27,7 @@ import type { Signer } from './signer-store'; import type { KnownMba } from './orbital'; import { fanoutBroadcast } from './broadcast'; import { cashscriptNetwork, currentNetwork } from './network'; -import { buildBcmrJson, serializeBcmr, bcmrPublicUrl, bcmrSiaUrl, bcmrOpReturnChunks } from './bcmr'; +import { buildBcmrJson, serializeBcmr, bcmrPublicUrl, bcmrOpReturnChunks } from './bcmr'; import { uploadBcmr } from './bcmr-upload'; const ART = artifact as unknown as Artifact; @@ -60,20 +60,17 @@ async function buildAndUploadBcmr( dvmbExtension: args.dvmbExtension, }); const canonical = serializeBcmr(json); - const primaryUrl = bcmrPublicUrl(categoryId, net); + // ADR-0057 step 2 — the primary URL is now Sia-backed via the BNS gateway + // (`navigate.st/bns/deviant.x/bcmr//...`). The gateway's + // /api/bcmr-upload handler writes to the Sia bucket and the same path + // reads back from it, so a silentmode.st outage no longer takes the + // metadata with it — any bns-gateway (Theseus, Ariadne, self-hosted) + // resolves the same bytes. Encoder stays variadic so additional mirrors + // (IPFS pin, alternate gateway) can be added later without API churn. + const url = bcmrPublicUrl(categoryId, net); try { await uploadBcmr(categoryId, net, canonical); } catch (e) { console.warn('[bcmr] upload failed (on-chain OP_RETURN still committed):', e instanceof Error ? e.message : e); } - - // ADR-0057 step 2 — emit the Sia-mirror URL alongside the primary when the - // mirror is enabled. Opt-in via VITE_BCMR_SIA_MIRROR so existing deploys - // don't start pushing an unresolvable URL until the server-side mirror is - // writing bytes whose SHA-256 matches the on-chain anchor. Any wallet - // reading the OP_RETURN tries URLs in order (CHIP-2023-04) so this is - // forward-compatible: a wallet that fetches the Sia URL before the mirror - // is live simply falls through to the primary. - const siaEnabled = (import.meta as unknown as { env?: Record }).env?.VITE_BCMR_SIA_MIRROR === '1'; - const urls = siaEnabled ? [primaryUrl, bcmrSiaUrl(categoryId, net)] : [primaryUrl]; - return bcmrOpReturnChunks(json, ...urls); + return bcmrOpReturnChunks(json, url); } function hexToBin(h: string): Uint8Array { diff --git a/wiki/decisions.md b/wiki/decisions.md index cabb075..1a2df9b 100644 --- a/wiki/decisions.md +++ b/wiki/decisions.md @@ -3291,18 +3291,23 @@ same minting NFT. ### Deferred to follow-on sessions -**Step 2 — Sia mirror in the BCMR OP_RETURN.** ⚠ **Client-side ready; server-side -mirror still TODO.** Encoder now accepts multiple URLs variadically -(`bcmrOpReturnChunks(bcmr, ...urls)`) and `bcmrSiaUrl(categoryId, net)` yields -the forward-compatible BNS-gateway URL -(`https://navigate.st/bns/bcmr.deviant.x//.json`). The -genesis emitter emits both URLs when `VITE_BCMR_SIA_MIRROR=1` is set at build; -default (unset) keeps the current single-URL behaviour so no live deploy flips -behaviour unexpectedly. Still owed: a server-side hook that mirrors every POST -to `/api/bcmr-upload//.json` into Sia bucket -`bns/bcmr//.json`, keyed so the SHA-256 of the stored -bytes matches the on-chain anchor. Once that mirror writes, flip the env flag -and every new genesis commits both URLs on-chain. +**Step 2 — Sia-backed BCMR primary URL.** ✅ **SHIPPED.** The bns-gateway at +`silentmode:/opt/bns-gateway/public-gateway.mjs` now handles +`POST /api/bcmr-upload//.json` — body must be valid +UTF-8 JSON ≤32 KB, path strictly validated — and PUTs the bytes to Sia at +`bns/deviant.x/bcmr//.json`. The same path reads back +through the BNS gateway at +`https://navigate.st/bns/deviant.x/bcmr//.json`, +which is now the ONE URL the client emits into the on-chain anchor. End-to-end +round-trip verified: client SHA-256 == response SHA-256 == fetched-bytes +SHA-256. The "primary + mirror" distinction collapsed once the primary +itself became Sia-backed: silentmode.st could go dark and any bns-gateway +(Theseus, Ariadne, self-hosted) still resolves the same bytes from the Sia +network, and the on-chain hash anchor confirms integrity. `bcmrSiaUrl()` +remains as an alias of `bcmrPublicUrl()` so earlier callers still compile; +the opt-in `VITE_BCMR_SIA_MIRROR` flag is no longer needed and was dropped. +Encoder stays variadic (`bcmrOpReturnChunks(bcmr, ...urls)`) so additional +mirrors (IPFS pin, alternate gateway) can be added later without API churn. **Step 3 — BCMR consumer in `shells/app`.** Biggest UX win. Scan each unknown token's genesis tx for the BCMR OP_RETURN → fetch the referenced JSON (fall