Merge pull request #4212 from shubhamkmr04/feat/ldk-24-word-seed
LDK: Add support for 24 word seed phrases
This commit is contained in:
@@ -223,9 +223,11 @@ import WalletSettings from './views/Onboarding/WalletSettings';
|
||||
|
||||
import EditFee from './views/EditFee';
|
||||
|
||||
// Embedded LND
|
||||
// Embedded LND/LDK
|
||||
import Seed from './views/Settings/Seed';
|
||||
import SeedRecovery from './views/Settings/SeedRecovery';
|
||||
import LdkWalletRecoverySettings from './views/Settings/LdkRecovery/LdkWalletRecoverySettings';
|
||||
import LdkRecoveryVssServer from './views/Settings/LdkRecovery/LdkRecoveryVssServer';
|
||||
import SeedQRExport from './views/Settings/SeedQRExport';
|
||||
import Sync from './views/Sync';
|
||||
import SyncRecovery from './views/SyncRecovery';
|
||||
@@ -1085,6 +1087,18 @@ export default class App extends React.PureComponent {
|
||||
SeedRecovery
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="LdkWalletRecoverySettings" // @ts-ignore:next-line
|
||||
component={
|
||||
LdkWalletRecoverySettings
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="LdkRecoveryVssServer" // @ts-ignore:next-line
|
||||
component={
|
||||
LdkRecoveryVssServer
|
||||
}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="SeedQRExport" // @ts-ignore:next-line
|
||||
component={
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import DropdownSetting from './DropdownSetting';
|
||||
import TextInput from './TextInput';
|
||||
|
||||
import { LDK_VSS_SERVER_KEYS } from '../stores/SettingsStore';
|
||||
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import { themeColor } from '../utils/ThemeUtils';
|
||||
import UrlUtils from '../utils/UrlUtils';
|
||||
import { DEFAULT_VSS_SERVER } from '../utils/LdkNodeUtils';
|
||||
|
||||
export const VSS_CUSTOM_VALUE = 'custom';
|
||||
|
||||
interface VssServerPickerProps {
|
||||
selectedValue: string;
|
||||
customServer: string;
|
||||
onChange: (selectedValue: string, customServer: string) => void;
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
export const resolveVssServer = (
|
||||
selectedValue: string,
|
||||
customServer: string
|
||||
): string =>
|
||||
selectedValue === VSS_CUSTOM_VALUE ? customServer.trim() : selectedValue;
|
||||
|
||||
export const isVssServerValid = (
|
||||
selectedValue: string,
|
||||
customServer: string
|
||||
): boolean => {
|
||||
if (selectedValue !== VSS_CUSTOM_VALUE) return true;
|
||||
const trimmed = customServer.trim();
|
||||
return trimmed !== '' && UrlUtils.isValidUrl(trimmed);
|
||||
};
|
||||
|
||||
export default function VssServerPicker({
|
||||
selectedValue,
|
||||
customServer,
|
||||
onChange,
|
||||
locked
|
||||
}: VssServerPickerProps) {
|
||||
const isCustom = selectedValue === VSS_CUSTOM_VALUE;
|
||||
const showInvalidUrlError =
|
||||
isCustom &&
|
||||
customServer.trim() !== '' &&
|
||||
!UrlUtils.isValidUrl(customServer);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<DropdownSetting
|
||||
title={localeString(
|
||||
'views.Settings.EmbeddedNode.VssServer.serverUrl'
|
||||
)}
|
||||
selectedValue={selectedValue}
|
||||
onValueChange={(value: string) =>
|
||||
onChange(
|
||||
value,
|
||||
value === VSS_CUSTOM_VALUE ? customServer : ''
|
||||
)
|
||||
}
|
||||
values={LDK_VSS_SERVER_KEYS}
|
||||
disabled={locked}
|
||||
/>
|
||||
|
||||
{isCustom && (
|
||||
<>
|
||||
<TextInput
|
||||
value={customServer}
|
||||
placeholder={DEFAULT_VSS_SERVER}
|
||||
onChangeText={(text: string) =>
|
||||
onChange(selectedValue, text)
|
||||
}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
locked={locked}
|
||||
/>
|
||||
{showInvalidUrlError && (
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor('error'),
|
||||
fontFamily: 'PPNeueMontreal-Book',
|
||||
fontSize: 12,
|
||||
marginTop: 4
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.EmbeddedNode.invalidServerUrl'
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -359,6 +359,9 @@
|
||||
"views.Settings.AddEditNode.certificateVerification": "Certificate Verification",
|
||||
"views.Settings.AddEditNode.createLndhub": "Create LNDHub account",
|
||||
"views.Settings.WalletConfiguration.database": "Database",
|
||||
"views.Settings.WalletConfiguration.seedPhraseLength": "Seed phrase length",
|
||||
"views.Settings.WalletConfiguration.seedPhraseLength.12": "12 words",
|
||||
"views.Settings.WalletConfiguration.seedPhraseLength.24": "24 words",
|
||||
"views.Settings.WalletConfiguration.saveWallet": "Save Wallet Config",
|
||||
"views.Settings.WalletConfiguration.setWalletActive": "Set Wallet Config as Active",
|
||||
"views.Settings.WalletConfiguration.walletActive": "Wallet Active",
|
||||
@@ -1387,6 +1390,10 @@
|
||||
"views.Settings.SeedRecovery.clipboardSeedWords": "Seed words detected in clipboard",
|
||||
"views.Settings.SeedRecovery.clipboardSeedWordsPrompt": "Would you like to use them for wallet recovery?",
|
||||
"views.Settings.SeedRecovery.duplicateWallet": "A wallet with the same seed phrase already exists",
|
||||
"views.Settings.SeedRecovery.invalidChecksum": "Invalid seed phrase — checksum failed. Please check the word order and spelling.",
|
||||
"views.Settings.LdkWalletRecoverySettings.title": "Recovery options",
|
||||
"views.Settings.LdkWalletRecoverySettings.seedPhraseLength": "How many words is your seed phrase?",
|
||||
"views.Settings.LdkWalletRecoverySettings.seedPhraseLength.subtitle": "Most wallets use 12 words. Choose 24 only if your backup has 24 words.",
|
||||
"views.Settings.SeedQRExport.title": "Export HD node root keys",
|
||||
"views.Settings.SeedQRExport.pleaseWait": "Generating yprv and zprv. This will take just a few moments.",
|
||||
"views.Settings.SeedQRExport.warning": "WARNING: DO NOT bump a channel opening transaction. This can cause you to lose funds!",
|
||||
|
||||
+16
-3
@@ -491,9 +491,22 @@ export default class CashuStore {
|
||||
|
||||
let cashuSeedPhrase: string;
|
||||
if (ldkMnemonic) {
|
||||
// LDK Node uses a 12-word mnemonic - use it directly as the
|
||||
// cashu seed since it already has exactly 128 bits of entropy
|
||||
cashuSeedPhrase = ldkMnemonic;
|
||||
const ldkWordCount = ldkMnemonic.trim().split(/\s+/).length;
|
||||
if (ldkWordCount === 12) {
|
||||
// A 12-word LDK mnemonic already has exactly 128 bits of
|
||||
// entropy - use it directly as the cashu seed
|
||||
cashuSeedPhrase = ldkMnemonic;
|
||||
} else {
|
||||
// Larger LDK mnemonics (e.g. 24 words) derive a 12-word cashu
|
||||
// seed from bytes [48:64] of the BIP-39 seed (v2-bip39 style)
|
||||
const seedFromMnemonic =
|
||||
bip39scure.mnemonicToSeedSync(ldkMnemonic);
|
||||
const entropy = seedFromMnemonic.slice(48, 64);
|
||||
cashuSeedPhrase = bip39scure.entropyToMnemonic(
|
||||
entropy,
|
||||
BIP39_WORD_LIST
|
||||
);
|
||||
}
|
||||
} else if (lndSeedPhrase && lndSeedPhrase.length > 0) {
|
||||
// LND uses a 24-word mnemonic - derive a 12-word cashu seed
|
||||
// from bytes [48:64] of the BIP-39 seed (v2-bip39 style)
|
||||
|
||||
@@ -427,6 +427,11 @@ export const EMBEDDED_NODE_NETWORK_KEYS = [
|
||||
}
|
||||
];
|
||||
|
||||
export const LDK_VSS_SERVER_KEYS = [
|
||||
{ key: 'ZEUS', value: DEFAULT_VSS_SERVER },
|
||||
{ key: 'Custom', translateKey: 'general.custom', value: 'custom' }
|
||||
];
|
||||
|
||||
export const LNC_MAILBOX_KEYS = [
|
||||
{
|
||||
key: 'mailbox.terminal.lightning.today:443',
|
||||
|
||||
+10
-6
@@ -777,13 +777,17 @@ export default class SwapStore {
|
||||
@action
|
||||
public generateRescueKey = async () => {
|
||||
console.log('GENERATING RESCUE FILE...');
|
||||
// LDK Node already has a 12-word BIP-39 mnemonic - reuse it
|
||||
// instead of generating a separate rescue key
|
||||
// A 12-word LDK Node wallet already has a 12-word BIP-39 mnemonic -
|
||||
// reuse it instead of generating a separate rescue key. 24-word LDK
|
||||
// wallets (and every other backend) get an independent 12-word key.
|
||||
const { implementation, ldkMnemonic } = this.settingsStore;
|
||||
const mnemonic =
|
||||
implementation === 'ldk-node' && ldkMnemonic
|
||||
? ldkMnemonic
|
||||
: generateMnemonic(BIP39_WORD_LIST);
|
||||
const reuseLdkSeed =
|
||||
implementation === 'ldk-node' &&
|
||||
!!ldkMnemonic &&
|
||||
ldkMnemonic.trim().split(/\s+/).length === 12;
|
||||
const mnemonic = reuseLdkSeed
|
||||
? ldkMnemonic
|
||||
: generateMnemonic(BIP39_WORD_LIST);
|
||||
await Storage.setItem(SWAPS_RESCUE_KEY, mnemonic);
|
||||
|
||||
return mnemonic;
|
||||
|
||||
@@ -243,6 +243,7 @@ async function initNode({
|
||||
export async function createLdkNodeWallet({
|
||||
nodeDir,
|
||||
seedMnemonic,
|
||||
wordCount = 12,
|
||||
passphrase,
|
||||
network,
|
||||
esploraServerUrl,
|
||||
@@ -255,6 +256,7 @@ export async function createLdkNodeWallet({
|
||||
}: {
|
||||
nodeDir: string;
|
||||
seedMnemonic?: string;
|
||||
wordCount?: number;
|
||||
passphrase?: string;
|
||||
network: SupportedNetwork;
|
||||
esploraServerUrl?: string;
|
||||
@@ -279,7 +281,7 @@ export async function createLdkNodeWallet({
|
||||
// Generate mnemonic if not provided (new wallet)
|
||||
let mnemonic = seedMnemonic;
|
||||
if (!mnemonic) {
|
||||
mnemonic = await generateMnemonic(12);
|
||||
mnemonic = await generateMnemonic(wordCount);
|
||||
}
|
||||
|
||||
const { vssError } = await initNode({
|
||||
|
||||
@@ -6,14 +6,19 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import Button from '../../../components/Button';
|
||||
import Header from '../../../components/Header';
|
||||
import Screen from '../../../components/Screen';
|
||||
import TextInput from '../../../components/TextInput';
|
||||
import VssServerPicker, {
|
||||
VSS_CUSTOM_VALUE,
|
||||
resolveVssServer,
|
||||
isVssServerValid
|
||||
} from '../../../components/VssServerPicker';
|
||||
|
||||
import SettingsStore from '../../../stores/SettingsStore';
|
||||
import SettingsStore, {
|
||||
LDK_VSS_SERVER_KEYS
|
||||
} from '../../../stores/SettingsStore';
|
||||
|
||||
import { localeString } from '../../../utils/LocaleUtils';
|
||||
import { restartNeeded } from '../../../utils/RestartUtils';
|
||||
import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import UrlUtils from '../../../utils/UrlUtils';
|
||||
import { DEFAULT_VSS_SERVER } from '../../../utils/LdkNodeUtils';
|
||||
|
||||
interface VssServerProps {
|
||||
@@ -22,7 +27,8 @@ interface VssServerProps {
|
||||
}
|
||||
|
||||
interface VssServerState {
|
||||
vssServer: string;
|
||||
selectedValue: string;
|
||||
customServer: string;
|
||||
savedVssServer: string;
|
||||
}
|
||||
|
||||
@@ -35,9 +41,15 @@ export default class VssServer extends React.Component<
|
||||
state = (() => {
|
||||
const { settings } = this.props.SettingsStore;
|
||||
const selectedNode = settings.selectedNode || 0;
|
||||
const saved = settings.nodes?.[selectedNode]?.ldkVssServer || '';
|
||||
const saved =
|
||||
settings.nodes?.[selectedNode]?.ldkVssServer || DEFAULT_VSS_SERVER;
|
||||
|
||||
const presetValues = LDK_VSS_SERVER_KEYS.map((s) => s.value);
|
||||
const isPreset = presetValues.includes(saved);
|
||||
|
||||
return {
|
||||
vssServer: saved,
|
||||
selectedValue: isPreset ? saved : VSS_CUSTOM_VALUE,
|
||||
customServer: isPreset ? '' : saved,
|
||||
savedVssServer: saved
|
||||
};
|
||||
})();
|
||||
@@ -60,16 +72,15 @@ export default class VssServer extends React.Component<
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { vssServer, savedVssServer } = this.state;
|
||||
|
||||
const showReset = vssServer !== DEFAULT_VSS_SERVER;
|
||||
|
||||
const vssServerTrimmed = vssServer.trim();
|
||||
const showInvalidUrlError =
|
||||
vssServerTrimmed !== '' && !UrlUtils.isValidUrl(vssServer);
|
||||
const { selectedValue, customServer, savedVssServer } = this.state;
|
||||
|
||||
const effectiveServer = resolveVssServer(selectedValue, customServer);
|
||||
const isValid = isVssServerValid(selectedValue, customServer);
|
||||
const hasUnsavedChanges =
|
||||
vssServer !== savedVssServer && !showInvalidUrlError;
|
||||
isValid &&
|
||||
effectiveServer !== '' &&
|
||||
effectiveServer !== savedVssServer;
|
||||
const showReset = effectiveServer !== DEFAULT_VSS_SERVER;
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
@@ -102,43 +113,17 @@ export default class VssServer extends React.Component<
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor('secondaryText'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.EmbeddedNode.VssServer.serverUrl'
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={vssServer}
|
||||
placeholder={DEFAULT_VSS_SERVER}
|
||||
onChangeText={(text: string) => {
|
||||
<VssServerPicker
|
||||
selectedValue={selectedValue}
|
||||
customServer={customServer}
|
||||
onChange={(value, custom) =>
|
||||
this.setState({
|
||||
vssServer: text
|
||||
});
|
||||
}}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
selectedValue: value,
|
||||
customServer: custom
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{showInvalidUrlError && (
|
||||
<Text
|
||||
style={{
|
||||
color: themeColor('error'),
|
||||
fontFamily: 'PPNeueMontreal-Book',
|
||||
fontSize: 12,
|
||||
marginTop: 4
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.EmbeddedNode.invalidServerUrl'
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<View style={{ marginTop: 10 }}>
|
||||
<Text
|
||||
style={{
|
||||
@@ -160,7 +145,9 @@ export default class VssServer extends React.Component<
|
||||
accessibilityLabel={localeString(
|
||||
'general.save'
|
||||
)}
|
||||
onPress={() => this.saveSettings(vssServer)}
|
||||
onPress={() =>
|
||||
this.saveSettings(effectiveServer)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@@ -175,7 +162,8 @@ export default class VssServer extends React.Component<
|
||||
secondary
|
||||
onPress={async () => {
|
||||
this.setState({
|
||||
vssServer: DEFAULT_VSS_SERVER
|
||||
selectedValue: DEFAULT_VSS_SERVER,
|
||||
customServer: ''
|
||||
});
|
||||
await this.saveSettings(
|
||||
DEFAULT_VSS_SERVER
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Route } from '@react-navigation/native';
|
||||
|
||||
import Button from '../../../components/Button';
|
||||
import Header from '../../../components/Header';
|
||||
import Screen from '../../../components/Screen';
|
||||
import Text from '../../../components/Text';
|
||||
import VssServerPicker, {
|
||||
resolveVssServer,
|
||||
isVssServerValid
|
||||
} from '../../../components/VssServerPicker';
|
||||
|
||||
import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import { localeString } from '../../../utils/LocaleUtils';
|
||||
import { DEFAULT_VSS_SERVER } from '../../../utils/LdkNodeUtils';
|
||||
|
||||
interface LdkRecoveryVssServerProps {
|
||||
navigation: NativeStackNavigationProp<any, any>;
|
||||
route: Route<
|
||||
'LdkRecoveryVssServer',
|
||||
{
|
||||
network: string;
|
||||
nickname?: string;
|
||||
photo?: string;
|
||||
wordCount: 12 | 24;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
interface LdkRecoveryVssServerState {
|
||||
selectedValue: string;
|
||||
customServer: string;
|
||||
}
|
||||
|
||||
export default class LdkRecoveryVssServer extends React.Component<
|
||||
LdkRecoveryVssServerProps,
|
||||
LdkRecoveryVssServerState
|
||||
> {
|
||||
state = {
|
||||
selectedValue: DEFAULT_VSS_SERVER,
|
||||
customServer: ''
|
||||
};
|
||||
|
||||
private continueToRecovery = () => {
|
||||
const { navigation, route } = this.props;
|
||||
const { network, nickname, photo, wordCount } = route.params ?? {};
|
||||
const { selectedValue, customServer } = this.state;
|
||||
|
||||
navigation.navigate('SeedRecovery', {
|
||||
network,
|
||||
implementation: 'ldk-node',
|
||||
nickname,
|
||||
photo,
|
||||
wordCount,
|
||||
vssServer:
|
||||
resolveVssServer(selectedValue, customServer) || undefined
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
const { selectedValue, customServer } = this.state;
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<Header
|
||||
leftComponent="Back"
|
||||
centerComponent={{
|
||||
text: localeString(
|
||||
'views.Settings.EmbeddedNode.VssServer.title'
|
||||
),
|
||||
style: {
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}
|
||||
}}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<View style={styles.content}>
|
||||
<Text
|
||||
style={{
|
||||
...styles.subtitle,
|
||||
color: themeColor('secondaryText')
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.EmbeddedNode.VssServer.subtitle'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<VssServerPicker
|
||||
selectedValue={selectedValue}
|
||||
customServer={customServer}
|
||||
onChange={(value, custom) =>
|
||||
this.setState({
|
||||
selectedValue: value,
|
||||
customServer: custom
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
title={localeString('general.next')}
|
||||
onPress={this.continueToRecovery}
|
||||
disabled={
|
||||
!isVssServerValid(selectedValue, customServer)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 20
|
||||
},
|
||||
subtitle: {
|
||||
fontFamily: 'PPNeueMontreal-Book',
|
||||
fontSize: 15,
|
||||
marginBottom: 24
|
||||
},
|
||||
button: {
|
||||
marginTop: 24
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Route } from '@react-navigation/native';
|
||||
|
||||
import Button from '../../../components/Button';
|
||||
import Header from '../../../components/Header';
|
||||
import Screen from '../../../components/Screen';
|
||||
import Text from '../../../components/Text';
|
||||
|
||||
import { themeColor } from '../../../utils/ThemeUtils';
|
||||
import { localeString } from '../../../utils/LocaleUtils';
|
||||
|
||||
interface LdkWalletRecoverySettingsProps {
|
||||
navigation: NativeStackNavigationProp<any, any>;
|
||||
route: Route<
|
||||
'LdkWalletRecoverySettings',
|
||||
{
|
||||
network: string;
|
||||
nickname?: string;
|
||||
photo?: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export default class LdkWalletRecoverySettings extends React.Component<
|
||||
LdkWalletRecoverySettingsProps,
|
||||
{}
|
||||
> {
|
||||
private continueToVssServer = (wordCount: 12 | 24) => {
|
||||
const { navigation, route } = this.props;
|
||||
const { network, nickname, photo } = route.params ?? {};
|
||||
|
||||
navigation.navigate('LdkRecoveryVssServer', {
|
||||
network,
|
||||
nickname,
|
||||
photo,
|
||||
wordCount
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<Header
|
||||
leftComponent="Back"
|
||||
centerComponent={{
|
||||
text: localeString(
|
||||
'views.Settings.LdkWalletRecoverySettings.title'
|
||||
),
|
||||
style: {
|
||||
color: themeColor('text'),
|
||||
fontFamily: 'PPNeueMontreal-Book'
|
||||
}
|
||||
}}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<View style={styles.content}>
|
||||
<Text
|
||||
style={{
|
||||
...styles.title,
|
||||
color: themeColor('text')
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.LdkWalletRecoverySettings.seedPhraseLength'
|
||||
)}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
...styles.subtitle,
|
||||
color: themeColor('secondaryText')
|
||||
}}
|
||||
>
|
||||
{localeString(
|
||||
'views.Settings.LdkWalletRecoverySettings.seedPhraseLength.subtitle'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
title={localeString(
|
||||
'views.Settings.WalletConfiguration.seedPhraseLength.12'
|
||||
)}
|
||||
onPress={() => this.continueToVssServer(12)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
title={localeString(
|
||||
'views.Settings.WalletConfiguration.seedPhraseLength.24'
|
||||
)}
|
||||
onPress={() => this.continueToVssServer(24)}
|
||||
secondary
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'PPNeueMontreal-Book',
|
||||
fontSize: 22,
|
||||
textAlign: 'center',
|
||||
marginBottom: 12
|
||||
},
|
||||
subtitle: {
|
||||
fontFamily: 'PPNeueMontreal-Book',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginBottom: 40
|
||||
},
|
||||
button: {
|
||||
marginVertical: 8
|
||||
}
|
||||
});
|
||||
+135
-177
@@ -23,6 +23,7 @@ import { Route } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import RNRestart from 'react-native-restart';
|
||||
import { validateMnemonic } from '@scure/bip39';
|
||||
|
||||
import {
|
||||
ErrorMessage,
|
||||
@@ -100,6 +101,8 @@ interface SeedRecoveryProps {
|
||||
nickname?: string;
|
||||
photo?: string;
|
||||
isSqlite?: boolean;
|
||||
wordCount?: 12 | 24;
|
||||
vssServer?: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
@@ -136,6 +139,7 @@ interface SeedRecoveryState {
|
||||
ldkMnemonic: string;
|
||||
ldkPassphrase: string;
|
||||
ldkNodeDir: string;
|
||||
ldkWordCount: 12 | 24;
|
||||
channelDbUri?: string;
|
||||
channelDbFileName?: string;
|
||||
olympusRestorePending: boolean;
|
||||
@@ -188,20 +192,41 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
ldkMnemonic: '',
|
||||
ldkPassphrase: '',
|
||||
ldkNodeDir: '',
|
||||
ldkWordCount: 12,
|
||||
olympusRestorePending: false,
|
||||
embeddedLndIsSqlite: false
|
||||
};
|
||||
}
|
||||
|
||||
private expectedMnemonicWordCount(
|
||||
private acceptedWordCounts(
|
||||
params: SeedRecoveryProps['route']['params']
|
||||
): 12 | 24 {
|
||||
): (12 | 24)[] {
|
||||
const implementation = params?.implementation ?? 'embedded-lnd';
|
||||
const shortMnemonicFlow =
|
||||
implementation === 'ldk-node' ||
|
||||
if (
|
||||
params?.restoreSwaps === true ||
|
||||
params?.restoreRescueKey === true;
|
||||
return shortMnemonicFlow ? 12 : 24;
|
||||
params?.restoreRescueKey === true
|
||||
) {
|
||||
return [12];
|
||||
}
|
||||
if (implementation === 'ldk-node') {
|
||||
return [12, 24];
|
||||
}
|
||||
return [24];
|
||||
}
|
||||
|
||||
// Effective seed length for the current flow, read from state for rendering.
|
||||
// LDK's value comes from the `wordCount` chosen on the
|
||||
// LdkWalletRecoverySettings screen
|
||||
private currentWordCount(): 12 | 24 {
|
||||
const { implementation, restoreSwaps, restoreRescueKey, ldkWordCount } =
|
||||
this.state;
|
||||
if (restoreSwaps || restoreRescueKey) {
|
||||
return 12;
|
||||
}
|
||||
if (implementation === 'ldk-node') {
|
||||
return ldkWordCount;
|
||||
}
|
||||
return 24;
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -213,9 +238,9 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
if (settings.privacy && settings.privacy.clipboard) {
|
||||
const clipboard = await Clipboard.getString();
|
||||
const clipboardWords = clipboard.trim().split(/\s+/);
|
||||
const expectedWords = this.expectedMnemonicWordCount(route.params);
|
||||
const acceptedWordCounts = this.acceptedWordCounts(route.params);
|
||||
|
||||
if (clipboardWords.length === expectedWords) {
|
||||
if (acceptedWordCounts.includes(clipboardWords.length as 12 | 24)) {
|
||||
this.setState({
|
||||
showClipboardPrompt: true,
|
||||
clipboardSeedArray: clipboardWords
|
||||
@@ -237,12 +262,14 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
const restoreSwaps = props.route.params?.restoreSwaps ?? false;
|
||||
const restoreRescueKey = props.route.params?.restoreRescueKey ?? false;
|
||||
const isSqlite = props.route.params?.isSqlite ?? false;
|
||||
const wordCount = props.route.params?.wordCount ?? 12;
|
||||
this.setState({
|
||||
embeddedLndIsSqlite: isSqlite,
|
||||
network,
|
||||
implementation,
|
||||
restoreSwaps,
|
||||
restoreRescueKey
|
||||
restoreRescueKey,
|
||||
ldkWordCount: wordCount
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,7 +461,7 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
ldkPassphrase,
|
||||
ldkEsploraServer: getDefaultEsploraServer(networkType),
|
||||
ldkRgsServer: getDefaultRgsServer(networkType),
|
||||
ldkVssServer: DEFAULT_VSS_SERVER
|
||||
ldkVssServer: route.params?.vssServer || DEFAULT_VSS_SERVER
|
||||
};
|
||||
|
||||
let nodes: any;
|
||||
@@ -481,6 +508,9 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
implementation
|
||||
} = this.state;
|
||||
|
||||
const wordCount = this.currentWordCount();
|
||||
const isLdkNode = implementation === 'ldk-node';
|
||||
|
||||
const invalidWordIndices: number[] = showValidation
|
||||
? seedArray.reduce((acc: number[], word, i) => {
|
||||
if (!BIP39_WORD_LIST.includes(word?.toLowerCase()?.trim())) {
|
||||
@@ -518,14 +548,7 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
} else if (selectedWordIndex != null) {
|
||||
seedArray[selectedWordIndex] = text || '';
|
||||
this.setState({ seedArray } as any);
|
||||
const is12WordMode =
|
||||
restoreSwaps ||
|
||||
restoreRescueKey ||
|
||||
implementation === 'ldk-node';
|
||||
if (
|
||||
(is12WordMode && selectedWordIndex < 11) ||
|
||||
(!is12WordMode && selectedWordIndex < 23)
|
||||
) {
|
||||
if (selectedWordIndex < wordCount - 1) {
|
||||
this.setState({
|
||||
selectedWordIndex:
|
||||
selectedWordIndex + 1,
|
||||
@@ -688,21 +711,26 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicate embedded-lnd wallet with same seed
|
||||
// Check for a duplicate wallet with the same seed.
|
||||
const { SettingsStore } = this.props;
|
||||
const { settings } = SettingsStore;
|
||||
const existingNodes = settings.nodes || [];
|
||||
const seedWords = seedArray
|
||||
.map((w: string) => w?.toLowerCase()?.trim())
|
||||
.join(' ');
|
||||
const duplicateNode = existingNodes.find(
|
||||
(node: any) =>
|
||||
node.implementation === 'embedded-lnd' &&
|
||||
node.seedPhrase &&
|
||||
node.seedPhrase
|
||||
.map((w: string) => w?.toLowerCase()?.trim())
|
||||
.join(' ') === seedWords
|
||||
);
|
||||
const duplicateNode = existingNodes.find((node: any) => {
|
||||
if (node.implementation === 'embedded-lnd' && node.seedPhrase) {
|
||||
return (
|
||||
node.seedPhrase
|
||||
.map((w: string) => w?.toLowerCase()?.trim())
|
||||
.join(' ') === seedWords
|
||||
);
|
||||
}
|
||||
if (node.implementation === 'ldk-node' && node.ldkMnemonic) {
|
||||
return node.ldkMnemonic.toLowerCase().trim() === seedWords;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (duplicateNode) {
|
||||
this.setState({
|
||||
errorMsg: localeString(
|
||||
@@ -716,9 +744,21 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
|
||||
if (implementation === 'ldk-node') {
|
||||
// LDK Node restore
|
||||
const mnemonic = seedArray.join(' ');
|
||||
|
||||
// LDK seeds are BIP39, so verify the checksum up front.
|
||||
if (!validateMnemonic(mnemonic, BIP39_WORD_LIST)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: localeString(
|
||||
'views.Settings.SeedRecovery.invalidChecksum'
|
||||
)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await stopLdkNode();
|
||||
|
||||
const mnemonic = seedArray.join(' ');
|
||||
const nodeDir = uuidv4();
|
||||
|
||||
try {
|
||||
@@ -767,7 +807,9 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
scorerUrl: DEFAULT_SCORER_URL,
|
||||
lsps1Config,
|
||||
trustedPeers0conf: trustedPeers,
|
||||
vssServerUrl: DEFAULT_VSS_SERVER
|
||||
vssServerUrl:
|
||||
this.props.route.params?.vssServer ||
|
||||
DEFAULT_VSS_SERVER
|
||||
});
|
||||
|
||||
// Node is already built — tell Wallet.tsx to skip re-init
|
||||
@@ -1079,10 +1121,9 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
if (
|
||||
(restoreSwaps ||
|
||||
restoreRescueKey ||
|
||||
implementation ===
|
||||
'ldk-node') &&
|
||||
isLdkNode) &&
|
||||
(selectedWordIndex == null ||
|
||||
selectedWordIndex >= 12)
|
||||
selectedWordIndex >= wordCount)
|
||||
) {
|
||||
return;
|
||||
} else if (selectedWordIndex != null) {
|
||||
@@ -1119,119 +1160,40 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{restoreSwaps ||
|
||||
restoreRescueKey ||
|
||||
implementation === 'ldk-node' ? (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
...styles.column,
|
||||
alignSelf:
|
||||
!selectedInputType
|
||||
? 'center'
|
||||
: undefined
|
||||
}}
|
||||
>
|
||||
{[0, 1, 2, 3, 4, 5].map(
|
||||
(i) => (
|
||||
<RecoveryLabel
|
||||
key={i}
|
||||
type="mnemonicWord"
|
||||
index={i}
|
||||
text={
|
||||
this.state
|
||||
.seedArray[
|
||||
i
|
||||
]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
...styles.column,
|
||||
alignSelf:
|
||||
!selectedInputType
|
||||
? 'center'
|
||||
: undefined
|
||||
}}
|
||||
>
|
||||
{[6, 7, 8, 9, 10, 11].map(
|
||||
(i) => (
|
||||
<RecoveryLabel
|
||||
key={i}
|
||||
type="mnemonicWord"
|
||||
index={i}
|
||||
text={
|
||||
this.state
|
||||
.seedArray[
|
||||
i
|
||||
]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
...styles.column,
|
||||
alignSelf:
|
||||
!selectedInputType
|
||||
? 'center'
|
||||
: undefined
|
||||
}}
|
||||
>
|
||||
{[
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11
|
||||
].map((index: number) => {
|
||||
return (
|
||||
<RecoveryLabel
|
||||
key={index}
|
||||
type="mnemonicWord"
|
||||
index={index}
|
||||
text={
|
||||
seedArray[
|
||||
index
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
...styles.column,
|
||||
alignSelf:
|
||||
!selectedInputType
|
||||
? 'center'
|
||||
: undefined
|
||||
}}
|
||||
>
|
||||
{[
|
||||
12, 13, 14, 15, 16, 17,
|
||||
18, 19, 20, 21, 22, 23
|
||||
].map((index: number) => {
|
||||
return (
|
||||
<RecoveryLabel
|
||||
key={index}
|
||||
type="mnemonicWord"
|
||||
index={index}
|
||||
text={
|
||||
seedArray[
|
||||
index
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{[
|
||||
Array.from(
|
||||
{ length: wordCount / 2 },
|
||||
(_, i) => i
|
||||
),
|
||||
Array.from(
|
||||
{ length: wordCount / 2 },
|
||||
(_, i) => i + wordCount / 2
|
||||
)
|
||||
].map((columnIndices, columnKey) => (
|
||||
<View
|
||||
key={columnKey}
|
||||
style={{
|
||||
...styles.column,
|
||||
alignSelf:
|
||||
!selectedInputType
|
||||
? 'center'
|
||||
: undefined
|
||||
}}
|
||||
>
|
||||
{columnIndices.map(
|
||||
(index: number) => (
|
||||
<RecoveryLabel
|
||||
key={index}
|
||||
type="mnemonicWord"
|
||||
index={index}
|
||||
text={
|
||||
seedArray[index]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
@@ -1552,30 +1514,16 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
)
|
||||
}
|
||||
disabled={
|
||||
restoreSwaps || restoreRescueKey
|
||||
? (rescueHost === 'Custom' &&
|
||||
!customRescueHost) ||
|
||||
seedArray.length !== 12 ||
|
||||
seedArray.some(
|
||||
(seed) =>
|
||||
!BIP39_WORD_LIST.includes(
|
||||
seed
|
||||
?.toLowerCase()
|
||||
?.trim()
|
||||
)
|
||||
)
|
||||
: implementation === 'ldk-node'
|
||||
? seedArray.length !== 12 ||
|
||||
seedArray.some((seed) => !seed)
|
||||
: seedArray.length !== 24 ||
|
||||
seedArray.some(
|
||||
(seed) =>
|
||||
!BIP39_WORD_LIST.includes(
|
||||
seed
|
||||
?.toLowerCase()
|
||||
?.trim()
|
||||
)
|
||||
)
|
||||
((restoreSwaps || restoreRescueKey) &&
|
||||
rescueHost === 'Custom' &&
|
||||
!customRescueHost) ||
|
||||
seedArray.length !== wordCount ||
|
||||
seedArray.some(
|
||||
(seed) =>
|
||||
!BIP39_WORD_LIST.includes(
|
||||
seed?.toLowerCase()?.trim()
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
@@ -1616,18 +1564,28 @@ export default class SeedRecovery extends React.PureComponent<
|
||||
<View style={{ marginBottom: 12 }}>
|
||||
<Button
|
||||
title={localeString('general.yes')}
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
const {
|
||||
clipboardSeedArray,
|
||||
implementation
|
||||
} = this.state;
|
||||
this.setState({
|
||||
seedArray:
|
||||
this.state.clipboardSeedArray.map(
|
||||
(w) =>
|
||||
w.toLowerCase().trim()
|
||||
),
|
||||
seedArray: clipboardSeedArray.map(
|
||||
(w) => w.toLowerCase().trim()
|
||||
),
|
||||
showClipboardPrompt: false,
|
||||
clipboardSeedArray: [],
|
||||
showValidation: true
|
||||
})
|
||||
}
|
||||
});
|
||||
if (implementation === 'ldk-node') {
|
||||
this.setState({
|
||||
ldkWordCount:
|
||||
clipboardSeedArray.length as
|
||||
| 12
|
||||
| 24
|
||||
});
|
||||
}
|
||||
}}
|
||||
tertiary
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -45,6 +45,11 @@ import {
|
||||
} from '../../components/SuccessErrorMessage';
|
||||
import Switch from '../../components/Switch';
|
||||
import TextInput from '../../components/TextInput';
|
||||
import Accordion from '../../components/Accordion';
|
||||
import VssServerPicker, {
|
||||
resolveVssServer,
|
||||
isVssServerValid
|
||||
} from '../../components/VssServerPicker';
|
||||
import { Row } from '../../components/layout/Row';
|
||||
import ShowHideToggle from '../../components/ShowHideToggle';
|
||||
|
||||
@@ -150,10 +155,13 @@ interface WalletConfigurationState {
|
||||
ldkPassphrase?: string;
|
||||
ldkNodeDir?: string;
|
||||
ldkNetwork?: string;
|
||||
ldkSeedWordCount: 12 | 24;
|
||||
ldkEsploraServer?: string;
|
||||
ldkRgsServer?: string;
|
||||
ldkScorerUrl?: string;
|
||||
ldkVssServer?: string;
|
||||
ldkVssServerSelected: string;
|
||||
ldkVssServerCustom: string;
|
||||
ldkNodeInitialized?: boolean;
|
||||
// NWC
|
||||
nostrWalletConnectUrl: string;
|
||||
@@ -231,10 +239,13 @@ export default class WalletConfiguration extends React.Component<
|
||||
ldkPassphrase: '',
|
||||
ldkNodeDir: '',
|
||||
ldkNetwork: 'mainnet',
|
||||
ldkSeedWordCount: 12,
|
||||
ldkEsploraServer: '',
|
||||
ldkRgsServer: '',
|
||||
ldkScorerUrl: '',
|
||||
ldkVssServer: '',
|
||||
ldkVssServer: DEFAULT_VSS_SERVER,
|
||||
ldkVssServerSelected: DEFAULT_VSS_SERVER,
|
||||
ldkVssServerCustom: '',
|
||||
ldkNodeInitialized: false,
|
||||
// NWC
|
||||
nostrWalletConnectUrl: '',
|
||||
@@ -1033,7 +1044,7 @@ export default class WalletConfiguration extends React.Component<
|
||||
network: string
|
||||
) => {
|
||||
const { SettingsStore, navigation } = this.props;
|
||||
const { nickname, photo, ldkPassphrase } = this.state;
|
||||
const { nickname, photo, ldkPassphrase, ldkVssServer } = this.state;
|
||||
const { setConnectingStatus, updateSettings, settings } = SettingsStore;
|
||||
|
||||
const node = {
|
||||
@@ -1049,7 +1060,7 @@ export default class WalletConfiguration extends React.Component<
|
||||
),
|
||||
ldkRgsServer: getDefaultRgsServer(network as SupportedNetwork),
|
||||
ldkScorerUrl: DEFAULT_SCORER_URL,
|
||||
ldkVssServer: DEFAULT_VSS_SERVER
|
||||
ldkVssServer: ldkVssServer || DEFAULT_VSS_SERVER
|
||||
};
|
||||
|
||||
let nodes: any;
|
||||
@@ -1081,7 +1092,9 @@ export default class WalletConfiguration extends React.Component<
|
||||
ldkEsploraServer,
|
||||
ldkRgsServer,
|
||||
ldkScorerUrl,
|
||||
ldkMnemonic
|
||||
ldkVssServer,
|
||||
ldkMnemonic,
|
||||
ldkSeedWordCount
|
||||
} = this.state;
|
||||
|
||||
this.setState({
|
||||
@@ -1125,6 +1138,7 @@ export default class WalletConfiguration extends React.Component<
|
||||
const response = await createLdkNodeWallet({
|
||||
nodeDir: ldkNodeDir,
|
||||
seedMnemonic: ldkMnemonic || undefined,
|
||||
wordCount: ldkSeedWordCount,
|
||||
passphrase: ldkPassphrase || undefined,
|
||||
network: networkType,
|
||||
esploraServerUrl:
|
||||
@@ -1136,7 +1150,7 @@ export default class WalletConfiguration extends React.Component<
|
||||
: ldkScorerUrl,
|
||||
lsps1Config,
|
||||
trustedPeers0conf: trustedPeers,
|
||||
vssServerUrl: DEFAULT_VSS_SERVER
|
||||
vssServerUrl: ldkVssServer || DEFAULT_VSS_SERVER
|
||||
});
|
||||
|
||||
// Node is already built — tell Wallet.tsx to skip re-init
|
||||
@@ -1221,6 +1235,9 @@ export default class WalletConfiguration extends React.Component<
|
||||
// LDK Node
|
||||
ldkMnemonic,
|
||||
ldkNetwork,
|
||||
ldkSeedWordCount,
|
||||
ldkVssServerSelected,
|
||||
ldkVssServerCustom,
|
||||
ldkNodeInitialized,
|
||||
// NWC
|
||||
nostrWalletConnectUrl,
|
||||
@@ -1761,6 +1778,67 @@ export default class WalletConfiguration extends React.Component<
|
||||
}}
|
||||
values={EMBEDDED_NODE_NETWORK_KEYS}
|
||||
/>
|
||||
|
||||
<Accordion
|
||||
headerLayout="form"
|
||||
id="ldk-advanced-settings"
|
||||
scrollRef={this.scrollViewRef}
|
||||
title={localeString(
|
||||
'general.advancedSettings'
|
||||
)}
|
||||
>
|
||||
<DropdownSetting
|
||||
title={localeString(
|
||||
'views.Settings.WalletConfiguration.seedPhraseLength'
|
||||
)}
|
||||
selectedValue={ldkSeedWordCount}
|
||||
onValueChange={(
|
||||
value: number
|
||||
) => {
|
||||
this.setState({
|
||||
ldkSeedWordCount:
|
||||
value as 12 | 24
|
||||
});
|
||||
}}
|
||||
values={[
|
||||
{
|
||||
key: localeString(
|
||||
'views.Settings.WalletConfiguration.seedPhraseLength.12'
|
||||
),
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
key: localeString(
|
||||
'views.Settings.WalletConfiguration.seedPhraseLength.24'
|
||||
),
|
||||
value: 24
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<VssServerPicker
|
||||
selectedValue={
|
||||
ldkVssServerSelected
|
||||
}
|
||||
customServer={
|
||||
ldkVssServerCustom
|
||||
}
|
||||
locked={loading}
|
||||
onChange={(value, custom) =>
|
||||
this.setState({
|
||||
ldkVssServerSelected:
|
||||
value,
|
||||
ldkVssServerCustom:
|
||||
custom,
|
||||
ldkVssServer:
|
||||
resolveVssServer(
|
||||
value,
|
||||
custom
|
||||
)
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Accordion>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -2771,7 +2849,13 @@ export default class WalletConfiguration extends React.Component<
|
||||
);
|
||||
}}
|
||||
tertiary
|
||||
disabled={loading}
|
||||
disabled={
|
||||
loading ||
|
||||
!isVssServerValid(
|
||||
ldkVssServerSelected,
|
||||
ldkVssServerCustom
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.button}>
|
||||
@@ -2794,12 +2878,10 @@ export default class WalletConfiguration extends React.Component<
|
||||
)}
|
||||
onPress={() =>
|
||||
navigation.navigate(
|
||||
'SeedRecovery',
|
||||
'LdkWalletRecoverySettings',
|
||||
{
|
||||
network:
|
||||
ldkNetwork,
|
||||
implementation:
|
||||
'ldk-node',
|
||||
nickname,
|
||||
photo
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user