Skip to content

Commit

Permalink
feat(wallet): Add Gap Limit Options
Browse files Browse the repository at this point in the history
Upgrades Beignet to 0.0.23.
Implements gapLimitOptions from upgrade.
Adds two dev-only views.
Updates broadcastTransaction, getReceiveAddress & getBalance methods.
  • Loading branch information
coreyphillips committed Feb 20, 2024
1 parent 26e3e3e commit 5d43a41
Show file tree
Hide file tree
Showing 16 changed files with 357 additions and 59 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/navigation/settings/SettingsNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -84,6 +85,7 @@ export type SettingsStackParamList = {
TransactionSpeedSettings: undefined;
CustomFee: undefined;
ElectrumConfig: undefined;
GapLimit: undefined;
RGSServer: undefined;
CoinSelectPreference: undefined;
PaymentPreference: undefined;
Expand Down Expand Up @@ -146,6 +148,7 @@ const SettingsNavigator = (): ReactElement => {
/>
<Stack.Screen name="CustomFee" component={CustomFee} />
<Stack.Screen name="ElectrumConfig" component={ElectrumConfig} />
<Stack.Screen name="GapLimit" component={GapLimit} />
<Stack.Screen name="RGSServer" component={RGSServer} />
<Stack.Screen
name="CoinSelectPreference"
Expand Down
80 changes: 72 additions & 8 deletions src/screens/Settings/AddressTypePreference/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import React, { memo, ReactElement, useMemo } from 'react';
import React, { memo, ReactElement, useMemo, useState } from 'react';
import { useAppSelector } from '../../../hooks/redux';
import { useTranslation } from 'react-i18next';

import { EItemType, IListData } from '../../../components/List';
import SettingsView from '../SettingsView';
import { refreshWallet } from '../../../utils/wallet';
import { updateSelectedAddressType } from '../../../store/actions/wallet';
import { addressTypeSelector } from '../../../store/reselect/wallet';
import {
updateSelectedAddressType,
updateWallet,
} from '../../../store/actions/wallet';
import {
addressTypeSelector,
addressTypesToMonitorSelector,
} from '../../../store/reselect/wallet';
import { addressTypes } from '../../../store/shapes/wallet';
import type { SettingsScreenProps } from '../../../navigation/types';
import { enableDevOptionsSelector } from '../../../store/reselect/settings';
import { EAddressType } from 'beignet';
import { showToast } from '../../../utils/notifications';

const AddressTypeSettings = ({
navigation,
Expand All @@ -19,6 +26,9 @@ const AddressTypeSettings = ({
const selectedAddressType = useAppSelector(addressTypeSelector);
const isDeveloperMode = useAppSelector(enableDevOptionsSelector);

const [hasShownMonitorNotification, setHasShownMonitorNotification] =
useState(false);

const availableAddressTypes = useMemo(() => {
if (isDeveloperMode) {
return Object.values(addressTypes);
Expand All @@ -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) => ({
Expand All @@ -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<void> => {
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 (
<SettingsView
Expand Down
7 changes: 7 additions & 0 deletions src/screens/Settings/Advanced/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const AdvancedSettings = ({
type: EItemType.button,
onPress: (): void => navigation.navigate('PaymentPreference'),
},
{
title: t('adv.gap_limit'),
type: EItemType.button,
onPress: (): void => navigation.navigate('GapLimit'),
testID: 'GapLimit',
hide: !enableDevOptions,
},
];

const networks: ItemData[] = [
Expand Down
171 changes: 171 additions & 0 deletions src/screens/Settings/GapLimit/index.tsx
Original file line number Diff line number Diff line change
@@ -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>(
String(gapLimitOptions.lookBehind),
);
const [lookAhead, setLookAhead] = useState<string>(
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<void> => {
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 (
<View style={styles.container}>
<SafeAreaInset type="top" />
<NavigationHeader title={t('adv.gap_limit')} />
<ScrollView contentContainerStyle={styles.content} bounces={false}>
<Text01S color="gray1">Look Behind</Text01S>
<TextInput
style={styles.textInput}
value={lookBehind}
placeholder="20"
textAlignVertical="center"
underlineColorAndroid="transparent"
autoCapitalize="none"
autoComplete="off"
keyboardType="number-pad"
autoCorrect={false}
onChangeText={(txt): void => {
setLookBehind(txt);
}}
returnKeyType="done"
testID="LookBehind"
/>

<Caption13Up color="gray1" style={styles.label}>
{'Look Ahead'}
</Caption13Up>
<TextInput
style={styles.textInput}
value={lookAhead}
placeholder="20"
textAlignVertical="center"
underlineColorAndroid="transparent"
autoCapitalize="none"
autoComplete="off"
keyboardType="number-pad"
autoCorrect={false}
onChangeText={(txt): void => {
setLookAhead(txt);
}}
testID="LookAhead"
/>

<View style={styles.buttons}>
<Button
style={styles.button}
text={t('gap.reset')}
variant="secondary"
size="large"
testID="ResetGapLimit"
onPress={clearChanges}
disabled={!hasEdited}
/>
<View style={styles.divider} />
<Button
style={styles.button}
text={t('gap.save')}
size="large"
testID="SaveGapLimit"
loading={loading}
disabled={!hasEdited || !areValid}
onPress={saveGapLimit}
/>
</View>
<SafeAreaInset type="bottom" minPadding={16} />
</ScrollView>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flexGrow: 1,
paddingHorizontal: 16,
},
label: {
marginTop: 16,
marginBottom: 4,
},
textInput: {
minHeight: 52,
marginTop: 5,
},
buttons: {
marginTop: 16,
flexDirection: 'row',
},
button: {
flex: 1,
},
divider: {
width: 16,
},
});

export default memo(GapLimit);
2 changes: 1 addition & 1 deletion src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const persistConfig = {
key: 'root',
storage: mmkvStorage,
// increase version after store shape changes
version: 37,
version: 38,
stateReconciler: autoMergeLevel2,
blacklist: ['receive', 'ui'],
migrate: createMigrate(migrations, { debug: __ENABLE_MIGRATION_DEBUG__ }),
Expand Down
10 changes: 10 additions & 0 deletions src/store/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { defaultBlocktankInfoShape } from '../shapes/blocktank';
import { initialTodosState } from '../shapes/todos';
import { defaultViewControllers } from '../shapes/ui';
import {
getDefaultGapLimitOptions,
getDefaultWalletStoreShape,
getNetworkContent,
} from '../shapes/wallet';
Expand Down Expand Up @@ -509,6 +510,15 @@ const migrations = {
}
return newState;
},
38: (state): PersistedState => {
return {
...state,
wallet: {
...state.wallet,
gapLimitOptions: getDefaultGapLimitOptions(),
},
};
},
};

export default migrations;
16 changes: 16 additions & 0 deletions src/store/reselect/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IFormattedTransaction,
IFormattedTransactions,
ISendTransaction,
TGapLimitOptions,
IUtxo,
} from 'beignet';
import { createSelector } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -58,6 +59,21 @@ export const currentWalletSelector = createSelector(
},
);

/**
* Returns the saved gap limit options for the wallet.
* @param {RootState} state
* @returns {TGapLimitOptions}
*/
export const gapLimitOptionsSelector = createSelector(
[walletState],
(wallet): TGapLimitOptions => wallet.gapLimitOptions,
);

export const addressTypesToMonitorSelector = createSelector(
[walletState],
(wallet): EAddressType[] => wallet.addressTypesToMonitor,
);

/**
* Returns the selected address type for a given wallet and network.
* @param {RootState} state
Expand Down
Loading

0 comments on commit 5d43a41

Please sign in to comment.