Nuno Coelho
5 years ago
committed by
GitHub
58 changed files with 1584 additions and 2247 deletions
@ -1,59 +0,0 @@ |
|||||
import { LegacyWallet } from './legacy-wallet'; |
|
||||
const bitcoin = require('bitcoinjs-lib'); |
|
||||
|
|
||||
export class SegwitBech32Wallet extends LegacyWallet { |
|
||||
static type = 'segwitBech32'; |
|
||||
static typeReadable = 'P2 WPKH'; |
|
||||
|
|
||||
getAddress() { |
|
||||
if (this._address) return this._address; |
|
||||
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; |
|
||||
} catch (err) { |
|
||||
return false; |
|
||||
} |
|
||||
this._address = address; |
|
||||
|
|
||||
return this._address; |
|
||||
} |
|
||||
|
|
||||
static witnessToAddress(witness) { |
|
||||
const pubKey = Buffer.from(witness, 'hex'); |
|
||||
return bitcoin.payments.p2wpkh({ |
|
||||
pubkey: pubKey, |
|
||||
network: bitcoin.networks.bitcoin, |
|
||||
}).address; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Converts script pub key to bech32 address if it can. Returns FALSE if it cant. |
|
||||
* |
|
||||
* @param scriptPubKey |
|
||||
* @returns {boolean|string} Either bech32 address or false |
|
||||
*/ |
|
||||
static scriptPubKeyToAddress(scriptPubKey) { |
|
||||
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); |
|
||||
let ret; |
|
||||
try { |
|
||||
ret = bitcoin.payments.p2wpkh({ |
|
||||
output: scriptPubKey2, |
|
||||
network: bitcoin.networks.bitcoin, |
|
||||
}).address; |
|
||||
} catch (_) { |
|
||||
return false; |
|
||||
} |
|
||||
return ret; |
|
||||
} |
|
||||
|
|
||||
allowSend() { |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,149 @@ |
|||||
|
import { LegacyWallet } from './legacy-wallet'; |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
const coinSelectAccumulative = require('coinselect/accumulative'); |
||||
|
const coinSelectSplit = require('coinselect/split'); |
||||
|
|
||||
|
export class SegwitBech32Wallet extends LegacyWallet { |
||||
|
static type = 'segwitBech32'; |
||||
|
static typeReadable = 'P2 WPKH'; |
||||
|
|
||||
|
getAddress() { |
||||
|
if (this._address) return this._address; |
||||
|
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; |
||||
|
} catch (err) { |
||||
|
return false; |
||||
|
} |
||||
|
this._address = address; |
||||
|
|
||||
|
return this._address; |
||||
|
} |
||||
|
|
||||
|
static witnessToAddress(witness) { |
||||
|
const pubKey = Buffer.from(witness, 'hex'); |
||||
|
return bitcoin.payments.p2wpkh({ |
||||
|
pubkey: pubKey, |
||||
|
network: bitcoin.networks.bitcoin, |
||||
|
}).address; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Converts script pub key to bech32 address if it can. Returns FALSE if it cant. |
||||
|
* |
||||
|
* @param scriptPubKey |
||||
|
* @returns {boolean|string} Either bech32 address or false |
||||
|
*/ |
||||
|
static scriptPubKeyToAddress(scriptPubKey) { |
||||
|
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); |
||||
|
let ret; |
||||
|
try { |
||||
|
ret = bitcoin.payments.p2wpkh({ |
||||
|
output: scriptPubKey2, |
||||
|
network: bitcoin.networks.bitcoin, |
||||
|
}).address; |
||||
|
} catch (_) { |
||||
|
return false; |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String, txhex: String, }>} List of spendable utxos |
||||
|
* @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) |
||||
|
* @param feeRate {Number} satoshi per byte |
||||
|
* @param changeAddress {String} Excessive coins will go back to that address |
||||
|
* @param sequence {Number} Used in RBF |
||||
|
* @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case |
||||
|
* @param masterFingerprint {number} Decimal number of wallet's master fingerprint |
||||
|
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} |
||||
|
*/ |
||||
|
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { |
||||
|
if (!changeAddress) throw new Error('No change address provided'); |
||||
|
sequence = sequence || 0xffffffff; // disable RBF by default
|
||||
|
|
||||
|
let algo = coinSelectAccumulative; |
||||
|
if (targets.length === 1 && targets[0] && !targets[0].value) { |
||||
|
// we want to send MAX
|
||||
|
algo = coinSelectSplit; |
||||
|
} |
||||
|
|
||||
|
let { inputs, outputs, fee } = algo(utxos, targets, feeRate); |
||||
|
|
||||
|
// .inputs and .outputs will be undefined if no solution was found
|
||||
|
if (!inputs || !outputs) { |
||||
|
throw new Error('Not enough balance. Try sending smaller amount'); |
||||
|
} |
||||
|
|
||||
|
let psbt = new bitcoin.Psbt(); |
||||
|
|
||||
|
let c = 0; |
||||
|
let values = {}; |
||||
|
let keyPair; |
||||
|
|
||||
|
inputs.forEach(input => { |
||||
|
if (!skipSigning) { |
||||
|
// skiping signing related stuff
|
||||
|
keyPair = bitcoin.ECPair.fromWIF(this.secret); // secret is WIF
|
||||
|
} |
||||
|
values[c] = input.value; |
||||
|
c++; |
||||
|
|
||||
|
const pubkey = keyPair.publicKey; |
||||
|
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); |
||||
|
|
||||
|
psbt.addInput({ |
||||
|
hash: input.txid, |
||||
|
index: input.vout, |
||||
|
sequence, |
||||
|
witnessUtxo: { |
||||
|
script: p2wpkh.output, |
||||
|
value: input.value, |
||||
|
}, |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
outputs.forEach(output => { |
||||
|
// if output has no address - this is change output
|
||||
|
if (!output.address) { |
||||
|
output.address = changeAddress; |
||||
|
} |
||||
|
|
||||
|
let outputData = { |
||||
|
address: output.address, |
||||
|
value: output.value, |
||||
|
}; |
||||
|
|
||||
|
psbt.addOutput(outputData); |
||||
|
}); |
||||
|
|
||||
|
if (!skipSigning) { |
||||
|
// skiping signing related stuff
|
||||
|
for (let cc = 0; cc < c; cc++) { |
||||
|
psbt.signInput(cc, keyPair); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let tx; |
||||
|
if (!skipSigning) { |
||||
|
tx = psbt.finalizeAllInputs().extractTransaction(); |
||||
|
} |
||||
|
return { tx, inputs, outputs, fee, psbt }; |
||||
|
} |
||||
|
|
||||
|
allowSend() { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
allowSendMax() { |
||||
|
return true; |
||||
|
} |
||||
|
} |
@ -1,342 +0,0 @@ |
|||||
/** |
|
||||
* Cashier-BTC |
|
||||
* ----------- |
|
||||
* Self-hosted bitcoin payment gateway |
|
||||
* |
|
||||
* https://github.com/Overtorment/Cashier-BTC
|
|
||||
* |
|
||||
**/ |
|
||||
const bitcoinjs = require('bitcoinjs-lib'); |
|
||||
const _p2wpkh = bitcoinjs.payments.p2wpkh; |
|
||||
const _p2sh = bitcoinjs.payments.p2sh; |
|
||||
const toSatoshi = num => parseInt((num * 100000000).toFixed(0)); |
|
||||
|
|
||||
exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) { |
|
||||
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); |
|
||||
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
|
|
||||
let txb = new bitcoinjs.TransactionBuilder(); |
|
||||
txb.setVersion(1); |
|
||||
let unspentAmountSatoshi = 0; |
|
||||
let ourOutputs = {}; |
|
||||
let outputNum = 0; |
|
||||
for (const unspent of utxos) { |
|
||||
if (unspent.confirmations < 1) { |
|
||||
// using only confirmed outputs
|
|
||||
continue; |
|
||||
} |
|
||||
txb.addInput(unspent.txid, unspent.vout); |
|
||||
ourOutputs[outputNum] = ourOutputs[outputNum] || {}; |
|
||||
ourOutputs[outputNum].keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif); |
|
||||
unspentAmountSatoshi += unspent.amount; |
|
||||
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) { |
|
||||
// found enough inputs to satisfy payee and pay fees
|
|
||||
break; |
|
||||
} |
|
||||
outputNum++; |
|
||||
} |
|
||||
if (unspentAmountSatoshi < amountToOutputSatoshi + feeInSatoshis) { |
|
||||
throw new Error('Not enough balance. Please, try sending a smaller amount.'); |
|
||||
} |
|
||||
|
|
||||
// adding outputs
|
|
||||
|
|
||||
txb.addOutput(toAddress, amountToOutputSatoshi); |
|
||||
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) { |
|
||||
// sending less than we have, so the rest should go back
|
|
||||
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) { |
|
||||
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
||||
// if not - we just dont send change and it wil add to fee
|
|
||||
txb.addOutput(changeAddress, unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// now, signing every input with a corresponding key
|
|
||||
|
|
||||
for (let c = 0; c <= outputNum; c++) { |
|
||||
txb.sign({ |
|
||||
prevOutScriptType: 'p2pkh', |
|
||||
vin: c, |
|
||||
keyPair: ourOutputs[c].keyPair, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
let tx = txb.build(); |
|
||||
return tx.toHex(); |
|
||||
}; |
|
||||
|
|
||||
exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) { |
|
||||
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); |
|
||||
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
|
|
||||
let psbt = new bitcoinjs.Psbt(); |
|
||||
psbt.setVersion(1); |
|
||||
let unspentAmountSatoshi = 0; |
|
||||
let ourOutputs = []; |
|
||||
let outputNum = 0; |
|
||||
for (const unspent of utxos) { |
|
||||
if (unspent.confirmations < 1) { |
|
||||
// using only confirmed outputs
|
|
||||
continue; |
|
||||
} |
|
||||
let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif); |
|
||||
let p2wpkh = _p2wpkh({ |
|
||||
pubkey: keyPair.publicKey, |
|
||||
}); |
|
||||
let p2sh = _p2sh({ |
|
||||
redeem: p2wpkh, |
|
||||
}); |
|
||||
psbt.addInput({ |
|
||||
hash: unspent.txid, |
|
||||
index: unspent.vout, |
|
||||
witnessUtxo: { |
|
||||
script: p2sh.output, |
|
||||
value: unspent.amount, |
|
||||
}, |
|
||||
redeemScript: p2wpkh.output, |
|
||||
}); |
|
||||
ourOutputs[outputNum] = ourOutputs[outputNum] || {}; |
|
||||
ourOutputs[outputNum].keyPair = keyPair; |
|
||||
ourOutputs[outputNum].redeemScript = p2wpkh.output; |
|
||||
ourOutputs[outputNum].amount = unspent.amount; |
|
||||
unspentAmountSatoshi += unspent.amount; |
|
||||
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) { |
|
||||
// found enough inputs to satisfy payee and pay fees
|
|
||||
break; |
|
||||
} |
|
||||
outputNum++; |
|
||||
} |
|
||||
|
|
||||
if (unspentAmountSatoshi < amountToOutputSatoshi + feeInSatoshis) { |
|
||||
throw new Error('Not enough balance. Please, try sending a smaller amount.'); |
|
||||
} |
|
||||
|
|
||||
// adding outputs
|
|
||||
|
|
||||
psbt.addOutput({ |
|
||||
address: toAddress, |
|
||||
value: amountToOutputSatoshi, |
|
||||
}); |
|
||||
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) { |
|
||||
// sending less than we have, so the rest should go back
|
|
||||
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) { |
|
||||
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
||||
// if not - we just dont send change and it wil add to fee
|
|
||||
psbt.addOutput({ |
|
||||
address: changeAddress, |
|
||||
value: unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis, |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// now, signing every input with a corresponding key
|
|
||||
|
|
||||
for (let c = 0; c <= outputNum; c++) { |
|
||||
psbt.signInput(c, ourOutputs[c].keyPair); |
|
||||
} |
|
||||
|
|
||||
let tx = psbt.finalizeAllInputs().extractTransaction(); |
|
||||
return tx.toHex(); |
|
||||
}; |
|
||||
|
|
||||
exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, WIF, changeAddress, sequence) { |
|
||||
changeAddress = changeAddress || exports.WIF2segwitAddress(WIF); |
|
||||
if (sequence === undefined) { |
|
||||
sequence = bitcoinjs.Transaction.DEFAULT_SEQUENCE; |
|
||||
} |
|
||||
|
|
||||
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0)); |
|
||||
let keyPair = bitcoinjs.ECPair.fromWIF(WIF); |
|
||||
let p2wpkh = _p2wpkh({ |
|
||||
pubkey: keyPair.publicKey, |
|
||||
}); |
|
||||
let p2sh = _p2sh({ |
|
||||
redeem: p2wpkh, |
|
||||
}); |
|
||||
|
|
||||
let psbt = new bitcoinjs.Psbt(); |
|
||||
psbt.setVersion(1); |
|
||||
let unspentAmount = 0; |
|
||||
for (const unspent of utxos) { |
|
||||
if (unspent.confirmations < 2) { |
|
||||
// using only confirmed outputs
|
|
||||
continue; |
|
||||
} |
|
||||
const satoshis = parseInt((unspent.amount * 100000000).toFixed(0)); |
|
||||
psbt.addInput({ |
|
||||
hash: unspent.txid, |
|
||||
index: unspent.vout, |
|
||||
sequence, |
|
||||
witnessUtxo: { |
|
||||
script: p2sh.output, |
|
||||
value: satoshis, |
|
||||
}, |
|
||||
redeemScript: p2wpkh.output, |
|
||||
}); |
|
||||
unspentAmount += satoshis; |
|
||||
} |
|
||||
let amountToOutput = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); |
|
||||
psbt.addOutput({ |
|
||||
address: toAddress, |
|
||||
value: amountToOutput, |
|
||||
}); |
|
||||
if (amountToOutput + feeInSatoshis < unspentAmount) { |
|
||||
// sending less than we have, so the rest should go back
|
|
||||
|
|
||||
if (unspentAmount - amountToOutput - feeInSatoshis > 3 * feeInSatoshis) { |
|
||||
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
||||
// if not - we just dont send change and it wil add to fee
|
|
||||
psbt.addOutput({ |
|
||||
address: changeAddress, |
|
||||
value: unspentAmount - amountToOutput - feeInSatoshis, |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
for (let c = 0; c < utxos.length; c++) { |
|
||||
psbt.signInput(c, keyPair); |
|
||||
} |
|
||||
|
|
||||
let tx = psbt.finalizeAllInputs().extractTransaction(); |
|
||||
return tx.toHex(); |
|
||||
}; |
|
||||
|
|
||||
exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta, WIF, utxodata) { |
|
||||
if (feeDelta < 0) { |
|
||||
throw Error('replace-by-fee requires increased fee, not decreased'); |
|
||||
} |
|
||||
|
|
||||
let tx = bitcoinjs.Transaction.fromHex(txhex); |
|
||||
|
|
||||
// looking for latest sequence number in inputs
|
|
||||
let highestSequence = 0; |
|
||||
for (let i of tx.ins) { |
|
||||
if (i.sequence > highestSequence) { |
|
||||
highestSequence = i.sequence; |
|
||||
} |
|
||||
} |
|
||||
let keyPair = bitcoinjs.ECPair.fromWIF(WIF); |
|
||||
let p2wpkh = _p2wpkh({ |
|
||||
pubkey: keyPair.publicKey, |
|
||||
}); |
|
||||
let p2sh = _p2sh({ |
|
||||
redeem: p2wpkh, |
|
||||
}); |
|
||||
|
|
||||
// creating TX
|
|
||||
let psbt = new bitcoinjs.Psbt(); |
|
||||
psbt.setVersion(1); |
|
||||
for (let unspent of tx.ins) { |
|
||||
let txid = Buffer.from(unspent.hash) |
|
||||
.reverse() |
|
||||
.toString('hex'); |
|
||||
let index = unspent.index; |
|
||||
let amount = utxodata[txid][index]; |
|
||||
psbt.addInput({ |
|
||||
hash: txid, |
|
||||
index, |
|
||||
sequence: highestSequence + 1, |
|
||||
witnessUtxo: { |
|
||||
script: p2sh.output, |
|
||||
value: amount, |
|
||||
}, |
|
||||
redeemScript: p2wpkh.output, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
for (let o of tx.outs) { |
|
||||
let outAddress = bitcoinjs.address.fromOutputScript(o.script); |
|
||||
if (addressReplaceMap[outAddress]) { |
|
||||
// means this is DESTINATION address, not messing with it's amount
|
|
||||
// but replacing the address itseld
|
|
||||
psbt.addOutput({ |
|
||||
address: addressReplaceMap[outAddress], |
|
||||
value: o.value, |
|
||||
}); |
|
||||
} else { |
|
||||
// CHANGE address, so we deduct increased fee from here
|
|
||||
let feeDeltaInSatoshi = parseInt((feeDelta * 100000000).toFixed(0)); |
|
||||
psbt.addOutput({ |
|
||||
address: outAddress, |
|
||||
value: o.value - feeDeltaInSatoshi, |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// signing
|
|
||||
for (let c = 0; c < tx.ins.length; c++) { |
|
||||
psbt.signInput(c, keyPair); |
|
||||
} |
|
||||
|
|
||||
let newTx = psbt.finalizeAllInputs().extractTransaction(); |
|
||||
return newTx.toHex(); |
|
||||
}; |
|
||||
|
|
||||
exports.generateNewSegwitAddress = function() { |
|
||||
let keyPair = bitcoinjs.ECPair.makeRandom(); |
|
||||
let address = bitcoinjs.payments.p2sh({ |
|
||||
redeem: bitcoinjs.payments.p2wpkh({ |
|
||||
pubkey: keyPair.publicKey, |
|
||||
}), |
|
||||
}).address; |
|
||||
|
|
||||
return { |
|
||||
address: address, |
|
||||
WIF: keyPair.toWIF(), |
|
||||
}; |
|
||||
}; |
|
||||
|
|
||||
exports.URI = function(paymentInfo) { |
|
||||
let uri = 'bitcoin:'; |
|
||||
uri += paymentInfo.address; |
|
||||
uri += '?amount='; |
|
||||
uri += parseFloat(paymentInfo.amount / 100000000); |
|
||||
uri += '&message='; |
|
||||
uri += encodeURIComponent(paymentInfo.message); |
|
||||
if (paymentInfo.label) { |
|
||||
uri += '&label='; |
|
||||
uri += encodeURIComponent(paymentInfo.label); |
|
||||
} |
|
||||
|
|
||||
return uri; |
|
||||
}; |
|
||||
|
|
||||
exports.WIF2segwitAddress = function(WIF) { |
|
||||
let keyPair = bitcoinjs.ECPair.fromWIF(WIF); |
|
||||
return bitcoinjs.payments.p2sh({ |
|
||||
redeem: bitcoinjs.payments.p2wpkh({ |
|
||||
pubkey: keyPair.publicKey, |
|
||||
}), |
|
||||
}).address; |
|
||||
}; |
|
||||
|
|
||||
exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, fromAddress) { |
|
||||
let fixedFee = toSatoshi(_fixedFee); |
|
||||
let amountToOutput = toSatoshi(_amount - _fixedFee); |
|
||||
let pk = bitcoinjs.ECPair.fromWIF(WIF); // eslint-disable-line new-cap
|
|
||||
let txb = new bitcoinjs.TransactionBuilder(); |
|
||||
txb.setVersion(1); |
|
||||
let unspentAmount = 0; |
|
||||
for (const unspent of utxos) { |
|
||||
if (unspent.confirmations < 2) { |
|
||||
// using only confirmed outputs
|
|
||||
continue; |
|
||||
} |
|
||||
txb.addInput(unspent.txid, unspent.vout); |
|
||||
unspentAmount += toSatoshi(unspent.amount); |
|
||||
} |
|
||||
txb.addOutput(toAddress, amountToOutput); |
|
||||
|
|
||||
if (amountToOutput + fixedFee < unspentAmount) { |
|
||||
// sending less than we have, so the rest should go back
|
|
||||
txb.addOutput(fromAddress, unspentAmount - amountToOutput - fixedFee); |
|
||||
} |
|
||||
|
|
||||
for (let c = 0; c < utxos.length; c++) { |
|
||||
txb.sign({ |
|
||||
prevOutScriptType: 'p2pkh', |
|
||||
vin: c, |
|
||||
keyPair: pk, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return txb.build().toHex(); |
|
||||
}; |
|
@ -0,0 +1,162 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { ActivityIndicator, Linking, StyleSheet, View, KeyboardAvoidingView, Platform, Text, TextInput } from 'react-native'; |
||||
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; |
||||
|
import { HDSegwitBech32Wallet } from '../../class'; |
||||
|
import { |
||||
|
SafeBlueArea, |
||||
|
BlueCard, |
||||
|
BlueButton, |
||||
|
BlueSpacing10, |
||||
|
BlueSpacing20, |
||||
|
BlueFormLabel, |
||||
|
BlueTextCentered, |
||||
|
BlueBigCheckmark, |
||||
|
} from '../../BlueComponents'; |
||||
|
import BlueElectrum from '../../BlueElectrum'; |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
|
||||
|
const BROADCAST_RESULT = Object.freeze({ |
||||
|
none: 'Input transaction hash', |
||||
|
pending: 'pending', |
||||
|
success: 'success', |
||||
|
error: 'error', |
||||
|
}); |
||||
|
|
||||
|
export default function Broadcast() { |
||||
|
const [tx, setTx] = useState(''); |
||||
|
const [txHex, setTxHex] = useState(''); |
||||
|
const [broadcastResult, setBroadcastResult] = useState(BROADCAST_RESULT.none); |
||||
|
const handleUpdateTxHex = nextValue => setTxHex(nextValue.trim()); |
||||
|
const handleBroadcast = async () => { |
||||
|
setBroadcastResult(BROADCAST_RESULT.pending); |
||||
|
try { |
||||
|
await BlueElectrum.ping(); |
||||
|
await BlueElectrum.waitTillConnected(); |
||||
|
const walletObj = new HDSegwitBech32Wallet(); |
||||
|
const result = await walletObj.broadcastTx(txHex); |
||||
|
if (result) { |
||||
|
let tx = bitcoin.Transaction.fromHex(txHex); |
||||
|
const txid = tx.getId(); |
||||
|
setTx(txid); |
||||
|
setBroadcastResult(BROADCAST_RESULT.success); |
||||
|
} else { |
||||
|
setBroadcastResult(BROADCAST_RESULT.error); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); |
||||
|
setBroadcastResult(BROADCAST_RESULT.error); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<SafeBlueArea style={styles.blueArea}> |
||||
|
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null} keyboardShouldPersistTaps="handled"> |
||||
|
<View style={styles.wrapper}> |
||||
|
{BROADCAST_RESULT.success !== broadcastResult && ( |
||||
|
<BlueCard style={styles.mainCard}> |
||||
|
<View style={styles.topFormRow}> |
||||
|
<BlueFormLabel>{broadcastResult}</BlueFormLabel> |
||||
|
{BROADCAST_RESULT.pending === broadcastResult && <ActivityIndicator size="small" />} |
||||
|
</View> |
||||
|
<TextInput |
||||
|
style={{ |
||||
|
flex: 1, |
||||
|
borderColor: '#ebebeb', |
||||
|
backgroundColor: '#d2f8d6', |
||||
|
borderRadius: 4, |
||||
|
marginTop: 20, |
||||
|
color: '#37c0a1', |
||||
|
fontWeight: '500', |
||||
|
fontSize: 14, |
||||
|
paddingHorizontal: 16, |
||||
|
paddingBottom: 16, |
||||
|
paddingTop: 16, |
||||
|
}} |
||||
|
maxHeight={100} |
||||
|
minHeight={100} |
||||
|
maxWidth={'100%'} |
||||
|
minWidth={'100%'} |
||||
|
multiline |
||||
|
editable |
||||
|
value={txHex} |
||||
|
onChangeText={handleUpdateTxHex} |
||||
|
/> |
||||
|
|
||||
|
<BlueSpacing10 /> |
||||
|
<BlueButton title="BROADCAST" onPress={handleBroadcast} disabled={broadcastResult === BROADCAST_RESULT.pending} /> |
||||
|
</BlueCard> |
||||
|
)} |
||||
|
{BROADCAST_RESULT.success === broadcastResult && <SuccessScreen tx={tx} />} |
||||
|
</View> |
||||
|
</KeyboardAvoidingView> |
||||
|
</SafeBlueArea> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
const styles = StyleSheet.create({ |
||||
|
wrapper: { |
||||
|
marginTop: 16, |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'flex-start', |
||||
|
}, |
||||
|
blueArea: { |
||||
|
flex: 1, |
||||
|
paddingTop: 19, |
||||
|
}, |
||||
|
broadcastResultWrapper: { |
||||
|
flex: 1, |
||||
|
flexDirection: 'column', |
||||
|
justifyContent: 'center', |
||||
|
alignItems: 'center', |
||||
|
height: '100%', |
||||
|
width: '100%', |
||||
|
}, |
||||
|
link: { |
||||
|
color: 'blue', |
||||
|
}, |
||||
|
mainCard: { |
||||
|
padding: 0, |
||||
|
display: 'flex', |
||||
|
flexDirection: 'column', |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'flex-start', |
||||
|
}, |
||||
|
topFormRow: { |
||||
|
flex: 0.1, |
||||
|
flexBasis: 0.1, |
||||
|
flexDirection: 'row', |
||||
|
alignItems: 'center', |
||||
|
justifyContent: 'space-between', |
||||
|
paddingBottom: 10, |
||||
|
paddingTop: 0, |
||||
|
paddingRight: 100, |
||||
|
height: 30, |
||||
|
maxHeight: 30, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
function SuccessScreen({ tx }) { |
||||
|
if (!tx) { |
||||
|
return null; |
||||
|
} |
||||
|
return ( |
||||
|
<View style={styles.wrapper}> |
||||
|
<BlueCard> |
||||
|
<View style={styles.broadcastResultWrapper}> |
||||
|
<BlueBigCheckmark /> |
||||
|
<BlueSpacing20 /> |
||||
|
<BlueTextCentered>Success! You transaction has been broadcasted!</BlueTextCentered> |
||||
|
<BlueSpacing10 /> |
||||
|
<Text style={styles.link} onPress={() => Linking.openURL(`https://blockstream.info/tx/${tx}`)}> |
||||
|
Open link in explorer |
||||
|
</Text> |
||||
|
</View> |
||||
|
</BlueCard> |
||||
|
</View> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
SuccessScreen.propTypes = { |
||||
|
tx: PropTypes.string.isRequired, |
||||
|
}; |
@ -1,430 +1,95 @@ |
|||||
import React, { Component } from 'react'; |
import React, { useEffect, useState, useCallback } from 'react'; |
||||
import { ActivityIndicator, View, BackHandler, Text } from 'react-native'; |
import { ActivityIndicator, View, BackHandler, Text, ScrollView } from 'react-native'; |
||||
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText, BlueButton } from '../../BlueComponents'; |
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText, BlueButton } from '../../BlueComponents'; |
||||
import { Badge } from 'react-native-elements'; |
import { Badge } from 'react-native-elements'; |
||||
import PropTypes from 'prop-types'; |
|
||||
import Privacy from '../../Privacy'; |
import Privacy from '../../Privacy'; |
||||
import { ScrollView } from 'react-native-gesture-handler'; |
import { useNavigation, useNavigationParam } from 'react-navigation-hooks'; |
||||
let loc = require('../../loc'); |
const loc = require('../../loc'); |
||||
|
|
||||
export default class PleaseBackup extends Component { |
const PleaseBackup = () => { |
||||
static navigationOptions = ({ navigation }) => ({ |
const [isLoading, setIsLoading] = useState(true); |
||||
...BlueNavigationStyle(navigation, true), |
const words = useNavigationParam('secret').split(' '); |
||||
title: loc.pleasebackup.title, |
const { dismiss } = useNavigation(); |
||||
headerLeft: null, |
|
||||
headerRight: null, |
|
||||
}); |
|
||||
|
|
||||
constructor(props) { |
const handleBackButton = useCallback(() => { |
||||
super(props); |
dismiss(); |
||||
|
|
||||
this.state = { |
|
||||
isLoading: true, |
|
||||
words: props.navigation.state.params.secret.split(' '), |
|
||||
}; |
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this)); |
|
||||
} |
|
||||
|
|
||||
handleBackButton() { |
|
||||
this.props.navigation.dismiss(); |
|
||||
return true; |
return true; |
||||
} |
}, [dismiss]); |
||||
|
|
||||
componentDidMount() { |
useEffect(() => { |
||||
Privacy.enableBlur(); |
Privacy.enableBlur(); |
||||
this.setState({ |
setIsLoading(false); |
||||
isLoading: false, |
return () => { |
||||
}); |
Privacy.disableBlur(); |
||||
} |
BackHandler.removeEventListener('hardwareBackPress', handleBackButton); |
||||
|
}; |
||||
componentWillUnmount() { |
}, [handleBackButton, words]); |
||||
Privacy.disableBlur(); |
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this)); |
|
||||
} |
|
||||
|
|
||||
render() { |
const renderSecret = () => { |
||||
if (this.state.isLoading) { |
let component = []; |
||||
return ( |
for (const [index, secret] of words.entries()) { |
||||
<View style={{ flex: 1, paddingTop: 20 }}> |
component.push( |
||||
<ActivityIndicator /> |
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }} key={`${secret}${index}`}> |
||||
</View> |
<Badge |
||||
|
containerStyle={{ |
||||
|
backgroundColor: '#f5f5f5', |
||||
|
paddingTop: 6, |
||||
|
paddingBottom: 6, |
||||
|
paddingLeft: 8, |
||||
|
paddingRight: 8, |
||||
|
borderRadius: 4, |
||||
|
}} |
||||
|
> |
||||
|
<Text style={{ color: '#81868E', fontWeight: 'bold' }}> |
||||
|
{`${index}`}. {secret} |
||||
|
</Text> |
||||
|
</Badge> |
||||
|
</View>, |
||||
); |
); |
||||
} |
} |
||||
|
return component; |
||||
|
}; |
||||
|
|
||||
return ( |
return isLoading ? ( |
||||
<SafeBlueArea style={{ flex: 1 }}> |
<View style={{ flex: 1, paddingTop: 20 }}> |
||||
<ScrollView contentContainerStyle={{ justifyContent: 'space-between' }} testID="PleaseBackupScrollView"> |
<ActivityIndicator /> |
||||
<View style={{ alignItems: 'center', paddingHorizontal: 16 }}> |
</View> |
||||
<BlueText style={{ textAlign: 'center', fontWeight: 'bold', color: '#0C2550' }}>{loc.pleasebackup.success}</BlueText> |
) : ( |
||||
<BlueText style={{ paddingBottom: 10, paddingRight: 0, paddingLeft: 0, color: '#0C2550' }}>{loc.pleasebackup.text}</BlueText> |
<SafeBlueArea style={{ flex: 1 }}> |
||||
|
<ScrollView contentContainerStyle={{ justifyContent: 'space-between' }} testID="PleaseBackupScrollView"> |
||||
|
<View style={{ alignItems: 'center', paddingHorizontal: 16 }}> |
||||
|
<BlueText style={{ textAlign: 'center', fontWeight: 'bold', color: '#0C2550' }}>{loc.pleasebackup.success}</BlueText> |
||||
|
<BlueText style={{ paddingBottom: 10, paddingRight: 0, paddingLeft: 0, color: '#0C2550' }}>{loc.pleasebackup.text}</BlueText> |
||||
|
|
||||
<View |
<View |
||||
style={{ |
style={{ |
||||
flex: 1, |
flex: 1, |
||||
flexDirection: 'row', |
flexDirection: 'row', |
||||
justifyContent: 'center', |
justifyContent: 'center', |
||||
flexWrap: 'wrap', |
flexWrap: 'wrap', |
||||
marginTop: 14, |
marginTop: 14, |
||||
}} |
}} |
||||
> |
> |
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
{renderSecret()} |
||||
<Badge |
</View> |
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>1. {this.state.words[0]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>2. {this.state.words[1]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>3. {this.state.words[2]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>4. {this.state.words[3]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>5. {this.state.words[4]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>6. {this.state.words[5]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>7. {this.state.words[6]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>8. {this.state.words[7]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>9. {this.state.words[8]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>10. {this.state.words[9]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>11. {this.state.words[10]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>12. {this.state.words[11]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>13. {this.state.words[12]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>14. {this.state.words[13]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>15. {this.state.words[14]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>16. {this.state.words[15]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>17. {this.state.words[16]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>18. {this.state.words[17]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>19. {this.state.words[18]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>20. {this.state.words[19]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>21. {this.state.words[20]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>22. {this.state.words[21]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>23. {this.state.words[22]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
<View style={{ width: 'auto', marginRight: 8, marginBottom: 8 }}> |
|
||||
<Badge |
|
||||
containerStyle={{ |
|
||||
backgroundColor: '#f5f5f5', |
|
||||
paddingTop: 6, |
|
||||
paddingBottom: 6, |
|
||||
paddingLeft: 8, |
|
||||
paddingRight: 8, |
|
||||
borderRadius: 4, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ color: '#81868E', fontWeight: 'bold' }}>24. {this.state.words[23]}</Text> |
|
||||
</Badge> |
|
||||
</View> |
|
||||
</View> |
|
||||
|
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap' }}> |
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap' }}> |
||||
<View style={{ flex: 1 }}> |
<View style={{ flex: 1 }}> |
||||
<BlueSpacing20 /> |
<BlueSpacing20 /> |
||||
<BlueButton testID="PleasebackupOk" onPress={() => this.props.navigation.dismiss()} title={loc.pleasebackup.ok} /> |
<BlueButton testID="PleasebackupOk" onPress={dismiss} title={loc.pleasebackup.ok} /> |
||||
</View> |
|
||||
</View> |
</View> |
||||
</View> |
</View> |
||||
</ScrollView> |
</View> |
||||
</SafeBlueArea> |
</ScrollView> |
||||
); |
</SafeBlueArea> |
||||
} |
); |
||||
} |
|
||||
|
|
||||
PleaseBackup.propTypes = { |
|
||||
navigation: PropTypes.shape({ |
|
||||
state: PropTypes.shape({ |
|
||||
params: PropTypes.shape({ |
|
||||
secret: PropTypes.string, |
|
||||
}), |
|
||||
}), |
|
||||
dismiss: PropTypes.func, |
|
||||
}), |
|
||||
}; |
}; |
||||
|
|
||||
|
PleaseBackup.navigationOptions = ({ navigation }) => ({ |
||||
|
...BlueNavigationStyle(navigation, true), |
||||
|
title: loc.pleasebackup.title, |
||||
|
headerLeft: null, |
||||
|
headerRight: null, |
||||
|
}); |
||||
|
|
||||
|
export default PleaseBackup; |
||||
|
@ -0,0 +1,27 @@ |
|||||
|
/* global it */ |
||||
|
import { HDLegacyBreadwalletWallet } from '../../class'; |
||||
|
const assert = require('assert'); |
||||
|
|
||||
|
it('Legacy HD Breadwallet works', async () => { |
||||
|
let hdBread = new HDLegacyBreadwalletWallet(); |
||||
|
hdBread.setSecret(process.env.HD_MNEMONIC_BREAD); |
||||
|
|
||||
|
assert.strictEqual(hdBread.validateMnemonic(), true); |
||||
|
assert.strictEqual(hdBread._getExternalAddressByIndex(0), '1ARGkNMdsBE36fJhddSwf8PqBXG3s4d2KU'); |
||||
|
assert.strictEqual(hdBread._getInternalAddressByIndex(0), '1JLvA5D7RpWgChb4A5sFcLNrfxYbyZdw3V'); |
||||
|
assert.strictEqual(hdBread._getExternalWIFByIndex(0), 'L25CoHfqWKR5byQhgp4M8sW1roifBteD3Lj3zCGNcV4JXhbxZ93F'); |
||||
|
assert.strictEqual(hdBread._getInternalWIFByIndex(0), 'KyEQuB73eueeS7D6iBJrNSvkD1kkdkJoUsavuxGXv5fxWkPJxt96'); |
||||
|
assert.strictEqual( |
||||
|
hdBread._getPubkeyByAddress(hdBread._getExternalAddressByIndex(0)).toString('hex'), |
||||
|
'0354d804a7943eb61ec13deef44586510506889175dc2f3a375867e4796debf2a9', |
||||
|
); |
||||
|
assert.strictEqual( |
||||
|
hdBread._getPubkeyByAddress(hdBread._getInternalAddressByIndex(0)).toString('hex'), |
||||
|
'02d241fadf3e48ff30a93360f6ef255cc3a797c588c907615d096510a918f46dce', |
||||
|
); |
||||
|
|
||||
|
assert.strictEqual( |
||||
|
hdBread.getXpub(), |
||||
|
'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh', |
||||
|
); |
||||
|
}); |
@ -0,0 +1,131 @@ |
|||||
|
/* global it */ |
||||
|
import { HDLegacyP2PKHWallet } from '../../class'; |
||||
|
const assert = require('assert'); |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
|
||||
|
it('Legacy HD (BIP44) works', async () => { |
||||
|
if (!process.env.HD_MNEMONIC) { |
||||
|
console.error('process.env.HD_MNEMONIC not set, skipped'); |
||||
|
return; |
||||
|
} |
||||
|
let hd = new HDLegacyP2PKHWallet(); |
||||
|
hd.setSecret(process.env.HD_MNEMONIC); |
||||
|
assert.ok(hd.validateMnemonic()); |
||||
|
|
||||
|
assert.strictEqual( |
||||
|
hd.getXpub(), |
||||
|
'xpub6ByZUAv558PPheJgcPYHpxPLwz8M7TtueYMAik84NADeQcvbzS8W3WxxJ3C9NzfYkMoChiMAumWbeEvMWhTVpH75NqGv5c9wF3wKDbfQShb', |
||||
|
); |
||||
|
|
||||
|
assert.strictEqual(hd._getExternalAddressByIndex(0), '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por'); |
||||
|
assert.strictEqual(hd._getInternalAddressByIndex(0), '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj'); |
||||
|
|
||||
|
assert.strictEqual(hd._getInternalWIFByIndex(0), 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb'); |
||||
|
assert.strictEqual(hd._getExternalWIFByIndex(0), 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH'); |
||||
|
|
||||
|
assert.strictEqual( |
||||
|
hd._getPubkeyByAddress(hd._getExternalAddressByIndex(0)).toString('hex'), |
||||
|
'0316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667', |
||||
|
); |
||||
|
assert.strictEqual( |
||||
|
hd._getPubkeyByAddress(hd._getInternalAddressByIndex(0)).toString('hex'), |
||||
|
'02ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002', |
||||
|
); |
||||
|
|
||||
|
assert.strictEqual(hd._getDerivationPathByAddress(hd._getExternalAddressByIndex(0)), "m/84'/0'/0'/0/0"); // wrong, FIXME
|
||||
|
assert.strictEqual(hd._getDerivationPathByAddress(hd._getInternalAddressByIndex(0)), "m/84'/0'/0'/1/0"); // wrong, FIXME
|
||||
|
}); |
||||
|
|
||||
|
it.only('Legacy HD (BIP44) can create TX', async () => { |
||||
|
if (!process.env.HD_MNEMONIC) { |
||||
|
console.error('process.env.HD_MNEMONIC not set, skipped'); |
||||
|
return; |
||||
|
} |
||||
|
let hd = new HDLegacyP2PKHWallet(); |
||||
|
hd.setSecret(process.env.HD_MNEMONIC); |
||||
|
assert.ok(hd.validateMnemonic()); |
||||
|
|
||||
|
const utxo = [ |
||||
|
{ |
||||
|
height: 554830, |
||||
|
value: 10000, |
||||
|
address: '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por', |
||||
|
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
vout: 0, |
||||
|
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
amount: 10000, |
||||
|
wif: 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH', |
||||
|
confirmations: 1, |
||||
|
txhex: |
||||
|
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
||||
|
}, |
||||
|
{ |
||||
|
height: 554830, |
||||
|
value: 20000, |
||||
|
address: '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj', |
||||
|
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
vout: 1, |
||||
|
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
amount: 20000, |
||||
|
wif: 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb', |
||||
|
confirmations: 1, |
||||
|
txhex: |
||||
|
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
||||
|
}, |
||||
|
{ |
||||
|
height: 554830, |
||||
|
value: 30000, |
||||
|
address: '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por', |
||||
|
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
vout: 2, |
||||
|
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
amount: 30000, |
||||
|
wif: 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH', |
||||
|
confirmations: 1, |
||||
|
txhex: |
||||
|
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
||||
|
}, |
||||
|
{ |
||||
|
height: 554830, |
||||
|
value: 40000, |
||||
|
address: '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj', |
||||
|
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
vout: 3, |
||||
|
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
||||
|
amount: 40000, |
||||
|
wif: 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb', |
||||
|
confirmations: 1, |
||||
|
txhex: |
||||
|
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
let txNew = hd.createTransaction( |
||||
|
utxo, |
||||
|
[{ address: '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', value: 80000 }], |
||||
|
1, |
||||
|
hd._getInternalAddressByIndex(hd.next_free_change_address_index), |
||||
|
); |
||||
|
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual(tx.ins.length, 4); |
||||
|
assert.strictEqual(tx.outs.length, 2); |
||||
|
assert.strictEqual(tx.outs[0].value, 80000); // payee
|
||||
|
assert.strictEqual(tx.outs[1].value, 19334); // change
|
||||
|
let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); |
||||
|
let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script); |
||||
|
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); |
||||
|
assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); |
||||
|
|
||||
|
// testing sendMax
|
||||
|
txNew = hd.createTransaction( |
||||
|
utxo, |
||||
|
[{ address: '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK' }], |
||||
|
1, |
||||
|
hd._getInternalAddressByIndex(hd.next_free_change_address_index), |
||||
|
); |
||||
|
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual(tx.ins.length, 4); |
||||
|
assert.strictEqual(tx.outs.length, 1); |
||||
|
toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); |
||||
|
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); |
||||
|
}); |
@ -0,0 +1,42 @@ |
|||||
|
/* global it, describe */ |
||||
|
import { LegacyWallet } from '../../class'; |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
const assert = require('assert'); |
||||
|
|
||||
|
describe('Legacy wallet', () => { |
||||
|
it('can create transaction', async () => { |
||||
|
let l = new LegacyWallet(); |
||||
|
l.setSecret('L4ccWrPMmFDZw4kzAKFqJNxgHANjdy6b7YKNXMwB4xac4FLF3Tov'); |
||||
|
assert.strictEqual(l.getAddress(), '14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M'); |
||||
|
assert.strictEqual(await l.getChangeAddressAsync(), l.getAddress()); |
||||
|
|
||||
|
let utxos = [ |
||||
|
{ |
||||
|
txid: 'cc44e933a094296d9fe424ad7306f16916253a3d154d52e4f1a757c18242cec4', |
||||
|
vout: 0, |
||||
|
value: 100000, |
||||
|
txhex: |
||||
|
'0200000000010161890cd52770c150da4d7d190920f43b9f88e7660c565a5a5ad141abb6de09de00000000000000008002a0860100000000001976a91426e01119d265aa980390c49eece923976c218f1588ac3e17000000000000160014c1af8c9dd85e0e55a532a952282604f820746fcd02473044022072b3f28808943c6aa588dd7a4e8f29fad7357a2814e05d6c5d767eb6b307b4e6022067bc6a8df2dbee43c87b8ce9ddd9fe678e00e0f7ae6690d5cb81eca6170c47e8012102e8fba5643e15ab70ec79528833a2c51338c1114c4eebc348a235b1a3e13ab07100000000', |
||||
|
}, |
||||
|
]; |
||||
|
// ^^ only non-segwit inputs need full transaction txhex
|
||||
|
|
||||
|
let txNew = l.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress()); |
||||
|
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual( |
||||
|
txNew.tx.toHex(), |
||||
|
'0200000001c4ce4282c157a7f1e4524d153d3a251669f10673ad24e49f6d2994a033e944cc000000006a47304402200faed160757433bcd4d9fe5f55eb92420406e8f3099a7e12ef720c77313c8c7e022044bc9e1abca6a81a8ad5c749f5ec4694301589172b83b1803bc134eda0487dbc01210337c09b3cb889801638078fd4e6998218b28c92d338ea2602720a88847aedceb3ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2f260000000000001976a91426e01119d265aa980390c49eece923976c218f1588ac00000000', |
||||
|
); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 2); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
assert.strictEqual(l.getAddress(), bitcoin.address.fromOutputScript(tx.outs[1].script)); // change address
|
||||
|
|
||||
|
// sendMax
|
||||
|
txNew = l.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress()); |
||||
|
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 1); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
}); |
||||
|
}); |
@ -0,0 +1,39 @@ |
|||||
|
/* global it, describe */ |
||||
|
import { SegwitBech32Wallet } from '../../class'; |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
const assert = require('assert'); |
||||
|
|
||||
|
describe('Segwit P2SH wallet', () => { |
||||
|
it('can create transaction', async () => { |
||||
|
let wallet = new SegwitBech32Wallet(); |
||||
|
wallet.setSecret('L4vn2KxgMLrEVpxjfLwxfjnPPQMnx42DCjZJ2H7nN4mdHDyEUWXd'); |
||||
|
assert.strictEqual(wallet.getAddress(), 'bc1q3rl0mkyk0zrtxfmqn9wpcd3gnaz00yv9yp0hxe'); |
||||
|
assert.strictEqual(await wallet.getChangeAddressAsync(), wallet.getAddress()); |
||||
|
|
||||
|
let utxos = [ |
||||
|
{ |
||||
|
txid: '57d18bc076b919583ff074cfba6201edd577f7fe35f69147ea512e970f95ffeb', |
||||
|
vout: 0, |
||||
|
value: 100000, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
let txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
||||
|
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual( |
||||
|
txNew.tx.toHex(), |
||||
|
'02000000000101ebff950f972e51ea4791f635fef777d5ed0162bacf74f03f5819b976c08bd1570000000000ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2f2600000000000016001488fefdd8967886b32760995c1c36289f44f791850248304502210094d8b9d291b3c131594dbacceebf9277ba598f454acbc2c9fa4a7b20895bb74302201a592c4c121f154be1212e6e6b8cd82bb72b97b0f9c098ce8dbe011fbefc8ac101210314cf2bf53f221e58c5adc1dd95adba9239b248f39b09eb2c550aadc1926fe7aa00000000', |
||||
|
); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 2); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
assert.strictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), wallet.getAddress()); // change address
|
||||
|
|
||||
|
// sendMax
|
||||
|
txNew = wallet.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
||||
|
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 1); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
}); |
||||
|
}); |
@ -0,0 +1,39 @@ |
|||||
|
/* global it, describe */ |
||||
|
import { SegwitP2SHWallet } from '../../class'; |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
const assert = require('assert'); |
||||
|
|
||||
|
describe('Segwit P2SH wallet', () => { |
||||
|
it('can create transaction', async () => { |
||||
|
let wallet = new SegwitP2SHWallet(); |
||||
|
wallet.setSecret('Ky1vhqYGCiCbPd8nmbUeGfwLdXB1h5aGwxHwpXrzYRfY5cTZPDo4'); |
||||
|
assert.strictEqual(wallet.getAddress(), '3CKN8HTCews4rYJYsyub5hjAVm5g5VFdQJ'); |
||||
|
assert.strictEqual(await wallet.getChangeAddressAsync(), wallet.getAddress()); |
||||
|
|
||||
|
let utxos = [ |
||||
|
{ |
||||
|
txid: 'a56b44080cb606c0bd90e77fcd4fb34c863e68e5562e75b4386e611390eb860c', |
||||
|
vout: 0, |
||||
|
value: 300000, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
let txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
||||
|
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual( |
||||
|
txNew.tx.toHex(), |
||||
|
'020000000001010c86eb9013616e38b4752e56e5683e864cb34fcd7fe790bdc006b60c08446ba50000000017160014139dc70d73097f9d775f8a3280ba3e3435515641ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac6f3303000000000017a914749118baa93fb4b88c28909c8bf0a8202a0484f487024730440220086b55a771f37daadbe64fe557a32fd68ee92995445af0b0a5b9343db67505e1022064c9a9778a19a0276761af69b8917d19ed4b791c785dd8cb4aae327f2a6b526f012103a5de146762f84055db3202c1316cd9008f16047f4f408c1482fdb108217eda0800000000', |
||||
|
); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 2); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
assert.strictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), wallet.getAddress()); // change address
|
||||
|
|
||||
|
// sendMax
|
||||
|
txNew = wallet.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
||||
|
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
||||
|
assert.strictEqual(tx.ins.length, 1); |
||||
|
assert.strictEqual(tx.outs.length, 1); |
||||
|
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
|
}); |
||||
|
}); |
@ -1,282 +0,0 @@ |
|||||
/* global describe, it */ |
|
||||
const bitcoinjs = require('bitcoinjs-lib'); |
|
||||
let assert = require('assert'); |
|
||||
|
|
||||
describe('unit - signer', function() { |
|
||||
describe('createSegwitTransaction()', function() { |
|
||||
it('should return valid tx hex for segwit transactions', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715', |
|
||||
vout: 1, |
|
||||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|
||||
amount: 0.001, |
|
||||
confirmations: 108, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let tx = signer.createSegwitTransaction( |
|
||||
utxos, |
|
||||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|
||||
0.001, |
|
||||
0.0001, |
|
||||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|
||||
); |
|
||||
assert.strictEqual( |
|
||||
tx, |
|
||||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a9ffffffff01905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac02473044022023eef496f43936550e08898d10b254ee910dfd19268341edb2f61b873ccba25502204b722787fabc37c2c9e9575832331b0ba0c3f7cd0c18a6fb90027f4327bd8d850121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000', |
|
||||
); |
|
||||
done(); |
|
||||
}); |
|
||||
|
|
||||
it('should return valid tx hex for RBF-able segwit transactions', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715', |
|
||||
vout: 1, |
|
||||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|
||||
amount: 0.1, |
|
||||
confirmations: 108, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let txhex = signer.createSegwitTransaction( |
|
||||
utxos, |
|
||||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|
||||
0.001, |
|
||||
0.0001, |
|
||||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|
||||
'3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
0, |
|
||||
); |
|
||||
assert.strictEqual( |
|
||||
txhex, |
|
||||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000', |
|
||||
); |
|
||||
// now, testing change addess, destination address, amounts & fees...
|
|
||||
let tx = bitcoinjs.Transaction.fromHex(txhex); |
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(tx.outs[0].script), '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr'); |
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(tx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); |
|
||||
assert.strictEqual(tx.outs[0].value, 90000); // 0.0009 because we deducted fee 0.0001
|
|
||||
assert.strictEqual(tx.outs[1].value, 9900000); // 0.099 because 0.1 - 0.001
|
|
||||
done(); |
|
||||
}); |
|
||||
|
|
||||
it('should create Replace-By-Fee tx, given txhex', () => { |
|
||||
let txhex = |
|
||||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000'; |
|
||||
let signer = require('../../models/signer'); |
|
||||
let dummyUtxodata = { |
|
||||
'1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715': { |
|
||||
// txid we use output from
|
|
||||
1: 10000000, // output index and it's value in satoshi
|
|
||||
}, |
|
||||
}; |
|
||||
let newhex = signer.createRBFSegwitTransaction( |
|
||||
txhex, |
|
||||
{ '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr': '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2' }, |
|
||||
0.0001, |
|
||||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|
||||
dummyUtxodata, |
|
||||
); |
|
||||
let oldTx = bitcoinjs.Transaction.fromHex(txhex); |
|
||||
let newTx = bitcoinjs.Transaction.fromHex(newhex); |
|
||||
// just checking old tx...
|
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(oldTx.outs[0].script), '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr'); // old DESTINATION address
|
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(oldTx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); // old CHANGE address
|
|
||||
assert.strictEqual(oldTx.outs[0].value, 90000); // 0.0009 because we deducted fee 0.0001
|
|
||||
assert.strictEqual(oldTx.outs[1].value, 9900000); // 0.099 because 0.1 - 0.001
|
|
||||
// finaly, new tx checks...
|
|
||||
assert.strictEqual(oldTx.outs[0].value, newTx.outs[0].value); // DESTINATION output amount remains unchanged
|
|
||||
assert.strictEqual(oldTx.outs[1].value - newTx.outs[1].value, 0.0001 * 100000000); // CHANGE output decreased on the amount of fee delta
|
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(newTx.outs[0].script), '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'); // new DESTINATION address
|
|
||||
assert.strictEqual(bitcoinjs.address.fromOutputScript(newTx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); // CHANGE address remains
|
|
||||
assert.strictEqual(oldTx.ins[0].sequence + 1, newTx.ins[0].sequence); |
|
||||
}); |
|
||||
|
|
||||
it('should return valid tx hex for segwit transactions with multiple inputs', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '4e2a536aaf6b0b8a4f439d0343436cd321b8bac9840a24d13b8eed484a257b0b', |
|
||||
vout: 0, |
|
||||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|
||||
amount: 0.0009, |
|
||||
confirmations: 67, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
{ |
|
||||
txid: '09e1b78d4ecd95dd4c7dbc840a2619da6d02caa345a63b2733f3972666462fbd', |
|
||||
vout: 0, |
|
||||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|
||||
amount: 0.0019, |
|
||||
confirmations: 142, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let tx = signer.createSegwitTransaction( |
|
||||
utxos, |
|
||||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|
||||
0.0028, |
|
||||
0.0002, |
|
||||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|
||||
); |
|
||||
assert.strictEqual( |
|
||||
tx, |
|
||||
'010000000001020b7b254a48ed8e3bd1240a84c9bab821d36c4343039d434f8a0b6baf6a532a4e00000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffffbd2f46662697f333273ba645a3ca026dda19260a84bc7d4cdd95cd4e8db7e10900000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffff01a0f70300000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac02483045022100b3e001b880a7a18294640165cc40c777669534803cee7206c8d3f03531bb315502204642a4569576a2e9e77342c7a9aaa508a21248b7720fe0f9e6d76713951c133001210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b02473044022078bd4f47178ce13c4fbf77c5ce78c80ac10251aa053c68c8febb21ce228f844e02207b02bdd754fbc2df9f62ea98e7dbd6c43e760b8f78c7c00b43512a06b498adb501210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b00000000', |
|
||||
); |
|
||||
done(); |
|
||||
}); |
|
||||
|
|
||||
it('should return valid tx hex for segwit transactions with change address', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '160559030484800a77f9b38717bb0217e87bfeb47b92e2e5bad6316ad9d8d360', |
|
||||
vout: 1, |
|
||||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|
||||
amount: 0.004, |
|
||||
confirmations: 271, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let tx = signer.createSegwitTransaction( |
|
||||
utxos, |
|
||||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|
||||
0.002, |
|
||||
0.0001, |
|
||||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|
||||
); |
|
||||
assert.strictEqual( |
|
||||
tx, |
|
||||
'0100000000010160d3d8d96a31d6bae5e2927bb4fe7be81702bb1787b3f9770a8084040359051601000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffff0230e60200000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac400d03000000000017a914e0d81f03546ab8f29392b488ec62ab355ee7c573870247304402202c962e14ae6abd45dc9613d2f088ad487e805670548e244deb25d762b310a60002204f12c7f9b8da3567b39906ff6c46b27ce087e7ae91bbe34fb1cdee1b994b9d3001210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b00000000', |
|
||||
); |
|
||||
done(); |
|
||||
}); |
|
||||
|
|
||||
it('should return valid tx hex for segwit transactions if change is too small so it causes @dust error', function(done) { |
|
||||
// checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee
|
|
||||
let signer = require('../../models/signer'); |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '160559030484800a77f9b38717bb0217e87bfeb47b92e2e5bad6316ad9d8d360', |
|
||||
vout: 1, |
|
||||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|
||||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|
||||
amount: 0.004, |
|
||||
confirmations: 271, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let txhex = signer.createSegwitTransaction( |
|
||||
utxos, |
|
||||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|
||||
0.003998, |
|
||||
0.000001, |
|
||||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|
||||
); |
|
||||
let bitcoin = bitcoinjs; |
|
||||
let tx = bitcoin.Transaction.fromHex(txhex); |
|
||||
assert.strictEqual(tx.ins.length, 1); |
|
||||
assert.strictEqual(tx.outs.length, 1); // only 1 output, which means change is neglected
|
|
||||
assert.strictEqual(tx.outs[0].value, 399700); |
|
||||
done(); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
describe('WIF2address()', function() { |
|
||||
it('should convert WIF to segwit P2SH address', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let address = signer.WIF2segwitAddress('L55uHs7pyz7rP18K38kB7kqDVNJaeYFzJtZyC3ZjD2c684dzXQWs'); |
|
||||
assert.strictEqual('3FSL9x8P8cQ74iW2HLP6JPGPRgc4K2FnsU', address); |
|
||||
done(); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
describe('generateNewAddress()', function() { |
|
||||
it('should generate new address', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let address = signer.generateNewSegwitAddress(); |
|
||||
assert.ok(address.WIF); |
|
||||
assert.ok(address.address); |
|
||||
assert.strictEqual(address.address, signer.WIF2segwitAddress(address.WIF)); |
|
||||
done(); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
describe('URI()', function() { |
|
||||
it('should form correct payment url', function(done) { |
|
||||
let signer = require('../../models/signer'); |
|
||||
let url = signer.URI({ |
|
||||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
message: 'For goods & services', |
|
||||
label: 'nolabel', |
|
||||
amount: 1000000, |
|
||||
}); |
|
||||
assert.strictEqual(url, 'bitcoin:3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi?amount=0.01&message=For%20goods%20%26%20services&label=nolabel'); |
|
||||
|
|
||||
url = signer.URI({ |
|
||||
address: '1DzJepHCRD2C9vpFjk11eXJi97juEZ3ftv', |
|
||||
message: 'wheres the money lebowski', |
|
||||
amount: 400000, |
|
||||
}); |
|
||||
assert.strictEqual(url, 'bitcoin:1DzJepHCRD2C9vpFjk11eXJi97juEZ3ftv?amount=0.004&message=wheres%20the%20money%20lebowski'); |
|
||||
done(); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
describe('createTransaction()', () => { |
|
||||
const signer = require('../../models/signer'); |
|
||||
it('should return valid TX hex for legacy transactions', () => { |
|
||||
let utxos = [ |
|
||||
{ |
|
||||
txid: '2f445cf016fa2772db7d473bff97515355b4e6148e1c980ce351d47cf54c517f', |
|
||||
vout: 1, |
|
||||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|
||||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|
||||
amount: 0.01, |
|
||||
confirmations: 108, |
|
||||
spendable: false, |
|
||||
solvable: false, |
|
||||
safe: true, |
|
||||
}, |
|
||||
]; |
|
||||
let toAddr = '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB'; |
|
||||
let amount = 0.001; |
|
||||
let fee = 0.0001; |
|
||||
let WIF = 'KzbTHhzzZyVhkTYpuReMBkE7zUvvDEZtavq1DJV85MtBZyHK1TTF'; |
|
||||
let fromAddr = '179JSjDc9Dh9pWWq9qv35sZsXQAV6VdE1E'; |
|
||||
let txHex = signer.createTransaction(utxos, toAddr, amount, fee, WIF, fromAddr); |
|
||||
assert.strictEqual( |
|
||||
txHex, |
|
||||
'01000000017f514cf57cd451e30c981c8e14e6b455535197ff3b477ddb7227fa16f05c442f010000006b483045022100c5d6b024db144aa1f0cb6d6212c326c9753f4144fd69947c1f38657944b92022022039214118b745afe6e031f96f3e98e705979f2b9f9cbbc6a91e11c89c811a3292012103f5438d524ad1cc288963466d6ef1a27d83183f7e9b7fe30879ecdae887692a31ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88aca0bb0d00000000001976a9144362a4c0dbf5102238164d1ec97f3b518bb651cd88ac00000000', |
|
||||
); |
|
||||
}); |
|
||||
}); |
|
||||
}); |
|
Loading…
Reference in new issue