mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
ADD: technical release to be able to send outputs with custom scripts (OP_RETURNs in particular)
This commit is contained in:
parent
f37818a956
commit
472cef126c
@ -6,7 +6,7 @@ import * as bip39 from 'bip39';
|
|||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import { Transaction as BTransaction, Psbt } from 'bitcoinjs-lib';
|
import { Transaction as BTransaction, Psbt } from 'bitcoinjs-lib';
|
||||||
import b58 from 'bs58check';
|
import b58 from 'bs58check';
|
||||||
import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect';
|
import { CoinSelectReturnInput } from 'coinselect';
|
||||||
import { ECPairFactory } from 'ecpair';
|
import { ECPairFactory } from 'ecpair';
|
||||||
import { ECPairInterface } from 'ecpair/src/ecpair';
|
import { ECPairInterface } from 'ecpair/src/ecpair';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import { ElectrumHistory } from '../../blue_modules/BlueElectrum';
|
|||||||
import ecc from '../../blue_modules/noble_ecc';
|
import ecc from '../../blue_modules/noble_ecc';
|
||||||
import { randomBytes } from '../rng';
|
import { randomBytes } from '../rng';
|
||||||
import { AbstractHDWallet } from './abstract-hd-wallet';
|
import { AbstractHDWallet } from './abstract-hd-wallet';
|
||||||
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
|
import { CreateTransactionResult, CreateTransactionTarget, CreateTransactionUtxo, Transaction, Utxo } from './types';
|
||||||
|
|
||||||
const ECPair = ECPairFactory(ecc);
|
const ECPair = ECPairFactory(ecc);
|
||||||
const bip32 = BIP32Factory(ecc);
|
const bip32 = BIP32Factory(ecc);
|
||||||
@ -52,10 +52,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
// BIP47
|
// BIP47
|
||||||
_enable_BIP47: boolean;
|
_enable_BIP47: boolean;
|
||||||
_payment_code: string;
|
_payment_code: string;
|
||||||
_sender_payment_codes: string[];
|
_sender_payment_codes: string[]; // who can pay us
|
||||||
|
_payer_payment_codes: string[]; // whom can we pay
|
||||||
_addresses_by_payment_code: Record<string, string[]>;
|
_addresses_by_payment_code: Record<string, string[]>;
|
||||||
_next_free_payment_code_address_index: Record<string, number>;
|
_next_free_payment_code_address_index: Record<string, number>;
|
||||||
_txs_by_payment_code_index: Record<string, Transaction[][]>;
|
_txs_by_payment_code_index: Record<string, Transaction[][]>; // incoming tx e.g. someone paid us
|
||||||
_balances_by_payment_code_index: Record<string, BalanceByIndex>;
|
_balances_by_payment_code_index: Record<string, BalanceByIndex>;
|
||||||
_bip47_instance?: BIP47Interface;
|
_bip47_instance?: BIP47Interface;
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
this._enable_BIP47 = false;
|
this._enable_BIP47 = false;
|
||||||
this._payment_code = '';
|
this._payment_code = '';
|
||||||
this._sender_payment_codes = [];
|
this._sender_payment_codes = [];
|
||||||
|
this._payer_payment_codes = [];
|
||||||
this._next_free_payment_code_address_index = {};
|
this._next_free_payment_code_address_index = {};
|
||||||
this._txs_by_payment_code_index = {};
|
this._txs_by_payment_code_index = {};
|
||||||
this._balances_by_payment_code_index = {};
|
this._balances_by_payment_code_index = {};
|
||||||
@ -1108,7 +1110,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
*/
|
*/
|
||||||
createTransaction(
|
createTransaction(
|
||||||
utxos: CreateTransactionUtxo[],
|
utxos: CreateTransactionUtxo[],
|
||||||
targets: CoinSelectTarget[],
|
targets: CreateTransactionTarget[],
|
||||||
feeRate: number,
|
feeRate: number,
|
||||||
changeAddress: string,
|
changeAddress: string,
|
||||||
sequence: number,
|
sequence: number,
|
||||||
@ -1128,13 +1130,18 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const t of targets) {
|
for (const t of targets) {
|
||||||
if (t.address.startsWith('bc1')) {
|
if (t.address && t.address.startsWith('bc1')) {
|
||||||
// in case address is non-typical and takes more bytes than coinselect library anticipates by default
|
// in case address is non-typical and takes more bytes than coinselect library anticipates by default
|
||||||
t.script = { length: bitcoin.address.toOutputScript(t.address).length + 3 };
|
t.script = { length: bitcoin.address.toOutputScript(t.address).length + 3 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (t.script?.hex) {
|
||||||
|
// setting length for coinselect lib manually as it is not aware of our field `hex`
|
||||||
|
t.script.length = t.script.hex.length / 2 - 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
|
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate);
|
||||||
|
|
||||||
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
||||||
let psbt = new bitcoin.Psbt();
|
let psbt = new bitcoin.Psbt();
|
||||||
@ -1172,9 +1179,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
});
|
});
|
||||||
|
|
||||||
outputs.forEach(output => {
|
outputs.forEach(output => {
|
||||||
// if output has no address - this is change output
|
// if output has no address - this is change output or a custom script output
|
||||||
let change = false;
|
let change = false;
|
||||||
if (!output.address) {
|
// @ts-ignore
|
||||||
|
if (!output.address && !output.script?.hex) {
|
||||||
change = true;
|
change = true;
|
||||||
output.address = changeAddress;
|
output.address = changeAddress;
|
||||||
}
|
}
|
||||||
@ -1197,6 +1205,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||||||
|
|
||||||
psbt.addOutput({
|
psbt.addOutput({
|
||||||
address: output.address,
|
address: output.address,
|
||||||
|
// @ts-ignore types from bitcoinjs are not exported so we cant define outputData separately and add fields conditionally (either address or script should be present)
|
||||||
|
script: output.script?.hex ? Buffer.from(output.script.hex, 'hex') : undefined,
|
||||||
value: output.value,
|
value: output.value,
|
||||||
bip32Derivation:
|
bip32Derivation:
|
||||||
change && path && pubkey
|
change && path && pubkey
|
||||||
|
@ -5,9 +5,9 @@ import { AbstractWallet } from './abstract-wallet';
|
|||||||
import { HDSegwitBech32Wallet } from '..';
|
import { HDSegwitBech32Wallet } from '..';
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||||
import coinSelect, { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget, CoinSelectUtxo } from 'coinselect';
|
import coinSelect, { CoinSelectOutput, CoinSelectReturnInput, CoinSelectTarget } from 'coinselect';
|
||||||
import coinSelectSplit from 'coinselect/split';
|
import coinSelectSplit from 'coinselect/split';
|
||||||
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
|
import { CreateTransactionResult, CreateTransactionTarget, CreateTransactionUtxo, Transaction, Utxo } from './types';
|
||||||
import { ECPairAPI, ECPairFactory, Signer } from 'ecpair';
|
import { ECPairAPI, ECPairFactory, Signer } from 'ecpair';
|
||||||
|
|
||||||
import ecc from '../../blue_modules/noble_ecc';
|
import ecc from '../../blue_modules/noble_ecc';
|
||||||
@ -369,24 +369,21 @@ export class LegacyWallet extends AbstractWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coinselect(
|
coinselect(
|
||||||
utxos: CoinSelectUtxo[],
|
utxos: CreateTransactionUtxo[],
|
||||||
targets: CoinSelectTarget[],
|
targets: CreateTransactionTarget[],
|
||||||
feeRate: number,
|
feeRate: number,
|
||||||
changeAddress: string,
|
|
||||||
): {
|
): {
|
||||||
inputs: CoinSelectReturnInput[];
|
inputs: CoinSelectReturnInput[];
|
||||||
outputs: CoinSelectOutput[];
|
outputs: CoinSelectOutput[];
|
||||||
fee: number;
|
fee: number;
|
||||||
} {
|
} {
|
||||||
if (!changeAddress) throw new Error('No change address provided');
|
|
||||||
|
|
||||||
let algo = coinSelect;
|
let algo = coinSelect;
|
||||||
// if targets has output without a value, we want send MAX to it
|
// if targets has output without a value, we want send MAX to it
|
||||||
if (targets.some(i => !('value' in i))) {
|
if (targets.some(i => !('value' in i))) {
|
||||||
algo = coinSelectSplit;
|
algo = coinSelectSplit;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { inputs, outputs, fee } = algo(utxos, targets, feeRate);
|
const { inputs, outputs, fee } = algo(utxos, targets as CoinSelectTarget[], feeRate);
|
||||||
|
|
||||||
// .inputs and .outputs will be undefined if no solution was found
|
// .inputs and .outputs will be undefined if no solution was found
|
||||||
if (!inputs || !outputs) {
|
if (!inputs || !outputs) {
|
||||||
@ -417,7 +414,7 @@ export class LegacyWallet extends AbstractWallet {
|
|||||||
masterFingerprint: number,
|
masterFingerprint: number,
|
||||||
): CreateTransactionResult {
|
): CreateTransactionResult {
|
||||||
if (targets.length === 0) throw new Error('No destination provided');
|
if (targets.length === 0) throw new Error('No destination provided');
|
||||||
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
|
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate);
|
||||||
sequence = sequence || 0xffffffff; // disable RBF by default
|
sequence = sequence || 0xffffffff; // disable RBF by default
|
||||||
const psbt = new bitcoin.Psbt();
|
const psbt = new bitcoin.Psbt();
|
||||||
let c = 0;
|
let c = 0;
|
||||||
|
@ -957,7 +957,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
|
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate);
|
||||||
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
||||||
|
|
||||||
let psbt = new bitcoin.Psbt();
|
let psbt = new bitcoin.Psbt();
|
||||||
|
@ -84,7 +84,7 @@ export class SegwitBech32Wallet extends LegacyWallet {
|
|||||||
for (const u of utxos) {
|
for (const u of utxos) {
|
||||||
u.script = { length: 27 };
|
u.script = { length: 27 };
|
||||||
}
|
}
|
||||||
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
|
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate);
|
||||||
sequence = sequence || 0xffffffff; // disable RBF by default
|
sequence = sequence || 0xffffffff; // disable RBF by default
|
||||||
const psbt = new bitcoin.Psbt();
|
const psbt = new bitcoin.Psbt();
|
||||||
let c = 0;
|
let c = 0;
|
||||||
|
@ -102,7 +102,7 @@ export class SegwitP2SHWallet extends LegacyWallet {
|
|||||||
for (const u of utxos) {
|
for (const u of utxos) {
|
||||||
u.script = { length: 50 };
|
u.script = { length: 50 };
|
||||||
}
|
}
|
||||||
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
|
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate);
|
||||||
sequence = sequence || 0xffffffff; // disable RBF by default
|
sequence = sequence || 0xffffffff; // disable RBF by default
|
||||||
const psbt = new bitcoin.Psbt();
|
const psbt = new bitcoin.Psbt();
|
||||||
let c = 0;
|
let c = 0;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import bitcoin from 'bitcoinjs-lib';
|
import bitcoin from 'bitcoinjs-lib';
|
||||||
import { CoinSelectOutput, CoinSelectReturnInput } from 'coinselect';
|
import { CoinSelectOutput, CoinSelectReturnInput, CoinSelectUtxo } from 'coinselect';
|
||||||
import { HDAezeedWallet } from './hd-aezeed-wallet';
|
import { HDAezeedWallet } from './hd-aezeed-wallet';
|
||||||
import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
|
import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
|
||||||
import { HDLegacyElectrumSeedP2PKHWallet } from './hd-legacy-electrum-seed-p2pkh-wallet';
|
import { HDLegacyElectrumSeedP2PKHWallet } from './hd-legacy-electrum-seed-p2pkh-wallet';
|
||||||
@ -31,16 +31,19 @@ export type Utxo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* basically the same as coinselect.d.ts/CoinselectUtxo
|
* same as coinselect.d.ts/CoinSelectUtxo
|
||||||
* and should be unified as soon as bullshit with txid/txId is sorted
|
|
||||||
*/
|
*/
|
||||||
export type CreateTransactionUtxo = {
|
export interface CreateTransactionUtxo extends CoinSelectUtxo {}
|
||||||
txid: string;
|
|
||||||
txhex: string;
|
/**
|
||||||
vout: number;
|
* if address is missing and `script.hex` is set - this is a custom script (like OP_RETURN)
|
||||||
value: number;
|
*/
|
||||||
|
export type CreateTransactionTarget = {
|
||||||
|
address?: string;
|
||||||
|
value?: number;
|
||||||
script?: {
|
script?: {
|
||||||
length: number;
|
length?: number; // either length or hex should be present
|
||||||
|
hex?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import BIP47Factory from '@spsina/bip47';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import ecc from 'tiny-secp256k1';
|
import ecc from '../../blue_modules/noble_ecc';
|
||||||
|
|
||||||
const ECPair = ECPairFactory(ecc);
|
const ECPair = ECPairFactory(ecc);
|
||||||
|
|
||||||
jest.setTimeout(90 * 1000);
|
jest.setTimeout(90 * 1000);
|
||||||
@ -135,5 +136,11 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||||||
?.scriptPubKey.hex,
|
?.scriptPubKey.hex,
|
||||||
'6a4c50' + blindedPaymentCode,
|
'6a4c50' + blindedPaymentCode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
w.getTransactions().find(tx => tx.txid === '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f')?.outputs?.[1]
|
||||||
|
.scriptPubKey.addresses[0],
|
||||||
|
bobBip47.getNotificationAddress(),
|
||||||
|
); // transaction is to Bob's notification address
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BIP47Factory from '@spsina/bip47';
|
import BIP47Factory from '@spsina/bip47';
|
||||||
import ecc from 'tiny-secp256k1';
|
import ecc from '../../blue_modules/noble_ecc';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||||
@ -23,6 +23,8 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||||||
|
|
||||||
expect(bobNotificationAddress).toEqual('1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV'); // our notif address
|
expect(bobNotificationAddress).toEqual('1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV'); // our notif address
|
||||||
|
|
||||||
|
assert.strictEqual(bobWallet.getBIP47NotificationAddress(), '1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV'); // our notif address
|
||||||
|
|
||||||
assert.ok(!bobWallet.weOwnAddress('1JDdmqFLhpzcUwPeinhJbUPw4Co3aWLyzW')); // alice notif address, we dont own it
|
assert.ok(!bobWallet.weOwnAddress('1JDdmqFLhpzcUwPeinhJbUPw4Co3aWLyzW')); // alice notif address, we dont own it
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,6 +70,8 @@ describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|||||||
|
|
||||||
expect(ourNotificationAddress).toEqual('1EiP2kSqxNqRhn8MPMkrtSEqaWiCWLYyTS'); // our notif address
|
expect(ourNotificationAddress).toEqual('1EiP2kSqxNqRhn8MPMkrtSEqaWiCWLYyTS'); // our notif address
|
||||||
|
|
||||||
|
assert.strictEqual(w.getBIP47NotificationAddress(), '1EiP2kSqxNqRhn8MPMkrtSEqaWiCWLYyTS'); // our notif address
|
||||||
|
|
||||||
// since we dont do network calls in unit test we cant get counterparties payment codes from our notif address,
|
// since we dont do network calls in unit test we cant get counterparties payment codes from our notif address,
|
||||||
// and thus, dont know collaborative addresses with our payers. lets hardcode our counterparty payment code to test
|
// and thus, dont know collaborative addresses with our payers. lets hardcode our counterparty payment code to test
|
||||||
// this functionality
|
// this functionality
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { HDSegwitBech32Wallet } from '../../class';
|
import { HDSegwitBech32Wallet } from '../../class';
|
||||||
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
|
|
||||||
describe('Bech32 Segwit HD (BIP84)', () => {
|
describe('Bech32 Segwit HD (BIP84)', () => {
|
||||||
it('can create', async function () {
|
it('can create', async function () {
|
||||||
@ -213,7 +214,7 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||||||
assert.notStrictEqual(id1, id3);
|
assert.notStrictEqual(id1, id3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cat createTransaction with a correct feerate (with lenghty segwit address)', () => {
|
it('can createTransaction with a correct feerate (with lenghty segwit address)', () => {
|
||||||
if (!process.env.HD_MNEMONIC_BIP84) {
|
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||||
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||||
return;
|
return;
|
||||||
@ -232,18 +233,71 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const { tx, psbt } = hd.createTransaction(
|
const { tx, psbt, outputs } = hd.createTransaction(
|
||||||
utxo,
|
utxo,
|
||||||
[{ address: 'bc1qtmcfj7lvgjp866w8lytdpap82u7eege58jy52hp4ctk0hsncegyqel8prp' }], // sendMAX
|
[{ address: 'bc1qtmcfj7lvgjp866w8lytdpap82u7eege58jy52hp4ctk0hsncegyqel8prp', value: 546 }],
|
||||||
1,
|
10,
|
||||||
'bc1qtmcfj7lvgjp866w8lytdpap82u7eege58jy52hp4ctk0hsncegyqel8prp', // change wont actually be used
|
'bc1qtmcfj7lvgjp866w8lytdpap82u7eege58jy52hp4ctk0hsncegyqel8prp',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(outputs.length, 2);
|
||||||
|
|
||||||
const actualFeerate = psbt.getFee() / tx.virtualSize();
|
const actualFeerate = psbt.getFee() / tx.virtualSize();
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
actualFeerate >= 1.0,
|
Math.round(actualFeerate) >= 10 && actualFeerate <= 11,
|
||||||
true,
|
true,
|
||||||
`bad feerate, got ${actualFeerate}, expected at least 1; fee: ${psbt.getFee()}; virsualSize: ${tx.virtualSize()} vbytes; ${tx.toHex()}`,
|
`bad feerate, got ${actualFeerate}, expected at least 10; fee: ${psbt.getFee()}; virsualSize: ${tx.virtualSize()} vbytes; ${tx.toHex()}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can createTransaction with OP_RETURN', () => {
|
||||||
|
if (!process.env.HD_MNEMONIC_BIP84) {
|
||||||
|
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hd = new HDSegwitBech32Wallet();
|
||||||
|
hd.setSecret(process.env.HD_MNEMONIC_BIP84);
|
||||||
|
assert.ok(hd.validateMnemonic());
|
||||||
|
|
||||||
|
const utxo = [
|
||||||
|
{
|
||||||
|
address: 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl',
|
||||||
|
vout: 0,
|
||||||
|
txid: '8b0ab2c7196312e021e0d3dc73f801693826428782970763df6134457bd2ec20',
|
||||||
|
value: 69909,
|
||||||
|
wif: '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { tx, psbt, outputs } = hd.createTransaction(
|
||||||
|
utxo,
|
||||||
|
[
|
||||||
|
{ address: hd._getExternalAddressByIndex(0), value: 546 },
|
||||||
|
{ script: { hex: '00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff' }, value: 0 },
|
||||||
|
],
|
||||||
|
150,
|
||||||
|
hd._getInternalAddressByIndex(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(outputs.length, 3); // destination, op_return, change
|
||||||
|
assert.ok(!outputs[1].address); // should not be there as it should be OP_RETURN
|
||||||
|
|
||||||
|
const decodedTx = bitcoin.Transaction.fromHex(tx.toHex());
|
||||||
|
// console.log(decodedTx.outs);
|
||||||
|
|
||||||
|
assert.strictEqual(decodedTx.outs[0].value, 546); // first output - destination
|
||||||
|
assert.strictEqual(decodedTx.outs[1].value, 0); // second output - op_return
|
||||||
|
assert.ok(decodedTx.outs[2].value > 0); // third output - change
|
||||||
|
|
||||||
|
assert.strictEqual(decodedTx.outs[1].script.toString('hex'), '00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff'); // custom script that we are passing
|
||||||
|
|
||||||
|
// console.log(outputs);
|
||||||
|
|
||||||
|
const actualFeerate = psbt.getFee() / tx.virtualSize();
|
||||||
|
assert.strictEqual(
|
||||||
|
Math.round(actualFeerate) >= 150 && actualFeerate < 151,
|
||||||
|
true,
|
||||||
|
`bad feerate, got ${actualFeerate}, expected at least 11; fee: ${psbt.getFee()}; virsualSize: ${tx.virtualSize()} vbytes; ${tx.toHex()}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
4
typings/coinselect.d.ts
vendored
4
typings/coinselect.d.ts
vendored
@ -7,6 +7,10 @@ declare module 'coinselect' {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* not an accurate definition since coinselect lib can ignore certain fields, and just passes through unknown fields,
|
||||||
|
* which we actually rely on
|
||||||
|
*/
|
||||||
export type CoinSelectUtxo = {
|
export type CoinSelectUtxo = {
|
||||||
vout: number;
|
vout: number;
|
||||||
value: number;
|
value: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user