Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions components/Amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface AmountDisplayProps {
accessibilityLabel?: string;
roundAmount?: boolean;
separatorSwap?: boolean;
onPendingPress?: () => void;
}

interface SymbolProps {
Expand All @@ -71,28 +72,44 @@ function AmountDisplay({
accessible,
accessibilityLabel,
roundAmount = false,
separatorSwap = false
separatorSwap = false,
onPendingPress
}: AmountDisplayProps) {
if (unit === 'fiat' && !symbol) {
console.error('Must include a symbol when rendering fiat');
}

const actualSymbol = unit === 'BTC' ? '₿' : symbol;

const Pending = () => (
<View
style={{
paddingHorizontal: 4,
paddingTop: jumboText ? 4 : 1
}}
>
<ClockIcon
color={themeColor('bitcoin')}
width={jumboText ? 24 : 12}
height={jumboText ? 24 : 12}
/>
</View>
);
const Pending = () => {
const icon = (
<View
style={{
paddingHorizontal: 4,
paddingTop: jumboText ? 4 : 1
}}
>
<ClockIcon
color={themeColor('bitcoin')}
width={jumboText ? 24 : 12}
height={jumboText ? 24 : 12}
/>
</View>
);

if (!onPendingPress) {
return icon;
}

return (
<TouchableOpacity
onPress={onPendingPress}
accessibilityLabel={localeString('general.pending')}
>
{icon}
</TouchableOpacity>
);
};

const FiatSymbol: React.FC<SymbolProps> = ({ accessible }) => (
<Body
Expand Down Expand Up @@ -324,6 +341,7 @@ interface AmountProps {
accessibilityLabel?: string;
negative?: boolean;
roundAmount?: boolean;
onPendingPress?: () => void;
}

@inject('FiatStore', 'UnitsStore', 'SettingsStore')
Expand All @@ -347,7 +365,8 @@ export default class Amount extends React.Component<AmountProps, {}> {
accessible,
accessibilityLabel,
negative = false,
roundAmount = false
roundAmount = false,
onPendingPress
} = this.props;
const FiatStore = this.props.FiatStore!;
const UnitsStore = this.props.UnitsStore!;
Expand Down Expand Up @@ -401,6 +420,9 @@ export default class Amount extends React.Component<AmountProps, {}> {
accessible={accessible}
accessibilityLabel={accessibilityLabel}
roundAmount={roundAmount}
onPendingPress={
pending ? onPendingPress : undefined
}
/>
</TouchableOpacity>
);
Expand All @@ -420,6 +442,7 @@ export default class Amount extends React.Component<AmountProps, {}> {
accessible={accessible}
accessibilityLabel={accessibilityLabel}
roundAmount={roundAmount}
onPendingPress={pending ? onPendingPress : undefined}
/>
);
}
Expand Down Expand Up @@ -474,6 +497,7 @@ export default class Amount extends React.Component<AmountProps, {}> {
accessible={accessible}
accessibilityLabel={accessibilityLabel}
roundAmount={roundAmount}
onPendingPress={pending ? onPendingPress : undefined}
/>
</TouchableOpacity>
);
Expand All @@ -493,6 +517,7 @@ export default class Amount extends React.Component<AmountProps, {}> {
accessible={accessible}
accessibilityLabel={accessibilityLabel}
roundAmount={roundAmount}
onPendingPress={pending ? onPendingPress : undefined}
/>
);
}
Expand Down
6 changes: 6 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,12 @@
"views.Wallet.batterySaverWarningText": "Battery saver mode is currently enabled. This may affect Lightning payments, routing, and overall performance. For the best experience, consider disabling battery saver mode.",
"views.Wallet.KeypadPane.lspExplainerFirstChannel": "It is recommended that your first lightning receive be 100,000 sats or more; the larger the better. A setup fee will be deducted from this amount.",
"views.Wallet.KeypadPane.lspExplainer": "You will be charged a setup fee if paid over lightning.",
"views.Wallet.pendingBalanceIcon.title": "Pending Balance",
"views.Wallet.pendingBalanceIcon.explainer": "This balance is pending confirmation and will update once confirmed.",
"views.Wallet.pendingBalanceIcon.explainerOnchain": "This balance is pending confirmation and will update once confirmed on-chain.",
"views.Wallet.pendingBalanceIcon.explainerOnchainLine2": "Channel operations (ie. opens and closes) may require multiple confirmations",
"views.Wallet.pendingBalanceIcon.explainerLightning": "This balance is pending confirmation and will update once settled.",
"views.Wallet.pendingBalanceIcon.explainerCashu": "This balance is pending confirmation and will update once redeemed.",
"view.Wallet.PosPane.orderNumber": "Order number",
"views.BTCPayConfigQRScanner.text": "Scan a BTCPay Config under Settings > Services > LND Rest",
"views.BTCPayConfigQRScanner.error": "Error fetching BTCPay config",
Expand Down
145 changes: 113 additions & 32 deletions views/Wallet/BalancePane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import CashuStore from '../../stores/CashuStore';
import NodeInfoStore from '../../stores/NodeInfoStore';
import SettingsStore from '../../stores/SettingsStore';
import SyncStore from '../../stores/SyncStore';
import ModalStore from '../../stores/ModalStore';

import LockIcon from '../../assets/images/SVG/Lock.svg';

Expand All @@ -39,6 +40,7 @@ interface BalancePaneProps {
NodeInfoStore: NodeInfoStore;
SettingsStore: SettingsStore;
SyncStore: SyncStore;
ModalStore: ModalStore;
loading: boolean;
}

Expand All @@ -51,7 +53,8 @@ interface BalancePaneState {
'CashuStore',
'NodeInfoStore',
'SettingsStore',
'SyncStore'
'SyncStore',
'ModalStore'
)
@observer
export default class BalancePane extends React.PureComponent<
Expand All @@ -71,6 +74,38 @@ export default class BalancePane extends React.PureComponent<
}
}

