Skip to content

Commit

Permalink
Feat: Add dust calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
junderw committed Dec 3, 2023
1 parent 31b6c27 commit 4651424
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/address.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export declare function fromBech32(address: string): Bech32Result;
export declare function toBase58Check(hash: Buffer, version: number): string;
export declare function toBech32(data: Buffer, version: number, prefix: string): string;
export declare function fromOutputScript(output: Buffer, network?: Network): string;
export declare function dustAmountFromOutputScript(script: Buffer, satPerKvb?: number): number;
export declare function toOutputScript(address: string, network?: Network): Buffer;
34 changes: 34 additions & 0 deletions src/address.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.toOutputScript =
exports.dustAmountFromOutputScript =
exports.fromOutputScript =
exports.toBech32 =
exports.toBase58Check =
exports.fromBech32 =
exports.fromBase58Check =
void 0;
const networks = require('./networks');
const ops_1 = require('./ops');
const payments = require('./payments');
const bscript = require('./script');
const types_1 = require('./types');
const varuint = require('bip174/src/lib/converter/varint');
const bech32_1 = require('bech32');
const bs58check = require('bs58check');
const FUTURE_SEGWIT_MAX_SIZE = 40;
Expand Down Expand Up @@ -116,6 +119,37 @@ function fromOutputScript(output, network) {
throw new Error(bscript.toASM(output) + ' has no matching Address');
}
exports.fromOutputScript = fromOutputScript;
/*
* This uses the logic from Bitcoin Core to decide what is the dust threshold for a given script.
*
* Ref: https://github.com/bitcoin/bitcoin/blob/160d23677ad799cf9b493eaa923b2ac080c3fb8e/src/policy/policy.cpp#L26-L63
*/
function dustAmountFromOutputScript(script, satPerKvb = 1000) {
// If unspendable, return 0
// https://github.com/bitcoin/bitcoin/blob/160d23677ad799cf9b493eaa923b2ac080c3fb8e/src/script/script.h#L554C16-L554C84
// (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
if (
(script.length > 0 && script[0] == ops_1.OPS.OP_RETURN) ||
script.length > 10000
) {
return 0;
}
const inputBytes = isSegwit(script) ? 67 : 148;
const outputBytes = script.length + 8 + varuint.encodingLength(script.length);
return Math.ceil((inputBytes + outputBytes) * 3 * (satPerKvb / 1000));
}
exports.dustAmountFromOutputScript = dustAmountFromOutputScript;
function isSegwit(script) {
if (script.length < 4 || script.length > 42) return false;
if (
script[0] !== ops_1.OPS.OP_0 &&
script[0] < ops_1.OPS.OP_1 &&
script[0] > ops_1.OPS.OP_16
)
return false;
if (script[1] + 2 !== script.length) return false;
return true;
}
function toOutputScript(address, network) {
network = network || networks.bitcoin;
let decodeBase58;
Expand Down
45 changes: 45 additions & 0 deletions test/address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,49 @@ describe('address', () => {
});
});
});

describe('dustAmountFromOutputScript', () => {
it('gets correct values', () => {
const vectors = [
// OP_RETURN is always 0 regardless of size
[Buffer.from('6a04deadbeef', 'hex'), 1000, 0],
[Buffer.from('6a08deadbeefdeadbeef', 'hex'), 1000, 0],
// 3 byte non-segwit output is 3 + 1 + 8 + 148 = 160 * 3 = 480
[Buffer.from('020102', 'hex'), 1000, 480],
// * 2 the feerate, * 2 the result
[Buffer.from('020102', 'hex'), 2000, 960],
// P2PKH is 546 (well known)
[
Buffer.from(
'76a914b6211d1f14f26ea4aed0e4a55e56e82656c7233d88ac',
'hex',
),
1000,
546,
],
// P2WPKH is 294 (mentioned in Core comments)
[
Buffer.from('00145f72106b919817aa740fc655cce1a59f2d804e16', 'hex'),
1000,
294,
],
// P2TR (and P2WSH) is
[
Buffer.from(
'51208215bbb39e58fc799515d72a76a29400c146f7044dcf44925877ed3219782963',
'hex',
),
1000,
330,
],
] as const;

for (const [script, feeRatekvB, expected] of vectors) {
assert.strictEqual(
baddress.dustAmountFromOutputScript(script, feeRatekvB),
expected,
);
}
});
});
});
35 changes: 35 additions & 0 deletions ts_src/address.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Network } from './networks';
import * as networks from './networks';
import { OPS } from './ops';
import * as payments from './payments';
import * as bscript from './script';
import { typeforce, tuple, Hash160bit, UInt8 } from './types';
import * as varuint from 'bip174/src/lib/converter/varint';
import { bech32, bech32m } from 'bech32';
import * as bs58check from 'bs58check';
export interface Base58CheckResult {
Expand Down Expand Up @@ -139,6 +141,39 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
throw new Error(bscript.toASM(output) + ' has no matching Address');
}

/*
* This uses the logic from Bitcoin Core to decide what is the dust threshold for a given script.
*
* Ref: https://github.com/bitcoin/bitcoin/blob/160d23677ad799cf9b493eaa923b2ac080c3fb8e/src/policy/policy.cpp#L26-L63
*/
export function dustAmountFromOutputScript(
script: Buffer,
satPerKvb: number = 1000,
): number {
// If unspendable, return 0
// https://github.com/bitcoin/bitcoin/blob/160d23677ad799cf9b493eaa923b2ac080c3fb8e/src/script/script.h#L554C16-L554C84
// (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
if (
(script.length > 0 && script[0] == OPS.OP_RETURN) ||
script.length > 10000
) {
return 0;
}

const inputBytes = isSegwit(script) ? 67 : 148;
const outputBytes = script.length + 8 + varuint.encodingLength(script.length);

return Math.ceil((inputBytes + outputBytes) * 3 * (satPerKvb / 1000));
}

function isSegwit(script: Buffer): boolean {
if (script.length < 4 || script.length > 42) return false;
if (script[0] !== OPS.OP_0 && script[0] < OPS.OP_1 && script[0] > OPS.OP_16)
return false;
if (script[1] + 2 !== script.length) return false;
return true;
}

export function toOutputScript(address: string, network?: Network): Buffer {
network = network || networks.bitcoin;

Expand Down

0 comments on commit 4651424

Please sign in to comment.