aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-04 07:15:52 -0700
committerAdam Soltys <[email protected]>2026-07-04 07:15:52 -0700
commit6cc0dd619e71099675a7cb1b1566cbe2c06b7bc1 (patch)
treeb672290c5c628eb33bfcb27dff12a2c14f69a6a9
parentd39aa82e0e97f1f3373980cb28f04e0415965723 (diff)
downloadcoinosv3-6cc0dd619e71099675a7cb1b1566cbe2c06b7bc1.tar.gz
coinosv3-6cc0dd619e71099675a7cb1b1566cbe2c06b7bc1.zip
Ark: history rows open a detail view
Ark movements are clickable like on-chain txs. The detail view shows amount, date, status, the destination address (sends, copyable), the vtxo id, and for boards the funding txid with copy + explorer link. Movements now record those fields at completion time; older records without them still render (amount/date/status only). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
-rw-r--r--src/app.js46
-rw-r--r--src/ark/manager.js11
-rw-r--r--src/i18n.js3
-rw-r--r--tools/ark/ui-test.js8
4 files changed, 61 insertions, 7 deletions
diff --git a/src/app.js b/src/app.js
index 5115c9a..61b453b 100644
--- a/src/app.js
+++ b/src/app.js
@@ -2758,6 +2758,7 @@ function tabsBar() {
ui.tab = id;
ui.revealShown = false; // re-mask the recovery phrase whenever tabs change
ui.txDetail = null; // back to the history list when leaving/returning
+ ui.arkMoveDetail = null;
ui.giftsAll = false; // and back to the paged history, not the all-gifts view
ui.addrScan = false; // and back to the main Settings, not the address list
ui.bump = null;
@@ -3821,14 +3822,48 @@ function giftHistoryItem(g) {
);
}
-// An Ark movement (receive / send / board) as a history row. No txid to open —
-// these are off-chain; the Settings card shows the fuller activity detail.
+// Detail view for an Ark movement. Off-chain payments have no on-chain txid;
+// we show what exists: amount, time, status, destination (sends), the vtxo id,
+// and — for boards — the funding txid with an explorer link.
+function arkMoveDetailView(m) {
+ const incoming = m.type !== 'send';
+ const label = m.type === 'receive' ? t('received') : m.type === 'board' ? t('arkBoarded') : t('sent');
+ const row = (k, v) => h('div', { class: 'row between', style: 'gap:12px' },
+ h('span', { class: 'small muted', style: 'flex-shrink:0' }, k), h('span', { class: 'small', style: 'text-align:right;word-break:break-all' }, v));
+ const url = m.txid ? wallet.api.explorerTx(m.txid) : null;
+ return h(
+ 'div',
+ { class: 'card col', style: 'gap:10px' },
+ h('div', { class: 'row gap6', style: 'align-items:center' },
+ h('h3', { style: 'margin:0' }, label),
+ h('span', { class: 'tag' }, 'Ark'),
+ m.status !== 'complete' ? h('span', { class: 'tag pending' }, m.status) : null),
+ h('div', { class: incoming ? 'amount-pos' : 'amount-neg', style: 'font-size:20px' },
+ (incoming ? '+' : '-') + fmtAmount(m.amountSat) + ' ' + unitLabel()),
+ row(t('dateLabel'), new Date(m.ts).toLocaleString()),
+ m.to ? row(t('arkPayTo'), shortAddr(m.to, 16, 12)) : null,
+ m.vtxoId ? row(t('arkVtxoId'), shortTxid(m.vtxoId)) : null,
+ m.detail ? row(t('detailsLabel'), m.detail) : null,
+ m.txid
+ ? h('div', { class: 'col', style: 'gap:6px' },
+ h('div', { class: 'small muted' }, t('transactionId')),
+ h('div', { class: 'addr-box', style: 'width:100%' }, m.txid),
+ h('div', { class: 'row gap6' },
+ copyBtn(m.txid, t('copyTxid')),
+ h('a', { class: 'btn btn-sm', href: url, target: '_blank', rel: 'noopener', onClick: (e) => { e.preventDefault(); openExternal(url); } }, t('viewOnMempool'))))
+ : null,
+ m.to ? copyBtn(m.to, t('copyAddress')) : null,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.arkMoveDetail = null; render(); } }, t('back'))
+ );
+}
+
+// An Ark movement (receive / send / board) as a history row.
function arkHistoryItem(m) {
const incoming = m.type !== 'send';
const label = m.type === 'receive' ? t('received') : m.type === 'board' ? t('arkBoarded') : t('sent');
return h(
'div',
- { class: 'item' },
+ { class: 'item', style: 'cursor:pointer', onClick: () => { ui.arkMoveDetail = m.id; render(); } },
h('div', { class: `ico ${incoming ? 'in' : 'out'}` }, incoming ? '↓' : '↑'),
h('div', { class: 'grow' },
h('div', { class: 'row gap6' },
@@ -3897,6 +3932,11 @@ function giftsAllView(gifts) {
function historyTab() {
if (ui.bump) return bumpView();
const txs = wallet.history; // BIP84 txs + silent-payment receipts, newest first
+ if (ui.arkMoveDetail) {
+ const m = ark && ark.state ? ark.movements().find((x) => x.id === ui.arkMoveDetail) : null;
+ if (m) return arkMoveDetailView(m);
+ ui.arkMoveDetail = null;
+ }
if (ui.txDetail) {
const tx = txs.find((x) => x.txid === ui.txDetail);
if (tx) return txDetailView(tx);
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 1f21159..c2fd0c6 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -163,7 +163,7 @@ export class ArkManager {
throw e; // network errors etc: retry next sync
}
this._addVtxo(v, v._raw.bytes, 0);
- this._movement({ type: 'receive', amountSat: v.amountSat, status: 'complete' });
+ this._movement({ type: 'receive', amountSat: v.amountSat, status: 'complete', vtxoId: v.id });
changed = true;
}
changed = true;
@@ -211,7 +211,7 @@ export class ArkManager {
const changeSat = input.amountSat - amountSat;
const action = {
id: `send-${Date.now()}`, type: 'send', step: 'created',
- inputId: input.id, amountSat,
+ inputId: input.id, amountSat, destAddress: addrString,
destPubkey: dest.userPubkey, destBlindedId: mailboxDelivery.data,
changeIndex: changeSat > 0 ? this.state.nextKeyIndex++ : null, changeSat,
};
@@ -278,7 +278,10 @@ export class ArkManager {
if (change) change.state = 'spendable';
}
action.step = 'done';
- this._movement({ type: 'send', amountSat: action.amountSat, status: 'complete' });
+ this._movement({
+ type: 'send', amountSat: action.amountSat, status: 'complete',
+ to: action.destAddress, vtxoId: decodeVtxo(hex.decode(action.destBytes)).id,
+ });
this._save();
}
}
@@ -359,7 +362,7 @@ export class ArkManager {
const decoded = decodeVtxo(bytes);
this._addVtxo(decoded, bytes, action.keyIndex);
action.step = 'done';
- this._movement({ type: 'board', amountSat: decoded.amountSat, status: 'complete' });
+ this._movement({ type: 'board', amountSat: decoded.amountSat, status: 'complete', txid: action.fundingTxid, vtxoId: decoded.id });
this._save();
}
}
diff --git a/src/i18n.js b/src/i18n.js
index 5371e5c..9cbc09d 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -377,6 +377,9 @@ const STR = {
arkSendBtn: 'Send',
arkSentTitle: 'Sent!',
arkBoarded: 'Boarded to Ark',
+ dateLabel: 'Date',
+ detailsLabel: 'Details',
+ arkVtxoId: 'Coin (VTXO)',
lnReceiveIntro: 'Receive over Lightning — paid into this wallet on-chain, non-custodial.',
lnReceiveAwaiting: 'Share this invoice. When it’s paid, the sats land here automatically.',
lnReceiveWatching: 'Waiting for payment…',
diff --git a/tools/ark/ui-test.js b/tools/ark/ui-test.js
index 2c488d8..a3de1c1 100644
--- a/tools/ark/ui-test.js
+++ b/tools/ark/ui-test.js
@@ -141,6 +141,14 @@ try {
check('history shows ark receive', /received\s*\n?\s*ark/i.test(hist));
check('history shows ark send', /sent\s*\n?\s*ark/i.test(hist));
+ // rows open a detail view
+ await clickText('.item', 'sent');
+ await sleep(300);
+ check('ark detail view', await waitText('Coin (VTXO)', 5000));
+ check('detail shows destination', (await bodyText()).includes('tark1'));
+ await clickText('button', 'Back');
+ await sleep(300);
+
// --- board from settings ---
console.log('\n[4] board');
await clickText('.tabs button', 'Receive');