@atomiqlabs/sdk-lib
Version:
Basic SDK functionality library for atomiq
180 lines (154 loc) • 4.68 kB
text/typescript
// baseline estimates, used to improve performance
const TX_EMPTY_SIZE = 4 + 1 + 1 + 4;
const TX_INPUT_BASE = 32 + 4 + 1 + 4;
const WITNESS_OVERHEAD = 2/4;
const P2WPKH_WITNESS = (1+1+72+1+33)/4;
const P2TR_WITNESS = (1+1+65)/4;
const TX_INPUT_PUBKEYHASH = 107;
const TX_INPUT_P2SH_P2WPKH = 23 + P2WPKH_WITNESS + 1;
const TX_INPUT_P2WPKH = 0 + P2WPKH_WITNESS;
const TX_INPUT_P2WSH = 0 + (1+1+64)/4;
const TX_INPUT_P2TR = 0 + P2TR_WITNESS;
const TX_OUTPUT_BASE = 8 + 1;
const TX_OUTPUT_PUBKEYHASH = 25;
const TX_OUTPUT_P2SH_P2WPKH = 23;
const TX_OUTPUT_P2WPKH = 22;
const TX_OUTPUT_P2WSH = 34;
const TX_OUTPUT_P2TR = 34;
export type CoinselectAddressTypes = "p2sh-p2wpkh" | "p2wpkh" | "p2wsh" | "p2tr" | "p2pkh";
export type CoinselectTxInput = {
script?: Buffer,
txId: string,
vout: number,
type?: CoinselectAddressTypes,
value: number,
outputScript?: Buffer,
address?: string,
cpfp?: {
txVsize: number,
txEffectiveFeeRate: number
}
};
export type CoinselectTxOutput = {
script?: Buffer,
address?: string,
type?: CoinselectAddressTypes,
value: number
};
const INPUT_BYTES = {
"p2sh-p2wpkh": TX_INPUT_P2SH_P2WPKH,
"p2wpkh": TX_INPUT_P2WPKH,
"p2tr": TX_INPUT_P2TR,
"p2pkh": TX_INPUT_PUBKEYHASH,
"p2wsh": TX_INPUT_P2WSH
};
function inputBytes (input: {
script?: Buffer,
type?: CoinselectAddressTypes
}) {
return TX_INPUT_BASE + (input.script ? input.script.length : INPUT_BYTES[input.type]);
}
const OUTPUT_BYTES = {
"p2sh-p2wpkh": TX_OUTPUT_P2SH_P2WPKH,
"p2wpkh": TX_OUTPUT_P2WPKH,
"p2tr": TX_OUTPUT_P2TR,
"p2pkh": TX_OUTPUT_PUBKEYHASH,
"p2wsh": TX_OUTPUT_P2WSH
};
function outputBytes (output: {
script?: Buffer,
type?: CoinselectAddressTypes
}): number {
return TX_OUTPUT_BASE + (output.script ? output.script.length : OUTPUT_BYTES[output.type]);
}
export const DUST_THRESHOLDS = {
"p2sh-p2wpkh": 540,
"p2wpkh": 294,
"p2tr": 330,
"p2pkh": 546,
"p2wsh": 330
};
function dustThreshold (output: {
script?: Buffer,
type: CoinselectAddressTypes
}): number {
return DUST_THRESHOLDS[output.type];
}
function transactionBytes (
inputs: {
script?: Buffer,
type?: CoinselectAddressTypes
}[],
outputs: {
script?: Buffer,
type?: CoinselectAddressTypes
}[],
changeType: CoinselectAddressTypes
): number {
let size = TX_EMPTY_SIZE;
let isSegwit = false;
if(changeType!=="p2pkh") {
size += WITNESS_OVERHEAD;
let isSegwit = true;
}
for(let input of inputs) {
if(!isSegwit && (input.type!=="p2pkh")) {
isSegwit = true;
size += WITNESS_OVERHEAD;
}
size += inputBytes(input);
}
for(let output of outputs) {
size += outputBytes(output);
}
return Math.ceil(size);
}
function uintOrNaN(v: number): number {
if (typeof v !== 'number') return NaN;
if (!isFinite(v)) return NaN;
if (Math.floor(v) !== v) return NaN;
if (v < 0) return NaN;
return v;
}
function sumForgiving(range: {value: number}[]): number {
return range.reduce((a, x) => a + (isFinite(x.value) ? x.value : 0), 0);
}
function sumOrNaN(range: {value: number}[]): number {
return range.reduce((a, x) => a + uintOrNaN(x.value), 0);
}
function finalize(
inputs: CoinselectTxInput[],
outputs: CoinselectTxOutput[],
feeRate: number,
changeType: CoinselectAddressTypes,
cpfpAddFee: number = 0
): {
inputs?: CoinselectTxInput[],
outputs?: CoinselectTxOutput[],
fee: number
} {
const bytesAccum = transactionBytes(inputs, outputs, changeType);
const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({type: changeType}))) + cpfpAddFee;
const remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput)
// is it worth a change output?
if (remainderAfterExtraOutput >= dustThreshold({type: changeType})) {
outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType })
}
const fee = sumOrNaN(inputs) - sumOrNaN(outputs)
if (!isFinite(fee)) return { fee: (feeRate * bytesAccum) + cpfpAddFee }
return {
inputs: inputs,
outputs: outputs,
fee: fee
}
}
export const utils = {
dustThreshold: dustThreshold,
finalize: finalize,
inputBytes: inputBytes,
outputBytes: outputBytes,
sumOrNaN: sumOrNaN,
sumForgiving: sumForgiving,
transactionBytes: transactionBytes,
uintOrNaN: uintOrNaN
};