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 |
|||
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 |
|||
|
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