mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 06:52:41 +01:00
ADD: low-level code for BIP84 RBF
This commit is contained in:
parent
934fffee8b
commit
bd2b6670c1
5 changed files with 402 additions and 1 deletions
210
class/hd-segwit-bech32-transaction.js
Normal file
210
class/hd-segwit-bech32-transaction.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
import { HDSegwitBech32Wallet, SegwitBech32Wallet } from './';
|
||||
const bitcoin = require('bitcoinjs5');
|
||||
const BlueElectrum = require('../BlueElectrum');
|
||||
const reverse = require('buffer-reverse');
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
/**
|
||||
* Represents transaction of a BIP84 wallet.
|
||||
* Helpers for RBF, CPFP etc.
|
||||
*/
|
||||
export class HDSegwitBech32Transaction {
|
||||
/**
|
||||
* @param txhex string Object initialized with txhex
|
||||
* @param wallet {HDSegwitBech32Wallet} If set - a wallet object to which transacton belongs
|
||||
*/
|
||||
constructor(txhex, wallet) {
|
||||
this._txhex = txhex;
|
||||
|
||||
if (wallet) {
|
||||
if (wallet.type === HDSegwitBech32Wallet.type) {
|
||||
/** @type {HDSegwitBech32Wallet} */
|
||||
this._wallet = wallet;
|
||||
} else {
|
||||
throw new Error('Only HD Bech32 wallets supported');
|
||||
}
|
||||
}
|
||||
|
||||
this._txDecoded = bitcoin.Transaction.fromHex(this._txhex);
|
||||
this._remoteTx = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns max used sequence for this transaction. Next RBF transaction
|
||||
* should have this sequence + 1
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
getMaxUsedSequence() {
|
||||
let max = 0;
|
||||
for (let inp of this._txDecoded.ins) {
|
||||
max = Math.max(inp.sequence, max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic check that Sequence num for this TX is replaceable
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSequenceReplaceable() {
|
||||
return this.getMaxUsedSequence() < bitcoin.Transaction.DEFAULT_SEQUENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* If internal extended tx data not set - this is a method
|
||||
* to fetch and set this data from electrum
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
async _fetchRemoteTx() {
|
||||
let result = await BlueElectrum.multiGetTransactionByTxid([this._txDecoded.getId()]);
|
||||
this._remoteTx = Object.values(result)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches from electrum actual confirmations number for this tx
|
||||
*
|
||||
* @returns {Promise<Number>}
|
||||
*/
|
||||
async getRemoteConfirmationsNum() {
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
return this._remoteTx.confirmations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that tx belongs to a wallet and also
|
||||
* tx value is < 0, which means its a spending transaction
|
||||
* definately initiated by us.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async isOurTransaction() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
let found = false;
|
||||
for (let tx of this._wallet.getTransactions()) {
|
||||
if (tx.txid === this._txDecoded.getId()) {
|
||||
// its our transaction, and its spending transaction, which means we initiated it
|
||||
if (tx.value < 0) found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
let prevInputs = [];
|
||||
for (let inp of this._txDecoded.ins) {
|
||||
let reversedHash = Buffer.from(reverse(inp.hash));
|
||||
reversedHash = reversedHash.toString('hex');
|
||||
prevInputs.push(reversedHash);
|
||||
}
|
||||
|
||||
let prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs);
|
||||
|
||||
// fetched, now lets count how much satoshis went in
|
||||
let wentIn = 0;
|
||||
let utxos = [];
|
||||
for (let inp of this._txDecoded.ins) {
|
||||
let reversedHash = Buffer.from(reverse(inp.hash));
|
||||
reversedHash = reversedHash.toString('hex');
|
||||
if (prevTransactions[reversedHash] && prevTransactions[reversedHash].vout && prevTransactions[reversedHash].vout[inp.index]) {
|
||||
let value = prevTransactions[reversedHash].vout[inp.index].value;
|
||||
value = new BigNumber(value).multipliedBy(100000000).toNumber();
|
||||
wentIn += value;
|
||||
let address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]);
|
||||
utxos.push({ vout: inp.index, value: value, txId: reversedHash, address: address });
|
||||
}
|
||||
}
|
||||
|
||||
// counting how much went into actual outputs
|
||||
|
||||
let wasSpent = 0;
|
||||
for (let outp of this._txDecoded.outs) {
|
||||
wasSpent += +outp.value;
|
||||
}
|
||||
|
||||
let fee = wentIn - wasSpent;
|
||||
let feeRate = Math.floor(fee / (this._txhex.length / 2));
|
||||
|
||||
// lets take a look at change
|
||||
let changeAmount = 0;
|
||||
let targets = [];
|
||||
for (let outp of this._remoteTx.vout) {
|
||||
let address = outp.scriptPubKey.addresses[0];
|
||||
let value = new BigNumber(outp.value).multipliedBy(100000000).toNumber();
|
||||
if (this._wallet.weOwnAddress(address)) {
|
||||
changeAmount += value;
|
||||
} else {
|
||||
// this is target
|
||||
targets.push({ value: value, address: address });
|
||||
}
|
||||
}
|
||||
|
||||
return { fee, feeRate, targets, changeAmount, utxos };
|
||||
|
||||
// this means...
|
||||
// let maxPossibleFee = fee + changeAmount;
|
||||
// let maxPossibleFeeRate = Math.floor(maxPossibleFee / (this._txhex.length / 2));
|
||||
// console.warn({maxPossibleFeeRate});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if tx has single output and that output belongs to us - that
|
||||
* means we already canceled this tx and we can only bump fees. Or plain all outputs belong to us.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canCancelTx() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
|
||||
// if theres at least one output we dont own - we can cancel this transaction!
|
||||
for (let outp of this._txDecoded.outs) {
|
||||
if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newFeerate
|
||||
* @returns {Promise<{outputs: Array, tx: HDSegwitBech32Transaction, inputs: Array, fee: Number}>}
|
||||
*/
|
||||
async createRBFcancelTx(newFeerate) {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
let { feeRate, utxos } = await this.getInfo();
|
||||
|
||||
if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one');
|
||||
let myAddress = await this._wallet.getChangeAddressAsync();
|
||||
|
||||
return this._wallet.createTransaction(
|
||||
utxos,
|
||||
[{ address: myAddress }],
|
||||
newFeerate,
|
||||
/* meaningless in this context */ myAddress,
|
||||
this.getMaxUsedSequence() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newFeerate
|
||||
* @returns {Promise<{outputs: Array, tx: HDSegwitBech32Transaction, inputs: Array, fee: Number}>}
|
||||
*/
|
||||
async createRBFbumpFee(newFeerate) {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._remoteTx) await this._fetchRemoteTx();
|
||||
|
||||
let { feeRate, targets, utxos } = await this.getInfo();
|
||||
|
||||
if (newFeerate <= feeRate) throw new Error('New feerate should be bigger than the old one');
|
||||
let myAddress = await this._wallet.getChangeAddressAsync();
|
||||
|
||||
return this._wallet.createTransaction(utxos, targets, newFeerate, myAddress, this.getMaxUsedSequence() + 1);
|
||||
}
|
||||
}
|
|
@ -646,6 +646,9 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
createTx(utxos, amount, fee, address) {
|
||||
throw new Error('Deprecated');
|
||||
}
|
||||
|
|
|
@ -11,3 +11,4 @@ export * from './watch-only-wallet';
|
|||
export * from './lightning-custodian-wallet';
|
||||
export * from './abstract-hd-wallet';
|
||||
export * from './hd-segwit-bech32-wallet';
|
||||
export * from './hd-segwit-bech32-transaction';
|
||||
|
|
182
tests/integration/hd-segwit-bech32-transaction.test.js
Normal file
182
tests/integration/hd-segwit-bech32-transaction.test.js
Normal file
|
@ -0,0 +1,182 @@
|
|||
/* global it, describe, jasmine, afterAll, beforeAll */
|
||||
import { HDSegwitBech32Wallet, HDSegwitBech32Transaction, SegwitBech32Wallet } from '../../class';
|
||||
const bitcoin = require('bitcoinjs5');
|
||||
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
|
||||
let assert = require('assert');
|
||||
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js
|
||||
let BlueElectrum = require('../../BlueElectrum');
|
||||
|
||||
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('HDSegwitBech32Transaction', () => {
|
||||
it('can decode & check sequence', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||
let T = new HDSegwitBech32Transaction(
|
||||
'020000000001035b6bd5f35a4ae9fb97da83929d042ee1609aa37e763e1428f31578d2308b5afb0000000000ffffffff66595fc361ad1d5954f7c06e38482d0abf1b981feee7353d854cd25c7f6516e80000000000ffffffffc33a839294313ae2674ce4c574364ff3837d31c8e9c40b030409bff318ddcda10100000000ffffffff02888a01000000000017a914e286d58e53f9247a4710e51232cce0686f16873c87d2880000000000001600140c75ccd91c55c4f54788931d48e6f23909d3a21f024730440220078c59543244d8b642f9ef7a7ea2747ddf34c2937a89fe9641038832f5baebf00220146b116e9a915a6fd9ab855c63b7549d44b8abf1607639aaf67321d045596046012103e73f0f28b3007440783b188facc81e835df195687a6dc5e9fbd775022d27eebc02483045022100e2f3a3064517226dbde0d87733b0179e0bb0cd94968f27961ba66847f4553d3602200ab8a858b7967cdb656816861a8cab46cf86db69be8a285292157b1fe1546872012103d8a20ce5c997b78ebeb0611f12bcf6c6bd797e485ff99253fee0e9794ac73dfd0247304402207dd7833521682e01c399195d37965b84ca6085f5ff62a34e09446e3244760d2702207672b60ec9d307ed502dc023b30d00415c061fd68a7f503738c57ab81419c788012102b8faed0d077eb0523af21e14fe51bd6dcc5a14ba6e800071bac2a3583a9d3a6a00000000',
|
||||
);
|
||||
assert.strictEqual(T.getMaxUsedSequence(), 0xffffffff);
|
||||
assert.strictEqual(T.isSequenceReplaceable(), false);
|
||||
|
||||
T = new HDSegwitBech32Transaction(
|
||||
'02000000000102f1155666b534f7cb476a0523a45dc8731d38d56b5b08e877c968812423fbd7f3010000000000000000d8a2882a692ee759b43e6af48ac152dd3410cc4b7d25031e83b3396c16ffbc8900000000000000000002400d03000000000017a914e286d58e53f9247a4710e51232cce0686f16873c870695010000000000160014d3e2ecbf4d91321794e0297e0284c47527cf878b02483045022100d18dc865fb4d087004d021d480b983b8afb177a1934ce4cd11cf97b03e17944f02206d7310687a84aab5d4696d535bca69c2db4449b48feb55fff028aa004f2d1744012103af4b208608c75f38e78f6e5abfbcad9c360fb60d3e035193b2cd0cdc8fc0155c0247304402207556e859845df41d897fe442f59b6106c8fa39c74ba5b7b8e3268ab0aebf186f0220048a9f3742339c44a1e5c78b491822b96070bcfda3f64db9dc6434f8e8068475012102456e5223ed3884dc6b0e152067fd836e3eb1485422eda45558bf83f59c6ad09f00000000',
|
||||
);
|
||||
assert.strictEqual(T.getMaxUsedSequence(), 0);
|
||||
assert.strictEqual(T.isSequenceReplaceable(), true);
|
||||
|
||||
assert.ok((await T.getRemoteConfirmationsNum()) >= 292);
|
||||
});
|
||||
|
||||
it('can tell if its our transaction', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
await hd.fetchTransactions();
|
||||
|
||||
let tt = new HDSegwitBech32Transaction(
|
||||
'02000000000102f1155666b534f7cb476a0523a45dc8731d38d56b5b08e877c968812423fbd7f3010000000000000000d8a2882a692ee759b43e6af48ac152dd3410cc4b7d25031e83b3396c16ffbc8900000000000000000002400d03000000000017a914e286d58e53f9247a4710e51232cce0686f16873c870695010000000000160014d3e2ecbf4d91321794e0297e0284c47527cf878b02483045022100d18dc865fb4d087004d021d480b983b8afb177a1934ce4cd11cf97b03e17944f02206d7310687a84aab5d4696d535bca69c2db4449b48feb55fff028aa004f2d1744012103af4b208608c75f38e78f6e5abfbcad9c360fb60d3e035193b2cd0cdc8fc0155c0247304402207556e859845df41d897fe442f59b6106c8fa39c74ba5b7b8e3268ab0aebf186f0220048a9f3742339c44a1e5c78b491822b96070bcfda3f64db9dc6434f8e8068475012102456e5223ed3884dc6b0e152067fd836e3eb1485422eda45558bf83f59c6ad09f00000000',
|
||||
hd,
|
||||
);
|
||||
|
||||
assert.ok(await tt.isOurTransaction());
|
||||
|
||||
tt = new HDSegwitBech32Transaction(
|
||||
'01000000000101e141f756746932f869c7323d941f26e6a1a6817143b97250a51f8c08510547a901000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a60000000002400d03000000000016001465eb5c39aa6785f69f292fdb41c282ea7799721ff85c09000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702473044022009a072e3a920708a63bac6452f5ff74a0e918057bb79f9f0fce72494c7edd5c9022000e430179e9051fe37b6ea8ba538b4af94e120dd70fc30aed4e54d5054bc9f9d0121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000',
|
||||
hd,
|
||||
);
|
||||
|
||||
assert.ok(!(await tt.isOurTransaction()));
|
||||
});
|
||||
|
||||
it('can tell tx info', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||
await hd.fetchBalance();
|
||||
await hd.fetchTransactions();
|
||||
|
||||
// 881c54edd95cbdd1583d6b9148eb35128a47b64a2e67a5368a649d6be960f08e
|
||||
let tt = new HDSegwitBech32Transaction(
|
||||
'02000000000102f1155666b534f7cb476a0523a45dc8731d38d56b5b08e877c968812423fbd7f3010000000000000000d8a2882a692ee759b43e6af48ac152dd3410cc4b7d25031e83b3396c16ffbc8900000000000000000002400d03000000000017a914e286d58e53f9247a4710e51232cce0686f16873c870695010000000000160014d3e2ecbf4d91321794e0297e0284c47527cf878b02483045022100d18dc865fb4d087004d021d480b983b8afb177a1934ce4cd11cf97b03e17944f02206d7310687a84aab5d4696d535bca69c2db4449b48feb55fff028aa004f2d1744012103af4b208608c75f38e78f6e5abfbcad9c360fb60d3e035193b2cd0cdc8fc0155c0247304402207556e859845df41d897fe442f59b6106c8fa39c74ba5b7b8e3268ab0aebf186f0220048a9f3742339c44a1e5c78b491822b96070bcfda3f64db9dc6434f8e8068475012102456e5223ed3884dc6b0e152067fd836e3eb1485422eda45558bf83f59c6ad09f00000000',
|
||||
hd,
|
||||
);
|
||||
|
||||
let { fee, feeRate, targets, changeAmount, utxos } = await tt.getInfo();
|
||||
assert.strictEqual(fee, 4464);
|
||||
assert.strictEqual(changeAmount, 103686);
|
||||
assert.strictEqual(feeRate, 12);
|
||||
assert.strictEqual(targets.length, 1);
|
||||
assert.strictEqual(targets[0].value, 200000);
|
||||
assert.strictEqual(targets[0].address, '3NLnALo49CFEF4tCRhCvz45ySSfz3UktZC');
|
||||
assert.strictEqual(
|
||||
JSON.stringify(utxos),
|
||||
JSON.stringify([
|
||||
{
|
||||
vout: 1,
|
||||
value: 108150,
|
||||
txId: 'f3d7fb23248168c977e8085b6bd5381d73c85da423056a47cbf734b5665615f1',
|
||||
address: 'bc1qahhgjtxexjx9t0e5pjzqwtjnxexzl6f5an38hq',
|
||||
},
|
||||
{
|
||||
vout: 0,
|
||||
value: 200000,
|
||||
txId: '89bcff166c39b3831e03257d4bcc1034dd52c18af46a3eb459e72e692a88a2d8',
|
||||
address: 'bc1qvh44cwd2v7zld8ef9ld5rs5zafmejuslp6yd73',
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('can do RBF - cancel tx', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||
await hd.fetchBalance();
|
||||
await hd.fetchTransactions();
|
||||
|
||||
// 881c54edd95cbdd1583d6b9148eb35128a47b64a2e67a5368a649d6be960f08e
|
||||
let tt = new HDSegwitBech32Transaction(
|
||||
'02000000000102f1155666b534f7cb476a0523a45dc8731d38d56b5b08e877c968812423fbd7f3010000000000000000d8a2882a692ee759b43e6af48ac152dd3410cc4b7d25031e83b3396c16ffbc8900000000000000000002400d03000000000017a914e286d58e53f9247a4710e51232cce0686f16873c870695010000000000160014d3e2ecbf4d91321794e0297e0284c47527cf878b02483045022100d18dc865fb4d087004d021d480b983b8afb177a1934ce4cd11cf97b03e17944f02206d7310687a84aab5d4696d535bca69c2db4449b48feb55fff028aa004f2d1744012103af4b208608c75f38e78f6e5abfbcad9c360fb60d3e035193b2cd0cdc8fc0155c0247304402207556e859845df41d897fe442f59b6106c8fa39c74ba5b7b8e3268ab0aebf186f0220048a9f3742339c44a1e5c78b491822b96070bcfda3f64db9dc6434f8e8068475012102456e5223ed3884dc6b0e152067fd836e3eb1485422eda45558bf83f59c6ad09f00000000',
|
||||
hd,
|
||||
);
|
||||
|
||||
assert.strictEqual(tt.canCancelTx(), true);
|
||||
|
||||
let { tx } = await tt.createRBFcancelTx(15);
|
||||
|
||||
let createdTx = bitcoin.Transaction.fromHex(tx.toHex());
|
||||
assert.strictEqual(createdTx.ins.length, 2);
|
||||
assert.strictEqual(createdTx.outs.length, 1);
|
||||
let addr = SegwitBech32Wallet.scriptPubKeyToAddress(createdTx.outs[0].script);
|
||||
assert.ok(hd.weOwnAddress(addr));
|
||||
|
||||
let actualFeerate = (108150 + 200000 - createdTx.outs[0].value) / (tx.toHex().length / 2);
|
||||
assert.strictEqual(Math.round(actualFeerate), 15);
|
||||
|
||||
let tt2 = new HDSegwitBech32Transaction(tx.toHex(), hd);
|
||||
assert.strictEqual(tt2.canCancelTx(), false); // newly created cancel tx is not cancellable anymore
|
||||
});
|
||||
|
||||
it('can do RBF - bumpfees tx', async function() {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
|
||||
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||
await hd.fetchBalance();
|
||||
await hd.fetchTransactions();
|
||||
|
||||
// 881c54edd95cbdd1583d6b9148eb35128a47b64a2e67a5368a649d6be960f08e
|
||||
let tt = new HDSegwitBech32Transaction(
|
||||
'02000000000102f1155666b534f7cb476a0523a45dc8731d38d56b5b08e877c968812423fbd7f3010000000000000000d8a2882a692ee759b43e6af48ac152dd3410cc4b7d25031e83b3396c16ffbc8900000000000000000002400d03000000000017a914e286d58e53f9247a4710e51232cce0686f16873c870695010000000000160014d3e2ecbf4d91321794e0297e0284c47527cf878b02483045022100d18dc865fb4d087004d021d480b983b8afb177a1934ce4cd11cf97b03e17944f02206d7310687a84aab5d4696d535bca69c2db4449b48feb55fff028aa004f2d1744012103af4b208608c75f38e78f6e5abfbcad9c360fb60d3e035193b2cd0cdc8fc0155c0247304402207556e859845df41d897fe442f59b6106c8fa39c74ba5b7b8e3268ab0aebf186f0220048a9f3742339c44a1e5c78b491822b96070bcfda3f64db9dc6434f8e8068475012102456e5223ed3884dc6b0e152067fd836e3eb1485422eda45558bf83f59c6ad09f00000000',
|
||||
hd,
|
||||
);
|
||||
|
||||
assert.strictEqual(tt.canCancelTx(), true);
|
||||
|
||||
let { tx } = await tt.createRBFbumpFee(17);
|
||||
|
||||
let createdTx = bitcoin.Transaction.fromHex(tx.toHex());
|
||||
assert.strictEqual(createdTx.ins.length, 2);
|
||||
assert.strictEqual(createdTx.outs.length, 2);
|
||||
let addr0 = SegwitBech32Wallet.scriptPubKeyToAddress(createdTx.outs[0].script);
|
||||
assert.ok(!hd.weOwnAddress(addr0));
|
||||
assert.strictEqual(addr0, '3NLnALo49CFEF4tCRhCvz45ySSfz3UktZC'); // dest address
|
||||
let addr1 = SegwitBech32Wallet.scriptPubKeyToAddress(createdTx.outs[1].script);
|
||||
assert.ok(hd.weOwnAddress(addr1));
|
||||
|
||||
let actualFeerate = (108150 + 200000 - (createdTx.outs[0].value + createdTx.outs[1].value)) / (tx.toHex().length / 2);
|
||||
assert.strictEqual(Math.round(actualFeerate), 17);
|
||||
|
||||
let tt2 = new HDSegwitBech32Transaction(tx.toHex(), hd);
|
||||
assert.strictEqual(tt2.canCancelTx(), true); // new tx is still cancellable since we only bumped fees
|
||||
});
|
||||
});
|
|
@ -227,6 +227,10 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'zpub6qoWjSiZRHzSYPGYJ6EzxEXJXP1b2Rj9syWwJZFNCmupMwkbSAWSBk3UvSkJyQLEhQpaBAwvhmNj3HPKpwCJiTBB9Tutt46FtEmjL2DoU3J',
|
||||
);
|
||||
|
||||
let start = +new Date();
|
||||
await hd.fetchBalance();
|
||||
|
@ -284,7 +288,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||
|
||||
let { tx, inputs, outputs, fee } = hd.createTransaction(
|
||||
hd.getUtxo(),
|
||||
[{ address: 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu', value: 101000 }],
|
||||
[{ address: 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu', value: 51000 }],
|
||||
13,
|
||||
changeAddress,
|
||||
);
|
||||
|
@ -296,6 +300,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||
totalInput += inp.value;
|
||||
}
|
||||
|
||||
assert.strictEqual(outputs.length, 2);
|
||||
let totalOutput = 0;
|
||||
for (let outp of outputs) {
|
||||
totalOutput += outp.value;
|
Loading…
Add table
Reference in a new issue