handlePendingPress = (
context: 'onchain' | 'lightning' | 'cashu' = 'onchain'
) => {
const { ModalStore } = this.props;

const explainerKeyMap = {
onchain: 'views.Wallet.pendingBalanceIcon.explainerOnchain',
lightning: 'views.Wallet.pendingBalanceIcon.explainerLightning',
cashu: 'views.Wallet.pendingBalanceIcon.explainerCashu'
};

let modalText: string | Array<string>;
if (context === 'onchain') {
modalText = [
localeString(
'views.Wallet.pendingBalanceIcon.explainerOnchain'
),
localeString(
'views.Wallet.pendingBalanceIcon.explainerOnchainLine2'
)
];
} else {
modalText = localeString(explainerKeyMap[context]);
}

ModalStore.toggleInfoModal({
title: localeString('views.Wallet.pendingBalanceIcon.title'),
text: modalText,
link: 'https://docs.zeusln.app/for-users/using-zeus/pending-balances'
});
};

render() {
const {
NodeInfoStore,
Expand Down Expand Up @@ -101,6 +136,17 @@ export default class BalancePane extends React.PureComponent<
rescanCurrentHeight
} = SyncStore;

const unspentSentTokens =
CashuStore.sentTokens?.filter((token) => !token.spent) || [];
const pendingCashuBalance = new BigNumber(
unspentSentTokens.reduce(
(sum, token) => sum + (token.getAmount || 0),
0
)
)
.toNumber()
.toFixed(3);

// Calculate rescan progress (clamped to max 1 in case current exceeds best)
// Use explicit null checks since rescanStartHeight can be 0 for genesis rescans
const rescanProgress =
Expand Down Expand Up @@ -146,6 +192,9 @@ export default class BalancePane extends React.PureComponent<
jumboText
toggleable
pending
onPendingPress={() =>
this.handlePendingPress('lightning')
}
/>
<View style={styles.conversion}>
<Conversion
Expand All @@ -158,39 +207,71 @@ export default class BalancePane extends React.PureComponent<
) : null}
</View>
);
const BalanceViewCombined = () => (
<View style={styles.balance}>
<Amount
sats={combinedBalanceValue}
sensitive
jumboText
toggleable
/>
{!(unconfirmedBlockchainBalance || pendingOpenBalance) && (
<View style={styles.conversion}>
<Conversion sats={combinedBalanceValue} sensitive />
</View>
)}
{unconfirmedBlockchainBalance || pendingOpenBalance ? (
<>
<Amount
sats={pendingUnconfirmedBalance}
sensitive
jumboText
toggleable
pending
/>
<View style={styles.conversionSecondary}>
<Conversion
sats={combinedBalanceValue}
satsPending={pendingUnconfirmedBalance}
const BalanceViewCombined = () => {
const hasOnchainPending =
unconfirmedBlockchainBalance || pendingOpenBalance;
const hasCashuPending =
settings?.ecash?.enableCashu && Number(pendingCashuBalance) > 0;
const hasAnyPending = hasOnchainPending || hasCashuPending;

return (
<View style={styles.balance}>
<Amount
sats={combinedBalanceValue}
sensitive
jumboText
toggleable
/>
{!hasAnyPending && (
<View style={styles.conversion}>
<Conversion sats={combinedBalanceValue} sensitive />
</View>
)}
{hasOnchainPending && (
<>
<Amount
sats={pendingUnconfirmedBalance}
sensitive
jumboText
toggleable
pending
onPendingPress={() =>
this.handlePendingPress('onchain')
}
/>
</View>
</>
) : null}
</View>
);
<View style={styles.conversionSecondary}>
<Conversion
sats={combinedBalanceValue}
satsPending={pendingUnconfirmedBalance}
sensitive
/>
</View>
</>
)}
{hasCashuPending && (
<>
<Amount
sats={pendingCashuBalance}
sensitive
jumboText
toggleable
pending
onPendingPress={() =>
this.handlePendingPress('cashu')
}
/>
<View style={styles.conversionSecondary}>
<Conversion
sats={combinedBalanceValue}
satsPending={pendingCashuBalance}
sensitive
/>
</View>
</>
)}
</View>
);
};

let balancePane;
const error =
Expand Down
4 changes: 3 additions & 1 deletion views/Wallet/Wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,8 @@ export default class Wallet extends React.Component<WalletProps, WalletState> {
CashuStore,
SettingsStore,
SyncStore,
navigation
navigation,
ModalStore
} = this.props;
const { isSyncing, isInExpressGraphSync } = SyncStore;
const { nodeInfo } = NodeInfoStore;
Expand Down Expand Up @@ -1053,6 +1054,7 @@ export default class Wallet extends React.Component<WalletProps, WalletState> {
CashuStore={CashuStore}
SettingsStore={SettingsStore}
SyncStore={SyncStore}
ModalStore={ModalStore}
loading={loading}
/>

Expand Down