feat: refactor import wallet to typescript

This commit is contained in:
Ivan Vershigora 2024-02-11 18:39:44 +00:00
parent 967b0642f9
commit 1223945cf9
No known key found for this signature in database
GPG Key ID: DCCF7FB5ED2CEBD7
4 changed files with 127 additions and 21 deletions

View File

@ -20,43 +20,57 @@ import {
SegwitP2SHWallet,
WatchOnlyWallet,
} from '.';
import type { TWallet } from './wallets/types';
import loc from '../loc';
import bip39WalletFormats from './bip39_wallet_formats.json'; // https://github.com/spesmilo/electrum/blob/master/electrum/bip39_wallet_formats.json
import bip39WalletFormatsBlueWallet from './bip39_wallet_formats_bluewallet.json';
// https://github.com/bitcoinjs/bip32/blob/master/ts-src/bip32.ts#L43
export const validateBip32 = path => path.match(/^(m\/)?(\d+'?\/)*\d+'?$/) !== null;
export const validateBip32 = (path: string) => path.match(/^(m\/)?(\d+'?\/)*\d+'?$/) !== null;
type TReturn = {
cancelled: boolean;
stopped: boolean;
wallets: TWallet[];
};
/**
* Function that starts wallet search and import process. It has async generator inside, so
* that the process can be stoped at any time. It reporst all the progress through callbacks.
*
* @param askPassphrase {bool} If true import process will call onPassword callback for wallet with optional password.
* @param searchAccounts {bool} If true import process will scan for all known derivation path from bip39_wallet_formats.json. If false it will use limited version.
* @param askPassphrase {boolean} If true import process will call onPassword callback for wallet with optional password.
* @param searchAccounts {boolean} If true import process will scan for all known derivation path from bip39_wallet_formats.json. If false it will use limited version.
* @param onProgress {function} Callback to report scanning progress
* @param onWallet {function} Callback to report wallet found
* @param onPassword {function} Callback to ask for password if needed
* @returns {{promise: Promise, stop: function}}
*/
const startImport = (importTextOrig, askPassphrase = false, searchAccounts = false, onProgress, onWallet, onPassword) => {
const startImport = (
importTextOrig: string,
askPassphrase: boolean = false,
searchAccounts: boolean = false,
onProgress: (name: string) => void,
onWallet: (wallet: TWallet) => void,
onPassword: (title: string, text: string) => Promise<string>,
): { promise: Promise<TReturn>; stop: () => void } => {
// state
let promiseResolve;
let promiseReject;
let promiseResolve: (arg: TReturn) => void;
let promiseReject: (reason?: any) => void;
let running = true; // if you put it to false, internal generator stops
const wallets = [];
const promise = new Promise((resolve, reject) => {
const wallets: TWallet[] = [];
const promise = new Promise<TReturn>((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
// actions
const reportProgress = name => {
const reportProgress = (name: string) => {
onProgress(name);
};
const reportFinish = (cancelled, stopped) => {
const reportFinish = (cancelled: boolean = false, stopped: boolean = false) => {
promiseResolve({ cancelled, stopped, wallets });
};
const reportWallet = wallet => {
const reportWallet = (wallet: TWallet) => {
if (wallets.some(w => w.getID() === wallet.getID())) return; // do not add duplicates
wallets.push(wallet);
onWallet(wallet);
@ -134,7 +148,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
}
// is it bip38 encrypted
if (text.startsWith('6P')) {
if (text.startsWith('6P') && password) {
const decryptedKey = await bip38.decryptAsync(text, password);
if (decryptedKey) {
@ -184,7 +198,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
yield { progress: 'bip39' };
const hd2 = new HDSegwitBech32Wallet();
hd2.setSecret(text);
hd2.setPassphrase(password);
if (password) {
hd2.setPassphrase(password);
}
if (hd2.validateMnemonic()) {
let walletFound = false;
// by default we don't try all the paths and options
@ -214,7 +230,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
for (const path of paths) {
const wallet = new WalletClass();
wallet.setSecret(text);
wallet.setPassphrase(password);
if (password) {
wallet.setPassphrase(password);
}
wallet.setDerivationPath(path);
yield { progress: `bip39 ${i.script_type} ${path}` };
if (await wallet.wasEverUsed()) {
@ -230,7 +248,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
// to decide which one is it let's compare number of transactions
const m0Legacy = new HDLegacyP2PKHWallet();
m0Legacy.setSecret(text);
m0Legacy.setPassphrase(password);
if (password) {
m0Legacy.setPassphrase(password);
}
m0Legacy.setDerivationPath("m/0'");
yield { progress: "bip39 p2pkh m/0'" };
// BRD doesn't support passphrase and only works with 12 words seeds
@ -332,7 +352,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
yield { progress: 'electrum p2wpkh-p2sh' };
const el1 = new HDSegwitElectrumSeedP2WPKHWallet();
el1.setSecret(text);
el1.setPassphrase(password);
if (password) {
el1.setPassphrase(password);
}
if (el1.validateMnemonic()) {
yield { wallet: el1 }; // not fetching txs or balances, fuck it, yolo, life is too short
}
@ -341,7 +363,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
yield { progress: 'electrum p2pkh' };
const el2 = new HDLegacyElectrumSeedP2PKHWallet();
el2.setSecret(text);
el2.setPassphrase(password);
if (password) {
el2.setPassphrase(password);
}
if (el2.validateMnemonic()) {
yield { wallet: el2 }; // not fetching txs or balances, fuck it, yolo, life is too short
}
@ -350,7 +374,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
yield { progress: 'aezeed' };
const aezeed2 = new HDAezeedWallet();
aezeed2.setSecret(text);
aezeed2.setPassphrase(password);
if (password) {
aezeed2.setPassphrase(password);
}
if (await aezeed2.validateMnemonicAsync()) {
yield { wallet: aezeed2 }; // not fetching txs or balances, fuck it, yolo, life is too short
}
@ -364,14 +390,18 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
if (s1.validateMnemonic()) {
yield { progress: 'SLIP39 p2wpkh-p2sh' };
s1.setPassphrase(password);
if (password) {
s1.setPassphrase(password);
}
if (await s1.wasEverUsed()) {
yield { wallet: s1 };
}
yield { progress: 'SLIP39 p2pkh' };
const s2 = new SLIP39LegacyP2PKHWallet();
s2.setPassphrase(password);
if (password) {
s2.setPassphrase(password);
}
s2.setSecret(text);
if (await s2.wasEverUsed()) {
yield { wallet: s2 };
@ -380,7 +410,9 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
yield { progress: 'SLIP39 p2wpkh' };
const s3 = new SLIP39SegwitBech32Wallet();
s3.setSecret(text);
s3.setPassphrase(password);
if (password) {
s3.setPassphrase(password);
}
yield { wallet: s3 };
}
}

View File

@ -1,5 +1,20 @@
import bitcoin from 'bitcoinjs-lib';
import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect';
import { HDAezeedWallet } from './hd-aezeed-wallet';
import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
import { HDLegacyElectrumSeedP2PKHWallet } from './hd-legacy-electrum-seed-p2pkh-wallet';
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
import { HDSegwitElectrumSeedP2WPKHWallet } from './hd-segwit-electrum-seed-p2wpkh-wallet';
import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet';
import { LegacyWallet } from './legacy-wallet';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import { LightningLdkWallet } from './lightning-ldk-wallet';
import { MultisigHDWallet } from './multisig-hd-wallet';
import { SegwitBech32Wallet } from './segwit-bech32-wallet';
import { SegwitP2SHWallet } from './segwit-p2sh-wallet';
import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './slip39-wallets';
import { WatchOnlyWallet } from './watch-only-wallet';
export type Utxo = {
// Returned by BlueElectrum
@ -80,3 +95,22 @@ export type Transaction = {
received?: number;
value?: number;
};
export type TWallet =
| HDAezeedWallet
| HDLegacyBreadwalletWallet
| HDLegacyElectrumSeedP2PKHWallet
| HDLegacyP2PKHWallet
| HDSegwitBech32Wallet
| HDSegwitElectrumSeedP2WPKHWallet
| HDSegwitP2SHWallet
| LegacyWallet
| LightningCustodianWallet
| LightningLdkWallet
| MultisigHDWallet
| SLIP39LegacyP2PKHWallet
| SLIP39SegwitBech32Wallet
| SLIP39SegwitP2SHWallet
| SegwitBech32Wallet
| SegwitP2SHWallet
| WatchOnlyWallet;

38
package-lock.json generated
View File

@ -117,12 +117,14 @@
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.73.0",
"@tsconfig/react-native": "^3.0.2",
"@types/bip38": "^3.1.2",
"@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2",
"@types/jest": "^29.4.0",
"@types/react": "^18.2.16",
"@types/react-native": "^0.72.0",
"@types/react-test-renderer": "^18.0.0",
"@types/wif": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"eslint": "^8.45.0",
@ -6556,6 +6558,15 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/bip38": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/bip38/-/bip38-3.1.2.tgz",
"integrity": "sha512-KF5aiS7DUJs2llJJeg1O1Io129PETszfUfDQotJ4VPBXzytpIUmb7n2MHWEdFYRHs2LYoaRivP/aJbTlF56J+Q==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
@ -6719,6 +6730,15 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"node_modules/@types/wif": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.5.tgz",
"integrity": "sha512-addYBlYjDxLfJxDUoyTzICnu0u4snCdGJpICIIFk65zGcdjah3twTJq1Fdy+OdeZSRiof2raFtMqSqF9KeqthQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yargs": {
"version": "16.0.9",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz",
@ -27490,6 +27510,15 @@
"@babel/types": "^7.20.7"
}
},
"@types/bip38": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/bip38/-/bip38-3.1.2.tgz",
"integrity": "sha512-KF5aiS7DUJs2llJJeg1O1Io129PETszfUfDQotJ4VPBXzytpIUmb7n2MHWEdFYRHs2LYoaRivP/aJbTlF56J+Q==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
@ -27655,6 +27684,15 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"@types/wif": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.5.tgz",
"integrity": "sha512-addYBlYjDxLfJxDUoyTzICnu0u4snCdGJpICIIFk65zGcdjah3twTJq1Fdy+OdeZSRiof2raFtMqSqF9KeqthQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/yargs": {
"version": "16.0.9",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz",

View File

@ -13,12 +13,14 @@
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.73.0",
"@tsconfig/react-native": "^3.0.2",
"@types/bip38": "^3.1.2",
"@types/bs58check": "^2.1.0",
"@types/create-hash": "^1.2.2",
"@types/jest": "^29.4.0",
"@types/react": "^18.2.16",
"@types/react-native": "^0.72.0",
"@types/react-test-renderer": "^18.0.0",
"@types/wif": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"eslint": "^8.45.0",