@scure/btc-signer 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -7
- package/index.ts +528 -117
- package/{index.js → lib/esm/index.js} +500 -126
- package/lib/esm/package.json +1 -0
- package/{index.d.ts → lib/index.d.ts} +199 -5
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3035 -0
- package/package.json +20 -12
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
package/index.ts
CHANGED
|
@@ -416,7 +416,7 @@ type PSBTKeyMapInfo = Readonly<
|
|
|
416
416
|
any,
|
|
417
417
|
readonly number[], // versionsRequiringInclusion
|
|
418
418
|
readonly number[], // versionsAllowsInclusion
|
|
419
|
-
boolean // silentIgnore
|
|
419
|
+
boolean, // silentIgnore
|
|
420
420
|
]
|
|
421
421
|
>;
|
|
422
422
|
|
|
@@ -1705,6 +1705,109 @@ function validateOpts(opts: TxOpts) {
|
|
|
1705
1705
|
return Object.freeze(_opts);
|
|
1706
1706
|
}
|
|
1707
1707
|
|
|
1708
|
+
// Normalizes input
|
|
1709
|
+
function getPrevOut(input: TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
1710
|
+
if (input.nonWitnessUtxo) {
|
|
1711
|
+
if (input.index === undefined) throw new Error('Unknown input index');
|
|
1712
|
+
return input.nonWitnessUtxo.outputs[input.index];
|
|
1713
|
+
} else if (input.witnessUtxo) return input.witnessUtxo;
|
|
1714
|
+
else throw new Error('Cannot find previous output info');
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function normalizeInput(
|
|
1718
|
+
i: TransactionInputUpdate,
|
|
1719
|
+
cur?: TransactionInput,
|
|
1720
|
+
allowedFields?: (keyof TransactionInput)[],
|
|
1721
|
+
disableScriptCheck = false
|
|
1722
|
+
): TransactionInput {
|
|
1723
|
+
let { nonWitnessUtxo, txid } = i;
|
|
1724
|
+
// String support for common fields. We usually prefer Uint8Array to avoid errors
|
|
1725
|
+
// like hex looking string accidentally passed, however, in case of nonWitnessUtxo
|
|
1726
|
+
// it is better to expect string, since constructing this complex object will be
|
|
1727
|
+
// difficult for user
|
|
1728
|
+
if (typeof nonWitnessUtxo === 'string') nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1729
|
+
if (isBytes(nonWitnessUtxo)) nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1730
|
+
if (!('nonWitnessUtxo' in i) && nonWitnessUtxo === undefined)
|
|
1731
|
+
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1732
|
+
if (typeof txid === 'string') txid = hex.decode(txid);
|
|
1733
|
+
// TODO: if we have nonWitnessUtxo, we can extract txId from here
|
|
1734
|
+
if (txid === undefined) txid = cur?.txid;
|
|
1735
|
+
let res: PSBTKeyMapKeys<typeof PSBTInput> = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1736
|
+
if (!('nonWitnessUtxo' in i) && res.nonWitnessUtxo === undefined) delete res.nonWitnessUtxo;
|
|
1737
|
+
if (res.sequence === undefined) res.sequence = DEFAULT_SEQUENCE;
|
|
1738
|
+
if (res.tapMerkleRoot === null) delete res.tapMerkleRoot;
|
|
1739
|
+
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
1740
|
+
PSBTInputCoder.encode(res); // Validates that everything is correct at this point
|
|
1741
|
+
|
|
1742
|
+
let prevOut;
|
|
1743
|
+
if (res.nonWitnessUtxo && res.index !== undefined)
|
|
1744
|
+
prevOut = res.nonWitnessUtxo.outputs[res.index];
|
|
1745
|
+
else if (res.witnessUtxo) prevOut = res.witnessUtxo;
|
|
1746
|
+
if (prevOut && !disableScriptCheck)
|
|
1747
|
+
checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
|
|
1748
|
+
return res;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function getInputType(input: TransactionInput, allowLegacyWitnessUtxo = false) {
|
|
1752
|
+
let txType = 'legacy';
|
|
1753
|
+
let defaultSighash = SignatureHash.ALL;
|
|
1754
|
+
const prevOut = getPrevOut(input);
|
|
1755
|
+
const first = OutScript.decode(prevOut.script);
|
|
1756
|
+
let type = first.type;
|
|
1757
|
+
let cur = first;
|
|
1758
|
+
const stack = [first];
|
|
1759
|
+
if (first.type === 'tr') {
|
|
1760
|
+
defaultSighash = SignatureHash.DEFAULT;
|
|
1761
|
+
return {
|
|
1762
|
+
txType: 'taproot',
|
|
1763
|
+
type: 'tr',
|
|
1764
|
+
last: first,
|
|
1765
|
+
lastScript: prevOut.script,
|
|
1766
|
+
defaultSighash,
|
|
1767
|
+
sighash: input.sighashType || defaultSighash,
|
|
1768
|
+
};
|
|
1769
|
+
} else {
|
|
1770
|
+
if (first.type === 'wpkh' || first.type === 'wsh') txType = 'segwit';
|
|
1771
|
+
if (first.type === 'sh') {
|
|
1772
|
+
if (!input.redeemScript) throw new Error('inputType: sh without redeemScript');
|
|
1773
|
+
let child = OutScript.decode(input.redeemScript);
|
|
1774
|
+
if (child.type === 'wpkh' || child.type === 'wsh') txType = 'segwit';
|
|
1775
|
+
stack.push(child);
|
|
1776
|
+
cur = child;
|
|
1777
|
+
type += `-${child.type}`;
|
|
1778
|
+
}
|
|
1779
|
+
// wsh can be inside sh
|
|
1780
|
+
if (cur.type === 'wsh') {
|
|
1781
|
+
if (!input.witnessScript) throw new Error('inputType: wsh without witnessScript');
|
|
1782
|
+
let child = OutScript.decode(input.witnessScript);
|
|
1783
|
+
if (child.type === 'wsh') txType = 'segwit';
|
|
1784
|
+
stack.push(child);
|
|
1785
|
+
cur = child;
|
|
1786
|
+
type += `-${child.type}`;
|
|
1787
|
+
}
|
|
1788
|
+
const last = stack[stack.length - 1];
|
|
1789
|
+
if (last.type === 'sh' || last.type === 'wsh')
|
|
1790
|
+
throw new Error('inputType: sh/wsh cannot be terminal type');
|
|
1791
|
+
const lastScript = OutScript.encode(last);
|
|
1792
|
+
const res = {
|
|
1793
|
+
type,
|
|
1794
|
+
txType,
|
|
1795
|
+
last,
|
|
1796
|
+
lastScript,
|
|
1797
|
+
defaultSighash,
|
|
1798
|
+
sighash: input.sighashType || defaultSighash,
|
|
1799
|
+
};
|
|
1800
|
+
if (txType === 'legacy' && !allowLegacyWitnessUtxo && !input.nonWitnessUtxo) {
|
|
1801
|
+
throw new Error(
|
|
1802
|
+
`Transaction/sign: legacy input without nonWitnessUtxo, can result in attack that forces paying higher fees. Pass allowLegacyWitnessUtxo=true, if you sure`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
return res;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
const toVsize = (weight: number) => Math.ceil(weight / 4);
|
|
1810
|
+
|
|
1708
1811
|
export class Transaction {
|
|
1709
1812
|
private global: PSBTKeyMapKeys<typeof PSBTGlobal> = {};
|
|
1710
1813
|
private inputs: TransactionInput[] = []; // use getInput()
|
|
@@ -1845,7 +1948,7 @@ export class Transaction {
|
|
|
1845
1948
|
// We will lose some vectors -> smaller test coverage of preimages (very important!)
|
|
1846
1949
|
private inputSighash(idx: number) {
|
|
1847
1950
|
this.checkInputIdx(idx);
|
|
1848
|
-
const sighash =
|
|
1951
|
+
const sighash = getInputType(this.inputs[idx], this.opts.allowLegacyWitnessUtxo).sighash;
|
|
1849
1952
|
// ALL or DEFAULT -- everything signed
|
|
1850
1953
|
// NONE -- all inputs + no outputs
|
|
1851
1954
|
// SINGLE -- all inputs + output with same index
|
|
@@ -1898,23 +2001,23 @@ export class Transaction {
|
|
|
1898
2001
|
// https://en.bitcoin.it/wiki/Weight_units
|
|
1899
2002
|
get weight(): number {
|
|
1900
2003
|
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
1901
|
-
// TODO: Can we find out how much witnesses/script will be used before signing?
|
|
1902
2004
|
let out = 32;
|
|
2005
|
+
// Outputs
|
|
1903
2006
|
const outputs = this.outputs.map(outputBeforeSign);
|
|
2007
|
+
out += 4 * CompactSizeLen.encode(this.outputs.length).length;
|
|
2008
|
+
for (const o of outputs) out += 32 + 4 * VarBytes.encode(o.script).length;
|
|
2009
|
+
// Inputs
|
|
1904
2010
|
if (this.hasWitnesses) out += 2;
|
|
1905
2011
|
out += 4 * CompactSizeLen.encode(this.inputs.length).length;
|
|
1906
|
-
|
|
1907
|
-
for (const i of this.inputs)
|
|
2012
|
+
for (const i of this.inputs) {
|
|
1908
2013
|
out += 160 + 4 * VarBytes.encode(i.finalScriptSig || P.EMPTY).length;
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
for (const i of this.inputs)
|
|
1912
|
-
if (i.finalScriptWitness) out += RawWitness.encode(i.finalScriptWitness).length;
|
|
2014
|
+
if (this.hasWitnesses && i.finalScriptWitness)
|
|
2015
|
+
out += RawWitness.encode(i.finalScriptWitness).length;
|
|
1913
2016
|
}
|
|
1914
2017
|
return out;
|
|
1915
2018
|
}
|
|
1916
2019
|
get vsize(): number {
|
|
1917
|
-
return
|
|
2020
|
+
return toVsize(this.weight);
|
|
1918
2021
|
}
|
|
1919
2022
|
toBytes(withScriptSig = false, withWitness = false) {
|
|
1920
2023
|
return RawTx.encode({
|
|
@@ -1957,40 +2060,10 @@ export class Transaction {
|
|
|
1957
2060
|
return this.inputs.length;
|
|
1958
2061
|
}
|
|
1959
2062
|
// Modification
|
|
1960
|
-
private normalizeInput(
|
|
1961
|
-
i: TransactionInputUpdate,
|
|
1962
|
-
cur?: TransactionInput,
|
|
1963
|
-
allowedFields?: (keyof TransactionInput)[]
|
|
1964
|
-
): TransactionInput {
|
|
1965
|
-
let { nonWitnessUtxo, txid } = i;
|
|
1966
|
-
// String support for common fields. We usually prefer Uint8Array to avoid errors (like hex looking string accidentally passed),
|
|
1967
|
-
// however in case of nonWitnessUtxo it is better to expect string, since constructing this complex object will be difficult for user
|
|
1968
|
-
if (typeof nonWitnessUtxo === 'string') nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1969
|
-
if (isBytes(nonWitnessUtxo)) nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1970
|
-
if (!('nonWitnessUtxo' in i) && nonWitnessUtxo === undefined)
|
|
1971
|
-
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1972
|
-
if (typeof txid === 'string') txid = hex.decode(txid);
|
|
1973
|
-
if (txid === undefined) txid = cur?.txid;
|
|
1974
|
-
let res: PSBTKeyMapKeys<typeof PSBTInput> = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1975
|
-
if (!('nonWitnessUtxo' in i) && res.nonWitnessUtxo === undefined) delete res.nonWitnessUtxo;
|
|
1976
|
-
if (res.sequence === undefined) res.sequence = DEFAULT_SEQUENCE;
|
|
1977
|
-
if (res.tapMerkleRoot === null) delete res.tapMerkleRoot;
|
|
1978
|
-
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
1979
|
-
PSBTInputCoder.encode(res); // Validates that everything is correct at this point
|
|
1980
|
-
|
|
1981
|
-
let prevOut;
|
|
1982
|
-
if (res.nonWitnessUtxo && res.index !== undefined)
|
|
1983
|
-
prevOut = res.nonWitnessUtxo.outputs[res.index];
|
|
1984
|
-
else if (res.witnessUtxo) prevOut = res.witnessUtxo;
|
|
1985
|
-
if (prevOut && !this.opts.disableScriptCheck)
|
|
1986
|
-
checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
|
|
1987
|
-
|
|
1988
|
-
return res;
|
|
1989
|
-
}
|
|
1990
2063
|
addInput(input: TransactionInputUpdate, _ignoreSignStatus = false): number {
|
|
1991
2064
|
if (!_ignoreSignStatus && !this.signStatus().addInput)
|
|
1992
2065
|
throw new Error('Tx has signed inputs, cannot add new one');
|
|
1993
|
-
this.inputs.push(
|
|
2066
|
+
this.inputs.push(normalizeInput(input, undefined, undefined, this.opts.disableScriptCheck));
|
|
1994
2067
|
return this.inputs.length - 1;
|
|
1995
2068
|
}
|
|
1996
2069
|
updateInput(idx: number, input: TransactionInputUpdate, _ignoreSignStatus = false) {
|
|
@@ -2000,7 +2073,12 @@ export class Transaction {
|
|
|
2000
2073
|
const status = this.signStatus();
|
|
2001
2074
|
if (!status.addInput || status.inputs.includes(idx)) allowedFields = PSBTInputUnsignedKeys;
|
|
2002
2075
|
}
|
|
2003
|
-
this.inputs[idx] =
|
|
2076
|
+
this.inputs[idx] = normalizeInput(
|
|
2077
|
+
input,
|
|
2078
|
+
this.inputs[idx],
|
|
2079
|
+
allowedFields,
|
|
2080
|
+
this.opts.disableScriptCheck
|
|
2081
|
+
);
|
|
2004
2082
|
}
|
|
2005
2083
|
// Output stuff
|
|
2006
2084
|
private checkOutputIdx(idx: number) {
|
|
@@ -2034,7 +2112,7 @@ export class Transaction {
|
|
|
2034
2112
|
OutScript.decode(res.script).type === 'unknown'
|
|
2035
2113
|
) {
|
|
2036
2114
|
throw new Error(
|
|
2037
|
-
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass
|
|
2115
|
+
'Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure'
|
|
2038
2116
|
);
|
|
2039
2117
|
}
|
|
2040
2118
|
if (!this.opts.disableScriptCheck) checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
@@ -2062,7 +2140,7 @@ export class Transaction {
|
|
|
2062
2140
|
get fee(): bigint {
|
|
2063
2141
|
let res = 0n;
|
|
2064
2142
|
for (const i of this.inputs) {
|
|
2065
|
-
const prevOut =
|
|
2143
|
+
const prevOut = getPrevOut(i);
|
|
2066
2144
|
if (!prevOut) throw new Error('Empty input amount');
|
|
2067
2145
|
res += prevOut.amount;
|
|
2068
2146
|
}
|
|
@@ -2110,7 +2188,7 @@ export class Transaction {
|
|
|
2110
2188
|
});
|
|
2111
2189
|
return sha256x2(tmpTx, P.I32LE.encode(hashType));
|
|
2112
2190
|
}
|
|
2113
|
-
|
|
2191
|
+
preimageWitnessV0(idx: number, prevOutScript: Bytes, hashType: number, amount: bigint) {
|
|
2114
2192
|
const { isAny, isNone, isSingle } = unpackSighash(hashType);
|
|
2115
2193
|
let inputHash = EMPTY32;
|
|
2116
2194
|
let sequenceHash = EMPTY32;
|
|
@@ -2139,7 +2217,7 @@ export class Transaction {
|
|
|
2139
2217
|
P.U32LE.encode(hashType)
|
|
2140
2218
|
);
|
|
2141
2219
|
}
|
|
2142
|
-
|
|
2220
|
+
preimageWitnessV1(
|
|
2143
2221
|
idx: number,
|
|
2144
2222
|
prevOutScript: Bytes[],
|
|
2145
2223
|
hashType: number,
|
|
@@ -2194,78 +2272,11 @@ export class Transaction {
|
|
|
2194
2272
|
out.push(tapLeafHash(leafScript, leafVer), P.U8.encode(0), P.I32LE.encode(codeSeparator));
|
|
2195
2273
|
return schnorr.utils.taggedHash('TapSighash', ...out);
|
|
2196
2274
|
}
|
|
2197
|
-
// Utils for sign/finalize
|
|
2198
|
-
// Used pretty often, should be fast
|
|
2199
|
-
private prevOut(input: TransactionInput): P.UnwrapCoder<typeof RawOutput> {
|
|
2200
|
-
if (input.nonWitnessUtxo) {
|
|
2201
|
-
if (input.index === undefined) throw new Error('Unknown input index');
|
|
2202
|
-
return input.nonWitnessUtxo.outputs[input.index];
|
|
2203
|
-
} else if (input.witnessUtxo) return input.witnessUtxo;
|
|
2204
|
-
else throw new Error('Cannot find previous output info');
|
|
2205
|
-
}
|
|
2206
|
-
private inputType(input: TransactionInput) {
|
|
2207
|
-
let txType = 'legacy';
|
|
2208
|
-
let defaultSighash = SignatureHash.ALL;
|
|
2209
|
-
const prevOut = this.prevOut(input);
|
|
2210
|
-
const first = OutScript.decode(prevOut.script);
|
|
2211
|
-
let type = first.type;
|
|
2212
|
-
let cur = first;
|
|
2213
|
-
const stack = [first];
|
|
2214
|
-
if (first.type === 'tr') {
|
|
2215
|
-
defaultSighash = SignatureHash.DEFAULT;
|
|
2216
|
-
return {
|
|
2217
|
-
txType: 'taproot',
|
|
2218
|
-
type: 'tr',
|
|
2219
|
-
last: first,
|
|
2220
|
-
lastScript: prevOut.script,
|
|
2221
|
-
defaultSighash,
|
|
2222
|
-
sighash: input.sighashType || defaultSighash,
|
|
2223
|
-
};
|
|
2224
|
-
} else {
|
|
2225
|
-
if (first.type === 'wpkh' || first.type === 'wsh') txType = 'segwit';
|
|
2226
|
-
if (first.type === 'sh') {
|
|
2227
|
-
if (!input.redeemScript) throw new Error('inputType: sh without redeemScript');
|
|
2228
|
-
let child = OutScript.decode(input.redeemScript);
|
|
2229
|
-
if (child.type === 'wpkh' || child.type === 'wsh') txType = 'segwit';
|
|
2230
|
-
stack.push(child);
|
|
2231
|
-
cur = child;
|
|
2232
|
-
type += `-${child.type}`;
|
|
2233
|
-
}
|
|
2234
|
-
// wsh can be inside sh
|
|
2235
|
-
if (cur.type === 'wsh') {
|
|
2236
|
-
if (!input.witnessScript) throw new Error('inputType: wsh without witnessScript');
|
|
2237
|
-
let child = OutScript.decode(input.witnessScript);
|
|
2238
|
-
if (child.type === 'wsh') txType = 'segwit';
|
|
2239
|
-
stack.push(child);
|
|
2240
|
-
cur = child;
|
|
2241
|
-
type += `-${child.type}`;
|
|
2242
|
-
}
|
|
2243
|
-
const last = stack[stack.length - 1];
|
|
2244
|
-
if (last.type === 'sh' || last.type === 'wsh')
|
|
2245
|
-
throw new Error('inputType: sh/wsh cannot be terminal type');
|
|
2246
|
-
const lastScript = OutScript.encode(last);
|
|
2247
|
-
const res = {
|
|
2248
|
-
type,
|
|
2249
|
-
txType,
|
|
2250
|
-
last,
|
|
2251
|
-
lastScript,
|
|
2252
|
-
defaultSighash,
|
|
2253
|
-
sighash: input.sighashType || defaultSighash,
|
|
2254
|
-
};
|
|
2255
|
-
if (txType === 'legacy' && !this.opts.allowLegacyWitnessUtxo && !input.nonWitnessUtxo) {
|
|
2256
|
-
throw new Error(
|
|
2257
|
-
`Transaction/sign: legacy input without nonWitnessUtxo, can result in attack that forces paying higher fees. Pass allowLegacyWitnessUtxo=true, if you sure`
|
|
2258
|
-
);
|
|
2259
|
-
}
|
|
2260
|
-
return res;
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
2275
|
// Signer can be privateKey OR instance of bip32 HD stuff
|
|
2265
2276
|
signIdx(privateKey: Signer, idx: number, allowedSighash?: SigHash[], _auxRand?: Bytes): boolean {
|
|
2266
2277
|
this.checkInputIdx(idx);
|
|
2267
2278
|
const input = this.inputs[idx];
|
|
2268
|
-
const inputType = this.
|
|
2279
|
+
const inputType = getInputType(input, this.opts.allowLegacyWitnessUtxo);
|
|
2269
2280
|
// Handle BIP32 HDKey
|
|
2270
2281
|
if (!isBytes(privateKey)) {
|
|
2271
2282
|
if (!input.bip32Derivation || !input.bip32Derivation.length)
|
|
@@ -2308,10 +2319,10 @@ export class Transaction {
|
|
|
2308
2319
|
|
|
2309
2320
|
// Actual signing
|
|
2310
2321
|
// Taproot
|
|
2311
|
-
const prevOut =
|
|
2322
|
+
const prevOut = getPrevOut(input);
|
|
2312
2323
|
if (inputType.txType === 'taproot') {
|
|
2313
2324
|
if (input.tapBip32Derivation) throw new Error('tapBip32Derivation unsupported');
|
|
2314
|
-
const prevOuts = this.inputs.map(
|
|
2325
|
+
const prevOuts = this.inputs.map(getPrevOut);
|
|
2315
2326
|
const prevOutScript = prevOuts.map((i) => i.script);
|
|
2316
2327
|
const amount = prevOuts.map((i) => i.amount);
|
|
2317
2328
|
let signed = false;
|
|
@@ -2428,7 +2439,7 @@ export class Transaction {
|
|
|
2428
2439
|
this.checkInputIdx(idx);
|
|
2429
2440
|
if (this.fee < 0n) throw new Error('Outputs spends more than inputs amount');
|
|
2430
2441
|
const input = this.inputs[idx];
|
|
2431
|
-
const inputType = this.
|
|
2442
|
+
const inputType = getInputType(input, this.opts.allowLegacyWitnessUtxo);
|
|
2432
2443
|
// Taproot finalize
|
|
2433
2444
|
if (inputType.txType === 'taproot') {
|
|
2434
2445
|
if (input.tapKeySig) input.finalScriptWitness = [input.tapKeySig];
|
|
@@ -2640,3 +2651,403 @@ export function PSBTCombine(psbts: Bytes[]): Bytes {
|
|
|
2640
2651
|
for (let i = 1; i < psbts.length; i++) tx.combine(Transaction.fromPSBT(psbts[i]));
|
|
2641
2652
|
return tx.toPSBT();
|
|
2642
2653
|
}
|
|
2654
|
+
|
|
2655
|
+
// UTXO Select
|
|
2656
|
+
type Output = { address: string; amount: bigint } | { script: Uint8Array; amount: bigint };
|
|
2657
|
+
|
|
2658
|
+
function estimateInput(
|
|
2659
|
+
inputType: ReturnType<typeof getInputType>,
|
|
2660
|
+
input: TransactionInput,
|
|
2661
|
+
opts: TxOpts
|
|
2662
|
+
) {
|
|
2663
|
+
let script: Bytes = P.EMPTY,
|
|
2664
|
+
witness: Bytes[] | undefined;
|
|
2665
|
+
|
|
2666
|
+
// schnorr sig is always 64 bytes. except for cases when sighash is not default!
|
|
2667
|
+
if (inputType.txType === 'taproot') {
|
|
2668
|
+
const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
|
|
2669
|
+
if (input.tapInternalKey && !P.equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
|
|
2670
|
+
witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
|
|
2671
|
+
} else if (input.tapLeafScript) {
|
|
2672
|
+
// If user want to select specific leaf (which can signed, it is possible to remove all other leafs manually);
|
|
2673
|
+
// Sort leafs by control block length.
|
|
2674
|
+
const leafs = input.tapLeafScript.sort(
|
|
2675
|
+
(a, b) => TaprootControlBlock.encode(a[0]).length - TaprootControlBlock.encode(b[0]).length
|
|
2676
|
+
);
|
|
2677
|
+
for (const [cb, _script] of leafs) {
|
|
2678
|
+
// Last byte is version
|
|
2679
|
+
const script = _script.slice(0, -1);
|
|
2680
|
+
const outScript = OutScript.decode(script);
|
|
2681
|
+
let signatures: Bytes[] = [];
|
|
2682
|
+
if (outScript.type === 'tr_ms') {
|
|
2683
|
+
const m = outScript.m;
|
|
2684
|
+
for (let i = 0; i < m; i++) signatures.push(new Uint8Array(SCHNORR_SIG_SIZE));
|
|
2685
|
+
const n = outScript.pubkeys.length - m;
|
|
2686
|
+
for (let i = 0; i < n; i++) signatures.push(P.EMPTY);
|
|
2687
|
+
} else if (outScript.type === 'tr_ns') {
|
|
2688
|
+
for (const _pub of outScript.pubkeys) signatures.push(new Uint8Array(SCHNORR_SIG_SIZE));
|
|
2689
|
+
} else throw new Error('Finalize: Unknown tapLeafScript');
|
|
2690
|
+
// Witness is stack, so last element will be used first
|
|
2691
|
+
witness = signatures.reverse().concat([script, TaprootControlBlock.encode(cb)]);
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
} else throw new Error('estimateInput/taproot: unknown input');
|
|
2695
|
+
} else {
|
|
2696
|
+
// It is possible to grind signatures until it has minimal size (but changing fee value +N satoshi),
|
|
2697
|
+
// which will make estimations exact. But will be very hard for multi sig (need to make sure all signatures has small size).
|
|
2698
|
+
const SIG_SIZE = 72; // Maximum size of signatures
|
|
2699
|
+
const PUB_KEY_SIZE = 33;
|
|
2700
|
+
let inputScript = P.EMPTY;
|
|
2701
|
+
let inputWitness: Uint8Array[] = [];
|
|
2702
|
+
if (inputType.last.type === 'ms') {
|
|
2703
|
+
const m = inputType.last.m;
|
|
2704
|
+
const sig: (number | Uint8Array)[] = [0];
|
|
2705
|
+
for (let i = 0; i < m; i++) sig.push(new Uint8Array(SIG_SIZE));
|
|
2706
|
+
inputScript = Script.encode(sig);
|
|
2707
|
+
} else if (inputType.last.type === 'pk') {
|
|
2708
|
+
// 71 sig + 1 sighash
|
|
2709
|
+
inputScript = Script.encode([new Uint8Array(SIG_SIZE)]);
|
|
2710
|
+
} else if (inputType.last.type === 'pkh') {
|
|
2711
|
+
inputScript = Script.encode([new Uint8Array(SIG_SIZE), new Uint8Array(PUB_KEY_SIZE)]);
|
|
2712
|
+
} else if (inputType.last.type === 'wpkh') {
|
|
2713
|
+
inputScript = P.EMPTY;
|
|
2714
|
+
inputWitness = [new Uint8Array(SIG_SIZE), new Uint8Array(PUB_KEY_SIZE)];
|
|
2715
|
+
} else if (inputType.last.type === 'unknown' && !opts.allowUnknownInputs)
|
|
2716
|
+
throw new Error('Unknown inputs not allowed');
|
|
2717
|
+
if (inputType.type.includes('wsh-')) {
|
|
2718
|
+
// P2WSH
|
|
2719
|
+
if (inputScript.length && inputType.lastScript.length) {
|
|
2720
|
+
inputWitness = Script.decode(inputScript).map((i) => {
|
|
2721
|
+
if (i === 0) return P.EMPTY;
|
|
2722
|
+
if (isBytes(i)) return i;
|
|
2723
|
+
throw new Error(`Wrong witness op=${i}`);
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
inputWitness = inputWitness.concat(inputType.lastScript);
|
|
2727
|
+
}
|
|
2728
|
+
if (inputType.txType === 'segwit') witness = inputWitness;
|
|
2729
|
+
if (inputType.type.startsWith('sh-wsh-')) {
|
|
2730
|
+
script = Script.encode([Script.encode([0, new Uint8Array(sha256.outputLen)])]);
|
|
2731
|
+
} else if (inputType.type.startsWith('sh-')) {
|
|
2732
|
+
script = Script.encode([...Script.decode(inputScript), inputType.lastScript]);
|
|
2733
|
+
} else if (inputType.type.startsWith('wsh-')) {
|
|
2734
|
+
} else if (inputType.txType !== 'segwit') script = inputScript;
|
|
2735
|
+
}
|
|
2736
|
+
let weight = 160 + 4 * VarBytes.encode(script).length;
|
|
2737
|
+
let hasWitnesses = false;
|
|
2738
|
+
if (witness) {
|
|
2739
|
+
weight += RawWitness.encode(witness).length;
|
|
2740
|
+
hasWitnesses = true;
|
|
2741
|
+
}
|
|
2742
|
+
return { weight, hasWitnesses };
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
// Exported for tests, internal method
|
|
2746
|
+
export const _cmpBig = (a: bigint, b: bigint) => {
|
|
2747
|
+
const n = a - b;
|
|
2748
|
+
if (n < 0n) return -1;
|
|
2749
|
+
else if (n > 0n) return 1;
|
|
2750
|
+
return 0;
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2753
|
+
export type EstimatorOpts = TxOpts & {
|
|
2754
|
+
// NOTE: fees less than 1 satoshi per vbyte is not supported. Please create issue if you have valid use case for that.
|
|
2755
|
+
feePerByte: bigint; // satoshi per vbyte
|
|
2756
|
+
changeAddress: string; // address where change will be sent
|
|
2757
|
+
// Optional
|
|
2758
|
+
alwaysChange?: boolean; // always create change, even if less than dust threshold
|
|
2759
|
+
bip69?: boolean; // https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
|
|
2760
|
+
network?: typeof NETWORK;
|
|
2761
|
+
dust?: number; // how much vbytes considered dust?
|
|
2762
|
+
createTx?: boolean; // Create tx inside selection
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
function getScript(o: Output, opts: TxOpts = {}, network = NETWORK) {
|
|
2766
|
+
let script;
|
|
2767
|
+
if ('script' in o && o.script instanceof Uint8Array) {
|
|
2768
|
+
script = o.script;
|
|
2769
|
+
}
|
|
2770
|
+
if ('address' in o) {
|
|
2771
|
+
if (typeof o.address !== 'string')
|
|
2772
|
+
throw new Error(`Estimator: wrong output address=${o.address}`);
|
|
2773
|
+
script = OutScript.encode(Address(network).decode(o.address));
|
|
2774
|
+
}
|
|
2775
|
+
if (!script) throw new Error('Estimator: wrong output script');
|
|
2776
|
+
if (typeof o.amount !== 'bigint') throw new Error(`Estimator: wrong output amount=${o.amount}`);
|
|
2777
|
+
if (script && !opts.allowUnknownOutputs && OutScript.decode(script).type === 'unknown') {
|
|
2778
|
+
throw new Error(
|
|
2779
|
+
'Estimator: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure'
|
|
2780
|
+
);
|
|
2781
|
+
}
|
|
2782
|
+
if (!opts.disableScriptCheck) checkScript(script);
|
|
2783
|
+
return script;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// exact is meaningless without additional accum (will often fail if not possible to find right utxo)
|
|
2787
|
+
// -> we support only exact+accum or accum
|
|
2788
|
+
type SortStrategy = 'Newest' | 'Oldest' | 'Smallest' | 'Biggest';
|
|
2789
|
+
type ExactStrategy = `exact${SortStrategy}`;
|
|
2790
|
+
type AccumStrategy = `accum${SortStrategy}`;
|
|
2791
|
+
|
|
2792
|
+
export type SelectionStrategy =
|
|
2793
|
+
| 'all'
|
|
2794
|
+
| 'default'
|
|
2795
|
+
| AccumStrategy
|
|
2796
|
+
| `${ExactStrategy}/${AccumStrategy}`;
|
|
2797
|
+
|
|
2798
|
+
// class, because we need to re-use normalized inputs, instead of parsing each time
|
|
2799
|
+
// internal stuff, exported for tests only
|
|
2800
|
+
export class _Estimator {
|
|
2801
|
+
private baseWeight: number;
|
|
2802
|
+
private changeWeight: number;
|
|
2803
|
+
private amount: bigint;
|
|
2804
|
+
private normalizedInputs: {
|
|
2805
|
+
inputType: ReturnType<typeof getInputType>;
|
|
2806
|
+
normalized: ReturnType<typeof normalizeInput>;
|
|
2807
|
+
amount: bigint;
|
|
2808
|
+
value: bigint;
|
|
2809
|
+
estimate: { weight: number; hasWitnesses: boolean };
|
|
2810
|
+
}[];
|
|
2811
|
+
// https://github.com/bitcoin/bitcoin/blob/f90603ac6d24f5263649675d51233f1fce8b2ecd/src/policy/policy.cpp#L44
|
|
2812
|
+
// 32 + 4 + 1 + 107 + 4
|
|
2813
|
+
// Dust used in accumExact + change address algo
|
|
2814
|
+
// - change address: can be smaller for segwit
|
|
2815
|
+
// - accumExact: ???
|
|
2816
|
+
private dust = 148n; // compat with coinselect
|
|
2817
|
+
|
|
2818
|
+
constructor(
|
|
2819
|
+
private inputs: TransactionInputUpdate[],
|
|
2820
|
+
private outputs: Output[],
|
|
2821
|
+
private opts: EstimatorOpts
|
|
2822
|
+
) {
|
|
2823
|
+
if (typeof opts.feePerByte !== 'bigint')
|
|
2824
|
+
throw new Error(`Estimator: wrong feePerByte=${opts.feePerByte}`);
|
|
2825
|
+
if (opts.dust) {
|
|
2826
|
+
if (typeof opts.dust !== 'bigint') throw new Error(`Estimator: wrong dust=${opts.dust}`);
|
|
2827
|
+
this.dust = opts.dust;
|
|
2828
|
+
}
|
|
2829
|
+
const network = opts.network || NETWORK;
|
|
2830
|
+
let amount = 0n;
|
|
2831
|
+
// Base weight: tx with outputs, no inputs
|
|
2832
|
+
let baseWeight = 32;
|
|
2833
|
+
for (const o of outputs) {
|
|
2834
|
+
const script = getScript(o, opts, opts.network);
|
|
2835
|
+
baseWeight += 32 + 4 * VarBytes.encode(script).length;
|
|
2836
|
+
amount += o.amount;
|
|
2837
|
+
}
|
|
2838
|
+
if (typeof opts.changeAddress !== 'string')
|
|
2839
|
+
throw new Error(`Estimator: wrong change address=${opts.changeAddress}`);
|
|
2840
|
+
let changeWeight =
|
|
2841
|
+
baseWeight +
|
|
2842
|
+
32 +
|
|
2843
|
+
4 * VarBytes.encode(OutScript.encode(Address(network).decode(opts.changeAddress))).length;
|
|
2844
|
+
baseWeight += 4 * CompactSizeLen.encode(outputs.length).length;
|
|
2845
|
+
// If there a lot of outputs change can change fee
|
|
2846
|
+
changeWeight += 4 * CompactSizeLen.encode(outputs.length + 1).length;
|
|
2847
|
+
this.baseWeight = baseWeight;
|
|
2848
|
+
this.changeWeight = changeWeight;
|
|
2849
|
+
this.amount = amount;
|
|
2850
|
+
this.normalizedInputs = this.inputs.map((i) => {
|
|
2851
|
+
const normalized = normalizeInput(i, undefined, undefined, opts.disableScriptCheck);
|
|
2852
|
+
inputBeforeSign(normalized); // check fields
|
|
2853
|
+
const inputType = getInputType(normalized, opts.allowLegacyWitnessUtxo);
|
|
2854
|
+
const prev = getPrevOut(normalized);
|
|
2855
|
+
const estimate = estimateInput(inputType, normalized, this.opts);
|
|
2856
|
+
const value = prev.amount - opts.feePerByte * BigInt(toVsize(estimate.weight)); // value = amount-fee
|
|
2857
|
+
return { inputType, normalized, amount: prev.amount, value, estimate };
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
private checkInputIdx(idx: number) {
|
|
2861
|
+
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
|
|
2862
|
+
throw new Error(`Wrong input index=${idx}`);
|
|
2863
|
+
return idx;
|
|
2864
|
+
}
|
|
2865
|
+
private sortIndices(indices: number[]) {
|
|
2866
|
+
return indices.slice().sort((a, b) => {
|
|
2867
|
+
const ai = this.normalizedInputs[this.checkInputIdx(a)];
|
|
2868
|
+
const bi = this.normalizedInputs[this.checkInputIdx(b)];
|
|
2869
|
+
const out = _cmpBytes(ai.normalized.txid!, bi.normalized.txid!);
|
|
2870
|
+
if (out !== 0) return out;
|
|
2871
|
+
return ai.normalized.index! - bi.normalized.index!;
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
private sortOutputs(outputs: Output[]) {
|
|
2875
|
+
const scripts = outputs.map((o) => getScript(o, this.opts, this.opts.network));
|
|
2876
|
+
const indices = outputs.map((_, j) => j);
|
|
2877
|
+
return indices.sort((a, b) => {
|
|
2878
|
+
const aa = outputs[a].amount;
|
|
2879
|
+
const ba = outputs[b].amount;
|
|
2880
|
+
const out = _cmpBig(aa, ba);
|
|
2881
|
+
if (out !== 0) return out;
|
|
2882
|
+
return _cmpBytes(scripts[a], scripts[b]);
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
private getSatoshi(weigth: number) {
|
|
2886
|
+
return this.opts.feePerByte * BigInt(toVsize(weigth));
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// Sort by value instead of amount
|
|
2890
|
+
get biggest() {
|
|
2891
|
+
return this.inputs
|
|
2892
|
+
.map((_i, j) => j)
|
|
2893
|
+
.sort((a, b) => _cmpBig(this.normalizedInputs[b].value, this.normalizedInputs[a].value));
|
|
2894
|
+
}
|
|
2895
|
+
get smallest() {
|
|
2896
|
+
return this.biggest.reverse();
|
|
2897
|
+
}
|
|
2898
|
+
// These assume that UTXO array has historical order.
|
|
2899
|
+
// Otherwise, we have no way to know which tx is oldest
|
|
2900
|
+
// Explorers usually give UTXO in this order.
|
|
2901
|
+
get oldest() {
|
|
2902
|
+
return this.inputs.map((_i, j) => j);
|
|
2903
|
+
}
|
|
2904
|
+
get newest() {
|
|
2905
|
+
return this.oldest.reverse();
|
|
2906
|
+
}
|
|
2907
|
+
// exact - like blackjack from coinselect.
|
|
2908
|
+
// exact(biggest) will select one big utxo which is closer to targetValue+dust, if possible.
|
|
2909
|
+
// If not, it will accumulate largest utxo until value is close to targetValue+dust.
|
|
2910
|
+
accumulate(indices: number[], exact = false, skipNegative = true, all = false) {
|
|
2911
|
+
const { feePerByte } = this.opts;
|
|
2912
|
+
// TODO: how to handle change addresses?
|
|
2913
|
+
// - cost of input
|
|
2914
|
+
// - cost of change output (if input requires change)
|
|
2915
|
+
// - cost of output spending
|
|
2916
|
+
// Dust threshold should be significantly bigger, no point in
|
|
2917
|
+
// creating an output, which cannot be spent.
|
|
2918
|
+
// coinselect doesn't consider cost of output address for dust.
|
|
2919
|
+
// Changing that can actually reduce privacy
|
|
2920
|
+
let weight = this.opts.alwaysChange ? this.changeWeight : this.baseWeight;
|
|
2921
|
+
let hasWitnesses = false;
|
|
2922
|
+
let num = 0;
|
|
2923
|
+
let inputsAmount = 0n;
|
|
2924
|
+
const targetAmount = this.amount;
|
|
2925
|
+
const res = [];
|
|
2926
|
+
let fee;
|
|
2927
|
+
for (const idx of indices) {
|
|
2928
|
+
this.checkInputIdx(idx);
|
|
2929
|
+
const { estimate, amount, value } = this.normalizedInputs[idx];
|
|
2930
|
+
let newWeight = weight + estimate.weight;
|
|
2931
|
+
if (!hasWitnesses && estimate.hasWitnesses) newWeight += 2; // enable witness if needed
|
|
2932
|
+
const totalWeight = newWeight + 4 * CompactSizeLen.encode(num).length; // number of outputs can change weight
|
|
2933
|
+
fee = this.getSatoshi(totalWeight);
|
|
2934
|
+
// Best case scenario exact(biggest) -> we find biggest output, less than target+threshold
|
|
2935
|
+
if (exact) {
|
|
2936
|
+
const dust = this.dust * feePerByte;
|
|
2937
|
+
// skip if added value is bigger than dust
|
|
2938
|
+
if (amount + inputsAmount > targetAmount + fee + dust) continue;
|
|
2939
|
+
}
|
|
2940
|
+
// Negative: cost of using input is more than value provided (negative)
|
|
2941
|
+
// By default 'blackjack' mode in coinselect doesn't use that, which means
|
|
2942
|
+
// it will use negative output if sorted by 'smallest'
|
|
2943
|
+
if (skipNegative && value <= 0n) continue;
|
|
2944
|
+
weight = newWeight;
|
|
2945
|
+
if (estimate.hasWitnesses) hasWitnesses = true;
|
|
2946
|
+
num++;
|
|
2947
|
+
inputsAmount += amount;
|
|
2948
|
+
res.push(idx);
|
|
2949
|
+
// inputsAmount is enough to cover cost of tx
|
|
2950
|
+
if (!all && targetAmount + fee < inputsAmount)
|
|
2951
|
+
return { indices: res, fee, weight: totalWeight, total: inputsAmount };
|
|
2952
|
+
}
|
|
2953
|
+
if (all) {
|
|
2954
|
+
const newWeight = weight + 4 * CompactSizeLen.encode(num).length;
|
|
2955
|
+
return { indices: res, fee, weight: newWeight, total: inputsAmount };
|
|
2956
|
+
}
|
|
2957
|
+
return undefined;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// Works like coinselect default method
|
|
2961
|
+
default() {
|
|
2962
|
+
const { biggest } = this;
|
|
2963
|
+
const exact = this.accumulate(biggest, true, false);
|
|
2964
|
+
if (exact) return exact;
|
|
2965
|
+
return this.accumulate(biggest);
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
private select(strategy: SelectionStrategy) {
|
|
2969
|
+
if (strategy === 'all') {
|
|
2970
|
+
return this.accumulate(
|
|
2971
|
+
this.inputs.map((_, j) => j),
|
|
2972
|
+
false,
|
|
2973
|
+
true,
|
|
2974
|
+
true
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
if (strategy === 'default') return this.default();
|
|
2978
|
+
const data: Record<SortStrategy, () => number[]> = {
|
|
2979
|
+
Oldest: () => this.oldest,
|
|
2980
|
+
Newest: () => this.newest,
|
|
2981
|
+
Smallest: () => this.smallest,
|
|
2982
|
+
Biggest: () => this.biggest,
|
|
2983
|
+
};
|
|
2984
|
+
if (strategy.startsWith('exact')) {
|
|
2985
|
+
const [exactData, left] = strategy.slice(5).split('/') as [SortStrategy, SelectionStrategy];
|
|
2986
|
+
if (!data[exactData]) throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2987
|
+
strategy = left;
|
|
2988
|
+
const exact = this.accumulate(data[exactData](), true, true);
|
|
2989
|
+
if (exact) return exact;
|
|
2990
|
+
}
|
|
2991
|
+
if (strategy.startsWith('accum')) {
|
|
2992
|
+
const accumData = strategy.slice(5) as SortStrategy;
|
|
2993
|
+
if (!data[accumData]) throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2994
|
+
return this.accumulate(data[accumData]());
|
|
2995
|
+
}
|
|
2996
|
+
throw new Error(`Estimator.select: wrong strategy=${strategy}`);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
result(strategy: SelectionStrategy) {
|
|
3000
|
+
const s = this.select(strategy);
|
|
3001
|
+
if (!s) return;
|
|
3002
|
+
const { indices, weight, total } = s;
|
|
3003
|
+
let needChange = this.opts.alwaysChange;
|
|
3004
|
+
const changeWeight = this.opts.alwaysChange
|
|
3005
|
+
? weight
|
|
3006
|
+
: weight + (this.changeWeight - this.baseWeight);
|
|
3007
|
+
|
|
3008
|
+
const changeFee = this.getSatoshi(changeWeight);
|
|
3009
|
+
let fee = s.fee;
|
|
3010
|
+
const change = total - this.amount - changeFee;
|
|
3011
|
+
if (change > this.dust) needChange = true;
|
|
3012
|
+
let inputs = indices;
|
|
3013
|
+
let outputs = Array.from(this.outputs);
|
|
3014
|
+
if (needChange) {
|
|
3015
|
+
fee = changeFee;
|
|
3016
|
+
// this shouldn't happen!
|
|
3017
|
+
if (change < 0n) throw new Error(`Estimator.result: negative change=${change}`);
|
|
3018
|
+
outputs.push({ address: this.opts.changeAddress, amount: change });
|
|
3019
|
+
}
|
|
3020
|
+
if (this.opts.bip69) {
|
|
3021
|
+
inputs = this.sortIndices(inputs);
|
|
3022
|
+
outputs = this.sortOutputs(outputs).map((i) => outputs[i]);
|
|
3023
|
+
}
|
|
3024
|
+
const res = {
|
|
3025
|
+
inputs: inputs.map((i) => this.inputs[i]),
|
|
3026
|
+
outputs,
|
|
3027
|
+
fee,
|
|
3028
|
+
weight: this.opts.alwaysChange ? s.weight : changeWeight,
|
|
3029
|
+
change: !!needChange,
|
|
3030
|
+
};
|
|
3031
|
+
let tx;
|
|
3032
|
+
if (this.opts.createTx) {
|
|
3033
|
+
const { inputs, outputs } = res;
|
|
3034
|
+
tx = new Transaction(this.opts);
|
|
3035
|
+
for (const i of inputs) tx.addInput(i);
|
|
3036
|
+
for (const o of outputs)
|
|
3037
|
+
tx.addOutput({ ...o, script: getScript(o, this.opts, this.opts.network) });
|
|
3038
|
+
}
|
|
3039
|
+
return { ...res, tx };
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
export function selectUTXO(
|
|
3044
|
+
inputs: TransactionInputUpdate[],
|
|
3045
|
+
outputs: Output[],
|
|
3046
|
+
strategy: SelectionStrategy,
|
|
3047
|
+
opts: EstimatorOpts
|
|
3048
|
+
) {
|
|
3049
|
+
// Defaults: do we want bip69 by default?
|
|
3050
|
+
const _opts = { createTx: true, bip69: true, ...opts };
|
|
3051
|
+
const est = new _Estimator(inputs, outputs, _opts);
|
|
3052
|
+
return est.result(strategy);
|
|
3053
|
+
}
|