mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 23:08:07 +01:00
Merge branch 'master' of https://github.com/BlueWallet/BlueWallet into settingsui
This commit is contained in:
commit
4a5a55c254
66 changed files with 1728 additions and 635 deletions
|
@ -1382,7 +1382,7 @@ export class NewWalletPanel extends Component {
|
|||
style={{
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
minHeight: 164,
|
||||
minHeight: Platform.OS === 'ios' ? 164 : 181,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
|
@ -1838,7 +1838,9 @@ export class WalletsCarousel extends Component {
|
|||
<NewWalletPanel
|
||||
onPress={() => {
|
||||
if (WalletsCarousel.handleClick) {
|
||||
this.onPressedOut();
|
||||
WalletsCarousel.handleClick(index);
|
||||
this.onPressedOut();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -1858,7 +1860,9 @@ export class WalletsCarousel extends Component {
|
|||
onPressOut={item.getIsFailure() ? this.onPressedOut : null}
|
||||
onPress={() => {
|
||||
if (item.getIsFailure() && WalletsCarousel.handleClick) {
|
||||
this.onPressedOut();
|
||||
WalletsCarousel.handleClick(index);
|
||||
this.onPressedOut();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -1926,7 +1930,9 @@ export class WalletsCarousel extends Component {
|
|||
onLongPress={WalletsCarousel.handleLongPress}
|
||||
onPress={() => {
|
||||
if (WalletsCarousel.handleClick) {
|
||||
this.onPressedOut();
|
||||
WalletsCarousel.handleClick(index);
|
||||
this.onPressedOut();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -2077,7 +2083,7 @@ export class BlueAddressInput extends Component {
|
|||
value={this.props.address}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.props.isLoading}
|
||||
onSubmitEditing={() => Keyboard.dismiss()}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
{...this.props}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
|
|
|
@ -194,6 +194,12 @@ const LNDCreateInvoiceStackNavigator = createStackNavigator({
|
|||
LNDCreateInvoice: {
|
||||
screen: LNDCreateInvoice,
|
||||
},
|
||||
SelectWallet: {
|
||||
screen: SelectWallet,
|
||||
navigationOptions: {
|
||||
headerLeft: null,
|
||||
},
|
||||
},
|
||||
LNDViewInvoice: {
|
||||
screen: LNDViewInvoice,
|
||||
swipeEnabled: false,
|
||||
|
@ -210,6 +216,7 @@ const CreateWalletStackNavigator = createStackNavigator({
|
|||
},
|
||||
ImportWallet: {
|
||||
screen: ImportWallet,
|
||||
routeName: 'ImportWallet',
|
||||
},
|
||||
PleaseBackup: {
|
||||
screen: PleaseBackup,
|
||||
|
|
|
@ -119,7 +119,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "4.9.1"
|
||||
versionName "5.0.0"
|
||||
multiDexEnabled true
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
|
@ -34,6 +34,26 @@
|
|||
<data android:scheme="lapp" />
|
||||
<data android:scheme="blue" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="application/octet-stream"
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.psbt"
|
||||
/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="text/plain"
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.psbt"
|
||||
/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
|
|
|
@ -8,6 +8,7 @@ const BlueElectrum = require('../BlueElectrum');
|
|||
const HDNode = require('bip32');
|
||||
const coinSelectAccumulative = require('coinselect/accumulative');
|
||||
const coinSelectSplit = require('coinselect/split');
|
||||
const reverse = require('buffer-reverse');
|
||||
|
||||
const { RNRandomBytes } = NativeModules;
|
||||
|
||||
|
@ -635,6 +636,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
|
||||
async fetchUtxo() {
|
||||
// considering only confirmed balance
|
||||
// also, fetching utxo of addresses that only have some balance
|
||||
let addressess = [];
|
||||
|
||||
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
|
@ -717,9 +719,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
* @param changeAddress {String} Excessive coins will go back to that address
|
||||
* @param sequence {Number} Used in RBF
|
||||
* @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case
|
||||
* @param masterFingerprint {number} Decimal number of wallet's master fingerprint
|
||||
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}}
|
||||
*/
|
||||
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false) {
|
||||
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) {
|
||||
if (!changeAddress) throw new Error('No change address provided');
|
||||
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
||||
|
||||
|
@ -756,7 +759,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input');
|
||||
}
|
||||
let pubkey = this._getPubkeyByAddress(input.address);
|
||||
let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||
let masterFingerprintBuffer;
|
||||
if (masterFingerprint) {
|
||||
let masterFingerprintHex = Number(masterFingerprint).toString(16);
|
||||
if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
||||
const hexBuffer = Buffer.from(masterFingerprintHex, 'hex');
|
||||
masterFingerprintBuffer = Buffer.from(reverse(hexBuffer));
|
||||
} else {
|
||||
masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
// this is not correct fingerprint, as we dont know real fingerprint - we got zpub with 84/0, but fingerpting
|
||||
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
|
||||
let path = this._getDerivationPathByAddress(input.address);
|
||||
|
@ -767,7 +778,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
sequence,
|
||||
bip32Derivation: [
|
||||
{
|
||||
masterFingerprint,
|
||||
masterFingerprint: masterFingerprintBuffer,
|
||||
path,
|
||||
pubkey,
|
||||
},
|
||||
|
@ -789,7 +800,17 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
|
||||
let path = this._getDerivationPathByAddress(output.address);
|
||||
let pubkey = this._getPubkeyByAddress(output.address);
|
||||
let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||
let masterFingerprintBuffer;
|
||||
|
||||
if (masterFingerprint) {
|
||||
let masterFingerprintHex = Number(masterFingerprint).toString(16);
|
||||
if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
||||
const hexBuffer = Buffer.from(masterFingerprintHex, 'hex');
|
||||
masterFingerprintBuffer = Buffer.from(reverse(hexBuffer));
|
||||
} else {
|
||||
masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
// this is not correct fingerprint, as we dont know realfingerprint - we got zpub with 84/0, but fingerpting
|
||||
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
|
||||
|
||||
|
@ -801,7 +822,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
if (change) {
|
||||
outputData['bip32Derivation'] = [
|
||||
{
|
||||
masterFingerprint,
|
||||
masterFingerprint: masterFingerprintBuffer,
|
||||
path,
|
||||
pubkey,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
|
||||
const createHash = require('create-hash');
|
||||
|
||||
export class AbstractWallet {
|
||||
static type = 'abstract';
|
||||
static typeReadable = 'abstract';
|
||||
|
@ -128,6 +129,19 @@ export class AbstractWallet {
|
|||
|
||||
setSecret(newSecret) {
|
||||
this.secret = newSecret.trim();
|
||||
|
||||
try {
|
||||
const parsedSecret = JSON.parse(this.secret);
|
||||
if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) {
|
||||
let masterFingerprint = false;
|
||||
if (parsedSecret.keystore.ckcc_xfp) {
|
||||
// It is a ColdCard Hardware Wallet
|
||||
masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp);
|
||||
}
|
||||
this.secret = parsedSecret.keystore.xpub;
|
||||
this.masterFingerprint = masterFingerprint;
|
||||
}
|
||||
} catch (_) {}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -144,4 +158,8 @@ export class AbstractWallet {
|
|||
getAddressAsync() {
|
||||
return new Promise(resolve => resolve(this.getAddress()));
|
||||
}
|
||||
|
||||
useWithHardwareWalletEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -349,7 +349,6 @@ export class AppStorage {
|
|||
if (key.prepareForSerialization) key.prepareForSerialization();
|
||||
walletsToSave.push(JSON.stringify({ ...key, type: key.type }));
|
||||
}
|
||||
|
||||
let data = {
|
||||
wallets: walletsToSave,
|
||||
tx_metadata: this.tx_metadata,
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { AppStorage, LightningCustodianWallet } from './';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import BitcoinBIP70TransactionDecode from '../bip70/bip70';
|
||||
import RNFS from 'react-native-fs';
|
||||
import url from 'url';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const BlueApp = require('../BlueApp');
|
||||
const BlueApp: AppStorage = require('../BlueApp');
|
||||
|
||||
class DeeplinkSchemaMatch {
|
||||
static hasSchema(schemaString) {
|
||||
if (typeof schemaString !== 'string' || schemaString.length <= 0) return false;
|
||||
|
@ -30,6 +34,21 @@ class DeeplinkSchemaMatch {
|
|||
if (typeof event.url !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) {
|
||||
RNFS.readFile(event.url)
|
||||
.then(file => {
|
||||
if (file) {
|
||||
completionHandler({
|
||||
routeName: 'PsbtWithHardwareWallet',
|
||||
params: {
|
||||
deepLinkPSBT: file,
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => console.warn(e));
|
||||
return;
|
||||
}
|
||||
let isBothBitcoinAndLightning;
|
||||
try {
|
||||
isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url);
|
||||
|
@ -40,7 +59,8 @@ class DeeplinkSchemaMatch {
|
|||
completionHandler({
|
||||
routeName: 'HandleOffchainAndOnChain',
|
||||
params: {
|
||||
onWalletSelect: this.isBothBitcoinAndLightningWalletSelect,
|
||||
onWalletSelect: wallet =>
|
||||
completionHandler(DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning)),
|
||||
},
|
||||
});
|
||||
} else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url) || BitcoinBIP70TransactionDecode.matchesPaymentURL(event.url)) {
|
||||
|
@ -95,7 +115,7 @@ class DeeplinkSchemaMatch {
|
|||
if (!haveLnWallet) {
|
||||
// need to create one
|
||||
let w = new LightningCustodianWallet();
|
||||
w.setLabel(this.state.label || w.typeReadable);
|
||||
w.setLabel(w.typeReadable);
|
||||
|
||||
try {
|
||||
let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
|
||||
|
@ -128,17 +148,14 @@ class DeeplinkSchemaMatch {
|
|||
return;
|
||||
}
|
||||
|
||||
this.navigator &&
|
||||
this.navigator.dispatch(
|
||||
completionHandler({
|
||||
routeName: 'LappBrowser',
|
||||
params: {
|
||||
fromSecret: lnWallet.getSecret(),
|
||||
fromWallet: lnWallet,
|
||||
url: urlObject.query.url,
|
||||
},
|
||||
}),
|
||||
);
|
||||
completionHandler({
|
||||
routeName: 'LappBrowser',
|
||||
params: {
|
||||
fromSecret: lnWallet.getSecret(),
|
||||
fromWallet: lnWallet,
|
||||
url: urlObject.query.url,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +163,34 @@ class DeeplinkSchemaMatch {
|
|||
}
|
||||
}
|
||||
|
||||
static isTXNFile(filePath) {
|
||||
return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('.txn');
|
||||
}
|
||||
|
||||
static isPossiblyPSBTFile(filePath) {
|
||||
return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('-signed.psbt');
|
||||
}
|
||||
|
||||
static isBothBitcoinAndLightningOnWalletSelect(wallet, uri) {
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
return {
|
||||
routeName: 'SendDetails',
|
||||
params: {
|
||||
uri: uri.bitcoin,
|
||||
fromWallet: wallet,
|
||||
},
|
||||
};
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
return {
|
||||
routeName: 'ScanLndInvoice',
|
||||
params: {
|
||||
uri: uri.lndInvoice,
|
||||
fromSecret: wallet.getSecret(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static isBitcoinAddress(address) {
|
||||
address = address
|
||||
.replace('bitcoin:', '')
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { LegacyWallet } from './legacy-wallet';
|
||||
import Frisbee from 'frisbee';
|
||||
import bolt11 from 'bolt11';
|
||||
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
|
||||
|
||||
export class LightningCustodianWallet extends LegacyWallet {
|
||||
|
@ -515,7 +516,7 @@ export class LightningCustodianWallet extends LegacyWallet {
|
|||
* Example return:
|
||||
* { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',
|
||||
* payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4',
|
||||
* num_satoshisnum_satoshis: '100',
|
||||
* num_satoshis: '100',
|
||||
* timestamp: '1535116657',
|
||||
* expiry: '3600',
|
||||
* description: 'hundredSatoshis blitzhub',
|
||||
|
@ -527,31 +528,46 @@ export class LightningCustodianWallet extends LegacyWallet {
|
|||
* @param invoice BOLT invoice string
|
||||
* @return {Promise.<Object>}
|
||||
*/
|
||||
async decodeInvoice(invoice) {
|
||||
await this.checkLogin();
|
||||
decodeInvoice(invoice) {
|
||||
let { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice);
|
||||
|
||||
let response = await this._api.get('/decodeinvoice?invoice=' + invoice, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||
},
|
||||
});
|
||||
let decoded = {
|
||||
destination: payeeNodeKey,
|
||||
num_satoshis: satoshis ? satoshis.toString() : '0',
|
||||
num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0',
|
||||
timestamp: timestamp.toString(),
|
||||
fallback_addr: '',
|
||||
route_hints: [],
|
||||
};
|
||||
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined') {
|
||||
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
let { tagName, data } = tags[i];
|
||||
switch (tagName) {
|
||||
case 'payment_hash':
|
||||
decoded.payment_hash = data;
|
||||
break;
|
||||
case 'purpose_commit_hash':
|
||||
decoded.description_hash = data;
|
||||
break;
|
||||
case 'min_final_cltv_expiry':
|
||||
decoded.cltv_expiry = data.toString();
|
||||
break;
|
||||
case 'expire_time':
|
||||
decoded.expiry = data.toString();
|
||||
break;
|
||||
case 'description':
|
||||
decoded.description = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (json && json.error) {
|
||||
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||
if (!decoded.expiry) decoded.expiry = '3600'; // default
|
||||
|
||||
if (parseInt(decoded.num_satoshis) === 0 && decoded.num_millisatoshis > 0) {
|
||||
decoded.num_satoshis = (decoded.num_millisatoshis / 1000).toString();
|
||||
}
|
||||
|
||||
if (!json.payment_hash) {
|
||||
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||
}
|
||||
|
||||
return (this.decoded_invoice_raw = json);
|
||||
return (this.decoded_invoice_raw = decoded);
|
||||
}
|
||||
|
||||
async fetchInfo() {
|
||||
|
@ -602,6 +618,49 @@ export class LightningCustodianWallet extends LegacyWallet {
|
|||
allowReceive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example return:
|
||||
* { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',
|
||||
* payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4',
|
||||
* num_satoshis: '100',
|
||||
* timestamp: '1535116657',
|
||||
* expiry: '3600',
|
||||
* description: 'hundredSatoshis blitzhub',
|
||||
* description_hash: '',
|
||||
* fallback_addr: '',
|
||||
* cltv_expiry: '10',
|
||||
* route_hints: [] }
|
||||
*
|
||||
* @param invoice BOLT invoice string
|
||||
* @return {Promise.<Object>}
|
||||
*/
|
||||
async decodeInvoiceRemote(invoice) {
|
||||
await this.checkLogin();
|
||||
|
||||
let response = await this._api.get('/decodeinvoice?invoice=' + invoice, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer' + ' ' + this.access_token,
|
||||
},
|
||||
});
|
||||
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined') {
|
||||
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||
}
|
||||
|
||||
if (json && json.error) {
|
||||
throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
|
||||
}
|
||||
|
||||
if (!json.payment_hash) {
|
||||
throw new Error('API unexpected response: ' + JSON.stringify(response.body));
|
||||
}
|
||||
|
||||
return (this.decoded_invoice_raw = json);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -18,24 +18,41 @@ const BlueApp = require('../BlueApp');
|
|||
const loc = require('../loc');
|
||||
|
||||
export default class WalletImport {
|
||||
static async _saveWallet(w) {
|
||||
/**
|
||||
*
|
||||
* @param w
|
||||
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
static async _saveWallet(w, additionalProperties) {
|
||||
try {
|
||||
const wallet = BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret && wallet.type !== PlaceholderWallet.type);
|
||||
if (wallet) {
|
||||
alert('This wallet has been previously imported.');
|
||||
WalletImport.removePlaceholderWallet();
|
||||
} else {
|
||||
alert(loc.wallets.import.success);
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
|
||||
w.setUserHasSavedExport(true);
|
||||
if (additionalProperties) {
|
||||
for (const [key, value] of Object.entries(additionalProperties)) {
|
||||
w[key] = value;
|
||||
}
|
||||
}
|
||||
WalletImport.removePlaceholderWallet();
|
||||
BlueApp.wallets.push(w);
|
||||
await BlueApp.saveToDisk();
|
||||
A(A.ENUM.CREATED_WALLET);
|
||||
alert(loc.wallets.import.success);
|
||||
}
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
} catch (_e) {}
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
console.log(e);
|
||||
WalletImport.removePlaceholderWallet();
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
static removePlaceholderWallet() {
|
||||
|
@ -58,7 +75,13 @@ export default class WalletImport {
|
|||
return BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type);
|
||||
}
|
||||
|
||||
static async processImportText(importText) {
|
||||
/**
|
||||
*
|
||||
* @param importText
|
||||
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async processImportText(importText, additionalProperties) {
|
||||
if (WalletImport.isCurrentlyImportingWallet()) {
|
||||
return;
|
||||
}
|
||||
|
@ -209,7 +232,7 @@ export default class WalletImport {
|
|||
if (watchOnly.valid()) {
|
||||
await watchOnly.fetchTransactions();
|
||||
await watchOnly.fetchBalance();
|
||||
return WalletImport._saveWallet(watchOnly);
|
||||
return WalletImport._saveWallet(watchOnly, additionalProperties);
|
||||
}
|
||||
|
||||
// nope?
|
||||
|
|
|
@ -11,21 +11,26 @@ export class WatchOnlyWallet extends LegacyWallet {
|
|||
constructor() {
|
||||
super();
|
||||
this.use_with_hardware_wallet = false;
|
||||
this.masterFingerprint = false;
|
||||
}
|
||||
|
||||
allowSend() {
|
||||
return !!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend();
|
||||
return (
|
||||
this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend()
|
||||
);
|
||||
}
|
||||
|
||||
allowBatchSend() {
|
||||
return (
|
||||
!!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowBatchSend()
|
||||
this.useWithHardwareWalletEnabled() &&
|
||||
this._hdWalletInstance instanceof HDSegwitBech32Wallet &&
|
||||
this._hdWalletInstance.allowBatchSend()
|
||||
);
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return (
|
||||
!!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax()
|
||||
this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -43,7 +48,7 @@ export class WatchOnlyWallet extends LegacyWallet {
|
|||
try {
|
||||
bitcoin.address.toOutputScript(this.getAddress());
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -146,9 +151,43 @@ export class WatchOnlyWallet extends LegacyWallet {
|
|||
*/
|
||||
createTransaction(utxos, targets, feeRate, changeAddress, sequence) {
|
||||
if (this._hdWalletInstance instanceof HDSegwitBech32Wallet) {
|
||||
return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true);
|
||||
return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint());
|
||||
} else {
|
||||
throw new Error('Not a zpub watch-only wallet, cant create PSBT (or just not initialized)');
|
||||
}
|
||||
}
|
||||
|
||||
getMasterFingerprint() {
|
||||
return this.masterFingerprint;
|
||||
}
|
||||
|
||||
getMasterFingerprintHex() {
|
||||
if (!this.masterFingerprint) return '00000000';
|
||||
let masterFingerprintHex = Number(this.masterFingerprint).toString(16);
|
||||
if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
||||
// poor man's little-endian conversion:
|
||||
// ¯\_(ツ)_/¯
|
||||
return (
|
||||
masterFingerprintHex[6] +
|
||||
masterFingerprintHex[7] +
|
||||
masterFingerprintHex[4] +
|
||||
masterFingerprintHex[5] +
|
||||
masterFingerprintHex[2] +
|
||||
masterFingerprintHex[3] +
|
||||
masterFingerprintHex[0] +
|
||||
masterFingerprintHex[1]
|
||||
);
|
||||
}
|
||||
|
||||
isHd() {
|
||||
return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub');
|
||||
}
|
||||
|
||||
useWithHardwareWalletEnabled() {
|
||||
return !!this.use_with_hardware_wallet;
|
||||
}
|
||||
|
||||
setUseWithHardwareWalletEnabled(enabled) {
|
||||
this.use_with_hardware_wallet = !!enabled;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
3271B0BA236E329400DA766F /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = "<group>"; };
|
||||
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = "<group>"; };
|
||||
32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWalletRelease.entitlements; path = BlueWallet/BlueWalletRelease.entitlements; sourceTree = "<group>"; };
|
||||
32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = "<group>"; };
|
||||
32F0A2502310B0910095C559 /* BlueWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWallet.entitlements; path = BlueWallet/BlueWallet.entitlements; sourceTree = "<group>"; };
|
||||
32F0A2992311DBB20095C559 /* ComplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = "<group>"; };
|
||||
|
@ -332,6 +333,7 @@
|
|||
13B07FAE1A68108700A75B9A /* BlueWallet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */,
|
||||
32F0A2502310B0910095C559 /* BlueWallet.entitlements */,
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
|
@ -681,7 +683,7 @@
|
|||
attributes = {
|
||||
LastSwiftUpdateCheck = 1120;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
ORGANIZATIONNAME = BlueWallet;
|
||||
TargetAttributes = {
|
||||
00E356ED1AD99517003FC87E = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
|
@ -1234,7 +1236,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = BlueWallet/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
@ -1266,11 +1268,11 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
|
||||
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWalletRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = BlueWallet/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
|
20
ios/BlueWallet/BlueWalletRelease.entitlements
Normal file
20
ios/BlueWallet/BlueWalletRelease.entitlements
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array/>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudDocuments</string>
|
||||
</array>
|
||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||
<array/>
|
||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
||||
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.bluewallet.bluewallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -2,12 +2,41 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>BlueWallet</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>PSBT</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>TXN</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
@ -19,7 +48,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.9.1</string>
|
||||
<string>5.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
@ -43,6 +72,8 @@
|
|||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
@ -58,18 +89,18 @@
|
|||
</dict>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>In order to confirm your identity, we need your permission to use FaceID.</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>In order to use FaceID please confirm your permission.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSMotionUsageDescription</key>
|
||||
|
@ -116,7 +147,91 @@
|
|||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Partially Signed Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Partially Signed Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>psbt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Bitcoin Transaction</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.psbt.txn</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.9.1</string>
|
||||
<string>5.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.9.1</string>
|
||||
<string>5.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
|
|
@ -76,6 +76,8 @@ PODS:
|
|||
- React
|
||||
- react-native-camera/RN (3.4.0):
|
||||
- React
|
||||
- react-native-document-picker (3.2.0):
|
||||
- React
|
||||
- react-native-haptic-feedback (1.7.1):
|
||||
- React
|
||||
- react-native-image-picker (1.1.0):
|
||||
|
@ -169,6 +171,7 @@ DEPENDENCIES:
|
|||
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
|
||||
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
|
||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-haptic-feedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
|
||||
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
|
||||
|
@ -243,6 +246,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/@react-native-community/blur"
|
||||
react-native-camera:
|
||||
:path: "../node_modules/react-native-camera"
|
||||
react-native-document-picker:
|
||||
:path: "../node_modules/react-native-document-picker"
|
||||
react-native-haptic-feedback:
|
||||
:path: "../node_modules/react-native-haptic-feedback"
|
||||
react-native-image-picker:
|
||||
|
@ -331,6 +336,7 @@ SPEC CHECKSUMS:
|
|||
react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5
|
||||
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
|
||||
react-native-camera: 203091b4bf99d48b788a0682ad573e8718724893
|
||||
react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154
|
||||
react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d
|
||||
react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f
|
||||
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.9.1</string>
|
||||
<string>5.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
|
|
@ -1,3 +1,48 @@
|
|||
v5.0.0
|
||||
======
|
||||
|
||||
* ADD: Coldcard support
|
||||
* FIX: allow capitalized bech32 addresses (closes #838)
|
||||
* FIX: lnurl scan to receive is not returning the correct view (closes #828)
|
||||
* FIX: watch-only delete wallet doesnt have confirmation now
|
||||
* FIX: typo in spanish
|
||||
|
||||
v4.9.4
|
||||
======
|
||||
|
||||
* REL: ver bump 4.9.4
|
||||
* FIX: Lint
|
||||
* FIX: Listen to lnd invoice changes
|
||||
* FIX: There's two refresh icons on the main view, when you pull-to-refresh
|
||||
* FIX: Crash on scan in wallets import when closing the view
|
||||
* FIX: Allow walet change on invoice creation
|
||||
* FIX: Allow wallet text input to be empty for new wallet naming
|
||||
* FIX: LN Invoice amount renders 0
|
||||
* FIX: Handle both chains
|
||||
* FIX: deeplinking (safello etc)
|
||||
* DEL: Remove alerts from main list
|
||||
|
||||
v4.9.2
|
||||
======
|
||||
|
||||
* ADD: Swipe to Scan
|
||||
* ADD: Handle clipboard content with both bitcoin: and lightning:
|
||||
* ADD: Ask user if they have backed up their seed phrase
|
||||
* ADD: Export screen allows copying to clipboard if its a LNDHub wallet
|
||||
* ADD: Show LNDHub backup when creating lnd wallet
|
||||
* ADD: CLP Fiat
|
||||
* FIX: TX Time visual glitch
|
||||
* FIX: Show an alert when theres a fetch transactions error
|
||||
* FIX: TX list uses whole canvas area
|
||||
* FIX: Don't allow empty wallet labels
|
||||
* FIX: Wallet type selecion clipping on advanced mode
|
||||
* FIX: Receive address was not being rendered
|
||||
* FIX: Don't show wallet export warning if wallet was imported
|
||||
* REF: Reworked Import wallet flow
|
||||
* REF: BIP49 to use electrum
|
||||
* REF: Custom receive
|
||||
|
||||
|
||||
v4.9.0
|
||||
======
|
||||
|
||||
|
@ -10,44 +55,4 @@ v4.9.0
|
|||
* FIX: Hide Manage Funds button if wallet doesn't allow onchain refill.
|
||||
* FIX: LN Scan to receive is more visible
|
||||
* FIX: Quick actions not appearing on non-3d touch devices.
|
||||
* FIX: Dont show clipboard modal when biometrics is dismissed
|
||||
|
||||
v4.8.1
|
||||
======
|
||||
|
||||
* FIX: Updated biometrics
|
||||
* FIX: Import QR Code from screenshot not working
|
||||
|
||||
v4.8.0
|
||||
======
|
||||
|
||||
* ADD: Today Extension and Quick Actions
|
||||
* ADD: Send max option on advanced menu
|
||||
* ADD: Add Onchain address view for Lightning
|
||||
* FIX: Allow textfield to be visible above keyboard
|
||||
* FIX: lapp browser when typing URL without https scheme it doesnt work
|
||||
* ADD: Value and memo to the success screen fix logic for both sent and receive
|
||||
* FIX: layout for small devices with flexbox
|
||||
* FIX: Dont allow zero invoices to enable create invoice button
|
||||
* FIX: Change create button on Receive LN payment should be create invoice
|
||||
* FIX: Update for watch
|
||||
|
||||
|
||||
v4.7.1
|
||||
======
|
||||
|
||||
* ADD: Lapp browser
|
||||
* FIX: White screen on boot
|
||||
* FIX: Lightning wallet was not shown on Watch app
|
||||
* FIX: crash on PSBT tx broadcast (when using with hardware wallet)
|
||||
* REF: mnemonic backup screen
|
||||
* DEL: Auto brightenss
|
||||
|
||||
v4.7.0
|
||||
======
|
||||
|
||||
* ADD: external marketplace link
|
||||
* FIX: electrum connection
|
||||
* FIX: Now able to use biometrics with encrypted storage (not for unlocking)
|
||||
* FIX: LApp marketplace address is now editable
|
||||
* FIX: single address watch-only wallet Receive button crash
|
||||
* FIX: Dont show clipboard modal when biometrics is dismissed
|
|
@ -25,6 +25,9 @@
|
|||
latest_transaction: 'laaste transaksie',
|
||||
empty_txs1: 'U transaksies is hier beskikbaar,',
|
||||
empty_txs2: 'huidiglik geen transaksies',
|
||||
empty_txs1_lightning:
|
||||
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
|
||||
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
|
||||
tap_here_to_buy: 'Raak hier om Bitcoin te koop',
|
||||
},
|
||||
reorder: {
|
||||
|
@ -50,6 +53,7 @@
|
|||
details: {
|
||||
title: 'Beursiet',
|
||||
address: 'AdresAddress',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipe',
|
||||
label: 'Etiket',
|
||||
destination: 'bestemming',
|
||||
|
@ -165,6 +169,7 @@
|
|||
create: 'Skep',
|
||||
setAmount: 'Bedrag ontvang',
|
||||
},
|
||||
scan_lnurl: 'Scan to receive',
|
||||
},
|
||||
buyBitcoin: {
|
||||
header: 'Koop Bitcoin',
|
||||
|
@ -186,10 +191,14 @@
|
|||
'Om u eie LND node te konnekteer, installeer asseblief LndHub' +
|
||||
' and put its URL here in settings. Leave blank om die standaard LndHub' +
|
||||
'(lndhub.io) te gebruik',
|
||||
electrum_settings: 'Electrum Settings',
|
||||
electrum_settings_explain: 'Set to blank to use default',
|
||||
save: 'stoor',
|
||||
about: 'info',
|
||||
language: 'Taal',
|
||||
currency: 'Geldeenheid',
|
||||
advanced_options: 'Advanced Options',
|
||||
enable_advanced_mode: 'Enable advanced mode',
|
||||
},
|
||||
plausibledeniability: {
|
||||
title: 'Geloofwaardige Ontkenbaarheid',
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
latest_transaction: 'Utshintsho olutsha',
|
||||
empty_txs1: 'Intengiso yakho iya kubonakala apha,',
|
||||
empty_txs2: 'akuho nanye okwangoku',
|
||||
empty_txs1_lightning:
|
||||
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
|
||||
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
|
||||
tap_here_to_buy: 'Cofa apha ukuthenga ibitcoin',
|
||||
},
|
||||
reorder: {
|
||||
|
@ -48,6 +51,7 @@
|
|||
details: {
|
||||
title: 'Ingxowa',
|
||||
address: 'Ikheli',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Uhlobo',
|
||||
label: 'Igama',
|
||||
destination: 'ukuya kuyo',
|
||||
|
@ -163,6 +167,7 @@
|
|||
create: 'Yenza',
|
||||
setAmount: 'Fumana ngexabiso',
|
||||
},
|
||||
scan_lnurl: 'Scan to receive',
|
||||
},
|
||||
buyBitcoin: {
|
||||
header: 'Thenga Ibitcoin',
|
||||
|
@ -183,10 +188,14 @@
|
|||
lightning_settings_explain:
|
||||
'Ukuxhuma kwi-node yakho ye-LND nceda ufake iLndHub' +
|
||||
' kwaye ufake iURL apha izicwangciso. Shiya kungenanto yokusebenzisa iLndHub (Indhub.io)',
|
||||
electrum_settings: 'Electrum Settings',
|
||||
electrum_settings_explain: 'Set to blank to use default',
|
||||
save: 'ndoloza',
|
||||
about: 'Malunga',
|
||||
language: 'Ulwimi',
|
||||
currency: 'Lwemali',
|
||||
advanced_options: 'Advanced Options',
|
||||
enable_advanced_mode: 'Enable advanced mode',
|
||||
},
|
||||
plausibledeniability: {
|
||||
title: 'Ukuphika',
|
||||
|
|
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Peněženka',
|
||||
address: 'Adresa',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Typ',
|
||||
label: 'Popisek',
|
||||
destination: 'cíl',
|
||||
|
|
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Wallet',
|
||||
address: 'Adresse',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Etiket',
|
||||
destination: 'destination',
|
||||
|
@ -223,6 +224,8 @@ module.exports = {
|
|||
refill_lnd_balance: 'Genopfyld Lightning wallet',
|
||||
refill: 'Genopfyld',
|
||||
withdraw: 'Træk coins tilbage',
|
||||
expired: 'Expired',
|
||||
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
|
||||
},
|
||||
pleasebackup: {
|
||||
title: 'Your wallet is created...',
|
||||
|
|
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Wallet',
|
||||
address: 'Adresse',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Typ',
|
||||
label: 'Bezeichnung',
|
||||
destination: 'Zieladresse',
|
||||
|
@ -225,6 +226,7 @@ module.exports = {
|
|||
refill_lnd_balance: 'Lade deine Lightning Wallet auf',
|
||||
refill: 'Aufladen',
|
||||
withdraw: 'Abheben',
|
||||
expired: 'Expired',
|
||||
placeholder: 'Invoice',
|
||||
sameWalletAsInvoiceError:
|
||||
'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.',
|
||||
|
|
|
@ -53,6 +53,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Πορτοφόλι',
|
||||
address: 'Διεύθυνση',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Τύπος',
|
||||
label: 'Ετικέτα',
|
||||
destination: 'προορισμός',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Wallet',
|
||||
address: 'Address',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Label',
|
||||
destination: 'destination',
|
||||
|
@ -80,7 +81,7 @@ module.exports = {
|
|||
error: 'Failed to import. Please, make sure that the provided data is valid.',
|
||||
success: 'Success',
|
||||
do_import: 'Import',
|
||||
scan_qr: 'or scan QR code instead?',
|
||||
scan_qr: '...scan QR or import file instead?',
|
||||
},
|
||||
scanQrWif: {
|
||||
go_back: 'Go Back',
|
||||
|
@ -145,7 +146,7 @@ module.exports = {
|
|||
title: 'create transaction',
|
||||
error: 'Error creating transaction. Invalid address or send amount?',
|
||||
go_back: 'Go Back',
|
||||
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
|
||||
this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`,
|
||||
to: 'To',
|
||||
amount: 'Amount',
|
||||
fee: 'Fee',
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = {
|
|||
title: 'Añadir billetera',
|
||||
description:
|
||||
'Puedes escanear la billetera de papel (en WIF - Formato de importación de billeteras) o crear una nueva billetera. Las billeteras SegWit estan compatibles por defecto.',
|
||||
scan: 'Escaniar',
|
||||
scan: 'Escanear',
|
||||
create: 'Crear',
|
||||
label_new_segwit: 'Nuevo SegWit',
|
||||
label_new_lightning: 'Nuevo Lightning',
|
||||
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Detalles de la billetera',
|
||||
address: 'Dirección',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipo',
|
||||
label: 'Etiqueta',
|
||||
delete: 'Eliminar',
|
||||
|
|
|
@ -53,6 +53,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Lompakko',
|
||||
address: 'Osoite',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tyyppi',
|
||||
label: 'Etiketti',
|
||||
destination: 'määränpää',
|
||||
|
|
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Portefeuille',
|
||||
address: 'Adresse',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Libelé',
|
||||
destination: 'destination',
|
||||
|
@ -146,7 +147,7 @@ module.exports = {
|
|||
title: 'créer une transaction',
|
||||
error: 'Erreur creating transaction. Invalid address or send amount?',
|
||||
go_back: 'Retour',
|
||||
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
|
||||
this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`,
|
||||
to: 'À',
|
||||
amount: 'Montant',
|
||||
fee: 'Frais',
|
||||
|
|
|
@ -10,6 +10,8 @@ module.exports = {
|
|||
wallets: {
|
||||
select_wallet: 'Odaberi volet',
|
||||
options: 'opcije',
|
||||
createBitcoinWallet:
|
||||
'You currently do not have a Bitcoin wallet. In order to fund a Lightning wallet, a Bitcoin wallet needs to be created or imported. Would you like to continue anyway?',
|
||||
list: {
|
||||
app_name: 'BlueWallet',
|
||||
title: 'Voleti',
|
||||
|
@ -48,6 +50,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Volet',
|
||||
address: 'Adresa',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tip',
|
||||
label: 'Oznaka',
|
||||
destination: 'odredište',
|
||||
|
|
|
@ -51,8 +51,10 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Tárca',
|
||||
address: 'Cím',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Típus',
|
||||
label: 'Cimke',
|
||||
destination: 'destination',
|
||||
description: 'leírás',
|
||||
are_you_sure: 'Biztos vagy benne?',
|
||||
yes_delete: 'Igen, töröld',
|
||||
|
@ -187,6 +189,8 @@ module.exports = {
|
|||
'Saját LND-csomóponthoz való csatlakozáshoz telepítsd az LndHub-ot' +
|
||||
' és írd be az URL-ét alul. Hagyd üresen, ha a BlueWallet saját LNDHub-jához (lndhub.io) szeretnél csatlakozni.' +
|
||||
' A beállítások mentése után, minden újonnan létrehozott tárca a megadott LDNHubot fogja használni.',
|
||||
electrum_settings: 'Electrum Settings',
|
||||
electrum_settings_explain: 'Set to blank to use default',
|
||||
save: 'Ment',
|
||||
about: 'Egyéb',
|
||||
language: 'Nyelv',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Dompet',
|
||||
address: 'Alamat',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipe',
|
||||
label: 'Label',
|
||||
destination: 'tujuan',
|
||||
|
|
|
@ -53,6 +53,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Portafoglio',
|
||||
address: 'Indirizzo',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipo',
|
||||
label: 'Etichetta',
|
||||
destination: 'Destinazione',
|
||||
|
|
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'ウォレット',
|
||||
address: 'アドレス',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'タイプ',
|
||||
label: 'ラベル',
|
||||
destination: '送り先',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Lommebok',
|
||||
address: 'Adresse',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Merkelapp',
|
||||
destination: 'mål',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Portemonnee',
|
||||
address: 'Adres',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Label',
|
||||
destination: 'bestemming',
|
||||
|
|
|
@ -53,6 +53,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Carteira',
|
||||
address: 'Endereço',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipo',
|
||||
destination: 'destino',
|
||||
description: 'descrição',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'wallet',
|
||||
address: 'Endereço',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tipo',
|
||||
delete: 'Eliminar',
|
||||
save: 'Guardar',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Информация о кошельке',
|
||||
address: 'Адрес',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Тип',
|
||||
label: 'Метка',
|
||||
delete: 'Удалить',
|
||||
|
|
|
@ -51,8 +51,10 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Plånbok',
|
||||
address: 'Adress',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Typ',
|
||||
label: 'Etikett',
|
||||
destination: 'destination',
|
||||
description: 'beskrivning',
|
||||
are_you_sure: 'Är du säker?',
|
||||
yes_delete: 'Ja, ta bort',
|
||||
|
|
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'กระเป๋าสตางค์',
|
||||
address: 'แอดเดรส',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'ชนิด',
|
||||
label: 'ป้าย',
|
||||
destination: 'เป้าหมาย',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Cüzdan',
|
||||
address: 'Adres',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Tip',
|
||||
label: 'Etiket',
|
||||
destination: 'hedef',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Інформація про Гаманець',
|
||||
address: 'Адреса',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Тип',
|
||||
delete: 'Delete',
|
||||
save: 'Save',
|
||||
|
|
|
@ -51,6 +51,7 @@ module.exports = {
|
|||
details: {
|
||||
title: 'Wallet',
|
||||
address: 'Address',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: 'Type',
|
||||
label: 'Label',
|
||||
destination: 'destination',
|
||||
|
@ -145,7 +146,7 @@ module.exports = {
|
|||
title: 'create transaction',
|
||||
error: 'Error creating transaction. Invalid address or send amount?',
|
||||
go_back: 'Go Back',
|
||||
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
|
||||
this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`,
|
||||
to: 'To',
|
||||
amount: 'Amount',
|
||||
fee: 'Fee',
|
||||
|
|
|
@ -49,6 +49,7 @@ module.exports = {
|
|||
details: {
|
||||
title: '钱包',
|
||||
address: '地址',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: '类型',
|
||||
label: '标签',
|
||||
destination: '目的',
|
||||
|
@ -219,6 +220,7 @@ module.exports = {
|
|||
withdraw: '提取',
|
||||
expired: '超时',
|
||||
sameWalletAsInvoiceError: '你不能用创建账单的钱包去支付该账单',
|
||||
placeholder: 'Invoice',
|
||||
},
|
||||
pleasebackup: {
|
||||
title: 'Your wallet is created...',
|
||||
|
|
|
@ -49,6 +49,7 @@ module.exports = {
|
|||
details: {
|
||||
title: '錢包',
|
||||
address: '地址',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
type: '類型',
|
||||
label: '標籤',
|
||||
destination: '目的',
|
||||
|
@ -163,6 +164,7 @@ module.exports = {
|
|||
create: '建立',
|
||||
setAmount: '收款金額',
|
||||
},
|
||||
scan_lnurl: 'Scan to receive',
|
||||
},
|
||||
buyBitcoin: {
|
||||
header: '購買比特幣',
|
||||
|
@ -181,6 +183,8 @@ module.exports = {
|
|||
encrypt_storage: '加密儲存',
|
||||
lightning_settings: '閃電網路設定',
|
||||
lightning_settings_explain: '如要要連線你自己的閃電節點請安裝LndHub' + ' 並把url地址輸入到下面. 空白將使用預設的LndHub (lndhub.io)',
|
||||
electrum_settings: 'Electrum Settings',
|
||||
electrum_settings_explain: 'Set to blank to use default',
|
||||
save: '儲存',
|
||||
about: '關於',
|
||||
language: '語言',
|
||||
|
@ -215,6 +219,7 @@ module.exports = {
|
|||
refill: '充值',
|
||||
withdraw: '提取',
|
||||
expired: '超時',
|
||||
placeholder: 'Invoice',
|
||||
sameWalletAsInvoiceError: '你不能用建立賬單的錢包去支付該賬單',
|
||||
},
|
||||
pleasebackup: {
|
||||
|
|
584
package-lock.json
generated
584
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "BlueWallet",
|
||||
"version": "4.9.1",
|
||||
"version": "5.0.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/runtime": "^7.5.1",
|
||||
|
@ -38,7 +38,8 @@
|
|||
"postinstall": "./node_modules/.bin/rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path --hack; npm run releasenotes2json; npm run podinstall; npx jetify",
|
||||
"test": "npm run unit && npm run jest && npm run lint",
|
||||
"jest": "node node_modules/jest/bin/jest.js tests/integration/*",
|
||||
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ --fix",
|
||||
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/",
|
||||
"lint:fix": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ --fix",
|
||||
"unit": "./node_modules/.bin/mocha tests/unit/*"
|
||||
},
|
||||
"jest": {
|
||||
|
@ -64,6 +65,7 @@
|
|||
"bip32": "2.0.3",
|
||||
"bip39": "2.5.0",
|
||||
"bitcoinjs-lib": "5.1.6",
|
||||
"bolt11": "1.2.7",
|
||||
"buffer": "5.2.1",
|
||||
"buffer-reverse": "1.0.1",
|
||||
"coinselect": "3.1.11",
|
||||
|
@ -71,7 +73,7 @@
|
|||
"dayjs": "1.8.14",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git",
|
||||
"eslint-config-prettier": "6.0.0",
|
||||
"eslint-config-prettier": "6.10.0",
|
||||
"eslint-config-standard": "12.0.0",
|
||||
"eslint-config-standard-react": "7.0.2",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
|
@ -93,6 +95,7 @@
|
|||
"react-native-camera": "3.4.0",
|
||||
"react-native-default-preference": "1.4.1",
|
||||
"react-native-device-info": "4.0.1",
|
||||
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa",
|
||||
"react-native-elements": "0.19.0",
|
||||
"react-native-flexi-radio-button": "0.2.2",
|
||||
"react-native-fs": "2.13.3",
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||
import PropTypes from 'prop-types';
|
||||
import bech32 from 'bech32';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import NavigationService from '../../NavigationService';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { Icon } from 'react-native-elements';
|
||||
|
@ -36,7 +36,8 @@ export default class LNDCreateInvoice extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||
let fromWallet;
|
||||
if (props.navigation.state.params.fromWallet) fromWallet = props.navigation.getParam('fromWallet');
|
||||
|
||||
|
@ -56,6 +57,7 @@ export default class LNDCreateInvoice extends Component {
|
|||
lnurl: '',
|
||||
lnurlParams: null,
|
||||
isLoading: true,
|
||||
renderWalletSelectionButtonHidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -85,6 +87,19 @@ export default class LNDCreateInvoice extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
}
|
||||
|
||||
_keyboardDidShow = () => {
|
||||
this.setState({ renderWalletSelectionButtonHidden: true });
|
||||
};
|
||||
|
||||
_keyboardDidHide = () => {
|
||||
this.setState({ renderWalletSelectionButtonHidden: false });
|
||||
};
|
||||
|
||||
async createInvoice() {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
try {
|
||||
|
@ -195,7 +210,10 @@ export default class LNDCreateInvoice extends Component {
|
|||
<TouchableOpacity
|
||||
disabled={this.state.isLoading}
|
||||
onPress={() => {
|
||||
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl });
|
||||
NavigationService.navigate('ScanQrAddress', {
|
||||
onBarScanned: this.processLnurl,
|
||||
launchedBy: this.props.navigation.state.routeName,
|
||||
});
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
style={{
|
||||
|
@ -216,6 +234,45 @@ export default class LNDCreateInvoice extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderWalletSelectionButton = () => {
|
||||
if (this.state.renderWalletSelectionButtonHidden) return;
|
||||
return (
|
||||
<View style={{ marginBottom: 16, alignItems: 'center', justifyContent: 'center' }}>
|
||||
{!this.state.isLoading && (
|
||||
<TouchableOpacity
|
||||
style={{ flexDirection: 'row', alignItems: 'center' }}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
|
||||
}
|
||||
>
|
||||
<Text style={{ color: '#9aa0aa', fontSize: 14, marginRight: 8 }}>{loc.wallets.select_wallet.toLowerCase()}</Text>
|
||||
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 4 }}>
|
||||
<TouchableOpacity
|
||||
style={{ flexDirection: 'row', alignItems: 'center' }}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
|
||||
}
|
||||
>
|
||||
<Text style={{ color: '#0c2550', fontSize: 14 }}>{this.state.fromWallet.getLabel()}</Text>
|
||||
<Text style={{ color: '#0c2550', fontSize: 14, fontWeight: '600', marginLeft: 8, marginRight: 4 }}>
|
||||
{loc.formatBalanceWithoutSuffix(this.state.fromWallet.getBalance(), BitcoinUnit.SATS, false)}
|
||||
</Text>
|
||||
<Text style={{ color: '#0c2550', fontSize: 11, fontWeight: '600', textAlignVertical: 'bottom', marginTop: 2 }}>
|
||||
{BitcoinUnit.SATS}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
onWalletSelect = wallet => {
|
||||
this.setState({ fromWallet: wallet }, () => this.props.navigation.pop());
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.fromWallet) {
|
||||
return (
|
||||
|
@ -283,6 +340,7 @@ export default class LNDCreateInvoice extends Component {
|
|||
{this.renderCreateButton()}
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
{this.renderWalletSelectionButton()}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
|
@ -295,7 +353,9 @@ LNDCreateInvoice.propTypes = {
|
|||
dismiss: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
pop: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
routeName: PropTypes.string,
|
||||
params: PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
fromWallet: PropTypes.shape({}),
|
||||
|
|
|
@ -80,10 +80,51 @@ export default class ScanLndInvoice extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.navigation.state.params.uri) {
|
||||
this.processTextForInvoice(this.props.navigation.getParam('uri'));
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (props.navigation.state.params.uri) {
|
||||
let data = props.navigation.state.params.uri;
|
||||
// handling BIP21 w/BOLT11 support
|
||||
let ind = data.indexOf('lightning=');
|
||||
if (ind !== -1) {
|
||||
data = data.substring(ind + 10).split('&')[0];
|
||||
}
|
||||
|
||||
data = data.replace('LIGHTNING:', '').replace('lightning:', '');
|
||||
console.log(data);
|
||||
|
||||
/**
|
||||
* @type {LightningCustodianWallet}
|
||||
*/
|
||||
let w = state.fromWallet;
|
||||
let decoded;
|
||||
try {
|
||||
decoded = w.decodeInvoice(data);
|
||||
|
||||
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
|
||||
if (+new Date() > expiresIn) {
|
||||
expiresIn = 'expired';
|
||||
} else {
|
||||
expiresIn = Math.round((expiresIn - +new Date()) / (60 * 1000)) + ' min';
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
props.navigation.setParams({ uri: undefined });
|
||||
return {
|
||||
invoice: data,
|
||||
decoded,
|
||||
expiresIn,
|
||||
destination: data,
|
||||
isAmountInitiallyEmpty: decoded.num_satoshis === '0',
|
||||
isLoading: false,
|
||||
};
|
||||
} catch (Err) {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
Keyboard.dismiss();
|
||||
props.navigation.setParams({ uri: undefined });
|
||||
setTimeout(() => alert(Err.message), 10);
|
||||
return { ...state, isLoading: false };
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -100,52 +141,7 @@ export default class ScanLndInvoice extends React.Component {
|
|||
};
|
||||
|
||||
processInvoice = data => {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
if (!this.state.fromWallet) {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
|
||||
return this.props.navigation.goBack();
|
||||
}
|
||||
|
||||
// handling BIP21 w/BOLT11 support
|
||||
let ind = data.indexOf('lightning=');
|
||||
if (ind !== -1) {
|
||||
data = data.substring(ind + 10).split('&')[0];
|
||||
}
|
||||
|
||||
data = data.replace('LIGHTNING:', '').replace('lightning:', '');
|
||||
console.log(data);
|
||||
|
||||
/**
|
||||
* @type {LightningCustodianWallet}
|
||||
*/
|
||||
let w = this.state.fromWallet;
|
||||
let decoded;
|
||||
try {
|
||||
decoded = await w.decodeInvoice(data);
|
||||
|
||||
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
|
||||
if (+new Date() > expiresIn) {
|
||||
expiresIn = 'expired';
|
||||
} else {
|
||||
expiresIn = Math.round((expiresIn - +new Date()) / (60 * 1000)) + ' min';
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
this.setState({
|
||||
invoice: data,
|
||||
decoded,
|
||||
expiresIn,
|
||||
destination: data,
|
||||
isAmountInitiallyEmpty: decoded.num_satoshis === '0',
|
||||
isLoading: false,
|
||||
});
|
||||
} catch (Err) {
|
||||
Keyboard.dismiss();
|
||||
this.setState({ isLoading: false });
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
alert(Err.message);
|
||||
}
|
||||
});
|
||||
this.props.navigation.setParams({ uri: data });
|
||||
};
|
||||
|
||||
async pay() {
|
||||
|
@ -216,7 +212,7 @@ export default class ScanLndInvoice extends React.Component {
|
|||
if (typeof this.state.decoded !== 'object') {
|
||||
return true;
|
||||
} else {
|
||||
if (!this.state.decoded.hasOwnProperty('num_satoshis')) {
|
||||
if (!this.state.decoded.num_satoshis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +291,7 @@ export default class ScanLndInvoice extends React.Component {
|
|||
<BlueCard>
|
||||
<BlueAddressInput
|
||||
onChangeText={text => {
|
||||
this.setState({ destination: text });
|
||||
text = text.trim();
|
||||
this.processTextForInvoice(text);
|
||||
}}
|
||||
onBarScanned={this.processInvoice}
|
||||
|
@ -355,6 +351,7 @@ ScanLndInvoice.propTypes = {
|
|||
navigate: PropTypes.func,
|
||||
pop: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
setParams: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
routeName: PropTypes.string,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
TextInput,
|
||||
|
@ -11,26 +12,36 @@ import {
|
|||
Keyboard,
|
||||
Text,
|
||||
View,
|
||||
Platform,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
import { BlueNavigationStyle, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import Privacy from '../../Privacy';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import Share from 'react-native-share';
|
||||
import RNFS from 'react-native-fs';
|
||||
/** @type {AppStorage} */
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const loc = require('../../loc');
|
||||
const currency = require('../../currency');
|
||||
|
||||
export default class SendCreate extends Component {
|
||||
static navigationOptions = () => ({
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
...BlueNavigationStyle,
|
||||
title: loc.send.create.details,
|
||||
headerRight: navigation.state.params.exportTXN ? (
|
||||
<TouchableOpacity style={{ marginRight: 16 }} onPress={navigation.state.params.exportTXN}>
|
||||
<Icon size={22} name="share-alternative" type="entypo" color={BlueApp.settings.foregroundColor} />
|
||||
</TouchableOpacity>
|
||||
) : null,
|
||||
});
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log('send/create constructor');
|
||||
|
||||
props.navigation.setParams({ exportTXN: this.exportTXN });
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
fee: props.navigation.getParam('fee'),
|
||||
|
@ -44,11 +55,43 @@ export default class SendCreate extends Component {
|
|||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
componentDidMount() {
|
||||
Privacy.enableBlur();
|
||||
console.log('send/create - componentDidMount');
|
||||
}
|
||||
|
||||
exportTXN = async () => {
|
||||
const fileName = `${Date.now()}.txn`;
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, this.state.tx);
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: 'BlueWallet Storage Access Permission',
|
||||
message: 'BlueWallet needs your permission to access your storage to save this transaction.',
|
||||
buttonNeutral: 'Ask Me Later',
|
||||
buttonNegative: 'Cancel',
|
||||
buttonPositive: 'OK',
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.ExternalCachesDirectoryPath + `/${this.fileName}`;
|
||||
await RNFS.writeFile(filePath, this.state.tx);
|
||||
alert(`This transaction has been saved in ${filePath}`);
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
Privacy.disableBlur();
|
||||
}
|
||||
|
@ -164,6 +207,7 @@ const styles = StyleSheet.create({
|
|||
SendCreate.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
setParams: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
|
|
|
@ -38,6 +38,9 @@ import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
|||
import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, WatchOnlyWallet } from '../../class';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { BitcoinTransaction } from '../../models/bitcoinTransactionInfo';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const bip21 = require('bip21');
|
||||
let BigNumber = require('bignumber.js');
|
||||
|
@ -135,7 +138,10 @@ export default class SendDetails extends Component {
|
|||
} else {
|
||||
let recipients = this.state.addresses;
|
||||
const dataWithoutSchema = data.replace('bitcoin:', '');
|
||||
if (btcAddressRx.test(dataWithoutSchema) || (dataWithoutSchema.indexOf('bc1') === 0 && dataWithoutSchema.indexOf('?') === -1)) {
|
||||
if (
|
||||
btcAddressRx.test(dataWithoutSchema) ||
|
||||
((dataWithoutSchema.indexOf('bc1') === 0 || dataWithoutSchema.indexOf('BC1') === 0) && dataWithoutSchema.indexOf('?') === -1)
|
||||
) {
|
||||
recipients[[this.state.recipientsScrollIndex]].address = dataWithoutSchema;
|
||||
this.setState({
|
||||
address: recipients,
|
||||
|
@ -161,7 +167,7 @@ export default class SendDetails extends Component {
|
|||
this.setState({ isLoading: false });
|
||||
}
|
||||
console.log(options);
|
||||
if (btcAddressRx.test(address) || address.indexOf('bc1') === 0) {
|
||||
if (btcAddressRx.test(address) || address.indexOf('bc1') === 0 || address.indexOf('BC1') === 0) {
|
||||
recipients[[this.state.recipientsScrollIndex]].address = address;
|
||||
recipients[[this.state.recipientsScrollIndex]].amount = options.amount;
|
||||
this.setState({
|
||||
|
@ -707,6 +713,49 @@ export default class SendDetails extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
importTransaction = async () => {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const bufferDecoded = Buffer.from(file, 'ascii').toString('base64');
|
||||
if (bufferDecoded) {
|
||||
if (this.state.fromWallet.type === WatchOnlyWallet.type) {
|
||||
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
|
||||
// user whether he wants to broadcast it.
|
||||
// alternatively, user can export psbt file, sign it externally and then import it
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
psbt: file,
|
||||
isFirstPSBTAlreadyBase64: true,
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
txhex: file,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert('The selected file does not contain a signed transaction that can be imported.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderAdvancedTransactionOptionsModal = () => {
|
||||
const isSendMaxUsed = this.state.addresses.some(element => element.amount === BitcoinUnit.MAX);
|
||||
return (
|
||||
|
@ -738,6 +787,11 @@ export default class SendDetails extends Component {
|
|||
onSwitch={this.onReplaceableFeeSwitchValueChanged}
|
||||
/>
|
||||
)}
|
||||
{this.state.fromWallet.type === WatchOnlyWallet.type &&
|
||||
this.state.fromWallet.isHd() &&
|
||||
this.state.fromWallet.getSecret().startsWith('zpub') && (
|
||||
<BlueListItem title="Import Transaction" hideChevron component={TouchableOpacity} onPress={this.importTransaction} />
|
||||
)}
|
||||
{this.state.fromWallet.allowBatchSend() && (
|
||||
<>
|
||||
<BlueListItem
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import { ActivityIndicator, TouchableOpacity, View, Dimensions, Image, TextInput, Clipboard, Linking } from 'react-native';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
View,
|
||||
Dimensions,
|
||||
Image,
|
||||
TextInput,
|
||||
Clipboard,
|
||||
Linking,
|
||||
Platform,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { Icon, Text } from 'react-native-elements';
|
||||
import {
|
||||
|
@ -13,8 +25,11 @@ import {
|
|||
BlueCopyToClipboardButton,
|
||||
} from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import Share from 'react-native-share';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { RNCamera } from 'react-native-camera';
|
||||
import RNFS from 'react-native-fs';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
let loc = require('../../loc');
|
||||
let EV = require('../../events');
|
||||
let BlueElectrum = require('../../BlueElectrum');
|
||||
|
@ -33,10 +48,19 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
|
||||
onBarCodeRead = ret => {
|
||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
|
||||
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
this.setState({ renderScanner: false, txhex: ret.data });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ renderScanner: false }, () => {
|
||||
console.log(ret.data);
|
||||
try {
|
||||
let Tx = this.state.fromWallet.combinePsbt(this.state.psbt.toBase64(), ret.data);
|
||||
let Tx = this.state.fromWallet.combinePsbt(
|
||||
this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(),
|
||||
ret.data,
|
||||
);
|
||||
this.setState({ txhex: Tx.toHex() });
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
|
@ -46,18 +70,47 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
renderScanner: false,
|
||||
qrCodeHeight: height > width ? width - 40 : width / 2,
|
||||
qrCodeHeight: height > width ? width - 40 : width / 3,
|
||||
memo: props.navigation.getParam('memo'),
|
||||
psbt: props.navigation.getParam('psbt'),
|
||||
fromWallet: props.navigation.getParam('fromWallet'),
|
||||
isFirstPSBTAlreadyBase64: props.navigation.getParam('isFirstPSBTAlreadyBase64'),
|
||||
isSecondPSBTAlreadyBase64: false,
|
||||
deepLinkPSBT: undefined,
|
||||
txhex: props.navigation.getParam('txhex') || undefined,
|
||||
};
|
||||
this.fileName = `${Date.now()}.psbt`;
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const deepLinkPSBT = nextProps.navigation.state.params.deepLinkPSBT;
|
||||
const txhex = nextProps.navigation.state.params.txhex;
|
||||
if (deepLinkPSBT) {
|
||||
try {
|
||||
let Tx = prevState.fromWallet.combinePsbt(
|
||||
prevState.isFirstPSBTAlreadyBase64 ? prevState.psbt : prevState.psbt.toBase64(),
|
||||
deepLinkPSBT,
|
||||
);
|
||||
return {
|
||||
...prevState,
|
||||
txhex: Tx.toHex(),
|
||||
};
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
}
|
||||
} else if (txhex) {
|
||||
return {
|
||||
...prevState,
|
||||
txhex: txhex,
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('send/psbtWithHardwareWallet - componentDidMount');
|
||||
}
|
||||
|
||||
|
@ -185,6 +238,56 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
exportPSBT = async () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`;
|
||||
await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64());
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: 'BlueWallet Storage Access Permission',
|
||||
message: 'BlueWallet needs your permission to access your storage to save this transaction.',
|
||||
buttonNeutral: 'Ask Me Later',
|
||||
buttonNegative: 'Cancel',
|
||||
buttonPositive: 'OK',
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.ExternalCachesDirectoryPath + `/${this.fileName}`;
|
||||
await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64());
|
||||
alert(`This transaction has been saved in ${filePath}`);
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
openSignedTransaction = async () => {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
const file = await RNFS.readFile(res.uri);
|
||||
if (file) {
|
||||
this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: file }));
|
||||
} else {
|
||||
this.setState({ isSecondPSBTAlreadyBase64: false });
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert('The selected file does not contain a signed transaction that can be imported.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
|
@ -200,27 +303,58 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1 }}>
|
||||
<View style={{ alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<ScrollView centerContent contentContainerStyle={{ flexGrow: 1, justifyContent: 'space-between' }}>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
|
||||
<BlueCard>
|
||||
<BlueText>This is partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet.</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<QRCode
|
||||
value={this.state.psbt.toBase64()}
|
||||
value={this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}
|
||||
size={this.state.qrCodeHeight}
|
||||
color={BlueApp.settings.foregroundColor}
|
||||
logoBackgroundColor={BlueApp.settings.brandingColor}
|
||||
ecl={'L'}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<BlueButton onPress={() => this.setState({ renderScanner: true })} title={'Scan signed transaction'} />
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'qrcode',
|
||||
type: 'font-awesome',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
onPress={() => this.setState({ renderScanner: true })}
|
||||
title={'Scan Signed Transaction'}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'file-import',
|
||||
type: 'material-community',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
onPress={this.openSignedTransaction}
|
||||
title={'Open Signed Transaction'}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'share-alternative',
|
||||
type: 'entypo',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
onPress={this.exportPSBT}
|
||||
title={'Export to file'}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<View style={{ justifyContent: 'center', alignItems: 'center' }}>
|
||||
<BlueCopyToClipboardButton stringToCopy={this.state.psbt.toBase64()} displayText={'Copy to Clipboard'} />
|
||||
<BlueCopyToClipboardButton
|
||||
stringToCopy={this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}
|
||||
displayText={'Copy to Clipboard'}
|
||||
/>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,16 +6,20 @@ import { Icon } from 'react-native-elements';
|
|||
import ImagePicker from 'react-native-image-picker';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useNavigationParam, useNavigation } from 'react-navigation-hooks';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
|
||||
const ScanQRCode = ({
|
||||
onBarScanned = useNavigationParam('onBarScanned'),
|
||||
cameraPreviewIsPaused = false,
|
||||
showCloseButton = true,
|
||||
showFileImportButton = useNavigationParam('showFileImportButton') || false,
|
||||
launchedBy = useNavigationParam('launchedBy'),
|
||||
}) => {
|
||||
if (!launchedBy || !onBarScanned) console.warn('Necessary params missing');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { navigate } = useNavigation();
|
||||
const { navigate, goBack } = useNavigation();
|
||||
|
||||
const onBarCodeRead = ret => {
|
||||
if (!isLoading && !cameraPreviewIsPaused) {
|
||||
|
@ -24,7 +28,11 @@ const ScanQRCode = ({
|
|||
if (showCloseButton && launchedBy) {
|
||||
navigate(launchedBy);
|
||||
}
|
||||
onBarScanned(ret.data);
|
||||
if (ret.additionalProperties) {
|
||||
onBarScanned(ret.data, ret.additionalProperties);
|
||||
} else {
|
||||
onBarScanned(ret.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
@ -32,6 +40,30 @@ const ScanQRCode = ({
|
|||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const showFilePicker = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await DocumentPicker.pick();
|
||||
const file = await RNFS.readFile(res.uri);
|
||||
const fileParsed = JSON.parse(file);
|
||||
if (fileParsed.keystore.xpub) {
|
||||
let masterFingerprint;
|
||||
if (fileParsed.keystore.ckcc_xfp) {
|
||||
masterFingerprint = Number(fileParsed.keystore.ckcc_xfp);
|
||||
}
|
||||
onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint } });
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert('The selected file does not contain a wallet that can be imported.');
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {}, [cameraPreviewIsPaused]);
|
||||
|
||||
return (
|
||||
|
@ -62,7 +94,7 @@ const ScanQRCode = ({
|
|||
right: 16,
|
||||
top: 64,
|
||||
}}
|
||||
onPress={() => navigate(launchedBy)}
|
||||
onPress={() => (launchedBy ? navigate(launchedBy) : goBack(null))}
|
||||
>
|
||||
<Image style={{ alignSelf: 'center' }} source={require('../../img/close-white.png')} />
|
||||
</TouchableOpacity>
|
||||
|
@ -106,6 +138,23 @@ const ScanQRCode = ({
|
|||
>
|
||||
<Icon name="image" type="font-awesome" color="#0c2550" />
|
||||
</TouchableOpacity>
|
||||
{showFileImportButton && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: '#FFFFFF',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 20,
|
||||
position: 'absolute',
|
||||
left: 96,
|
||||
bottom: 48,
|
||||
}}
|
||||
onPress={showFilePicker}
|
||||
>
|
||||
<Icon name="file-import" type="material-community" color="#0c2550" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -117,6 +166,7 @@ ScanQRCode.propTypes = {
|
|||
launchedBy: PropTypes.string,
|
||||
onBarScanned: PropTypes.func,
|
||||
cameraPreviewIsPaused: PropTypes.bool,
|
||||
showFileImportButton: PropTypes.bool,
|
||||
showCloseButton: PropTypes.bool,
|
||||
};
|
||||
export default ScanQRCode;
|
||||
|
|
|
@ -203,7 +203,7 @@ export default class SendCreate extends Component {
|
|||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueSpacing />
|
||||
<BlueCard title={'Replace Transaction'} style={{ alignItems: 'center', flex: 1 }}>
|
||||
<BlueText>This is transaction hex, signed and ready to be broadcast to the network. Continue?</BlueText>
|
||||
<BlueText>This is your transaction's hex, signed and ready to be broadcasted to the network. Continue?</BlueText>
|
||||
|
||||
<TextInput
|
||||
style={{
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
Text,
|
||||
TextInput,
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
TouchableOpacity,
|
||||
Keyboard,
|
||||
TouchableWithoutFeedback,
|
||||
|
@ -19,12 +20,13 @@ import { HDLegacyP2PKHWallet } from '../../class/hd-legacy-p2pkh-wallet';
|
|||
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||
let EV = require('../../events');
|
||||
let prompt = require('../../prompt');
|
||||
import { HDSegwitBech32Wallet, SegwitP2SHWallet, LegacyWallet, SegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||
import { ScrollView } from 'react-native-gesture-handler';
|
||||
const EV = require('../../events');
|
||||
const prompt = require('../../prompt');
|
||||
/** @type {AppStorage} */
|
||||
let BlueApp = require('../../BlueApp');
|
||||
let loc = require('../../loc');
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const loc = require('../../loc');
|
||||
|
||||
export default class WalletDetails extends Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
|
@ -54,7 +56,7 @@ export default class WalletDetails extends Component {
|
|||
isLoading,
|
||||
walletName: wallet.getLabel(),
|
||||
wallet,
|
||||
useWithHardwareWallet: !!wallet.use_with_hardware_wallet,
|
||||
useWithHardwareWallet: wallet.useWithHardwareWalletEnabled(),
|
||||
};
|
||||
this.props.navigation.setParams({ isLoading, saveAction: () => this.setLabel() });
|
||||
}
|
||||
|
@ -70,7 +72,9 @@ export default class WalletDetails extends Component {
|
|||
setLabel() {
|
||||
this.props.navigation.setParams({ isLoading: true });
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
this.state.wallet.setLabel(this.state.walletName);
|
||||
if (this.state.walletName.trim().length > 0) {
|
||||
this.state.wallet.setLabel(this.state.walletName);
|
||||
}
|
||||
BlueApp.saveToDisk();
|
||||
alert('Wallet updated.');
|
||||
this.props.navigation.goBack(null);
|
||||
|
@ -106,7 +110,7 @@ export default class WalletDetails extends Component {
|
|||
async onUseWithHardwareWalletSwitch(value) {
|
||||
this.setState((state, props) => {
|
||||
let wallet = state.wallet;
|
||||
wallet.use_with_hardware_wallet = !!value;
|
||||
wallet.setUseWithHardwareWalletEnabled(value);
|
||||
return { useWithHardwareWallet: !!value, wallet };
|
||||
});
|
||||
}
|
||||
|
@ -122,170 +126,188 @@ export default class WalletDetails extends Component {
|
|||
return (
|
||||
<SafeBlueArea style={{ flex: 1 }}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
|
||||
{(() => {
|
||||
if (this.state.wallet.getAddress()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>
|
||||
{loc.wallets.details.address.toLowerCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.getAddress()}</Text>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 16 }}>
|
||||
{loc.wallets.add.wallet_name.toLowerCase()}
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderColor: '#d2d2d2',
|
||||
borderBottomColor: '#d2d2d2',
|
||||
borderWidth: 1.0,
|
||||
borderBottomWidth: 0.5,
|
||||
backgroundColor: '#f5f5f5',
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
placeholder={loc.send.details.note_placeholder}
|
||||
value={this.state.walletName}
|
||||
onChangeText={text => {
|
||||
if (text.trim().length === 0) {
|
||||
text = this.state.wallet.getLabel();
|
||||
}
|
||||
this.setState({ walletName: text });
|
||||
}}
|
||||
numberOfLines={1}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.state.isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>
|
||||
{loc.wallets.details.type.toLowerCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.typeReadable}</Text>
|
||||
{this.state.wallet.type === LightningCustodianWallet.type && (
|
||||
<React.Fragment>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>{'connected to'}</Text>
|
||||
<BlueText>{this.state.wallet.getBaseURI()}</BlueText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<View>
|
||||
<BlueSpacing20 />
|
||||
|
||||
{this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && (
|
||||
<React.Fragment>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<BlueText>{'Use with hardware wallet'}</BlueText>
|
||||
<Switch value={this.state.useWithHardwareWallet} onValueChange={value => this.onUseWithHardwareWalletSwitch(value)} />
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.state.wallet.getAddress(),
|
||||
secret: this.state.wallet.getSecret(),
|
||||
})
|
||||
<KeyboardAvoidingView behavior="position">
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
|
||||
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
|
||||
{(() => {
|
||||
if (
|
||||
[LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(this.state.wallet.type) ||
|
||||
(this.state.wallet.type === WatchOnlyWallet.type && !this.state.wallet.isHd())
|
||||
) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>
|
||||
{loc.wallets.details.address.toLowerCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.getAddress()}</Text>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
title={loc.wallets.details.export_backup}
|
||||
/>
|
||||
})()}
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 16 }}>
|
||||
{loc.wallets.add.wallet_name.toLowerCase()}
|
||||
</Text>
|
||||
|
||||
<BlueSpacing20 />
|
||||
|
||||
{(this.state.wallet.type === HDLegacyBreadwalletWallet.type ||
|
||||
this.state.wallet.type === HDLegacyP2PKHWallet.type ||
|
||||
this.state.wallet.type === HDSegwitBech32Wallet.type ||
|
||||
this.state.wallet.type === HDSegwitP2SHWallet.type) && (
|
||||
<React.Fragment>
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('WalletXpub', {
|
||||
secret: this.state.wallet.getSecret(),
|
||||
})
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderColor: '#d2d2d2',
|
||||
borderBottomColor: '#d2d2d2',
|
||||
borderWidth: 1.0,
|
||||
borderBottomWidth: 0.5,
|
||||
backgroundColor: '#f5f5f5',
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
placeholder={loc.send.details.note_placeholder}
|
||||
value={this.state.walletName}
|
||||
onChangeText={text => {
|
||||
this.setState({ walletName: text });
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (this.state.walletName.trim().length === 0) {
|
||||
const walletLabel = this.state.wallet.getLabel();
|
||||
this.setState({ walletName: walletLabel });
|
||||
}
|
||||
title={loc.wallets.details.show_xpub}
|
||||
/>
|
||||
|
||||
<BlueSpacing20 />
|
||||
}}
|
||||
numberOfLines={1}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.state.isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>
|
||||
{loc.wallets.details.type.toLowerCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>{this.state.wallet.typeReadable}</Text>
|
||||
{this.state.wallet.type === LightningCustodianWallet.type && (
|
||||
<React.Fragment>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>{'connected to'}</Text>
|
||||
<BlueText>{this.state.wallet.getBaseURI()}</BlueText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<View>
|
||||
<BlueSpacing20 />
|
||||
{this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && (
|
||||
<>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 16 }}>{'advanced'}</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<BlueText>{'Use with hardware wallet'}</BlueText>
|
||||
<Switch
|
||||
value={this.state.useWithHardwareWallet}
|
||||
onValueChange={value => this.onUseWithHardwareWalletSwitch(value)}
|
||||
/>
|
||||
</View>
|
||||
<React.Fragment>
|
||||
<Text style={{ color: '#0c2550', fontWeight: '500', fontSize: 14, marginVertical: 12 }}>
|
||||
{loc.wallets.details.master_fingerprint.toLowerCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#81868e', fontWeight: '500', fontSize: 14 }}>
|
||||
{this.state.wallet.getMasterFingerprintHex()}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.wallet.type !== LightningCustodianWallet.type && (
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'shopping-cart',
|
||||
type: 'font-awesome',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('BuyBitcoin', {
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.state.wallet.getAddress(),
|
||||
secret: this.state.wallet.getSecret(),
|
||||
})
|
||||
}
|
||||
title={loc.wallets.details.buy_bitcoin}
|
||||
title={loc.wallets.details.export_backup}
|
||||
/>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
|
||||
<TouchableOpacity
|
||||
style={{ alignItems: 'center' }}
|
||||
onPress={() => {
|
||||
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
|
||||
Alert.alert(
|
||||
loc.wallets.details.delete + ' ' + loc.wallets.details.title,
|
||||
loc.wallets.details.are_you_sure,
|
||||
[
|
||||
{
|
||||
text: loc.wallets.details.yes_delete,
|
||||
onPress: async () => {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
<BlueSpacing20 />
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return;
|
||||
{(this.state.wallet.type === HDLegacyBreadwalletWallet.type ||
|
||||
this.state.wallet.type === HDLegacyP2PKHWallet.type ||
|
||||
this.state.wallet.type === HDSegwitBech32Wallet.type ||
|
||||
this.state.wallet.type === HDSegwitP2SHWallet.type) && (
|
||||
<React.Fragment>
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('WalletXpub', {
|
||||
secret: this.state.wallet.getSecret(),
|
||||
})
|
||||
}
|
||||
title={loc.wallets.details.show_xpub}
|
||||
/>
|
||||
|
||||
<BlueSpacing20 />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{this.state.wallet.type !== LightningCustodianWallet.type && (
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'shopping-cart',
|
||||
type: 'font-awesome',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('BuyBitcoin', {
|
||||
address: this.state.wallet.getAddress(),
|
||||
secret: this.state.wallet.getSecret(),
|
||||
})
|
||||
}
|
||||
title={loc.wallets.details.buy_bitcoin}
|
||||
/>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
|
||||
<TouchableOpacity
|
||||
style={{ alignItems: 'center' }}
|
||||
onPress={() => {
|
||||
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
|
||||
Alert.alert(
|
||||
loc.wallets.details.delete + ' ' + loc.wallets.details.title,
|
||||
loc.wallets.details.are_you_sure,
|
||||
[
|
||||
{
|
||||
text: loc.wallets.details.yes_delete,
|
||||
onPress: async () => {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.state.wallet.getBalance() > 0) {
|
||||
this.presentWalletHasBalanceAlert();
|
||||
} else {
|
||||
this.props.navigation.setParams({ isLoading: true });
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
BlueApp.deleteWallet(this.state.wallet);
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
await BlueApp.saveToDisk();
|
||||
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
this.props.navigation.navigate('Wallets');
|
||||
});
|
||||
}
|
||||
if (this.state.wallet.getBalance() > 0 && this.state.wallet.allowSend()) {
|
||||
this.presentWalletHasBalanceAlert();
|
||||
} else {
|
||||
this.props.navigation.setParams({ isLoading: true });
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
BlueApp.deleteWallet(this.state.wallet);
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
await BlueApp.saveToDisk();
|
||||
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
this.props.navigation.navigate('Wallets');
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
style: 'destructive',
|
||||
},
|
||||
{ text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' },
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#d0021b', fontSize: 15, fontWeight: '500' }}>{loc.wallets.details.delete}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</View>
|
||||
{ text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' },
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#d0021b', fontSize: 15, fontWeight: '500' }}>{loc.wallets.details.delete}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
|
|
|
@ -35,9 +35,14 @@ const WalletsImport = () => {
|
|||
importMnemonic(importText);
|
||||
};
|
||||
|
||||
const importMnemonic = importText => {
|
||||
/**
|
||||
*
|
||||
* @param importText
|
||||
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
|
||||
*/
|
||||
const importMnemonic = (importText, additionalProperties) => {
|
||||
try {
|
||||
WalletImport.processImportText(importText);
|
||||
WalletImport.processImportText(importText, additionalProperties);
|
||||
dismiss();
|
||||
} catch (error) {
|
||||
alert(loc.wallets.import.error);
|
||||
|
@ -45,9 +50,14 @@ const WalletsImport = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const onBarScanned = value => {
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
|
||||
*/
|
||||
const onBarScanned = (value, additionalProperties) => {
|
||||
setImportText(value);
|
||||
importMnemonic(value);
|
||||
importMnemonic(value, additionalProperties);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -110,7 +120,7 @@ const WalletsImport = () => {
|
|||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
navigate('ScanQrAddress', { onBarScanned });
|
||||
navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
View,
|
||||
|
@ -68,7 +67,7 @@ export default class WalletsList extends Component {
|
|||
console.log('fetch all wallet txs took', (end - start) / 1000, 'sec');
|
||||
} catch (error) {
|
||||
noErr = false;
|
||||
alert(error);
|
||||
console.log(error);
|
||||
}
|
||||
if (noErr) this.redrawScreen();
|
||||
});
|
||||
|
@ -111,7 +110,6 @@ export default class WalletsList extends Component {
|
|||
console.log('fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
alert(err);
|
||||
console.warn(err);
|
||||
}
|
||||
if (noErr) await BlueApp.saveToDisk(); // caching
|
||||
|
@ -262,7 +260,6 @@ export default class WalletsList extends Component {
|
|||
}
|
||||
} catch (Err) {
|
||||
noErr = false;
|
||||
alert(Err);
|
||||
console.warn(Err);
|
||||
}
|
||||
|
||||
|
@ -338,16 +335,7 @@ export default class WalletsList extends Component {
|
|||
}}
|
||||
onWillBlur={() => this.setState({ cameraPreviewIsPaused: true })}
|
||||
/>
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
onRefresh={() => this.refreshTransactions()}
|
||||
refreshing={!this.state.isFlatListRefreshControlHidden}
|
||||
shouldRefresh={this.state.timeElpased}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScrollView contentContainerStyle={{ flex: 1 }}>
|
||||
<Swiper
|
||||
style={styles.wrapper}
|
||||
onIndexChanged={this.onSwiperIndexChanged}
|
||||
|
|
|
@ -7,13 +7,12 @@ import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet
|
|||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import WalletGradient from '../../class/walletGradient';
|
||||
import { useNavigationParam } from 'react-navigation-hooks';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
/** @type {AppStorage} */
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const loc = require('../../loc');
|
||||
|
||||
const SelectWallet = () => {
|
||||
const chainType = useNavigationParam('chainType') || Chain.ONCHAIN;
|
||||
const chainType = useNavigationParam('chainType');
|
||||
const onWalletSelect = useNavigationParam('onWalletSelect');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const data = chainType
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
StatusBar,
|
||||
Linking,
|
||||
KeyboardAvoidingView,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
|
@ -29,7 +30,7 @@ import {
|
|||
} from '../../BlueComponents';
|
||||
import WalletGradient from '../../class/walletGradient';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { LightningCustodianWallet } from '../../class';
|
||||
import { LightningCustodianWallet, WatchOnlyWallet } from '../../class';
|
||||
import Handoff from 'react-native-handoff';
|
||||
import Modal from 'react-native-modal';
|
||||
import NavigationService from '../../NavigationService';
|
||||
|
@ -400,7 +401,7 @@ export default class WalletTransactions extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
async onWillBlur() {
|
||||
onWillBlur() {
|
||||
StatusBar.setBarStyle('dark-content');
|
||||
}
|
||||
|
||||
|
@ -409,6 +410,14 @@ export default class WalletTransactions extends Component {
|
|||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
navigateToSendScreen = () => {
|
||||
this.props.navigation.navigate('SendDetails', {
|
||||
fromAddress: this.state.wallet.getAddress(),
|
||||
fromSecret: this.state.wallet.getSecret(),
|
||||
fromWallet: this.state.wallet,
|
||||
});
|
||||
};
|
||||
|
||||
renderItem = item => {
|
||||
return (
|
||||
<BlueTransactionListItem
|
||||
|
@ -569,18 +578,51 @@ export default class WalletTransactions extends Component {
|
|||
})()}
|
||||
|
||||
{(() => {
|
||||
if (this.state.wallet.allowSend()) {
|
||||
if (
|
||||
this.state.wallet.allowSend() ||
|
||||
(this.state.wallet.type === WatchOnlyWallet.type &&
|
||||
this.state.wallet.isHd() &&
|
||||
this.state.wallet.getSecret().startsWith('zpub'))
|
||||
) {
|
||||
return (
|
||||
<BlueSendButtonIcon
|
||||
onPress={() => {
|
||||
if (this.state.wallet.chain === Chain.OFFCHAIN) {
|
||||
navigate('ScanLndInvoice', { fromSecret: this.state.wallet.getSecret() });
|
||||
} else {
|
||||
navigate('SendDetails', {
|
||||
fromAddress: this.state.wallet.getAddress(),
|
||||
fromSecret: this.state.wallet.getSecret(),
|
||||
fromWallet: this.state.wallet,
|
||||
});
|
||||
if (
|
||||
this.state.wallet.type === WatchOnlyWallet.type &&
|
||||
this.state.wallet.isHd() &&
|
||||
this.state.wallet.getSecret().startsWith('zpub')
|
||||
) {
|
||||
if (this.state.wallet.useWithHardwareWalletEnabled()) {
|
||||
this.navigateToSendScreen();
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Wallet',
|
||||
'This wallet is not being used in conjunction with a hardwarde wallet. Would you like to enable hardware wallet use?',
|
||||
[
|
||||
{
|
||||
text: loc._.ok,
|
||||
onPress: () => {
|
||||
const wallet = this.state.wallet;
|
||||
wallet.setUseWithHardwareWalletEnabled(true);
|
||||
this.setState({ wallet }, async () => {
|
||||
await BlueApp.saveToDisk();
|
||||
this.navigateToSendScreen();
|
||||
});
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
|
||||
{ text: loc.send.details.cancel, onPress: () => {}, style: 'cancel' },
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.navigateToSendScreen();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('LightningCustodianWallet', () => {
|
|||
let l1 = new LightningCustodianWallet();
|
||||
|
||||
it.skip('issue credentials', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
assert.ok(l1.refill_addressess.length === 0);
|
||||
assert.ok(l1._refresh_token_created_ts === 0);
|
||||
assert.ok(l1._access_token_created_ts === 0);
|
||||
|
@ -24,7 +24,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('can create, auth and getbtc', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
assert.ok(l1.refill_addressess.length === 0);
|
||||
assert.ok(l1._refresh_token_created_ts === 0);
|
||||
assert.ok(l1._access_token_created_ts === 0);
|
||||
|
@ -51,7 +51,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('can refresh token', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
let oldRefreshToken = l1.refresh_token;
|
||||
let oldAccessToken = l1.access_token;
|
||||
await l1.refreshAcessToken();
|
||||
|
@ -62,7 +62,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('can use existing login/pass', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
|
@ -100,11 +100,12 @@ describe('LightningCustodianWallet', () => {
|
|||
|
||||
let invoice =
|
||||
'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy';
|
||||
let decoded = await l2.decodeInvoice(invoice);
|
||||
let decoded = l2.decodeInvoice(invoice);
|
||||
|
||||
assert.ok(decoded.payment_hash);
|
||||
assert.ok(decoded.description);
|
||||
assert.ok(decoded.num_satoshis);
|
||||
assert.strictEqual(parseInt(decoded.num_satoshis) * 1000, parseInt(decoded.num_millisatoshis));
|
||||
|
||||
await l2.checkRouteInvoice(invoice);
|
||||
|
||||
|
@ -112,15 +113,44 @@ describe('LightningCustodianWallet', () => {
|
|||
invoice = 'gsom';
|
||||
let error = false;
|
||||
try {
|
||||
await l2.decodeInvoice(invoice);
|
||||
l2.decodeInvoice(invoice);
|
||||
} catch (Err) {
|
||||
error = true;
|
||||
}
|
||||
assert.ok(error);
|
||||
});
|
||||
|
||||
it('decode can handle zero sats but present msats', async () => {
|
||||
let l = new LightningCustodianWallet();
|
||||
let decoded = l.decodeInvoice(
|
||||
'lnbc89n1p0zptvhpp5j3h5e80vdlzn32df8y80nl2t7hssn74lzdr96ve0u4kpaupflx2sdphgfkx7cmtwd68yetpd5s9xct5v4kxc6t5v5s9gunpdeek66tnwd5k7mscqp2sp57m89zv0lrgc9zzaxy5p3d5rr2cap2pm6zm4n0ew9vyp2d5zf2mfqrzjqfxj8p6qjf5l8du7yuytkwdcjhylfd4gxgs48t65awjg04ye80mq7z990yqq9jsqqqqqqqqqqqqq05qqrc9qy9qsq9mynpa9ucxg53hwnvw323r55xdd3l6lcadzs584zvm4wdw5pv3eksdlcek425pxaqrn9u5gpw0dtpyl9jw2pynjtqexxgh50akwszjgq4ht4dh',
|
||||
);
|
||||
assert.strictEqual(decoded.num_satoshis, '8.9');
|
||||
});
|
||||
|
||||
it('can decode invoice locally & remotely', async () => {
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
}
|
||||
let l2 = new LightningCustodianWallet();
|
||||
l2.setSecret(process.env.BLITZHUB);
|
||||
await l2.authorize();
|
||||
let invoice =
|
||||
'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy';
|
||||
let decodedLocally = l2.decodeInvoice(invoice);
|
||||
let decodedRemotely = await l2.decodeInvoiceRemote(invoice);
|
||||
assert.strictEqual(decodedLocally.destination, decodedRemotely.destination);
|
||||
assert.strictEqual(decodedLocally.num_satoshis, decodedRemotely.num_satoshis);
|
||||
assert.strictEqual(decodedLocally.timestamp, decodedRemotely.timestamp);
|
||||
assert.strictEqual(decodedLocally.expiry, decodedRemotely.expiry);
|
||||
assert.strictEqual(decodedLocally.payment_hash, decodedRemotely.payment_hash);
|
||||
assert.strictEqual(decodedLocally.description, decodedRemotely.description);
|
||||
assert.strictEqual(decodedLocally.cltv_expiry, decodedRemotely.cltv_expiry);
|
||||
});
|
||||
|
||||
it('can pay invoice', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
|
@ -155,7 +185,7 @@ describe('LightningCustodianWallet', () => {
|
|||
await l2.fetchTransactions();
|
||||
let txLen = l2.transactions_raw.length;
|
||||
|
||||
let decoded = await l2.decodeInvoice(invoice);
|
||||
let decoded = l2.decodeInvoice(invoice);
|
||||
assert.ok(decoded.payment_hash);
|
||||
assert.ok(decoded.description);
|
||||
|
||||
|
@ -194,7 +224,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('can create invoice and pay other blitzhub invoice', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
|
@ -294,7 +324,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('can pay free amount (tip) invoice', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
|
@ -336,7 +366,7 @@ describe('LightningCustodianWallet', () => {
|
|||
let oldBalance = +l2.balance;
|
||||
let txLen = l2.transactions_raw.length;
|
||||
|
||||
let decoded = await l2.decodeInvoice(invoice);
|
||||
let decoded = l2.decodeInvoice(invoice);
|
||||
assert.ok(decoded.payment_hash);
|
||||
assert.ok(decoded.description);
|
||||
assert.strictEqual(+decoded.num_satoshis, 0);
|
||||
|
@ -371,7 +401,7 @@ describe('LightningCustodianWallet', () => {
|
|||
|
||||
it('cant create zemo amt invoices yet', async () => {
|
||||
let l1 = new LightningCustodianWallet();
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
assert.ok(l1.refill_addressess.length === 0);
|
||||
assert.ok(l1._refresh_token_created_ts === 0);
|
||||
assert.ok(l1._access_token_created_ts === 0);
|
||||
|
@ -405,7 +435,7 @@ describe('LightningCustodianWallet', () => {
|
|||
});
|
||||
|
||||
it('cant pay negative free amount', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
if (!process.env.BLITZHUB) {
|
||||
console.error('process.env.BLITZHUB not set, skipped');
|
||||
return;
|
||||
|
@ -443,7 +473,7 @@ describe('LightningCustodianWallet', () => {
|
|||
let oldBalance = +l2.balance;
|
||||
let txLen = l2.transactions_raw.length;
|
||||
|
||||
let decoded = await l2.decodeInvoice(invoice);
|
||||
let decoded = l2.decodeInvoice(invoice);
|
||||
assert.ok(decoded.payment_hash);
|
||||
assert.ok(decoded.description);
|
||||
assert.strictEqual(+decoded.num_satoshis, 0);
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
/* global it, describe */
|
||||
let assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
describe('Localization', () => {
|
||||
it('has all keys in all locales', async () => {
|
||||
let en = require('../../loc/en');
|
||||
let noErrors = true;
|
||||
let issues = 0;
|
||||
for (let key1 of Object.keys(en)) {
|
||||
for (let key2 of Object.keys(en[key1])) {
|
||||
// iterating all keys and subkeys in EN locale, which is main
|
||||
let files = fs.readdirSync('./loc/');
|
||||
|
||||
for (let lang of files) {
|
||||
if (lang === 'en.js') continue; // iteratin all locales except EN
|
||||
if (lang === 'index.js') continue;
|
||||
|
||||
for (let lang of ['es', 'pt_BR', 'pt_PT', 'ru', 'ua']) {
|
||||
// iteratin all locales except EN
|
||||
let locale = require('../../loc/' + lang);
|
||||
|
||||
if (typeof locale[key1] === 'undefined') {
|
||||
console.error('Missing: ' + lang + '.' + key1);
|
||||
noErrors = false;
|
||||
issues++;
|
||||
} else if (typeof locale[key1][key2] === 'undefined') {
|
||||
console.error('Missing: ' + lang + '.' + key1 + '.' + key2);
|
||||
noErrors = false;
|
||||
issues++;
|
||||
}
|
||||
|
||||
// level 1 & 2 done, doing level 3 (if it exists):
|
||||
|
@ -27,13 +31,13 @@ describe('Localization', () => {
|
|||
for (let key3 of Object.keys(en[key1][key2])) {
|
||||
if (typeof locale[key1][key2][key3] === 'undefined') {
|
||||
console.error('Missing: ' + lang + '.' + key1 + '.' + key2 + '.' + key3);
|
||||
noErrors = false;
|
||||
issues++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.ok(noErrors, 'Some localizations are missing keys');
|
||||
assert.ok(issues === 0, 'Some localizations are missing keys. Total issues: ' + issues);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -56,8 +56,10 @@ describe('Watch only wallet', () => {
|
|||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), false);
|
||||
w.setSecret('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), false);
|
||||
w.setSecret('not valid');
|
||||
assert.ok(!w.valid());
|
||||
|
||||
|
@ -67,6 +69,9 @@ describe('Watch only wallet', () => {
|
|||
assert.ok(w.valid());
|
||||
w.setSecret('zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), true);
|
||||
assert.strictEqual(w.getMasterFingerprint(), false);
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '00000000');
|
||||
});
|
||||
|
||||
it('can fetch balance & transactions from zpub HD', async () => {
|
||||
|
@ -111,6 +116,47 @@ describe('Watch only wallet', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('can import coldcard/electrum compatible JSON skeleton wallet, and create a tx with master fingerprint', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120 * 1000;
|
||||
const skeleton =
|
||||
'{"keystore": {"ckcc_xpub": "xpub661MyMwAqRbcGmUDQVKxmhEESB5xTk8hbsdTSV3Pmhm3HE9Fj3s45R9Y8LwyaQWjXXPytZjuhTKSyCBPeNrB1VVWQq1HCvjbEZ27k44oNmg", "xpub": "zpub6rFDtF1nuXZ9PUL4XzKURh3vJBW6Kj6TUrYL4qPtFNtDXtcTVfiqjQDyrZNwjwzt5HS14qdqo3Co2282Lv3Re6Y5wFZxAVuMEpeygnnDwfx", "label": "Coldcard Import 168DD603", "ckcc_xfp": 64392470, "type": "hardware", "hw_type": "coldcard", "derivation": "m/84\'/0\'/0\'"}, "wallet_type": "standard", "use_encryption": false, "seed_version": 17}';
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret(skeleton);
|
||||
w.init();
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(
|
||||
w.getSecret(),
|
||||
'zpub6rFDtF1nuXZ9PUL4XzKURh3vJBW6Kj6TUrYL4qPtFNtDXtcTVfiqjQDyrZNwjwzt5HS14qdqo3Co2282Lv3Re6Y5wFZxAVuMEpeygnnDwfx',
|
||||
);
|
||||
assert.strictEqual(w.getMasterFingerprint(), 64392470);
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '168dd603');
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
height: 618811,
|
||||
value: 66600,
|
||||
address: 'bc1qzqjwye4musmz56cg44ttnchj49zueh9yr0qsxt',
|
||||
txId: '5df595dc09ee7a5c245b34ea519288137ffee731629c4ff322a6de4f72c06222',
|
||||
vout: 0,
|
||||
txid: '5df595dc09ee7a5c245b34ea519288137ffee731629c4ff322a6de4f72c06222',
|
||||
amount: 66600,
|
||||
wif: false,
|
||||
confirmations: 1,
|
||||
},
|
||||
];
|
||||
|
||||
let { psbt } = await w.createTransaction(
|
||||
utxos,
|
||||
[{ address: 'bc1qdamevhw3zwm0ajsmyh39x8ygf0jr0syadmzepn', value: 5000 }],
|
||||
22,
|
||||
'bc1qtutssamysdkgd87df0afjct0mztx56qpze7wqe',
|
||||
);
|
||||
assert.strictEqual(
|
||||
psbt.toBase64(),
|
||||
'cHNidP8BAHECAAAAASJiwHJP3qYi80+cYjHn/n8TiJJR6jRbJFx67gnclfVdAAAAAAAAAACAAogTAAAAAAAAFgAUb3eWXdETtv7KGyXiUxyIS+Q3wJ1K3QAAAAAAABYAFF8XCHdkg2yGn81L+plhb9iWamgBAAAAAAABAR8oBAEAAAAAABYAFBAk4ma75DYqawitVrni8qlFzNykIgYDNK9TxoCjQ8P0+qI2Hu4hrnXnJuYAC3h2puZbgRORp+sYFo3WA1QAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgL1DWeV+AfIP5RRB5zHv5vuXsIt8+rF9rrsji3FhQlhzBgWjdYDVAAAgAAAAIAAAACAAQAAAAAAAAAA',
|
||||
);
|
||||
});
|
||||
|
||||
it('can combine signed PSBT and prepare it for broadcast', async () => {
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('zpub6rjLjQVqVnj7crz9E4QWj4WgczmEseJq22u2B6k2HZr6NE2PQx3ZYg8BnbjN9kCfHymSeMd2EpwpM5iiz5Nrb3TzvddxW2RMcE3VXdVaXHk');
|
||||
|
|
|
@ -4,12 +4,15 @@ const assert = require('assert');
|
|||
|
||||
describe('unit - DeepLinkSchemaMatch', function() {
|
||||
it('hasSchema', () => {
|
||||
const hasSchema = DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
|
||||
assert.ok(hasSchema);
|
||||
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
|
||||
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo'));
|
||||
assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7?amount=666&label=Yo'));
|
||||
});
|
||||
|
||||
it('isBitcoin Address', () => {
|
||||
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
|
||||
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('bc1qykcp2x3djgdtdwelxn9z4j2y956npte0a4sref'));
|
||||
assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('BC1QYKCP2X3DJGDTDWELXN9Z4J2Y956NPTE0A4SREF'));
|
||||
});
|
||||
|
||||
it('isLighting Invoice', () => {
|
||||
|
@ -36,6 +39,11 @@ describe('unit - DeepLinkSchemaMatch', function() {
|
|||
);
|
||||
});
|
||||
|
||||
it('isSafelloRedirect', () => {
|
||||
assert.ok(DeeplinkSchemaMatch.isSafelloRedirect({ url: 'bluewallet:?safello-state-token=TEST' }));
|
||||
assert.ok(!DeeplinkSchemaMatch.isSafelloRedirect({ url: 'bluewallet:' }));
|
||||
});
|
||||
|
||||
it('navigationForRoute', () => {
|
||||
const event = { uri: '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG' };
|
||||
DeeplinkSchemaMatch.navigationRouteFor(event, navValue => {
|
||||
|
|
|
@ -213,7 +213,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||
console.error('process.env.FAULTY_ZPUB not set, skipped');
|
||||
return;
|
||||
}
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd._xpub = process.env.FAULTY_ZPUB;
|
||||
|
||||
|
|
|
@ -19,4 +19,50 @@ jest.mock('react-native-default-preference', () => {
|
|||
setName: jest.fn(),
|
||||
set: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('react-native-fs', () => {
|
||||
return {
|
||||
mkdir: jest.fn(),
|
||||
moveFile: jest.fn(),
|
||||
copyFile: jest.fn(),
|
||||
pathForBundle: jest.fn(),
|
||||
pathForGroup: jest.fn(),
|
||||
getFSInfo: jest.fn(),
|
||||
getAllExternalFilesDirs: jest.fn(),
|
||||
unlink: jest.fn(),
|
||||
exists: jest.fn(),
|
||||
stopDownload: jest.fn(),
|
||||
resumeDownload: jest.fn(),
|
||||
isResumable: jest.fn(),
|
||||
stopUpload: jest.fn(),
|
||||
completeHandlerIOS: jest.fn(),
|
||||
readDir: jest.fn(),
|
||||
readDirAssets: jest.fn(),
|
||||
existsAssets: jest.fn(),
|
||||
readdir: jest.fn(),
|
||||
setReadable: jest.fn(),
|
||||
stat: jest.fn(),
|
||||
readFile: jest.fn(),
|
||||
read: jest.fn(),
|
||||
readFileAssets: jest.fn(),
|
||||
hash: jest.fn(),
|
||||
copyFileAssets: jest.fn(),
|
||||
copyFileAssetsIOS: jest.fn(),
|
||||
copyAssetsVideoIOS: jest.fn(),
|
||||
writeFile: jest.fn(),
|
||||
appendFile: jest.fn(),
|
||||
write: jest.fn(),
|
||||
downloadFile: jest.fn(),
|
||||
uploadFiles: jest.fn(),
|
||||
touch: jest.fn(),
|
||||
MainBundlePath: jest.fn(),
|
||||
CachesDirectoryPath: jest.fn(),
|
||||
DocumentDirectoryPath: jest.fn(),
|
||||
ExternalDirectoryPath: jest.fn(),
|
||||
ExternalStorageDirectoryPath: jest.fn(),
|
||||
TemporaryDirectoryPath: jest.fn(),
|
||||
LibraryDirectoryPath: jest.fn(),
|
||||
PicturesDirectoryPath: jest.fn(),
|
||||
}
|
||||
})
|
Loading…
Add table
Reference in a new issue