mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
feat: BlueApp TS
This commit is contained in:
parent
52ff33ad2b
commit
e1c99f19b4
@ -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.<AbstractWallet>} */
|
||||
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<any>|Promise<any> | Promise<void> | * | Promise | void}
|
||||
*/
|
||||
setItem = (key, value) => {
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
setItem = (key: string, value: any): Promise<any> => {
|
||||
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<any>|*}
|
||||
*/
|
||||
getItem = key => {
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
getItem = (key: string): Promise<any> => {
|
||||
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<any | null> => {
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
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<void> => {
|
||||
// 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.<boolean>} Success or failure
|
||||
*/
|
||||
createFakeStorage = async fakePassword => {
|
||||
createFakeStorage = async (fakePassword: string): Promise<boolean> => {
|
||||
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<Realm>}
|
||||
*/
|
||||
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<Realm>}
|
||||
*/
|
||||
async openRealmKeyValue() {
|
||||
async openRealmKeyValue(): Promise<Realm> {
|
||||
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.<boolean>}
|
||||
*/
|
||||
async loadFromDisk(password) {
|
||||
async loadFromDisk(password?: string): Promise<boolean> {
|
||||
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<void> {
|
||||
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.<void>}
|
||||
*/
|
||||
fetchWalletBalances = async index => {
|
||||
fetchWalletBalances = async (index?: number): Promise<void> => {
|
||||
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.<void>}
|
||||
*/
|
||||
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.<AbstractWallet>}
|
||||
*/
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
try {
|
||||
return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY));
|
||||
} catch (_) {}
|
||||
return false;
|
||||
};
|
||||
|
||||
setIsHandoffEnabled = async value => {
|
||||
setIsHandoffEnabled = async (value: boolean): Promise<void> => {
|
||||
await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : '');
|
||||
};
|
||||
|
||||
isDoNotTrackEnabled = async () => {
|
||||
isDoNotTrackEnabled = async (): Promise<boolean> => {
|
||||
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<*> | Promise<*>>}
|
||||
*/
|
||||
sleep = ms => {
|
||||
sleep = (ms: number): Promise<void> => {
|
||||
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<boolean> => {
|
||||
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;
|
@ -11,7 +11,6 @@ const getIsTorCapable = (): boolean => {
|
||||
} else if (isDesktop) {
|
||||
capable = false;
|
||||
}
|
||||
console.log('getIsTorCapable', capable);
|
||||
return capable;
|
||||
};
|
||||
|
||||
|
@ -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<void>;
|
||||
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<void>;
|
||||
fetchAndSaveWalletTransactions: (walletID: string) => Promise<void>;
|
||||
walletsInitialized: boolean;
|
||||
setWalletsInitialized: (initialized: boolean) => void;
|
||||
refreshAllWalletTransactions: (lastSnappedTo?: number, showUpdateStatusIndicator?: boolean) => Promise<void>;
|
||||
resetWallets: () => void;
|
||||
setPreferredFiatCurrency: () => void;
|
||||
preferredFiatCurrency: TFiatUnit;
|
||||
setLanguage: () => void;
|
||||
language: string | undefined;
|
||||
isHandOffUseEnabled: boolean;
|
||||
setIsHandOffUseEnabledAsyncStorage: (value: boolean) => Promise<void>;
|
||||
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<BlueStorageContextType>(undefined);
|
||||
export const BlueStorageProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [wallets, setWallets] = useState<TWallet[]>([]);
|
||||
const [selectedWalletID, setSelectedWalletID] = useState<undefined | string>();
|
||||
const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState<WalletTransactionsStatus | string>(
|
||||
WalletTransactionsStatus.NONE,
|
||||
);
|
||||
const [walletsInitialized, setWalletsInitialized] = useState<boolean>(false);
|
||||
const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState<TFiatUnit>(FiatUnit.USD);
|
||||
const [language, _setLanguage] = useState<string | undefined>();
|
||||
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState<boolean>(false);
|
||||
const [isElectrumDisabled, setIsElectrumDisabled] = useState<boolean>(true);
|
||||
const [isPrivacyBlurEnabled, setIsPrivacyBlurEnabled] = useState<boolean>(true);
|
||||
const [currentSharedCosigner, setCurrentSharedCosigner] = useState<string>('');
|
||||
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 (
|
||||
<BlueStorageContext.Provider
|
||||
value={{
|
||||
wallets,
|
||||
setWalletsWithNewOrder,
|
||||
txMetadata,
|
||||
saveToDisk,
|
||||
getTransactions,
|
||||
selectedWalletID,
|
||||
setSelectedWalletID,
|
||||
addWallet,
|
||||
deleteWallet,
|
||||
currentSharedCosigner,
|
||||
setSharedCosigner,
|
||||
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,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BlueStorageContext.Provider>
|
||||
);
|
||||
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 <BlueStorageContext.Provider value={value}>{children}</BlueStorageContext.Provider>;
|
||||
};
|
@ -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<AbstractWallet | boolean> {
|
||||
let selectedWallet: AbstractWallet | false = false;
|
||||
static async getSelectedDefaultWallet(): Promise<TWallet | undefined> {
|
||||
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;
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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<number, BalanceByIndex>;
|
||||
_balances_by_internal_index: Record<number, BalanceByIndex>;
|
||||
|
@ -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;
|
||||
|
@ -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<string, UtxoMetadata>;
|
||||
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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<string, ElectrumHistory[]>;
|
||||
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]);
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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[] = [];
|
||||
|
@ -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<void> {
|
||||
// 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.<Array>}
|
||||
*/
|
||||
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.<void>}
|
||||
*/
|
||||
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<void> {
|
||||
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.<Object>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -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<string> {
|
||||
throw new Error('getAddressAsync: Not implemented');
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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<HDLegacyP2PKHWallet | HDSegwitP2SHWallet | HDSegwitBech32Wallet, 'secret'> & {
|
||||
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;
|
||||
}
|
@ -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);
|
||||
|
@ -114,3 +114,5 @@ export type TWallet =
|
||||
| SegwitBech32Wallet
|
||||
| SegwitP2SHWallet
|
||||
| WatchOnlyWallet;
|
||||
|
||||
export type THDWalletForWatchOnly = HDSegwitBech32Wallet | HDSegwitP2SHWallet | HDLegacyP2PKHWallet;
|
||||
|
@ -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<string> {
|
||||
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<THDWalletForWatchOnly['getUtxo']>) {
|
||||
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<THDWalletForWatchOnly['combinePsbt']>) {
|
||||
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<THDWalletForWatchOnly['broadcastTx']>) {
|
||||
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<THDWalletForWatchOnly['createTransaction']>) {
|
||||
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<THDWalletForWatchOnly['addressIsChange']>) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args);
|
||||
return super.addressIsChange(...args);
|
||||
}
|
||||
|
||||
getUTXOMetadata(...args) {
|
||||
getUTXOMetadata(...args: Parameters<THDWalletForWatchOnly['getUTXOMetadata']>) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args);
|
||||
return super.getUTXOMetadata(...args);
|
||||
}
|
||||
|
||||
setUTXOMetadata(...args) {
|
||||
setUTXOMetadata(...args: Parameters<THDWalletForWatchOnly['setUTXOMetadata']>) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args);
|
||||
return super.setUTXOMetadata(...args);
|
||||
}
|
||||
|
||||
getDerivationPath(...args) {
|
||||
getDerivationPath(...args: Parameters<THDWalletForWatchOnly['getDerivationPath']>) {
|
||||
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<THDWalletForWatchOnly['setDerivationPath']>) {
|
||||
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();
|
||||
}
|
@ -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<TransactionsNavigationHeaderProps>
|
||||
}
|
||||
};
|
||||
|
||||
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<TransactionsNavigationHeaderProps>
|
||||
saveToDisk();
|
||||
};
|
||||
|
||||
const updateWalletWithNewUnit = (w: AbstractWallet, newPreferredUnit: BitcoinUnit) => {
|
||||
const updateWalletWithNewUnit = (w: TWallet, newPreferredUnit: BitcoinUnit) => {
|
||||
w.preferredBalanceUnit = newPreferredUnit;
|
||||
return w;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -34,7 +34,7 @@ const LdkInfo = () => {
|
||||
const { wallets } = useContext(BlueStorageContext);
|
||||
const refreshDataInterval = useRef<NodeJS.Timer>();
|
||||
const sectionList = useRef<SectionList | null>();
|
||||
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 });
|
||||
}
|
||||
|
@ -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<LdkOpenChannelProps>().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<BitcoinUnit | string>(ldkWallet.getPreferredBalanceUnit());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const psbtOpenChannelStartedTs = useRef<number>();
|
||||
|
@ -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<DrawerListProps> = memo(({ navigation }) => {
|
||||
};
|
||||
|
||||
const [state, dispatch] = useReducer(walletReducer, initialState);
|
||||
const walletsCarousel = useRef<FlatList<AbstractWallet>>(null);
|
||||
const walletsCarousel = useRef<FlatList<TWallet>>(null);
|
||||
const { wallets } = useContext(BlueStorageContext);
|
||||
const { colors } = useTheme();
|
||||
const isFocused = useIsFocused();
|
||||
@ -108,7 +108,7 @@ const DrawerList: React.FC<DrawerListProps> = memo(({ navigation }) => {
|
||||
}, [wallets, isFocused]);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(item: AbstractWallet) => {
|
||||
(item: TWallet) => {
|
||||
if (item?.getID) {
|
||||
const walletID = item.getID();
|
||||
const walletType = item.type;
|
||||
|
@ -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' });
|
||||
}
|
||||
|
@ -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(),
|
||||
},
|
||||
];
|
||||
|
@ -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();
|
||||
|
@ -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<MultisigHDWallet>();
|
||||
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();
|
||||
}
|
||||
|
@ -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<WalletXpubRouteProp>();
|
||||
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<boolean>(true);
|
||||
const [xPubText, setXPubText] = useState<string | undefined>(undefined);
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList, 'WalletXpub'>>();
|
||||
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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());
|
||||
|
2
typings/globals.d.ts
vendored
2
typings/globals.d.ts
vendored
@ -1 +1,3 @@
|
||||
declare function alert(message: string): void;
|
||||
|
||||
declare const navigator: undefined | { product: 'ReactNative' };
|
||||
|
8
typings/slip39.d.ts
vendored
Normal file
8
typings/slip39.d.ts
vendored
Normal file
@ -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[];
|
||||
}
|
Loading…
Reference in New Issue
Block a user