From 2717c6c6b1f225d6ba0919d5b709bb0ef6bccec6 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Thu, 6 Feb 2025 15:25:26 +0100 Subject: [PATCH] fix(wallet): #1733 avoid duplicate notification for received onchain tx --- .../Settings/AddressTypePreference/index.tsx | 2 +- src/screens/Settings/DevSettings/index.tsx | 2 +- src/screens/Settings/ElectrumConfig/index.tsx | 5 ++-- src/screens/Settings/GapLimit/index.tsx | 6 +--- src/screens/Wallets/BoostPrompt.tsx | 12 ++------ src/store/mmkv-storage.ts | 30 +++++++++++++++++++ src/store/utils/blocktank.ts | 12 ++------ src/store/utils/ui.ts | 11 ------- src/utils/startup/index.ts | 1 - src/utils/wallet/checks.ts | 8 +---- src/utils/wallet/electrum.ts | 4 +-- src/utils/wallet/index.ts | 24 +++------------ src/utils/wallet/transactions.ts | 13 +------- 13 files changed, 47 insertions(+), 83 deletions(-) diff --git a/src/screens/Settings/AddressTypePreference/index.tsx b/src/screens/Settings/AddressTypePreference/index.tsx index c3e637c57..f0fe4d653 100644 --- a/src/screens/Settings/AddressTypePreference/index.tsx +++ b/src/screens/Settings/AddressTypePreference/index.tsx @@ -51,7 +51,7 @@ const AddressTypeSettings = ({ onPress: async (): Promise => { navigation.goBack(); await updateSelectedAddressType({ addressType: addressType.type }); - await refreshWallet({ lightning: false, onchain: true }); + await refreshWallet({ lightning: false }); }, testID: addressType.type, })), diff --git a/src/screens/Settings/DevSettings/index.tsx b/src/screens/Settings/DevSettings/index.tsx index 76c360846..d9de34316 100644 --- a/src/screens/Settings/DevSettings/index.tsx +++ b/src/screens/Settings/DevSettings/index.tsx @@ -279,7 +279,7 @@ const DevSettings = ({ const fakeTx = getFakeTransaction(id); fakeTx[id].height = 0; injectFakeTransaction(fakeTx); - refreshWallet({ selectedWallet, selectedNetwork }).then(); + refreshWallet().then(); }, }, { diff --git a/src/screens/Settings/ElectrumConfig/index.tsx b/src/screens/Settings/ElectrumConfig/index.tsx index 60f8bb6f7..d3ae31fe9 100644 --- a/src/screens/Settings/ElectrumConfig/index.tsx +++ b/src/screens/Settings/ElectrumConfig/index.tsx @@ -308,9 +308,9 @@ const ElectrumConfig = ({ autoComplete="off" keyboardType="default" autoCorrect={false} - onChangeText={setHost} returnKeyType="done" testID="HostInput" + onChangeText={setHost} /> @@ -326,8 +326,9 @@ const ElectrumConfig = ({ autoComplete="off" keyboardType="number-pad" autoCorrect={false} - onChangeText={setPort} + returnKeyType="done" testID="PortInput" + onChangeText={setPort} /> { }); if (res.isOk()) { dispatch(updateWallet({ gapLimitOptions: res.value })); - await refreshWallet({ - lightning: false, - onchain: true, - scanAllAddresses: true, - }); + await refreshWallet({ lightning: false, scanAllAddresses: true }); showToast({ type: 'success', title: t('gap.gap_limit_update_title'), diff --git a/src/screens/Wallets/BoostPrompt.tsx b/src/screens/Wallets/BoostPrompt.tsx index 775f45b6c..463edf538 100644 --- a/src/screens/Wallets/BoostPrompt.tsx +++ b/src/screens/Wallets/BoostPrompt.tsx @@ -20,11 +20,7 @@ import { useAppDispatch, useAppSelector } from '../../hooks/redux'; import { rootNavigation } from '../../navigation/root/RootNavigator'; import { resetSendTransaction } from '../../store/actions/wallet'; import { viewControllerSelector } from '../../store/reselect/ui'; -import { - selectedNetworkSelector, - selectedWalletSelector, - transactionSelector, -} from '../../store/reselect/wallet'; +import { transactionSelector } from '../../store/reselect/wallet'; import { closeSheet } from '../../store/slices/ui'; import { TOnchainActivityItem } from '../../store/types/activity'; import { EUnit } from '../../store/types/wallet'; @@ -48,8 +44,6 @@ const BoostForm = ({ const { t } = useTranslation('wallet'); const dispatch = useAppDispatch(); const transaction = useAppSelector(transactionSelector); - const selectedNetwork = useAppSelector(selectedNetworkSelector); - const selectedWallet = useAppSelector(selectedWalletSelector); const [preparing, setPreparing] = useState(true); const [loading, setLoading] = useState(false); @@ -69,8 +63,6 @@ const BoostForm = ({ useEffect(() => { (async (): Promise => { const res = await setupBoost({ - selectedWallet, - selectedNetwork, txid: activityItem.txId, }); setPreparing(false); @@ -84,7 +76,7 @@ const BoostForm = ({ return (): void => { resetSendTransaction(); }; - }, [activityItem.txId, selectedNetwork, selectedWallet, dispatch]); + }, [activityItem.txId, dispatch]); const onSwitchView = (): void => { if (showCustom) { diff --git a/src/store/mmkv-storage.ts b/src/store/mmkv-storage.ts index a34ea4464..c0ab17cdc 100644 --- a/src/store/mmkv-storage.ts +++ b/src/store/mmkv-storage.ts @@ -19,6 +19,36 @@ export const reduxStorage: Storage = { }, }; +// Used to prevent duplicate notifications for the same txId that seems to occur when: +// - when Bitkit is brought from background to foreground +// - connection to electrum server is lost and then re-established +export const receivedTxIds = { + STORAGE_KEY: 'receivedTxIds', + + // Get stored txIds + get: (): string[] => { + return JSON.parse(storage.getString(receivedTxIds.STORAGE_KEY) || '[]'); + }, + + // Save txIds to storage + save: (txIds: string[]): void => { + storage.set(receivedTxIds.STORAGE_KEY, JSON.stringify(txIds)); + }, + + // Add a new txId + add: (txId: string): void => { + const txIds = receivedTxIds.get(); + txIds.push(txId); + receivedTxIds.save(txIds); + }, + + // Check if txId exists + has: (txId: string): boolean => { + const txIds = receivedTxIds.get(); + return txIds.includes(txId); + }, +}; + export class WebRelayCache { location: string; diff --git a/src/store/utils/blocktank.ts b/src/store/utils/blocktank.ts index 5e0c10048..7546512e1 100644 --- a/src/store/utils/blocktank.ts +++ b/src/store/utils/blocktank.ts @@ -244,14 +244,10 @@ export const startChannelPurchase = async ({ /** * Creates, broadcasts and confirms a given Blocktank channel purchase by orderId. * @param {string} orderId - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] * @returns {Promise>} */ export const confirmChannelPurchase = async ({ order, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), }: { order: IBtOrder; selectedNetwork?: EAvailableNetwork; @@ -293,12 +289,8 @@ export const confirmChannelPurchase = async ({ watchOrder(order.id).then(); dispatch(setLightningSetupStep(0)); - refreshWallet({ - onchain: true, - lightning: false, // No need to refresh lightning wallet at this time. - selectedWallet, - selectedNetwork, - }).then(); + // No need to refresh lightning wallet at this time. + refreshWallet({ lightning: false }).then(); if (!__E2E__) { await scheduleNotifications(); diff --git a/src/store/utils/ui.ts b/src/store/utils/ui.ts index ab1610e94..ec55f2767 100644 --- a/src/store/utils/ui.ts +++ b/src/store/utils/ui.ts @@ -48,20 +48,9 @@ export const showNewOnchainTxPrompt = ({ value, }, }); - dispatch(closeSheet('receiveNavigation')); }; -export const showNewTxPrompt = (txId: string): void => { - const activityItem = getActivityStore().items.find(({ id }) => id === txId); - - if (activityItem) { - vibrate({ type: 'default' }); - showBottomSheet('newTxPrompt', { activityItem }); - dispatch(closeSheet('receiveNavigation')); - } -}; - export const checkForAppUpdate = async (): Promise => { const currentBuild = Number(getBuildNumber()); const response = await fetch(releaseUrl); diff --git a/src/utils/startup/index.ts b/src/utils/startup/index.ts index f0ae14f2c..632401742 100644 --- a/src/utils/startup/index.ts +++ b/src/utils/startup/index.ts @@ -165,7 +165,6 @@ export const startWalletServices = async ({ onchain: restore, lightning, scanAllAddresses: restore, - showNotification: !restore, }); await runChecks({ selectedWallet, selectedNetwork }); } diff --git a/src/utils/wallet/checks.ts b/src/utils/wallet/checks.ts index 060ae5249..20730cf8c 100644 --- a/src/utils/wallet/checks.ts +++ b/src/utils/wallet/checks.ts @@ -145,13 +145,7 @@ export const runStorageCheck = async ({ ); await clearUtxos(); - - await refreshWallet({ - onchain: true, - lightning: true, - scanAllAddresses: true, - showNotification: false, - }); + await refreshWallet({ scanAllAddresses: true }); return ok('Replaced Impacted Addresses'); }; diff --git a/src/utils/wallet/electrum.ts b/src/utils/wallet/electrum.ts index 3c54cb8dd..488d10e38 100644 --- a/src/utils/wallet/electrum.ts +++ b/src/utils/wallet/electrum.ts @@ -211,11 +211,9 @@ export const getAddressHistory = async ({ */ export const connectToElectrum = async ({ customPeers, - showNotification = true, selectedNetwork = getSelectedNetwork(), }: { customPeers?: TServer[]; - showNotification?: boolean; selectedNetwork?: EAvailableNetwork; } = {}): Promise> => { const electrum = await getOnChainWalletElectrumAsync(); @@ -240,7 +238,7 @@ export const connectToElectrum = async ({ } // Check for any new transactions that we might have missed while disconnected. - refreshWallet({ showNotification }).then(); + refreshWallet().then(); return ok(connectRes.value); }; diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index a072c44b7..3eedf1ec3 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -39,7 +39,6 @@ import { BIP32Factory } from 'bip32'; import * as bip39 from 'bip39'; import { getAddressInfo } from 'bitcoin-address-validation'; import * as bitcoin from 'bitcoinjs-lib'; -import { InteractionManager } from 'react-native'; import { generateNewReceiveAddress, @@ -54,6 +53,7 @@ import { getStore, getWalletStore, } from '../../store/helpers'; +import { receivedTxIds } from '../../store/mmkv-storage'; import { getDefaultGapLimitOptions, getDefaultWalletShape, @@ -75,7 +75,7 @@ import { import { updateActivityList } from '../../store/utils/activity'; import { refreshOrdersList } from '../../store/utils/blocktank'; import { moveMetaIncTxTags } from '../../store/utils/metadata'; -import { showNewOnchainTxPrompt, showNewTxPrompt } from '../../store/utils/ui'; +import { showNewOnchainTxPrompt } from '../../store/utils/ui'; import BitcoinActions from '../bitcoin-actions'; import { btcToSats } from '../conversion'; import { promiseTimeout } from '../helpers'; @@ -147,25 +147,16 @@ export const refreshWallet = async ({ onchain = true, lightning = true, scanAllAddresses = false, // If set to false, on-chain scanning will adhere to the gap limit (20). - showNotification = true, // Whether to show newTxPrompt on new incoming transactions. selectedWallet = getSelectedWallet(), selectedNetwork = getSelectedNetwork(), }: { onchain?: boolean; lightning?: boolean; scanAllAddresses?: boolean; - showNotification?: boolean; selectedWallet?: TWalletName; selectedNetwork?: EAvailableNetwork; } = {}): Promise> => { try { - // wait for interactions/animations to be completed - await new Promise((resolve) => { - InteractionManager.runAfterInteractions(() => resolve(null)); - }); - - let notificationTxid: string | undefined; - if (onchain) { await refreshBeignet(scanAllAddresses); } @@ -180,10 +171,6 @@ export const refreshWallet = async ({ moveMetaIncTxTags(); } - if (showNotification && notificationTxid) { - showNewTxPrompt(notificationTxid); - } - return ok(''); } catch (e) { return err(e); @@ -1013,9 +1000,6 @@ const onElectrumConnectionChange = (isConnected: boolean): void => { } }; -// Used to prevent duplicate notifications for the same txid that seems to occur when Bitkit is brought from background to foreground. -const receivedTxids: string[] = []; - const onMessage: TOnMessage = async (key, data): Promise => { switch (key) { case 'transactionReceived': { @@ -1026,7 +1010,7 @@ const onMessage: TOnMessage = async (key, data): Promise => { !wallet?.isSwitchingNetworks ) { const { transaction } = txMsg; - const isDuplicate = receivedTxids.includes(transaction.txid); + const isDuplicate = receivedTxIds.has(transaction.txid); const transfer = await getTransferForTx(transaction); if (!transfer && !isDuplicate) { @@ -1034,7 +1018,7 @@ const onMessage: TOnMessage = async (key, data): Promise => { id: transaction.txid, value: btcToSats(transaction.value), }); - receivedTxids.push(transaction.txid); + receivedTxIds.add(transaction.txid); } } refreshWallet({ lightning: false }).then(); diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index 79a464add..3fc38e39f 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -745,26 +745,15 @@ const runCoinSelect = async ({ /** * - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] * @param {string} txid */ export const setupBoost = async ({ txid, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), }: { txid: string; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; }): Promise>> => { // Ensure all utxos are up-to-date if attempting to boost immediately after a transaction. - const refreshResponse = await refreshWallet({ - onchain: true, - lightning: false, - selectedWallet, - selectedNetwork, - }); + const refreshResponse = await refreshWallet({ lightning: false }); if (refreshResponse.isErr()) { return err(refreshResponse.error.message); }