opnet
Version:
The perfect library for building Bitcoin-based applications.
114 lines (94 loc) • 3.76 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> = {
[AddressTypes.P2PKH]: 148 * 4,
[AddressTypes.P2SH_OR_P2SH_P2WPKH]: 91 * 4 + 107,
[AddressTypes.P2WPKH]: 41 * 4 + 107,
[AddressTypes.P2TR]: 41 * 4 + 65,
[AddressTypes.P2PK]: 148 * 4,
[AddressTypes.P2OP]: 41 * 4 + 107,
};
const OUTPUT_BYTES: Record<AddressTypes, number> = {
[AddressTypes.P2PKH]: 34,
[AddressTypes.P2SH_OR_P2SH_P2WPKH]: 32,
[AddressTypes.P2WPKH]: 31,
[AddressTypes.P2TR]: 43,
[AddressTypes.P2PK]: 34,
[AddressTypes.P2OP]: 32,
};
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
);
});
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);
}
}