Merge branch 'master' into actionmenu

This commit is contained in:
Marcos Rodriguez Vélez 2021-08-26 08:41:30 -04:00
commit 0066612f8a
21 changed files with 538 additions and 861 deletions

View file

@ -137,10 +137,12 @@ const showImagePickerAndReadImage = () => {
takePhotoButtonTitle: null,
maxHeight: 800,
maxWidth: 600,
selectionLimit: 1,
},
response => {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
const asset = response.assets[0];
if (asset.uri) {
const uri = asset.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
resolve(result);

View file

@ -97,9 +97,9 @@ export default class Lnurl {
if (!this._lnurlPayServicePayload.callback) throw new Error('this._lnurlPayServicePayload.callback is not set');
if (amountSat < this._lnurlPayServicePayload.min || amountSat > this._lnurlPayServicePayload.max)
throw new Error(
'amount is not right, ' +
'The specified amount is invalid, ' +
amountSat +
' should be between ' +
' it should be between ' +
this._lnurlPayServicePayload.min +
' and ' +
this._lnurlPayServicePayload.max,

View file

@ -1,4 +1,5 @@
import b58 from 'bs58check';
import { MultisigHDWallet } from './wallets/multisig-hd-wallet';
const HDNode = require('bip32');
export class MultisigCosigner {
@ -63,6 +64,19 @@ export class MultisigCosigner {
this._path = json.path;
this._cosigners = [true];
this._valid = true;
// a bit more logic here: according to the formal BIP48 spec, this xpub field _can_ start with 'xpub', but
// the actual type of segwit can be inferred from the path
if (
this._xpub.startsWith('xpub') &&
[MultisigHDWallet.PATH_NATIVE_SEGWIT, MultisigHDWallet.PATH_WRAPPED_SEGWIT].includes(this._path)
) {
const w = new MultisigHDWallet();
w.addCosigner(this._xpub, '00000000', this._path);
w.setDerivationPath(this._path);
this._xpub = w.convertXpubToMultisignatureXpub(this._xpub);
}
return;
}
} catch (_) {

View file

@ -897,6 +897,10 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
return true;
}
allowSignVerifyMessage() {
return false;
}
async fetchUtxo() {
await super.fetchUtxo();
// now we need to fetch txhash for each input as required by PSBT

View file

@ -1304,7 +1304,7 @@
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = A7W54YZ4WU;
ENABLE_BITCODE = NO;
@ -1354,7 +1354,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEVELOPMENT_TEAM = A7W54YZ4WU;
ENABLE_BITCODE = NO;
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
@ -1399,7 +1399,7 @@
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1438,7 +1438,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1475,7 +1475,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1507,7 +1507,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1540,7 +1540,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1585,7 +1585,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1731,7 +1731,7 @@
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1771,7 +1771,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1811,7 +1811,7 @@
CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1850,7 +1850,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 611;
CURRENT_PROJECT_VERSION = 615;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;

View file

@ -269,13 +269,13 @@ PODS:
- React
- react-native-blur (0.8.0):
- React
- react-native-camera (4.0.3):
- react-native-camera (4.1.0):
- React-Core
- react-native-camera/RCT (= 4.0.3)
- react-native-camera/RN (= 4.0.3)
- react-native-camera/RCT (4.0.3):
- react-native-camera/RCT (= 4.1.0)
- react-native-camera/RN (= 4.1.0)
- react-native-camera/RCT (4.1.0):
- React-Core
- react-native-camera/RN (4.0.3):
- react-native-camera/RN (4.1.0):
- React-Core
- react-native-document-picker (3.5.4):
- React
@ -428,7 +428,7 @@ PODS:
- React-RCTVibration
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.6.0):
- RNScreens (3.5.0):
- React-Core
- React-RCTImage
- RNSecureKeyStore (1.0.0):
@ -744,7 +744,7 @@ SPEC CHECKSUMS:
React-jsinspector: cc614ec18a9ca96fd275100c16d74d62ee11f0ae
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: 4f6b1e1cf2066b3a5b0d8bc062c55881c97325b6
react-native-camera: 87f9f4e0ae5bc7eaf867c891866bf1e830dc5277
react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf
react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0
react-native-idle-timer: 97b8283237d45146a7a5c25bdebe9e1e85f3687b
@ -786,7 +786,7 @@ SPEC CHECKSUMS:
RNRate: e0af7e724e5fcf89578dbd22ab6395c85402ef29
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
RNScreens: eb0dfb2d6b21d2d7f980ad46b14eb306d2f1062e
RNScreens: 01ab149b5dd5c27f5ff26741b1d2bdf2cee1af35
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
RNShare: 00e7409201e3cab13e0febc41fd4465a83ae8225
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f

View file

@ -119,6 +119,7 @@
"lightning_invoice": "Lightning Invoice",
"has_been_paid": "This invoice has been paid.",
"open_direct_channel": "Open direct channel with this node:",
"please_pay_between_and": "Please pay between {min} and {max}",
"please_pay": "Please pay",
"preimage": "Preimage",
"sats": "sats.",
@ -262,6 +263,7 @@
"default_wallets": "View All Wallets",
"electrum_connected": "Connected",
"electrum_connected_not": "Not Connected",
"electrum_connnected_not_description": "An active Electrum connection is required to import a wallet. You can verify if a connection is established by going to the Network screeen in Settings.",
"electrum_error_connect": "Cant connect to the provided Electrum server",
"electrum_host": "E.g. {example}",
"electrum_offline_mode": "Offline Mode",
@ -285,6 +287,7 @@
"electrum_reset_to_default": "Are you sure to want to reset your Electrum settings to default?",
"electrum_clear": "Clear",
"tor_supported": "Tor supported",
"tor_unsupported": "Tor connections are not supported.",
"encrypt_decrypt": "Decrypt Storage",
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
"encrypt_del_uninstall": "Delete if BlueWallet is uninstalled",

907
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -61,7 +61,7 @@
"e2e:debug-test": "detox test -c android.emu.debug -l fatal",
"e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || npm run e2e:debug-build; npm run e2e:debug-test",
"e2e:release-build": "detox build -c android.emu.release",
"e2e:release-test": "detox test -c android.emu.release --record-videos all --take-screenshots all --headless --loglevel trace",
"e2e:release-test": "detox test -c android.emu.release --record-videos all --take-screenshots all --headless -d 60000 --loglevel info",
"tslint": "tsc",
"lint": "eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components",
"lint:fix": "npm run lint -- --fix",
@ -99,7 +99,7 @@
"@bugsnag/source-maps": "2.3.0",
"@keystonehq/bc-ur-registry": "https://github.com/BlueWallet/ur-registry",
"@ngraveio/bc-ur": "https://github.com/BlueWallet/bc-ur",
"@react-native-async-storage/async-storage": "1.15.7",
"@react-native-async-storage/async-storage": "1.15.5",
"@react-native-clipboard/clipboard": "1.8.4",
"@react-native-community/blur": "3.6.0",
"@react-native-community/masked-view": "0.1.11",
@ -176,10 +176,10 @@
"react-native-rate": "1.2.6",
"react-native-reanimated": "2.2.0",
"react-native-safe-area-context": "3.3.0",
"react-native-screens": "3.6.0",
"react-native-screens": "3.5.0",
"react-native-secure-key-store": "https://github.com/BlueWallet/react-native-secure-key-store#63ab38c9d382a819844a086a69cc204c46aa93f9",
"react-native-share": "7.0.0",
"react-native-sortable-list": "https://github.com/BlueWallet/react-native-sortable-list.git#46e39a30ae0c4328e7c06c30b72b1af0b69e1aeb",
"react-native-sortable-list": "https://github.com/BlueWallet/react-native-sortable-list.git#e4e44a01a90ee2dcb9c57182262595abf36bfdf7",
"react-native-svg": "12.1.1",
"react-native-tcp-socket": "5.2.1",
"react-native-tor": "0.1.7",
@ -187,7 +187,7 @@
"react-native-watch-connectivity": "1.0.4",
"react-native-webview": "11.13.0",
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#b1382bbe1ed631b50a8aa03390f64c50775bc9ff",
"react-native-windows": "0.65.0",
"react-native-windows": "0.64.14",
"react-test-render": "1.1.2",
"readable-stream": "3.6.0",
"realm": "10.6.1",

View file

@ -254,7 +254,7 @@ const LNDCreateInvoice = () => {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: wallet.current.getID(),
walletID: walletID ?? wallet.current.getID(),
},
});
return;

View file

@ -1,6 +1,5 @@
/* global alert */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { useState, useEffect, useContext } from 'react';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { I18nManager, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
@ -17,59 +16,70 @@ import {
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import AmountInput from '../../components/AmountInput';
import { BlueCurrentTheme } from '../../components/themes';
import Lnurl from '../../class/lnurl';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import loc, { formatBalanceWithoutSuffix, formatBalance } from '../../loc';
import Biometric from '../../class/biometrics';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
const prompt = require('../../blue_modules/prompt');
const currency = require('../../blue_modules/currency');
export default class LnurlPay extends Component {
static contextType = BlueStorageContext;
const LnurlPay = () => {
const { wallets } = useContext(BlueStorageContext);
const { walletID, lnurl } = useRoute().params;
const wallet = wallets.find(w => w.getID() === walletID);
const [unit, setUnit] = useState(wallet.getPreferredBalanceUnit());
const [isLoading, setIsLoading] = useState(true);
const [LN, setLN] = useState();
const [payButtonDisabled, setPayButtonDisabled] = useState(true);
const [payload, setPayload] = useState();
const { setParams, pop, navigate } = useNavigation();
const [amount, setAmount] = useState();
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
constructor(props, context) {
super(props);
const fromWalletID = props.route.params.fromWalletID;
const lnurl = props.route.params.lnurl;
walletWrapLabel: {
color: colors.buttonAlternativeTextColor,
},
walletWrapBalance: {
color: colors.buttonAlternativeTextColor,
},
walletWrapSats: {
color: colors.buttonAlternativeTextColor,
},
});
const fromWallet = context.wallets.find(w => w.getID() === fromWalletID);
useEffect(() => {
if (lnurl) {
const ln = new Lnurl(lnurl, AsyncStorage);
ln.callLnurlPayService().then(setPayload);
setLN(ln);
setIsLoading(false);
}
}, [lnurl]);
this.state = {
isLoading: true,
fromWalletID,
fromWallet,
lnurl,
payButtonDisabled: false,
unit: fromWallet.getPreferredBalanceUnit(),
};
}
useEffect(() => {
setPayButtonDisabled(isLoading);
}, [isLoading]);
async componentDidMount() {
const LN = new Lnurl(this.state.lnurl, AsyncStorage);
const payload = await LN.callLnurlPayService();
useEffect(() => {
if (payload) {
setAmount(payload.min);
}
}, [payload]);
this.setState({
payload,
amount: payload.min,
isLoading: false,
LN,
});
}
onWalletSelect = wallet => {
this.setState({ fromWallet: wallet, fromWalletID: wallet.getID() }, () => {
this.props.navigation.pop();
});
const onWalletSelect = wallet => {
setParams({ walletID: wallet.getID() });
pop();
};
pay = async () => {
this.setState({
payButtonDisabled: true,
});
const pay = async () => {
setPayButtonDisabled(true);
/** @type {Lnurl} */
const LN = this.state.LN;
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled) {
@ -78,8 +88,8 @@ export default class LnurlPay extends Component {
}
}
let amountSats = this.state.amount;
switch (this.state.unit) {
let amountSats = amount;
switch (unit) {
case BitcoinUnit.SATS:
amountSats = parseInt(amountSats); // nop
break;
@ -92,7 +102,6 @@ export default class LnurlPay extends Component {
}
/** @type {LightningCustodianWallet} */
const fromWallet = this.state.fromWallet;
let bolt11payload;
try {
@ -102,129 +111,106 @@ export default class LnurlPay extends Component {
}
bolt11payload = await LN.requestBolt11FromLnurlPayService(amountSats, comment);
await fromWallet.payInvoice(bolt11payload.pr);
const decoded = fromWallet.decodeInvoice(bolt11payload.pr);
this.setState({ payButtonDisabled: false });
await wallet.payInvoice(bolt11payload.pr);
const decoded = wallet.decodeInvoice(bolt11payload.pr);
setPayButtonDisabled(false);
// success, probably
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
if (fromWallet.last_paid_invoice_result && fromWallet.last_paid_invoice_result.payment_preimage) {
await LN.storeSuccess(decoded.payment_hash, fromWallet.last_paid_invoice_result.payment_preimage);
if (wallet.last_paid_invoice_result && wallet.last_paid_invoice_result.payment_preimage) {
await LN.storeSuccess(decoded.payment_hash, wallet.last_paid_invoice_result.payment_preimage);
}
this.props.navigation.navigate('ScanLndInvoiceRoot', {
navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPaySuccess',
params: {
paymentHash: decoded.payment_hash,
justPaid: true,
fromWalletID: this.state.fromWalletID,
fromWalletID: walletID,
},
});
} catch (Err) {
console.log(Err.message);
this.setState({ isLoading: false, payButtonDisabled: false });
setIsLoading(false);
setPayButtonDisabled(false);
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
return alert(Err.message);
}
};
renderWalletSelectionButton = () => {
if (this.state.renderWalletSelectionButtonHidden) return;
return (
<View style={styles.walletSelectRoot}>
{!this.state.isLoading && (
<TouchableOpacity
accessibilityRole="button"
style={styles.walletSelectTouch}
onPress={() =>
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
}
>
<Text style={styles.walletSelectText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
<Icon name={I18nManager.isRTL ? 'angle-left' : 'angle-right'} size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={styles.walletWrap}>
<TouchableOpacity
accessibilityRole="button"
style={styles.walletWrapTouch}
onPress={() =>
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
}
>
<Text style={styles.walletWrapLabel}>{this.state.fromWallet.getLabel()}</Text>
<Text style={styles.walletWrapBalance}>
{formatBalanceWithoutSuffix(this.state.fromWallet.getBalance(), BitcoinUnit.SATS, false)}
</Text>
<Text style={styles.walletWrapSats}>{BitcoinUnit.SATS}</Text>
</TouchableOpacity>
</View>
const renderWalletSelectionButton = (
<View style={styles.walletSelectRoot}>
{!isLoading && (
<TouchableOpacity
accessibilityRole="button"
style={styles.walletSelectTouch}
onPress={() => navigate('SelectWallet', { onWalletSelect, chainType: Chain.OFFCHAIN })}
>
<Text style={styles.walletSelectText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
<Icon name={I18nManager.isRTL ? 'angle-left' : 'angle-right'} size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={styles.walletWrap}>
<TouchableOpacity
accessibilityRole="button"
style={styles.walletWrapTouch}
onPress={() => navigate('SelectWallet', { onWalletSelect, chainType: Chain.OFFCHAIN })}
>
<Text style={[styles.walletWrapLabel, stylesHook.walletWrapLabel]}>{wallet.getLabel()}</Text>
<Text style={[styles.walletWrapBalance, stylesHook.walletWrapBalance]}>
{formatBalanceWithoutSuffix(wallet.getBalance(), BitcoinUnit.SATS, false)}
</Text>
<Text style={[styles.walletWrapSats, stylesHook.walletWrapSats]}>{BitcoinUnit.SATS}</Text>
</TouchableOpacity>
</View>
);
};
</View>
);
renderGotPayload() {
const renderGotPayload = () => {
return (
<SafeBlueArea>
<ScrollView>
<ScrollView contentContainertyle={{ justifyContent: 'space-around' }}>
<BlueCard>
<AmountInput
isLoading={this.state.isLoading}
amount={this.state.amount.toString()}
onAmountUnitChange={unit => {
this.setState({ unit });
}}
onChangeText={text => {
this.setState({ amount: text });
}}
disabled={this.state.payload && this.state.payload.fixed}
unit={this.state.unit}
isLoading={isLoading}
amount={amount && amount.toString()}
onAmountUnitChange={setUnit}
onChangeText={setAmount}
disabled={payload && payload.fixed}
unit={unit}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
<BlueText style={styles.alignSelfCenter}>
please pay between {this.state.payload.min} and {this.state.payload.max} sat
{loc.formatString(loc.lndViewInvoice.please_pay_between_and, {
min: formatBalance(payload?.min, wallet.getPreferredBalanceUnit()),
max: formatBalance(payload?.max, wallet.getPreferredBalanceUnit()),
})}
</BlueText>
<BlueSpacing20 />
{this.state.payload.image && <Image style={styles.img} source={{ uri: this.state.payload.image }} />}
<BlueText style={styles.alignSelfCenter}>{this.state.payload.description}</BlueText>
<BlueText style={styles.alignSelfCenter}>{this.state.payload.domain}</BlueText>
{payload?.image && <Image style={styles.img} source={{ uri: payload?.image }} />}
<BlueText style={styles.alignSelfCenter}>{payload?.description}</BlueText>
<BlueText style={styles.alignSelfCenter}>{payload?.domain}</BlueText>
<BlueSpacing20 />
<BlueButton title={loc.lnd.payButton} onPress={this.pay} disabled={this.state.payButtonDisabled} />
<BlueButton title={loc.lnd.payButton} onPress={pay} disabled={payButtonDisabled} />
<BlueSpacing20 />
{this.renderWalletSelectionButton()}
</BlueCard>
</ScrollView>
{renderWalletSelectionButton}
</SafeBlueArea>
);
}
};
render() {
if (this.state.isLoading) {
return (
<View style={styles.root}>
<BlueLoading />
</View>
);
}
return this.renderGotPayload();
}
}
LnurlPay.propTypes = {
route: PropTypes.shape({
params: PropTypes.shape({
fromWalletID: PropTypes.string.isRequired,
lnurl: PropTypes.string.isRequired,
}),
}),
navigation: PropTypes.shape({
navigate: PropTypes.func,
pop: PropTypes.func,
dangerouslyGetParent: PropTypes.func,
}),
return isLoading || wallet === undefined || amount === undefined ? (
<View style={[styles.root, stylesHook.root]}>
<BlueLoading />
</View>
) : (
renderGotPayload()
);
};
export default LnurlPay;
const styles = StyleSheet.create({
img: { width: 200, height: 200, alignSelf: 'center' },
alignSelfCenter: {
@ -232,10 +218,9 @@ const styles = StyleSheet.create({
},
root: {
flex: 1,
backgroundColor: BlueCurrentTheme.colors.background,
justifyContent: 'center',
},
walletSelectRoot: {
marginBottom: 16,
alignItems: 'center',
justifyContent: 'flex-end',
},
@ -258,18 +243,15 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
walletWrapLabel: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 14,
},
walletWrapBalance: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 14,
fontWeight: '600',
marginLeft: 4,
marginRight: 4,
},
walletWrapSats: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 11,
fontWeight: '600',
textAlignVertical: 'bottom',

View file

@ -119,7 +119,7 @@ export default class LnurlPaySuccess extends Component {
screen: 'LnurlPay',
params: {
lnurl: lnurl,
fromWalletID: this.state.fromWalletID,
walletID: this.state.fromWalletID,
},
});
}}

View file

@ -152,7 +152,7 @@ const ScanLndInvoice = () => {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: walletID || wallet.getID(),
walletID: walletID || wallet.getID(),
},
});
};

View file

@ -254,13 +254,15 @@ const ScanQRCode = () => {
takePhotoButtonTitle: null,
maxHeight: 800,
maxWidth: 600,
selectionLimit: 1,
},
response => {
if (response.didCancel) {
setIsLoading(false);
} else {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
const asset = response.assets[0];
if (asset.uri) {
const uri = asset.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarCodeRead({ data: result });

View file

@ -162,6 +162,10 @@ export default class ElectrumSettings extends Component {
const sslPort = this.state.sslPort ? this.state.sslPort : '';
const serverHistory = this.state.serverHistory || [];
if (host.endsWith('.onion') && !isTorCapable) {
return alert(loc.settings.tor_unsupported);
}
this.setState({ isLoading: true }, async () => {
try {
if (!host && !port && !sslPort) {

View file

@ -96,6 +96,10 @@ const LightningSettings: React.FC & { navigationOptions: NavigationOptionsGetter
setIsLoading(true);
try {
if (URI) {
if (URI.endsWith('.onion') && !isTorCapable) {
setIsLoading(false);
return alert(loc.settings.tor_unsupported);
}
await LightningCustodianWallet.isValidNodeAddress(URI);
// validating only if its not empty. empty means use default
}

View file

@ -1,3 +1,4 @@
/* global alert */
import React, { useContext, useEffect, useState } from 'react';
import { Platform, View, Keyboard, StatusBar, StyleSheet, Alert } from 'react-native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@ -19,11 +20,12 @@ import { isDesktop, isMacCatalina } from '../../blue_modules/environment';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const fs = require('../../blue_modules/fs');
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const WalletsImport = () => {
const [isToolbarVisibleForAndroid, setIsToolbarVisibleForAndroid] = useState(false);
const route = useRoute();
const { isImportingWallet } = useContext(BlueStorageContext);
const { isImportingWallet, isElectrumDisabled } = useContext(BlueStorageContext);
const label = (route.params && route.params.label) || '';
const triggerImport = (route.params && route.params.triggerImport) || false;
const [importText, setImportText] = useState(label);
@ -69,6 +71,13 @@ const WalletsImport = () => {
* @param importText
*/
const importMnemonic = async importText => {
if (!importText.trim().startsWith('lndhub://')) {
const config = await BlueElectrum.getConfig();
if (config.connected !== 1) {
return alert(loc.settings.electrum_connnected_not_description);
}
}
if (isImportingWallet && isImportingWallet.isFailure === false) {
return;
}
@ -141,6 +150,21 @@ const WalletsImport = () => {
}
};
const isImportDisabled = () => {
let disabled = false;
const seed = importText.trim();
if (seed.length === 0) {
disabled = true;
}
if (!seed.startsWith('lndhub://') && isElectrumDisabled) {
disabled = true;
}
return disabled;
};
return (
<SafeBlueArea style={styles.root}>
<StatusBar barStyle="light-content" />
@ -158,12 +182,7 @@ const WalletsImport = () => {
<BlueSpacing20 />
<View style={styles.center}>
<>
<BlueButton
testID="DoImport"
disabled={importText.trim().length === 0}
title={loc.wallets.import_do_import}
onPress={importButtonPressed}
/>
<BlueButton testID="DoImport" disabled={isImportDisabled()} title={loc.wallets.import_do_import} onPress={importButtonPressed} />
<BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} testID="ScanImport" />
</>

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef, useContext } from 'react';
import { View, ActivityIndicator, Image, Text, StyleSheet, StatusBar, ScrollView, I18nManager } from 'react-native';
import { View, ActivityIndicator, Image, Text, StyleSheet, StatusBar, I18nManager } from 'react-native';
import { BluePrivateBalance } from '../../BlueComponents';
import SortableList from 'react-native-sortable-list';
import LinearGradient from 'react-native-linear-gradient';
@ -23,7 +23,6 @@ const styles = StyleSheet.create({
itemRoot: {
backgroundColor: 'transparent',
padding: 10,
marginVertical: 17,
},
gradient: {
padding: 15,
@ -73,7 +72,6 @@ const ReorderWallets = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const [hasMovedARow, setHasMovedARow] = useState(false);
const [scrollEnabled, setScrollEnabled] = useState(true);
const sortableList = useRef();
const { colors } = useTheme();
const { wallets, setWalletsWithNewOrder } = useContext(BlueStorageContext);
@ -163,12 +161,10 @@ const ReorderWallets = () => {
const onActivateRow = () => {
ReactNativeHapticFeedback.trigger('selection', { ignoreAndroidSystemSettings: false });
setScrollEnabled(false);
};
const onReleaseRow = () => {
ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false });
setScrollEnabled(true);
};
return isLoading ? (
@ -178,17 +174,15 @@ const ReorderWallets = () => {
) : (
<View style={[styles.root, stylesHook.root]}>
<StatusBar barStyle="light-content" />
<ScrollView scrollEnabled={scrollEnabled}>
<SortableList
ref={sortableList}
data={data}
renderRow={renderItem}
scrollEnabled={false}
onChangeOrder={onChangeOrder}
onActivateRow={onActivateRow}
onReleaseRow={onReleaseRow}
/>
</ScrollView>
<SortableList
ref={sortableList}
data={data}
renderRow={renderItem}
onChangeOrder={onChangeOrder}
onActivateRow={onActivateRow}
onReleaseRow={onReleaseRow}
style={styles.root}
/>
</View>
);
};

View file

@ -4,6 +4,7 @@ import {
Alert,
Keyboard,
KeyboardAvoidingView,
LayoutAnimation,
Platform,
StyleSheet,
TextInput,
@ -12,6 +13,8 @@ import {
} from 'react-native';
import { useRoute, useTheme, useNavigation } from '@react-navigation/native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import Share from 'react-native-share';
import AOPP from '../../class/aopp';
import { BlueDoneAndDismissKeyboardInputAccessory, BlueFormLabel, BlueSpacing10, BlueSpacing20, SafeBlueArea } from '../../BlueComponents';
@ -20,8 +23,6 @@ import { FContainer, FButton } from '../../components/FloatButtons';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import loc from '../../loc';
import confirm from '../../helpers/confirm';
import { Icon } from 'react-native-elements';
import Share from 'react-native-share';
const SignVerify = () => {
const { colors } = useTheme();
@ -129,6 +130,11 @@ const SignVerify = () => {
setLoading(false);
};
const handleFocus = value => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setMessageHasFocus(value);
};
if (loading)
return (
<View style={[stylesHooks.root, styles.loading]}>
@ -195,8 +201,8 @@ const SignVerify = () => {
autoCapitalize="none"
spellCheck={false}
editable={!loading}
onFocus={() => setMessageHasFocus(true)}
onBlur={() => setMessageHasFocus(false)}
onFocus={() => handleFocus(true)}
onBlur={() => handleFocus(false)}
/>
<BlueSpacing10 />

View file

@ -20,7 +20,6 @@ import {
View,
I18nManager,
} from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';
import { Icon } from 'react-native-elements';
import { useRoute, useNavigation, useTheme, useFocusEffect } from '@react-navigation/native';
import { Chain } from '../../models/bitcoinUnits';
@ -42,7 +41,6 @@ import { TransactionListItem } from '../../components/TransactionListItem';
const fs = require('../../blue_modules/fs');
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
@ -473,27 +471,7 @@ const WalletTransactions = () => {
};
const choosePhoto = () => {
launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
maxHeight: 800,
maxWidth: 600,
},
response => {
if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarCodeRead({ data: result });
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
}
},
);
fs.showImagePickerAndReadImage().then(onBarCodeRead);
};
const copyFromClipboard = async () => {

View file

@ -1788,6 +1788,52 @@ describe('multisig-cosigner', () => {
assert.strictEqual(cosigner.getXpub(), Zpub1);
assert.strictEqual(cosigner.getPath(), "m/48'/0'/0'/2'");
assert.strictEqual(cosigner.howManyCosignersWeHave(), 1);
assert.ok(cosigner.isNativeSegwit());
assert.ok(!cosigner.isLegacy());
assert.ok(!cosigner.isWrappedSegwit());
});
it('can parse cobo json, if xpub is plain xpub (not Zpub or Ypub)', () => {
let xpub = MultisigCosigner._zpubToXpub(Zpub1);
assert.ok(xpub.startsWith('xpub'));
let cosigner = new MultisigCosigner(`{"xfp":"${fp1cobo}","xpub":"${xpub}","path":"${MultisigHDWallet.PATH_NATIVE_SEGWIT}"}`);
assert.ok(cosigner.isValid());
assert.strictEqual(cosigner.getFp(), fp1cobo);
assert.strictEqual(cosigner.getXpub(), Zpub1);
assert.strictEqual(cosigner.getPath(), MultisigHDWallet.PATH_NATIVE_SEGWIT);
assert.strictEqual(cosigner.howManyCosignersWeHave(), 1);
assert.ok(cosigner.isNativeSegwit());
assert.ok(!cosigner.isLegacy());
assert.ok(!cosigner.isWrappedSegwit());
//
const Ypub1 = 'Ypub6jtUX12KGcqFosZWP4YcHc9qbKRTvgBpb8aE58hsYqby3SQVTr5KGfMmdMg38ekmQ9iLhCdgbAbjih7AWSkA7pgRhiLfah3zT6u1PFvVEbc';
xpub = MultisigCosigner._zpubToXpub(Ypub1);
assert.ok(xpub.startsWith('xpub'));
cosigner = new MultisigCosigner(`{"xfp":"${fp1cobo}","xpub":"${xpub}","path":"${MultisigHDWallet.PATH_WRAPPED_SEGWIT}"}`);
assert.ok(cosigner.isValid());
assert.strictEqual(cosigner.getFp(), fp1cobo);
assert.strictEqual(cosigner.getXpub(), Ypub1);
assert.strictEqual(cosigner.getPath(), MultisigHDWallet.PATH_WRAPPED_SEGWIT);
assert.strictEqual(cosigner.howManyCosignersWeHave(), 1);
assert.ok(!cosigner.isNativeSegwit());
assert.ok(!cosigner.isLegacy());
assert.ok(cosigner.isWrappedSegwit());
//
xpub = MultisigCosigner._zpubToXpub(Ypub1);
assert.ok(xpub.startsWith('xpub'));
cosigner = new MultisigCosigner(`{"xfp":"${fp1cobo}","xpub":"${xpub}","path":"${MultisigHDWallet.PATH_LEGACY}"}`);
assert.ok(cosigner.isValid());
assert.strictEqual(cosigner.getFp(), fp1cobo);
assert.strictEqual(cosigner.getXpub(), xpub);
assert.strictEqual(cosigner.getPath(), MultisigHDWallet.PATH_LEGACY);
assert.strictEqual(cosigner.howManyCosignersWeHave(), 1);
assert.ok(!cosigner.isNativeSegwit());
assert.ok(cosigner.isLegacy());
assert.ok(!cosigner.isWrappedSegwit());
});
it('can parse cobo URv2 account', () => {