diff --git a/class/wallet-import.js b/class/wallet-import.ts similarity index 88% rename from class/wallet-import.js rename to class/wallet-import.ts index 5367980df..abe17c68f 100644 --- a/class/wallet-import.js +++ b/class/wallet-import.ts @@ -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, +): { promise: Promise; 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((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 }; } } diff --git a/class/wallets/types.ts b/class/wallets/types.ts index e4d9b10a4..fdf56caf1 100644 --- a/class/wallets/types.ts +++ b/class/wallets/types.ts @@ -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; diff --git a/package-lock.json b/package-lock.json index 1b02cbf6a..ea9a455c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8c00af005..8128a08ef 100644 --- a/package.json +++ b/package.json @@ -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",