mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-18 21:35:21 +01:00
Merge branch 'master' into settingsui
This commit is contained in:
commit
d4b27bb73c
@ -1,25 +1,23 @@
|
||||
# Javascript Node CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/node:8-stretch
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
# documented at https://circleci.com/docs/2.0/circleci-images/
|
||||
# - image: circleci/mongo:3.4.4
|
||||
- image: circleci/node:10.16.3
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
key: node_modules-{{ checksum "package-lock.json" }}
|
||||
|
||||
- run: npm i
|
||||
|
||||
- save_cache:
|
||||
key: node_modules-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
# run tests!
|
||||
- run: npm run test
|
||||
|
@ -2089,7 +2089,7 @@ export class BlueAddressInput extends Component {
|
||||
<TouchableOpacity
|
||||
disabled={this.props.isLoading}
|
||||
onPress={() => {
|
||||
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned, launchedBy: this.props.launchedBy });
|
||||
NavigationService.navigate('ScanQRCode', { onBarScanned: this.props.onBarScanned, launchedBy: this.props.launchedBy });
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
style={{
|
||||
|
@ -295,7 +295,7 @@ module.exports.multiGetHistoryByAddress = async function(addresses, batchsize) {
|
||||
};
|
||||
|
||||
module.exports.multiGetTransactionByTxid = async function(txids, batchsize, verbose) {
|
||||
batchsize = batchsize || 81;
|
||||
batchsize = batchsize || 61;
|
||||
// this value is fine-tuned so althrough wallets in test suite will occasionally
|
||||
// throw 'response too large (over 1,000,000 bytes', test suite will pass
|
||||
verbose = verbose !== false;
|
||||
@ -341,7 +341,7 @@ module.exports.waitTillConnected = async function() {
|
||||
clearInterval(waitTillConnectedInterval);
|
||||
reject(new Error('Waiting for Electrum connection timeout'));
|
||||
}
|
||||
}, 1000);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -28,8 +28,6 @@ import SelectWallet from './screen/wallets/selectWallet';
|
||||
|
||||
import details from './screen/transactions/details';
|
||||
import TransactionStatus from './screen/transactions/transactionStatus';
|
||||
import rbf from './screen/transactions/RBF';
|
||||
import createrbf from './screen/transactions/RBF-create';
|
||||
import cpfp from './screen/transactions/CPFP';
|
||||
import rbfBumpFee from './screen/transactions/RBFBumpFee';
|
||||
import rbfCancel from './screen/transactions/RBFCancel';
|
||||
@ -37,7 +35,7 @@ import rbfCancel from './screen/transactions/RBFCancel';
|
||||
import receiveDetails from './screen/receive/details';
|
||||
|
||||
import sendDetails from './screen/send/details';
|
||||
import ScanQRCode from './screen/send/scanQrAddress';
|
||||
import ScanQRCode from './screen/send/ScanQRCode';
|
||||
import sendCreate from './screen/send/create';
|
||||
import Confirm from './screen/send/confirm';
|
||||
import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet';
|
||||
@ -78,12 +76,6 @@ const WalletsStackNavigator = createStackNavigator(
|
||||
WalletDetails: {
|
||||
screen: WalletDetails,
|
||||
},
|
||||
RBF: {
|
||||
screen: rbf,
|
||||
},
|
||||
CreateRBF: {
|
||||
screen: createrbf,
|
||||
},
|
||||
CPFP: {
|
||||
screen: cpfp,
|
||||
},
|
||||
@ -259,7 +251,7 @@ const HandleOffchainAndOnChainStackNavigator = createStackNavigator(
|
||||
header: null,
|
||||
},
|
||||
},
|
||||
ScanQrAddress: {
|
||||
ScanQRCode: {
|
||||
screen: ScanQRCode,
|
||||
},
|
||||
SendDetails: {
|
||||
@ -330,7 +322,7 @@ const MainBottomTabs = createStackNavigator(
|
||||
header: null,
|
||||
},
|
||||
},
|
||||
ScanQrAddress: {
|
||||
ScanQRCode: {
|
||||
screen: ScanQRCode,
|
||||
},
|
||||
LappBrowser: {
|
||||
|
@ -119,7 +119,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "5.0.1"
|
||||
versionName "5.1.0"
|
||||
multiDexEnabled true
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
}
|
||||
|
@ -635,22 +635,39 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
}
|
||||
|
||||
async fetchUtxo() {
|
||||
// considering only confirmed balance
|
||||
// also, fetching utxo of addresses that only have some balance
|
||||
// fetching utxo of addresses that only have some balance
|
||||
let addressess = [];
|
||||
|
||||
// considering confirmed balance:
|
||||
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
if (this._balances_by_external_index[c] && this._balances_by_external_index[c].c && this._balances_by_external_index[c].c > 0) {
|
||||
addressess.push(this._getExternalAddressByIndex(c));
|
||||
}
|
||||
}
|
||||
|
||||
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
if (this._balances_by_internal_index[c] && this._balances_by_internal_index[c].c && this._balances_by_internal_index[c].c > 0) {
|
||||
addressess.push(this._getInternalAddressByIndex(c));
|
||||
}
|
||||
}
|
||||
|
||||
// considering UNconfirmed balance:
|
||||
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
if (this._balances_by_external_index[c] && this._balances_by_external_index[c].u && this._balances_by_external_index[c].u > 0) {
|
||||
addressess.push(this._getExternalAddressByIndex(c));
|
||||
}
|
||||
}
|
||||
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
if (this._balances_by_internal_index[c] && this._balances_by_internal_index[c].u && this._balances_by_internal_index[c].u > 0) {
|
||||
addressess.push(this._getInternalAddressByIndex(c));
|
||||
}
|
||||
}
|
||||
|
||||
// note: we could remove checks `.c` and `.u` to simplify code, but the resulting `addressess` array would be bigger, thus bigger batch
|
||||
// to fetch (or maybe even several fetches), which is not critical but undesirable.
|
||||
// anyway, result has `.confirmations` property for each utxo, so outside caller can easily filter out unconfirmed if he wants to
|
||||
|
||||
addressess = [...new Set(addressess)]; // deduplicate just for any case
|
||||
|
||||
this._utxo = [];
|
||||
for (let arr of Object.values(await BlueElectrum.multiGetUtxoByAddress(addressess))) {
|
||||
this._utxo = this._utxo.concat(arr);
|
||||
@ -665,8 +682,25 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
u.wif = this._getWifForAddress(u.address);
|
||||
u.confirmations = u.height ? 1 : 0;
|
||||
}
|
||||
|
||||
this.utxo = this.utxo.sort((a, b) => a.amount - b.amount);
|
||||
// more consistent, so txhex in unit tests wont change
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for previously fetched UTXO. For example:
|
||||
* [ { height: 0,
|
||||
* value: 666,
|
||||
* address: 'string',
|
||||
* txId: 'string',
|
||||
* vout: 1,
|
||||
* txid: 'string',
|
||||
* amount: 666,
|
||||
* wif: 'string',
|
||||
* confirmations: 0 } ]
|
||||
*
|
||||
* @returns {[]}
|
||||
*/
|
||||
getUtxo() {
|
||||
return this._utxo;
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ const bitcoin = require('bitcoinjs-lib');
|
||||
const bip39 = require('bip39');
|
||||
const BlueElectrum = require('../BlueElectrum');
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class AbstractHDWallet extends LegacyWallet {
|
||||
static type = 'abstract';
|
||||
static typeReadable = 'abstract';
|
||||
|
@ -105,7 +105,7 @@ export class AbstractWallet {
|
||||
}
|
||||
|
||||
weOwnAddress(address) {
|
||||
return this._address === address;
|
||||
throw Error('not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +128,12 @@ export class AbstractWallet {
|
||||
}
|
||||
|
||||
setSecret(newSecret) {
|
||||
this.secret = newSecret.trim();
|
||||
this.secret = newSecret
|
||||
.trim()
|
||||
.replace('bitcoin:', '')
|
||||
.replace('BITCOIN:', '');
|
||||
|
||||
if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase();
|
||||
|
||||
try {
|
||||
const parsedSecret = JSON.parse(this.secret);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { AbstractHDWallet } from './abstract-hd-wallet';
|
||||
import Frisbee from 'frisbee';
|
||||
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||
import bip39 from 'bip39';
|
||||
const bip32 = require('bip32');
|
||||
const bitcoinjs = require('bitcoinjs-lib');
|
||||
@ -8,7 +7,7 @@ const bitcoinjs = require('bitcoinjs-lib');
|
||||
* HD Wallet (BIP39).
|
||||
* In particular, Breadwallet-compatible (Legacy addresses)
|
||||
*/
|
||||
export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
|
||||
export class HDLegacyBreadwalletWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDLegacyBreadwallet';
|
||||
static typeReadable = 'HD Legacy Breadwallet (P2PKH)';
|
||||
|
||||
@ -35,15 +34,10 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
|
||||
_getExternalAddressByIndex(index) {
|
||||
index = index * 1; // cast to int
|
||||
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
||||
const mnemonic = this.secret;
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bip32.fromSeed(seed);
|
||||
|
||||
const path = "m/0'/0/" + index;
|
||||
const child = root.derivePath(path);
|
||||
|
||||
const node = bitcoinjs.bip32.fromBase58(this.getXpub());
|
||||
const address = bitcoinjs.payments.p2pkh({
|
||||
pubkey: child.publicKey,
|
||||
pubkey: node.derive(0).derive(index).publicKey,
|
||||
}).address;
|
||||
|
||||
return (this.external_addresses_cache[index] = address);
|
||||
@ -52,15 +46,10 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
|
||||
_getInternalAddressByIndex(index) {
|
||||
index = index * 1; // cast to int
|
||||
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
|
||||
const mnemonic = this.secret;
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bip32.fromSeed(seed);
|
||||
|
||||
const path = "m/0'/1/" + index;
|
||||
const child = root.derivePath(path);
|
||||
|
||||
const node = bitcoinjs.bip32.fromBase58(this.getXpub());
|
||||
const address = bitcoinjs.payments.p2pkh({
|
||||
pubkey: child.publicKey,
|
||||
pubkey: node.derive(1).derive(index).publicKey,
|
||||
}).address;
|
||||
|
||||
return (this.internal_addresses_cache[index] = address);
|
||||
@ -90,26 +79,4 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
|
||||
|
||||
return child.keyPair.toWIF();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
async fetchBalance() {
|
||||
try {
|
||||
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
|
||||
|
||||
let response = await api.get('/balance?active=' + this.getXpub());
|
||||
|
||||
if (response && response.body) {
|
||||
for (let xpub of Object.keys(response.body)) {
|
||||
this.balance = response.body[xpub].final_balance / 100000000;
|
||||
}
|
||||
this._lastBalanceFetch = +new Date();
|
||||
} else {
|
||||
throw new Error('Could not fetch balance from API: ' + response.err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AbstractHDWallet } from './abstract-hd-wallet';
|
||||
import bip39 from 'bip39';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import signer from '../models/signer';
|
||||
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const HDNode = require('bip32');
|
||||
|
||||
@ -10,7 +10,7 @@ const HDNode = require('bip32');
|
||||
* In particular, BIP44 (P2PKH legacy addressess)
|
||||
* @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
*/
|
||||
export class HDLegacyP2PKHWallet extends AbstractHDWallet {
|
||||
export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDlegacyP2PKH';
|
||||
static typeReadable = 'HD Legacy (BIP44 P2PKH)';
|
||||
|
||||
|
@ -20,4 +20,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowRBF() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { AbstractWallet } from './abstract-wallet';
|
||||
import { SegwitBech32Wallet } from './';
|
||||
import { useBlockcypherTokens } from './constants';
|
||||
import Frisbee from 'frisbee';
|
||||
import { HDSegwitBech32Wallet } from './';
|
||||
import { NativeModules } from 'react-native';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const { RNRandomBytes } = NativeModules;
|
||||
@ -37,7 +35,7 @@ export class LegacyWallet extends AbstractWallet {
|
||||
* @return {boolean}
|
||||
*/
|
||||
timeToRefreshTransaction() {
|
||||
for (let tx of this.transactions) {
|
||||
for (let tx of this.getTransactions()) {
|
||||
if (tx.confirmations < 7) {
|
||||
return true;
|
||||
}
|
||||
@ -104,24 +102,13 @@ export class LegacyWallet extends AbstractWallet {
|
||||
*/
|
||||
async fetchBalance() {
|
||||
try {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://api.blockcypher.com/v1/btc/main/addrs/',
|
||||
});
|
||||
|
||||
let response = await api.get(
|
||||
this.getAddress() + '/balance' + ((useBlockcypherTokens && '?token=' + this.getRandomBlockcypherToken()) || ''),
|
||||
);
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined' || typeof json.final_balance === 'undefined') {
|
||||
throw new Error('Could not fetch balance from API: ' + response.err + ' ' + JSON.stringify(response.body));
|
||||
}
|
||||
|
||||
this.balance = Number(json.final_balance);
|
||||
this.unconfirmed_balance = new BigNumber(json.unconfirmed_balance);
|
||||
this.unconfirmed_balance = this.unconfirmed_balance.dividedBy(100000000).toString() * 1;
|
||||
let balance = await BlueElectrum.getBalanceByAddress(this.getAddress());
|
||||
this.balance = Number(balance.confirmed);
|
||||
this.unconfirmed_balance = new BigNumber(balance.unconfirmed);
|
||||
this.unconfirmed_balance = this.unconfirmed_balance.dividedBy(100000000).toString() * 1; // wtf
|
||||
this._lastBalanceFetch = +new Date();
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
} catch (Error) {
|
||||
console.warn(Error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,230 +118,116 @@ export class LegacyWallet extends AbstractWallet {
|
||||
* @return {Promise.<void>}
|
||||
*/
|
||||
async fetchUtxo() {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://api.blockcypher.com/v1/btc/main/addrs/',
|
||||
});
|
||||
|
||||
let response;
|
||||
try {
|
||||
let maxHeight = 0;
|
||||
this.utxo = [];
|
||||
let json;
|
||||
let utxos = await BlueElectrum.multiGetUtxoByAddress([this.getAddress()]);
|
||||
for (let arr of Object.values(utxos)) {
|
||||
this.utxo = this.utxo.concat(arr);
|
||||
}
|
||||
} catch (Error) {
|
||||
console.warn(Error);
|
||||
}
|
||||
|
||||
do {
|
||||
response = await api.get(
|
||||
this.getAddress() +
|
||||
'?limit=2000&after=' +
|
||||
maxHeight +
|
||||
((useBlockcypherTokens && '&token=' + this.getRandomBlockcypherToken()) || ''),
|
||||
);
|
||||
json = response.body;
|
||||
if (typeof json === 'undefined' || typeof json.final_balance === 'undefined') {
|
||||
throw new Error('Could not fetch UTXO from API' + response.err);
|
||||
}
|
||||
json.txrefs = json.txrefs || []; // case when source address is empty (or maxheight too high, no txs)
|
||||
|
||||
for (let txref of json.txrefs) {
|
||||
maxHeight = Math.max(maxHeight, txref.block_height) + 1;
|
||||
if (typeof txref.spent !== 'undefined' && txref.spent === false) {
|
||||
this.utxo.push(txref);
|
||||
}
|
||||
}
|
||||
} while (json.txrefs.length);
|
||||
|
||||
json.unconfirmed_txrefs = json.unconfirmed_txrefs || [];
|
||||
this.utxo = this.utxo.concat(json.unconfirmed_txrefs);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
// backward compatibility
|
||||
for (let u of this.utxo) {
|
||||
u.tx_output_n = u.vout;
|
||||
u.tx_hash = u.txId;
|
||||
u.confirmations = u.height ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
getUtxo() {
|
||||
return this.utxo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches transactions via API. Returns VOID.
|
||||
* Use getter to get the actual list.
|
||||
* Fetches transactions via Electrum. Returns VOID.
|
||||
* Use getter to get the actual list. *
|
||||
* @see AbstractHDElectrumWallet.fetchTransactions()
|
||||
*
|
||||
* @return {Promise.<void>}
|
||||
*/
|
||||
async fetchTransactions() {
|
||||
try {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://api.blockcypher.com/',
|
||||
});
|
||||
// Below is a simplified copypaste from HD electrum wallet
|
||||
this._txs_by_external_index = [];
|
||||
let addresses2fetch = [this.getAddress()];
|
||||
|
||||
let after = 0;
|
||||
let before = 100500100;
|
||||
|
||||
for (let oldTx of this.getTransactions()) {
|
||||
if (oldTx.block_height && oldTx.confirmations < 7) {
|
||||
after = Math.max(after, oldTx.block_height);
|
||||
}
|
||||
// first: batch fetch for all addresses histories
|
||||
let histories = await BlueElectrum.multiGetHistoryByAddress(addresses2fetch);
|
||||
let txs = {};
|
||||
for (let history of Object.values(histories)) {
|
||||
for (let tx of history) {
|
||||
txs[tx.tx_hash] = tx;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
let response = await api.get(
|
||||
'v1/btc/main/addrs/' +
|
||||
this.getAddress() +
|
||||
'/full?after=' +
|
||||
after +
|
||||
'&before=' +
|
||||
before +
|
||||
'&limit=50' +
|
||||
((useBlockcypherTokens && '&token=' + this.getRandomBlockcypherToken()) || ''),
|
||||
);
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined' || !json.txs) {
|
||||
throw new Error('Could not fetch transactions from API:' + response.err);
|
||||
}
|
||||
|
||||
let alreadyFetchedTransactions = this.transactions;
|
||||
this.transactions = json.txs;
|
||||
this._lastTxFetch = +new Date();
|
||||
|
||||
// now, calculating value per each transaction...
|
||||
for (let tx of this.transactions) {
|
||||
if (tx.block_height) {
|
||||
before = Math.min(before, tx.block_height); // so next time we fetch older TXs
|
||||
}
|
||||
|
||||
// now, if we dont have enough outputs or inputs in response we should collect them from API:
|
||||
if (tx.next_outputs) {
|
||||
let newOutputs = await this._fetchAdditionalOutputs(tx.next_outputs);
|
||||
tx.outputs = tx.outputs.concat(newOutputs);
|
||||
}
|
||||
if (tx.next_inputs) {
|
||||
let newInputs = await this._fetchAdditionalInputs(tx.next_inputs);
|
||||
tx.inputs = tx.inputs.concat(newInputs);
|
||||
}
|
||||
|
||||
// how much came in...
|
||||
let value = 0;
|
||||
for (let out of tx.outputs) {
|
||||
if (out && out.addresses && out.addresses.indexOf(this.getAddress()) !== -1) {
|
||||
// found our address in outs of this TX
|
||||
value += out.value;
|
||||
}
|
||||
}
|
||||
tx.value = value;
|
||||
// end
|
||||
|
||||
// how much came out
|
||||
value = 0;
|
||||
for (let inp of tx.inputs) {
|
||||
if (!inp.addresses) {
|
||||
// console.log('inp.addresses empty');
|
||||
// console.log('got witness', inp.witness); // TODO
|
||||
|
||||
inp.addresses = [];
|
||||
if (inp.witness && inp.witness[1]) {
|
||||
let address = SegwitBech32Wallet.witnessToAddress(inp.witness[1]);
|
||||
inp.addresses.push(address);
|
||||
} else {
|
||||
inp.addresses.push('???');
|
||||
}
|
||||
}
|
||||
if (inp && inp.addresses && inp.addresses.indexOf(this.getAddress()) !== -1) {
|
||||
// found our address in outs of this TX
|
||||
value -= inp.output_value;
|
||||
}
|
||||
}
|
||||
tx.value += value;
|
||||
// end
|
||||
}
|
||||
|
||||
this.transactions = alreadyFetchedTransactions.concat(this.transactions);
|
||||
|
||||
let txsUnconf = [];
|
||||
let txs = [];
|
||||
let hashPresent = {};
|
||||
// now, rearranging TXs. unconfirmed go first:
|
||||
for (let tx of this.transactions.reverse()) {
|
||||
if (hashPresent[tx.hash]) continue;
|
||||
hashPresent[tx.hash] = 1;
|
||||
if (tx.block_height && tx.block_height === -1) {
|
||||
// unconfirmed
|
||||
console.log(tx);
|
||||
if (+new Date(tx.received) < +new Date() - 3600 * 24 * 1000) {
|
||||
// nop, too old unconfirmed tx - skipping it
|
||||
} else {
|
||||
txsUnconf.push(tx);
|
||||
}
|
||||
} else {
|
||||
txs.push(tx);
|
||||
}
|
||||
}
|
||||
this.transactions = txsUnconf.reverse().concat(txs.reverse());
|
||||
// all reverses needed so freshly fetched TXs replace same old TXs
|
||||
|
||||
this.transactions = this.transactions.sort((a, b) => {
|
||||
return a.received < b.received;
|
||||
});
|
||||
|
||||
if (json.txs.length < 50) {
|
||||
// final batch, so it has les than max txs
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
// next, batch fetching each txid we got
|
||||
let txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs));
|
||||
|
||||
// now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too.
|
||||
// then we combine all this data (we need inputs to see source addresses and amounts)
|
||||
let vinTxids = [];
|
||||
for (let txdata of Object.values(txdatas)) {
|
||||
for (let vin of txdata.vin) {
|
||||
vinTxids.push(vin.txid);
|
||||
}
|
||||
}
|
||||
let vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids);
|
||||
|
||||
// fetched all transactions from our inputs. now we need to combine it.
|
||||
// iterating all _our_ transactions:
|
||||
for (let txid of Object.keys(txdatas)) {
|
||||
// iterating all inputs our our single transaction:
|
||||
for (let inpNum = 0; inpNum < txdatas[txid].vin.length; inpNum++) {
|
||||
let inpTxid = txdatas[txid].vin[inpNum].txid;
|
||||
let inpVout = txdatas[txid].vin[inpNum].vout;
|
||||
// got txid and output number of _previous_ transaction we shoud look into
|
||||
if (vintxdatas[inpTxid] && vintxdatas[inpTxid].vout[inpVout]) {
|
||||
// extracting amount & addresses from previous output and adding it to _our_ input:
|
||||
txdatas[txid].vin[inpNum].addresses = vintxdatas[inpTxid].vout[inpVout].scriptPubKey.addresses;
|
||||
txdatas[txid].vin[inpNum].value = vintxdatas[inpTxid].vout[inpVout].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now, we need to put transactions in all relevant `cells` of internal hashmaps: this.transactions_by_internal_index && this.transactions_by_external_index
|
||||
|
||||
for (let tx of Object.values(txdatas)) {
|
||||
for (let vin of tx.vin) {
|
||||
if (vin.addresses && vin.addresses.indexOf(this.getAddress()) !== -1) {
|
||||
// this TX is related to our address
|
||||
let clonedTx = Object.assign({}, tx);
|
||||
clonedTx.inputs = tx.vin.slice(0);
|
||||
clonedTx.outputs = tx.vout.slice(0);
|
||||
delete clonedTx.vin;
|
||||
delete clonedTx.vout;
|
||||
|
||||
this._txs_by_external_index.push(clonedTx);
|
||||
}
|
||||
}
|
||||
for (let vout of tx.vout) {
|
||||
if (vout.scriptPubKey.addresses.indexOf(this.getAddress()) !== -1) {
|
||||
// this TX is related to our address
|
||||
let clonedTx = Object.assign({}, tx);
|
||||
clonedTx.inputs = tx.vin.slice(0);
|
||||
clonedTx.outputs = tx.vout.slice(0);
|
||||
delete clonedTx.vin;
|
||||
delete clonedTx.vout;
|
||||
|
||||
this._txs_by_external_index.push(clonedTx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._lastTxFetch = +new Date();
|
||||
}
|
||||
|
||||
async _fetchAdditionalOutputs(nextOutputs) {
|
||||
let outputs = [];
|
||||
let baseURI = nextOutputs.split('/');
|
||||
baseURI = baseURI[0] + '/' + baseURI[1] + '/' + baseURI[2] + '/';
|
||||
const api = new Frisbee({
|
||||
baseURI: baseURI,
|
||||
});
|
||||
getTransactions() {
|
||||
// a hacky code reuse from electrum HD wallet:
|
||||
this._txs_by_external_index = this._txs_by_external_index || [];
|
||||
this._txs_by_internal_index = [];
|
||||
|
||||
do {
|
||||
await (() => new Promise(resolve => setTimeout(resolve, 1000)))();
|
||||
nextOutputs = nextOutputs.replace(baseURI, '');
|
||||
|
||||
let response = await api.get(nextOutputs + ((useBlockcypherTokens && '&token=' + this.getRandomBlockcypherToken()) || ''));
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined') {
|
||||
throw new Error('Could not fetch transactions from API:' + response.err);
|
||||
}
|
||||
|
||||
if (json.outputs && json.outputs.length) {
|
||||
outputs = outputs.concat(json.outputs);
|
||||
nextOutputs = json.next_outputs;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
async _fetchAdditionalInputs(nextInputs) {
|
||||
let inputs = [];
|
||||
let baseURI = nextInputs.split('/');
|
||||
baseURI = baseURI[0] + '/' + baseURI[1] + '/' + baseURI[2] + '/';
|
||||
const api = new Frisbee({
|
||||
baseURI: baseURI,
|
||||
});
|
||||
|
||||
do {
|
||||
await (() => new Promise(resolve => setTimeout(resolve, 1000)))();
|
||||
nextInputs = nextInputs.replace(baseURI, '');
|
||||
|
||||
let response = await api.get(nextInputs + ((useBlockcypherTokens && '&token=' + this.getRandomBlockcypherToken()) || ''));
|
||||
let json = response.body;
|
||||
if (typeof json === 'undefined') {
|
||||
throw new Error('Could not fetch transactions from API:' + response.err);
|
||||
}
|
||||
|
||||
if (json.inputs && json.inputs.length) {
|
||||
inputs = inputs.concat(json.inputs);
|
||||
nextInputs = json.next_inputs;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
return inputs;
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
return hd.getTransactions.apply(this);
|
||||
}
|
||||
|
||||
async broadcastTx(txhex) {
|
||||
@ -366,66 +239,8 @@ export class LegacyWallet extends AbstractWallet {
|
||||
}
|
||||
}
|
||||
|
||||
async _broadcastTxBtczen(txhex) {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://btczen.com',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
let res = await api.get('/broadcast/' + txhex);
|
||||
console.log('response btczen', res.body);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async _broadcastTxChainso(txhex) {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://chain.so',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
let res = await api.post('/api/v2/send_tx/BTC', {
|
||||
body: { tx_hex: txhex },
|
||||
});
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async _broadcastTxSmartbit(txhex) {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://api.smartbit.com.au',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
let res = await api.post('/v1/blockchain/pushtx', {
|
||||
body: { hex: txhex },
|
||||
});
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async _broadcastTxBlockcypher(txhex) {
|
||||
const api = new Frisbee({
|
||||
baseURI: 'https://api.blockcypher.com',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
let res = await api.post('/v1/btc/main/txs/push', { body: { tx: txhex } });
|
||||
// console.log('blockcypher response', res);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes UTXOs (as presented by blockcypher api), transforms them into
|
||||
* Takes UTXOs, transforms them into
|
||||
* format expected by signer module, creates tx and returns signed string txhex.
|
||||
*
|
||||
* @param utxos Unspent outputs, expects blockcypher format
|
||||
@ -462,22 +277,12 @@ export class LegacyWallet extends AbstractWallet {
|
||||
return new Date(max).toString();
|
||||
}
|
||||
|
||||
getRandomBlockcypherToken() {
|
||||
return (array => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
let j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array[0];
|
||||
})([
|
||||
'0326b7107b4149559d18ce80612ef812',
|
||||
'a133eb7ccacd4accb80cb1225de4b155',
|
||||
'7c2b1628d27b4bd3bf8eaee7149c577f',
|
||||
'f1e5a02b9ec84ec4bc8db2349022e5f5',
|
||||
'e5926dbeb57145979153adc41305b183',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates any address, including legacy, p2sh and bech32
|
||||
*
|
||||
* @param address
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAddressValid(address) {
|
||||
try {
|
||||
bitcoin.address.toOutputScript(address);
|
||||
@ -486,4 +291,8 @@ export class LegacyWallet extends AbstractWallet {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
weOwnAddress(address) {
|
||||
return this.getAddress() === address || this._address === address;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ export class SegwitBech32Wallet extends LegacyWallet {
|
||||
let address;
|
||||
try {
|
||||
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
|
||||
if (!keyPair.compressed) {
|
||||
console.warn('only compressed public keys are good for segwit');
|
||||
return false;
|
||||
}
|
||||
address = bitcoin.payments.p2wpkh({
|
||||
pubkey: keyPair.publicKey,
|
||||
}).address;
|
||||
|
@ -22,10 +22,6 @@ export class SegwitP2SHWallet extends LegacyWallet {
|
||||
static type = 'segwitP2SH';
|
||||
static typeReadable = 'SegWit (P2SH)';
|
||||
|
||||
allowRBF() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static witnessToAddress(witness) {
|
||||
const pubKey = Buffer.from(witness, 'hex');
|
||||
return pubkeyToP2shSegwitAddress(pubKey);
|
||||
|
@ -125,7 +125,7 @@ export default class WalletImport {
|
||||
if (hd4.validateMnemonic()) {
|
||||
await hd4.fetchBalance();
|
||||
if (hd4.getBalance() > 0) {
|
||||
await hd4.fetchTransactions();
|
||||
// await hd4.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
|
||||
return WalletImport._saveWallet(hd4);
|
||||
}
|
||||
}
|
||||
@ -168,7 +168,7 @@ export default class WalletImport {
|
||||
if (hd1.validateMnemonic()) {
|
||||
await hd1.fetchBalance();
|
||||
if (hd1.getBalance() > 0) {
|
||||
await hd1.fetchTransactions();
|
||||
// await hd1.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
|
||||
return WalletImport._saveWallet(hd1);
|
||||
}
|
||||
}
|
||||
@ -178,7 +178,7 @@ export default class WalletImport {
|
||||
if (hd2.validateMnemonic()) {
|
||||
await hd2.fetchBalance();
|
||||
if (hd2.getBalance() > 0) {
|
||||
await hd2.fetchTransactions();
|
||||
// await hd2.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
|
||||
return WalletImport._saveWallet(hd2);
|
||||
}
|
||||
}
|
||||
@ -188,7 +188,7 @@ export default class WalletImport {
|
||||
if (hd3.validateMnemonic()) {
|
||||
await hd3.fetchBalance();
|
||||
if (hd3.getBalance() > 0) {
|
||||
await hd3.fetchTransactions();
|
||||
// await hd3.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
|
||||
return WalletImport._saveWallet(hd3);
|
||||
}
|
||||
}
|
||||
@ -230,7 +230,7 @@ export default class WalletImport {
|
||||
let watchOnly = new WatchOnlyWallet();
|
||||
watchOnly.setSecret(importText);
|
||||
if (watchOnly.valid()) {
|
||||
await watchOnly.fetchTransactions();
|
||||
// await watchOnly.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
|
||||
await watchOnly.fetchBalance();
|
||||
return WalletImport._saveWallet(watchOnly, additionalProperties);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.1</string>
|
||||
<string>5.1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.1</string>
|
||||
<string>5.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.1</string>
|
||||
<string>5.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
@ -68,25 +68,23 @@ PODS:
|
||||
- React
|
||||
- react-native-blur (0.8.0):
|
||||
- React
|
||||
- react-native-camera (3.4.0):
|
||||
- react-native-camera (3.17.0):
|
||||
- React
|
||||
- react-native-camera/RCT (= 3.4.0)
|
||||
- react-native-camera/RN (= 3.4.0)
|
||||
- react-native-camera/RCT (3.4.0):
|
||||
- react-native-camera/RCT (= 3.17.0)
|
||||
- react-native-camera/RN (= 3.17.0)
|
||||
- react-native-camera/RCT (3.17.0):
|
||||
- React
|
||||
- react-native-camera/RN (3.4.0):
|
||||
- react-native-camera/RN (3.17.0):
|
||||
- React
|
||||
- react-native-document-picker (3.2.0):
|
||||
- React
|
||||
- react-native-haptic-feedback (1.7.1):
|
||||
- React
|
||||
- react-native-image-picker (1.1.0):
|
||||
- React
|
||||
- react-native-randombytes (3.5.3):
|
||||
- React
|
||||
- react-native-slider (2.0.8):
|
||||
- React
|
||||
- react-native-webview (6.9.0):
|
||||
- react-native-webview (6.11.1):
|
||||
- React
|
||||
- React-RCTActionSheet (0.60.5):
|
||||
- React-Core (= 0.60.5)
|
||||
@ -115,30 +113,32 @@ PODS:
|
||||
- React
|
||||
- RemobileReactNativeQrcodeLocalImage (1.0.4):
|
||||
- React
|
||||
- RNCAsyncStorage (1.6.2):
|
||||
- RNCAsyncStorage (1.7.1):
|
||||
- React
|
||||
- RNDefaultPreference (1.4.1):
|
||||
- React
|
||||
- RNDeviceInfo (4.0.1):
|
||||
- React
|
||||
- RNFS (2.13.3):
|
||||
- RNFS (2.16.4):
|
||||
- React
|
||||
- RNGestureHandler (1.3.0):
|
||||
- RNGestureHandler (1.5.6):
|
||||
- React
|
||||
- RNHandoff (0.0.3):
|
||||
- React
|
||||
- RNQuickAction (0.3.13):
|
||||
- React
|
||||
- RNRate (1.0.1):
|
||||
- RNRate (1.1.10):
|
||||
- React
|
||||
- RNReactNativeHapticFeedback (1.9.0):
|
||||
- React
|
||||
- RNSecureKeyStore (1.0.0):
|
||||
- React
|
||||
- RNSentry (1.2.1):
|
||||
- RNSentry (1.3.1):
|
||||
- React
|
||||
- Sentry (~> 4.4.0)
|
||||
- RNShare (2.0.0):
|
||||
- React
|
||||
- RNSVG (9.5.1):
|
||||
- RNSVG (9.13.6):
|
||||
- React
|
||||
- RNVectorIcons (6.6.0):
|
||||
- React
|
||||
@ -172,7 +172,6 @@ DEPENDENCIES:
|
||||
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
|
||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-haptic-feedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
|
||||
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
|
||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||
@ -196,7 +195,8 @@ DEPENDENCIES:
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNHandoff (from `../node_modules/react-native-handoff`)
|
||||
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
|
||||
- RNRate (from `../node_modules/react-native-rate/ios`)
|
||||
- RNRate (from `../node_modules/react-native-rate`)
|
||||
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
- RNSecureKeyStore (from `../node_modules/react-native-secure-key-store/ios`)
|
||||
- "RNSentry (from `../node_modules/@sentry/react-native`)"
|
||||
- RNShare (from `../node_modules/react-native-share`)
|
||||
@ -208,7 +208,7 @@ DEPENDENCIES:
|
||||
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
trunk:
|
||||
- boost-for-react-native
|
||||
- EFQRCode
|
||||
- lottie-ios
|
||||
@ -248,8 +248,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-camera"
|
||||
react-native-document-picker:
|
||||
:path: "../node_modules/react-native-document-picker"
|
||||
react-native-haptic-feedback:
|
||||
:path: "../node_modules/react-native-haptic-feedback"
|
||||
react-native-image-picker:
|
||||
:path: "../node_modules/react-native-image-picker"
|
||||
react-native-randombytes:
|
||||
@ -297,7 +295,9 @@ EXTERNAL SOURCES:
|
||||
RNQuickAction:
|
||||
:path: "../node_modules/react-native-quick-actions"
|
||||
RNRate:
|
||||
:path: "../node_modules/react-native-rate/ios"
|
||||
:path: "../node_modules/react-native-rate"
|
||||
RNReactNativeHapticFeedback:
|
||||
:path: "../node_modules/react-native-haptic-feedback"
|
||||
RNSecureKeyStore:
|
||||
:path: "../node_modules/react-native-secure-key-store/ios"
|
||||
RNSentry:
|
||||
@ -335,13 +335,12 @@ SPEC CHECKSUMS:
|
||||
React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4
|
||||
react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5
|
||||
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
|
||||
react-native-camera: 203091b4bf99d48b788a0682ad573e8718724893
|
||||
react-native-camera: 4ead7a30a89f275f531d80aa720cc69363c38135
|
||||
react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154
|
||||
react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d
|
||||
react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f
|
||||
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
|
||||
react-native-slider: b2f361499888302147205f17f6fffa921a7bda70
|
||||
react-native-webview: f72ac4078e115dfa741cc588acb1cca25566457d
|
||||
react-native-webview: f11ac6c8bcaba5b71ddda1c12a10c8ea059b080f
|
||||
React-RCTActionSheet: b0f1ea83f4bf75fb966eae9bfc47b78c8d3efd90
|
||||
React-RCTAnimation: 359ba1b5690b1e87cc173558a78e82d35919333e
|
||||
React-RCTBlob: 5e2b55f76e9a1c7ae52b826923502ddc3238df24
|
||||
@ -354,18 +353,19 @@ SPEC CHECKSUMS:
|
||||
React-RCTWebSocket: cd932a16b7214898b6b7f788c8bddb3637246ac4
|
||||
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
|
||||
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
|
||||
RNCAsyncStorage: 5ae4d57458804e99f73d427214442a6b10a53856
|
||||
RNCAsyncStorage: 8539fc80a0075fcc9c8e2dff84cd22dc5bf1dacf
|
||||
RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a
|
||||
RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d
|
||||
RNFS: c9bbde46b0d59619f8e7b735991c60e0f73d22c1
|
||||
RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0
|
||||
RNFS: 90d1a32d3bc8f75cc7fc3dd2f67506049664346b
|
||||
RNGestureHandler: 911d3b110a7a233a34c4f800e7188a84b75319c6
|
||||
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNRate: 29be49c24b314c4e8ec09d848c3965f61cb0be47
|
||||
RNRate: d44a8bca6ee08f5d890ecccddaec2810955ffbb3
|
||||
RNReactNativeHapticFeedback: 2566b468cc8d0e7bb2f84b23adc0f4614594d071
|
||||
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
|
||||
RNSentry: 9b1d983b2d5d1c215ba6490348fd2a4cc23a8a9d
|
||||
RNSentry: 6458ba85aa3f8ae291abed4f72abbd7080839c71
|
||||
RNShare: 8b171d4b43c1d886917fdd303bf7a4b87167b05c
|
||||
RNSVG: 0eb087cfb5d7937be93c45b163b26352a647e681
|
||||
RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f
|
||||
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
||||
RNWatch: a36ea17fac675b98b1d8cd41604af68cf1fa9a03
|
||||
Sentry: 14bdd673870e8cf64932b149fad5bbbf39a9b390
|
||||
@ -376,4 +376,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: f93db402b02d7f01a8bc51d9b2cc10a39391b081
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
COCOAPODS: 1.8.4
|
||||
|
@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.1</string>
|
||||
<string>5.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
@ -1,3 +1,18 @@
|
||||
v5.1.0
|
||||
======
|
||||
|
||||
* FIX: weird import screen scan qr code behaviour
|
||||
* FIX: allow using unconfirmed utxo when creating transaction
|
||||
* REF: removed obsolete single address RBF;
|
||||
* REF: refactored breadwallet format a bit
|
||||
* FIX: Wallet name input character entry bug
|
||||
* REF: experimental - dont fetch transactions when importing wallet, only balance. should be faster. txs can be fetched later manually
|
||||
* FIX: Import ColdCard wallet using JSON's label.
|
||||
* REF: German translations
|
||||
* REF: now BIP44 works through electrum
|
||||
* REF: single-address wallets now work through electrum
|
||||
* REF: wallet export screen improvements
|
||||
|
||||
v5.0.0
|
||||
======
|
||||
|
||||
@ -40,19 +55,4 @@ v4.9.2
|
||||
* FIX: Don't show wallet export warning if wallet was imported
|
||||
* REF: Reworked Import wallet flow
|
||||
* REF: BIP49 to use electrum
|
||||
* REF: Custom receive
|
||||
|
||||
|
||||
v4.9.0
|
||||
======
|
||||
|
||||
* ADD: Native segwit (BIP84) are now default wallets
|
||||
* ADD: Toggle to turn off RBF when creating a transaction
|
||||
* ADD: Scroll to end of wallets list when adding a wallet
|
||||
* FIX: Default LN invoice expiry is now 24h instead of 1h
|
||||
* FIX: Speeded up lighnint wallets
|
||||
* FIX: Force Light theme mode even if system is in dark mode
|
||||
* FIX: Hide Manage Funds button if wallet doesn't allow onchain refill.
|
||||
* FIX: LN Scan to receive is more visible
|
||||
* FIX: Quick actions not appearing on non-3d touch devices.
|
||||
* FIX: Dont show clipboard modal when biometrics is dismissed
|
||||
* REF: Custom receive
|
42
loc/de_DE.js
42
loc/de_DE.js
@ -25,8 +25,8 @@ module.exports = {
|
||||
empty_txs1: 'Deine Transaktionen erscheinen hier',
|
||||
empty_txs2: 'Noch keine Transaktionen',
|
||||
empty_txs1_lightning:
|
||||
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
|
||||
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
|
||||
'Verwende das Lightning Wallet für Deine täglichen Bezahlungen. Lightning Transaktionen sind konkurrenzlos günstig und verblüffend schnell.',
|
||||
empty_txs2_lightning: '\nDrücke zum Starten «Beträge verwalten», um das Wallet zu laden.',
|
||||
tap_here_to_buy: 'Klicke hier, um Bitcoin zu kaufen',
|
||||
},
|
||||
reorder: {
|
||||
@ -52,7 +52,7 @@ module.exports = {
|
||||
details: {
|
||||
title: 'Wallet',
|
||||
address: 'Adresse',
|
||||
master_fingerprint: 'Master fingerprint',
|
||||
master_fingerprint: 'Fingerabdruckerkennung',
|
||||
type: 'Typ',
|
||||
label: 'Bezeichnung',
|
||||
destination: 'Zieladresse',
|
||||
@ -105,7 +105,7 @@ module.exports = {
|
||||
tabBarLabel: 'Transaktionen',
|
||||
title: 'Transaktionen',
|
||||
description: 'Eine Liste eingehender oder ausgehender Transaktionen deiner Wallets',
|
||||
conf: 'conf',
|
||||
conf: 'Konf',
|
||||
},
|
||||
details: {
|
||||
title: 'Transaktionen',
|
||||
@ -130,7 +130,7 @@ module.exports = {
|
||||
fee_placeholder: 'plus Gebühr (in BTC)',
|
||||
note_placeholder: 'Notiz',
|
||||
cancel: 'Abbrechen',
|
||||
scan: 'Scan',
|
||||
scan: 'Scannen',
|
||||
send: 'Senden',
|
||||
create: 'Erstellen',
|
||||
remaining_balance: 'Verfügbarer Betrag',
|
||||
@ -165,10 +165,10 @@ module.exports = {
|
||||
share: 'Teilen',
|
||||
copiedToClipboard: 'In die Zwischenablage kopiert.',
|
||||
label: 'Beschreibung',
|
||||
create: 'Create',
|
||||
create: 'Erstelle',
|
||||
setAmount: 'Zu erhaltender Betrag',
|
||||
},
|
||||
scan_lnurl: 'Scan to receive',
|
||||
scan_lnurl: 'Scannen, zum Erhalten',
|
||||
},
|
||||
buyBitcoin: {
|
||||
header: 'Kaufe Bitcoin',
|
||||
@ -190,14 +190,14 @@ module.exports = {
|
||||
'Bitte installier Lndhub, um mit deiner eigenen LND Node zu verbinden' +
|
||||
' und setz seine URL hier in den Einstellungen. Lass das Feld leer, um Standard- ' +
|
||||
'LndHub\n (lndhub.io) zu verwenden',
|
||||
electrum_settings: 'Electrum Settings',
|
||||
electrum_settings_explain: 'Set to blank to use default',
|
||||
electrum_settings: 'Electrum Einstellungen',
|
||||
electrum_settings_explain: 'Leer lassen, um den Standard zu verwenden.',
|
||||
save: 'Speichern',
|
||||
about: 'Über',
|
||||
language: 'Sprache',
|
||||
currency: 'Währung',
|
||||
advanced_options: 'Advanced Options',
|
||||
enable_advanced_mode: 'Enable advanced mode',
|
||||
advanced_options: 'Erweiterte Optionen',
|
||||
enable_advanced_mode: 'Erweiterter Modus verwenden',
|
||||
},
|
||||
plausibledeniability: {
|
||||
title: 'Glaubhafte Täuschung',
|
||||
@ -226,24 +226,24 @@ module.exports = {
|
||||
refill_lnd_balance: 'Lade deine Lightning Wallet auf',
|
||||
refill: 'Aufladen',
|
||||
withdraw: 'Abheben',
|
||||
expired: 'Expired',
|
||||
placeholder: 'Invoice',
|
||||
expired: 'Abgelaufen',
|
||||
placeholder: 'Rechnung',
|
||||
sameWalletAsInvoiceError:
|
||||
'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.',
|
||||
},
|
||||
pleasebackup: {
|
||||
title: 'Your wallet is created...',
|
||||
title: 'Ihr Wallet wird erstellt...',
|
||||
text:
|
||||
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
|
||||
ok: 'OK, I wrote this down!',
|
||||
'Nimm Dir Zeit die mnemonischen Wörter zur späteren Wiederherstellung des Wallets aufzuschreiben. Die Wörter sind dien einziges Backup im Fall eines Geräteverlustes.',
|
||||
ok: 'Ja, mein Geld ist sicher!',
|
||||
},
|
||||
lndViewInvoice: {
|
||||
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
|
||||
has_been_paid: 'This invoice has been paid for',
|
||||
please_pay: 'Please pay',
|
||||
wasnt_paid_and_expired: 'Diese Rechnung ist unbezahlt und abgelaufen.',
|
||||
has_been_paid: 'Diese Rechnung wurde bezahlt.',
|
||||
please_pay: 'Bitte zahle',
|
||||
sats: 'sats',
|
||||
for: 'For:',
|
||||
for: 'für:',
|
||||
additional_info: 'Additional Information',
|
||||
open_direct_channel: 'Open direct channel with this node:',
|
||||
open_direct_channel: 'Direkten Kanal zu diesem Knoten eröffnen:',
|
||||
},
|
||||
};
|
||||
|
2292
package-lock.json
generated
2292
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "BlueWallet",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/runtime": "^7.5.1",
|
||||
@ -52,31 +52,31 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "7.5.0",
|
||||
"@react-native-community/async-storage": "1.6.2",
|
||||
"@babel/preset-env": "7.8.4",
|
||||
"@react-native-community/async-storage": "1.7.1",
|
||||
"@react-native-community/blur": "3.4.1",
|
||||
"@react-native-community/slider": "2.0.8",
|
||||
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
|
||||
"@sentry/react-native": "1.2.1",
|
||||
"amplitude-js": "5.6.0",
|
||||
"@sentry/react-native": "1.3.1",
|
||||
"amplitude-js": "5.9.0",
|
||||
"bech32": "1.1.3",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bip21": "2.0.2",
|
||||
"bip32": "2.0.3",
|
||||
"bip32": "2.0.5",
|
||||
"bip39": "2.5.0",
|
||||
"bitcoinjs-lib": "5.1.6",
|
||||
"bolt11": "1.2.7",
|
||||
"buffer": "5.2.1",
|
||||
"buffer": "5.4.3",
|
||||
"buffer-reverse": "1.0.1",
|
||||
"coinselect": "3.1.11",
|
||||
"crypto-js": "3.1.9-1",
|
||||
"dayjs": "1.8.14",
|
||||
"dayjs": "1.8.20",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git",
|
||||
"eslint-config-prettier": "6.10.0",
|
||||
"eslint-config-standard": "12.0.0",
|
||||
"eslint-config-standard-react": "7.0.2",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
"eslint-plugin-prettier": "3.1.2",
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"events": "1.1.1",
|
||||
"frisbee": "2.0.9",
|
||||
@ -85,50 +85,50 @@
|
||||
"mocha": "5.2.0",
|
||||
"node-libs-react-native": "1.0.3",
|
||||
"path-browserify": "1.0.0",
|
||||
"prettier": "1.18.2",
|
||||
"prettier": "1.19.1",
|
||||
"process": "0.11.10",
|
||||
"prop-types": "15.7.2",
|
||||
"react": "16.8.6",
|
||||
"react-localization": "1.0.13",
|
||||
"react-localization": "1.0.15",
|
||||
"react-native": "0.60.5",
|
||||
"react-native-biometrics": "git+https://github.com/BlueWallet/react-native-biometrics.git#2.0.0",
|
||||
"react-native-camera": "3.4.0",
|
||||
"react-native-camera": "3.17.0",
|
||||
"react-native-default-preference": "1.4.1",
|
||||
"react-native-device-info": "4.0.1",
|
||||
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa",
|
||||
"react-native-elements": "0.19.0",
|
||||
"react-native-flexi-radio-button": "0.2.2",
|
||||
"react-native-fs": "2.13.3",
|
||||
"react-native-gesture-handler": "1.3.0",
|
||||
"react-native-fs": "2.16.4",
|
||||
"react-native-gesture-handler": "1.5.6",
|
||||
"react-native-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
|
||||
"react-native-haptic-feedback": "1.7.1",
|
||||
"react-native-haptic-feedback": "1.9.0",
|
||||
"react-native-image-picker": "1.1.0",
|
||||
"react-native-level-fs": "3.0.1",
|
||||
"react-native-linear-gradient": "2.5.4",
|
||||
"react-native-modal": "11.1.0",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-modal": "11.5.3",
|
||||
"react-native-obscure": "1.2.1",
|
||||
"react-native-popup-menu-android": "1.0.3",
|
||||
"react-native-privacy-snapshot": "git+https://github.com/BlueWallet/react-native-privacy-snapshot.git",
|
||||
"react-native-prompt-android": "git+https://github.com/marcosrdz/react-native-prompt-android.git",
|
||||
"react-native-qrcode-svg": "5.1.2",
|
||||
"react-native-qrcode-svg": "5.3.2",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-randombytes": "3.5.3",
|
||||
"react-native-rate": "1.1.7",
|
||||
"react-native-rate": "1.1.10",
|
||||
"react-native-secure-key-store": "git+https://github.com/marcosrdz/react-native-secure-key-store.git#38332f629f577cdd57c69fc8cc971b3cbad193c9",
|
||||
"react-native-share": "2.0.0",
|
||||
"react-native-snap-carousel": "3.8.4",
|
||||
"react-native-sortable-list": "0.0.23",
|
||||
"react-native-svg": "9.5.1",
|
||||
"react-native-svg": "9.13.6",
|
||||
"react-native-swiper": "git+https://github.com/BlueWallet/react-native-swiper.git#1.5.14",
|
||||
"react-native-tcp": "git+https://github.com/aprock/react-native-tcp.git",
|
||||
"react-native-tooltip": "git+https://github.com/marcosrdz/react-native-tooltip.git",
|
||||
"react-native-vector-icons": "6.6.0",
|
||||
"react-native-watch-connectivity": "0.4.2",
|
||||
"react-native-webview": "6.9.0",
|
||||
"react-native-webview": "6.11.1",
|
||||
"react-navigation": "3.11.0",
|
||||
"react-navigation-hooks": "1.1.0",
|
||||
"react-test-render": "1.1.2",
|
||||
"readable-stream": "3.4.0",
|
||||
"readable-stream": "3.6.0",
|
||||
"secure-random": "1.1.2",
|
||||
"stream-browserify": "2.0.2",
|
||||
"url": "0.11.0",
|
||||
|
@ -79,8 +79,7 @@ export default class LNDCreateInvoice extends Component {
|
||||
onFailure: () => {
|
||||
this.props.navigation.dismiss();
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.state.fromWallet.getAddress(),
|
||||
secret: this.state.fromWallet.getSecret(),
|
||||
wallet: this.state.fromWallet,
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -210,7 +209,7 @@ export default class LNDCreateInvoice extends Component {
|
||||
<TouchableOpacity
|
||||
disabled={this.state.isLoading}
|
||||
onPress={() => {
|
||||
NavigationService.navigate('ScanQrAddress', {
|
||||
NavigationService.navigate('ScanQRCode', {
|
||||
onBarScanned: this.processLnurl,
|
||||
launchedBy: this.props.navigation.state.routeName,
|
||||
});
|
||||
|
@ -109,8 +109,7 @@ export default class ReceiveDetails extends Component {
|
||||
onFailure: () => {
|
||||
this.props.navigation.goBack();
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.wallet.getAddress(),
|
||||
secret: this.wallet.getSecret(),
|
||||
wallet: this.wallet,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import { useNavigationParam, useNavigation } from 'react-navigation-hooks';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
const createHash = require('create-hash');
|
||||
|
||||
const ScanQRCode = ({
|
||||
onBarScanned = useNavigationParam('onBarScanned'),
|
||||
@ -21,7 +22,23 @@ const ScanQRCode = ({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { navigate, goBack } = useNavigation();
|
||||
|
||||
const scannedCache = {};
|
||||
|
||||
const HashIt = function(s) {
|
||||
return createHash('sha256')
|
||||
.update(s)
|
||||
.digest()
|
||||
.toString('hex');
|
||||
};
|
||||
|
||||
const onBarCodeRead = ret => {
|
||||
const h = HashIt(ret.data);
|
||||
if (scannedCache[h]) {
|
||||
// this QR was already scanned by this ScanQRCode, lets prevent firing duplicate callbacks
|
||||
return;
|
||||
}
|
||||
scannedCache[h] = +new Date();
|
||||
|
||||
if (!isLoading && !cameraPreviewIsPaused) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
@ -51,7 +68,7 @@ const ScanQRCode = ({
|
||||
if (fileParsed.keystore.ckcc_xfp) {
|
||||
masterFingerprint = Number(fileParsed.keystore.ckcc_xfp);
|
||||
}
|
||||
onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint } });
|
||||
onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint, label: fileParsed.keystore.label } });
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
@ -121,12 +121,10 @@ const About = () => {
|
||||
<BlueText h3>Built with awesome:</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueText h4>* React Native</BlueText>
|
||||
<BlueText h4>* Bitcoinjs-lib</BlueText>
|
||||
<BlueText h4>* blockcypher.com API</BlueText>
|
||||
<BlueText h4>* bitcoinjs-lib</BlueText>
|
||||
<BlueText h4>* Nodejs</BlueText>
|
||||
<BlueText h4>* react-native-elements</BlueText>
|
||||
<BlueText h4>* rn-nodeify</BlueText>
|
||||
<BlueText h4>* bignumber.js</BlueText>
|
||||
<BlueText h4>* Electrum server</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueButton onPress={handleOnReleaseNotesPress} title="Release notes" />
|
||||
|
@ -1,255 +0,0 @@
|
||||
/** @type {AppStorage} */
|
||||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import { TextInput, View, ActivtyIndicator } from 'react-native';
|
||||
import { FormValidationMessage } from 'react-native-elements';
|
||||
import {
|
||||
BlueLoading,
|
||||
BlueSpacing20,
|
||||
BlueButton,
|
||||
SafeBlueArea,
|
||||
BlueCard,
|
||||
BlueText,
|
||||
BlueSpacing,
|
||||
BlueNavigationStyle,
|
||||
} from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
const bitcoinjs = require('bitcoinjs-lib');
|
||||
let BigNumber = require('bignumber.js');
|
||||
let BlueApp = require('../../BlueApp');
|
||||
|
||||
export default class SendCreate extends Component {
|
||||
static navigationOptions = () => ({
|
||||
...BlueNavigationStyle(null, false),
|
||||
title: 'Create RBF',
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log('send/create constructor');
|
||||
if (!props.navigation.state.params.feeDelta) {
|
||||
props.navigation.state.params.feeDelta = '0';
|
||||
}
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
feeDelta: props.navigation.state.params.feeDelta,
|
||||
newDestinationAddress: props.navigation.state.params.newDestinationAddress,
|
||||
txid: props.navigation.state.params.txid,
|
||||
sourceTx: props.navigation.state.params.sourceTx,
|
||||
fromWallet: props.navigation.state.params.sourceWallet,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
console.log('RBF-create - componentDidMount');
|
||||
|
||||
let utxo = [];
|
||||
|
||||
let lastSequence = 0;
|
||||
let totalInputAmountSatoshi = 0;
|
||||
for (let input of this.state.sourceTx.inputs) {
|
||||
if (input.sequence > lastSequence) {
|
||||
lastSequence = input.sequence;
|
||||
}
|
||||
totalInputAmountSatoshi += input.output_value;
|
||||
// let amount = new BigNumber(input.output_value)
|
||||
// amount = amount.div(10000000).toString(10)
|
||||
utxo.push({
|
||||
tx_hash: input.prev_hash,
|
||||
tx_output_n: input.output_index,
|
||||
value: input.output_value,
|
||||
});
|
||||
}
|
||||
|
||||
// check seq=MAX and fail if it is
|
||||
if (lastSequence === bitcoinjs.Transaction.DEFAULT_SEQUENCE) {
|
||||
return this.setState({
|
||||
isLoading: false,
|
||||
nonReplaceable: true,
|
||||
});
|
||||
// lastSequence = 1
|
||||
}
|
||||
|
||||
let txMetadata = BlueApp.tx_metadata[this.state.txid];
|
||||
if (txMetadata) {
|
||||
if (txMetadata.last_sequence) {
|
||||
lastSequence = Math.max(lastSequence, txMetadata.last_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
lastSequence += 1;
|
||||
|
||||
let changeAddress;
|
||||
let transferAmount;
|
||||
let totalOutputAmountSatoshi = 0;
|
||||
for (let o of this.state.sourceTx.outputs) {
|
||||
totalOutputAmountSatoshi += o.value;
|
||||
if (o.addresses[0] === this.state.fromWallet.getAddress()) {
|
||||
// change
|
||||
changeAddress = o.addresses[0];
|
||||
} else {
|
||||
transferAmount = new BigNumber(o.value);
|
||||
transferAmount = transferAmount.dividedBy(100000000).toString(10);
|
||||
}
|
||||
}
|
||||
let oldFee = new BigNumber(totalInputAmountSatoshi - totalOutputAmountSatoshi);
|
||||
oldFee = parseFloat(oldFee.dividedBy(100000000).toString(10));
|
||||
|
||||
console.log('changeAddress = ', changeAddress);
|
||||
console.log('utxo', utxo);
|
||||
console.log('lastSequence', lastSequence);
|
||||
console.log('totalInputAmountSatoshi', totalInputAmountSatoshi);
|
||||
console.log('totalOutputAmountSatoshi', totalOutputAmountSatoshi);
|
||||
console.log('transferAmount', transferAmount);
|
||||
console.log('oldFee', oldFee);
|
||||
|
||||
let newFee = new BigNumber(oldFee);
|
||||
newFee = newFee.plus(this.state.feeDelta).toString(10);
|
||||
console.log('new Fee', newFee);
|
||||
|
||||
// creating TX
|
||||
|
||||
setTimeout(() => {
|
||||
// more responsive
|
||||
let tx;
|
||||
try {
|
||||
tx = this.state.fromWallet.createTx(utxo, transferAmount, newFee, this.state.newDestinationAddress, false, lastSequence);
|
||||
BlueApp.tx_metadata[this.state.txid] = txMetadata || {};
|
||||
BlueApp.tx_metadata[this.state.txid]['last_sequence'] = lastSequence;
|
||||
|
||||
// in case new TX get confirmed, we must save metadata under new txid
|
||||
let bitcoin = bitcoinjs;
|
||||
let txDecoded = bitcoin.Transaction.fromHex(tx);
|
||||
let txid = txDecoded.getId();
|
||||
BlueApp.tx_metadata[txid] = BlueApp.tx_metadata[this.state.txid];
|
||||
BlueApp.tx_metadata[txid]['txhex'] = tx;
|
||||
//
|
||||
BlueApp.saveToDisk();
|
||||
console.log('BlueApp.txMetadata[this.state.txid]', BlueApp.tx_metadata[this.state.txid]);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return this.setState({
|
||||
isError: true,
|
||||
errorMessage: JSON.stringify(err.message),
|
||||
});
|
||||
}
|
||||
|
||||
let newFeeSatoshi = new BigNumber(newFee);
|
||||
newFeeSatoshi = parseInt(newFeeSatoshi.multipliedBy(100000000));
|
||||
let satoshiPerByte = Math.round(newFeeSatoshi / (tx.length / 2));
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
size: Math.round(tx.length / 2),
|
||||
tx,
|
||||
satoshiPerByte: satoshiPerByte,
|
||||
amount: transferAmount,
|
||||
fee: newFee,
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
async broadcast() {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
console.log('broadcasting', this.state.tx);
|
||||
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
|
||||
console.log('broadcast result = ', result);
|
||||
if (typeof result === 'string') {
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch (_) {
|
||||
result = { result };
|
||||
}
|
||||
}
|
||||
if (result && result.error) {
|
||||
alert(JSON.stringify(result.error));
|
||||
this.setState({ isLoading: false });
|
||||
} else {
|
||||
alert(JSON.stringify(result.result || result.txid));
|
||||
this.props.navigation.navigate('TransactionStatus');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.isError) {
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueSpacing />
|
||||
<BlueCard title={'Replace Transaction'} style={{ alignItems: 'center', flex: 1 }}>
|
||||
<BlueText>Error creating transaction. Invalid address or send amount?</BlueText>
|
||||
<FormValidationMessage>{this.state.errorMessage}</FormValidationMessage>
|
||||
</BlueCard>
|
||||
<BlueButton onPress={() => this.props.navigation.goBack()} title="Go back" />
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
||||
if (this.state.nonReplaceable) {
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignContent: 'center' }}>
|
||||
<BlueText h4 style={{ textAlign: 'center' }}>
|
||||
This transaction is not replaceable
|
||||
</BlueText>
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueSpacing />
|
||||
<BlueCard title={'Replace Transaction'} style={{ alignItems: 'center', flex: 1 }}>
|
||||
<BlueText>This is your transaction's hex, signed and ready to be broadcasted to the network. Continue?</BlueText>
|
||||
|
||||
<TextInput
|
||||
style={{
|
||||
borderColor: '#ebebeb',
|
||||
borderWidth: 1,
|
||||
marginTop: 20,
|
||||
color: '#ebebeb',
|
||||
}}
|
||||
maxHeight={70}
|
||||
multiline
|
||||
editable={false}
|
||||
value={this.state.tx}
|
||||
/>
|
||||
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueText style={{ paddingTop: 20 }}>To: {this.state.newDestinationAddress}</BlueText>
|
||||
<BlueText>Amount: {this.state.amount} BTC</BlueText>
|
||||
<BlueText>Fee: {this.state.fee} BTC</BlueText>
|
||||
<BlueText>TX size: {this.state.size} Bytes</BlueText>
|
||||
<BlueText>satoshiPerByte: {this.state.satoshiPerByte} Sat/B</BlueText>
|
||||
</BlueCard>
|
||||
{this.state.isLoading ? (
|
||||
<ActivtyIndicator />
|
||||
) : (
|
||||
<BlueButton icon={{ name: 'bullhorn', type: 'font-awesome' }} onPress={() => this.broadcast()} title="Broadcast" />
|
||||
)}
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SendCreate.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
feeDelta: PropTypes.string,
|
||||
fromAddress: PropTypes.string,
|
||||
newDestinationAddress: PropTypes.string,
|
||||
txid: PropTypes.string,
|
||||
sourceTx: PropTypes.object,
|
||||
sourceWallet: PropTypes.object,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
};
|
@ -1,202 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ActivityIndicator, View, TextInput } from 'react-native';
|
||||
import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueSpacing, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SegwitBech32Wallet } from '../../class';
|
||||
/** @type {AppStorage} */
|
||||
let BlueApp = require('../../BlueApp');
|
||||
|
||||
export default class RBF extends Component {
|
||||
static navigationOptions = () => ({
|
||||
...BlueNavigationStyle(null, false),
|
||||
title: 'RBF',
|
||||
});
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let txid;
|
||||
if (props.navigation.state.params) txid = props.navigation.state.params.txid;
|
||||
|
||||
let sourceWallet;
|
||||
let sourceTx;
|
||||
for (let w of BlueApp.getWallets()) {
|
||||
for (let t of w.getTransactions()) {
|
||||
if (t.hash === txid) {
|
||||
// found our source wallet
|
||||
sourceWallet = w;
|
||||
sourceTx = t;
|
||||
console.log(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let destinationAddress;
|
||||
|
||||
for (let o of sourceTx.outputs) {
|
||||
if (!o.addresses && o.script) {
|
||||
// probably bech32 output, so we need to decode address
|
||||
o.addresses = [SegwitBech32Wallet.scriptPubKeyToAddress(o.script)];
|
||||
}
|
||||
|
||||
if (o.addresses && o.addresses[0] === sourceWallet.getAddress()) {
|
||||
// change
|
||||
// nop
|
||||
} else {
|
||||
// DESTINATION address
|
||||
|
||||
destinationAddress = (o.addresses && o.addresses[0]) || '';
|
||||
console.log('dest = ', destinationAddress);
|
||||
}
|
||||
}
|
||||
|
||||
if (!destinationAddress || sourceWallet.type === 'legacy') {
|
||||
// for now I'm too lazy to add RBF support for legacy addresses
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
nonReplaceable: true,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
txid,
|
||||
sourceTx,
|
||||
sourceWallet,
|
||||
newDestinationAddress: destinationAddress,
|
||||
feeDelta: '',
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
let startTime = Date.now();
|
||||
console.log('transactions/RBF - componentDidMount');
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
});
|
||||
let endTime = Date.now();
|
||||
console.log('componentDidMount took', (endTime - startTime) / 1000, 'sec');
|
||||
}
|
||||
|
||||
createTransaction() {
|
||||
this.props.navigation.navigate('CreateRBF', {
|
||||
feeDelta: this.state.feeDelta,
|
||||
newDestinationAddress: this.state.newDestinationAddress,
|
||||
txid: this.state.txid,
|
||||
sourceTx: this.state.sourceTx,
|
||||
sourceWallet: this.state.sourceWallet,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.nonReplaceable) {
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueText h4>This transaction is not replaceable</BlueText>
|
||||
|
||||
<BlueButton onPress={() => this.props.navigation.goBack()} title="Back" />
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.sourceWallet.getAddress) {
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueText>System error: Source wallet not found (this should never happen)</BlueText>
|
||||
<BlueButton onPress={() => this.props.navigation.goBack()} title="Back" />
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeBlueArea style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueSpacing />
|
||||
<BlueCard title={'Replace By Fee'} style={{ alignItems: 'center', flex: 1 }}>
|
||||
<BlueText>RBF allows you to increase fee on already sent but not confirmed transaction, thus speeding up mining</BlueText>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueText>
|
||||
From wallet '{this.state.sourceWallet.getLabel()}' ({this.state.sourceWallet.getAddress()})
|
||||
</BlueText>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderColor: '#d2d2d2',
|
||||
borderBottomColor: '#d2d2d2',
|
||||
borderWidth: 1.0,
|
||||
borderBottomWidth: 0.5,
|
||||
backgroundColor: '#f5f5f5',
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
onChangeText={text => this.setState({ newDestinationAddress: text })}
|
||||
placeholder={'receiver address here'}
|
||||
value={this.state.newDestinationAddress}
|
||||
style={{ flex: 1, minHeight: 33, marginHorizontal: 8 }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderColor: '#d2d2d2',
|
||||
borderBottomColor: '#d2d2d2',
|
||||
borderWidth: 1.0,
|
||||
borderBottomWidth: 0.5,
|
||||
backgroundColor: '#f5f5f5',
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
onChangeText={text => this.setState({ feeDelta: text })}
|
||||
keyboardType={'numeric'}
|
||||
placeholder={'fee to add (in BTC)'}
|
||||
value={this.state.feeDelta + ''}
|
||||
style={{ flex: 1, minHeight: 33, marginHorizontal: 8 }}
|
||||
/>
|
||||
</View>
|
||||
</BlueCard>
|
||||
<BlueSpacing />
|
||||
|
||||
<BlueButton onPress={() => this.createTransaction()} title="Create" />
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RBF.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
txid: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
};
|
@ -13,7 +13,7 @@ import {
|
||||
BlueNavigationStyle,
|
||||
} from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
|
||||
import { HDSegwitBech32Transaction } from '../../class';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import Handoff from 'react-native-handoff';
|
||||
@ -93,7 +93,7 @@ export default class TransactionsStatus extends Component {
|
||||
}
|
||||
|
||||
async checkPossibilityOfCPFP() {
|
||||
if (this.state.wallet.type !== HDSegwitBech32Wallet.type) {
|
||||
if (!this.state.wallet.allowRBF()) {
|
||||
return this.setState({ isCPFPpossible: buttonStatus.notPossible });
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ export default class TransactionsStatus extends Component {
|
||||
}
|
||||
|
||||
async checkPossibilityOfRBFBumpFee() {
|
||||
if (this.state.wallet.type !== HDSegwitBech32Wallet.type) {
|
||||
if (!this.state.wallet.allowRBF()) {
|
||||
return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible });
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ export default class TransactionsStatus extends Component {
|
||||
}
|
||||
|
||||
async checkPossibilityOfRBFCancel() {
|
||||
if (this.state.wallet.type !== HDSegwitBech32Wallet.type) {
|
||||
if (!this.state.wallet.allowRBF()) {
|
||||
return this.setState({ isRBFCancelPossible: buttonStatus.notPossible });
|
||||
}
|
||||
|
||||
@ -250,24 +250,6 @@ export default class TransactionsStatus extends Component {
|
||||
</BlueCard>
|
||||
|
||||
<View style={{ alignSelf: 'center', justifyContent: 'center' }}>
|
||||
{(() => {
|
||||
if (this.state.tx.confirmations === 0 && this.state.wallet && this.state.wallet.allowRBF()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('RBF', {
|
||||
txid: this.state.tx.hash,
|
||||
})
|
||||
}
|
||||
title="Replace-By-Fee (RBF)"
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
|
||||
{(() => {
|
||||
if (this.state.isCPFPpossible === buttonStatus.unknown) {
|
||||
return (
|
||||
|
@ -98,36 +98,35 @@ export default class WalletsAdd extends Component {
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||
<ScrollView>
|
||||
<BlueFormLabel>{loc.wallets.add.wallet_name}</BlueFormLabel>
|
||||
<KeyboardAvoidingView
|
||||
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,
|
||||
}}
|
||||
enabled
|
||||
behavior={Platform.OS === 'ios' ? 'position' : null}
|
||||
keyboardVerticalOffset={20}
|
||||
>
|
||||
<TextInput
|
||||
value={this.state.label}
|
||||
placeholderTextColor="#81868e"
|
||||
placeholder="my first wallet"
|
||||
onChangeText={text => {
|
||||
this.setLabel(text);
|
||||
<KeyboardAvoidingView enabled behavior={Platform.OS === 'ios' ? 'position' : null} keyboardVerticalOffset={20}>
|
||||
<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,
|
||||
}}
|
||||
style={{ flex: 1, marginHorizontal: 8, color: '#81868e' }}
|
||||
editable={!this.state.isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
>
|
||||
<TextInput
|
||||
value={this.state.label}
|
||||
placeholderTextColor="#81868e"
|
||||
placeholder="my first wallet"
|
||||
onChangeText={text => {
|
||||
this.setLabel(text);
|
||||
}}
|
||||
style={{ flex: 1, marginHorizontal: 8, color: '#81868e' }}
|
||||
editable={!this.state.isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
<BlueFormLabel>{loc.wallets.add.wallet_type}</BlueFormLabel>
|
||||
|
||||
|
@ -218,8 +218,7 @@ export default class WalletDetails extends Component {
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.state.wallet.getAddress(),
|
||||
secret: this.state.wallet.getSecret(),
|
||||
wallet: this.state.wallet,
|
||||
})
|
||||
}
|
||||
title={loc.wallets.details.export_backup}
|
||||
|
@ -5,7 +5,7 @@ import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText, BlueCopyTex
|
||||
import PropTypes from 'prop-types';
|
||||
import Privacy from '../../Privacy';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { LightningCustodianWallet } from '../../class';
|
||||
import { LegacyWallet, LightningCustodianWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../../class';
|
||||
/** @type {AppStorage} */
|
||||
let BlueApp = require('../../BlueApp');
|
||||
let loc = require('../../loc');
|
||||
@ -20,17 +20,7 @@ export default class WalletExport extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let address = props.navigation.state.params.address;
|
||||
let secret = props.navigation.state.params.secret;
|
||||
let wallet;
|
||||
for (let w of BlueApp.getWallets()) {
|
||||
if ((address && w.getAddress() === address) || w.getSecret() === secret) {
|
||||
// found our wallet
|
||||
wallet = w;
|
||||
}
|
||||
}
|
||||
|
||||
let wallet = props.navigation.state.params.wallet;
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
qrCodeHeight: height > width ? width - 40 : width / 2,
|
||||
@ -89,7 +79,7 @@ export default class WalletExport extends Component {
|
||||
</View>
|
||||
|
||||
{(() => {
|
||||
if (this.state.wallet.getAddress()) {
|
||||
if ([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(this.state.wallet.type)) {
|
||||
return (
|
||||
<BlueCard>
|
||||
<BlueText>{this.state.wallet.getAddress()}</BlueText>
|
||||
@ -125,8 +115,7 @@ WalletExport.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
secret: PropTypes.string,
|
||||
wallet: PropTypes.object.isRequired,
|
||||
}),
|
||||
}),
|
||||
navigate: PropTypes.func,
|
||||
|
@ -120,7 +120,7 @@ const WalletsImport = () => {
|
||||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true });
|
||||
navigate('ScanQRCode', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
|
||||
import { PlaceholderWallet } from '../../class';
|
||||
import WalletImport from '../../class/walletImport';
|
||||
import Swiper from 'react-native-swiper';
|
||||
import ScanQRCode from '../send/scanQrAddress';
|
||||
import ScanQRCode from '../send/ScanQRCode';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch';
|
||||
let EV = require('../../events');
|
||||
let A = require('../../analytics');
|
||||
|
@ -466,8 +466,7 @@ export default class WalletTransactions extends Component {
|
||||
},
|
||||
onFailure: () =>
|
||||
this.props.navigation.navigate('WalletExport', {
|
||||
address: this.state.wallet.getAddress(),
|
||||
secret: this.state.wallet.getSecret(),
|
||||
wallet: this.state.wallet,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* global describe, it, expect, jest, jasmine */
|
||||
import React from 'react';
|
||||
import { LegacyWallet, SegwitP2SHWallet, AppStorage } from '../../class';
|
||||
import { AppStorage } from '../../class';
|
||||
import TestRenderer from 'react-test-renderer';
|
||||
import Settings from '../../screen/settings/settings';
|
||||
import Selftest from '../../screen/selftest';
|
||||
@ -49,28 +49,6 @@ jest.mock('ScrollView', () => {
|
||||
return ScrollView;
|
||||
});
|
||||
|
||||
describe('unit - LegacyWallet', function() {
|
||||
it('serialize and unserialize work correctly', () => {
|
||||
let a = new LegacyWallet();
|
||||
a.setLabel('my1');
|
||||
let key = JSON.stringify(a);
|
||||
|
||||
let b = LegacyWallet.fromJson(key);
|
||||
assert(key === JSON.stringify(b));
|
||||
|
||||
assert.strictEqual(key, JSON.stringify(b));
|
||||
});
|
||||
|
||||
it('can validate addresses', () => {
|
||||
let w = new LegacyWallet();
|
||||
assert.ok(w.isAddressValid('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
|
||||
assert.ok(!w.isAddressValid('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2j'));
|
||||
assert.ok(w.isAddressValid('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'));
|
||||
assert.ok(!w.isAddressValid('3BDsBDxDimYgNZzsqszNZobqQq3yeUo'));
|
||||
assert.ok(!w.isAddressValid('12345'));
|
||||
});
|
||||
});
|
||||
|
||||
it('BlueHeader works', () => {
|
||||
const rendered = TestRenderer.create(<BlueHeader />).toJSON();
|
||||
expect(rendered).toBeTruthy();
|
||||
@ -105,92 +83,6 @@ it('Selftest work', () => {
|
||||
assert.ok(okFound, 'OK not found. Got: ' + allTests.join('; '));
|
||||
});
|
||||
|
||||
it('Wallet can fetch UTXO', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
let w = new SegwitP2SHWallet();
|
||||
w._address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
|
||||
await w.fetchUtxo();
|
||||
assert.ok(w.utxo.length > 0, 'unexpected empty UTXO');
|
||||
});
|
||||
|
||||
it('SegwitP2SHWallet can generate segwit P2SH address from WIF', async () => {
|
||||
let l = new SegwitP2SHWallet();
|
||||
l.setSecret('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct');
|
||||
assert.ok(l.getAddress() === '34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53', 'expected ' + l.getAddress());
|
||||
assert.ok(l.getAddress() === (await l.getAddressAsync()));
|
||||
});
|
||||
|
||||
it('Wallet can fetch balance', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
let w = new LegacyWallet();
|
||||
w._address = '115fUy41sZkAG14CmdP1VbEKcNRZJWkUWG'; // hack internals
|
||||
assert.ok(w.getBalance() === 0);
|
||||
assert.ok(w.getUnconfirmedBalance() === 0);
|
||||
assert.ok(w._lastBalanceFetch === 0);
|
||||
await w.fetchBalance();
|
||||
assert.ok(w.getBalance() === 18262000);
|
||||
assert.ok(w.getUnconfirmedBalance() === 0);
|
||||
assert.ok(w._lastBalanceFetch > 0);
|
||||
});
|
||||
|
||||
it('Wallet can fetch TXs', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG';
|
||||
await w.fetchTransactions();
|
||||
assert.strictEqual(w.getTransactions().length, 2);
|
||||
|
||||
let tx0 = w.getTransactions()[0];
|
||||
let txExpected = {
|
||||
block_hash: '0000000000000000000d05c54a592db8532f134e12b4c3ae0821ce582fad3566',
|
||||
block_height: 530933,
|
||||
block_index: 1587,
|
||||
hash: '4924f3a29acdee007ebcf6084d2c9e1752c4eb7f26f7d1a06ef808780bf5fe6d',
|
||||
addresses: ['12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG', '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'],
|
||||
total: 800,
|
||||
fees: 200,
|
||||
size: 190,
|
||||
preference: 'low',
|
||||
relayed_by: '18.197.135.148:8333',
|
||||
confirmed: '2018-07-07T20:05:30Z',
|
||||
received: '2018-07-07T20:02:01.637Z',
|
||||
ver: 1,
|
||||
double_spend: false,
|
||||
vin_sz: 1,
|
||||
vout_sz: 1,
|
||||
confirmations: 593,
|
||||
confidence: 1,
|
||||
inputs: [
|
||||
{
|
||||
prev_hash: 'd0432027a86119c63a0be8fa453275c2333b59067f1e559389cd3e0e377c8b96',
|
||||
output_index: 1,
|
||||
script:
|
||||
'483045022100e443784abe25b6d39e01c95900834bf4eeaa82505ac0eb84c08e11c287d467de02203327c2b1136f4976f755ed7631b427d66db2278414e7faf1268eedf44c034e0c012103c69b905f7242b3688122f06951339a1ee00da652f6ecc6527ea6632146cace62',
|
||||
output_value: 1000,
|
||||
sequence: 4294967295,
|
||||
addresses: ['12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'],
|
||||
script_type: 'pay-to-pubkey-hash',
|
||||
age: 530926,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
value: 800,
|
||||
script: 'a914688eb9af71aab8ca221f4e6171a45fc46ea8743b87',
|
||||
spent_by: '009c6219deeac341833642193e4a3b72e511105a61b48e375c5025b1bcbd6fb5',
|
||||
addresses: ['3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'],
|
||||
script_type: 'pay-to-script-hash',
|
||||
},
|
||||
],
|
||||
value: -1000,
|
||||
};
|
||||
|
||||
delete tx0.confirmations;
|
||||
delete txExpected.confirmations;
|
||||
delete tx0.preference; // that bs is not always the same
|
||||
delete txExpected.preference;
|
||||
assert.deepStrictEqual(tx0, txExpected);
|
||||
});
|
||||
|
||||
describe('currency', () => {
|
||||
it('fetches exchange rate and saves to AsyncStorage', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
|
||||
|
@ -306,7 +306,7 @@ it('Legacy HD (BIP44) can generate addressess based on xpub', async function() {
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU');
|
||||
});
|
||||
|
||||
it.skip('Legacy HD (BIP44) can create TX', async () => {
|
||||
it('Legacy HD (BIP44) can create TX', async () => {
|
||||
if (!process.env.HD_MNEMONIC) {
|
||||
console.error('process.env.HD_MNEMONIC not set, skipped');
|
||||
return;
|
||||
@ -315,15 +315,14 @@ it.skip('Legacy HD (BIP44) can create TX', async () => {
|
||||
hd.setSecret(process.env.HD_MNEMONIC);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
|
||||
await hd.fetchBalance();
|
||||
await hd.fetchUtxo();
|
||||
assert.strictEqual(hd.utxo.length, 4);
|
||||
await hd.getChangeAddressAsync(); // to refresh internal pointer to next free address
|
||||
await hd.getAddressAsync(); // to refresh internal pointer to next free address
|
||||
let txhex = hd.createTx(hd.utxo, 0.0008, 0.000005, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
|
||||
|
||||
assert.strictEqual(
|
||||
txhex,
|
||||
'01000000045fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f030000006b4830450221009be5dbe37db5a8409ddce3570140c95d162a07651b1e48cf39a6a741892adc53022061a25b8024d8f3cb1b94f264245de0c6e9a103ea557ddeb66245b40ec8e9384b012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f000000006a47304402207106e9fa4e2e35d351fbccc9c0fad3356d85d0cd35a9d7e9cbcefce5440da1e5022073c1905b5927447378c0f660e62900c1d4b2691730799458889fb87d86f5159101210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f020000006a4730440220250b15094096c4d4fe6793da8e45fa118ed057cc2759a480c115e76e23590791022079cdbdc9e630d713395602071e2837ecc1d192a36a24d8ec71bc51d5e62b203b01210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f010000006b483045022100879da610e6ed12c84d55f12baf3bf6222d59b5282502b3c7f4db1d22152c16900220759a1c88583cbdaf7fde21c273ad985dfdf94a2fa85e42ee41dcea2fd69136fd012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff02803801000000000017a914a3a65daca3064280ae072b9d6773c027b30abace872c4c0000000000001976a9146ee5e3e66dc73587a3a2d77a1a6c8554fae21b8a88ac00000000',
|
||||
'01000000045fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f000000006b48304502210080ffbde0d510c3fb9abcc5f7570448e9c0f7138d0b355d00bb97cada0679ac9502207ffd205373829c800ec08079a4280c3abe6f6f8c94ae7af0157a14ea5629d28701210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f010000006a473044022077788d7e118802fd7268aac7a1dde5a6724f01936e23edd46ac2750fd39265be0220776ac9e4c285580d06510a00b561cec6de1813293e7b04b6f870138af832bf9e012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f020000006b4830450221009e47b48dd1eee6d00a1817480605f446e11949b1e6f464f43f04bce2fc787ea9022022b3dcf80e7b2c995cf6defb3425b57d8a80918c7f543faaa0497d853820779101210316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667ffffffff5fbc74110c2d6fcf4d1161a59913fbcd2b6ab3c5a9eb4d0dc0859515cbc8654f030000006b48304502210089c20d6c0f6486c5979cf69a3c849f09e36416e5604499c05ae2dc22bea8553d022011241a206d550e55b4476ac5ba0fd744f0965d8f8bd69a740e428770689749a1012102ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002ffffffff02803801000000000017a914a3a65daca3064280ae072b9d6773c027b30abace872c4c0000000000001976a9146ee5e3e66dc73587a3a2d77a1a6c8554fae21b8a88ac00000000',
|
||||
);
|
||||
|
||||
var tx = bitcoin.Transaction.fromHex(txhex);
|
||||
@ -346,22 +345,7 @@ it.skip('Legacy HD (BIP44) can create TX', async () => {
|
||||
assert.strictEqual(tx.outs[0].value, 99800);
|
||||
});
|
||||
|
||||
it('Legacy HD (BIP44) can fetch UTXO', async function() {
|
||||
let hd = new HDLegacyP2PKHWallet();
|
||||
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
|
||||
await hd.fetchUtxo();
|
||||
assert.ok(hd.utxo.length >= 12);
|
||||
assert.ok(typeof hd.utxo[0].confirmations === 'number');
|
||||
assert.ok(hd.utxo[0].txid);
|
||||
assert.ok(hd.utxo[0].vout);
|
||||
assert.ok(hd.utxo[0].amount);
|
||||
assert.ok(
|
||||
hd.utxo[0].address &&
|
||||
(hd.utxo[0].address === '1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55' || hd.utxo[0].address === '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV'),
|
||||
);
|
||||
});
|
||||
|
||||
it.skip('HD breadwallet works', async function() {
|
||||
it('HD breadwallet works', async function() {
|
||||
if (!process.env.HD_MNEMONIC_BREAD) {
|
||||
console.error('process.env.HD_MNEMONIC_BREAD not set, skipped');
|
||||
return;
|
||||
@ -379,17 +363,17 @@ it.skip('HD breadwallet works', async function() {
|
||||
'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh',
|
||||
);
|
||||
await hdBread.fetchBalance();
|
||||
assert.strictEqual(hdBread.balance, 0);
|
||||
assert.strictEqual(hdBread.getBalance(), 123456);
|
||||
|
||||
assert.ok(hdBread._lastTxFetch === 0);
|
||||
await hdBread.fetchTransactions();
|
||||
assert.ok(hdBread._lastTxFetch > 0);
|
||||
assert.strictEqual(hdBread.getTransactions().length, 177);
|
||||
assert.strictEqual(hdBread.getTransactions().length, 178);
|
||||
for (let tx of hdBread.getTransactions()) {
|
||||
assert.ok(tx.confirmations);
|
||||
}
|
||||
|
||||
assert.strictEqual(hdBread.next_free_address_index, 10);
|
||||
assert.strictEqual(hdBread.next_free_address_index, 11);
|
||||
assert.strictEqual(hdBread.next_free_change_address_index, 118);
|
||||
|
||||
// checking that internal pointer and async address getter return the same address
|
||||
|
154
tests/integration/LegacyWallet.test.js
Normal file
154
tests/integration/LegacyWallet.test.js
Normal file
@ -0,0 +1,154 @@
|
||||
/* global describe, it, jasmine, afterAll, beforeAll */
|
||||
import { LegacyWallet, SegwitP2SHWallet, SegwitBech32Wallet } from '../../class';
|
||||
let assert = require('assert');
|
||||
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js
|
||||
let BlueElectrum = require('../../BlueElectrum'); // so it connects ASAP
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
|
||||
afterAll(async () => {
|
||||
// after all tests we close socket so the test suite can actually terminate
|
||||
BlueElectrum.forceDisconnect();
|
||||
return new Promise(resolve => setTimeout(resolve, 10000)); // simple sleep to wait for all timeouts termination
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// awaiting for Electrum to be connected. For RN Electrum would naturally connect
|
||||
// while app starts up, but for tests we need to wait for it
|
||||
await BlueElectrum.waitTillConnected();
|
||||
});
|
||||
|
||||
describe('LegacyWallet', function() {
|
||||
it('can serialize and unserialize correctly', () => {
|
||||
let a = new LegacyWallet();
|
||||
a.setLabel('my1');
|
||||
let key = JSON.stringify(a);
|
||||
|
||||
let b = LegacyWallet.fromJson(key);
|
||||
assert(key === JSON.stringify(b));
|
||||
|
||||
assert.strictEqual(key, JSON.stringify(b));
|
||||
});
|
||||
|
||||
it('can validate addresses', () => {
|
||||
let w = new LegacyWallet();
|
||||
assert.ok(w.isAddressValid('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'));
|
||||
assert.ok(!w.isAddressValid('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2j'));
|
||||
assert.ok(w.isAddressValid('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'));
|
||||
assert.ok(!w.isAddressValid('3BDsBDxDimYgNZzsqszNZobqQq3yeUo'));
|
||||
assert.ok(!w.isAddressValid('12345'));
|
||||
assert.ok(w.isAddressValid('bc1quuafy8htjjj263cvpj7md84magzmc8svmh8lrm'));
|
||||
assert.ok(w.isAddressValid('BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7'));
|
||||
});
|
||||
|
||||
it('can fetch balance', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = '115fUy41sZkAG14CmdP1VbEKcNRZJWkUWG'; // hack internals
|
||||
assert.ok(w.weOwnAddress('115fUy41sZkAG14CmdP1VbEKcNRZJWkUWG'));
|
||||
assert.ok(!w.weOwnAddress('aaa'));
|
||||
assert.ok(w.getBalance() === 0);
|
||||
assert.ok(w.getUnconfirmedBalance() === 0);
|
||||
assert.ok(w._lastBalanceFetch === 0);
|
||||
await w.fetchBalance();
|
||||
assert.ok(w.getBalance() === 18262000);
|
||||
assert.ok(w.getUnconfirmedBalance() === 0);
|
||||
assert.ok(w._lastBalanceFetch > 0);
|
||||
});
|
||||
|
||||
it('can fetch TXs', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG';
|
||||
await w.fetchTransactions();
|
||||
assert.strictEqual(w.getTransactions().length, 2);
|
||||
|
||||
for (let tx of w.getTransactions()) {
|
||||
assert.ok(tx.hash);
|
||||
assert.ok(tx.value);
|
||||
assert.ok(tx.received);
|
||||
assert.ok(tx.confirmations > 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('can fetch UTXO', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
|
||||
await w.fetchUtxo();
|
||||
assert.ok(w.utxo.length > 0, 'unexpected empty UTXO');
|
||||
assert.ok(w.getUtxo().length > 0, 'unexpected empty UTXO');
|
||||
|
||||
assert.ok(w.getUtxo()[0]['value']);
|
||||
assert.ok(w.getUtxo()[0]['tx_output_n'] === 0 || w.getUtxo()[0]['tx_output_n'] === 1, JSON.stringify(w.getUtxo()[0]));
|
||||
assert.ok(w.getUtxo()[0]['tx_hash']);
|
||||
assert.ok(w.getUtxo()[0]['confirmations']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SegwitP2SHWallet', function() {
|
||||
it('can generate segwit P2SH address from WIF', async () => {
|
||||
let l = new SegwitP2SHWallet();
|
||||
l.setSecret('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct');
|
||||
assert.ok(l.getAddress() === '34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53', 'expected ' + l.getAddress());
|
||||
assert.ok(l.getAddress() === (await l.getAddressAsync()));
|
||||
assert.ok(l.weOwnAddress('34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('SegwitBech32Wallet', function() {
|
||||
it('can fetch balance', async () => {
|
||||
let w = new SegwitBech32Wallet();
|
||||
w._address = 'bc1qn887fmetaytw4vj68vsh529ft408q8j9x3dndc';
|
||||
assert.ok(w.weOwnAddress('bc1qn887fmetaytw4vj68vsh529ft408q8j9x3dndc'));
|
||||
await w.fetchBalance();
|
||||
assert.strictEqual(w.getBalance(), 100000);
|
||||
});
|
||||
|
||||
it('can fetch UTXO', async () => {
|
||||
let w = new SegwitBech32Wallet();
|
||||
w._address = 'bc1qn887fmetaytw4vj68vsh529ft408q8j9x3dndc';
|
||||
await w.fetchUtxo();
|
||||
assert.ok(w.getUtxo().length > 0, 'unexpected empty UTXO');
|
||||
|
||||
assert.ok(w.getUtxo()[0]['value']);
|
||||
assert.ok(w.getUtxo()[0]['tx_output_n'] === 0);
|
||||
assert.ok(w.getUtxo()[0]['tx_hash']);
|
||||
assert.ok(w.getUtxo()[0]['confirmations']);
|
||||
});
|
||||
|
||||
it('can fetch TXs', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv';
|
||||
await w.fetchTransactions();
|
||||
assert.strictEqual(w.getTransactions().length, 2);
|
||||
|
||||
for (let tx of w.getTransactions()) {
|
||||
assert.ok(tx.hash);
|
||||
assert.ok(tx.value);
|
||||
assert.ok(tx.received);
|
||||
assert.ok(tx.confirmations > 1);
|
||||
}
|
||||
|
||||
assert.strictEqual(w.getTransactions()[0].value, -892111);
|
||||
assert.strictEqual(w.getTransactions()[1].value, 892111);
|
||||
});
|
||||
|
||||
it('can fetch TXs', async () => {
|
||||
let w = new LegacyWallet();
|
||||
w._address = 'bc1qn887fmetaytw4vj68vsh529ft408q8j9x3dndc';
|
||||
assert.ok(w.weOwnAddress('bc1qn887fmetaytw4vj68vsh529ft408q8j9x3dndc'));
|
||||
await w.fetchTransactions();
|
||||
assert.strictEqual(w.getTransactions().length, 1);
|
||||
|
||||
for (let tx of w.getTransactions()) {
|
||||
assert.ok(tx.hash);
|
||||
assert.strictEqual(tx.value, 100000);
|
||||
assert.ok(tx.received);
|
||||
assert.ok(tx.confirmations > 1);
|
||||
}
|
||||
|
||||
let tx0 = w.getTransactions()[0];
|
||||
assert.ok(tx0['inputs']);
|
||||
assert.ok(tx0['inputs'].length === 1);
|
||||
assert.ok(tx0['outputs']);
|
||||
assert.ok(tx0['outputs'].length === 3);
|
||||
});
|
||||
});
|
@ -16,6 +16,8 @@ beforeAll(async () => {
|
||||
await BlueElectrum.waitTillConnected();
|
||||
});
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 500 * 1000;
|
||||
|
||||
describe('Watch only wallet', () => {
|
||||
it('can fetch balance', async () => {
|
||||
let w = new WatchOnlyWallet();
|
||||
@ -25,12 +27,11 @@ describe('Watch only wallet', () => {
|
||||
});
|
||||
|
||||
it('can fetch tx', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
|
||||
w.setSecret('167zK5iZrs1U6piDqubD3FjRqUTM2CZnb8');
|
||||
await w.fetchTransactions();
|
||||
assert.strictEqual(w.getTransactions().length, 233);
|
||||
assert.ok(w.getTransactions().length >= 215);
|
||||
// should be 233 but electrum server cant return huge transactions >.<
|
||||
|
||||
w = new WatchOnlyWallet();
|
||||
w.setSecret('1BiJW1jyUaxcJp2JWwbPLPzB1toPNWTFJV');
|
||||
@ -42,8 +43,33 @@ describe('Watch only wallet', () => {
|
||||
assert.strictEqual(w.getTransactions().length, 2);
|
||||
});
|
||||
|
||||
it('can fetch TXs with values', async () => {
|
||||
let w = new WatchOnlyWallet();
|
||||
for (let sec of [
|
||||
'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv',
|
||||
'BC1QUHNVE8Q4TK3UNHMJTS7YMXV8CD6W9XV8WY29UV',
|
||||
'bitcoin:bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv',
|
||||
'BITCOIN:BC1QUHNVE8Q4TK3UNHMJTS7YMXV8CD6W9XV8WY29UV',
|
||||
]) {
|
||||
w.setSecret(sec);
|
||||
assert.strictEqual(w.getAddress(), 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv');
|
||||
assert.strictEqual(await w.getAddressAsync(), 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv');
|
||||
assert.ok(w.weOwnAddress('bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv'));
|
||||
await w.fetchTransactions();
|
||||
|
||||
for (let tx of w.getTransactions()) {
|
||||
assert.ok(tx.hash);
|
||||
assert.ok(tx.value);
|
||||
assert.ok(tx.received);
|
||||
assert.ok(tx.confirmations > 1);
|
||||
}
|
||||
|
||||
assert.strictEqual(w.getTransactions()[0].value, -892111);
|
||||
assert.strictEqual(w.getTransactions()[1].value, 892111);
|
||||
}
|
||||
});
|
||||
|
||||
it('can fetch complex TXs', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('3NLnALo49CFEF4tCRhCvz45ySSfz3UktZC');
|
||||
await w.fetchTransactions();
|
||||
@ -54,28 +80,34 @@ describe('Watch only wallet', () => {
|
||||
|
||||
it('can validate address', async () => {
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), false);
|
||||
w.setSecret('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), false);
|
||||
for (let secret of [
|
||||
'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv',
|
||||
'12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG',
|
||||
'3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2',
|
||||
'BC1QUHNVE8Q4TK3UNHMJTS7YMXV8CD6W9XV8WY29UV',
|
||||
]) {
|
||||
w.setSecret(secret);
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), false);
|
||||
}
|
||||
|
||||
w.setSecret('not valid');
|
||||
assert.ok(!w.valid());
|
||||
|
||||
w.setSecret('xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps');
|
||||
assert.ok(w.valid());
|
||||
w.setSecret('ypub6XRzrn3HB1tjhhvrHbk1vnXCecZEdXohGzCk3GXwwbDoJ3VBzZ34jNGWbC6WrS7idXrYjjXEzcPDX5VqnHEnuNf5VAXgLfSaytMkJ2rwVqy');
|
||||
assert.ok(w.valid());
|
||||
w.setSecret('zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP');
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), true);
|
||||
assert.strictEqual(w.getMasterFingerprint(), false);
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '00000000');
|
||||
for (let secret of [
|
||||
'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps',
|
||||
'ypub6XRzrn3HB1tjhhvrHbk1vnXCecZEdXohGzCk3GXwwbDoJ3VBzZ34jNGWbC6WrS7idXrYjjXEzcPDX5VqnHEnuNf5VAXgLfSaytMkJ2rwVqy',
|
||||
'zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP',
|
||||
]) {
|
||||
w.setSecret(secret);
|
||||
assert.ok(w.valid());
|
||||
assert.strictEqual(w.isHd(), true);
|
||||
assert.strictEqual(w.getMasterFingerprint(), false);
|
||||
assert.strictEqual(w.getMasterFingerprintHex(), '00000000');
|
||||
}
|
||||
});
|
||||
|
||||
it('can fetch balance & transactions from zpub HD', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP');
|
||||
await w.fetchBalance();
|
||||
@ -117,7 +149,6 @@ describe('Watch only wallet', () => {
|
||||
});
|
||||
|
||||
it('can import coldcard/electrum compatible JSON skeleton wallet, and create a tx with master fingerprint', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120 * 1000;
|
||||
const skeleton =
|
||||
'{"keystore": {"ckcc_xpub": "xpub661MyMwAqRbcGmUDQVKxmhEESB5xTk8hbsdTSV3Pmhm3HE9Fj3s45R9Y8LwyaQWjXXPytZjuhTKSyCBPeNrB1VVWQq1HCvjbEZ27k44oNmg", "xpub": "zpub6rFDtF1nuXZ9PUL4XzKURh3vJBW6Kj6TUrYL4qPtFNtDXtcTVfiqjQDyrZNwjwzt5HS14qdqo3Co2282Lv3Re6Y5wFZxAVuMEpeygnnDwfx", "label": "Coldcard Import 168DD603", "ckcc_xfp": 64392470, "type": "hardware", "hw_type": "coldcard", "derivation": "m/84\'/0\'/0\'"}, "wallet_type": "standard", "use_encryption": false, "seed_version": 17}';
|
||||
let w = new WatchOnlyWallet();
|
||||
@ -175,7 +206,6 @@ describe('Watch only wallet', () => {
|
||||
});
|
||||
|
||||
it('can fetch balance & transactions from ypub HD', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('ypub6Y9u3QCRC1HkZv3stNxcQVwmw7vC7KX5Ldz38En5P88RQbesP2oy16hNyQocVCfYRQPxdHcd3pmu9AFhLv7NdChWmw5iNLryZ2U6EEHdnfo');
|
||||
await w.fetchBalance();
|
||||
@ -186,7 +216,6 @@ describe('Watch only wallet', () => {
|
||||
});
|
||||
|
||||
it('can fetch balance & transactions from xpub HD', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps');
|
||||
await w.fetchBalance();
|
||||
@ -197,7 +226,6 @@ describe('Watch only wallet', () => {
|
||||
});
|
||||
|
||||
it('can fetch large HD', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 500 * 1000;
|
||||
let w = new WatchOnlyWallet();
|
||||
w.setSecret('ypub6WnnYxkQCGeowv4BXq9Y9PHaXgHMJg9TkFaDJkunhcTAfbDw8z3LvV9kFNHGjeVaEoGdsSJgaMWpUBvYvpYGMJd43gTK5opecVVkvLwKttx');
|
||||
await w.fetchBalance();
|
||||
|
Loading…
Reference in New Issue
Block a user