mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 20:07:11 +01:00
Development (#103)
ADD: New send screen ADD: Support for BIP70 decoding
This commit is contained in:
parent
4000519dfc
commit
f5dd8252e1
29 changed files with 849 additions and 406 deletions
|
@ -13,5 +13,9 @@
|
||||||
trailingComma: 'all'
|
trailingComma: 'all'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"env":{
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"globals": { "fetch": false }
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,13 @@ export class BlueButton extends Component {
|
||||||
delayPressIn={0}
|
delayPressIn={0}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
|
||||||
borderWidth: 0.7,
|
borderWidth: 0.7,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
buttonStyle={Object.assign(
|
buttonStyle={Object.assign(
|
||||||
{
|
{
|
||||||
backgroundColor: '#ccddf9',
|
backgroundColor: '#ccddf9',
|
||||||
|
minHeight: 45,
|
||||||
height: 45,
|
height: 45,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
borderRadius: 25,
|
borderRadius: 25,
|
||||||
|
@ -668,8 +668,8 @@ export class BlueReceiveButtonIcon extends Component {
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: 110,
|
minWidth: 110,
|
||||||
height: 40,
|
minHeight: 40,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
backgroundColor: '#ccddf9',
|
backgroundColor: '#ccddf9',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -677,9 +677,8 @@ export class BlueReceiveButtonIcon extends Component {
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: 30,
|
minWidth: 30,
|
||||||
height: 30,
|
minHeight: 30,
|
||||||
borderBottomLeftRadius: 15,
|
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
transform: [{ rotate: '-45deg' }],
|
transform: [{ rotate: '-45deg' }],
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -716,7 +715,6 @@ export class BlueSendButtonIcon extends Component {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: 110,
|
width: 110,
|
||||||
height: 40,
|
height: 40,
|
||||||
position: 'relative',
|
|
||||||
backgroundColor: '#ccddf9',
|
backgroundColor: '#ccddf9',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingLeft: 15,
|
paddingLeft: 15,
|
||||||
|
@ -724,10 +722,9 @@ export class BlueSendButtonIcon extends Component {
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: 30,
|
minWidth: 30,
|
||||||
height: 30,
|
minHeight: 30,
|
||||||
left: 5,
|
left: 5,
|
||||||
borderBottomLeftRadius: 15,
|
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
transform: [{ rotate: '225deg' }],
|
transform: [{ rotate: '225deg' }],
|
||||||
}}
|
}}
|
||||||
|
@ -760,9 +757,8 @@ export class ManageFundsBigButton extends Component {
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: 160,
|
minWidth: 160,
|
||||||
height: 40,
|
minHeight: 40,
|
||||||
position: 'relative',
|
|
||||||
backgroundColor: '#ccddf9',
|
backgroundColor: '#ccddf9',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
|
@ -782,7 +778,6 @@ export class ManageFundsBigButton extends Component {
|
||||||
fontSize: (isIpad && 10) || 16,
|
fontSize: (isIpad && 10) || 16,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
position: 'relative',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loc.lnd.title}
|
{loc.lnd.title}
|
||||||
|
|
|
@ -55,6 +55,25 @@ const WalletsStackNavigator = createStackNavigator({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CreateTransactionStackNavigator = createStackNavigator({
|
||||||
|
SendDetails: {
|
||||||
|
screen: sendDetails,
|
||||||
|
},
|
||||||
|
ScanQrAddress: {
|
||||||
|
screen: sendScanQrAddress,
|
||||||
|
},
|
||||||
|
CreateTransaction: {
|
||||||
|
screen: sendCreate,
|
||||||
|
navigationOptions: {
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
},
|
||||||
|
headerTintColor: '#0c2550',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const Tabs = createStackNavigator(
|
const Tabs = createStackNavigator(
|
||||||
{
|
{
|
||||||
Wallets: {
|
Wallets: {
|
||||||
|
@ -83,6 +102,12 @@ const Tabs = createStackNavigator(
|
||||||
screen: WalletExport,
|
screen: WalletExport,
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
|
SendDetails: {
|
||||||
|
screen: CreateTransactionStackNavigator,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
TransactionDetails: {
|
TransactionDetails: {
|
||||||
screen: details,
|
screen: details,
|
||||||
|
@ -102,16 +127,6 @@ const Tabs = createStackNavigator(
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
SendDetails: {
|
|
||||||
screen: sendDetails,
|
|
||||||
},
|
|
||||||
ScanQrAddress: {
|
|
||||||
screen: sendScanQrAddress,
|
|
||||||
},
|
|
||||||
CreateTransaction: {
|
|
||||||
screen: sendCreate,
|
|
||||||
},
|
|
||||||
|
|
||||||
// LND:
|
// LND:
|
||||||
|
|
||||||
ManageFunds: {
|
ManageFunds: {
|
||||||
|
|
74
bip70/bip70.js
Normal file
74
bip70/bip70.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import Frisbee from 'frisbee';
|
||||||
|
|
||||||
|
export class BitcoinBIP70Transaction {
|
||||||
|
constructor(amount, address, memo, fee, expires) {
|
||||||
|
this.amount = amount;
|
||||||
|
this.address = address;
|
||||||
|
this.memo = memo;
|
||||||
|
this.fee = fee;
|
||||||
|
this.expires = expires;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BitcoinBIP70TransactionError {
|
||||||
|
constructor(errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BitcoinBIP70TransactionDecode {
|
||||||
|
static decode(data) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const url = data.match(/bitcoin:\?r=https?:\/\/\S+/gi);
|
||||||
|
const api = new Frisbee({
|
||||||
|
baseURI: url.toString().split('bitcoin:?r=')[1],
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/payment-request',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let response = await api.get();
|
||||||
|
if (response && response.body) {
|
||||||
|
const parsedJSON = JSON.parse(response.body);
|
||||||
|
|
||||||
|
// Check that the invoice has not expired
|
||||||
|
const expires = new Date(parsedJSON.expires).getTime();
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (now > expires) {
|
||||||
|
throw new BitcoinBIP70TransactionError('This invoice has expired.');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
const decodedTransaction = new BitcoinBIP70Transaction(
|
||||||
|
parsedJSON.outputs[0].amount,
|
||||||
|
parsedJSON.outputs[0].address,
|
||||||
|
parsedJSON.memo,
|
||||||
|
parsedJSON.requiredFeeRate.toFixed(0),
|
||||||
|
parsedJSON.expires,
|
||||||
|
);
|
||||||
|
console.log(decodedTransaction);
|
||||||
|
resolve(decodedTransaction);
|
||||||
|
} else {
|
||||||
|
console.log('Could not fetch transaction details: ' + response.err);
|
||||||
|
throw new BitcoinBIP70TransactionError('Unable to fetch transaction details. Please, make sure the provided link is valid.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static isExpired(transactionExpires) {
|
||||||
|
if (transactionExpires === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const expires = new Date(transactionExpires).getTime();
|
||||||
|
const now = new Date().getTime();
|
||||||
|
return now > expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
static matchesPaymentURL(data) {
|
||||||
|
return data !== null && data.match(/bitcoin:\?r=https?:\/\/\S+/gi) !== null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -345,14 +345,14 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// no luck - lets iterate over all addressess we have up to first unused address index
|
// no luck - lets iterate over all addressess we have up to first unused address index
|
||||||
for (let c = 0; c <= this.next_free_change_address_index; c++) {
|
for (let c = 0; c <= this.next_free_change_address_index + 3; c++) {
|
||||||
let possibleAddress = this._getInternalAddressByIndex(c);
|
let possibleAddress = this._getInternalAddressByIndex(c);
|
||||||
if (possibleAddress === address) {
|
if (possibleAddress === address) {
|
||||||
return (this._address_to_wif_cache[address] = this._getInternalWIFByIndex(c));
|
return (this._address_to_wif_cache[address] = this._getInternalWIFByIndex(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let c = 0; c <= this.next_free_address_index; c++) {
|
for (let c = 0; c <= this.next_free_address_index + 3; c++) {
|
||||||
let possibleAddress = this._getExternalAddressByIndex(c);
|
let possibleAddress = this._getExternalAddressByIndex(c);
|
||||||
if (possibleAddress === address) {
|
if (possibleAddress === address) {
|
||||||
return (this._address_to_wif_cache[address] = this._getExternalWIFByIndex(c));
|
return (this._address_to_wif_cache[address] = this._getExternalWIFByIndex(c));
|
||||||
|
|
|
@ -59,8 +59,8 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
|
||||||
this.unconfirmed_balance += addr.unconfirmed;
|
this.unconfirmed_balance += addr.unconfirmed;
|
||||||
this.usedAddresses.push(addr.addr);
|
this.usedAddresses.push(addr.addr);
|
||||||
}
|
}
|
||||||
this.balance = new BigNumber(this.balance).div(100000000).toString() * 1;
|
this.balance = new BigNumber(this.balance).dividedBy(100000000).toString() * 1;
|
||||||
this.unconfirmed_balance = new BigNumber(this.unconfirmed_balance).div(100000000).toString() * 1;
|
this.unconfirmed_balance = new BigNumber(this.unconfirmed_balance).dividedBy(100000000).toString() * 1;
|
||||||
this._lastBalanceFetch = +new Date();
|
this._lastBalanceFetch = +new Date();
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Could not fetch balance from API: ' + response.err);
|
throw new Error('Could not fetch balance from API: ' + response.err);
|
||||||
|
@ -344,7 +344,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
|
||||||
utxo.wif = this._getWifForAddress(utxo.address);
|
utxo.wif = this._getWifForAddress(utxo.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
|
let amountPlusFee = parseFloat(new BigNumber(amount).plus(fee).toString(10));
|
||||||
return signer.createHDSegwitTransaction(
|
return signer.createHDSegwitTransaction(
|
||||||
utxos,
|
utxos,
|
||||||
address,
|
address,
|
||||||
|
|
|
@ -110,9 +110,9 @@ export class LegacyWallet extends AbstractWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.balance = new BigNumber(json.final_balance);
|
this.balance = new BigNumber(json.final_balance);
|
||||||
this.balance = this.balance.div(100000000).toString() * 1;
|
this.balance = this.balance.dividedBy(100000000).toString() * 1;
|
||||||
this.unconfirmed_balance = new BigNumber(json.unconfirmed_balance);
|
this.unconfirmed_balance = new BigNumber(json.unconfirmed_balance);
|
||||||
this.unconfirmed_balance = this.unconfirmed_balance.div(100000000).toString() * 1;
|
this.unconfirmed_balance = this.unconfirmed_balance.dividedBy(100000000).toString() * 1;
|
||||||
this._lastBalanceFetch = +new Date();
|
this._lastBalanceFetch = +new Date();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
|
@ -457,11 +457,11 @@ export class LegacyWallet extends AbstractWallet {
|
||||||
u.txid = u.tx_hash;
|
u.txid = u.tx_hash;
|
||||||
u.vout = u.tx_output_n;
|
u.vout = u.tx_output_n;
|
||||||
u.amount = new BigNumber(u.value);
|
u.amount = new BigNumber(u.value);
|
||||||
u.amount = u.amount.div(100000000);
|
u.amount = u.amount.dividedBy(100000000);
|
||||||
u.amount = u.amount.toString(10);
|
u.amount = u.amount.toString(10);
|
||||||
}
|
}
|
||||||
// console.log('creating legacy tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
|
// console.log('creating legacy tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
|
||||||
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
|
let amountPlusFee = parseFloat(new BigNumber(amount).plus(fee).toString(10));
|
||||||
return signer.createTransaction(utxos, toAddress, amountPlusFee, fee, this.getSecret(), this.getAddress());
|
return signer.createTransaction(utxos, toAddress, amountPlusFee, fee, this.getSecret(), this.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,7 @@ export class LightningCustodianWallet extends LegacyWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
getBalance() {
|
getBalance() {
|
||||||
return new BigNumber(this.balance).div(100000000).toString(10);
|
return new BigNumber(this.balance).dividedBy(100000000).toString(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBalance(noRetry) {
|
async fetchBalance(noRetry) {
|
||||||
|
|
|
@ -62,11 +62,11 @@ export class SegwitP2SHWallet extends LegacyWallet {
|
||||||
u.txid = u.tx_hash;
|
u.txid = u.tx_hash;
|
||||||
u.vout = u.tx_output_n;
|
u.vout = u.tx_output_n;
|
||||||
u.amount = new BigNumber(u.value);
|
u.amount = new BigNumber(u.value);
|
||||||
u.amount = u.amount.div(100000000);
|
u.amount = u.amount.dividedBy(100000000);
|
||||||
u.amount = u.amount.toString(10);
|
u.amount = u.amount.toString(10);
|
||||||
}
|
}
|
||||||
// console.log('creating tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
|
// console.log('creating tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
|
||||||
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
|
let amountPlusFee = parseFloat(new BigNumber(amount).plus(fee).toString(10));
|
||||||
// to compensate that module substracts fee from amount
|
// to compensate that module substracts fee from amount
|
||||||
return signer.createSegwitTransaction(utxos, address, amountPlusFee, fee, this.getSecret(), this.getAddress(), sequence);
|
return signer.createSegwitTransaction(utxos, address, amountPlusFee, fee, this.getSecret(), this.getAddress(), sequence);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,8 +59,8 @@ function satoshiToLocalCurrency(satoshi) {
|
||||||
|
|
||||||
let b = new BigNumber(satoshi);
|
let b = new BigNumber(satoshi);
|
||||||
b = b
|
b = b
|
||||||
.div(100000000)
|
.dividedBy(100000000)
|
||||||
.mul(lang[STRUCT.BTC_USD])
|
.multipliedBy(lang[STRUCT.BTC_USD])
|
||||||
.toString(10);
|
.toString(10);
|
||||||
b = parseFloat(b).toFixed(2);
|
b = parseFloat(b).toFixed(2);
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ function satoshiToLocalCurrency(satoshi) {
|
||||||
|
|
||||||
function satoshiToBTC(satoshi) {
|
function satoshiToBTC(satoshi) {
|
||||||
let b = new BigNumber(satoshi);
|
let b = new BigNumber(satoshi);
|
||||||
b = b.div(100000000);
|
b = b.dividedBy(100000000);
|
||||||
return b.toString(10) + ' BTC';
|
return b.toString(10) + ' BTC';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
loc/en.js
18
loc/en.js
|
@ -31,7 +31,7 @@ module.exports = {
|
||||||
create: 'Create',
|
create: 'Create',
|
||||||
label_new_segwit: 'New SegWit',
|
label_new_segwit: 'New SegWit',
|
||||||
wallet_name: 'wallet name',
|
wallet_name: 'wallet name',
|
||||||
wallet_type: 'wallet type',
|
wallet_type: 'type',
|
||||||
or: 'or',
|
or: 'or',
|
||||||
import_wallet: 'Import wallet',
|
import_wallet: 'Import wallet',
|
||||||
imported: 'Imported',
|
imported: 'Imported',
|
||||||
|
@ -97,15 +97,17 @@ module.exports = {
|
||||||
header: 'Send',
|
header: 'Send',
|
||||||
details: {
|
details: {
|
||||||
title: 'create transaction',
|
title: 'create transaction',
|
||||||
amount_fiels_is_not_valid: 'Amount field is not valid',
|
amount_field_is_not_valid: 'Amount field is not valid',
|
||||||
fee_fiels_is_not_valid: 'Fee field is not valid',
|
fee_field_is_not_valid: 'Fee field is not valid',
|
||||||
address_fiels_is_not_valid: 'Address field is not valid',
|
address_field_is_not_valid: 'Address field is not valid',
|
||||||
receiver_placeholder: 'receiver address here',
|
total_exceeds_balance: 'The sending amount exceeds the available balance.',
|
||||||
|
address: 'address',
|
||||||
amount_placeholder: 'amount to send (in BTC)',
|
amount_placeholder: 'amount to send (in BTC)',
|
||||||
fee_placeholder: 'plus transaction fee (in BTC)',
|
fee_placeholder: 'plus transaction fee (in BTC)',
|
||||||
memo_placeholder: 'memo to self',
|
note_placeholder: 'note to self',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
scan: 'Scan',
|
scan: 'Scan',
|
||||||
|
send: 'Send',
|
||||||
create: 'Create',
|
create: 'Create',
|
||||||
remaining_balance: 'Remaining balance',
|
remaining_balance: 'Remaining balance',
|
||||||
},
|
},
|
||||||
|
@ -113,12 +115,12 @@ module.exports = {
|
||||||
title: 'create transaction',
|
title: 'create transaction',
|
||||||
error: 'Error creating transaction. Invalid address or send amount?',
|
error: 'Error creating transaction. Invalid address or send amount?',
|
||||||
go_back: 'Go Back',
|
go_back: 'Go Back',
|
||||||
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network. Continue?',
|
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
|
||||||
to: 'To',
|
to: 'To',
|
||||||
amount: 'Amount',
|
amount: 'Amount',
|
||||||
fee: 'Fee',
|
fee: 'Fee',
|
||||||
tx_size: 'TX size',
|
tx_size: 'TX size',
|
||||||
satoshi_per_byte: 'satoshiPerByte',
|
satoshi_per_byte: 'Satoshi per byte',
|
||||||
memo: 'Memo',
|
memo: 'Memo',
|
||||||
broadcast: 'Broadcast',
|
broadcast: 'Broadcast',
|
||||||
not_enough_fee: 'Not enough fee. Increase the fee',
|
not_enough_fee: 'Not enough fee. Increase the fee',
|
||||||
|
|
11
loc/es.js
11
loc/es.js
|
@ -97,17 +97,20 @@ module.exports = {
|
||||||
header: 'enviar',
|
header: 'enviar',
|
||||||
details: {
|
details: {
|
||||||
title: 'Crear Transaccion',
|
title: 'Crear Transaccion',
|
||||||
amount_fiels_is_not_valid: 'La cantidad no es válida',
|
amount_field_is_not_valid: 'La cantidad no es válida',
|
||||||
fee_fiels_is_not_valid: 'La tasa no es válida',
|
fee_field_is_not_valid: 'La tasa no es válida',
|
||||||
address_fiels_is_not_valid: 'La dirección no es válida',
|
address_field_is_not_valid: 'La dirección no es válida',
|
||||||
receiver_placeholder: 'La dirección de recipiente',
|
receiver_placeholder: 'La dirección de recipiente',
|
||||||
amount_placeholder: 'cantidad para enviar (in BTC)',
|
amount_placeholder: 'cantidad para enviar (in BTC)',
|
||||||
fee_placeholder: 'más tasa de transaccion (in BTC)',
|
fee_placeholder: 'más tasa de transaccion (in BTC)',
|
||||||
memo_placeholder: 'comentario (para ti mismo)',
|
note_placeholder: 'comentario (para ti mismo)',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
scan: 'Escaniar',
|
scan: 'Escaniar',
|
||||||
|
address: 'Direccion',
|
||||||
create: 'Crear',
|
create: 'Crear',
|
||||||
|
send: 'Envíar',
|
||||||
remaining_balance: 'Balance disponible',
|
remaining_balance: 'Balance disponible',
|
||||||
|
total_exceeds_balance: 'El monto excede el balance disponible.',
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
title: 'Crear una Transaccion',
|
title: 'Crear una Transaccion',
|
||||||
|
|
42
loc/index.js
42
loc/index.js
|
@ -1,6 +1,7 @@
|
||||||
import Localization from 'react-localization';
|
import Localization from 'react-localization';
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { AppStorage } from '../class';
|
import { AppStorage } from '../class';
|
||||||
|
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||||
let BigNumber = require('bignumber.js');
|
let BigNumber = require('bignumber.js');
|
||||||
let strings;
|
let strings;
|
||||||
|
|
||||||
|
@ -61,12 +62,47 @@ strings.transactionTimeToReadable = function(time) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
strings.formatBalance = function(balance) {
|
strings.formatBalance = (balance, unit) => {
|
||||||
|
const conversion = 100000000;
|
||||||
|
if (unit === undefined) {
|
||||||
if (balance < 0.1 && balance !== 0) {
|
if (balance < 0.1 && balance !== 0) {
|
||||||
let b = new BigNumber(balance);
|
let b = new BigNumber(balance);
|
||||||
return b.mul(1000).toString() + ' mBTC';
|
return b.multipliedBy(1000).toString() + ' ' + BitcoinUnit.MBTC;
|
||||||
}
|
}
|
||||||
return balance + ' BTC';
|
return balance + ' ' + BitcoinUnit.BTC;
|
||||||
|
} else {
|
||||||
|
if (balance !== 0) {
|
||||||
|
let b = new BigNumber(balance);
|
||||||
|
if (unit === BitcoinUnit.MBTC) {
|
||||||
|
return b.multipliedBy(1000).toString() + ' ' + BitcoinUnit.MBTC;
|
||||||
|
} else if (unit === BitcoinUnit.BITS) {
|
||||||
|
return b.multipliedBy(1000000).toString() + ' ' + BitcoinUnit.BITS;
|
||||||
|
} else if (unit === BitcoinUnit.SATOSHIS) {
|
||||||
|
return (b.times(conversion).toString() + ' ' + BitcoinUnit.SATOSHIS).replace(/\./g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return balance + ' ' + BitcoinUnit.BTC;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
strings.formatBalanceWithoutSuffix = (balance, unit) => {
|
||||||
|
const conversion = 100000000;
|
||||||
|
if (balance !== 0) {
|
||||||
|
let b = new BigNumber(balance);
|
||||||
|
if (unit === BitcoinUnit.BTC) {
|
||||||
|
return Number(b.div(conversion));
|
||||||
|
} else if (unit === BitcoinUnit.MBTC) {
|
||||||
|
return b.multipliedBy(1000).toString();
|
||||||
|
} else if (unit === BitcoinUnit.BITS) {
|
||||||
|
return b.multipliedBy(1000000).toString();
|
||||||
|
} else if (unit === BitcoinUnit.SATOSHIS) {
|
||||||
|
return b
|
||||||
|
.times(conversion)
|
||||||
|
.toString()
|
||||||
|
.replace(/\./g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return balance;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = strings;
|
module.exports = strings;
|
||||||
|
|
11
loc/pt_BR.js
11
loc/pt_BR.js
|
@ -98,16 +98,19 @@ module.exports = {
|
||||||
header: 'Enviar',
|
header: 'Enviar',
|
||||||
details: {
|
details: {
|
||||||
title: 'Criar Transacção',
|
title: 'Criar Transacção',
|
||||||
amount_fiels_is_not_valid: 'Campo de quantia não é válido',
|
amount_field_is_not_valid: 'Campo de quantia não é válido',
|
||||||
fee_fiels_is_not_valid: 'Campo de taxa não é válido',
|
fee_field_is_not_valid: 'Campo de taxa não é válido',
|
||||||
address_fiels_is_not_valid: 'Campo de endereço não é válido',
|
address_field_is_not_valid: 'Campo de endereço não é válido',
|
||||||
receiver_placeholder: 'endereço de envio aqui',
|
receiver_placeholder: 'endereço de envio aqui',
|
||||||
amount_placeholder: 'quantia a enviar (em BTC)',
|
amount_placeholder: 'quantia a enviar (em BTC)',
|
||||||
fee_placeholder: 'mais a taxa de transacção (em BTC)',
|
fee_placeholder: 'mais a taxa de transacção (em BTC)',
|
||||||
memo_placeholder: 'Nota pessoal',
|
note_placeholder: 'Nota pessoal',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
scan: 'Scanear',
|
scan: 'Scanear',
|
||||||
create: 'Criar',
|
create: 'Criar',
|
||||||
|
address: 'Address',
|
||||||
|
total_exceeds_balance: 'The total amount exceeds balance',
|
||||||
|
send: 'Send',
|
||||||
remaining_balance: 'Saldo restante',
|
remaining_balance: 'Saldo restante',
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
|
|
11
loc/pt_PT.js
11
loc/pt_PT.js
|
@ -97,16 +97,19 @@ module.exports = {
|
||||||
header: 'Enviar',
|
header: 'Enviar',
|
||||||
details: {
|
details: {
|
||||||
title: 'Criar Transacção',
|
title: 'Criar Transacção',
|
||||||
amount_fiels_is_not_valid: 'Campo de quantia não é válido',
|
amount_field_is_not_valid: 'Campo de quantia não é válido',
|
||||||
fee_fiels_is_not_valid: 'Campo de taxa não é válido',
|
fee_field_is_not_valid: 'Campo de taxa não é válido',
|
||||||
address_fiels_is_not_valid: 'Campo de endereço não é válido',
|
address_field_is_not_valid: 'Campo de endereço não é válido',
|
||||||
receiver_placeholder: 'endereço de envio aqui',
|
receiver_placeholder: 'endereço de envio aqui',
|
||||||
amount_placeholder: 'quantia a enviar (em BTC)',
|
amount_placeholder: 'quantia a enviar (em BTC)',
|
||||||
fee_placeholder: 'mais a taxa de transacção (em BTC)',
|
fee_placeholder: 'mais a taxa de transacção (em BTC)',
|
||||||
memo_placeholder: 'Nota pessoal',
|
note_placeholder: 'Nota pessoal',
|
||||||
|
total_exceeds_balance: 'Total amount exceeds available balance.',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
scan: 'Scan',
|
scan: 'Scan',
|
||||||
create: 'Criar',
|
create: 'Criar',
|
||||||
|
address: 'Address',
|
||||||
|
send: 'Send',
|
||||||
remaining_balance: 'Saldo restante',
|
remaining_balance: 'Saldo restante',
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
|
|
11
loc/ru.js
11
loc/ru.js
|
@ -96,16 +96,19 @@ module.exports = {
|
||||||
header: 'Отправить',
|
header: 'Отправить',
|
||||||
details: {
|
details: {
|
||||||
title: 'Создать Транзакцию',
|
title: 'Создать Транзакцию',
|
||||||
amount_fiels_is_not_valid: 'Поле не валидно',
|
amount_field_is_not_valid: 'Поле не валидно',
|
||||||
fee_fiels_is_not_valid: 'Поле `комиссия` не валидно',
|
fee_field_is_not_valid: 'Поле `комиссия` не валидно',
|
||||||
address_fiels_is_not_valid: 'Поле `адрес` не валидно',
|
address_field_is_not_valid: 'Поле `адрес` не валидно',
|
||||||
receiver_placeholder: 'Адрес получателя',
|
receiver_placeholder: 'Адрес получателя',
|
||||||
amount_placeholder: 'сколько отправить (в BTC)',
|
amount_placeholder: 'сколько отправить (в BTC)',
|
||||||
fee_placeholder: 'плюс комиссия за перевод (в BTC)',
|
fee_placeholder: 'плюс комиссия за перевод (в BTC)',
|
||||||
memo_placeholder: 'примечание платежа',
|
note_placeholder: 'примечание платежа',
|
||||||
cancel: 'Отмена',
|
cancel: 'Отмена',
|
||||||
scan: 'Скан QR',
|
scan: 'Скан QR',
|
||||||
create: 'Создать',
|
create: 'Создать',
|
||||||
|
send: 'Send',
|
||||||
|
address: 'Address',
|
||||||
|
total_exceeds_balance: 'Total amount exceeds balance.',
|
||||||
remaining_balance: 'Остаток баланса',
|
remaining_balance: 'Остаток баланса',
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
|
|
11
loc/ua.js
11
loc/ua.js
|
@ -96,15 +96,18 @@ module.exports = {
|
||||||
header: 'Переказ',
|
header: 'Переказ',
|
||||||
details: {
|
details: {
|
||||||
title: 'Створити Транзакцію',
|
title: 'Створити Транзакцію',
|
||||||
amount_fiels_is_not_valid: 'Поле не валідно',
|
amount_field_is_not_valid: 'Поле не валідно',
|
||||||
fee_fiels_is_not_valid: 'Поле `комісія` не валідно',
|
fee_field_is_not_valid: 'Поле `комісія` не валідно',
|
||||||
address_fiels_is_not_valid: 'Поле `адреса` не валідно',
|
address_field_is_not_valid: 'Поле `адреса` не валідно',
|
||||||
receiver_placeholder: 'Адреса одержувача',
|
receiver_placeholder: 'Адреса одержувача',
|
||||||
amount_placeholder: 'скільки відправити (в BTC)',
|
amount_placeholder: 'скільки відправити (в BTC)',
|
||||||
fee_placeholder: 'плюс комісія за переказ (в BTC)',
|
fee_placeholder: 'плюс комісія за переказ (в BTC)',
|
||||||
memo_placeholder: 'примітка платежу',
|
note_placeholder: 'примітка платежу',
|
||||||
cancel: 'Відміна',
|
cancel: 'Відміна',
|
||||||
scan: 'Скан QR',
|
scan: 'Скан QR',
|
||||||
|
send: 'Send',
|
||||||
|
total_exceeds_balance: 'total_exceeds_balance',
|
||||||
|
address: 'Address',
|
||||||
create: 'Створити',
|
create: 'Створити',
|
||||||
remaining_balance: 'Залишок балансу',
|
remaining_balance: 'Залишок балансу',
|
||||||
},
|
},
|
||||||
|
|
6
models/bitcoinUnits.js
Normal file
6
models/bitcoinUnits.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export const BitcoinUnit = Object.freeze({
|
||||||
|
BTC: 'BTC',
|
||||||
|
MBTC: 'mBTC',
|
||||||
|
BITS: 'bits',
|
||||||
|
SATOSHIS: 'satoshis',
|
||||||
|
});
|
30
models/networkTransactionFees.js
Normal file
30
models/networkTransactionFees.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import Frisbee from 'frisbee';
|
||||||
|
|
||||||
|
export class NetworkTransactionFee {
|
||||||
|
constructor(fastestFee, halfHourFee, hourFee) {
|
||||||
|
this.fastestFee = fastestFee;
|
||||||
|
this.halfHourFee = halfHourFee;
|
||||||
|
this.hourFee = hourFee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NetworkTransactionFees {
|
||||||
|
static recommendedFees() {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const api = new Frisbee({ baseURI: 'https://bitcoinfees.earn.com' });
|
||||||
|
let response = await api.get('/api/v1/fees/recommended');
|
||||||
|
if (response && response.body) {
|
||||||
|
const networkFee = new NetworkTransactionFee(response.body.fastestFee, response.body.halfHourFee, response.body.hourFee);
|
||||||
|
resolve(networkFee);
|
||||||
|
} else {
|
||||||
|
throw new Error('Could not fetch recommended network fees: ' + response.err);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
const networkFee = new NetworkTransactionFee(1, 1, 1);
|
||||||
|
reject(networkFee);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
39
package-lock.json
generated
39
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "BlueWallet",
|
"name": "BlueWallet",
|
||||||
"version": "2.6.1",
|
"version": "3.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -2952,9 +2952,9 @@
|
||||||
"integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU="
|
"integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU="
|
||||||
},
|
},
|
||||||
"bignumber.js": {
|
"bignumber.js": {
|
||||||
"version": "5.0.0",
|
"version": "7.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
|
||||||
"integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg=="
|
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ=="
|
||||||
},
|
},
|
||||||
"bip21": {
|
"bip21": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
@ -11754,6 +11754,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-animatable": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-GGYEYvderfzPZcPnw7xov4nlRmi9d6oqcIzx0fGkUUsMshOQEtq5IEzFp3np0uTB9n8/gZIZcdbUPggVlVydMg==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-branch": {
|
"react-native-branch": {
|
||||||
"version": "2.2.5",
|
"version": "2.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-branch/-/react-native-branch-2.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-branch/-/react-native-branch-2.2.5.tgz",
|
||||||
|
@ -11840,6 +11848,20 @@
|
||||||
"prop-types": "^15.5.10"
|
"prop-types": "^15.5.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-iphone-x-helper": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-xIeTo4s77wwKgBZLVRIZC9tM9/PkXS46Ul76NXmvmixEb3ZwqGdQesR3zRiLMOoIdfOURB6N9bba9po7+x9Bag=="
|
||||||
|
},
|
||||||
|
"react-native-keyboard-aware-scroll-view": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-keyboard-aware-scroll-view/-/react-native-keyboard-aware-scroll-view-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-5AJ11sGcleiX43IJ32T0kPtIxcd1JWw0R+YPwkgyZWro7uuGBlq09CFW5jS9JTNoGq7crFzFfTcoTIY6B41rNQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-native-iphone-x-helper": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-level-fs": {
|
"react-native-level-fs": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-level-fs/-/react-native-level-fs-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-level-fs/-/react-native-level-fs-3.0.1.tgz",
|
||||||
|
@ -11950,6 +11972,15 @@
|
||||||
"prop-types": "^15.5.9"
|
"prop-types": "^15.5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-modal": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-ewchdETAGd32xLGLK93NETEGkRcePtN7ZwjmLSQnNW1Zd0SRUYE8NqftjamPyfKvK0i2DZjX4YAghGZTqaRUbA==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"react-native-animatable": "^1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-qrcode": {
|
"react-native-qrcode": {
|
||||||
"version": "0.2.7",
|
"version": "0.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-qrcode/-/react-native-qrcode-0.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-qrcode/-/react-native-qrcode-0.2.7.tgz",
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asyncstorage-down": "^3.1.1",
|
"asyncstorage-down": "^3.1.1",
|
||||||
"bignumber.js": "^5.0.0",
|
"bignumber.js": "^7.0.0",
|
||||||
"bip21": "^2.0.2",
|
"bip21": "^2.0.2",
|
||||||
"bip39": "^2.5.0",
|
"bip39": "^2.5.0",
|
||||||
"bitcoinjs-lib": "^3.3.2",
|
"bitcoinjs-lib": "^3.3.2",
|
||||||
|
@ -63,8 +63,10 @@
|
||||||
"react-native-camera": "^0.12.0",
|
"react-native-camera": "^0.12.0",
|
||||||
"react-native-elements": "^0.19.0",
|
"react-native-elements": "^0.19.0",
|
||||||
"react-native-flexi-radio-button": "^0.2.2",
|
"react-native-flexi-radio-button": "^0.2.2",
|
||||||
|
"react-native-keyboard-aware-scroll-view": "^0.7.4",
|
||||||
"react-native-level-fs": "^3.0.0",
|
"react-native-level-fs": "^3.0.0",
|
||||||
"react-native-material-dropdown": "^0.11.1",
|
"react-native-material-dropdown": "^0.11.1",
|
||||||
|
"react-native-modal": "^6.5.0",
|
||||||
"react-native-qrcode": "^0.2.7",
|
"react-native-qrcode": "^0.2.7",
|
||||||
"react-native-snap-carousel": "^3.7.4",
|
"react-native-snap-carousel": "^3.7.4",
|
||||||
"react-navigation": "^2.17.0",
|
"react-navigation": "^2.17.0",
|
||||||
|
|
|
@ -175,8 +175,8 @@ export default class Selftest extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let feeSatoshi = new BigNumber(0.0001);
|
let feeSatoshi = new BigNumber(0.0001);
|
||||||
feeSatoshi = feeSatoshi.mul(100000000);
|
feeSatoshi = feeSatoshi.multipliedBy(100000000);
|
||||||
let satoshiPerByte = feeSatoshi.div(Math.round(tx.length / 2));
|
let satoshiPerByte = feeSatoshi.dividedBy(Math.round(tx.length / 2));
|
||||||
satoshiPerByte = Math.round(satoshiPerByte.toString(10));
|
satoshiPerByte = Math.round(satoshiPerByte.toString(10));
|
||||||
|
|
||||||
if (satoshiPerByte !== 46) {
|
if (satoshiPerByte !== 46) {
|
||||||
|
|
|
@ -1,237 +1,146 @@
|
||||||
|
/* global alert */
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { TextInput } from 'react-native';
|
import { TextInput, ActivityIndicator, TouchableOpacity, Clipboard, StyleSheet, ScrollView } from 'react-native';
|
||||||
import { Text, FormValidationMessage } from 'react-native-elements';
|
import { Text } from 'react-native-elements';
|
||||||
import {
|
import { BlueButton, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
|
||||||
BlueSpacingVariable,
|
|
||||||
BlueHeaderDefaultSub,
|
|
||||||
BlueLoading,
|
|
||||||
BlueSpacing20,
|
|
||||||
BlueButton,
|
|
||||||
SafeBlueArea,
|
|
||||||
BlueCard,
|
|
||||||
BlueText,
|
|
||||||
} from '../../BlueComponents';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
let BigNumber = require('bignumber.js');
|
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
let BlueApp = require('../../BlueApp');
|
// let BlueApp = require('../../BlueApp');
|
||||||
let loc = require('../../loc');
|
let loc = require('../../loc');
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
|
|
||||||
export default class SendCreate extends Component {
|
export default class SendCreate extends Component {
|
||||||
static navigationOptions = {
|
|
||||||
header: ({ navigation }) => {
|
|
||||||
return <BlueHeaderDefaultSub leftText={loc.send.create.title} onClose={() => navigation.goBack(null)} />;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
console.log('send/create constructor');
|
console.log('send/create constructor');
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: false,
|
||||||
amount: props.navigation.state.params.amount,
|
amount: props.navigation.state.params.amount,
|
||||||
fee: props.navigation.state.params.fee,
|
fee: props.navigation.state.params.fee,
|
||||||
address: props.navigation.state.params.address,
|
address: props.navigation.state.params.address,
|
||||||
memo: props.navigation.state.params.memo,
|
memo: props.navigation.state.params.memo,
|
||||||
fromAddress: props.navigation.state.params.fromAddress,
|
size: Math.round(props.navigation.getParam('tx').length / 2),
|
||||||
fromSecret: props.navigation.state.params.fromSecret,
|
tx: props.navigation.getParam('tx'),
|
||||||
broadcastErrorMessage: '',
|
satoshiPerByte: props.navigation.getParam('satoshiPerByte'),
|
||||||
|
fromWallet: props.navigation.getParam('fromWallet'),
|
||||||
};
|
};
|
||||||
|
|
||||||
let fromWallet = false;
|
|
||||||
for (let w of BlueApp.getWallets()) {
|
|
||||||
if (w.getSecret() === this.state.fromSecret) {
|
|
||||||
fromWallet = w;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w.getAddress() && w.getAddress() === this.state.fromAddress) {
|
|
||||||
fromWallet = w;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.state['fromWallet'] = fromWallet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
console.log('send/create - componentDidMount');
|
console.log('send/create - componentDidMount');
|
||||||
console.log('address = ', this.state.address);
|
console.log('address = ', this.state.address);
|
||||||
|
|
||||||
let utxo;
|
|
||||||
let satoshiPerByte;
|
|
||||||
let tx;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.state.fromWallet.fetchUtxo();
|
|
||||||
if (this.state.fromWallet.getChangeAddressAsync) {
|
|
||||||
await this.state.fromWallet.getChangeAddressAsync(); // to refresh internal pointer to next free address
|
|
||||||
}
|
|
||||||
if (this.state.fromWallet.getAddressAsync) {
|
|
||||||
await this.state.fromWallet.getAddressAsync(); // to refresh internal pointer to next free address
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utxo = this.state.fromWallet.utxo;
|
broadcast() {
|
||||||
let startTime = Date.now();
|
this.setState({ isLoading: true }, async () => {
|
||||||
|
|
||||||
tx = this.state.fromWallet.createTx(utxo, this.state.amount, this.state.fee, this.state.address, this.state.memo);
|
|
||||||
let endTime = Date.now();
|
|
||||||
console.log('create tx ', (endTime - startTime) / 1000, 'sec');
|
|
||||||
|
|
||||||
let bitcoin = require('bitcoinjs-lib');
|
|
||||||
let txDecoded = bitcoin.Transaction.fromHex(tx);
|
|
||||||
let txid = txDecoded.getId();
|
|
||||||
console.log('txid', txid);
|
|
||||||
console.log('txhex', tx);
|
|
||||||
|
|
||||||
BlueApp.tx_metadata = BlueApp.tx_metadata || {};
|
|
||||||
BlueApp.tx_metadata[txid] = {
|
|
||||||
txhex: tx,
|
|
||||||
memo: this.state.memo,
|
|
||||||
};
|
|
||||||
BlueApp.saveToDisk();
|
|
||||||
|
|
||||||
let feeSatoshi = new BigNumber(this.state.fee);
|
|
||||||
feeSatoshi = feeSatoshi.mul(100000000);
|
|
||||||
satoshiPerByte = feeSatoshi.div(Math.round(tx.length / 2));
|
|
||||||
satoshiPerByte = Math.floor(satoshiPerByte.toString(10));
|
|
||||||
if (satoshiPerByte < 1) {
|
|
||||||
throw new Error(loc.send.create.not_enough_fee);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return this.setState({
|
|
||||||
isError: true,
|
|
||||||
errorMessage: JSON.stringify(err.message),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isLoading: false,
|
|
||||||
size: Math.round(tx.length / 2),
|
|
||||||
tx,
|
|
||||||
satoshiPerByte,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async broadcast() {
|
|
||||||
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
|
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
|
||||||
console.log('broadcast result = ', result);
|
console.log('broadcast result = ', result);
|
||||||
if (typeof result === 'string') {
|
if (typeof result === 'string') {
|
||||||
result = JSON.parse(result);
|
result = JSON.parse(result);
|
||||||
}
|
}
|
||||||
|
this.setState({ isLoading: false });
|
||||||
if (result && result.error) {
|
if (result && result.error) {
|
||||||
this.setState({
|
alert(JSON.stringify(result.error));
|
||||||
broadcastErrorMessage: JSON.stringify(result.error),
|
|
||||||
broadcastSuccessMessage: '',
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
|
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
|
||||||
this.setState({ broadcastErrorMessage: '' });
|
alert('Transaction has been successfully broadcasted. Your transaction ID is: ' + JSON.stringify(result.result));
|
||||||
this.setState({
|
this.props.navigation.navigate('Wallets');
|
||||||
broadcastSuccessMessage: 'Success! TXID: ' + JSON.stringify(result.result),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.isError) {
|
|
||||||
return (
|
return (
|
||||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
<SafeBlueArea style={{ flex: 1, paddingTop: 19 }}>
|
||||||
|
<ScrollView>
|
||||||
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
|
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
|
||||||
<BlueText>{loc.send.create.error}</BlueText>
|
<BlueText style={{ color: '#0c2550', fontWeight: '500' }}>{loc.send.create.this_is_hex}</BlueText>
|
||||||
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
|
|
||||||
</BlueCard>
|
|
||||||
<BlueButton onPress={() => this.props.navigation.goBack()} title={loc.send.create.go_back} />
|
|
||||||
</SafeBlueArea>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.isLoading) {
|
|
||||||
return <BlueLoading />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
|
||||||
<BlueSpacingVariable />
|
|
||||||
|
|
||||||
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
|
|
||||||
<BlueText>{loc.send.create.this_is_hex}</BlueText>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={{
|
style={{
|
||||||
borderColor: '#ebebeb',
|
borderColor: '#ebebeb',
|
||||||
borderWidth: 1,
|
backgroundColor: '#d2f8d6',
|
||||||
|
borderRadius: 4,
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
color: '#ebebeb',
|
color: '#37c0a1',
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: 14,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingBottom: 16,
|
||||||
|
paddingTop: 16,
|
||||||
}}
|
}}
|
||||||
maxHeight={70}
|
height={72}
|
||||||
multiline
|
multiline
|
||||||
editable={false}
|
editable={false}
|
||||||
value={this.state.tx}
|
value={this.state.tx}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BlueSpacing20 />
|
<TouchableOpacity style={{ marginVertical: 24 }} onPress={() => Clipboard.setString(this.state.tx)}>
|
||||||
|
<Text style={{ color: '#0c2550', fontSize: 15, fontWeight: '500', alignSelf: 'center' }}>Copy and broadcast later</Text>
|
||||||
<BlueText style={{ paddingTop: 20 }}>
|
</TouchableOpacity>
|
||||||
{loc.send.create.to}: {this.state.address}
|
{this.state.isLoading ? (
|
||||||
</BlueText>
|
<ActivityIndicator />
|
||||||
<BlueText>
|
) : (
|
||||||
{loc.send.create.amount}: {this.state.amount} BTC
|
<BlueButton onPress={() => this.broadcast()} title={loc.send.details.send} style={{ maxWidth: 263, paddingHorizontal: 56 }} />
|
||||||
</BlueText>
|
)}
|
||||||
<BlueText>
|
|
||||||
{loc.send.create.fee}: {this.state.fee} BTC
|
|
||||||
</BlueText>
|
|
||||||
<BlueText>
|
|
||||||
{loc.send.create.tx_size}: {this.state.size} Bytes
|
|
||||||
</BlueText>
|
|
||||||
<BlueText>
|
|
||||||
{loc.send.create.satoshi_per_byte}: {this.state.satoshiPerByte} Sat/B
|
|
||||||
</BlueText>
|
|
||||||
<BlueText>
|
|
||||||
{loc.send.create.memo}: {this.state.memo}
|
|
||||||
</BlueText>
|
|
||||||
</BlueCard>
|
</BlueCard>
|
||||||
|
<BlueCard>
|
||||||
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.to}</Text>
|
||||||
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.address}</Text>
|
||||||
|
|
||||||
<BlueButton
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.amount}</Text>
|
||||||
icon={{
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.amount} BTC</Text>
|
||||||
name: 'megaphone',
|
|
||||||
type: 'octicon',
|
|
||||||
color: BlueApp.settings.buttonTextColor,
|
|
||||||
}}
|
|
||||||
onPress={() => this.broadcast()}
|
|
||||||
title={loc.send.create.broadcast}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BlueButton
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.fee}</Text>
|
||||||
icon={{
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.fee} BTC</Text>
|
||||||
name: 'arrow-left',
|
|
||||||
type: 'octicon',
|
|
||||||
color: BlueApp.settings.buttonTextColor,
|
|
||||||
}}
|
|
||||||
onPress={() => this.props.navigation.goBack()}
|
|
||||||
title={loc.send.create.go_back}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormValidationMessage>{this.state.broadcastErrorMessage}</FormValidationMessage>
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.tx_size}</Text>
|
||||||
<Text style={{ padding: 0, color: '#0f0' }}>{this.state.broadcastSuccessMessage}</Text>
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.size} bytes</Text>
|
||||||
|
|
||||||
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.satoshi_per_byte}</Text>
|
||||||
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.satoshiPerByte} Sat/B</Text>
|
||||||
|
|
||||||
|
<Text style={styles.transactionDetailsTitle}>{loc.send.create.memo}</Text>
|
||||||
|
<Text style={styles.transactionDetailsSubtitle}>{this.state.memo}</Text>
|
||||||
|
</BlueCard>
|
||||||
|
</ScrollView>
|
||||||
</SafeBlueArea>
|
</SafeBlueArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
transactionDetailsTitle: {
|
||||||
|
color: '#0c2550',
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: 17,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
transactionDetailsSubtitle: {
|
||||||
|
color: '#9aa0aa',
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: 15,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
SendCreate.propTypes = {
|
SendCreate.propTypes = {
|
||||||
navigation: PropTypes.shape({
|
navigation: PropTypes.shape({
|
||||||
goBack: PropTypes.function,
|
goBack: PropTypes.function,
|
||||||
|
getParam: PropTypes.function,
|
||||||
|
navigate: PropTypes.function,
|
||||||
state: PropTypes.shape({
|
state: PropTypes.shape({
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
amount: PropTypes.string,
|
amount: PropTypes.string,
|
||||||
fee: PropTypes.string,
|
fee: PropTypes.number,
|
||||||
address: PropTypes.string,
|
address: PropTypes.string,
|
||||||
memo: PropTypes.string,
|
memo: PropTypes.string,
|
||||||
|
fromWallet: PropTypes.shape({
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromSecret: PropTypes.string,
|
fromSecret: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
|
/* global alert */
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { ActivityIndicator, View } from 'react-native';
|
|
||||||
import { Text, FormValidationMessage } from 'react-native-elements';
|
|
||||||
import {
|
import {
|
||||||
BlueSpacing20,
|
ActivityIndicator,
|
||||||
BlueHeaderDefaultSub,
|
View,
|
||||||
BlueButton,
|
TextInput,
|
||||||
SafeBlueArea,
|
TouchableOpacity,
|
||||||
BlueText,
|
KeyboardAvoidingView,
|
||||||
BlueFormInput,
|
Keyboard,
|
||||||
BlueFormInputAddress,
|
TouchableWithoutFeedback,
|
||||||
} from '../../BlueComponents';
|
StyleSheet,
|
||||||
|
Slider,
|
||||||
|
} from 'react-native';
|
||||||
|
import { Text, Icon } from 'react-native-elements';
|
||||||
|
import { BlueHeaderDefaultSub, BlueButton } from '../../BlueComponents';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import Modal from 'react-native-modal';
|
||||||
|
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
|
||||||
|
import BitcoinBIP70TransactionDecode from '../../bip70/bip70';
|
||||||
|
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||||
const bip21 = require('bip21');
|
const bip21 = require('bip21');
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
let BigNumber = require('bignumber.js');
|
let BigNumber = require('bignumber.js');
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
let BlueApp = require('../../BlueApp');
|
let BlueApp = require('../../BlueApp');
|
||||||
let loc = require('../../loc');
|
let loc = require('../../loc');
|
||||||
|
let bitcoin = require('bitcoinjs-lib');
|
||||||
|
|
||||||
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
|
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
|
||||||
|
|
||||||
export default class SendDetails extends Component {
|
export default class SendDetails extends Component {
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
header: ({ navigation }) => {
|
header: ({ navigation }) => {
|
||||||
return <BlueHeaderDefaultSub leftText={loc.send.details.title.toLowerCase()} onClose={() => navigation.goBack(null)} />;
|
return <BlueHeaderDefaultSub leftText={loc.send.header.toLowerCase()} onClose={() => navigation.goBack(null)} />;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +66,7 @@ export default class SendDetails extends Component {
|
||||||
console.log({ memo });
|
console.log({ memo });
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errorMessage: false,
|
isFeeSelectionModalVisible: false,
|
||||||
fromAddress: fromAddress,
|
fromAddress: fromAddress,
|
||||||
fromWallet: fromWallet,
|
fromWallet: fromWallet,
|
||||||
fromSecret: fromSecret,
|
fromSecret: fromSecret,
|
||||||
|
@ -66,25 +74,40 @@ export default class SendDetails extends Component {
|
||||||
address: address,
|
address: address,
|
||||||
amount: '',
|
amount: '',
|
||||||
memo,
|
memo,
|
||||||
fee: '',
|
fee: 1,
|
||||||
|
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
|
||||||
|
feeSliderValue: 1,
|
||||||
|
bip70TransactionExpiration: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => {
|
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => {
|
||||||
console.log('received event with ', data);
|
|
||||||
|
|
||||||
if (btcAddressRx.test(data)) {
|
if (btcAddressRx.test(data)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
address: data,
|
address: data,
|
||||||
|
bip70TransactionExpiration: null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { address, options } = bip21.decode(data);
|
const { address, options } = bip21.decode(data);
|
||||||
|
console.warn(data);
|
||||||
if (btcAddressRx.test(address)) {
|
if (btcAddressRx.test(address)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
address,
|
address,
|
||||||
amount: options.amount,
|
amount: options.amount,
|
||||||
memo: options.label,
|
memo: options.label,
|
||||||
|
bip70TransactionExpiration: null,
|
||||||
});
|
});
|
||||||
|
} else if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) {
|
||||||
|
BitcoinBIP70TransactionDecode.decode(data)
|
||||||
|
.then(response => {
|
||||||
|
this.setState({
|
||||||
|
address: response.address,
|
||||||
|
amount: loc.formatBalanceWithoutSuffix(response.amount, BitcoinUnit.BTC),
|
||||||
|
memo: response.memo,
|
||||||
|
fee: response.fee,
|
||||||
|
bip70TransactionExpiration: response.expires,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => alert(error.errorMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -93,6 +116,14 @@ export default class SendDetails extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => {
|
||||||
|
this.setState({ fee: response.halfHourFee, networkTransactionFees: response, feeSliderValue: response.halfHourFee });
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
fee: recommendedFees.halfHourFee,
|
||||||
|
networkTransactionFees: recommendedFees,
|
||||||
|
feeSliderValue: recommendedFees.halfHourFee,
|
||||||
|
});
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
console.log('send/details - componentDidMount');
|
console.log('send/details - componentDidMount');
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -108,8 +139,8 @@ export default class SendDetails extends Component {
|
||||||
let availableBalance;
|
let availableBalance;
|
||||||
try {
|
try {
|
||||||
availableBalance = new BigNumber(balance);
|
availableBalance = new BigNumber(balance);
|
||||||
availableBalance = availableBalance.sub(amount);
|
availableBalance = availableBalance.minus(amount);
|
||||||
availableBalance = availableBalance.sub(fee);
|
availableBalance = availableBalance.minus(fee);
|
||||||
availableBalance = availableBalance.toString(10);
|
availableBalance = availableBalance.toString(10);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return balance;
|
return balance;
|
||||||
|
@ -118,62 +149,193 @@ export default class SendDetails extends Component {
|
||||||
return (availableBalance === 'NaN' && balance) || availableBalance;
|
return (availableBalance === 'NaN' && balance) || availableBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
createTransaction() {
|
async createTransaction() {
|
||||||
if (!this.state.amount || this.state.amount === '0') {
|
let error = false;
|
||||||
this.setState({
|
let requestedSatPerByte = this.state.fee.toString().replace(/\D/g, '');
|
||||||
errorMessage: loc.send.details.amount_fiels_is_not_valid,
|
|
||||||
});
|
console.log({ requestedSatPerByte });
|
||||||
|
|
||||||
|
if (!this.state.amount || this.state.amount === '0' || parseFloat(this.state.amount) === 0) {
|
||||||
|
error = loc.send.details.amount_field_is_not_valid;
|
||||||
console.log('validation error');
|
console.log('validation error');
|
||||||
|
} else if (!this.state.fee || !requestedSatPerByte || parseFloat(requestedSatPerByte) < 1) {
|
||||||
|
error = loc.send.details.fee_field_is_not_valid;
|
||||||
|
console.log('validation error');
|
||||||
|
} else if (!this.state.address) {
|
||||||
|
error = loc.send.details.address_field_is_not_valid;
|
||||||
|
console.log('validation error');
|
||||||
|
} else if (this.recalculateAvailableBalance(this.state.fromWallet.getBalance(), this.state.amount, 0) < 0) {
|
||||||
|
// first sanity check is that sending amount is not bigger than available balance
|
||||||
|
error = loc.send.details.total_exceeds_balance;
|
||||||
|
console.log('validation error');
|
||||||
|
} else if (BitcoinBIP70TransactionDecode.isExpired(this.state.bip70TransactionExpiration)) {
|
||||||
|
error = 'Transaction has expired.';
|
||||||
|
console.log('validation error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
alert(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.fee) {
|
this.setState({ isLoading: true }, async () => {
|
||||||
this.setState({
|
let utxo;
|
||||||
errorMessage: loc.send.details.fee_fiels_is_not_valid,
|
let actualSatoshiPerByte;
|
||||||
});
|
let tx, txid;
|
||||||
console.log('validation error');
|
let tries = 1;
|
||||||
|
let fee = 0.000001; // initial fee guess
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.state.fromWallet.fetchUtxo();
|
||||||
|
if (this.state.fromWallet.getChangeAddressAsync) {
|
||||||
|
await this.state.fromWallet.getChangeAddressAsync(); // to refresh internal pointer to next free address
|
||||||
|
}
|
||||||
|
if (this.state.fromWallet.getAddressAsync) {
|
||||||
|
await this.state.fromWallet.getAddressAsync(); // to refresh internal pointer to next free address
|
||||||
|
}
|
||||||
|
|
||||||
|
utxo = this.state.fromWallet.utxo;
|
||||||
|
|
||||||
|
do {
|
||||||
|
console.log('try #', tries, 'fee=', fee);
|
||||||
|
if (this.recalculateAvailableBalance(this.state.fromWallet.getBalance(), this.state.amount, fee) < 0) {
|
||||||
|
// we could not add any fee. user is trying to send all he's got. that wont work
|
||||||
|
throw new Error(loc.send.details.total_exceeds_balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
let startTime = Date.now();
|
||||||
|
tx = this.state.fromWallet.createTx(utxo, this.state.amount, fee, this.state.address, this.state.memo);
|
||||||
|
let endTime = Date.now();
|
||||||
|
console.log('create tx ', (endTime - startTime) / 1000, 'sec');
|
||||||
|
|
||||||
|
let txDecoded = bitcoin.Transaction.fromHex(tx);
|
||||||
|
txid = txDecoded.getId();
|
||||||
|
console.log('txid', txid);
|
||||||
|
console.log('txhex', tx);
|
||||||
|
|
||||||
|
let feeSatoshi = new BigNumber(fee).multipliedBy(100000000);
|
||||||
|
actualSatoshiPerByte = feeSatoshi.dividedBy(Math.round(tx.length / 2));
|
||||||
|
actualSatoshiPerByte = actualSatoshiPerByte.toNumber();
|
||||||
|
console.log({ satoshiPerByte: actualSatoshiPerByte });
|
||||||
|
|
||||||
|
if (Math.round(actualSatoshiPerByte) !== requestedSatPerByte * 1 || Math.floor(actualSatoshiPerByte) < 1) {
|
||||||
|
console.log('fee is not correct, retrying');
|
||||||
|
fee = feeSatoshi
|
||||||
|
.multipliedBy(requestedSatPerByte / actualSatoshiPerByte)
|
||||||
|
.plus(10)
|
||||||
|
.dividedBy(100000000)
|
||||||
|
.toNumber();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (tries++ < 5);
|
||||||
|
|
||||||
|
BlueApp.tx_metadata = BlueApp.tx_metadata || {};
|
||||||
|
BlueApp.tx_metadata[txid] = {
|
||||||
|
txhex: tx,
|
||||||
|
memo: this.state.memo,
|
||||||
|
};
|
||||||
|
await BlueApp.saveToDisk();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
alert(err);
|
||||||
|
this.setState({ isLoading: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.address) {
|
this.setState({ isLoading: false }, () =>
|
||||||
this.setState({
|
|
||||||
errorMessage: loc.send.details.address_fiels_is_not_valid,
|
|
||||||
});
|
|
||||||
console.log('validation error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.recalculateAvailableBalance(this.state.fromWallet.getBalance(), this.state.amount, this.state.fee) < 0) {
|
|
||||||
this.setState({
|
|
||||||
errorMessage: loc.send.details.amount_fiels_is_not_valid,
|
|
||||||
});
|
|
||||||
console.log('validation error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
errorMessage: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.navigation.navigate('CreateTransaction', {
|
this.props.navigation.navigate('CreateTransaction', {
|
||||||
amount: this.state.amount,
|
amount: this.state.amount,
|
||||||
fee: this.state.fee,
|
fee: fee.toFixed(8),
|
||||||
address: this.state.address,
|
address: this.state.address,
|
||||||
memo: this.state.memo,
|
memo: this.state.memo,
|
||||||
fromAddress: this.state.fromAddress,
|
fromWallet: this.state.fromWallet,
|
||||||
fromSecret: this.state.fromSecret,
|
tx: tx,
|
||||||
|
satoshiPerByte: actualSatoshiPerByte.toFixed(2),
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderFeeSelectionModal = () => {
|
||||||
if (this.state.isLoading) {
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
<Modal
|
||||||
|
isVisible={this.state.isFeeSelectionModalVisible}
|
||||||
|
style={styles.bottomModal}
|
||||||
|
onBackdropPress={() => this.setState({ isFeeSelectionModalVisible: false })}
|
||||||
|
>
|
||||||
|
<KeyboardAvoidingView behavior="position">
|
||||||
|
<View style={styles.modalContent}>
|
||||||
|
<TouchableOpacity style={styles.satoshisTextInput} onPress={() => this.textInput.focus()}>
|
||||||
|
<TextInput
|
||||||
|
keyboardType="numeric"
|
||||||
|
ref={ref => {
|
||||||
|
this.textInput = ref;
|
||||||
|
}}
|
||||||
|
value={this.state.fee.toString()}
|
||||||
|
onChangeText={value => {
|
||||||
|
let newValue = value.replace(/\D/g, '');
|
||||||
|
if (newValue.length === 0) {
|
||||||
|
newValue = 1;
|
||||||
|
}
|
||||||
|
this.setState({ fee: newValue, feeSliderValue: newValue });
|
||||||
|
}}
|
||||||
|
maxLength={9}
|
||||||
|
editable={!this.state.isLoading}
|
||||||
|
placeholderTextColor="#37c0a1"
|
||||||
|
placeholder={this.state.networkTransactionFees.halfHourFee.toString()}
|
||||||
|
style={{ fontWeight: '600', color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right', fontSize: 36 }}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#37c0a1',
|
||||||
|
paddingRight: 4,
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: 16,
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
marginBottom: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
sat/b
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{this.state.networkTransactionFees.fastestFee > 1 && (
|
||||||
|
<View style={{ flex: 1, marginTop: 32, minWidth: 240, width: 240 }}>
|
||||||
|
<Slider
|
||||||
|
onValueChange={value => this.setState({ feeSliderValue: this.state.feeSliderValue, fee: value.toFixed(0) })}
|
||||||
|
minimumValue={1}
|
||||||
|
maximumValue={this.state.networkTransactionFees.fastestFee}
|
||||||
|
value={Number(this.state.feeSliderValue)}
|
||||||
|
maximumTrackTintColor="#d8d8d8"
|
||||||
|
minimumTrackTintColor="#37c0a1"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between', marginTop: 14 }}>
|
||||||
|
<Text style={{ fontWeight: '500', fontSize: 13, color: '#37c0a1' }}>slow</Text>
|
||||||
|
<Text style={{ fontWeight: '500', fontSize: 13, color: '#37c0a1' }}>fast</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderCreateButton = () => {
|
||||||
|
return (
|
||||||
|
<View style={{ paddingHorizontal: 56, paddingVertical: 16, alignContent: 'center', backgroundColor: '#FFFFFF' }}>
|
||||||
|
{this.state.isLoading ? (
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
|
) : (
|
||||||
|
<BlueButton onPress={() => this.createTransaction()} title={loc.send.details.create} />
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
if (!this.state.fromWallet.getAddress) {
|
if (!this.state.fromWallet.getAddress) {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
<View style={{ flex: 1, paddingTop: 20 }}>
|
||||||
|
@ -183,62 +345,190 @@ export default class SendDetails extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeBlueArea style={{ flex: 1 }}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||||
<View>
|
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
|
||||||
<BlueFormInputAddress
|
<KeyboardAvoidingView behavior="position">
|
||||||
onChangeText={text => this.setState({ address: text })}
|
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
|
||||||
placeholder={loc.send.details.receiver_placeholder}
|
<TextInput
|
||||||
value={this.state.address}
|
keyboardType="numeric"
|
||||||
/>
|
|
||||||
|
|
||||||
<BlueFormInput
|
|
||||||
onChangeText={text => this.setState({ amount: text.replace(',', '.') })}
|
onChangeText={text => this.setState({ amount: text.replace(',', '.') })}
|
||||||
keyboardType={'numeric'}
|
placeholder="0"
|
||||||
placeholder={loc.send.details.amount_placeholder}
|
maxLength={10}
|
||||||
|
editable={!this.state.isLoading}
|
||||||
value={this.state.amount + ''}
|
value={this.state.amount + ''}
|
||||||
/>
|
placeholderTextColor="#0f5cc0"
|
||||||
|
style={{
|
||||||
<BlueFormInput
|
color: '#0f5cc0',
|
||||||
onChangeText={text => this.setState({ fee: text.replace(',', '.') })}
|
fontSize: 36,
|
||||||
keyboardType={'numeric'}
|
fontWeight: '600',
|
||||||
placeholder={loc.send.details.fee_placeholder}
|
|
||||||
value={this.state.fee + ''}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BlueFormInput
|
|
||||||
onChangeText={text => this.setState({ memo: text })}
|
|
||||||
placeholder={loc.send.details.memo_placeholder}
|
|
||||||
value={this.state.memo}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BlueSpacing20 />
|
|
||||||
<BlueText style={{ paddingLeft: 20 }}>
|
|
||||||
{loc.send.details.remaining_balance}:{' '}
|
|
||||||
{this.recalculateAvailableBalance(this.state.fromWallet.getBalance(), this.state.amount, this.state.fee)} BTC
|
|
||||||
</BlueText>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
|
|
||||||
|
|
||||||
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
||||||
<BlueButton onPress={() => this.props.navigation.goBack()} title={loc.send.details.cancel} />
|
|
||||||
<BlueButton
|
|
||||||
icon={{
|
|
||||||
name: 'qrcode',
|
|
||||||
type: 'font-awesome',
|
|
||||||
color: BlueApp.settings.buttonTextColor,
|
|
||||||
}}
|
}}
|
||||||
style={{}}
|
|
||||||
title={loc.send.details.scan}
|
|
||||||
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
|
|
||||||
/>
|
/>
|
||||||
<BlueButton onPress={() => this.createTransaction()} title={loc.send.details.create} />
|
<Text
|
||||||
|
style={{
|
||||||
|
color: '#0f5cc0',
|
||||||
|
fontSize: 16,
|
||||||
|
marginHorizontal: 4,
|
||||||
|
paddingBottom: 6,
|
||||||
|
fontWeight: '600',
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{' ' + BitcoinUnit.BTC}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</SafeBlueArea>
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderColor: '#d2d2d2',
|
||||||
|
borderBottomColor: '#d2d2d2',
|
||||||
|
borderWidth: 1.0,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
minHeight: 44,
|
||||||
|
height: 44,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
onChangeText={text => {
|
||||||
|
if (BitcoinBIP70TransactionDecode.matchesPaymentURL(text)) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLoading: true,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
BitcoinBIP70TransactionDecode.decode(text).then(response => {
|
||||||
|
this.setState({
|
||||||
|
address: response.address,
|
||||||
|
amount: loc.formatBalanceWithoutSuffix(response.amount, BitcoinUnit.BTC),
|
||||||
|
memo: response.memo,
|
||||||
|
fee: response.fee,
|
||||||
|
bip70TransactionExpiration: response.expires,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={loc.send.details.address}
|
||||||
|
numberOfLines={1}
|
||||||
|
value={this.state.address}
|
||||||
|
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
|
||||||
|
editable={!this.state.isLoading}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
disabled={this.state.isLoading}
|
||||||
|
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
|
||||||
|
style={{
|
||||||
|
width: 75,
|
||||||
|
height: 36,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
backgroundColor: '#bebebe',
|
||||||
|
borderRadius: 4,
|
||||||
|
paddingVertical: 4,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
marginHorizontal: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
|
||||||
|
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View
|
||||||
|
hide={!this.state.showMemoRow}
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderColor: '#d2d2d2',
|
||||||
|
borderBottomColor: '#d2d2d2',
|
||||||
|
borderWidth: 1.0,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
minHeight: 44,
|
||||||
|
height: 44,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
onChangeText={text => this.setState({ memo: text })}
|
||||||
|
placeholder={loc.send.details.note_placeholder}
|
||||||
|
value={this.state.memo}
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
|
||||||
|
editable={!this.state.isLoading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => this.setState({ isFeeSelectionModalVisible: true })}
|
||||||
|
disabled={this.state.isLoading}
|
||||||
|
style={{ flexDirection: 'row', marginHorizontal: 20, justifyContent: 'space-between', alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Text style={{ color: '#81868e', fontSize: 14 }}>Fee</Text>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#d2f8d6',
|
||||||
|
minWidth: 40,
|
||||||
|
height: 25,
|
||||||
|
borderRadius: 4,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right' }}>{this.state.fee}</Text>
|
||||||
|
<Text style={{ color: '#37c0a1', paddingRight: 4, textAlign: 'left' }}>sat/b</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
{this.renderCreateButton()}
|
||||||
|
{this.renderFeeSelectionModal()}
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
modalContent: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
padding: 22,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
borderColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
minHeight: 200,
|
||||||
|
height: 200,
|
||||||
|
},
|
||||||
|
bottomModal: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
satoshisTextInput: {
|
||||||
|
backgroundColor: '#d2f8d6',
|
||||||
|
minWidth: 127,
|
||||||
|
height: 60,
|
||||||
|
borderRadius: 8,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
SendDetails.propTypes = {
|
SendDetails.propTypes = {
|
||||||
navigation: PropTypes.shape({
|
navigation: PropTypes.shape({
|
||||||
goBack: PropTypes.function,
|
goBack: PropTypes.function,
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default class CameraExample extends React.Component {
|
||||||
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, ret.data);
|
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, ret.data);
|
||||||
} // end
|
} // end
|
||||||
|
|
||||||
async componentWillMount() {
|
async componentDidMount() {
|
||||||
const { status } = await Permissions.askAsync(Permissions.CAMERA);
|
const { status } = await Permissions.askAsync(Permissions.CAMERA);
|
||||||
this.setState({ hasCameraPermission: status === 'granted' });
|
this.setState({ hasCameraPermission: status === 'granted' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,11 +78,11 @@ export default class SendCreate extends Component {
|
||||||
changeAddress = o.addresses[0];
|
changeAddress = o.addresses[0];
|
||||||
} else {
|
} else {
|
||||||
transferAmount = new BigNumber(o.value);
|
transferAmount = new BigNumber(o.value);
|
||||||
transferAmount = transferAmount.div(100000000).toString(10);
|
transferAmount = transferAmount.dividedBy(100000000).toString(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let oldFee = new BigNumber(totalInputAmountSatoshi - totalOutputAmountSatoshi);
|
let oldFee = new BigNumber(totalInputAmountSatoshi - totalOutputAmountSatoshi);
|
||||||
oldFee = parseFloat(oldFee.div(100000000).toString(10));
|
oldFee = parseFloat(oldFee.dividedBy(100000000).toString(10));
|
||||||
|
|
||||||
console.log('changeAddress = ', changeAddress);
|
console.log('changeAddress = ', changeAddress);
|
||||||
console.log('utxo', utxo);
|
console.log('utxo', utxo);
|
||||||
|
@ -93,7 +93,7 @@ export default class SendCreate extends Component {
|
||||||
console.log('oldFee', oldFee);
|
console.log('oldFee', oldFee);
|
||||||
|
|
||||||
let newFee = new BigNumber(oldFee);
|
let newFee = new BigNumber(oldFee);
|
||||||
newFee = newFee.add(this.state.feeDelta).toString(10);
|
newFee = newFee.plus(this.state.feeDelta).toString(10);
|
||||||
console.log('new Fee', newFee);
|
console.log('new Fee', newFee);
|
||||||
|
|
||||||
// creating TX
|
// creating TX
|
||||||
|
@ -124,7 +124,7 @@ export default class SendCreate extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let newFeeSatoshi = new BigNumber(newFee);
|
let newFeeSatoshi = new BigNumber(newFee);
|
||||||
newFeeSatoshi = parseInt(newFeeSatoshi.mul(100000000));
|
newFeeSatoshi = parseInt(newFeeSatoshi.multipliedBy(100000000));
|
||||||
let satoshiPerByte = Math.round(newFeeSatoshi / (tx.length / 2));
|
let satoshiPerByte = Math.round(newFeeSatoshi / (tx.length / 2));
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* global alert */
|
/* global alert */
|
||||||
import { SegwitP2SHWallet } from '../../class';
|
import { SegwitP2SHWallet } from '../../class';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { ActivityIndicator, Dimensions, View } from 'react-native';
|
import { ActivityIndicator, Dimensions, View, TextInput } from 'react-native';
|
||||||
import {
|
import {
|
||||||
BlueTextCentered,
|
BlueTextCentered,
|
||||||
BlueText,
|
BlueText,
|
||||||
|
@ -9,7 +9,6 @@ import {
|
||||||
BitcoinButton,
|
BitcoinButton,
|
||||||
BlueButtonLink,
|
BlueButtonLink,
|
||||||
BlueFormLabel,
|
BlueFormLabel,
|
||||||
BlueFormInput,
|
|
||||||
BlueButton,
|
BlueButton,
|
||||||
SafeBlueArea,
|
SafeBlueArea,
|
||||||
BlueCard,
|
BlueCard,
|
||||||
|
@ -75,14 +74,33 @@ export default class WalletsAdd extends Component {
|
||||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||||
<BlueCard>
|
<BlueCard>
|
||||||
<BlueFormLabel>{loc.wallets.add.wallet_name}</BlueFormLabel>
|
<BlueFormLabel>{loc.wallets.add.wallet_name}</BlueFormLabel>
|
||||||
<BlueFormInput
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderColor: '#d2d2d2',
|
||||||
|
borderBottomColor: '#d2d2d2',
|
||||||
|
borderWidth: 1.0,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
minHeight: 44,
|
||||||
|
height: 44,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: 16,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
value={this.state.label}
|
value={this.state.label}
|
||||||
|
placeholderTextColor="#81868e"
|
||||||
placeholder={loc.wallets.add.label_new_segwit}
|
placeholder={loc.wallets.add.label_new_segwit}
|
||||||
onChangeText={text => {
|
onChangeText={text => {
|
||||||
this.setLabel(text);
|
this.setLabel(text);
|
||||||
}}
|
}}
|
||||||
|
style={{ flex: 1, marginHorizontal: 8, color: '#81868e' }}
|
||||||
|
editable={!this.state.isLoading}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
<BlueFormLabel>{loc.wallets.add.wallet_type}</BlueFormLabel>
|
<BlueFormLabel>{loc.wallets.add.wallet_type}</BlueFormLabel>
|
||||||
|
|
||||||
<View style={{ flexDirection: 'row', paddingTop: 10, paddingLeft: 20, width: width - 80, borderColor: 'red', borderWidth: 0 }}>
|
<View style={{ flexDirection: 'row', paddingTop: 10, paddingLeft: 20, width: width - 80, borderColor: 'red', borderWidth: 0 }}>
|
||||||
|
|
|
@ -346,7 +346,7 @@ export default class WalletsList extends Component {
|
||||||
containerStyle: { marginTop: 0 },
|
containerStyle: { marginTop: 0 },
|
||||||
}}
|
}}
|
||||||
hideChevron
|
hideChevron
|
||||||
rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).div(100000000).toString()}
|
rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).dividedBy(100000000).toString()}
|
||||||
rightTitleStyle={{
|
rightTitleStyle={{
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
|
@ -24,7 +24,9 @@ import {
|
||||||
BlueListItem,
|
BlueListItem,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
import { Icon } from 'react-native-elements';
|
import { Icon } from 'react-native-elements';
|
||||||
|
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
|
|
||||||
let BlueApp = require('../../BlueApp');
|
let BlueApp = require('../../BlueApp');
|
||||||
let loc = require('../../loc');
|
let loc = require('../../loc');
|
||||||
const BigNumber = require('bignumber.js');
|
const BigNumber = require('bignumber.js');
|
||||||
|
@ -61,8 +63,8 @@ export default class WalletTransactions extends Component {
|
||||||
wallet: props.navigation.getParam('wallet'),
|
wallet: props.navigation.getParam('wallet'),
|
||||||
gradientColors: ['#FFFFFF', '#FFFFFF'],
|
gradientColors: ['#FFFFFF', '#FFFFFF'],
|
||||||
dataSource: props.navigation.getParam('wallet').getTransactions(),
|
dataSource: props.navigation.getParam('wallet').getTransactions(),
|
||||||
|
walletBalanceUnit: BitcoinUnit.MBTC,
|
||||||
};
|
};
|
||||||
|
|
||||||
// here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet
|
// here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet
|
||||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactionsFunction.bind(this));
|
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactionsFunction.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -197,6 +199,18 @@ export default class WalletTransactions extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeWalletBalanceUnit() {
|
||||||
|
if (this.state.walletBalanceUnit === undefined || this.state.walletBalanceUnit === BitcoinUnit.BTC) {
|
||||||
|
this.setState({ walletBalanceUnit: BitcoinUnit.MBTC });
|
||||||
|
} else if (this.state.walletBalanceUnit === BitcoinUnit.MBTC) {
|
||||||
|
this.setState({ walletBalanceUnit: BitcoinUnit.BITS });
|
||||||
|
} else if (this.state.walletBalanceUnit === BitcoinUnit.BITS) {
|
||||||
|
this.setState({ walletBalanceUnit: BitcoinUnit.SATOSHIS });
|
||||||
|
} else if (this.state.walletBalanceUnit === BitcoinUnit.SATOSHIS) {
|
||||||
|
this.setState({ walletBalanceUnit: BitcoinUnit.BTC });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderWalletHeader = () => {
|
renderWalletHeader = () => {
|
||||||
return (
|
return (
|
||||||
<LinearGradient colors={[this.state.gradientColors[0], this.state.gradientColors[1]]} style={{ padding: 15, height: 164 }}>
|
<LinearGradient colors={[this.state.gradientColors[0], this.state.gradientColors[1]]} style={{ padding: 15, height: 164 }}>
|
||||||
|
@ -225,6 +239,7 @@ export default class WalletTransactions extends Component {
|
||||||
>
|
>
|
||||||
{this.state.wallet.getLabel()}
|
{this.state.wallet.getLabel()}
|
||||||
</Text>
|
</Text>
|
||||||
|
<TouchableOpacity onPress={() => this.changeWalletBalanceUnit()}>
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
adjustsFontSizeToFit
|
adjustsFontSizeToFit
|
||||||
|
@ -235,8 +250,9 @@ export default class WalletTransactions extends Component {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loc.formatBalance(this.state.wallet.getBalance())}
|
{loc.formatBalance(this.state.wallet.getBalance(), this.state.walletBalanceUnit)}
|
||||||
</Text>
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
<Text style={{ backgroundColor: 'transparent' }} />
|
<Text style={{ backgroundColor: 'transparent' }} />
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
@ -420,7 +436,7 @@ export default class WalletTransactions extends Component {
|
||||||
containerStyle: { marginTop: 0 },
|
containerStyle: { marginTop: 0 },
|
||||||
}}
|
}}
|
||||||
hideChevron
|
hideChevron
|
||||||
rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).div(100000000).toString()}
|
rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).dividedBy(100000000).toString()}
|
||||||
rightTitleStyle={{
|
rightTitleStyle={{
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
Loading…
Add table
Reference in a new issue