aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark/manager.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-29 00:08:41 -0700
committerAdam Soltys <[email protected]>2026-08-29 00:08:41 -0700
commitf06644e729ab5be2fcfa45e32f8b1de52ea36eb3 (patch)
tree5e5737a15a124c0f21fd2580a3de415b778441bb /src/ark/manager.js
parent63b26f2d7e3af485479068af40a9ef7a7386d1d2 (diff)
downloadcoinosv3-f06644e729ab5be2fcfa45e32f8b1de52ea36eb3.tar.gz
coinosv3-f06644e729ab5be2fcfa45e32f8b1de52ea36eb3.zip
The mailbox checkpoint was a timestamp all along: receives are dated by when the server actually delivered them (verified to the millisecond), so an import shows hours-old payments as hours old — no celebration for news that isn't; a wallet whose Savings holds nothing opens onto the Spending that holds everything; and same-payment duplicates fold at display time on every device, however the state arrived
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
Diffstat (limited to 'src/ark/manager.js')
-rw-r--r--src/ark/manager.js18
1 files changed, 11 insertions, 7 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 508cd85..50214fc 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -321,19 +321,23 @@ export class ArkManager {
if (m.kind !== 'arkoor') return changed;
const ourKeys = [hex.encode(this._key(0).pubkey)];
const tip = await this._tipMemo();
- // Expiry-based dating is for the FIRST catch-up only, where old receipts
- // deserve old dates. A delivery landing on a live wallet happened NOW —
- // the estimate mis-dated a fresh receive by a month when the server's
- // vtxo lifetime changed under it (the math assumes every coin began with
- // the full current lifetime).
+ // The mailbox checkpoint IS the delivery timestamp (ms << 20, verified
+ // to the millisecond against the server's rows) — the one honest date
+ // for a receive, whether it lands live or surfaces in an import's
+ // catch-up. Expiry-based estimation stays only as the fallback for
+ // messages without a usable checkpoint.
const live = !this.state.baselinePending;
+ const ckTs = (() => {
+ const t = Math.floor(Number(m.checkpoint || 0) / 1048576);
+ return t > 1.6e12 && t < Date.now() + 60_000 ? t : null;
+ })();
for (const v of m.vtxos) {
if (this._vtxo(v.id)) {
// The vtxo beat the message here (a mirror or snapshot merge carried
// it) — the money is present but the story is missing. Backfill the
// receive movement so history matches the balance.
if (!this.state.movements.some((x) => x.vtxoId === v.id)) {
- this._movement({ type: 'receive', amountSat: v.amountSat, status: 'complete', vtxoId: v.id, ts: live ? Date.now() : this._estimateReceiveTs(this._decoded(this._vtxo(v.id)), tip) });
+ this._movement({ type: 'receive', amountSat: v.amountSat, status: 'complete', vtxoId: v.id, ts: ckTs ?? (live ? Date.now() : this._estimateReceiveTs(this._decoded(this._vtxo(v.id)), tip)) });
changed = true;
}
continue;
@@ -354,7 +358,7 @@ export class ArkManager {
if (!this._addVtxo(v, v._raw.bytes, 0)) continue;
this._movement({
type: 'receive', amountSat: v.amountSat, status: 'complete', vtxoId: v.id,
- ts: live ? Date.now() : this._estimateReceiveTs(v, tip),
+ ts: ckTs ?? (live ? Date.now() : this._estimateReceiveTs(v, tip)),
});
changed = true;
}