From 58a05ea398724b66a0dd5e3cc81f61d98d96e26c Mon Sep 17 00:00:00 2001 From: peachbits Date: Mon, 9 Nov 2026 12:54:07 -0800 Subject: [PATCH 1/6] Show recipient and amount when accelerating The Accelerate modal showed only the old and new fee. Acceleration rebuilds and re-signs the whole payment, so the confirmation should show everything the user is authorizing. The modal now lists the recipient address(es) and the send amount, taken from the `spendTargets` the plugin's `accelerate()` returns, and the text asks the user to review them along with the new fee. With a plugin that does not return `spendTargets` the rows are absent. --- CHANGELOG.md | 1 + .../modals/AccelerateTxModal.test.tsx | 208 +++++++++++---- .../AccelerateTxModal.test.tsx.snap | 245 +++++++++++++++++- src/components/modals/AccelerateTxModal.tsx | 85 +++++- src/locales/en_US.ts | 5 +- src/locales/strings/enUS.json | 4 +- 6 files changed, 480 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea74eb21598..88cf5bac427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased (develop) +- changed: The Accelerate Transaction confirmation shows the recipient address and send amount alongside the fees. - fixed: Exchange info, gift card info, Houdini chain, and Zcash migration status refreshes running twice per interval ## 4.53.0 (staging) diff --git a/src/__tests__/modals/AccelerateTxModal.test.tsx b/src/__tests__/modals/AccelerateTxModal.test.tsx index 7624093a1b0..2133d467c7c 100644 --- a/src/__tests__/modals/AccelerateTxModal.test.tsx +++ b/src/__tests__/modals/AccelerateTxModal.test.tsx @@ -1,5 +1,12 @@ import { describe, expect, it } from '@jest/globals' import { render } from '@testing-library/react-native' +import type { + EdgeCurrencyInfo, + EdgeDenomination, + EdgeTokenId, + EdgeTokenMap, + EdgeTransaction +} from 'edge-core-js' import * as React from 'react' import { AccelerateTxModalComponent } from '../../components/modals/AccelerateTxModal' @@ -7,66 +14,163 @@ import { getTheme } from '../../components/services/ThemeContext' import { fakeAirshipBridge } from '../../util/fake/fakeAirshipBridge' import { btcCurrencyInfo } from '../../util/fake/fakeBtcInfo' import { makeFakeCurrencyConfig } from '../../util/fake/fakeCurrencyConfig' +import { ethCurrencyInfo } from '../../util/fake/fakeEthInfo' import { FakeProviders } from '../../util/fake/FakeProviders' +type TxSpendTarget = NonNullable[number] + +const usdcTokens: EdgeTokenMap = { + usdc: { + currencyCode: 'USDC', + displayName: 'USD Coin', + denominations: [{ multiplier: '1000000', name: 'USDC' }], + networkLocation: undefined + } +} + +function makeTx( + currencyCode: string, + tokenId: EdgeTokenId, + nativeAmount: string, + networkFee: string, + spendTargets?: TxSpendTarget[] +): EdgeTransaction { + return { + blockHeight: 0, + currencyCode, + date: 0, + isSend: true, + memos: [], + nativeAmount, + networkFee, + networkFees: [], + otherParams: {}, + ourReceiveAddresses: ['123123123'], + signedTx: '', + spendTargets, + tokenId, + txid: '', + walletId: '' + } +} + +function makeTarget( + currencyCode: string, + nativeAmount: string, + publicAddress: string +): TxSpendTarget { + return { + currencyCode, + nativeAmount, + publicAddress, + memo: undefined, + uniqueIdentifier: undefined + } +} + +function renderModal(opts: { + currencyInfo: EdgeCurrencyInfo + tokens?: EdgeTokenMap + replacedTx: EdgeTransaction + acceleratedTx: EdgeTransaction + amountDisplayDenomination: EdgeDenomination + feeDisplayDenomination: EdgeDenomination +}): ReturnType { + const fakeWallet: any = { + fiatCurrencyCode: 'iso:USD', + currencyInfo: opts.currencyInfo, + currencyConfig: makeFakeCurrencyConfig(opts.currencyInfo, opts.tokens) + } + + return render( + + + + ) +} + +const mbtc: EdgeDenomination = { multiplier: '100000', name: 'mBTC' } +const legacyFixtureDenom: EdgeDenomination = { + multiplier: '1000000', + name: 'BTC' +} + describe('AccelerateTxModalComponent', () => { it('should render with loading props', () => { - const fakeWallet: any = { - addCustomToken: 'shib', - fiatCurrencyCode: 'iso:USD', + const rendered = renderModal({ + currencyInfo: btcCurrencyInfo, + replacedTx: makeTx('BTC', null, '-681', '681'), + acceleratedTx: makeTx('BTC', null, '-95362', '1362', [ + makeTarget('BTC', '94000', 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') + ]), + amountDisplayDenomination: legacyFixtureDenom, + feeDisplayDenomination: legacyFixtureDenom + }) + + expect(rendered.toJSON()).toMatchSnapshot() + rendered.unmount() + }) + + it('shows each recipient with its own amount', () => { + const rendered = renderModal({ currencyInfo: btcCurrencyInfo, - currencyConfig: makeFakeCurrencyConfig(btcCurrencyInfo) + replacedTx: makeTx('BTC', null, '-150681', '681'), + acceleratedTx: makeTx('BTC', null, '-151362', '1362', [ + makeTarget('BTC', '100000', 'bc1qfirstrecipient'), + makeTarget('BTC', '50000', 'bc1qsecondrecipient') + ]), + amountDisplayDenomination: mbtc, + feeDisplayDenomination: mbtc + }) + + expect(rendered.getByText('bc1qfirstrecipient')).toBeTruthy() + expect(rendered.getByText('bc1qsecondrecipient')).toBeTruthy() + expect(rendered.getByText('1 ($ 0.00)')).toBeTruthy() + expect(rendered.getByText('0.5 ($ 0.00)')).toBeTruthy() + rendered.unmount() + }) + + it('shows a token amount in the token, not the parent currency', () => { + const eth: EdgeDenomination = { + multiplier: '1000000000000000000', + name: 'ETH' } + const rendered = renderModal({ + currencyInfo: ethCurrencyInfo, + tokens: usdcTokens, + replacedTx: makeTx('USDC', 'usdc', '-25000000', '0'), + acceleratedTx: makeTx('USDC', 'usdc', '-25000000', '0', [ + makeTarget('USDC', '25000000', '0xrecipient') + ]), + amountDisplayDenomination: usdcTokens.usdc.denominations[0], + feeDisplayDenomination: eth + }) - const rendered = render( - - - - ) + expect(rendered.getByText('25 ($ 0.00)')).toBeTruthy() + rendered.unmount() + }) - expect(rendered.toJSON()).toMatchSnapshot() + it('omits the recipient rows when the plugin names no spend targets', () => { + const rendered = renderModal({ + currencyInfo: btcCurrencyInfo, + replacedTx: makeTx('BTC', null, '-681', '681'), + acceleratedTx: makeTx('BTC', null, '-1362', '1362'), + amountDisplayDenomination: mbtc, + feeDisplayDenomination: mbtc + }) + + expect(rendered.queryByText('Recipient Address')).toBeNull() + expect(rendered.queryByText('Send Amount')).toBeNull() rendered.unmount() }) }) diff --git a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap index 39ab8dfb6b0..485cac2462c 100644 --- a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap @@ -308,7 +308,7 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` ] } > - This will resend your transaction with its fee doubled to help prioritize it for network confirmation. Please review new fee amount before confirming. + This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming. + + + + Recipient Address + + + bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu + + + + +  + + + + + + + Send Amount + + + 0.094 ($ 0.00) + + + { } getTxFeeDisplay = (edgeTransaction: EdgeTransaction): string => { - const { - exchangeRates, - feeDisplayDenomination, - wallet, - isoFiatCurrencyCode - } = this.props + return this.formatAmount( + edgeTransaction, + null, + this.props.feeDisplayDenomination + ) + } + + /** + * The amount one spend target receives, in the units and fiat rate of the + * asset being sent (a token send pays out in the token, not the parent + * currency the fee is paid in). + */ + getSendAmountDisplay = (nativeAmount: string): string => { + const { acceleratedTx, amountDisplayDenomination } = this.props + return this.formatAmount( + { + ...acceleratedTx, + networkFee: nativeAmount, + parentNetworkFee: undefined + }, + acceleratedTx.tokenId, + amountDisplayDenomination + ) + } + + /** + * Formats the fee of a transaction as " ()" for the asset + * named by tokenId. The send amount reuses it by standing in for the fee. + */ + formatAmount = ( + edgeTransaction: EdgeTransaction, + tokenId: EdgeTokenId, + displayDenomination: EdgeDenomination + ): string => { + const { exchangeRates, wallet, isoFiatCurrencyCode } = this.props - const feeDefaultDenomination = getExchangeDenom(wallet.currencyConfig, null) const transactionFee = convertTransactionFeeToDisplayFee( wallet.currencyInfo.pluginId, - null, + tokenId, isoFiatCurrencyCode, exchangeRates, edgeTransaction, - feeDisplayDenomination, - feeDefaultDenomination + displayDenomination, + getExchangeDenom(wallet.currencyConfig, tokenId) ) - const feeSyntax = `${transactionFee.cryptoSymbol ?? ''} ${ + return `${transactionFee.cryptoSymbol ?? ''} ${ transactionFee.cryptoAmount } (${transactionFee.fiatSymbol ?? ''} ${transactionFee.fiatAmount})` - - return feeSyntax } render(): React.ReactElement { @@ -136,6 +165,10 @@ export class AccelerateTxModalComponent extends PureComponent { const oldFee = this.getTxFeeDisplay(replacedTx) const newFee = this.getTxFeeDisplay(acceleratedTx) + // Where the accelerated funds go. The plugin names every output of the + // replacement that is not the wallet's own. + const spendTargets = acceleratedTx.spendTargets ?? [] + const isLowerAmount = lt( getTxSendAmount(acceleratedTx), getTxSendAmount(replacedTx) @@ -152,6 +185,24 @@ export class AccelerateTxModalComponent extends PureComponent { {lstrings.transaction_details_accelerate_transaction_instructional} + {spendTargets.map((target, index) => ( + + + + + ))} ({ export function AccelerateTxModal(props: OwnProps): React.ReactElement { const theme = useTheme() + const amountDisplayDenomination = useSelector(state => + selectDisplayDenom( + state, + props.wallet.currencyConfig, + props.acceleratedTx.tokenId + ) + ) const exchangeRates = useSelector(state => state.exchangeRates) const feeDisplayDenomination = useSelector(state => selectDisplayDenom(state, props.wallet.currencyConfig, null) @@ -234,6 +292,7 @@ export function AccelerateTxModal(props: OwnProps): React.ReactElement { return ( Date: Fri, 13 Nov 2026 15:32:17 -0800 Subject: [PATCH 2/6] fixup! Show recipient and amount when accelerating --- .../AccelerateTxModal.test.tsx.snap | 645 +++++++++--------- src/components/modals/AccelerateTxModal.tsx | 13 +- 2 files changed, 334 insertions(+), 324 deletions(-) diff --git a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap index 485cac2462c..3abd943e8cf 100644 --- a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap @@ -310,428 +310,431 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` > This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming. - - + - - Recipient Address - - - bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu - - - - + Recipient Address + + + bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu + + + -  - + +  + + - - - - Send Amount - - - 0.094 ($ 0.00) - + + Send Amount + + + 0.094 ($ 0.00) + + - - - - Old Fee - - - 0.000681 ($ 0.00) - + + Old Fee + + + 0.000681 ($ 0.00) + + - - - - New Fee - - - 0.001362 ($ 0.00) - + + New Fee + + + 0.001362 ($ 0.00) + + - + { {lstrings.transaction_details_accelerate_transaction_instructional} - + {spendTargets.map((target, index) => ( { body={newFee} /> )} - + {isLowerAmount ? ( ({ container: { maxHeight: theme.rem(20), marginVertical: theme.rem(0.5) + }, + // The rows scroll once they outgrow their share of the modal, so every + // recipient stays reachable above the slider: + rows: { + flexGrow: 0, + maxHeight: theme.rem(20), + marginVertical: theme.rem(0.5) } })) From 9109bbbb39e7ebc567677c88599afa69503e5987 Mon Sep 17 00:00:00 2001 From: peachbits Date: Fri, 13 Nov 2026 15:32:47 -0800 Subject: [PATCH 3/6] fixup! Show recipient and amount when accelerating --- .../modals/__snapshots__/AccelerateTxModal.test.tsx.snap | 5 +++++ src/components/modals/AccelerateTxModal.tsx | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap index 3abd943e8cf..6a88e982dad 100644 --- a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap @@ -311,6 +311,11 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming. Date: Fri, 13 Nov 2026 15:33:19 -0800 Subject: [PATCH 4/6] fixup! Show recipient and amount when accelerating --- src/__tests__/modals/AccelerateTxModal.test.tsx | 4 ++++ src/components/modals/AccelerateTxModal.tsx | 4 +++- src/locales/en_US.ts | 2 ++ src/locales/strings/enUS.json | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/__tests__/modals/AccelerateTxModal.test.tsx b/src/__tests__/modals/AccelerateTxModal.test.tsx index 2133d467c7c..0a9181c0147 100644 --- a/src/__tests__/modals/AccelerateTxModal.test.tsx +++ b/src/__tests__/modals/AccelerateTxModal.test.tsx @@ -134,6 +134,7 @@ describe('AccelerateTxModalComponent', () => { }) expect(rendered.getByText('bc1qfirstrecipient')).toBeTruthy() + expect(rendered.getByText(/review the recipient/)).toBeTruthy() expect(rendered.getByText('bc1qsecondrecipient')).toBeTruthy() expect(rendered.getByText('1 ($ 0.00)')).toBeTruthy() expect(rendered.getByText('0.5 ($ 0.00)')).toBeTruthy() @@ -171,6 +172,9 @@ describe('AccelerateTxModalComponent', () => { expect(rendered.queryByText('Recipient Address')).toBeNull() expect(rendered.queryByText('Send Amount')).toBeNull() + // ...and the text does not ask the user to review rows that are absent: + expect(rendered.queryByText(/review the recipient/)).toBeNull() + expect(rendered.getByText(/review new fee amount/)).toBeTruthy() rendered.unmount() }) }) diff --git a/src/components/modals/AccelerateTxModal.tsx b/src/components/modals/AccelerateTxModal.tsx index 9b17467c9dd..bc8e453a430 100644 --- a/src/components/modals/AccelerateTxModal.tsx +++ b/src/components/modals/AccelerateTxModal.tsx @@ -183,7 +183,9 @@ export class AccelerateTxModalComponent extends PureComponent { title={lstrings.transaction_details_accelerate_transaction_header} > - {lstrings.transaction_details_accelerate_transaction_instructional} + {spendTargets.length > 0 + ? lstrings.transaction_details_accelerate_transaction_instructional_recipient + : lstrings.transaction_details_accelerate_transaction_instructional} {spendTargets.map((target, index) => ( diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index a480449bd7f..0fb65e3124b 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1006,6 +1006,8 @@ const strings = { transaction_details_advance_details_minertip: 'Miner Tip Gwei', transaction_details_accelerate_transaction_header: 'Accelerate Transaction', transaction_details_accelerate_transaction_instructional: + 'This will resend your transaction with its fee doubled to help prioritize it for network confirmation. Please review new fee amount before confirming.', + transaction_details_accelerate_transaction_instructional_recipient: 'This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming.', transaction_details_accelerate_transaction_recipient_title: 'Recipient Address', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 6b4e5aa1242..f95552c5090 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -793,7 +793,8 @@ "transaction_details_advance_details_gaslimit": "Gas Limit", "transaction_details_advance_details_minertip": "Miner Tip Gwei", "transaction_details_accelerate_transaction_header": "Accelerate Transaction", - "transaction_details_accelerate_transaction_instructional": "This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming.", + "transaction_details_accelerate_transaction_instructional": "This will resend your transaction with its fee doubled to help prioritize it for network confirmation. Please review new fee amount before confirming.", + "transaction_details_accelerate_transaction_instructional_recipient": "This will resend this transaction with its fee doubled to help prioritize it for network confirmation. Please review the recipient, amount, and new fee before confirming.", "transaction_details_accelerate_transaction_recipient_title": "Recipient Address", "transaction_details_accelerate_transaction_amount_title": "Send Amount", "confirm_finish": "Confirm & Finish", From 68dab72af0119ac2c0211f62611f31053b59753b Mon Sep 17 00:00:00 2001 From: peachbits Date: Fri, 13 Nov 2026 15:34:48 -0800 Subject: [PATCH 5/6] fixup! Show recipient and amount when accelerating --- src/components/modals/AccelerateTxModal.tsx | 94 ++++++------ src/util/utils.ts | 150 +++++++++++++------- 2 files changed, 152 insertions(+), 92 deletions(-) diff --git a/src/components/modals/AccelerateTxModal.tsx b/src/components/modals/AccelerateTxModal.tsx index bc8e453a430..b21314f485c 100644 --- a/src/components/modals/AccelerateTxModal.tsx +++ b/src/components/modals/AccelerateTxModal.tsx @@ -2,7 +2,6 @@ import { abs, lt, sub } from 'biggystring' import type { EdgeCurrencyWallet, EdgeDenomination, - EdgeTokenId, EdgeTransaction } from 'edge-core-js' import React, { PureComponent } from 'react' @@ -17,7 +16,10 @@ import { selectDisplayDenom } from '../../selectors/DenominationSelectors' import { useSelector } from '../../types/reactRedux' -import { convertTransactionFeeToDisplayFee } from '../../util/utils' +import { + convertNativeAmountToDisplayAmount, + convertTransactionFeeToDisplayFee +} from '../../util/utils' import { WarningCard } from '../cards/WarningCard' import { EdgeRow } from '../rows/EdgeRow' import { @@ -106,10 +108,23 @@ export class AccelerateTxModalComponent extends PureComponent { } getTxFeeDisplay = (edgeTransaction: EdgeTransaction): string => { - return this.formatAmount( - edgeTransaction, - null, - this.props.feeDisplayDenomination + const { + exchangeRates, + feeDisplayDenomination, + wallet, + isoFiatCurrencyCode + } = this.props + + return formatAmount( + convertTransactionFeeToDisplayFee( + wallet.currencyInfo.pluginId, + null, + isoFiatCurrencyCode, + exchangeRates, + edgeTransaction, + feeDisplayDenomination, + getExchangeDenom(wallet.currencyConfig, null) + ) ) } @@ -119,42 +134,26 @@ export class AccelerateTxModalComponent extends PureComponent { * currency the fee is paid in). */ getSendAmountDisplay = (nativeAmount: string): string => { - const { acceleratedTx, amountDisplayDenomination } = this.props - return this.formatAmount( - { - ...acceleratedTx, - networkFee: nativeAmount, - parentNetworkFee: undefined - }, - acceleratedTx.tokenId, - amountDisplayDenomination - ) - } - - /** - * Formats the fee of a transaction as " ()" for the asset - * named by tokenId. The send amount reuses it by standing in for the fee. - */ - formatAmount = ( - edgeTransaction: EdgeTransaction, - tokenId: EdgeTokenId, - displayDenomination: EdgeDenomination - ): string => { - const { exchangeRates, wallet, isoFiatCurrencyCode } = this.props - - const transactionFee = convertTransactionFeeToDisplayFee( - wallet.currencyInfo.pluginId, - tokenId, - isoFiatCurrencyCode, + const { + acceleratedTx, + amountDisplayDenomination, exchangeRates, - edgeTransaction, - displayDenomination, - getExchangeDenom(wallet.currencyConfig, tokenId) - ) + wallet, + isoFiatCurrencyCode + } = this.props + const { tokenId } = acceleratedTx - return `${transactionFee.cryptoSymbol ?? ''} ${ - transactionFee.cryptoAmount - } (${transactionFee.fiatSymbol ?? ''} ${transactionFee.fiatAmount})` + return formatAmount( + convertNativeAmountToDisplayAmount( + wallet.currencyInfo.pluginId, + tokenId, + isoFiatCurrencyCode, + exchangeRates, + nativeAmount, + amountDisplayDenomination, + getExchangeDenom(wallet.currencyConfig, tokenId) + ) + ) } render(): React.ReactElement { @@ -311,6 +310,21 @@ export function AccelerateTxModal(props: OwnProps): React.ReactElement { ) } +/** + * Lays out an amount as " ()". The fee rows and the send amount + * rows share it, so they read the same way. + */ +function formatAmount(amount: { + cryptoSymbol?: string + cryptoAmount: string + fiatSymbol?: string + fiatAmount: string +}): string { + return `${amount.cryptoSymbol ?? ''} ${amount.cryptoAmount} (${ + amount.fiatSymbol ?? '' + } ${amount.fiatAmount})` +} + function getTxSendAmount(edgeTransaction: EdgeTransaction): string { // Transaction amounts are negative for send transactions const nativeAmount = abs(edgeTransaction.nativeAmount) diff --git a/src/util/utils.ts b/src/util/utils.ts index 2c38b3068eb..eacf97cf21e 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -502,6 +502,81 @@ export const feeStyle = { warning: 'warningText' } +/** + * Converts a native amount of an asset into its amount in a display + * denomination and its fiat value. + */ +export const convertNativeAmountToDisplayAmount = ( + pluginId: string, + tokenId: EdgeTokenId, + isoFiatCurrencyCode: string, + exchangeRates: GuiExchangeRates, + nativeAmount: string, + displayDenomination: EdgeDenomination, + exchangeDenomination: EdgeDenomination +): { + fiatSymbol: string + fiatAmount: string + cryptoSymbol?: string + cryptoAmount: string + /** The amount in the exchange denomination, for further rate lookups */ + exchangeAmount: string + currencyName?: string +} => { + const secondaryDisplayDenomination = getDenomFromIsoCode(isoFiatCurrencyCode) + const fiatSymbol = + secondaryDisplayDenomination?.symbol ?? + secondaryDisplayDenomination?.name ?? + '' + + if (!gt(nativeAmount, '0')) { + return { + fiatSymbol, + fiatAmount: '0', + cryptoSymbol: displayDenomination?.symbol, + cryptoAmount: '0', + exchangeAmount: '0', + currencyName: displayDenomination?.name + } + } + + const displayMultiplier = + displayDenomination != null ? displayDenomination.multiplier : '' + const exchangeMultiplier = + exchangeDenomination != null ? exchangeDenomination.multiplier : '' + const exchangeDenomAmount = + nativeAmount != null && nativeAmount !== '' + ? convertNativeToDisplay(exchangeMultiplier)(nativeAmount) + : '' + const exchangeToDisplayMultiplierRatio = div( + exchangeMultiplier, + displayMultiplier, + DECIMAL_PRECISION + ) + const cryptoAmount = mul( + exchangeDenomAmount, + exchangeToDisplayMultiplierRatio + ) + const exchangeAmount = + convertNativeToExchange(exchangeMultiplier)(nativeAmount) + const fiatAmount = convertCurrency( + exchangeRates, + pluginId, + tokenId, + isoFiatCurrencyCode, + exchangeAmount + ) + + return { + fiatSymbol, + fiatAmount: toFixed(fiatAmount, FIAT_PRECISION, FIAT_PRECISION), + cryptoSymbol: displayDenomination?.symbol, + cryptoAmount, + exchangeAmount, + currencyName: displayDenomination?.name + } +} + export const convertTransactionFeeToDisplayFee = ( pluginId: string, tokenId: EdgeTokenId, @@ -519,83 +594,54 @@ export const convertTransactionFeeToDisplayFee = ( nativeCryptoAmount: string currencyName?: string } => { - const secondaryDisplayDenomination = getDenomFromIsoCode(isoFiatCurrencyCode) - let feeNativeAmount if (transaction.parentNetworkFee != null) { feeNativeAmount = transaction.parentNetworkFee } else feeNativeAmount = transaction.networkFee + const amount = convertNativeAmountToDisplayAmount( + pluginId, + tokenId, + isoFiatCurrencyCode, + exchangeRates, + feeNativeAmount, + feeDisplayDenomination, + feeDefaultDenomination + ) + if (gt(feeNativeAmount, '0')) { - const cryptoFeeSymbol = feeDisplayDenomination?.symbol ?? '' - const displayMultiplier = - feeDisplayDenomination != null ? feeDisplayDenomination.multiplier : '' - const exchangeMultiplier = - feeDefaultDenomination != null ? feeDefaultDenomination.multiplier : '' - const cryptoFeeExchangeDenomAmount = - feeNativeAmount != null && feeNativeAmount !== '' - ? convertNativeToDisplay(exchangeMultiplier)(feeNativeAmount) - : '' - const exchangeToDisplayMultiplierRatio = div( - exchangeMultiplier, - displayMultiplier, - DECIMAL_PRECISION - ) - const cryptoAmount = mul( - cryptoFeeExchangeDenomAmount, - exchangeToDisplayMultiplierRatio - ) - const cryptoFeeExchangeAmount = - convertNativeToExchange(exchangeMultiplier)(feeNativeAmount) - const fiatFeeAmount = convertCurrency( - exchangeRates, - pluginId, - tokenId, - isoFiatCurrencyCode, - cryptoFeeExchangeAmount - ) const feeAmountInUSD = convertCurrency( exchangeRates, pluginId, tokenId, 'iso:USD', - cryptoFeeExchangeAmount + amount.exchangeAmount ) - const fiatAmount = { - amount: toFixed(fiatFeeAmount, FIAT_PRECISION, FIAT_PRECISION), - style: + + return { + fiatSymbol: amount.fiatSymbol, + fiatAmount: amount.fiatAmount, + fiatStyle: parseFloat(feeAmountInUSD) > FEE_ALERT_THRESHOLD ? feeStyle.danger : parseFloat(feeAmountInUSD) > FEE_COLOR_THRESHOLD ? feeStyle.warning - : undefined - } - - return { - fiatSymbol: - secondaryDisplayDenomination?.symbol ?? - secondaryDisplayDenomination?.name ?? - '', - fiatAmount: fiatAmount.amount, - fiatStyle: fiatAmount.style, - cryptoSymbol: cryptoFeeSymbol, - cryptoAmount, + : undefined, + cryptoSymbol: amount.cryptoSymbol ?? '', + cryptoAmount: amount.cryptoAmount, nativeCryptoAmount: feeNativeAmount, - currencyName: feeDisplayDenomination?.name + currencyName: amount.currencyName } } return { - fiatSymbol: - secondaryDisplayDenomination?.symbol ?? - secondaryDisplayDenomination?.name ?? - '', + fiatSymbol: amount.fiatSymbol, fiatAmount: '0', fiatStyle: undefined, - cryptoSymbol: feeDisplayDenomination?.symbol, + cryptoSymbol: amount.cryptoSymbol, cryptoAmount: '0', nativeCryptoAmount: '0', - currencyName: feeDisplayDenomination?.name + currencyName: amount.currencyName } } From aeb839e1658c3cfc4d7a39ba535c3bdc1c24a49d Mon Sep 17 00:00:00 2001 From: peachbits Date: Fri, 13 Nov 2026 15:37:12 -0800 Subject: [PATCH 6/6] fixup! Show recipient and amount when accelerating --- .../modals/AccelerateTxModal.test.tsx | 39 ++++++++++++++-- .../AccelerateTxModal.test.tsx.snap | 6 +-- src/components/modals/AccelerateTxModal.tsx | 46 ++++++++++++++----- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/__tests__/modals/AccelerateTxModal.test.tsx b/src/__tests__/modals/AccelerateTxModal.test.tsx index 0a9181c0147..c56b3f7e0e3 100644 --- a/src/__tests__/modals/AccelerateTxModal.test.tsx +++ b/src/__tests__/modals/AccelerateTxModal.test.tsx @@ -9,6 +9,7 @@ import type { } from 'edge-core-js' import * as React from 'react' +import type { GuiExchangeRates } from '../../actions/ExchangeRateActions' import { AccelerateTxModalComponent } from '../../components/modals/AccelerateTxModal' import { getTheme } from '../../components/services/ThemeContext' import { fakeAirshipBridge } from '../../util/fake/fakeAirshipBridge' @@ -75,6 +76,7 @@ function renderModal(opts: { acceleratedTx: EdgeTransaction amountDisplayDenomination: EdgeDenomination feeDisplayDenomination: EdgeDenomination + exchangeRates?: GuiExchangeRates }): ReturnType { const fakeWallet: any = { fiatCurrencyCode: 'iso:USD', @@ -90,7 +92,7 @@ function renderModal(opts: { replacedTx={opts.replacedTx} acceleratedTx={opts.acceleratedTx} wallet={fakeWallet} - exchangeRates={{ crypto: {}, fiat: {} }} + exchangeRates={opts.exchangeRates ?? { crypto: {}, fiat: {} }} amountDisplayDenomination={opts.amountDisplayDenomination} feeDisplayDenomination={opts.feeDisplayDenomination} theme={getTheme()} @@ -136,11 +138,40 @@ describe('AccelerateTxModalComponent', () => { expect(rendered.getByText('bc1qfirstrecipient')).toBeTruthy() expect(rendered.getByText(/review the recipient/)).toBeTruthy() expect(rendered.getByText('bc1qsecondrecipient')).toBeTruthy() - expect(rendered.getByText('1 ($ 0.00)')).toBeTruthy() - expect(rendered.getByText('0.5 ($ 0.00)')).toBeTruthy() + expect(rendered.getByText('1')).toBeTruthy() + expect(rendered.getByText('0.5')).toBeTruthy() rendered.unmount() }) + it('shows fiat values only when there is an exchange rate', () => { + const txs = { + currencyInfo: btcCurrencyInfo, + replacedTx: makeTx('BTC', null, '-100681', '681'), + acceleratedTx: makeTx('BTC', null, '-101362', '1362', [ + makeTarget('BTC', '100000', 'bc1qfirstrecipient') + ]), + amountDisplayDenomination: mbtc, + feeDisplayDenomination: mbtc + } + + // Without a rate, neither the amount nor the fees claim a fiat value: + const withoutRate = renderModal(txs) + expect(withoutRate.getByText('1')).toBeTruthy() + expect(withoutRate.queryByText(/\$/)).toBeNull() + withoutRate.unmount() + + const btcUsdRate = { current: 50000, yesterday: 50000, expiration: 0 } + const withRate = renderModal({ + ...txs, + exchangeRates: { + crypto: { bitcoin: { '': { 'iso:USD': btcUsdRate } } }, + fiat: {} + } as unknown as GuiExchangeRates + }) + expect(withRate.getByText('1 ($ 50.00)')).toBeTruthy() + withRate.unmount() + }) + it('shows a token amount in the token, not the parent currency', () => { const eth: EdgeDenomination = { multiplier: '1000000000000000000', @@ -157,7 +188,7 @@ describe('AccelerateTxModalComponent', () => { feeDisplayDenomination: eth }) - expect(rendered.getByText('25 ($ 0.00)')).toBeTruthy() + expect(rendered.getByText('25')).toBeTruthy() rendered.unmount() }) diff --git a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap index 6a88e982dad..002afb3d75e 100644 --- a/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/AccelerateTxModal.test.tsx.snap @@ -564,7 +564,7 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` ] } > - 0.094 ($ 0.00) + 0.094 @@ -649,7 +649,7 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` ] } > - 0.000681 ($ 0.00) + 0.000681 @@ -734,7 +734,7 @@ exports[`AccelerateTxModalComponent should render with loading props 1`] = ` ] } > - 0.001362 ($ 0.00) + 0.001362 diff --git a/src/components/modals/AccelerateTxModal.tsx b/src/components/modals/AccelerateTxModal.tsx index b21314f485c..5e29e82099c 100644 --- a/src/components/modals/AccelerateTxModal.tsx +++ b/src/components/modals/AccelerateTxModal.tsx @@ -2,6 +2,7 @@ import { abs, lt, sub } from 'biggystring' import type { EdgeCurrencyWallet, EdgeDenomination, + EdgeTokenId, EdgeTransaction } from 'edge-core-js' import React, { PureComponent } from 'react' @@ -15,6 +16,7 @@ import { getExchangeDenom, selectDisplayDenom } from '../../selectors/DenominationSelectors' +import { getExchangeRate } from '../../selectors/WalletSelectors' import { useSelector } from '../../types/reactRedux' import { convertNativeAmountToDisplayAmount, @@ -124,7 +126,8 @@ export class AccelerateTxModalComponent extends PureComponent { edgeTransaction, feeDisplayDenomination, getExchangeDenom(wallet.currencyConfig, null) - ) + ), + this.hasExchangeRate(null) ) } @@ -152,7 +155,21 @@ export class AccelerateTxModalComponent extends PureComponent { nativeAmount, amountDisplayDenomination, getExchangeDenom(wallet.currencyConfig, tokenId) - ) + ), + this.hasExchangeRate(tokenId) + ) + } + + /** Whether a fiat value can be shown for one of the wallet's assets */ + hasExchangeRate = (tokenId: EdgeTokenId): boolean => { + const { exchangeRates, wallet, isoFiatCurrencyCode } = this.props + return ( + getExchangeRate( + exchangeRates, + wallet.currencyInfo.pluginId, + tokenId, + isoFiatCurrencyCode + ) !== 0 ) } @@ -312,17 +329,22 @@ export function AccelerateTxModal(props: OwnProps): React.ReactElement { /** * Lays out an amount as " ()". The fee rows and the send amount - * rows share it, so they read the same way. + * rows share it, so they read the same way. Without an exchange rate the fiat + * value would read as zero, which looks like a real amount on a confirmation, + * so it is left out. */ -function formatAmount(amount: { - cryptoSymbol?: string - cryptoAmount: string - fiatSymbol?: string - fiatAmount: string -}): string { - return `${amount.cryptoSymbol ?? ''} ${amount.cryptoAmount} (${ - amount.fiatSymbol ?? '' - } ${amount.fiatAmount})` +function formatAmount( + amount: { + cryptoSymbol?: string + cryptoAmount: string + fiatSymbol?: string + fiatAmount: string + }, + hasExchangeRate: boolean +): string { + const crypto = `${amount.cryptoSymbol ?? ''} ${amount.cryptoAmount}` + if (!hasExchangeRate) return crypto + return `${crypto} (${amount.fiatSymbol ?? ''} ${amount.fiatAmount})` } function getTxSendAmount(edgeTransaction: EdgeTransaction): string {