diff --git a/BlueApp.js b/BlueApp.ts similarity index 78% rename from BlueApp.js rename to BlueApp.ts index ca8ab489c..4cc109e01 100644 --- a/BlueApp.js +++ b/BlueApp.ts @@ -1,41 +1,61 @@ -import Biometric from './class/biometrics'; -import { Platform } from 'react-native'; -import loc from './loc'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; +import createHash from 'create-hash'; +import { Platform } from 'react-native'; +import DefaultPreference from 'react-native-default-preference'; import * as Keychain from 'react-native-keychain'; +import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; +import Realm from 'realm'; + +import BlueElectrum from './blue_modules/BlueElectrum'; +import { initCurrencyDaemon } from './blue_modules/currency'; import { - HDLegacyBreadwalletWallet, - HDSegwitP2SHWallet, - HDLegacyP2PKHWallet, - WatchOnlyWallet, - LegacyWallet, - SegwitP2SHWallet, - SegwitBech32Wallet, - HDSegwitBech32Wallet, - LightningCustodianWallet, - HDLegacyElectrumSeedP2PKHWallet, - HDSegwitElectrumSeedP2WPKHWallet, HDAezeedWallet, - MultisigHDWallet, + HDLegacyBreadwalletWallet, + HDLegacyElectrumSeedP2PKHWallet, + HDLegacyP2PKHWallet, + HDSegwitBech32Wallet, + HDSegwitElectrumSeedP2WPKHWallet, + HDSegwitP2SHWallet, + LegacyWallet, + LightningCustodianWallet, LightningLdkWallet, - SLIP39SegwitP2SHWallet, + MultisigHDWallet, SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, + SLIP39SegwitP2SHWallet, + SegwitBech32Wallet, + SegwitP2SHWallet, + WatchOnlyWallet, } from './class/'; +import Biometric from './class/biometrics'; import { randomBytes } from './class/rng'; +import { TWallet, Transaction } from './class/wallets/types'; import presentAlert from './components/Alert'; -import { initCurrencyDaemon } from './blue_modules/currency'; -import DefaultPreference from 'react-native-default-preference'; +import loc from './loc'; -const encryption = require('./blue_modules/encryption'); -const Realm = require('realm'); -const createHash = require('create-hash'); -let usedBucketNum = false; -let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk const prompt = require('./helpers/prompt'); +const encryption = require('./blue_modules/encryption'); -class AppStorage { +let usedBucketNum: boolean | number = false; +let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk +BlueElectrum.connectMain(); + +export type TTXMetadata = { + [txid: string]: { + memo?: string; + txhex?: string; + }; +}; + +type TRealmTransaction = { + internal: boolean; + index: number; + tx: string; +}; + +const isReactNative = typeof navigator !== 'undefined' && navigator?.product === 'ReactNative'; + +export class AppStorage { static FLAG_ENCRYPTED = 'data_encrypted'; static LNDHUB = 'lndhub'; static ADVANCED_MODE_ENABLED = 'advancedmodeenabled'; @@ -44,16 +64,23 @@ class AppStorage { static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED]; + public cachedPassword?: false | string; + public tx_metadata: TTXMetadata; + public wallets: TWallet[]; + constructor() { - /** {Array.} */ this.wallets = []; this.tx_metadata = {}; this.cachedPassword = false; } async migrateKeys() { - if (!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) return; - for (const key of this.constructor.keys2migrate) { + // do not migrate keys if we are not in RN env + if (!isReactNative) { + return; + } + + for (const key of AppStorage.keys2migrate) { try { const value = await RNSecureKeyStore.get(key); if (value) { @@ -67,13 +94,9 @@ class AppStorage { /** * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is * used for cli/tests - * - * @param key - * @param value - * @returns {Promise|Promise | Promise | * | Promise | void} */ - setItem = (key, value) => { - if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + setItem = (key: string, value: any): Promise => { + if (isReactNative) { return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); } else { return AsyncStorage.setItem(key, value); @@ -83,28 +106,20 @@ class AppStorage { /** * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is * used for cli/tests - * - * @param key - * @returns {Promise|*} */ - getItem = key => { - if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + getItem = (key: string): Promise => { + if (isReactNative) { return RNSecureKeyStore.get(key); } else { return AsyncStorage.getItem(key); } }; - /** - * @throws Error - * @param key {string} - * @returns {Promise<*>|null} - */ - getItemWithFallbackToRealm = async key => { + getItemWithFallbackToRealm = async (key: string): Promise => { let value; try { return await this.getItem(key); - } catch (error) { + } catch (error: any) { console.warn('error reading', key, error.message); console.warn('fallback to realm'); const realmKeyValue = await this.openRealmKeyValue(); @@ -112,6 +127,7 @@ class AppStorage { value = obj?.value; realmKeyValue.close(); if (value) { + // @ts-ignore value.length console.warn('successfully recovered', value.length, 'bytes from realm for key', key); return value; } @@ -119,23 +135,23 @@ class AppStorage { } }; - storageIsEncrypted = async () => { + storageIsEncrypted = async (): Promise => { let data; try { data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED); - } catch (error) { + } catch (error: any) { console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message); return false; } - return !!data; + return Boolean(data); }; - isPasswordInUse = async password => { + isPasswordInUse = async (password: string) => { try { let data = await this.getItem('data'); data = this.decryptData(data, password); - return !!data; + return Boolean(data); } catch (_e) { return false; } @@ -144,12 +160,8 @@ class AppStorage { /** * Iterates through all values of `data` trying to * decrypt each one, and returns first one successfully decrypted - * - * @param data {string} Serialized array - * @param password - * @returns {boolean|string} Either STRING of storage data (which is stringified JSON) or FALSE, which means failure */ - decryptData(data, password) { + decryptData(data: string, password: string): boolean | string { data = JSON.parse(data); let decrypted; let num = 0; @@ -166,19 +178,19 @@ class AppStorage { return false; } - decryptStorage = async password => { + decryptStorage = async (password: string): Promise => { if (password === this.cachedPassword) { - this.cachedPassword = undefined; + this.cachedPassword = undefined; // maybe reset to false ? await this.saveToDisk(); this.wallets = []; - this.tx_metadata = []; + this.tx_metadata = {}; return this.loadFromDisk(); } else { throw new Error('Incorrect password. Please, try again.'); } }; - encryptStorage = async password => { + encryptStorage = async (password: string): Promise => { // assuming the storage is not yet encrypted await this.saveToDisk(); let data = await this.getItem('data'); @@ -196,10 +208,8 @@ class AppStorage { /** * Cleans up all current application data (wallets, tx metadata etc) * Encrypts the bucket and saves it storage - * - * @returns {Promise.} Success or failure */ - createFakeStorage = async fakePassword => { + createFakeStorage = async (fakePassword: string): Promise => { usedBucketNum = false; // resetting currently used bucket so we wont overwrite it this.wallets = []; this.tx_metadata = {}; @@ -218,15 +228,13 @@ class AppStorage { return (await this.getItem('data')) === bucketsString; }; - hashIt = s => { + hashIt = (s: string): string => { return createHash('sha256').update(s).digest().toString('hex'); }; /** * Returns instace of the Realm database, which is encrypted either by cached user's password OR default password. * Database file is deterministically derived from encryption key. - * - * @returns {Promise} */ async getRealm() { const password = this.hashIt(this.cachedPassword || 'fyegjitkyf[eqjnc.lf'); @@ -245,7 +253,9 @@ class AppStorage { }, }, ]; + // @ts-ignore schema doesn't match Realm's schema type return Realm.open({ + // @ts-ignore schema doesn't match Realm's schema type schema, path, encryptionKey, @@ -258,7 +268,7 @@ class AppStorage { * * @returns {Promise} */ - async openRealmKeyValue() { + async openRealmKeyValue(): Promise { const service = 'realm_encryption_key'; let password; const credentials = await Keychain.getGenericPassword({ service }); @@ -284,14 +294,16 @@ class AppStorage { }, }, ]; + // @ts-ignore schema doesn't match Realm's schema type return Realm.open({ + // @ts-ignore schema doesn't match Realm's schema type schema, path, encryptionKey, }); } - saveToRealmKeyValue(realmkeyValue, key, value) { + saveToRealmKeyValue(realmkeyValue: Realm, key: string, value: any) { realmkeyValue.write(() => { realmkeyValue.create( 'KeyValue', @@ -311,7 +323,7 @@ class AppStorage { * @param password If present means storage must be decrypted before usage * @returns {Promise.} */ - async loadFromDisk(password) { + async loadFromDisk(password?: string): Promise { let data = await this.getItemWithFallbackToRealm('data'); if (password) { data = this.decryptData(data, password); @@ -324,7 +336,7 @@ class AppStorage { let realm; try { realm = await this.getRealm(); - } catch (error) { + } catch (error: any) { presentAlert({ message: error.message }); } data = JSON.parse(data); @@ -333,44 +345,44 @@ class AppStorage { for (const key of wallets) { // deciding which type is wallet and instatiating correct object const tempObj = JSON.parse(key); - let unserializedWallet; + let unserializedWallet: TWallet; switch (tempObj.type) { case SegwitBech32Wallet.type: - unserializedWallet = SegwitBech32Wallet.fromJson(key); + unserializedWallet = SegwitBech32Wallet.fromJson(key) as unknown as SegwitBech32Wallet; break; case SegwitP2SHWallet.type: - unserializedWallet = SegwitP2SHWallet.fromJson(key); + unserializedWallet = SegwitP2SHWallet.fromJson(key) as unknown as SegwitP2SHWallet; break; case WatchOnlyWallet.type: - unserializedWallet = WatchOnlyWallet.fromJson(key); + unserializedWallet = WatchOnlyWallet.fromJson(key) as unknown as WatchOnlyWallet; unserializedWallet.init(); if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) { continue; } break; case HDLegacyP2PKHWallet.type: - unserializedWallet = HDLegacyP2PKHWallet.fromJson(key); + unserializedWallet = HDLegacyP2PKHWallet.fromJson(key) as unknown as HDLegacyP2PKHWallet; break; case HDSegwitP2SHWallet.type: - unserializedWallet = HDSegwitP2SHWallet.fromJson(key); + unserializedWallet = HDSegwitP2SHWallet.fromJson(key) as unknown as HDSegwitP2SHWallet; break; case HDSegwitBech32Wallet.type: - unserializedWallet = HDSegwitBech32Wallet.fromJson(key); + unserializedWallet = HDSegwitBech32Wallet.fromJson(key) as unknown as HDSegwitBech32Wallet; break; case HDLegacyBreadwalletWallet.type: - unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key); + unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key) as unknown as HDLegacyBreadwalletWallet; break; case HDLegacyElectrumSeedP2PKHWallet.type: - unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key); + unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key) as unknown as HDLegacyElectrumSeedP2PKHWallet; break; case HDSegwitElectrumSeedP2WPKHWallet.type: - unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key); + unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key) as unknown as HDSegwitElectrumSeedP2WPKHWallet; break; case MultisigHDWallet.type: - unserializedWallet = MultisigHDWallet.fromJson(key); + unserializedWallet = MultisigHDWallet.fromJson(key) as unknown as MultisigHDWallet; break; case HDAezeedWallet.type: - unserializedWallet = HDAezeedWallet.fromJson(key); + unserializedWallet = HDAezeedWallet.fromJson(key) as unknown as HDAezeedWallet; // migrate password to this.passphrase field // remove this code somewhere in year 2022 if (unserializedWallet.secret.includes(':')) { @@ -381,21 +393,20 @@ class AppStorage { break; case LightningLdkWallet.type: - unserializedWallet = LightningLdkWallet.fromJson(key); + unserializedWallet = LightningLdkWallet.fromJson(key) as unknown as LightningLdkWallet; break; case SLIP39SegwitP2SHWallet.type: - unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key); + unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key) as unknown as SLIP39SegwitP2SHWallet; break; case SLIP39LegacyP2PKHWallet.type: - unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key); + unserializedWallet = SLIP39LegacyP2PKHWallet.fromJson(key) as unknown as SLIP39LegacyP2PKHWallet; break; case SLIP39SegwitBech32Wallet.type: - unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key); + unserializedWallet = SLIP39SegwitBech32Wallet.fromJson(key) as unknown as SLIP39SegwitBech32Wallet; break; case LightningCustodianWallet.type: { - /** @type {LightningCustodianWallet} */ - unserializedWallet = LightningCustodianWallet.fromJson(key); - let lndhub = false; + unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet; + let lndhub: false | any = false; try { lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); } catch (error) { @@ -416,13 +427,13 @@ class AppStorage { } case LegacyWallet.type: default: - unserializedWallet = LegacyWallet.fromJson(key); + unserializedWallet = LegacyWallet.fromJson(key) as unknown as LegacyWallet; break; } try { if (realm) this.inflateWalletFromRealm(realm, unserializedWallet); - } catch (error) { + } catch (error: any) { presentAlert({ message: error.message }); } @@ -446,12 +457,11 @@ class AppStorage { * * @param wallet {AbstractWallet} */ - deleteWallet = wallet => { + deleteWallet = (wallet: TWallet): void => { const ID = wallet.getID(); const tempWallets = []; if (wallet.type === LightningLdkWallet.type) { - /** @type {LightningLdkWallet} */ const ldkwallet = wallet; ldkwallet.stop().then(ldkwallet.purgeLocalStorage).catch(alert); } @@ -468,39 +478,44 @@ class AppStorage { this.wallets = tempWallets; }; - inflateWalletFromRealm(realm, walletToInflate) { + inflateWalletFromRealm(realm: Realm, walletToInflate: TWallet) { const transactions = realm.objects('WalletTransactions'); - const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`); + const transactionsForWallet = transactions.filtered(`walletid = "${walletToInflate.getID()}"`) as unknown as TRealmTransaction[]; for (const tx of transactionsForWallet) { if (tx.internal === false) { - if (walletToInflate._hdWalletInstance) { - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index] = - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index] || []; - walletToInflate._hdWalletInstance._txs_by_external_index[tx.index].push(JSON.parse(tx.tx)); + if ('_hdWalletInstance' in walletToInflate && walletToInflate._hdWalletInstance) { + const hd = walletToInflate._hdWalletInstance; + hd._txs_by_external_index[tx.index] = hd._txs_by_external_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + hd._txs_by_external_index[tx.index].push(transaction); } else { walletToInflate._txs_by_external_index[tx.index] = walletToInflate._txs_by_external_index[tx.index] || []; - walletToInflate._txs_by_external_index[tx.index].push(JSON.parse(tx.tx)); + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index[tx.index] as Transaction[]).push(transaction); } } else if (tx.internal === true) { - if (walletToInflate._hdWalletInstance) { - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index] = - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index] || []; - walletToInflate._hdWalletInstance._txs_by_internal_index[tx.index].push(JSON.parse(tx.tx)); + if ('_hdWalletInstance' in walletToInflate && walletToInflate._hdWalletInstance) { + const hd = walletToInflate._hdWalletInstance; + hd._txs_by_internal_index[tx.index] = hd._txs_by_internal_index[tx.index] || []; + const transaction = JSON.parse(tx.tx); + hd._txs_by_internal_index[tx.index].push(transaction); } else { walletToInflate._txs_by_internal_index[tx.index] = walletToInflate._txs_by_internal_index[tx.index] || []; - walletToInflate._txs_by_internal_index[tx.index].push(JSON.parse(tx.tx)); + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction); } } else { if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = []; walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || []; - walletToInflate._txs_by_external_index.push(JSON.parse(tx.tx)); + const transaction = JSON.parse(tx.tx); + (walletToInflate._txs_by_external_index as Transaction[]).push(transaction); } } } - offloadWalletToRealm(realm, wallet) { + offloadWalletToRealm(realm: Realm, wallet: TWallet): void { const id = wallet.getID(); - const walletToSave = wallet._hdWalletInstance ?? wallet; + const walletToSave = ('_hdWalletInstance' in wallet && wallet._hdWalletInstance) || wallet; if (Array.isArray(walletToSave._txs_by_external_index)) { // if this var is an array that means its a single-address wallet class, and this var is a flat array @@ -510,6 +525,7 @@ class AppStorage { const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`); realm.delete(walletTransactionsToDelete); + // @ts-ignore walletToSave._txs_by_external_index is array for (const tx of walletToSave._txs_by_external_index) { realm.create( 'WalletTransactions', @@ -535,6 +551,7 @@ class AppStorage { // insert new ones: for (const index of Object.keys(walletToSave._txs_by_external_index)) { + // @ts-ignore index is number const txs = walletToSave._txs_by_external_index[index]; for (const tx of txs) { realm.create( @@ -551,6 +568,7 @@ class AppStorage { } for (const index of Object.keys(walletToSave._txs_by_internal_index)) { + // @ts-ignore index is number const txs = walletToSave._txs_by_internal_index[index]; for (const tx of txs) { realm.create( @@ -576,7 +594,7 @@ class AppStorage { * * @returns {Promise} Result of storage save */ - async saveToDisk() { + async saveToDisk(): Promise { if (savingInProgress) { console.warn('saveToDisk is in progress'); if (++savingInProgress > 10) presentAlert({ message: 'Critical error. Last actions were not saved' }); // should never happen @@ -590,27 +608,29 @@ class AppStorage { let realm; try { realm = await this.getRealm(); - } catch (error) { + } catch (error: any) { presentAlert({ message: error.message }); } for (const key of this.wallets) { if (typeof key === 'boolean') continue; key.prepareForSerialization(); + // @ts-ignore wtf is wallet.current? Does it even exist? delete key.current; const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore - if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance); + if ('_hdWalletInstance' in key) { + const k = keyCloned as any & WatchOnlyWallet; + k._hdWalletInstance = Object.assign({}, key._hdWalletInstance); + k._hdWalletInstance._txs_by_external_index = {}; + k._hdWalletInstance._txs_by_internal_index = {}; + } if (realm) this.offloadWalletToRealm(realm, key); // stripping down: if (key._txs_by_external_index) { keyCloned._txs_by_external_index = {}; keyCloned._txs_by_internal_index = {}; } - if (key._hdWalletInstance) { - keyCloned._hdWalletInstance._txs_by_external_index = {}; - keyCloned._hdWalletInstance._txs_by_internal_index = {}; - } - if (keyCloned._bip47_instance) { + if ('_bip47_instance' in keyCloned) { delete keyCloned._bip47_instance; // since it wont be restored into a proper class instance } @@ -652,6 +672,7 @@ class AppStorage { newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword)); } } + // @ts-ignore bla bla bla data = newData; } @@ -663,7 +684,7 @@ class AppStorage { this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data)); this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); realmkeyValue.close(); - } catch (error) { + } catch (error: any) { console.error('save to disk exception:', error.message); presentAlert({ message: 'save to disk exception: ' + error.message }); if (error.message.includes('Realm file decryption failed')) { @@ -680,10 +701,8 @@ class AppStorage { * Use getter for a specific wallet to get actual balance. * Returns void. * If index is present then fetch only from this specific wallet - * - * @return {Promise.} */ - fetchWalletBalances = async index => { + fetchWalletBalances = async (index?: number): Promise => { console.log('fetchWalletBalances for wallet#', typeof index === 'undefined' ? '(all)' : index); if (index || index === 0) { let c = 0; @@ -710,17 +729,16 @@ class AppStorage { * blank to fetch from all wallets * @return {Promise.} */ - fetchWalletTransactions = async index => { + fetchWalletTransactions = async (index?: number) => { console.log('fetchWalletTransactions for wallet#', typeof index === 'undefined' ? '(all)' : index); if (index || index === 0) { let c = 0; for (const wallet of this.wallets) { if (c++ === index) { await wallet.fetchTransactions(); - if (wallet.fetchPendingTransactions) { + + if ('fetchPendingTransactions' in wallet) { await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { await wallet.fetchUserInvoices(); } } @@ -728,29 +746,28 @@ class AppStorage { } else { for (const wallet of this.wallets) { await wallet.fetchTransactions(); - if (wallet.fetchPendingTransactions) { + if ('fetchPendingTransactions' in wallet) { await wallet.fetchPendingTransactions(); - } - if (wallet.fetchUserInvoices) { await wallet.fetchUserInvoices(); } } } }; - fetchSenderPaymentCodes = async index => { + fetchSenderPaymentCodes = async (index?: number) => { console.log('fetchSenderPaymentCodes for wallet#', typeof index === 'undefined' ? '(all)' : index); if (index || index === 0) { + const wallet = this.wallets[index]; try { - if (!(this.wallets[index].allowBIP47() && this.wallets[index].isBIP47Enabled())) return; - await this.wallets[index].fetchBIP47SenderPaymentCodes(); + if (!(wallet.allowBIP47() && wallet.isBIP47Enabled() && 'fetchBIP47SenderPaymentCodes' in wallet)) return; + await wallet.fetchBIP47SenderPaymentCodes(); } catch (error) { console.error('Failed to fetch sender payment codes for wallet', index, error); } } else { for (const wallet of this.wallets) { try { - if (!(wallet.allowBIP47() && wallet.isBIP47Enabled())) continue; + if (!(wallet.allowBIP47() && wallet.isBIP47Enabled() && 'fetchBIP47SenderPaymentCodes' in wallet)) continue; await wallet.fetchBIP47SenderPaymentCodes(); } catch (error) { console.error('Failed to fetch sender payment codes for wallet', wallet.label, error); @@ -759,11 +776,7 @@ class AppStorage { } }; - /** - * - * @returns {Array.} - */ - getWallets = () => { + getWallets = (): TWallet[] => { return this.wallets; }; @@ -774,11 +787,14 @@ class AppStorage { * @param index {Integer|null} Wallet index in this.wallets. Empty (or null) for all wallets. * @param limit {Integer} How many txs return, starting from the earliest. Default: all of them. * @param includeWalletsWithHideTransactionsEnabled {Boolean} Wallets' _hideTransactionsInWalletsList property determines wether the user wants this wallet's txs hidden from the main list view. - * @return {Array} */ - getTransactions = (index, limit = Infinity, includeWalletsWithHideTransactionsEnabled = false) => { + getTransactions = ( + index?: number, + limit: number = Infinity, + includeWalletsWithHideTransactionsEnabled: boolean = false, + ): Transaction[] => { if (index || index === 0) { - let txs = []; + let txs: Transaction[] = []; let c = 0; for (const wallet of this.wallets) { if (c++ === index) { @@ -788,7 +804,7 @@ class AppStorage { return txs; } - let txs = []; + let txs: Transaction[] = []; for (const wallet of this.wallets.filter(w => includeWalletsWithHideTransactionsEnabled || !w.getHideTransactionsInWalletsList())) { const walletTransactions = wallet.getTransactions(); const walletID = wallet.getID(); @@ -799,23 +815,19 @@ class AppStorage { txs = txs.concat(walletTransactions); } - for (const t of txs) { - t.sort_ts = +new Date(t.received); - } - return txs - .sort(function (a, b) { - return b.sort_ts - a.sort_ts; + .sort((a, b) => { + const bTime = new Date(b.received!).getTime(); + const aTime = new Date(a.received!).getTime(); + return bTime - aTime; }) .slice(0, limit); }; /** * Getter for a sum of all balances of all wallets - * - * @return {number} */ - getBalance = () => { + getBalance = (): number => { let finalBalance = 0; for (const wal of this.wallets) { finalBalance += wal.getBalance(); @@ -823,48 +835,45 @@ class AppStorage { return finalBalance; }; - isAdvancedModeEnabled = async () => { + isAdvancedModeEnabled = async (): Promise => { try { return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); } catch (_) {} return false; }; - setIsAdvancedModeEnabled = async value => { + setIsAdvancedModeEnabled = async (value: boolean) => { await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); }; - isHandoffEnabled = async () => { + isHandoffEnabled = async (): Promise => { try { return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY)); } catch (_) {} return false; }; - setIsHandoffEnabled = async value => { + setIsHandoffEnabled = async (value: boolean): Promise => { await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : ''); }; - isDoNotTrackEnabled = async () => { + isDoNotTrackEnabled = async (): Promise => { try { return !!(await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK)); } catch (_) {} return false; }; - setDoNotTrack = async value => { + setDoNotTrack = async (value: string) => { await AsyncStorage.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : ''); await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set(AppStorage.DO_NOT_TRACK, value); + await DefaultPreference.set(AppStorage.DO_NOT_TRACK, value ? '1' : ''); }; /** * Simple async sleeper function - * - * @param ms {number} Milliseconds to sleep - * @returns {Promise | Promise<*>>} */ - sleep = ms => { + sleep = (ms: number): Promise => { return new Promise(resolve => setTimeout(resolve, ms)); }; @@ -880,14 +889,14 @@ const BlueApp = new AppStorage(); // If attempt reaches 10, a wipe keychain option will be provided to the user. let unlockAttempt = 0; -const startAndDecrypt = async retry => { +export const startAndDecrypt = async (retry?: boolean): Promise => { console.log('startAndDecrypt'); if (BlueApp.getWallets().length > 0) { console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt'); return true; } await BlueApp.migrateKeys(); - let password = false; + let password: undefined | string; if (await BlueApp.storageIsEncrypted()) { do { password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false); @@ -938,8 +947,6 @@ const startAndDecrypt = async retry => { } }; -BlueApp.startAndDecrypt = startAndDecrypt; -BlueApp.AppStorage = AppStorage; initCurrencyDaemon(); -module.exports = BlueApp; +export default BlueApp; diff --git a/blue_modules/environment.ts b/blue_modules/environment.ts index 49107914d..f0985a6e2 100644 --- a/blue_modules/environment.ts +++ b/blue_modules/environment.ts @@ -11,7 +11,6 @@ const getIsTorCapable = (): boolean => { } else if (isDesktop) { capable = false; } - console.log('getIsTorCapable', capable); return capable; }; diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.tsx similarity index 55% rename from blue_modules/storage-context.js rename to blue_modules/storage-context.tsx index af8e0eea6..018f06d14 100644 --- a/blue_modules/storage-context.js +++ b/blue_modules/storage-context.tsx @@ -1,34 +1,96 @@ import React, { createContext, useEffect, useState } from 'react'; import { useAsyncStorage } from '@react-native-async-storage/async-storage'; -import { FiatUnit } from '../models/fiatUnit'; + +import BlueApp, { TTXMetadata, startAndDecrypt } from '../BlueApp'; import Notifications from '../blue_modules/notifications'; -import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; import { LegacyWallet, WatchOnlyWallet } from '../class'; +import type { TWallet } from '../class/wallets/types'; import presentAlert from '../components/Alert'; -import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback'; +import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; +import { FiatUnit, TFiatUnit } from '../models/fiatUnit'; import { PREFERRED_CURRENCY_STORAGE_KEY } from './currency'; -const BlueApp = require('../BlueApp'); +import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback'; + const BlueElectrum = require('./BlueElectrum'); const A = require('../blue_modules/analytics'); -const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet +// hashmap of timestamps we _started_ refetching some wallet +const _lastTimeTriedToRefetchWallet: { [walletID: string]: number } = {}; -export const WalletTransactionsStatus = { NONE: false, ALL: true }; -export const BlueStorageContext = createContext(); -export const BlueStorageProvider = ({ children }) => { - const [wallets, setWallets] = useState([]); - const [selectedWalletID, setSelectedWalletID] = useState(); - const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState(WalletTransactionsStatus.NONE); - const [walletsInitialized, setWalletsInitialized] = useState(false); - const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD); - const [language, _setLanguage] = useState(); +interface BlueStorageContextType { + wallets: TWallet[]; + setWalletsWithNewOrder: (wallets: TWallet[]) => void; + txMetadata: TTXMetadata; + saveToDisk: (force?: boolean) => Promise; + selectedWalletID: string | undefined; + setSelectedWalletID: (walletID: string | undefined) => void; + addWallet: (wallet: TWallet) => void; + deleteWallet: (wallet: TWallet) => void; + currentSharedCosigner: string; + setSharedCosigner: (cosigner: string) => void; + addAndSaveWallet: (wallet: TWallet) => Promise; + fetchAndSaveWalletTransactions: (walletID: string) => Promise; + walletsInitialized: boolean; + setWalletsInitialized: (initialized: boolean) => void; + refreshAllWalletTransactions: (lastSnappedTo?: number, showUpdateStatusIndicator?: boolean) => Promise; + resetWallets: () => void; + setPreferredFiatCurrency: () => void; + preferredFiatCurrency: TFiatUnit; + setLanguage: () => void; + language: string | undefined; + isHandOffUseEnabled: boolean; + setIsHandOffUseEnabledAsyncStorage: (value: boolean) => Promise; + walletTransactionUpdateStatus: WalletTransactionsStatus | string; + setWalletTransactionUpdateStatus: (status: WalletTransactionsStatus | string) => void; + isElectrumDisabled: boolean; + setIsElectrumDisabled: (value: boolean) => void; + isPrivacyBlurEnabled: boolean; + setIsPrivacyBlurEnabled: (value: boolean) => void; + reloadTransactionsMenuActionFunction: () => void; + setReloadTransactionsMenuActionFunction: (func: () => void) => void; + + getTransactions: typeof BlueApp.getTransactions; + isAdvancedModeEnabled: typeof BlueApp.isAdvancedModeEnabled; + fetchWalletBalances: typeof BlueApp.fetchWalletBalances; + fetchWalletTransactions: typeof BlueApp.fetchWalletTransactions; + getBalance: typeof BlueApp.getBalance; + isStorageEncrypted: typeof BlueApp.storageIsEncrypted; + startAndDecrypt: typeof startAndDecrypt; + encryptStorage: typeof BlueApp.encryptStorage; + sleep: typeof BlueApp.sleep; + createFakeStorage: typeof BlueApp.createFakeStorage; + decryptStorage: typeof BlueApp.decryptStorage; + isPasswordInUse: typeof BlueApp.isPasswordInUse; + cachedPassword: typeof BlueApp.cachedPassword; + setIsAdvancedModeEnabled: typeof BlueApp.setIsAdvancedModeEnabled; + setDoNotTrack: typeof BlueApp.setDoNotTrack; + isDoNotTrackEnabled: typeof BlueApp.isDoNotTrackEnabled; + getItem: typeof BlueApp.getItem; + setItem: typeof BlueApp.setItem; +} + +export enum WalletTransactionsStatus { + NONE = 'NONE', + ALL = 'ALL', +} +// @ts-ignore defaut value does not match the type +export const BlueStorageContext = createContext(undefined); +export const BlueStorageProvider = ({ children }: { children: React.ReactNode }) => { + const [wallets, setWallets] = useState([]); + const [selectedWalletID, setSelectedWalletID] = useState(); + const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState( + WalletTransactionsStatus.NONE, + ); + const [walletsInitialized, setWalletsInitialized] = useState(false); + const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD); + const [language, _setLanguage] = useState(); + const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); + const [isElectrumDisabled, setIsElectrumDisabled] = useState(true); + const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true); + const [currentSharedCosigner, setCurrentSharedCosigner] = useState(''); const getPreferredCurrencyAsyncStorage = useAsyncStorage(PREFERRED_CURRENCY_STORAGE_KEY).getItem; const getLanguageAsyncStorage = useAsyncStorage(LOC_STORAGE_KEY).getItem; - const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); - const [isElectrumDisabled, setIsElectrumDisabled] = useState(true); - const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState(true); - const [currentSharedCosigner, setCurrentSharedCosigner] = useState(''); - const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState(() => {}); + const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState<() => void>(() => {}); useEffect(() => { BlueElectrum.isDisabled().then(setIsElectrumDisabled); @@ -47,12 +109,12 @@ export const BlueStorageProvider = ({ children }) => { } }, [isPrivacyBlurEnabled]); - const setIsHandOffUseEnabledAsyncStorage = value => { + const setIsHandOffUseEnabledAsyncStorage = (value: boolean) => { setIsHandOffUseEnabled(value); return BlueApp.setIsHandoffEnabled(value); }; - const saveToDisk = async (force = false) => { + const saveToDisk = async (force: boolean = false) => { if (BlueApp.getWallets().length === 0 && !force) { console.log('not saving empty wallets array'); return; @@ -80,6 +142,7 @@ export const BlueStorageProvider = ({ children }) => { }, []); const getPreferredCurrency = async () => { + // @ts-ignore TODO: fix this const item = JSON.parse(await getPreferredCurrencyAsyncStorage()) ?? FiatUnit.USD; _setPreferredFiatCurrency(item); return item; @@ -91,6 +154,9 @@ export const BlueStorageProvider = ({ children }) => { const getLanguage = async () => { const item = await getLanguageAsyncStorage(); + if (item === null) { + return; + } _setLanguage(item); }; @@ -108,12 +174,12 @@ export const BlueStorageProvider = ({ children }) => { setWallets(BlueApp.getWallets()); }; - const setWalletsWithNewOrder = wlts => { + const setWalletsWithNewOrder = (wlts: TWallet[]) => { BlueApp.wallets = wlts; saveToDisk(); }; - const refreshAllWalletTransactions = async (lastSnappedTo, showUpdateStatusIndicator = true) => { + const refreshAllWalletTransactions = async (lastSnappedTo?: number, showUpdateStatusIndicator: boolean = true) => { let noErr = true; try { await BlueElectrum.waitTillConnected(); @@ -121,7 +187,7 @@ export const BlueStorageProvider = ({ children }) => { setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL); } const paymentCodesStart = Date.now(); - await fetchSenderPaymentCodes(lastSnappedTo); + await BlueApp.fetchSenderPaymentCodes(lastSnappedTo); const paymentCodesEnd = Date.now(); console.log('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec'); const balanceStart = +new Date(); @@ -141,7 +207,7 @@ export const BlueStorageProvider = ({ children }) => { if (noErr) await saveToDisk(); // caching }; - const fetchAndSaveWalletTransactions = async walletID => { + const fetchAndSaveWalletTransactions = async (walletID: string) => { const index = wallets.findIndex(wallet => wallet.getID() === walletID); let noErr = true; try { @@ -171,17 +237,17 @@ export const BlueStorageProvider = ({ children }) => { if (noErr) await saveToDisk(); // caching }; - const addWallet = wallet => { + const addWallet = (wallet: TWallet) => { BlueApp.wallets.push(wallet); setWallets([...BlueApp.getWallets()]); }; - const deleteWallet = wallet => { + const deleteWallet = (wallet: TWallet) => { BlueApp.deleteWallet(wallet); setWallets([...BlueApp.getWallets()]); }; - const addAndSaveWallet = async w => { + const addAndSaveWallet = async (w: TWallet) => { if (wallets.some(i => i.getID() === w.getID())) { triggerHapticFeedback(HapticFeedbackTypes.NotificationError); presentAlert({ message: 'This wallet has been previously imported.' }); @@ -195,26 +261,20 @@ export const BlueStorageProvider = ({ children }) => { await saveToDisk(); A(A.ENUM.CREATED_WALLET); presentAlert({ message: w.type === WatchOnlyWallet.type ? loc.wallets.import_success_watchonly : loc.wallets.import_success }); + // @ts-ignore need to type notifications first Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []); // start balance fetching at the background await w.fetchBalance(); setWallets([...BlueApp.getWallets()]); }; - const setSharedCosigner = cosigner => { - setCurrentSharedCosigner(cosigner); - }; - - let txMetadata = BlueApp.tx_metadata || {}; + let txMetadata = BlueApp.tx_metadata; const getTransactions = BlueApp.getTransactions; const isAdvancedModeEnabled = BlueApp.isAdvancedModeEnabled; - - const fetchSenderPaymentCodes = BlueApp.fetchSenderPaymentCodes; const fetchWalletBalances = BlueApp.fetchWalletBalances; const fetchWalletTransactions = BlueApp.fetchWalletTransactions; const getBalance = BlueApp.getBalance; const isStorageEncrypted = BlueApp.storageIsEncrypted; - const startAndDecrypt = BlueApp.startAndDecrypt; const encryptStorage = BlueApp.encryptStorage; const sleep = BlueApp.sleep; const createFakeStorage = BlueApp.createFakeStorage; @@ -227,60 +287,56 @@ export const BlueStorageProvider = ({ children }) => { const getItem = BlueApp.getItem; const setItem = BlueApp.setItem; - return ( - - {children} - - ); + const value: BlueStorageContextType = { + wallets, + setWalletsWithNewOrder, + txMetadata, + saveToDisk, + getTransactions, + selectedWalletID, + setSelectedWalletID, + addWallet, + deleteWallet, + currentSharedCosigner, + setSharedCosigner: setCurrentSharedCosigner, + addAndSaveWallet, + setItem, + getItem, + isAdvancedModeEnabled, + fetchWalletBalances, + fetchWalletTransactions, + fetchAndSaveWalletTransactions, + isStorageEncrypted, + encryptStorage, + startAndDecrypt, + cachedPassword, + getBalance, + walletsInitialized, + setWalletsInitialized, + refreshAllWalletTransactions, + sleep, + createFakeStorage, + resetWallets, + decryptStorage, + isPasswordInUse, + setIsAdvancedModeEnabled, + setPreferredFiatCurrency, + preferredFiatCurrency, + setLanguage, + language, + isHandOffUseEnabled, + setIsHandOffUseEnabledAsyncStorage, + walletTransactionUpdateStatus, + setWalletTransactionUpdateStatus, + setDoNotTrack, + isDoNotTrackEnabled, + isElectrumDisabled, + setIsElectrumDisabled, + isPrivacyBlurEnabled, + setIsPrivacyBlurEnabled, + reloadTransactionsMenuActionFunction, + setReloadTransactionsMenuActionFunction, + }; + + return {children}; }; diff --git a/class/on-app-launch.ts b/class/on-app-launch.ts index 3c24deb2f..fb09eed8d 100644 --- a/class/on-app-launch.ts +++ b/class/on-app-launch.ts @@ -1,6 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { AbstractWallet } from './wallets/abstract-wallet'; -const BlueApp = require('../BlueApp'); +import { TWallet } from './wallets/types'; +import BlueApp from '../BlueApp'; export default class OnAppLaunch { static STORAGE_KEY = 'ONAPP_LAUNCH_SELECTED_DEFAULT_WALLET_KEY'; @@ -26,18 +26,18 @@ export default class OnAppLaunch { } } - static async getSelectedDefaultWallet(): Promise { - let selectedWallet: AbstractWallet | false = false; + static async getSelectedDefaultWallet(): Promise { + let selectedWallet: TWallet | undefined; try { const selectedWalletID = JSON.parse((await AsyncStorage.getItem(OnAppLaunch.STORAGE_KEY)) || 'null'); if (selectedWalletID) { - selectedWallet = BlueApp.getWallets().find((wallet: AbstractWallet) => wallet.getID() === selectedWalletID); + selectedWallet = BlueApp.getWallets().find(wallet => wallet.getID() === selectedWalletID); if (!selectedWallet) { await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, ''); } } } catch (_e) { - return false; + return; } return selectedWallet; } diff --git a/class/quick-actions.tsx b/class/quick-actions.tsx index 638f888a3..d08ed55c8 100644 --- a/class/quick-actions.tsx +++ b/class/quick-actions.tsx @@ -1,14 +1,15 @@ import React, { useContext, useEffect } from 'react'; -import QuickActions from 'react-native-quick-actions'; -import { DeviceEventEmitter, Linking, Platform } from 'react-native'; -import { formatBalance } from '../loc'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { CommonActions } from '@react-navigation/native'; +import { DeviceEventEmitter, Linking, Platform } from 'react-native'; +import QuickActions from 'react-native-quick-actions'; + +import * as NavigationService from '../NavigationService'; import { BlueStorageContext } from '../blue_modules/storage-context'; +import { formatBalance } from '../loc'; import DeeplinkSchemaMatch from './deeplink-schema-match'; import OnAppLaunch from './on-app-launch'; -import * as NavigationService from '../NavigationService'; -import { CommonActions } from '@react-navigation/native'; -import { AbstractWallet } from './wallets/abstract-wallet'; +import { TWallet } from './wallets/types'; const DeviceQuickActionsStorageKey = 'DeviceQuickActionsEnabled'; @@ -93,9 +94,9 @@ function DeviceQuickActions(): JSX.Element | null { } else { const isViewAllWalletsEnabled = await OnAppLaunch.isViewAllWalletsEnabled(); if (!isViewAllWalletsEnabled) { - const selectedDefaultWallet: AbstractWallet = (await OnAppLaunch.getSelectedDefaultWallet()) as AbstractWallet; + const selectedDefaultWallet = (await OnAppLaunch.getSelectedDefaultWallet()) as TWallet; if (selectedDefaultWallet) { - const wallet = wallets.find((w: AbstractWallet) => w.getID() === selectedDefaultWallet.getID()); + const wallet = wallets.find(w => w.getID() === selectedDefaultWallet.getID()); if (wallet) { NavigationService.dispatch( CommonActions.navigate({ diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index b93a9dc78..1426a8abf 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -31,10 +31,14 @@ type BalanceByIndex = { * Electrum - means that it utilizes Electrum protocol for blockchain data */ export class AbstractHDElectrumWallet extends AbstractHDWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; static defaultRBFSequence = 2147483648; // 1 << 31, minimum for replaceable transactions as per BIP68 static finalRBFSequence = 4294967295; // 0xFFFFFFFF + // @ts-ignore: override + public readonly type = AbstractHDElectrumWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractHDElectrumWallet.typeReadable; _balances_by_external_index: Record; _balances_by_internal_index: Record; diff --git a/class/wallets/abstract-hd-wallet.ts b/class/wallets/abstract-hd-wallet.ts index 50e528a6a..353f45aed 100644 --- a/class/wallets/abstract-hd-wallet.ts +++ b/class/wallets/abstract-hd-wallet.ts @@ -13,8 +13,12 @@ type AbstractHDWalletStatics = { * @deprecated */ export class AbstractHDWallet extends LegacyWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; + // @ts-ignore: override + public readonly type = AbstractHDWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractHDWallet.typeReadable; next_free_address_index: number; next_free_change_address_index: number; diff --git a/class/wallets/abstract-wallet.ts b/class/wallets/abstract-wallet.ts index b6ad1ca46..0122c0442 100644 --- a/class/wallets/abstract-wallet.ts +++ b/class/wallets/abstract-wallet.ts @@ -1,15 +1,8 @@ -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import b58 from 'bs58check'; import createHash from 'create-hash'; +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; -type WalletStatics = { - type: string; - typeReadable: string; - segwitType?: 'p2wpkh' | 'p2sh(p2wpkh)'; - derivationPath?: string; -}; - type WalletWithPassphrase = AbstractWallet & { getPassphrase: () => string }; type UtxoMetadata = { frozen?: boolean; @@ -17,8 +10,12 @@ type UtxoMetadata = { }; export class AbstractWallet { - static type = 'abstract'; - static typeReadable = 'abstract'; + static readonly type = 'abstract'; + static readonly typeReadable = 'abstract'; + // @ts-ignore: override + public readonly type = AbstractWallet.type; + // @ts-ignore: override + public readonly typeReadable = AbstractWallet.typeReadable; static fromJson(obj: string): AbstractWallet { const obj2 = JSON.parse(obj); @@ -31,8 +28,6 @@ export class AbstractWallet { return temp; } - type: string; - typeReadable: string; segwitType?: 'p2wpkh' | 'p2sh(p2wpkh)'; _derivationPath?: string; label: string; @@ -50,14 +45,9 @@ export class AbstractWallet { _hideTransactionsInWalletsList: boolean; _utxoMetadata: Record; use_with_hardware_wallet: boolean; - masterFingerprint: number | false; + masterFingerprint: number; constructor() { - const Constructor = this.constructor as unknown as WalletStatics; - - this.type = Constructor.type; - this.typeReadable = Constructor.typeReadable; - this.segwitType = Constructor.segwitType; this.label = ''; this.secret = ''; // private key or recovery phrase this.balance = 0; @@ -73,7 +63,7 @@ export class AbstractWallet { this._hideTransactionsInWalletsList = false; this._utxoMetadata = {}; this.use_with_hardware_wallet = false; - this.masterFingerprint = false; + this.masterFingerprint = 0; } /** @@ -257,7 +247,7 @@ export class AbstractWallet { parsedSecret = JSON.parse(newSecret); } if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) { - let masterFingerprint: number | false = false; + let masterFingerprint: number = 0; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); diff --git a/class/wallets/hd-aezeed-wallet.ts b/class/wallets/hd-aezeed-wallet.ts index 04fd2adcc..a47ac5256 100644 --- a/class/wallets/hd-aezeed-wallet.ts +++ b/class/wallets/hd-aezeed-wallet.ts @@ -18,10 +18,14 @@ const bip32 = BIP32Factory(ecc); * @see https://github.com/lightningnetwork/lnd/blob/master/keychain/derivation.go */ export class HDAezeedWallet extends AbstractHDElectrumWallet { - static type = 'HDAezeedWallet'; - static typeReadable = 'HD Aezeed'; - static segwitType = 'p2wpkh'; - static derivationPath = "m/84'/0'/0'"; + static readonly type = 'HDAezeedWallet'; + static readonly typeReadable = 'HD Aezeed'; + public readonly segwitType = 'p2wpkh'; + static readonly derivationPath = "m/84'/0'/0'"; + // @ts-ignore: override + public readonly type = HDAezeedWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDAezeedWallet.typeReadable; private _entropyHex?: string; diff --git a/class/wallets/hd-legacy-breadwallet-wallet.js b/class/wallets/hd-legacy-breadwallet-wallet.ts similarity index 71% rename from class/wallets/hd-legacy-breadwallet-wallet.js rename to class/wallets/hd-legacy-breadwallet-wallet.ts index 5a6d5e929..db685503c 100644 --- a/class/wallets/hd-legacy-breadwallet-wallet.js +++ b/class/wallets/hd-legacy-breadwallet-wallet.ts @@ -1,10 +1,12 @@ +import BIP32Factory, { BIP32Interface } from 'bip32'; import * as bitcoinjs from 'bitcoinjs-lib'; -import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import BIP32Factory from 'bip32'; +import { Psbt } from 'bitcoinjs-lib'; +import { CoinSelectReturnInput } from 'coinselect'; +import BlueElectrum, { ElectrumHistory } from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; +import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; +import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; -const BlueElectrum = require('../../blue_modules/BlueElectrum'); const bip32 = BIP32Factory(ecc); /** @@ -12,32 +14,46 @@ const bip32 = BIP32Factory(ecc); * In particular, Breadwallet-compatible (Legacy addresses) */ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { - static type = 'HDLegacyBreadwallet'; - static typeReadable = 'HD Legacy Breadwallet (P2PKH)'; - static derivationPath = "m/0'"; + static readonly type = 'HDLegacyBreadwallet'; + static readonly typeReadable = 'HD Legacy Breadwallet (P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyBreadwalletWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyBreadwalletWallet.typeReadable; + static readonly derivationPath = "m/0'"; // track address index at which wallet switched to segwit - _external_segwit_index = null; - _internal_segwit_index = null; + _external_segwit_index: number | null = null; + _internal_segwit_index: number | null = null; // we need a separate function without external_addresses_cache to use in binarySearch - _calcNodeAddressByIndex(node, index, p2wpkh = false) { - let _node; + _calcNodeAddressByIndex(node: number, index: number, p2wpkh: boolean = false) { + let _node: BIP32Interface | undefined; if (node === 0) { _node = this._node0 || (this._node0 = bip32.fromBase58(this.getXpub()).derive(node)); } if (node === 1) { _node = this._node1 || (this._node1 = bip32.fromBase58(this.getXpub()).derive(node)); } + + if (!_node) { + throw new Error('Internal error: this._node0 or this._node1 is undefined'); + } + const pubkey = _node.derive(index).publicKey; const address = p2wpkh ? bitcoinjs.payments.p2wpkh({ pubkey }).address : bitcoinjs.payments.p2pkh({ pubkey }).address; + + if (!address) { + throw new Error('Internal error: no address in _calcNodeAddressByIndex'); + } + return address; } // this function is different from HDLegacyP2PKHWallet._getNodeAddressByIndex. // It takes _external_segwit_index _internal_segwit_index for account // and starts to generate segwit addresses if index more than them - _getNodeAddressByIndex(node, index) { + _getNodeAddressByIndex(node: number, index: number): string { index = index * 1; // cast to int if (node === 0) { if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit @@ -64,6 +80,8 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { if (node === 1) { return (this.internal_addresses_cache[index] = address); } + + throw new Error('Internal error: unknown node'); } async fetchBalance() { @@ -96,8 +114,8 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { } } - async _binarySearchIteration(startIndex, endIndex, node = 0, p2wpkh = false) { - const gerenateChunkAddresses = chunkNum => { + async _binarySearchIteration(startIndex: number, endIndex: number, node: number = 0, p2wpkh: boolean = false) { + const gerenateChunkAddresses = (chunkNum: number) => { const ret = []; for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) { ret.push(this._calcNodeAddressByIndex(node, c, p2wpkh)); @@ -105,11 +123,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { return ret; }; - let lastChunkWithUsedAddressesNum = null; - let lastHistoriesWithUsedAddresses = null; + let lastChunkWithUsedAddressesNum: number; + let lastHistoriesWithUsedAddresses: Record; for (let c = 0; c < Math.round(endIndex / this.gap_limit); c++) { const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c)); - if (this.constructor._getTransactionsFromHistories(histories).length > 0) { + if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) { // in this particular chunk we have used addresses lastChunkWithUsedAddressesNum = c; lastHistoriesWithUsedAddresses = histories; @@ -121,11 +139,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { let lastUsedIndex = startIndex; - if (lastHistoriesWithUsedAddresses) { + if (lastHistoriesWithUsedAddresses!) { // now searching for last used address in batch lastChunkWithUsedAddressesNum for ( - let c = lastChunkWithUsedAddressesNum * this.gap_limit; - c < lastChunkWithUsedAddressesNum * this.gap_limit + this.gap_limit; + let c = lastChunkWithUsedAddressesNum! * this.gap_limit; + c < lastChunkWithUsedAddressesNum! * this.gap_limit + this.gap_limit; c++ ) { const address = this._calcNodeAddressByIndex(node, c, p2wpkh); @@ -138,11 +156,11 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet { return lastUsedIndex; } - _addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) { + _addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer: Buffer) { // hack to use // AbstractHDElectrumWallet._addPsbtInput for bech32 address // HDLegacyP2PKHWallet._addPsbtInput for legacy address - const ProxyClass = input.address.startsWith('bc1') ? AbstractHDElectrumWallet : HDLegacyP2PKHWallet; + const ProxyClass = input?.address?.startsWith('bc1') ? AbstractHDElectrumWallet : HDLegacyP2PKHWallet; const proxy = new ProxyClass(); return proxy._addPsbtInput.apply(this, [psbt, input, sequence, masterFingerprintBuffer]); } diff --git a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts index 835c8930a..befc78485 100644 --- a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts +++ b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.ts @@ -19,9 +19,13 @@ type SeedOpts = { * @see https://electrum.readthedocs.io/en/latest/seedphrase.html */ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { - static type = 'HDlegacyElectrumSeedP2PKH'; - static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)'; - static derivationPath = 'm'; + static readonly type = 'HDlegacyElectrumSeedP2PKH'; + static readonly typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyElectrumSeedP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyElectrumSeedP2PKHWallet.typeReadable; + static readonly derivationPath = 'm'; validateMnemonic() { return mn.validateMnemonic(this.secret, PREFIX); diff --git a/class/wallets/hd-legacy-p2pkh-wallet.ts b/class/wallets/hd-legacy-p2pkh-wallet.ts index 75cb422aa..4d6ff8e37 100644 --- a/class/wallets/hd-legacy-p2pkh-wallet.ts +++ b/class/wallets/hd-legacy-p2pkh-wallet.ts @@ -12,9 +12,13 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum'); * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki */ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { - static type = 'HDlegacyP2PKH'; - static typeReadable = 'HD Legacy (BIP44 P2PKH)'; - static derivationPath = "m/44'/0'/0'"; + static readonly type = 'HDlegacyP2PKH'; + static readonly typeReadable = 'HD Legacy (BIP44 P2PKH)'; + // @ts-ignore: override + public readonly type = HDLegacyP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDLegacyP2PKHWallet.typeReadable; + static readonly derivationPath = "m/44'/0'/0'"; allowSend() { return true; diff --git a/class/wallets/hd-segwit-bech32-wallet.ts b/class/wallets/hd-segwit-bech32-wallet.ts index 398afce80..488922cdf 100644 --- a/class/wallets/hd-segwit-bech32-wallet.ts +++ b/class/wallets/hd-segwit-bech32-wallet.ts @@ -6,10 +6,14 @@ import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; * @see https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki */ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet { - static type = 'HDsegwitBech32'; - static typeReadable = 'HD SegWit (BIP84 Bech32 Native)'; - static segwitType = 'p2wpkh'; - static derivationPath = "m/84'/0'/0'"; + static readonly type = 'HDsegwitBech32'; + static readonly typeReadable = 'HD SegWit (BIP84 Bech32 Native)'; + // @ts-ignore: override + public readonly type = HDSegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitBech32Wallet.typeReadable; + public readonly segwitType = 'p2wpkh'; + static readonly derivationPath = "m/84'/0'/0'"; allowSend() { return true; diff --git a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts index 793938d67..7bad54143 100644 --- a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts +++ b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.ts @@ -20,9 +20,13 @@ type SeedOpts = { * @see https://electrum.readthedocs.io/en/latest/seedphrase.html */ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { - static type = 'HDSegwitElectrumSeedP2WPKHWallet'; - static typeReadable = 'HD Electrum (BIP32 P2WPKH)'; - static derivationPath = "m/0'"; + static readonly type = 'HDSegwitElectrumSeedP2WPKHWallet'; + static readonly typeReadable = 'HD Electrum (BIP32 P2WPKH)'; + // @ts-ignore: override + public readonly type = HDSegwitElectrumSeedP2WPKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitElectrumSeedP2WPKHWallet.typeReadable; + static readonly derivationPath = "m/0'"; validateMnemonic() { return mn.validateMnemonic(this.secret, PREFIX); diff --git a/class/wallets/hd-segwit-p2sh-wallet.ts b/class/wallets/hd-segwit-p2sh-wallet.ts index 99a625f82..cf3b30910 100644 --- a/class/wallets/hd-segwit-p2sh-wallet.ts +++ b/class/wallets/hd-segwit-p2sh-wallet.ts @@ -14,10 +14,14 @@ const bip32 = BIP32Factory(ecc); * @see https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki */ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { - static type = 'HDsegwitP2SH'; - static typeReadable = 'HD SegWit (BIP49 P2SH)'; - static segwitType = 'p2sh(p2wpkh)'; - static derivationPath = "m/49'/0'/0'"; + static readonly type = 'HDsegwitP2SH'; + static readonly typeReadable = 'HD SegWit (BIP49 P2SH)'; + // @ts-ignore: override + public readonly type = HDSegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = HDSegwitP2SHWallet.typeReadable; + public readonly segwitType = 'p2sh(p2wpkh)'; + static readonly derivationPath = "m/49'/0'/0'"; allowSend() { return true; diff --git a/class/wallets/legacy-wallet.ts b/class/wallets/legacy-wallet.ts index f8e552d1c..f24a7f73e 100644 --- a/class/wallets/legacy-wallet.ts +++ b/class/wallets/legacy-wallet.ts @@ -19,8 +19,12 @@ bitcoin.initEccLib(ecc); * (legacy P2PKH compressed) */ export class LegacyWallet extends AbstractWallet { - static type = 'legacy'; - static typeReadable = 'Legacy (P2PKH)'; + static readonly type = 'legacy'; + static readonly typeReadable = 'Legacy (P2PKH)'; + // @ts-ignore: override + public readonly type = LegacyWallet.type; + // @ts-ignore: override + public readonly typeReadable = LegacyWallet.typeReadable; _txs_by_external_index: Transaction[] = []; _txs_by_internal_index: Transaction[] = []; diff --git a/class/wallets/lightning-custodian-wallet.js b/class/wallets/lightning-custodian-wallet.ts similarity index 86% rename from class/wallets/lightning-custodian-wallet.js rename to class/wallets/lightning-custodian-wallet.ts index 87bb4e410..e962edc56 100644 --- a/class/wallets/lightning-custodian-wallet.js +++ b/class/wallets/lightning-custodian-wallet.ts @@ -4,23 +4,32 @@ import bolt11 from 'bolt11'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; export class LightningCustodianWallet extends LegacyWallet { - static type = 'lightningCustodianWallet'; - static typeReadable = 'Lightning'; + static readonly type = 'lightningCustodianWallet'; + static readonly typeReadable = 'Lightning'; + // @ts-ignore: override + public readonly type = LightningCustodianWallet.type; + // @ts-ignore: override + public readonly typeReadable = LightningCustodianWallet.typeReadable; - constructor(props) { - super(props); - this.setBaseURI(); // no args to init with default value + baseURI?: string; + refresh_token: string = ''; + access_token: string = ''; + _refresh_token_created_ts: number = 0; + _access_token_created_ts: number = 0; + refill_addressess: string[] = []; + pending_transactions_raw: any[] = []; + transactions_raw: any[] = []; + user_invoices_raw: any[] = []; + info_raw = false; + preferredBalanceUnit = BitcoinUnit.SATS; + chain = Chain.OFFCHAIN; + private _api?: Frisbee; + last_paid_invoice_result?: any; + decoded_invoice_raw?: any; + + constructor() { + super(); this.init(); - this.refresh_token = ''; - this.access_token = ''; - this._refresh_token_created_ts = 0; - this._access_token_created_ts = 0; - this.refill_addressess = []; - this.pending_transactions_raw = []; - this.user_invoices_raw = []; - this.info_raw = false; - this.preferredBalanceUnit = BitcoinUnit.SATS; - this.chain = Chain.OFFCHAIN; } /** @@ -28,7 +37,7 @@ export class LightningCustodianWallet extends LegacyWallet { * * @param URI */ - setBaseURI(URI) { + setBaseURI(URI: string | undefined) { this.baseURI = URI; } @@ -40,11 +49,11 @@ export class LightningCustodianWallet extends LegacyWallet { return true; } - getAddress() { + getAddress(): string | false { if (this.refill_addressess.length > 0) { return this.refill_addressess[0]; } else { - return undefined; + return false; } } @@ -60,8 +69,9 @@ export class LightningCustodianWallet extends LegacyWallet { return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min } - static fromJson(param) { + static fromJson(param: any) { const obj = super.fromJson(param); + // @ts-ignore: local init obj.init(); return obj; } @@ -84,11 +94,13 @@ export class LightningCustodianWallet extends LegacyWallet { return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d } - generate() { + generate(): Promise { // nop + return Promise.resolve(); } - async createAccount(isTest) { + async createAccount(isTest: boolean = false) { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.post('/create', { body: { partnerid: 'bluewallet', accounttype: (isTest && 'test') || 'common' }, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, @@ -109,7 +121,8 @@ export class LightningCustodianWallet extends LegacyWallet { this.secret = 'lndhub://' + json.login + ':' + json.password; } - async payInvoice(invoice, freeAmount = 0) { + async payInvoice(invoice: string, freeAmount: number = 0) { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.post('/payinvoice', { body: { invoice, amount: freeAmount }, headers: { @@ -146,9 +159,10 @@ export class LightningCustodianWallet extends LegacyWallet { * * @return {Promise.} */ - async getUserInvoices(limit = false) { + async getUserInvoices(limit: number | false = false) { + if (!this._api) throw new Error('Internal error: _api is not initialized'); let limitString = ''; - if (limit) limitString = '?limit=' + parseInt(limit, 10); + if (limit) limitString = '?limit=' + parseInt(limit as unknown as string, 10); const response = await this._api.get('/getuserinvoices' + limitString, { headers: { 'Access-Control-Allow-Origin': '*', @@ -184,7 +198,7 @@ export class LightningCustodianWallet extends LegacyWallet { } } - this.user_invoices_raw = json.sort(function (a, b) { + this.user_invoices_raw = json.sort(function (a: { timestamp: number }, b: { timestamp: number }) { return a.timestamp - b.timestamp; }); @@ -201,15 +215,16 @@ export class LightningCustodianWallet extends LegacyWallet { await this.getUserInvoices(); } - isInvoiceGeneratedByWallet(paymentRequest) { + isInvoiceGeneratedByWallet(paymentRequest: string) { return this.user_invoices_raw.some(invoice => invoice.payment_request === paymentRequest); } - weOwnAddress(address) { + weOwnAddress(address: string) { return this.refill_addressess.some(refillAddress => address === refillAddress); } - async addInvoice(amt, memo) { + async addInvoice(amt: number, memo: string) { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.post('/addinvoice', { body: { amt: amt + '', memo }, headers: { @@ -241,6 +256,7 @@ export class LightningCustodianWallet extends LegacyWallet { * @return {Promise.} */ async authorize() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); let login, password; if (this.secret.indexOf('blitzhub://') !== -1) { login = this.secret.replace('blitzhub://', '').split(':')[0]; @@ -296,6 +312,7 @@ export class LightningCustodianWallet extends LegacyWallet { } async refreshAcessToken() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.post('/auth?type=refresh_token', { body: { refresh_token: this.refresh_token }, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, @@ -321,6 +338,7 @@ export class LightningCustodianWallet extends LegacyWallet { } async fetchBtcAddress() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.get('/getbtc', { headers: { 'Access-Control-Allow-Origin': '*', @@ -360,12 +378,9 @@ export class LightningCustodianWallet extends LegacyWallet { } getTransactions() { - let txs = []; - this.pending_transactions_raw = this.pending_transactions_raw || []; - this.user_invoices_raw = this.user_invoices_raw || []; - this.transactions_raw = this.transactions_raw || []; + let txs: any = []; txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned - // transforming to how wallets/list screen expects it + for (const tx of txs) { tx.walletID = this.getID(); if (tx.amount) { @@ -378,6 +393,7 @@ export class LightningCustodianWallet extends LegacyWallet { if (typeof tx.amt !== 'undefined' && typeof tx.fee !== 'undefined') { // lnd tx outgoing + // @ts-ignore: fixme wtf? tx.value = parseInt((tx.amt * 1 + tx.fee * 1) * -1, 10); } @@ -399,12 +415,13 @@ export class LightningCustodianWallet extends LegacyWallet { tx.received = new Date(tx.timestamp * 1000).toString(); } - return txs.sort(function (a, b) { + return txs.sort(function (a: { timestamp: number }, b: { timestamp: number }) { return b.timestamp - a.timestamp; }); } async fetchPendingTransactions() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.get('/getpending', { headers: { 'Access-Control-Allow-Origin': '*', @@ -426,6 +443,7 @@ export class LightningCustodianWallet extends LegacyWallet { } async fetchTransactions() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); // TODO: iterate over all available pages const limit = 10; let queryRes = ''; @@ -462,7 +480,8 @@ export class LightningCustodianWallet extends LegacyWallet { return this.balance; } - async fetchBalance(noRetry) { + async fetchBalance(noRetry?: boolean): Promise { + if (!this._api) throw new Error('Internal error: _api is not initialized'); await this.checkLogin(); const response = await this._api.get('/balance', { @@ -490,7 +509,6 @@ export class LightningCustodianWallet extends LegacyWallet { throw new Error('API unexpected response: ' + JSON.stringify(response.body)); } - this.balance_raw = json; this.balance = json.BTC.AvailableBalance; this._lastBalanceFetch = +new Date(); } @@ -511,14 +529,14 @@ export class LightningCustodianWallet extends LegacyWallet { * @param invoice BOLT invoice string * @return {payment_hash: string} */ - decodeInvoice(invoice) { + decodeInvoice(invoice: string) { const { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - const decoded = { + const decoded: any = { destination: payeeNodeKey, num_satoshis: satoshis ? satoshis.toString() : '0', num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', - timestamp: timestamp.toString(), + timestamp: timestamp?.toString() ?? '0', fallback_addr: '', route_hints: [], }; @@ -554,6 +572,7 @@ export class LightningCustodianWallet extends LegacyWallet { } async fetchInfo() { + if (!this._api) throw new Error('Internal error: _api is not initialized'); const response = await this._api.get('/getinfo', { headers: { 'Access-Control-Allow-Origin': '*', @@ -574,10 +593,9 @@ export class LightningCustodianWallet extends LegacyWallet { if (!json.identity_pubkey) { throw new Error('API unexpected response: ' + JSON.stringify(response.body)); } - this.info_raw = json; } - static async isValidNodeAddress(address) { + static async isValidNodeAddress(address: string) { const apiCall = new Frisbee({ baseURI: address, }); @@ -622,7 +640,8 @@ export class LightningCustodianWallet extends LegacyWallet { * @param invoice BOLT invoice string * @return {Promise.} */ - async decodeInvoiceRemote(invoice) { + async decodeInvoiceRemote(invoice: string) { + if (!this._api) throw new Error('Internal error: _api is not initialized'); await this.checkLogin(); const response = await this._api.get('/decodeinvoice?invoice=' + invoice, { @@ -649,7 +668,7 @@ export class LightningCustodianWallet extends LegacyWallet { return (this.decoded_invoice_raw = json); } - weOwnTransaction(txid) { + weOwnTransaction(txid: string) { for (const tx of this.getTransactions()) { if (tx && tx.payment_hash && tx.payment_hash === txid) return true; } @@ -657,7 +676,7 @@ export class LightningCustodianWallet extends LegacyWallet { return false; } - authenticate(lnurl) { + authenticate(lnurl: any) { return lnurl.authenticate(this.secret); } } diff --git a/class/wallets/lightning-ldk-wallet.ts b/class/wallets/lightning-ldk-wallet.ts index 188052348..aab484a55 100644 --- a/class/wallets/lightning-ldk-wallet.ts +++ b/class/wallets/lightning-ldk-wallet.ts @@ -1,19 +1,24 @@ -import RNFS from 'react-native-fs'; -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; -import RnLdk from 'rn-ldk/src/index'; -import { LightningCustodianWallet } from './lightning-custodian-wallet'; -import SyncedAsyncStorage from '../synced-async-storage'; -import { randomBytes } from '../rng'; import * as bip39 from 'bip39'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import * as bitcoin from 'bitcoinjs-lib'; import bolt11 from 'bolt11'; -import { SegwitBech32Wallet } from './segwit-bech32-wallet'; +import RNFS from 'react-native-fs'; +import RnLdk from 'rn-ldk/src/index'; import presentAlert from '../../components/Alert'; -const bitcoin = require('bitcoinjs-lib'); +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; +import { randomBytes } from '../rng'; +import SyncedAsyncStorage from '../synced-async-storage'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import { LightningCustodianWallet } from './lightning-custodian-wallet'; +import { SegwitBech32Wallet } from './segwit-bech32-wallet'; export class LightningLdkWallet extends LightningCustodianWallet { - static type = 'lightningLdk'; - static typeReadable = 'Lightning LDK'; + static readonly type = 'lightningLdk'; + static readonly typeReadable = 'Lightning LDK'; + // @ts-ignore: override + public readonly type = LightningLdkWallet.type; + // @ts-ignore: override + public readonly typeReadable = LightningLdkWallet.typeReadable; + private _listChannels: any[] = []; private _listPayments: any[] = []; private _listInvoices: any[] = []; @@ -44,8 +49,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { return pubkeyHex; } - constructor(props?: any) { - super(props); + constructor() { + super(); this.preferredBalanceUnit = BitcoinUnit.SATS; this.chain = Chain.OFFCHAIN; this.user_invoices_raw = []; // compatibility with other lightning wallet class @@ -202,8 +207,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { } } - getAddress() { - return undefined; + getAddress(): string | false { + return false; } getSecret() { @@ -366,7 +371,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { } } - async getUserInvoices(limit = false) { + async getUserInvoices(limit: number | false = false) { const newInvoices: any[] = []; let found = false; @@ -433,7 +438,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { return paymentRequest; } - async getAddressAsync() { + async getAddressAsync(): Promise { throw new Error('getAddressAsync: Not implemented'); } diff --git a/class/wallets/multisig-hd-wallet.ts b/class/wallets/multisig-hd-wallet.ts index c2dac5172..968741ca6 100644 --- a/class/wallets/multisig-hd-wallet.ts +++ b/class/wallets/multisig-hd-wallet.ts @@ -55,8 +55,12 @@ const electrumStandart = (passphrase?: string): SeedOpts => ({ const ELECTRUM_SEED_PREFIX = 'electrumseed:'; export class MultisigHDWallet extends AbstractHDElectrumWallet { - static type = 'HDmultisig'; - static typeReadable = 'Multisig Vault'; + static readonly type = 'HDmultisig'; + static readonly typeReadable = 'Multisig Vault'; + // @ts-ignore: override + public readonly type = MultisigHDWallet.type; + // @ts-ignore: override + public readonly typeReadable = MultisigHDWallet.typeReadable; static FORMAT_P2WSH = 'p2wsh'; static FORMAT_P2SH_P2WSH = 'p2sh-p2wsh'; diff --git a/class/wallets/segwit-bech32-wallet.ts b/class/wallets/segwit-bech32-wallet.ts index c1c66c5c7..44b5c34d7 100644 --- a/class/wallets/segwit-bech32-wallet.ts +++ b/class/wallets/segwit-bech32-wallet.ts @@ -8,9 +8,13 @@ import { CoinSelectTarget } from 'coinselect'; const ECPair = ECPairFactory(ecc); export class SegwitBech32Wallet extends LegacyWallet { - static type = 'segwitBech32'; - static typeReadable = 'P2 WPKH'; - static segwitType = 'p2wpkh'; + static readonly type = 'segwitBech32'; + static readonly typeReadable = 'P2 WPKH'; + // @ts-ignore: override + public readonly type = SegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = SegwitBech32Wallet.typeReadable; + public readonly segwitType = 'p2wpkh'; getAddress(): string | false { if (this._address) return this._address; diff --git a/class/wallets/segwit-p2sh-wallet.ts b/class/wallets/segwit-p2sh-wallet.ts index 9628b1370..2e95724c6 100644 --- a/class/wallets/segwit-p2sh-wallet.ts +++ b/class/wallets/segwit-p2sh-wallet.ts @@ -21,9 +21,13 @@ function pubkeyToP2shSegwitAddress(pubkey: Buffer): string | false { } export class SegwitP2SHWallet extends LegacyWallet { - static type = 'segwitP2SH'; - static typeReadable = 'SegWit (P2SH)'; - static segwitType = 'p2sh(p2wpkh)'; + static readonly type = 'segwitP2SH'; + static readonly typeReadable = 'SegWit (P2SH)'; + // @ts-ignore: override + public readonly type = SegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SegwitP2SHWallet.typeReadable; + public readonly segwitType = 'p2sh(p2wpkh)'; static witnessToAddress(witness: string): string | false { try { diff --git a/class/wallets/slip39-wallets.js b/class/wallets/slip39-wallets.ts similarity index 57% rename from class/wallets/slip39-wallets.js rename to class/wallets/slip39-wallets.ts index a8672ec1a..adaba1070 100644 --- a/class/wallets/slip39-wallets.js +++ b/class/wallets/slip39-wallets.ts @@ -6,25 +6,32 @@ import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +type TWalletThis = Omit & { + secret: string[]; +}; + // collection of SLIP39 functions const SLIP39Mixin = { _getSeed() { - const master = slip39.recoverSecret(this.secret, this.passphrase); + const self = this as unknown as TWalletThis; + const master = slip39.recoverSecret(self.secret, self.passphrase); return Buffer.from(master); }, validateMnemonic() { - if (!this.secret.every(m => slip39.validateMnemonic(m))) return false; + const self = this as unknown as TWalletThis; + if (!self.secret.every(m => slip39.validateMnemonic(m))) return false; try { - slip39.recoverSecret(this.secret); + slip39.recoverSecret(self.secret); } catch (e) { return false; } return true; }, - setSecret(newSecret) { + setSecret(newSecret: string) { + const self = this as unknown as TWalletThis; // Try to match words to the default slip39 wordlist and complete partial words const lookupMap = WORD_LIST.reduce((map, word) => { const prefix3 = word.substr(0, 3); @@ -36,7 +43,7 @@ const SLIP39Mixin = { return map; }, new Map()); - this.secret = newSecret + self.secret = newSecret .trim() .split('\n') .filter(s => s) @@ -54,18 +61,23 @@ const SLIP39Mixin = { return secret; }); - return this; + return self; }, getID() { - const string2hash = this.secret.sort().join(',') + (this.getPassphrase() || ''); + const self = this as unknown as TWalletThis; + const string2hash = self.secret.sort().join(',') + (self.getPassphrase() || ''); return createHash('sha256').update(string2hash).digest().toString('hex'); }, }; export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { - static type = 'SLIP39legacyP2PKH'; - static typeReadable = 'SLIP39 Legacy (P2PKH)'; + static readonly type = 'SLIP39legacyP2PKH'; + static readonly typeReadable = 'SLIP39 Legacy (P2PKH)'; + // @ts-ignore: override + public readonly type = SLIP39LegacyP2PKHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39LegacyP2PKHWallet.typeReadable; allowBIP47() { return false; @@ -73,23 +85,33 @@ export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet { _getSeed = SLIP39Mixin._getSeed; validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch setSecret = SLIP39Mixin.setSecret; getID = SLIP39Mixin.getID; } export class SLIP39SegwitP2SHWallet extends HDSegwitP2SHWallet { - static type = 'SLIP39segwitP2SH'; - static typeReadable = 'SLIP39 SegWit (P2SH)'; + static readonly type = 'SLIP39segwitP2SH'; + static readonly typeReadable = 'SLIP39 SegWit (P2SH)'; + // @ts-ignore: override + public readonly type = SLIP39SegwitP2SHWallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39SegwitP2SHWallet.typeReadable; _getSeed = SLIP39Mixin._getSeed; validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch setSecret = SLIP39Mixin.setSecret; getID = SLIP39Mixin.getID; } export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { - static type = 'SLIP39segwitBech32'; - static typeReadable = 'SLIP39 SegWit (Bech32)'; + static readonly type = 'SLIP39segwitBech32'; + static readonly typeReadable = 'SLIP39 SegWit (Bech32)'; + // @ts-ignore: override + public readonly type = SLIP39SegwitBech32Wallet.type; + // @ts-ignore: override + public readonly typeReadable = SLIP39SegwitBech32Wallet.typeReadable; allowBIP47() { return false; @@ -97,6 +119,7 @@ export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet { _getSeed = SLIP39Mixin._getSeed; validateMnemonic = SLIP39Mixin.validateMnemonic; + // @ts-ignore: this type mismatch setSecret = SLIP39Mixin.setSecret; getID = SLIP39Mixin.getID; } diff --git a/class/wallets/taproot-wallet.ts b/class/wallets/taproot-wallet.ts index 15531d32b..8f44df890 100644 --- a/class/wallets/taproot-wallet.ts +++ b/class/wallets/taproot-wallet.ts @@ -1,10 +1,14 @@ +import * as bitcoin from 'bitcoinjs-lib'; import { SegwitBech32Wallet } from './segwit-bech32-wallet'; -const bitcoin = require('bitcoinjs-lib'); export class TaprootWallet extends SegwitBech32Wallet { - static type = 'taproot'; - static typeReadable = 'P2 TR'; - static segwitType = 'p2wpkh'; + static readonly type = 'taproot'; + static readonly typeReadable = 'P2 TR'; + // @ts-ignore: override + public readonly type = TaprootWallet.type; + // @ts-ignore: override + public readonly typeReadable = TaprootWallet.typeReadable; + public readonly segwitType = 'p2wpkh'; /** * Converts script pub key to a Taproot address if it can. Returns FALSE if it cant. @@ -12,7 +16,7 @@ export class TaprootWallet extends SegwitBech32Wallet { * @param scriptPubKey * @returns {boolean|string} Either bech32 address or false */ - static scriptPubKeyToAddress(scriptPubKey: string) { + static scriptPubKeyToAddress(scriptPubKey: string): string | false { try { const publicKey = Buffer.from(scriptPubKey, 'hex'); return bitcoin.address.fromOutputScript(publicKey, bitcoin.networks.bitcoin); diff --git a/class/wallets/types.ts b/class/wallets/types.ts index fdf56caf1..a5ac86dab 100644 --- a/class/wallets/types.ts +++ b/class/wallets/types.ts @@ -114,3 +114,5 @@ export type TWallet = | SegwitBech32Wallet | SegwitP2SHWallet | WatchOnlyWallet; + +export type THDWalletForWatchOnly = HDSegwitBech32Wallet | HDSegwitP2SHWallet | HDLegacyP2PKHWallet; diff --git a/class/wallets/watch-only-wallet.js b/class/wallets/watch-only-wallet.ts similarity index 80% rename from class/wallets/watch-only-wallet.js rename to class/wallets/watch-only-wallet.ts index a30d846c2..a1730f263 100644 --- a/class/wallets/watch-only-wallet.js +++ b/class/wallets/watch-only-wallet.ts @@ -1,22 +1,26 @@ -import { LegacyWallet } from './legacy-wallet'; -import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import BIP32Factory from 'bip32'; +import * as bitcoin from 'bitcoinjs-lib'; +import ecc from '../../blue_modules/noble_ecc'; +import { AbstractWallet } from './abstract-wallet'; import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; -import BIP32Factory from 'bip32'; -import ecc from '../../blue_modules/noble_ecc'; +import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet'; +import { LegacyWallet } from './legacy-wallet'; +import { THDWalletForWatchOnly } from './types'; -const bitcoin = require('bitcoinjs-lib'); const bip32 = BIP32Factory(ecc); export class WatchOnlyWallet extends LegacyWallet { - static type = 'watchOnly'; - static typeReadable = 'Watch-only'; + static readonly type = 'watchOnly'; + static readonly typeReadable = 'Watch-only'; + // @ts-ignore: override + public readonly type = WatchOnlyWallet.type; + // @ts-ignore: override + public readonly typeReadable = WatchOnlyWallet.typeReadable; - constructor() { - super(); - this.use_with_hardware_wallet = false; - this.masterFingerprint = false; - } + public _hdWalletInstance?: THDWalletForWatchOnly; + use_with_hardware_wallet = false; + masterFingerprint: number = 0; /** * @inheritDoc @@ -37,7 +41,7 @@ export class WatchOnlyWallet extends LegacyWallet { } allowSend() { - return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance.allowSend(); + return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance!.allowSend(); } allowSignVerifyMessage() { @@ -65,11 +69,9 @@ export class WatchOnlyWallet extends LegacyWallet { * this method creates appropriate HD wallet class, depending on whether we have xpub, ypub or zpub * as a property of `this`, and in case such property exists - it recreates it and copies data from old one. * this is needed after serialization/save/load/deserialization procedure. - * - * @return {WatchOnlyWallet} this */ init() { - let hdWalletInstance; + let hdWalletInstance: THDWalletForWatchOnly; if (this.secret.startsWith('xpub')) hdWalletInstance = new HDLegacyP2PKHWallet(); else if (this.secret.startsWith('ypub')) hdWalletInstance = new HDSegwitP2SHWallet(); else if (this.secret.startsWith('zpub')) hdWalletInstance = new HDSegwitBech32Wallet(); @@ -84,6 +86,7 @@ export class WatchOnlyWallet extends LegacyWallet { if (this._hdWalletInstance) { // now, porting all properties from old object to new one for (const k of Object.keys(this._hdWalletInstance)) { + // @ts-ignore: JS magic here hdWalletInstance[k] = this._hdWalletInstance[k]; } @@ -117,6 +120,7 @@ export class WatchOnlyWallet extends LegacyWallet { async fetchBalance() { if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { if (!this._hdWalletInstance) this.init(); + if (!this._hdWalletInstance) throw new Error('Internal error: _hdWalletInstance is not initialized'); return this._hdWalletInstance.fetchBalance(); } else { // return LegacyWallet.prototype.fetchBalance.call(this); @@ -127,6 +131,7 @@ export class WatchOnlyWallet extends LegacyWallet { async fetchTransactions() { if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) { if (!this._hdWalletInstance) this.init(); + if (!this._hdWalletInstance) throw new Error('Internal error: _hdWalletInstance is not initialized'); return this._hdWalletInstance.fetchTransactions(); } else { // return LegacyWallet.prototype.fetchBalance.call(this); @@ -134,18 +139,18 @@ export class WatchOnlyWallet extends LegacyWallet { } } - async getAddressAsync() { + async getAddressAsync(): Promise { if (this.isAddressValid(this.secret)) return new Promise(resolve => resolve(this.secret)); if (this._hdWalletInstance) return this._hdWalletInstance.getAddressAsync(); throw new Error('Not initialized'); } - _getExternalAddressByIndex(index) { + _getExternalAddressByIndex(index: number) { if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index); throw new Error('Not initialized'); } - _getInternalAddressByIndex(index) { + _getInternalAddressByIndex(index: number) { if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index); throw new Error('Not initialized'); } @@ -170,29 +175,30 @@ export class WatchOnlyWallet extends LegacyWallet { throw new Error('Not initialized'); } - getUtxo(...args) { + getUtxo(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args); throw new Error('Not initialized'); } - combinePsbt(base64one, base64two) { - if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(base64one, base64two); + combinePsbt(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(...args); throw new Error('Not initialized'); } - broadcastTx(hex) { - if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(hex); + broadcastTx(...args: Parameters) { + if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(...args); throw new Error('Not initialized'); } /** * signature of this method is the same ad BIP84 createTransaction, BUT this method should be used to create * unsinged PSBT to be used with HW wallet (or other external signer) - * @see HDSegwitBech32Wallet.createTransaction */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence) { + createTransaction(...args: Parameters) { + const [utxos, targets, feeRate, changeAddress, sequence] = args; if (this._hdWalletInstance && this.isHd()) { - return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint()); + const masterFingerprint = this.getMasterFingerprint(); + return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, masterFingerprint); } else { throw new Error('Not a HD watch-only wallet, cant create PSBT (or just not initialized)'); } @@ -224,7 +230,7 @@ export class WatchOnlyWallet extends LegacyWallet { return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub'); } - weOwnAddress(address) { + weOwnAddress(address: string) { if (this.isHd()) { if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address); throw new Error('Not initialized'); @@ -243,7 +249,7 @@ export class WatchOnlyWallet extends LegacyWallet { return !!this.use_with_hardware_wallet; } - setUseWithHardwareWalletEnabled(enabled) { + setUseWithHardwareWalletEnabled(enabled: boolean) { this.use_with_hardware_wallet = !!enabled; } @@ -262,7 +268,7 @@ export class WatchOnlyWallet extends LegacyWallet { if (this.secret.startsWith('zpub')) { xpub = this._zpubToXpub(this.secret); } else if (this.secret.startsWith('ypub')) { - xpub = this.constructor._ypubToXpub(this.secret); + xpub = AbstractWallet._ypubToXpub(this.secret); } else { xpub = this.secret; } @@ -275,32 +281,32 @@ export class WatchOnlyWallet extends LegacyWallet { return false; } - addressIsChange(...args) { + addressIsChange(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args); return super.addressIsChange(...args); } - getUTXOMetadata(...args) { + getUTXOMetadata(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args); return super.getUTXOMetadata(...args); } - setUTXOMetadata(...args) { + setUTXOMetadata(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args); return super.setUTXOMetadata(...args); } - getDerivationPath(...args) { + getDerivationPath(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.getDerivationPath(...args); throw new Error("Not a HD watch-only wallet, can't use derivation path"); } - setDerivationPath(...args) { + setDerivationPath(...args: Parameters) { if (this._hdWalletInstance) return this._hdWalletInstance.setDerivationPath(...args); throw new Error("Not a HD watch-only wallet, can't use derivation path"); } - isSegwit() { + isSegwit(): boolean { if (this._hdWalletInstance) return this._hdWalletInstance.isSegwit(); return super.isSegwit(); } diff --git a/components/TransactionsNavigationHeader.tsx b/components/TransactionsNavigationHeader.tsx index 85cbc0a41..5fd61c944 100644 --- a/components/TransactionsNavigationHeader.tsx +++ b/components/TransactionsNavigationHeader.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useContext, useCallback, useMemo } import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import LinearGradient from 'react-native-linear-gradient'; -import { AbstractWallet, HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; +import { HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; import { BitcoinUnit } from '../models/bitcoinUnits'; import WalletGradient from '../class/wallet-gradient'; import Biometric from '../class/biometrics'; @@ -11,9 +11,10 @@ import { BlueStorageContext } from '../blue_modules/storage-context'; import ToolTipMenu from './TooltipMenu'; import { BluePrivateBalance } from '../BlueComponents'; import { FiatUnit } from '../models/fiatUnit'; +import { TWallet } from '../class/wallets/types'; interface TransactionsNavigationHeaderProps { - wallet: AbstractWallet; + wallet: TWallet; onWalletUnitChange?: (wallet: any) => void; navigation: { navigate: (route: string, params?: any) => void; @@ -71,7 +72,7 @@ const TransactionsNavigationHeader: React.FC } }; - const updateWalletVisibility = (w: AbstractWallet, newHideBalance: boolean) => { + const updateWalletVisibility = (w: TWallet, newHideBalance: boolean) => { w.hideBalance = newHideBalance; return w; }; @@ -90,7 +91,7 @@ const TransactionsNavigationHeader: React.FC saveToDisk(); }; - const updateWalletWithNewUnit = (w: AbstractWallet, newPreferredUnit: BitcoinUnit) => { + const updateWalletWithNewUnit = (w: TWallet, newPreferredUnit: BitcoinUnit) => { w.preferredBalanceUnit = newPreferredUnit; return w; }; diff --git a/components/WalletsCarousel.js b/components/WalletsCarousel.js index 8657f7da7..c77d73cd9 100644 --- a/components/WalletsCarousel.js +++ b/components/WalletsCarousel.js @@ -20,7 +20,7 @@ import loc, { formatBalance, transactionTimeToReadable } from '../loc'; import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class'; import WalletGradient from '../class/wallet-gradient'; import { BluePrivateBalance } from '../BlueComponents'; -import { BlueStorageContext } from '../blue_modules/storage-context'; +import { BlueStorageContext, WalletTransactionsStatus } from '../blue_modules/storage-context'; import { isTablet, isDesktop } from '../blue_modules/environment'; import { useTheme } from './themes'; @@ -180,7 +180,7 @@ export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelect } const latestTransactionText = - walletTransactionUpdateStatus === true || walletTransactionUpdateStatus === item.getID() + walletTransactionUpdateStatus === WalletTransactionsStatus.ALL || walletTransactionUpdateStatus === item.getID() ? loc.transactions.updating : item.getBalance() !== 0 && item.getLatestTransactionTime() === 0 ? loc.wallets.pull_to_refresh diff --git a/components/addresses/AddressItem.tsx b/components/addresses/AddressItem.tsx index b671a7716..092c08110 100644 --- a/components/addresses/AddressItem.tsx +++ b/components/addresses/AddressItem.tsx @@ -11,7 +11,6 @@ import Share from 'react-native-share'; import { useTheme } from '../themes'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import { AbstractWallet } from '../../class'; import Biometric from '../../class/biometrics'; import presentAlert from '../Alert'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; @@ -91,7 +90,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad }; const handleCopyPrivkeyPress = () => { - const wallet = wallets.find((w: AbstractWallet) => w.getID() === walletID); + const wallet = wallets.find(w => w.getID() === walletID); if (!wallet) { presentAlert({ message: 'Internal error: cant find wallet' }); return; diff --git a/models/fiatUnit.ts b/models/fiatUnit.ts index 846fffac8..90d93c9e3 100644 --- a/models/fiatUnit.ts +++ b/models/fiatUnit.ts @@ -164,15 +164,18 @@ const RateExtractors = { }, } as const; -type FiatUnit = { - [key: string]: { - endPointKey: string; - symbol: string; - locale: string; - source: 'CoinDesk' | 'Yadio' | 'Exir' | 'wazirx' | 'Bitstamp'; - }; +export type TFiatUnit = { + endPointKey: string; + symbol: string; + locale: string; + source: 'CoinDesk' | 'Yadio' | 'Exir' | 'wazirx' | 'Bitstamp'; }; -export const FiatUnit = untypedFiatUnit as FiatUnit; + +export type TFiatUnits = { + [key: string]: TFiatUnit; +}; + +export const FiatUnit = untypedFiatUnit as TFiatUnits; export type FiatUnitType = { endPointKey: string; diff --git a/screen/lnd/ldkInfo.tsx b/screen/lnd/ldkInfo.tsx index fa3dfef6b..ed95f9ae2 100644 --- a/screen/lnd/ldkInfo.tsx +++ b/screen/lnd/ldkInfo.tsx @@ -34,7 +34,7 @@ const LdkInfo = () => { const { wallets } = useContext(BlueStorageContext); const refreshDataInterval = useRef(); const sectionList = useRef(); - const wallet: LightningLdkWallet = wallets.find((w: AbstractWallet) => w.getID() === walletID); + const wallet = wallets.find(w => w.getID() === walletID) as LightningLdkWallet; const { colors } = useTheme(); const { setOptions, navigate } = useNavigation(); const name = useRoute().name; @@ -159,15 +159,9 @@ const LdkInfo = () => { if (!(await confirm())) return; setSelectedChannelIndex(undefined); - const wallets2use = wallets.filter((w: AbstractWallet) => w.chain === Chain.ONCHAIN); + const wallets2use = wallets.filter(w => w.chain === Chain.ONCHAIN); - const toWallet: AbstractWallet = await selectWallet( - navigate, - name, - null, - wallets2use, - 'Onchain wallet is required to withdraw funds to', - ); + const toWallet = await selectWallet(navigate, name, null, wallets2use, 'Onchain wallet is required to withdraw funds to'); // using wallets2use instead of simple Chain.ONCHAIN argument because by default this argument only selects wallets // that can send, which is not possible if user wants to withdraw to watch-only wallet if (!toWallet) return; @@ -190,14 +184,8 @@ const LdkInfo = () => { }; const claimBalance = async () => { - const wallets2use = wallets.filter((w: AbstractWallet) => w.chain === Chain.ONCHAIN); - const selectedWallet: AbstractWallet = await selectWallet( - navigate, - name, - null, - wallets2use, - 'Onchain wallet is required to withdraw funds to', - ); + const wallets2use = wallets.filter(w => w.chain === Chain.ONCHAIN); + const selectedWallet = await selectWallet(navigate, name, null, wallets2use, 'Onchain wallet is required to withdraw funds to'); // using wallets2use instead of simple Chain.ONCHAIN argument because by default this argument only selects wallets // that can send, which is not possible if user wants to withdraw to watch-only wallet if (!selectedWallet) return; @@ -316,7 +304,7 @@ const LdkInfo = () => { }; const navigateToOpenChannel = async ({ isPrivateChannel }: { isPrivateChannel: boolean }) => { - const availableWallets = [...wallets.filter((item: AbstractWallet) => item.isSegwit() && item.allowSend())]; + const availableWallets = [...wallets.filter(item => item.isSegwit() && item.allowSend())]; if (availableWallets.length === 0) { return presentAlert({ message: loc.lnd.refill_create }); } diff --git a/screen/lnd/ldkOpenChannel.tsx b/screen/lnd/ldkOpenChannel.tsx index a0e25de9d..9532d1da1 100644 --- a/screen/lnd/ldkOpenChannel.tsx +++ b/screen/lnd/ldkOpenChannel.tsx @@ -9,7 +9,7 @@ import AddressInput from '../../components/AddressInput'; import AmountInput from '../../components/AmountInput'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import loc from '../../loc'; -import { AbstractWallet, HDSegwitBech32Wallet, LightningLdkWallet } from '../../class'; +import { HDSegwitBech32Wallet, LightningLdkWallet } from '../../class'; import { ArrowPicker } from '../../components/ArrowPicker'; import { Psbt } from 'bitcoinjs-lib'; import Biometric from '../../class/biometrics'; @@ -45,8 +45,8 @@ const LdkOpenChannel = (props: any) => { psbt, remoteHostWithPubkey = '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f@52.50.244.44:9735' /* Bitrefill */, } = useRoute().params; - const fundingWallet: HDSegwitBech32Wallet = wallets.find((w: AbstractWallet) => w.getID() === fundingWalletID); - const ldkWallet: LightningLdkWallet = wallets.find((w: AbstractWallet) => w.getID() === ldkWalletID); + const fundingWallet = wallets.find(w => w.getID() === fundingWalletID) as HDSegwitBech32Wallet; + const ldkWallet = wallets.find(w => w.getID() === ldkWalletID) as LightningLdkWallet; const [unit, setUnit] = useState(ldkWallet.getPreferredBalanceUnit()); const [isLoading, setIsLoading] = useState(false); const psbtOpenChannelStartedTs = useRef(); diff --git a/screen/wallets/DrawerList.tsx b/screen/wallets/DrawerList.tsx index a1b8b62b5..f3240c757 100644 --- a/screen/wallets/DrawerList.tsx +++ b/screen/wallets/DrawerList.tsx @@ -8,7 +8,7 @@ import loc from '../../loc'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import { useTheme } from '../../components/themes'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; -import { AbstractWallet } from '../../class'; +import { TWallet } from '../../class/wallets/types'; enum WalletActionType { SetWallets = 'SET_WALLETS', @@ -18,7 +18,7 @@ enum WalletActionType { } interface WalletState { - wallets: AbstractWallet[]; + wallets: TWallet[]; selectedWalletID: string | null; isFocused: boolean; } @@ -46,7 +46,7 @@ interface SetFocusAction { interface SetWalletsAction { type: WalletActionType.SetWallets; - wallets: AbstractWallet[]; + wallets: TWallet[]; } interface SelectWalletAction { @@ -89,7 +89,7 @@ const DrawerList: React.FC = memo(({ navigation }) => { }; const [state, dispatch] = useReducer(walletReducer, initialState); - const walletsCarousel = useRef>(null); + const walletsCarousel = useRef>(null); const { wallets } = useContext(BlueStorageContext); const { colors } = useTheme(); const isFocused = useIsFocused(); @@ -108,7 +108,7 @@ const DrawerList: React.FC = memo(({ navigation }) => { }, [wallets, isFocused]); const handleClick = useCallback( - (item: AbstractWallet) => { + (item: TWallet) => { if (item?.getID) { const walletID = item.getID(); const walletType = item.type; diff --git a/screen/wallets/add.tsx b/screen/wallets/add.tsx index a4a6b1f04..9605b0caa 100644 --- a/screen/wallets/add.tsx +++ b/screen/wallets/add.tsx @@ -13,6 +13,7 @@ import { View, useColorScheme, } from 'react-native'; + import { BitcoinButton, BlueButtonLink, @@ -25,14 +26,7 @@ import { } from '../../BlueComponents'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import { - AbstractWallet, - HDSegwitBech32Wallet, - HDSegwitP2SHWallet, - LightningCustodianWallet, - LightningLdkWallet, - SegwitP2SHWallet, -} from '../../class'; +import { HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, LightningLdkWallet, SegwitP2SHWallet } from '../../class'; import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; import { LdkButton } from '../../components/LdkButton'; @@ -41,8 +35,7 @@ import { useTheme } from '../../components/themes'; import useAsyncPromise from '../../hooks/useAsyncPromise'; import loc from '../../loc'; import { Chain } from '../../models/bitcoinUnits'; -const BlueApp = require('../../BlueApp'); -const AppStorage = BlueApp.AppStorage; +import { AppStorage } from '../../BlueApp'; const A = require('../../blue_modules/analytics'); enum ButtonSelected { @@ -279,7 +272,7 @@ const WalletsAdd: React.FC = () => { }; const createLightningLdkWallet = async () => { - const foundLdk = wallets.find((w: AbstractWallet) => w.type === LightningLdkWallet.type); + const foundLdk = wallets.find(w => w.type === LightningLdkWallet.type); if (foundLdk) { return presentAlert({ message: 'LDK wallet already exists' }); } diff --git a/screen/wallets/paymentCodesList.tsx b/screen/wallets/paymentCodesList.tsx index e24fbab4d..8b795bf84 100644 --- a/screen/wallets/paymentCodesList.tsx +++ b/screen/wallets/paymentCodesList.tsx @@ -4,7 +4,6 @@ import { SectionList, StyleSheet, Text, View } from 'react-native'; import { BlueCopyTextToClipboard } from '../../BlueComponents'; import { PaymentCodeStackParamList } from '../../Navigation'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet'; import loc from '../../loc'; interface DataSection { @@ -22,12 +21,13 @@ export default function PaymentCodesList({ route }: Props) { useEffect(() => { if (!walletID) return; - const foundWallet: AbstractHDElectrumWallet = wallets.find((w: AbstractHDElectrumWallet) => w.getID() === walletID); + const foundWallet = wallets.find(w => w.getID() === walletID); if (!foundWallet) return; const newData: DataSection[] = [ { title: loc.bip47.who_can_pay_me, + // @ts-ignore remove later data: foundWallet.getBIP47SenderPaymentCodes(), }, ]; diff --git a/screen/wallets/pleaseBackup.tsx b/screen/wallets/pleaseBackup.tsx index 67eacaf95..17b726bdf 100644 --- a/screen/wallets/pleaseBackup.tsx +++ b/screen/wallets/pleaseBackup.tsx @@ -3,7 +3,6 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { ActivityIndicator, BackHandler, I18nManager, ScrollView, StyleSheet, Text, View } from 'react-native'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import { AbstractWallet } from '../../class'; import Button from '../../components/Button'; import SafeArea from '../../components/SafeArea'; import { useTheme } from '../../components/themes'; @@ -14,7 +13,7 @@ const PleaseBackup: React.FC = () => { const { wallets } = useContext(BlueStorageContext); const [isLoading, setIsLoading] = useState(true); const { walletID } = useRoute().params as { walletID: string }; - const wallet = wallets.find((w: AbstractWallet) => w.getID() === walletID); + const wallet = wallets.find(w => w.getID() === walletID); const navigation = useNavigation(); const { colors } = useTheme(); const { enableBlur, disableBlur } = usePrivacy(); diff --git a/screen/wallets/viewEditMultisigCosigners.tsx b/screen/wallets/viewEditMultisigCosigners.tsx index 67e9ee2e4..462bcbac3 100644 --- a/screen/wallets/viewEditMultisigCosigners.tsx +++ b/screen/wallets/viewEditMultisigCosigners.tsx @@ -18,6 +18,7 @@ import { findNodeHandle, } from 'react-native'; import { Badge, Icon } from 'react-native-elements'; + import { BlueButtonLink, BlueFormMultiInput, @@ -32,7 +33,7 @@ import { ViewEditMultisigCosignersStackParamsList } from '../../Navigation'; import * as NavigationService from '../../NavigationService'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import { encodeUR } from '../../blue_modules/ur'; -import { AbstractWallet, HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class'; +import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class'; import Biometric from '../../class/biometrics'; import presentAlert from '../../components/Alert'; import BottomModal from '../../components/BottomModal'; @@ -63,7 +64,7 @@ const ViewEditMultisigCosigners = ({ route }: Props) => { const { navigate, goBack, dispatch, addListener } = useNavigation(); const openScannerButtonRef = useRef(); const { walletId } = route.params; - const w = useRef(wallets.find((wallet: AbstractWallet) => wallet.getID() === walletId)); + const w = useRef(wallets.find(wallet => wallet.getID() === walletId)); const tempWallet = useRef(new MultisigHDWallet()); const [wallet, setWallet] = useState(); const [isLoading, setIsLoading] = useState(true); @@ -176,6 +177,9 @@ const ViewEditMultisigCosigners = ({ route }: Props) => { }; const onSave = async () => { + if (!wallet) { + throw new Error('Wallet is undefined'); + } setIsLoading(true); const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); @@ -188,9 +192,9 @@ const ViewEditMultisigCosigners = ({ route }: Props) => { } // eslint-disable-next-line prefer-const - let newWallets = wallets.filter((newWallet: MultisigHDWallet) => { + let newWallets = wallets.filter(newWallet => { return newWallet.getID() !== walletId; - }); + }) as MultisigHDWallet[]; if (!isElectrumDisabled) { await wallet?.fetchBalance(); } diff --git a/screen/wallets/xpub.tsx b/screen/wallets/xpub.tsx index 30f41ae54..ef2e69c2d 100644 --- a/screen/wallets/xpub.tsx +++ b/screen/wallets/xpub.tsx @@ -4,7 +4,6 @@ import { ActivityIndicator, InteractionManager, View } from 'react-native'; import Share from 'react-native-share'; import { BlueCopyTextToClipboard, BlueSpacing20, BlueText } from '../../BlueComponents'; import { BlueStorageContext } from '../../blue_modules/storage-context'; -import { AbstractWallet } from '../../class'; import Biometric from '../../class/biometrics'; import Button from '../../components/Button'; import QRCodeComponent from '../../components/QRCodeComponent'; @@ -26,7 +25,7 @@ const WalletXpub: React.FC = () => { const { wallets } = useContext(BlueStorageContext); const route = useRoute(); const { walletID, xpub } = route.params; - const wallet = wallets.find((w: AbstractWallet) => w.getID() === walletID); + const wallet = wallets.find(w => w.getID() === walletID); const [isLoading, setIsLoading] = useState(true); const [xPubText, setXPubText] = useState(undefined); const navigation = useNavigation>(); @@ -53,7 +52,7 @@ const WalletXpub: React.FC = () => { } const walletXpub = wallet.getXpub(); if (xpub !== walletXpub) { - navigation.setParams({ xpub: walletXpub }); + navigation.setParams({ xpub: walletXpub || undefined }); } setIsLoading(false); diff --git a/scripts/find-unused-loc.js b/scripts/find-unused-loc.js index abf5afcd5..b473234f7 100644 --- a/scripts/find-unused-loc.js +++ b/scripts/find-unused-loc.js @@ -3,7 +3,7 @@ const path = require('path'); const mainLocFile = './loc/en.json'; const dirsToInterate = ['components', 'screen', 'blue_modules', 'class']; -const addFiles = ['BlueComponents.js', 'App.js', 'BlueApp.js', 'Navigation.tsx']; +const addFiles = ['BlueComponents.js', 'App.js', 'BlueApp.ts', 'Navigation.tsx']; const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units']; const allLocKeysHashmap = {}; // loc key -> used or not diff --git a/tests/e2e/bluewallet.spec.js b/tests/e2e/bluewallet.spec.js index 97d2e132a..bee871442 100644 --- a/tests/e2e/bluewallet.spec.js +++ b/tests/e2e/bluewallet.spec.js @@ -461,6 +461,7 @@ describe('BlueWallet UI Tests - no wallets', () => { await helperSwitchAdvancedMode(); await yo('WalletsList'); await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit + await sleep(200); // Wait until bounce animation finishes. // going to Import Wallet screen and importing Vault await element(by.id('CreateAWallet')).tap(); await yo('ActivateVaultButton'); @@ -538,6 +539,7 @@ describe('BlueWallet UI Tests - no wallets', () => { await device.launchApp({ newInstance: true }); await yo('WalletsList'); await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit + await sleep(200); // Wait until bounce animation finishes. // going to Import Wallet screen and importing mnemonic await element(by.id('CreateAWallet')).tap(); await element(by.id('ImportWallet')).tap(); @@ -672,7 +674,7 @@ describe('BlueWallet UI Tests - no wallets', () => { await helperSwitchAdvancedMode(); await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit - await sleep(100); // wait swipe animation to finish + await sleep(200); // Wait until bounce animation finishes. // going to Import Wallet screen and importing mnemonic await element(by.id('CreateAWallet')).tap(); await element(by.id('ScrollView')).swipe('up', 'fast', 0.9); // in case emu screen is small and it doesnt fit diff --git a/tests/e2e/helperz.js b/tests/e2e/helperz.js index edb72b7a6..b8304c443 100644 --- a/tests/e2e/helperz.js +++ b/tests/e2e/helperz.js @@ -16,6 +16,7 @@ export async function helperImportWallet(importText, walletType, expectedWalletL await yo('WalletsList'); await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit + await sleep(200); // Wait until bounce animation finishes. // going to Import Wallet screen and importing mnemonic await element(by.id('CreateAWallet')).tap(); await element(by.id('ImportWallet')).tap(); @@ -114,6 +115,7 @@ export const expectToBeVisible = async id => { export async function helperCreateWallet(walletName) { await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit + await sleep(200); // Wait until bounce animation finishes. await element(by.id('CreateAWallet')).tap(); await element(by.id('WalletNameInput')).replaceText(walletName || 'cr34t3d'); await yo('ActivateBitcoinButton'); diff --git a/tests/unit/watch-only-wallet.test.js b/tests/unit/watch-only-wallet.test.js index fb9839fdf..ea3e28fd0 100644 --- a/tests/unit/watch-only-wallet.test.js +++ b/tests/unit/watch-only-wallet.test.js @@ -33,7 +33,7 @@ describe('Watch only wallet', () => { w.setSecret(secret); assert.ok(w.valid()); assert.strictEqual(w.isHd(), true); - assert.strictEqual(w.getMasterFingerprint(), false); + assert.strictEqual(w.getMasterFingerprint(), 0); assert.strictEqual(w.getMasterFingerprintHex(), '00000000'); assert.ok(w.isXpubValid(), w.secret); assert.ok(!w.useWithHardwareWalletEnabled()); diff --git a/typings/globals.d.ts b/typings/globals.d.ts index ac9f899d7..3ed1651e5 100644 --- a/typings/globals.d.ts +++ b/typings/globals.d.ts @@ -1 +1,3 @@ declare function alert(message: string): void; + +declare const navigator: undefined | { product: 'ReactNative' }; diff --git a/typings/slip39.d.ts b/typings/slip39.d.ts new file mode 100644 index 000000000..bfd2628d0 --- /dev/null +++ b/typings/slip39.d.ts @@ -0,0 +1,8 @@ +declare module 'slip39' { + export function recoverSecret(secret: string[], passphrase?: string): Buffer; + export function validateMnemonic(mnemonic: string): boolean; +} + +declare module 'slip39/dist/slip39_helper' { + export const WORD_LIST: string[]; +}