aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js17
-rw-r--r--src/i18n.js1
-rw-r--r--src/wallet.js13
3 files changed, 26 insertions, 5 deletions
diff --git a/src/app.js b/src/app.js
index 4aca644..ae21240 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1450,18 +1450,25 @@ function offlineBanner() {
function balanceCard() {
// Only dim on the very first load; background updates happen silently.
const firstLoad = wallet.scanning && !wallet.loaded;
+ const locked = wallet.lockedValue;
return h(
'div',
{ class: 'card balance' },
h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.05em' }, t('balance')),
- // Headline is the projected balance (confirmed + mempool), so a pending
- // spend is debited immediately rather than waiting for confirmation.
- h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' }, fmtAmount(wallet.total), ' ', unitTag('unit')),
- wallet.pending > 0
+ // Headline is the spendable, projected balance (confirmed + mempool, minus
+ // coins locked in unclaimed gifts), so both a pending spend and a reserved
+ // gift are debited immediately rather than waiting to confirm/claim.
+ h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' }, fmtAmount(wallet.spendable), ' ', unitTag('unit')),
+ wallet.pending > 0 || locked > 0
? h(
'div',
{ class: 'split' },
- h('div', {}, h('div', { class: 'k' }, t('pending')), h('div', { class: 'v pending' }, fmtAmount(wallet.pending), ' ', unitTag()))
+ wallet.pending > 0
+ ? h('div', {}, h('div', { class: 'k' }, t('pending')), h('div', { class: 'v pending' }, fmtAmount(wallet.pending), ' ', unitTag()))
+ : null,
+ locked > 0
+ ? h('div', {}, h('div', { class: 'k' }, t('lockedInGifts')), h('div', { class: 'v' }, fmtAmount(locked), ' ', unitTag()))
+ : null
)
: null
);
diff --git a/src/i18n.js b/src/i18n.js
index 42fd34b..8e7cbf4 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -195,6 +195,7 @@ const STR = {
giftAmountInvalid: 'Enter at least {n}.',
giftMinNote: 'Minimum {n} at current fees, so it clears dust when claimed.',
giftAnother: 'Create another',
+ lockedInGifts: 'Locked in gifts',
giftReserved: 'Unclaimed gift links ({n})',
giftReclaim: 'Reclaim',
giftReclaimPrompt: 'Reclaim frees this coin for your next payment, but the link stays claimable until you spend it. Pay a network fee to revoke it right now instead?',
diff --git a/src/wallet.js b/src/wallet.js
index 72d33be..2871c5e 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -583,6 +583,19 @@ export class Wallet {
return this.confirmed + this.pending;
}
+ // Confirmed coins locked behind unclaimed gift links. The whole source coin is
+ // committed until the gift is claimed (our change only comes back in the
+ // claimer's tx), so it's already excluded from spending — and from the
+ // spendable balance below.
+ get lockedValue() {
+ const res = this.reservedSet();
+ return this.utxos.reduce((s, u) => s + (res.has(utxoId(u)) ? u.value : 0), 0);
+ }
+ // What the user can actually spend right now: everything minus gift locks.
+ get spendable() {
+ return this.total - this.lockedValue;
+ }
+
_recomputeBalanceFromChains() {
let c = 0;
let p = 0;