From 5d43a418d4feffc2908e451a7124b689e0aab1b7 Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Mon, 19 Feb 2024 20:36:16 -0500 Subject: [PATCH 1/2] feat(wallet): Add Gap Limit Options Upgrades Beignet to 0.0.23. Implements gapLimitOptions from upgrade. Adds two dev-only views. Updates broadcastTransaction, getReceiveAddress & getBalance methods. --- package.json | 2 +- src/navigation/settings/SettingsNavigator.tsx | 3 + .../Settings/AddressTypePreference/index.tsx | 80 +++++++- src/screens/Settings/Advanced/index.tsx | 7 + src/screens/Settings/GapLimit/index.tsx | 171 ++++++++++++++++++ src/store/index.ts | 2 +- src/store/migrations/index.ts | 10 + src/store/reselect/wallet.ts | 16 ++ src/store/shapes/wallet.ts | 14 +- src/store/types/wallet.ts | 2 + src/utils/i18n/locales/en/settings.json | 10 + src/utils/startup/index.ts | 7 +- src/utils/wallet/electrum.ts | 15 -- src/utils/wallet/index.ts | 44 ++++- src/utils/wallet/transactions.ts | 25 +-- yarn.lock | 8 +- 16 files changed, 357 insertions(+), 59 deletions(-) create mode 100644 src/screens/Settings/GapLimit/index.tsx diff --git a/package.json b/package.json index d43293f47..5ac3d2cf4 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@synonymdev/web-relay": "1.0.7", "backpack-client": "github:synonymdev/bitkit-backup-client#f08fdb28529d8a3f8bfecc789443c43b966a7581", "bech32": "2.0.0", - "beignet": "0.0.22", + "beignet": "0.0.23", "bip21": "2.0.3", "bip32": "4.0.0", "bip39": "3.1.0", diff --git a/src/navigation/settings/SettingsNavigator.tsx b/src/navigation/settings/SettingsNavigator.tsx index 4180886db..3cee0b280 100644 --- a/src/navigation/settings/SettingsNavigator.tsx +++ b/src/navigation/settings/SettingsNavigator.tsx @@ -11,6 +11,7 @@ import { TChannel } from '@synonymdev/react-native-ldk'; import MainSettings from '../../screens/Settings'; import CurrenciesSettings from '../../screens/Settings/Currencies'; import ElectrumConfig from '../../screens/Settings/ElectrumConfig'; +import GapLimit from '../../screens/Settings/GapLimit'; import RGSServer from '../../screens/Settings/RGSServer'; import CoinSelectPreference from '../../screens/Settings/CoinSelectPreference'; import PaymentPreference from '../../screens/Settings/PaymentPreference'; @@ -84,6 +85,7 @@ export type SettingsStackParamList = { TransactionSpeedSettings: undefined; CustomFee: undefined; ElectrumConfig: undefined; + GapLimit: undefined; RGSServer: undefined; CoinSelectPreference: undefined; PaymentPreference: undefined; @@ -146,6 +148,7 @@ const SettingsNavigator = (): ReactElement => { /> + { if (isDeveloperMode) { return Object.values(addressTypes); @@ -27,9 +37,10 @@ const AddressTypeSettings = ({ (addressType) => addressType.type !== EAddressType.p2tr, ); }, [isDeveloperMode]); + const addressTypesToMonitor = useAppSelector(addressTypesToMonitorSelector); - const listData: IListData[] = useMemo( - () => [ + const listData = useMemo((): IListData[] => { + const data: IListData[] = [ { title: t('adv.address_type'), data: Object.values(availableAddressTypes).map((addressType) => ({ @@ -46,9 +57,62 @@ const AddressTypeSettings = ({ testID: addressType.type, })), }, - ], - [t, availableAddressTypes, selectedAddressType, navigation], - ); + ]; + + if (isDeveloperMode) { + const monitoredTypes: IListData = { + title: t('adv.monitored_address_types'), + data: Object.values(availableAddressTypes).map((addressType) => ({ + type: EItemType.button, + title: `${addressType.name} ${addressType.example}`, + subtitle: addressType.description, + value: addressTypesToMonitor.includes(addressType.type), + hide: !isDeveloperMode, + useCheckmark: true, + onPress: async (): Promise => { + const needsToBeAdded = !addressTypesToMonitor.includes( + addressType.type, + ); + let newAddressTypesToMonitor: EAddressType[] = []; + if (needsToBeAdded) { + newAddressTypesToMonitor = [ + ...addressTypesToMonitor, + addressType.type, + ]; + } else { + newAddressTypesToMonitor = addressTypesToMonitor.filter( + (type) => type !== addressType.type, + ); + } + updateWallet({ + addressTypesToMonitor: newAddressTypesToMonitor, + }); + if (!hasShownMonitorNotification) { + showToast({ + type: 'success', + title: t('adv.monitored_address_types_update_title'), + description: t( + 'adv.monitored_address_types_update_description', + ), + }); + setHasShownMonitorNotification(true); + } + }, + testID: `Monitor${addressType.type}`, + })), + }; + data.push(monitoredTypes); + } + return data; + }, [ + t, + availableAddressTypes, + isDeveloperMode, + selectedAddressType, + navigation, + addressTypesToMonitor, + hasShownMonitorNotification, + ]); return ( navigation.navigate('PaymentPreference'), }, + { + title: t('adv.gap_limit'), + type: EItemType.button, + onPress: (): void => navigation.navigate('GapLimit'), + testID: 'GapLimit', + hide: !enableDevOptions, + }, ]; const networks: ItemData[] = [ diff --git a/src/screens/Settings/GapLimit/index.tsx b/src/screens/Settings/GapLimit/index.tsx new file mode 100644 index 000000000..8f79574aa --- /dev/null +++ b/src/screens/Settings/GapLimit/index.tsx @@ -0,0 +1,171 @@ +import React, { memo, ReactElement, useMemo, useState } from 'react'; +import { StyleSheet } from 'react-native'; +import { useTranslation } from 'react-i18next'; + +import { View, TextInput, ScrollView } from '../../../styles/components'; +import { Text01S, Caption13Up } from '../../../styles/text'; +import { useAppSelector } from '../../../hooks/redux'; +import { gapLimitOptionsSelector } from '../../../store/reselect/wallet'; +import NavigationHeader from '../../../components/NavigationHeader'; +import SafeAreaInset from '../../../components/SafeAreaInset'; +import Button from '../../../components/Button'; +import type { SettingsScreenProps } from '../../../navigation/types'; +import { getOnChainWallet, refreshWallet } from '../../../utils/wallet'; +import { updateWallet } from '../../../store/actions/wallet'; +import { showToast } from '../../../utils/notifications'; + +const GapLimit = ({}: SettingsScreenProps<'GapLimit'>): ReactElement => { + const { t } = useTranslation('settings'); + const gapLimitOptions = useAppSelector(gapLimitOptionsSelector); + const [loading, setLoading] = useState(false); + const [lookBehind, setLookBehind] = useState( + String(gapLimitOptions.lookBehind), + ); + const [lookAhead, setLookAhead] = useState( + String(gapLimitOptions.lookAhead), + ); + + const hasEdited = useMemo(() => { + return ( + Number(lookBehind) !== gapLimitOptions.lookBehind || + Number(lookAhead) !== gapLimitOptions.lookAhead + ); + }, [ + gapLimitOptions.lookAhead, + gapLimitOptions.lookBehind, + lookAhead, + lookBehind, + ]); + + const areValid = useMemo(() => { + return Number(lookBehind) > 0 && Number(lookAhead) > 0; + }, [lookAhead, lookBehind]); + + const clearChanges = (): void => { + setLookBehind(String(gapLimitOptions.lookBehind)); + setLookAhead(String(gapLimitOptions.lookAhead)); + }; + + const saveGapLimit = async (): Promise => { + setLoading(true); + const wallet = getOnChainWallet(); + const res = wallet.updateGapLimit({ + lookAhead: Number(lookAhead), + lookBehind: Number(lookBehind), + }); + if (res.isOk()) { + updateWallet({ + gapLimitOptions: res.value, + }); + await refreshWallet({ + lightning: false, + onchain: true, + scanAllAddresses: true, + }); + showToast({ + type: 'success', + title: t('gap.gap_limit_update_title'), + description: t('gap.gap_limit_update_description'), + }); + } + setLoading(false); + }; + + return ( + + + + + Look Behind + { + setLookBehind(txt); + }} + returnKeyType="done" + testID="LookBehind" + /> + + + {'Look Ahead'} + + { + setLookAhead(txt); + }} + testID="LookAhead" + /> + + +