docs(snapshots): 0281 Potidaea balance-flash fix

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Local Dev 2026-10-22 19:31:36 +03:00
parent a1fffce620
commit 549e906802
2 changed files with 122 additions and 2 deletions

View file

@ -0,0 +1,118 @@
---
domain: wallet
summary: Potidaea's "0.82 BCH flash → empty" balance race fixed by removing the pending-swap optimistic overlay layer (−251 lines) and the wc.on('idle') auto-wipe.
tags: potidaea, bug-fix, cleanup, balance-paint, theseus
---
# 0281 — Potidaea balance-flash fix: drop pending-swap overlay
## Symptom
Loading Potidaea inside Theseus Navigator (via BNS name `potidaea.asm`)
showed the wallet balance briefly at ~0.82 BCH for about a second, then
flipped to an empty wallet. Hard-refresh didn't fix it; the behavior was
reproducible across loads. The number varied by user but the pattern
was consistent: a brief non-zero flash → zero.
## Root cause
Two defensive layers compounded a race that didn't need defending:
1. **Pending-swap optimistic overlay** (`main.ts:346-486` + call-sites):
`applyOptimisticSwapDelta` persisted a `{ txid, bchDeltaSats,
preSwapBchSats, ts }` entry to `localStorage` on every broadcast.
`loadPendingSwapsFromStorage` restored it on boot. `paintBalance`
overlaid the delta on top of whatever electrum returned, trying to
mask chipnet Fulcrum's mempool-indexing lag. `reapPendingSwaps`
dropped the overlay when the endpoint's own UTXO reading moved in
the expected direction, or after a 30-minute hard timeout.
The failure mode: a swap that never confirmed properly (or a
partial reap) left a stale overlay in localStorage. On next page
load, the first `paintBalance` call kept the overlay (reap couldn't
confirm the "moved" heuristic yet), yielding a fake lifted number.
The retry at 700 / 2500 / 6000 ms eventually met the hard-timeout
or saw the real UTXO set, dropped the overlay, and the real zero
balance painted — reading as "empty wallet".
2. **`wc.on('change', 'idle')` auto-wiped balances** when `!localSigner`,
on the theory a WC wallet had just disconnected. But `idle` is also
the initial state and a transient event, and in Theseus the user
attaches via Aegis (not WC, not a WIF), so a stray idle emit from
WC's initialization would nuke any painted balance.
## Fix
Dropped the entire pending-swap overlay system:
- Deleted `pendingSwaps[]`, `PENDING_SWAPS_KEY`, `PENDING_HARD_TIMEOUT_MS`
- Deleted `loadPendingSwapsFromStorage`, `savePendingSwapsToStorage`
- Deleted `reapPendingSwaps`, `reconcilePendingViaTxid`
- Deleted `applyOptimisticSwapDelta`, `registerPendingSwap`
- Deleted `currentBchSatsFromAssets`
- Simplified `paintBalance` to `bchSats / 1e8 → chip.textContent`;
no overlay math, no bonus accounting
- Removed `reconcilePendingViaTxid` await from `refreshBalance`
(saved a per-call RPC round-trip)
Fixed the wc-idle wipe:
- `wc.on('change', 'idle')` now only closes the cached electrum client;
no automatic `zeroAllBalances()`
- Explicit disconnect from the chip menu's WC-row X button now zeros
balances when the last wallet goes
- Boot order: `zeroAllBalances()` runs FIRST (before `renderChip` and
all other wires), not after. Removed a redundant second
`zeroAllBalances` call.
Collapsed the refresh retry storms:
- `onSignerChanged` dropped from 4 refreshes (0 / 700 / 2500 / 6000 ms)
to 2 (0 / 2500 ms)
- Broadcast-handler post-swap refresh dropped from 3 (1.5 / 5 / 12 s)
to 2 (2.5 / 8 s)
- Manual Refresh button on the chip menu covers the long-tail miss
## Trade-off accepted
Without the optimistic overlay, a swap confirmation that races chipnet
Fulcrum's mempool indexing will briefly show the pre-swap balance before
the follow-up refresh at 2.5s corrects it. In practice chipnet latency
is 1-3 seconds; the 8s retry catches the rest. If a user hits that
window, the Refresh button is one click away. Simpler and more honest
than a stateful overlay that persists across page lifetimes.
## Verification
- `tsc --noEmit`: clean
- `npm run build`: 899.83 KB bundle (902 → 900 pre/post), restructure OK
- Deployed to Sia at `bns/potidaea/`
- Grep of live bundle at `navigate.st/bns/potidaea.asm/assets/app-BFhFYoc9.js`:
0 occurrences of `pendingSwaps`, `reapPendingSwaps`,
`applyOptimisticSwapDelta`, `reconcilePendingViaTxid`,
`pending-swaps.v1`
- Fresh browser load: `walletChip = "Attach wallet"`,
`balTotal = "0.0000 BCH"` — no flash
## Diff summary
```
design/potidaea/src/main.ts | 303 ++++------------------------
1 file changed, 26 insertions(+), 277 deletions(-)
```
`main.ts`: 3763 → 3512 lines (-251, -6.7%)
## Commits
- `a1fffce` fix(potidaea): drop pending-swap overlay + fix idle-wipe
balance race
## Related
- Previous Potidaea swap-flow work: snapshots 0220 (fan-out),
0277 (orbital-swap retry adaptation)
- Theseus/Aegis integration (sibling work this session):
commit 9412836 adds `theseusHost.ts` + Attach-Aegis / Theseus-ID
menu rows; the fix here matters mainly for users loading Potidaea
inside Theseus Navigator

