aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.js13
-rw-r--r--src/ark/manager.js11
-rw-r--r--src/features/ark.js6
3 files changed, 22 insertions, 8 deletions
diff --git a/src/app.js b/src/app.js
index 10b294c..6ad7dcc 100644
--- a/src/app.js
+++ b/src/app.js
@@ -229,11 +229,7 @@ function footer() {
h('a', { href: 'https://adamsoltys.com', target: '_blank', rel: 'noopener' }, 'Adam Soltys'),
h('span', { class: 'faint' }, ' · '),
t('footerSourceOn') + ' ',
- h('a', { href: 'https://github.com/coinos/coinosv3', target: '_blank', rel: 'noopener' }, 'GitHub'),
- // The commit stamp settles "is my PWA fresh?" AND "what code is this?"
- // in one glance — filled at build time; the hash links to GitHub.
- h('span', { class: 'faint' }, ' · Running '),
- h('a', { class: 'faint', href: 'https://github.com/coinos/coinosv3/commit/{{COMMIT}}', target: '_blank', rel: 'noopener' }, '{{COMMIT}}')
+ h('a', { href: 'https://github.com/coinos/coinosv3', target: '_blank', rel: 'noopener' }, 'GitHub')
),
h(
'div',
@@ -250,7 +246,12 @@ function footer() {
h('span', { class: 'faint' }, ' · '),
h('button', { class: 'linklike', style: 'font-weight:400', onClick: toggleTheme }, resolvedTheme() === 'dark' ? t('lightMode') : t('darkMode'))
),
- h('div', { style: 'margin-top:8px' }, languagePicker())
+ h('div', { style: 'margin-top:8px' }, languagePicker()),
+ // The commit stamp settles "is my PWA fresh?" AND "what code is this?" in
+ // one glance — its own last line, so it never wraps the credits on mobile.
+ h('div', { class: 'faint', style: 'margin-top:6px' },
+ 'Running git commit ',
+ h('a', { class: 'faint', href: 'https://github.com/coinos/coinosv3/commit/{{COMMIT}}', target: '_blank', rel: 'noopener' }, '{{COMMIT}}'))
);
}
diff --git a/src/ark/manager.js b/src/ark/manager.js
index eb23b51..99ae2c5 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -298,7 +298,16 @@ export class ArkManager {
const ourKeys = [hex.encode(this._key(0).pubkey)];
const tip = await this._tipMemo();
for (const v of m.vtxos) {
- if (this._vtxo(v.id)) continue;
+ 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: this._estimateReceiveTs(this._decoded(this._vtxo(v.id)), tip) });
+ changed = true;
+ }
+ continue;
+ }
try {
await validateVtxo(v, { serverPubkey: this.serverPub, chain: this.chain, expectPubkeys: ourKeys });
} catch (e) {
diff --git a/src/features/ark.js b/src/features/ark.js
index bff84a9..66e4e44 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -183,7 +183,11 @@ export function mergeArkStates(a, b) {
else { lg.claimed = lg.claimed || rg.claimed; lg.revoked = lg.revoked || rg.revoked; }
}
out.gifts = [...gifts.values()];
- out.mailboxCheckpoint = Math.max(a.mailboxCheckpoint || 0, b.mailboxCheckpoint || 0);
+ // The checkpoint is READING PROGRESS, not state: adopting a remote one
+ // skips every message this device never processed — balance appears (the
+ // vtxo unions in) but its receive movement and celebration never happen.
+ // Keep the local checkpoint; re-read messages dedupe harmlessly.
+ out.mailboxCheckpoint = a.mailboxCheckpoint || 0;
out.nextKeyIndex = Math.max(a.nextKeyIndex || 1, b.nextKeyIndex || 1);
out.nextLnRecvIndex = Math.max(a.nextLnRecvIndex || 0, b.nextLnRecvIndex || 0);
out.receiveAckTs = Math.max(a.receiveAckTs || 0, b.receiveAckTs || 0);