Compare commits
20 Commits
master
...
edit-maste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e82bfa150 | ||
|
|
2c4fffd336 | ||
|
|
29fbbadd56 | ||
|
|
03e2913c1f | ||
|
|
b6438e6a3a | ||
|
|
15ec76b3e5 | ||
|
|
15c7a7574a | ||
|
|
1ed80d348f | ||
|
|
075164d39f | ||
|
|
c1619a4af5 | ||
|
|
ae18a326d2 | ||
|
|
e3d490fc72 | ||
|
|
4b9b78dabc | ||
|
|
5aaf2b00df | ||
|
|
da5f4a3bed | ||
|
|
da4dd9c983 | ||
|
|
44bb007591 | ||
|
|
250fcb212c | ||
|
|
cbd9937f58 | ||
|
|
32b1e7be5e |
@ -236,20 +236,35 @@ export class WatchOnlyWallet extends LegacyWallet {
|
||||
|
||||
getMasterFingerprintHex() {
|
||||
if (!this.masterFingerprint) return '00000000';
|
||||
let masterFingerprintHex = Number(this.masterFingerprint).toString(16);
|
||||
if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
||||
// poor man's little-endian conversion:
|
||||
// ¯\_(ツ)_/¯
|
||||
return (
|
||||
masterFingerprintHex[6] +
|
||||
masterFingerprintHex[7] +
|
||||
masterFingerprintHex[4] +
|
||||
masterFingerprintHex[5] +
|
||||
masterFingerprintHex[2] +
|
||||
masterFingerprintHex[3] +
|
||||
masterFingerprintHex[0] +
|
||||
masterFingerprintHex[1]
|
||||
);
|
||||
|
||||
const hex = Number(this.masterFingerprint).toString(16).padStart(8, '0');
|
||||
|
||||
const bytes = hex.match(/../g);
|
||||
|
||||
if (!bytes) {
|
||||
return '00000000';
|
||||
} else {
|
||||
// convert Big Endian to Little Endian
|
||||
return bytes.reverse().join('');
|
||||
}
|
||||
}
|
||||
|
||||
setMasterFingerprintHex(hex: string) {
|
||||
if (typeof hex !== 'string') {
|
||||
throw new Error('Fingerprint must be a hex string');
|
||||
}
|
||||
|
||||
// remove 0x
|
||||
hex = hex.replace(/^0x/i, '');
|
||||
|
||||
if (!/^[0-9a-fA-F]{8}$/.test(hex)) {
|
||||
throw new Error('Master fingerprint must be exactly 8 hex characters');
|
||||
}
|
||||
|
||||
// convert Little Endian to Big Endian
|
||||
const reversed = hex.slice(6, 8) + hex.slice(4, 6) + hex.slice(2, 4) + hex.slice(0, 2);
|
||||
|
||||
this.masterFingerprint = parseInt(reversed, 16);
|
||||
}
|
||||
|
||||
isHd() {
|
||||
|
||||
@ -138,9 +138,13 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
) : null}
|
||||
{rightSubtitle != null && rightSubtitle !== '' ? (
|
||||
<View style={styles.rightMemoWrapper}>
|
||||
<Text style={[stylesHook.subtitle, rightSubtitleStyle, stylesHook.rightMemoText]} numberOfLines={1} ellipsizeMode="tail">
|
||||
{rightSubtitle}
|
||||
</Text>
|
||||
{typeof rightSubtitle === 'string' ? (
|
||||
<Text style={[stylesHook.subtitle, rightSubtitleStyle, stylesHook.rightMemoText]} numberOfLines={1} ellipsizeMode="tail">
|
||||
{rightSubtitle}
|
||||
</Text>
|
||||
) : (
|
||||
rightSubtitle
|
||||
)}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@ -524,6 +524,8 @@
|
||||
"xpub_title": "Wallet XPUB",
|
||||
"manage_wallets_search_placeholder": "Search wallets, addresses, transactions and memos",
|
||||
"more_info": "More Info",
|
||||
"invalid_masterfingerprint_title": "Invalid Format",
|
||||
"invalid_masterfingerprint_description": "Please enter a valid hex value",
|
||||
"details_delete_wallet_error_message": "There was an issue confirming if this wallet was removed from notifications—this could be due to a network issue or poor connection. If you continue, you might still receive notifications for transactions related to this wallet, even after it is deleted.",
|
||||
"details_delete_anyway": "Delete anyway"
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Pressable, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { writeFileAndExport } from '../../blue_modules/fs';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { uint8ArrayToHex } from '../../blue_modules/uint8array-extras';
|
||||
@ -26,7 +26,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import loc, { formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useFocusEffect, useRoute, RouteProp, usePreventRemove, useLocale } from '@react-navigation/native';
|
||||
import { useFocusEffect, useRoute, RouteProp, useLocale } from '@react-navigation/native';
|
||||
import { LightningTransaction, Transaction, TWallet } from '../../class/wallets/types';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
@ -390,6 +390,10 @@ const WalletDetails: React.FC = () => {
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.cardBorderColor,
|
||||
},
|
||||
input: {
|
||||
borderColor: colors.formBorder,
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
const navigateToWalletExport = () => {
|
||||
@ -521,7 +525,31 @@ const WalletDetails: React.FC = () => {
|
||||
}
|
||||
}, [wallet, saveToDisk]);
|
||||
|
||||
usePreventRemove(false, () => {});
|
||||
const walletMasterFingerprintInputOnBlur = useCallback(async () => {
|
||||
if (wallet.type === WatchOnlyWallet.type) {
|
||||
const mfp = masterFingerprint?.trim().toLocaleLowerCase();
|
||||
|
||||
// masterfingerprint before editing started
|
||||
const currentMasterFingerprint = wallet.getMasterFingerprintHex();
|
||||
|
||||
if (!mfp) {
|
||||
presentAlert({ title: loc.wallets.invalid_masterfingerprint_title, message: loc.wallets.invalid_masterfingerprint_description });
|
||||
setMasterFingerprint(currentMasterFingerprint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mfp !== currentMasterFingerprint) {
|
||||
try {
|
||||
wallet.setMasterFingerprintHex(mfp);
|
||||
await saveToDisk();
|
||||
setMasterFingerprint(wallet.getMasterFingerprintHex());
|
||||
} catch (error) {
|
||||
presentAlert({ title: loc.wallets.invalid_masterfingerprint_title, message: loc.wallets.invalid_masterfingerprint_description });
|
||||
setMasterFingerprint(currentMasterFingerprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [wallet, masterFingerprint, setMasterFingerprint, saveToDisk]);
|
||||
|
||||
const onViewMasterFingerPrintPress = () => {
|
||||
setIsMasterFingerPrintVisible(true);
|
||||
@ -822,6 +850,7 @@ const WalletDetails: React.FC = () => {
|
||||
onPress={() => setIsAdvancedExpanded(prev => !prev)}
|
||||
style={[styles.sectionTitle, stylesHook.sectionTitle, styles.sectionTitleRowContainer]}
|
||||
activeOpacity={0.85}
|
||||
testID="advanced-details"
|
||||
>
|
||||
<BlueText style={[styles.sectionTitleText, stylesHook.sectionTitleText]}>{loc.wallets.details_advanced}</BlueText>
|
||||
<Icon
|
||||
@ -876,8 +905,34 @@ const WalletDetails: React.FC = () => {
|
||||
onPress={isMasterFingerPrintVisible ? undefined : onViewMasterFingerPrintPress}
|
||||
title={loc.wallets.details_master_fingerprint}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={
|
||||
isMasterFingerPrintVisible ? (masterFingerprint ?? loc.wallets.import_derivation_loading) : loc.multisig.view
|
||||
rightSubtitle={
|
||||
<View>
|
||||
{isMasterFingerPrintVisible ? (
|
||||
<View>
|
||||
{wallet.type === WatchOnlyWallet.type && wallet.isHd() ? (
|
||||
<TextInput
|
||||
value={masterFingerprint}
|
||||
onChangeText={(text: string) => {
|
||||
setMasterFingerprint(text);
|
||||
}}
|
||||
onBlur={walletMasterFingerprintInputOnBlur}
|
||||
numberOfLines={1}
|
||||
style={[styles.input, stylesHook.input, { writingDirection: direction }]}
|
||||
editable={!isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
testID="masterfingerPrintInput"
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<BlueText selectable>{masterFingerprint ?? loc.wallets.import_derivation_loading}</BlueText>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<Pressable onPress={onViewMasterFingerPrintPress} testID="viewMasterfingerprint">
|
||||
<BlueText>{loc.multisig.view}</BlueText>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
rightTitleSelectable={isMasterFingerPrintVisible}
|
||||
@ -983,6 +1038,17 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '500',
|
||||
lineHeight: 20,
|
||||
},
|
||||
input: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
padding: 6,
|
||||
marginTop: 12,
|
||||
minWidth: 88,
|
||||
maxWidth: 88,
|
||||
textAlign: 'center',
|
||||
},
|
||||
detailsCard: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 40,
|
||||
|
||||
@ -102,6 +102,24 @@ describe('Watch only wallet', () => {
|
||||
assert.strictEqual(nextChangeAddress, 'bc1q74tz7eflqc62v8utqlazcs3tqtwmvvzud5dmrz');
|
||||
});
|
||||
|
||||
it('can edit master finger print', async () => {
|
||||
const w = new WatchOnlyWallet();
|
||||
w.setSecret('zpub6rERe82dmmpndd2jSsRH5o3GaDfESv1Zk2cESDuB85HFNSujcDBDZTxvdNCdXzfi83okz7VKx46FA9RxkfYHcZLKU3FRY2b4sf2DzNoMdLU');
|
||||
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '00000000');
|
||||
w.setMasterFingerprintHex('398e3e5b');
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '398e3e5b');
|
||||
|
||||
w.setMasterFingerprintHex('0x398e3e5b');
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '398e3e5b');
|
||||
|
||||
assert.throws(() => w.setMasterFingerprintHex(''), /Master fingerprint must be exactly 8 hex characters/);
|
||||
|
||||
assert.throws(() => w.setMasterFingerprintHex('1234'), /Master fingerprint must be exactly 8 hex characters/);
|
||||
|
||||
assert.throws(() => w.setMasterFingerprintHex('123456789'), /Master fingerprint must be exactly 8 hex characters/);
|
||||
});
|
||||
|
||||
// skipped because its generally rare case
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('can fetch txs for address funded by genesis txs', async () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user