View file

@ -10,7 +10,7 @@
> Duplicate snapshot numbers exist (0018, 0019, 0022, 0027, 0053, 0054, 0057, 0062, 0083, 0084,
> 0144, 0145, 0153, 0164) — they are distinct files, disambiguated below by slug.
_323 snapshots · 323 tagged · 8 domains · regenerate after adding one._
_324 snapshots · 324 tagged · 8 domains · regenerate after adding one._
## Quick-find — all snapshots (Ctrl-F by №, domain, tag, or word)
@ -339,8 +339,9 @@ _323 snapshots · 323 tagged · 8 domains · regenerate after adding one._
| [0278](0278-asm-sequencer-service-drafted-and-proved.md) | asm | asm potidaea orbital-swap sequencer tier-2 zero-contention chipnet proof daemon relay | Tier-2 ASM sequencer service drafted as a coordinator-funded HTTP relay + tested end-to-end on chipnet. 5 simultaneous /claim requests → 5/5 accepted with the daemon-assigned order forming a single reservoir chain (each tx spent the previous tx's reservoir output). Complements the tier-4 wallet retry from 0272/0275/0277 by adding the low-latency relay path with the wallet retry as fallback. |
| [0279](0279-asm-sequencer-full-stack-userfunded-wallet-wire-systemd.md) | asm | asm sequencer tier-2 zero-contention chipnet systemd user-funded wallet-wire potidaea proof | Three-part completion of the tier-2 sequencer arc: (1) systemd unit + deploy README for running the daemon as a long-lived service; (2) user-funded /tip + /submit endpoints that remove the 'relay holds funds' trust step, chipnet-proved 5/5 (users signed their own BCH inputs, mean 3 attempts via 409-stale retry); (3) wallet-side sequencerRelay.ts client in both shells/app and design/potidaea with probe-first-then-commit race-free dispatch. Plus a chipnet-fund-from-h.mjs helper that topped up A/C/E/F/G from wallet H. |
| [0280](0280-asm-sequencer-settings-ui-toggle.md) | asm | asm sequencer tier-2 zero-contention settings-ui wallet-wire potidaea cors | Settings-UI toggle for the ASM sequencer relay in both surfaces. Beta wallet gets a new 'ASM sequencer relay' group in SettingsScreen with URL input + live reachability probe. Potidaea's dynamic walletMenu gains the same input + status chip (drafted, not yet committed — needs the DEA session's co-mingled main.ts/app.html changes to land first). Server gained permissive CORS so browser probes succeed. Browser-verified reachable↔unreachable transitions against a live chipnet daemon. |
| [0281](0281-wallet-potidaea-balance-flash-overlay-removed.md) | wallet | potidaea bug-fix cleanup balance-paint theseus | Potidaea's "0.82 BCH flash → empty" balance race fixed by removing the pending-swap optimistic overlay layer (−251 lines) and the wc.on('idle') auto-wipe. |
## Wallet UI (shells/app) `(103)`
## Wallet UI (shells/app) `(104)`
- [0023-wallet-display-layer](0023-wallet-display-layer.md) — wallet display layer — D-1/D-2/D-4/D-6 surfaced in one structure · _wallet_
- [0024-wallet-send-import-d5](0024-wallet-send-import-d5.md) — Phase 2 Step 5 — tier-B send + import flows with D-5 · _wallet_
@ -445,6 +446,7 @@ _323 snapshots · 323 tagged · 8 domains · regenerate after adding one._
- [0271-wallet-bulk-import-from-encrypted-keystore](0271-wallet-bulk-import-from-encrypted-keystore.md) — Design spec for Settings→Wallets → "Import from encrypted keystore": UI-side loop over `MultiKeyStore.importWif/importSeed` that reads `Keys/chipnet-keystore.json` (chipnet-keystore/2-encrypted, PBKDF2-SHA256/200k + AES-256-GCM), prompts once for the master password, previews entries with labels/categories/cashaddrs, checkbox-selects, and imports N wallets under one PIN entry. **No MultiKeyStore API changes**; uses the stable `importWif`/`importSeed` shipped 2026-07-07. · _wallet, keystore, bulk-import, multi-account, ui, chipnet, master-password, aes-gcm, pbkdf2, subtle-crypto, multi-key-store, settings_
- [0272-wallet-asmcore-optimistic-chain-retry](0272-wallet-asmcore-optimistic-chain-retry.md) — AsmCore beta wires the tier-4 client-side optimistic chain + retry pattern — swap/mint/swap-all now silently re-plan against the mempool-inclusive reservoir tip and re-broadcast on `txn-mempool-conflict`, resolving the same-target reservoir race client-side (chipnet-measured 1/6 → N/N on the naïve path) · _wallet, asm, orbital-swap, potidaea, agora, chipnet, retry, zero-contention_
- [0276-potidaea-chart-tab-plan](0276-potidaea-chart-tab-plan.md) — Design for a Potidaea Chart tab — historical price chart of any asset in USD, powered by a lean off-chain oracle history + on-chain covenant rate for MBAs · _potidaea, ui, chart, oracle, price-history, plan_
- [0281-wallet-potidaea-balance-flash-overlay-removed](0281-wallet-potidaea-balance-flash-overlay-removed.md) — Potidaea's "0.82 BCH flash → empty" balance race fixed by removing the pending-swap optimistic overlay layer (−251 lines) and the wc.on('idle') auto-wipe. · _potidaea, bug-fix, cleanup, balance-paint, theseus_
## ASM / Orbital Swap / Potidaea / Anthemus `(45)`