Merge branch 'master' into fix-3-dots

This commit is contained in:
Nuno Coelho 2019-08-07 21:29:51 +02:00 committed by GitHub
commit c8e1f0921e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 244 additions and 64 deletions

View File

@ -11,6 +11,7 @@ import {
Animated,
ActivityIndicator,
View,
KeyboardAvoidingView,
UIManager,
StyleSheet,
Dimensions,
@ -765,16 +766,71 @@ export class BlueUseAllFundsButton extends Component {
};
render() {
return (
<InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: BlueApp.settings.alternativeTextColor, fontSize: 16, marginHorizontal: 8 }}>
Total: {this.props.wallet.getBalance()} {BitcoinUnit.BTC}
const inputView = (
<View
style={{
flex: 1,
flexDirection: 'row',
maxHeight: 44,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#eef0f4',
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
<Text
style={{
color: BlueApp.settings.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
Total:
</Text>
<BlueButtonLink title="Use All" onPress={this.props.onUseAllPressed} />
{this.props.wallet.allowSendMax() && this.props.wallet.getBalance() > 0 ? (
<BlueButtonLink
onPress={this.props.onUseAllPressed}
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={`${loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} ${
BitcoinUnit.BTC
}`}
/>
) : (
<Text
style={{
color: BlueApp.settings.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
{loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} {BitcoinUnit.BTC}
</Text>
)}
</View>
</InputAccessoryView>
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
<BlueButtonLink
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title="Done"
onPress={Keyboard.dismiss}
/>
</View>
</View>
);
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
}
}
}
@ -1923,6 +1979,7 @@ export class BlueBitcoinAmount extends Component {
} else {
localCurrency = loc.formatBalanceWithoutSuffix(amount.toString(), BitcoinUnit.LOCAL_CURRENCY, false);
}
if (amount === BitcoinUnit.MAX) localCurrency = ''; // we dont want to display NaN
return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View>
@ -1939,6 +1996,12 @@ export class BlueBitcoinAmount extends Component {
}
this.props.onChangeText(text);
}}
onBlur={() => {
if (this.props.onBlur) this.props.onBlur();
}}
onFocus={() => {
if (this.props.onFocus) this.props.onFocus();
}}
placeholder="0"
maxLength={10}
ref={textInput => (this.textInput = textInput)}

View File

@ -68,6 +68,10 @@ export class AbstractWallet {
return true;
}
allowSendMax(): boolean {
return false;
}
allowRBF() {
return false;
}

View File

@ -36,6 +36,10 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
return true;
}
allowSendMax(): boolean {
return true;
}
/**
* @inheritDoc
*/

View File

@ -5,6 +5,7 @@ import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
import signer from '../models/signer';
import { BitcoinUnit } from '../models/bitcoinUnits';
const bitcoin = require('bitcoinjs-lib');
const bitcoin5 = require('bitcoinjs5');
const HDNode = require('bip32');
@ -49,6 +50,10 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
return true;
}
allowSendMax(): boolean {
return true;
}
async generate() {
let that = this;
return new Promise(function(resolve) {
@ -255,12 +260,29 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
}
}
/**
*
* @param utxos
* @param amount Either float (BTC) or string 'MAX' (BitcoinUnit.MAX) to send all
* @param fee
* @param address
* @returns {string}
*/
createTx(utxos, amount, fee, address) {
for (let utxo of utxos) {
utxo.wif = this._getWifForAddress(utxo.address);
}
let amountPlusFee = parseFloat(new BigNumber(amount).plus(fee).toString(10));
if (amount === BitcoinUnit.MAX) {
amountPlusFee = new BigNumber(0);
for (let utxo of utxos) {
amountPlusFee = amountPlusFee.plus(utxo.amount);
}
amountPlusFee = amountPlusFee.dividedBy(100000000).toString(10);
}
return signer.createHDSegwitTransaction(
utxos,
address,

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'Create',
setAmount: 'Přijmout částku...',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Koupit Bitcoin',

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'Create',
setAmount: 'Modtag med beløb',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Køb Bitcoin',

View File

@ -167,7 +167,7 @@ module.exports = {
create: 'Create',
setAmount: 'Zu erhaltender Betrag',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Kaufe Bitcoin',

View File

@ -168,7 +168,7 @@ module.exports = {
create: 'Δημιούργησε',
setAmount: 'Λάβε με ποσό',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Αγόρασε Bitcoin',

View File

@ -166,7 +166,7 @@ module.exports = {
create: 'Create',
setAmount: 'Receive with amount',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Buy Bitcoin',

View File

@ -167,7 +167,7 @@ module.exports = {
create: 'Create',
setAmount: 'Receive with amount',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Buy Bitcoin',

View File

@ -168,7 +168,7 @@ module.exports = {
create: 'Luo',
setAmount: 'Vastaanotettava summa',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Osta Bitcoinia',

View File

@ -167,7 +167,7 @@ module.exports = {
create: 'Create',
setAmount: 'Revevoir avec montant',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Acheter du Bitcoin',

View File

@ -163,7 +163,7 @@ module.exports = {
create: 'Stvori',
setAmount: 'Odredi iznos za primiti',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Kupovina Bitcoina',

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'Létrehoz',
setAmount: 'Fogadandó összeg',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Bitcoin vásárlása',

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'Buat',
setAmount: 'Terima sejumlah',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Beli bitcoin',

View File

@ -168,7 +168,7 @@ module.exports = {
create: 'Crea',
setAmount: 'Ricevi con importo',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Compra Bitcoin',

View File

@ -165,7 +165,7 @@ module.exports = {
create: '作成',
setAmount: '入金額',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Bitcoin の購入',

View File

@ -166,7 +166,7 @@ module.exports = {
create: 'Lag',
setAmount: 'Motta med beløp',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Kjøp Bitcoin',

View File

@ -166,7 +166,7 @@ module.exports = {
create: 'Create',
setAmount: 'Ontvang met bedrag',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Koop Bitcoin',

View File

@ -169,7 +169,7 @@ module.exports = {
create: 'Create',
setAmount: 'Valor a receber',
},
scan_lnurl: 'Receber lendo QR'
scan_lnurl: 'Receber lendo QR',
},
buyBitcoin: {
header: 'Comprar Bitcoin',

View File

@ -172,7 +172,7 @@ module.exports = {
create: 'Create',
setAmount: 'Receive with amount',
},
scan_lnurl: 'Receber lendo QR'
scan_lnurl: 'Receber lendo QR',
},
settings: {
tabBarLabel: 'Definições',

View File

@ -171,7 +171,7 @@ module.exports = {
create: 'Создать',
setAmount: 'Получить сумму',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
settings: {
tabBarLabel: 'Настройки',

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'Skapa',
setAmount: 'Ta emot med belopp',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Köp bitcoin',

View File

@ -165,7 +165,7 @@ module.exports = {
create: 'สร้าง',
setAmount: 'รับด้วยจำนวน',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'ซื้อบิตคอยน์',

View File

@ -166,7 +166,7 @@ module.exports = {
create: 'Oluştur',
setAmount: 'Miktar ile al',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Bitcoin Satın al',

View File

@ -166,7 +166,7 @@ module.exports = {
create: 'Create',
setAmount: 'Receive with amount',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Buy Bitcoin',

View File

@ -163,7 +163,7 @@ module.exports = {
create: '创建',
setAmount: '收款金额',
},
scan_lnurl: 'Scan to receive'
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: '购买比特币',

View File

@ -2,6 +2,7 @@ export const BitcoinUnit = Object.freeze({
BTC: 'BTC',
SATS: 'sats',
LOCAL_CURRENCY: 'local_currency',
MAX: 'MAX',
});
export const Chain = Object.freeze({

4
package-lock.json generated
View File

@ -3956,8 +3956,8 @@
"integrity": "sha512-SfD7WfmueKrtKeHUESLczuANgnpdnfrSz3ZzerLdtmZf2UBZmAB3z9Q525zI5p3n9I7ii/lllUlyKHm2pIG7QQ=="
},
"electrum-client": {
"version": "git+https://github.com/Overtorment/rn-electrum-client.git#d194ff69195ccc86f72088ea3712179b4be9cbb4",
"from": "git+https://github.com/Overtorment/rn-electrum-client.git"
"version": "git+https://github.com/BlueWallet/rn-electrum-client.git#d194ff69195ccc86f72088ea3712179b4be9cbb4",
"from": "git+https://github.com/BlueWallet/rn-electrum-client.git"
},
"elliptic": {
"version": "6.5.0",

View File

@ -65,7 +65,7 @@
"coinselect": "3.1.11",
"crypto-js": "3.1.9-1",
"dayjs": "1.8.14",
"electrum-client": "git+https://github.com/Overtorment/rn-electrum-client.git",
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git",
"eslint-config-prettier": "6.0.0",
"eslint-config-standard": "12.0.0",
"eslint-config-standard-react": "7.0.2",

View File

@ -1,6 +1,6 @@
module.exports = {
dependencies: {
'appcenter': {
appcenter: {
platforms: {
android: null, // disable Android platform, other platforms will still autolink if provided
},

View File

@ -1,6 +1,16 @@
/* global alert */
import React, { Component } from 'react';
import { Dimensions, ActivityIndicator, View, TextInput, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, TouchableOpacity, Text } from 'react-native';
import {
Dimensions,
ActivityIndicator,
View,
TextInput,
KeyboardAvoidingView,
Keyboard,
TouchableWithoutFeedback,
TouchableOpacity,
Text,
} from 'react-native';
import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount, BlueDismissKeyboardInputAccessory } from '../../BlueComponents';
import PropTypes from 'prop-types';
import bech32 from 'bech32';
@ -42,9 +52,9 @@ export default class LNDCreateInvoice extends Component {
// send to lnurl-withdraw callback url if that exists
if (this.state.lnurlParams) {
let {callback, k1} = this.state.lnurlParams;
let { callback, k1 } = this.state.lnurlParams;
let callbackUrl = callback + (callback.indexOf('?') !== -1 ? '&' : '?') + 'k1=' + k1 + '&pr=' + invoiceRequest;
let resp = await fetch(callbackUrl, {method: 'GET'});
let resp = await fetch(callbackUrl, { method: 'GET' });
if (resp.status >= 300) {
let text = await resp.text();
throw new Error(text);
@ -91,9 +101,9 @@ export default class LNDCreateInvoice extends Component {
// calling the url
try {
let resp = await fetch(url, {method: 'GET'})
let resp = await fetch(url, { method: 'GET' });
if (resp.status >= 300) {
throw new Error("Bad response from server");
throw new Error('Bad response from server');
}
let reply = await resp.json();
if (reply.status === 'ERROR') {
@ -112,7 +122,7 @@ export default class LNDCreateInvoice extends Component {
callback: reply.callback,
fixed: reply.minWithdrawable === reply.maxWithdrawable,
min: (reply.minWithdrawable || 0) / 1000,
max: reply.maxWithdrawable / 1000
max: reply.maxWithdrawable / 1000,
},
amount: (reply.maxWithdrawable / 1000).toString(),
description: reply.defaultDescription,
@ -124,7 +134,7 @@ export default class LNDCreateInvoice extends Component {
alert(Err.message);
}
});
}
};
renderCreateButton = () => {
return (
@ -142,18 +152,16 @@ export default class LNDCreateInvoice extends Component {
return (
<View style={{ marginHorizontal: 0, marginVertical: 16, minHeight: 25, alignContent: 'center' }}>
<TouchableOpacity
onPress={() => NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl }) }
onPress={() => NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl })}
style={{
flex: 1,
flexDirection: 'row',
minWidth: width,
justifyContent: 'center',
alignItems: 'center'
alignItems: 'center',
}}
>
<Text style={{color: BlueApp.settings.buttonTextColor, textAlign: 'center'}}>
{loc.receive.scan_lnurl}
</Text>
<Text style={{ color: BlueApp.settings.buttonTextColor, textAlign: 'center' }}>{loc.receive.scan_lnurl}</Text>
</TouchableOpacity>
</View>
);
@ -179,12 +187,12 @@ export default class LNDCreateInvoice extends Component {
onChangeText={text => {
if (this.state.lnurlParams) {
// in this case we prevent the user from changing the amount to < min or > max
let {min, max} = this.state.lnurlParams;
let nextAmount = parseInt(text)
let { min, max } = this.state.lnurlParams;
let nextAmount = parseInt(text);
if (nextAmount < min) {
text = min.toString()
text = min.toString();
} else if (nextAmount > max) {
text = max.toString()
text = max.toString();
}
}

View File

@ -4,6 +4,7 @@ import {
ActivityIndicator,
View,
TextInput,
Alert,
StatusBar,
TouchableOpacity,
KeyboardAvoidingView,
@ -22,6 +23,7 @@ import {
BlueAddressInput,
BlueDismissKeyboardInputAccessory,
BlueLoading,
BlueUseAllFundsButton,
} from '../../BlueComponents';
import Slider from '@react-native-community/slider';
import PropTypes from 'prop-types';
@ -71,14 +73,14 @@ export default class SendDetails extends Component {
fromSecret = fromWallet.getSecret();
}
this.state = {
isLoading: true,
isLoading: false,
showSendMax: false,
isFeeSelectionModalVisible: false,
fromAddress,
fromWallet,
fromSecret,
address,
memo,
amount: 0,
fee: 1,
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
feeSliderValue: 1,
@ -466,6 +468,9 @@ export default class SendDetails extends Component {
let targets = [];
targets.push({ address: this.state.address, value: satoshis });
if (this.state.amount === BitcoinUnit.MAX) {
targets = [{ address: this.state.address }];
}
let { tx, fee } = wallet.createTransaction(wallet.getUtxo(), targets, requestedSatPerByte, changeAddress);
@ -627,9 +632,11 @@ export default class SendDetails extends Component {
<KeyboardAvoidingView behavior="position">
<BlueBitcoinAmount
isLoading={this.state.isLoading}
amount={this.state.amount.toString()}
amount={this.state.amount ? this.state.amount.toString() : null}
onChangeText={text => this.setState({ amount: text })}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
inputAccessoryViewID={this.state.fromWallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
onFocus={() => this.setState({ isAmountToolbarVisibleForAndroid: true })}
onBlur={() => this.setState({ isAmountToolbarVisibleForAndroid: false })}
/>
<BlueAddressInput
onChangeText={text => {
@ -714,6 +721,56 @@ export default class SendDetails extends Component {
</KeyboardAvoidingView>
</View>
<BlueDismissKeyboardInputAccessory />
{Platform.select({
ios: (
<BlueUseAllFundsButton
onUseAllPressed={() => {
ReactNativeHapticFeedback.trigger('notificationWarning');
Alert.alert(
'Use full balance',
`Are you sure you want to use your wallet's full balance for this transaction?`,
[
{
text: loc._.ok,
onPress: async () => {
this.setState({ amount: 'MAX' });
},
style: 'default',
},
{ text: loc.send.details.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
Keyboard.dismiss();
}}
wallet={this.state.fromWallet}
/>
),
android: this.state.isAmountToolbarVisibleForAndroid && (
<BlueUseAllFundsButton
onUseAllPressed={() => {
Alert.alert(
'Use all funds',
`Are you sure you want to use your all of your wallet's funds for this transaction?`,
[
{
text: loc._.ok,
onPress: async () => {
this.setState({ amount: 'MAX' });
},
style: 'default',
},
{ text: loc.send.details.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
Keyboard.dismiss();
}}
wallet={this.state.fromWallet}
/>
),
})}
{this.renderWalletSelectionButton()}
</View>
</TouchableWithoutFeedback>

View File

@ -1,15 +1,6 @@
/* global alert */
import React, { Component } from 'react';
import {
Text,
View,
ActivityIndicator,
InteractionManager,
FlatList,
RefreshControl,
TouchableOpacity,
StatusBar,
} from 'react-native';
import { Text, View, ActivityIndicator, InteractionManager, FlatList, RefreshControl, TouchableOpacity, StatusBar } from 'react-native';
import PropTypes from 'prop-types';
import { NavigationEvents } from 'react-navigation';
import { BlueSendButtonIcon, BlueReceiveButtonIcon, BlueTransactionListItem, BlueWalletNavigationHeader } from '../../BlueComponents';

View File

@ -1,5 +1,6 @@
/* global it, jasmine, afterAll, beforeAll */
import { SegwitP2SHWallet, SegwitBech32Wallet, HDSegwitP2SHWallet, HDLegacyBreadwalletWallet, HDLegacyP2PKHWallet } from '../../class';
import {BitcoinUnit} from "../../models/bitcoinUnits";
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert');
let bitcoin = require('bitcoinjs-lib');
@ -177,6 +178,35 @@ it('HD (BIP49) can create TX', async () => {
chunksIn = bitcoin.script.decompile(tx.outs[0].script);
toAddress = bitcoin.address.fromOutputScript(chunksIn);
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress);
// testing sendMAX
hd.utxo = [
{
txid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
vout: 0,
amount: 26000,
address: '3GgPyzKfWXiaXMbJ9LeEVGetvEXdrX9Ecj',
wif: 'L3fg5Jb6tJDVMvoG2boP4u3CxjX1Er3e7Z4zDALQdGgVLLE8zVUr',
},
{
txid: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
vout: 0,
amount: 26000,
address: '3GgPyzKfWXiaXMbJ9LeEVGetvEXdrX9Ecj',
wif: 'L3fg5Jb6tJDVMvoG2boP4u3CxjX1Er3e7Z4zDALQdGgVLLE8zVUr',
},
{
txid: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
vout: 0,
amount: 26000,
address: '3GgPyzKfWXiaXMbJ9LeEVGetvEXdrX9Ecj',
wif: 'L3fg5Jb6tJDVMvoG2boP4u3CxjX1Er3e7Z4zDALQdGgVLLE8zVUr',
},
];
txhex = hd.createTx(hd.utxo, BitcoinUnit.MAX, 0.00003, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
tx = bitcoin.Transaction.fromHex(txhex);
assert.strictEqual(tx.outs.length, 1);
assert.strictEqual(tx.outs[0].value, 75000);
});
it('Segwit HD (BIP49) can fetch UTXO', async function() {