marcosrdz
5 years ago
41 changed files with 2471 additions and 1545 deletions
@ -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 |
version: 2 |
||||
jobs: |
jobs: |
||||
build: |
build: |
||||
docker: |
docker: |
||||
# specify the version you desire here |
- image: circleci/node:10.16.3 |
||||
- 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 |
|
||||
|
|
||||
working_directory: ~/repo |
working_directory: ~/repo |
||||
|
|
||||
steps: |
steps: |
||||
- checkout |
- checkout |
||||
|
|
||||
|
- restore_cache: |
||||
|
key: node_modules-{{ checksum "package-lock.json" }} |
||||
|
|
||||
- run: npm i |
- run: npm i |
||||
|
|
||||
|
- save_cache: |
||||
|
key: node_modules-{{ checksum "package-lock.json" }} |
||||
|
paths: |
||||
|
- node_modules |
||||
|
|
||||
# run tests! |
# run tests! |
||||
- run: npm run test |
- run: npm run test |
||||
|
File diff suppressed because it is too large
@ -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, |
|
||||
}), |
|
||||
}), |
|
||||
}), |
|
||||
}; |
|
@ -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); |
||||
|
}); |
||||
|
}); |
Loading…
Reference in new issue