diff options
| author | Adam Soltys <[email protected]> | 2026-08-01 09:04:18 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-01 09:04:18 -0700 |
| commit | 38662483c170029b1a397f5a47cd49311012324f (patch) | |
| tree | a72ef4f52c0ce9c600160bfc56f8998c8bd1bb55 /src/features | |
| parent | d0fc8569c478111bfa5da90570ee3a0ac449a60d (diff) | |
| download | coinosv3-38662483c170029b1a397f5a47cd49311012324f.tar.gz coinosv3-38662483c170029b1a397f5a47cd49311012324f.zip | |
Repair Ark state that an earlier build stamped with the wrong server
The first namespacing attempt could brand one ASP's coins with whichever
server happened to be connected, filing them under the wrong namespace — a
phantom balance that survives both later fixes because the damage is already
written to localStorage.
loadArkState now checks the stamp against the vtxos themselves. A vtxo names
its cosigner in its own bytes, which is fact; the stamp is just something we
wrote. When they disagree the state is misfiled: it moves to a holding key
under its true owner and this namespace shows nothing. adoptArkState claims
from that holding area when the matching server connects.
Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/ark.js | 37 |
1 files changed, 36 insertions, 1 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index b5c2cd9..fd8fbaf 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -45,6 +45,17 @@ export function installArkWallet(wallet) { }, _arkKey(url) { return this._cacheKey() + ':ark:' + this._arkNs(url); }, _arkLegacyKey() { return this._cacheKey() + ':ark'; }, + // Who really cosigned this state's coins? A vtxo names its server in its + // own bytes, which is cryptographic fact — unlike the stamp, which is + // just something we wrote and could have written wrongly. + _arkVtxoOwner(st) { + const v = (st?.vtxos || []).find((x) => x.bytes); + if (!v) return null; + try { return decodeVtxo(hex.decode(v.bytes)).serverPubkey; } catch { return null; } + }, + // Park state under the server that actually owns it, until the URL for + // that server is selected and can adopt it. + _arkHoldKey(pubkey) { return this._cacheKey() + ':ark:pk:' + String(pubkey).slice(0, 16); }, loadArkState(url) { // NB deliberately no fallback to the legacy key: the UI renders from // this before any server is contacted, so returning another ASP's state @@ -52,7 +63,23 @@ export function installArkWallet(wallet) { // is claimed via adoptArkState() once we know who cosigned it. try { const cur = localStorage.getItem(this._arkKey(url)); - return cur ? JSON.parse(cur) : null; + if (!cur) return null; + const st = JSON.parse(cur); + // Self-repair: an earlier build could stamp state with whichever + // server happened to be connected, filing one ASP's coins under + // another. The vtxos themselves settle it — if they disagree with the + // stamp, this state is misfiled. Move it to its real owner and show + // nothing here. + const owner = this._arkVtxoOwner(st); + if (owner && st.serverPubkey && owner !== st.serverPubkey) { + st.serverPubkey = owner; + try { + localStorage.setItem(this._arkHoldKey(owner), JSON.stringify(st)); + localStorage.removeItem(this._arkKey(url)); + } catch {} + return null; + } + return st; } catch { return null; } }, // Offer up pre-namespacing state, but only to the ASP that actually @@ -61,6 +88,14 @@ export function installArkWallet(wallet) { // server pubkey, so this needs no network call and cannot guess wrong. adoptArkState(serverPubkey, url) { try { + // state parked by the self-repair above belongs to exactly one server + const held = localStorage.getItem(this._arkHoldKey(serverPubkey)); + if (held) { + const st = JSON.parse(held); + this.saveArkState(st, url); + localStorage.removeItem(this._arkHoldKey(serverPubkey)); + return st; + } const raw = localStorage.getItem(this._arkLegacyKey()); if (!raw) return null; const st = JSON.parse(raw); |
