opnet
Version:
The perfect library for building Bitcoin-based applications.
119 lines (99 loc) • 3.99 kB
text/typescript
import { Network, PsbtOutputExtended } from '@btc-vision/bitcoin';
import { AddressTypes, AddressVerificator, UTXO } from '@btc-vision/transaction';
export class TransactionHelper {
public static estimateMiningCost(
utxos: readonly UTXO[],
extraOutputs: readonly PsbtOutputExtended[],
opReturnLen: number,
network: Network,
feeRate: number, // sat / vB
): bigint {
const vBytes = this.estimateVBytes(utxos, extraOutputs, opReturnLen, network);
return BigInt(Math.ceil(vBytes * feeRate));
}
private static varIntLen(n: number): number {
return n < 0xfd ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9;
}
private static estimateVBytes(
utxos: readonly UTXO[],
extraOutputs: readonly PsbtOutputExtended[],
scriptLength: number,
network: Network,
): number {
const INPUT_WU: Record<AddressTypes, number> = {
[]: 148 * 4,
[]: 91 * 4 + 107,
[]: 41 * 4 + 107,
[]: 41 * 4 + 65,
[]: 148 * 4,
[]: 41 * 4 + (1 + 73 + 1 + 33),
[]: 41 * 4 + 107,
[]: 41 * 4 + 253,
};
const OUTPUT_BYTES: Record<AddressTypes, number> = {
[]: 34,
[]: 32,
[]: 31,
[]: 43,
[]: 34,
[]: 32,
[]: 43,
[]: 43,
};
const ins: readonly UTXO[] = utxos.length ? utxos : new Array(3).fill(null); // simulate 3 taproot
let weight = 0;
// version + locktime
weight += 8 * 4;
// segwit marker+flag
const usesWitness =
utxos.length === 0 ||
utxos.some((u) => {
const t = AddressVerificator.detectAddressType(
u?.scriptPubKey?.address ?? '',
network,
);
return (
t === AddressTypes.P2WPKH ||
t === AddressTypes.P2SH_OR_P2SH_P2WPKH ||
t === AddressTypes.P2TR ||
t === AddressTypes.P2OP ||
t === AddressTypes.P2WSH
);
});
if (usesWitness) weight += 2 * 4;
// var-int counts
weight += this.varIntLen(ins.length) * 4;
weight += this.varIntLen(extraOutputs.length) * 4;
// inputs
for (const u of ins) {
const t =
utxos.length === 0
? AddressTypes.P2TR
: (AddressVerificator.detectAddressType(
u?.scriptPubKey?.address ?? '',
network,
) ?? AddressTypes.P2PKH);
weight += INPUT_WU[t] ?? 110 * 4;
}
// caller-supplied outputs
for (const o of extraOutputs) {
if ('address' in o) {
const t =
AddressVerificator.detectAddressType(o.address, network) ?? AddressTypes.P2PKH;
weight += (OUTPUT_BYTES[t] ?? 40) * 4;
} else if ('script' in o) {
const scriptLen = o.script.length;
const bytes = 8 + this.varIntLen(scriptLen) + scriptLen;
weight += bytes * 4;
} else {
weight += 34 * 4;
}
}
const witnessBytes = 1 + 3 * (this.varIntLen(32) + 32);
weight += witnessBytes;
const stackItemScript = this.varIntLen(scriptLength) + scriptLength;
const controlBlock = 1 + 33;
weight += stackItemScript + controlBlock;
return Math.ceil(weight / 4);
}
}