@teleportdao/bitcoin 3.0.3 → 4.0.0-alpha.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.
Files changed (105) hide show
  1. package/dist/bitcoin-interface-ordinal.d.ts +24 -24
  2. package/dist/bitcoin-interface-ordinal.d.ts.map +1 -1
  3. package/dist/bitcoin-interface-ordinal.js +89 -107
  4. package/dist/bitcoin-interface-ordinal.js.map +1 -1
  5. package/dist/bitcoin-interface-teleswap.d.ts +2 -2
  6. package/dist/bitcoin-interface-teleswap.d.ts.map +1 -1
  7. package/dist/bitcoin-interface-teleswap.js +77 -88
  8. package/dist/bitcoin-interface-teleswap.js.map +1 -1
  9. package/dist/bitcoin-interface-utils.d.ts +7 -4
  10. package/dist/bitcoin-interface-utils.d.ts.map +1 -1
  11. package/dist/bitcoin-interface-utils.js +11 -5
  12. package/dist/bitcoin-interface-utils.js.map +1 -1
  13. package/dist/bitcoin-interface-wallet.d.ts +1 -0
  14. package/dist/bitcoin-interface-wallet.d.ts.map +1 -1
  15. package/dist/bitcoin-interface-wallet.js +87 -95
  16. package/dist/bitcoin-interface-wallet.js.map +1 -1
  17. package/dist/bitcoin-interface.d.ts.map +1 -1
  18. package/dist/bitcoin-interface.js +58 -87
  19. package/dist/bitcoin-interface.js.map +1 -1
  20. package/dist/bitcoin-utils.d.ts +7 -4
  21. package/dist/bitcoin-utils.d.ts.map +1 -1
  22. package/dist/bitcoin-utils.js +93 -54
  23. package/dist/bitcoin-utils.js.map +1 -1
  24. package/dist/bitcoin-wallet-base.d.ts +23 -11
  25. package/dist/bitcoin-wallet-base.d.ts.map +1 -1
  26. package/dist/bitcoin-wallet-base.js +148 -138
  27. package/dist/bitcoin-wallet-base.js.map +1 -1
  28. package/dist/helper/brc20-helper.d.ts.map +1 -1
  29. package/dist/helper/brc20-helper.js +4 -6
  30. package/dist/helper/brc20-helper.js.map +1 -1
  31. package/dist/helper/index.js +17 -7
  32. package/dist/helper/index.js.map +1 -1
  33. package/dist/helper/ordinal-helper.d.ts +3 -9
  34. package/dist/helper/ordinal-helper.d.ts.map +1 -1
  35. package/dist/helper/ordinal-helper.js +20 -85
  36. package/dist/helper/ordinal-helper.js.map +1 -1
  37. package/dist/helper/teleswap-helper.d.ts.map +1 -1
  38. package/dist/helper/teleswap-helper.js +13 -12
  39. package/dist/helper/teleswap-helper.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +18 -7
  43. package/dist/index.js.map +1 -1
  44. package/dist/multisig-wallet-helper.d.ts +14 -1
  45. package/dist/multisig-wallet-helper.d.ts.map +1 -1
  46. package/dist/multisig-wallet-helper.js +135 -149
  47. package/dist/multisig-wallet-helper.js.map +1 -1
  48. package/dist/ordinal-wallet.d.ts +27 -19
  49. package/dist/ordinal-wallet.d.ts.map +1 -1
  50. package/dist/ordinal-wallet.js +263 -293
  51. package/dist/ordinal-wallet.js.map +1 -1
  52. package/dist/sign/sign-transaction.d.ts +5 -2
  53. package/dist/sign/sign-transaction.d.ts.map +1 -1
  54. package/dist/sign/sign-transaction.js +32 -39
  55. package/dist/sign/sign-transaction.js.map +1 -1
  56. package/dist/teleswap-wallet.d.ts +1 -0
  57. package/dist/teleswap-wallet.d.ts.map +1 -1
  58. package/dist/teleswap-wallet.js +40 -53
  59. package/dist/teleswap-wallet.js.map +1 -1
  60. package/dist/transaction-builder/bitcoin-transaction-builder.js +21 -21
  61. package/dist/transaction-builder/bitcoin-transaction-builder.js.map +1 -1
  62. package/dist/transaction-builder/coin-select.d.ts +19 -3
  63. package/dist/transaction-builder/coin-select.d.ts.map +1 -1
  64. package/dist/transaction-builder/coin-select.js +193 -181
  65. package/dist/transaction-builder/coin-select.js.map +1 -1
  66. package/dist/transaction-builder/index.d.ts +1 -0
  67. package/dist/transaction-builder/index.d.ts.map +1 -1
  68. package/dist/transaction-builder/index.js +1 -0
  69. package/dist/transaction-builder/index.js.map +1 -1
  70. package/dist/transaction-builder/ordinal-transaction-builder.d.ts +4 -4
  71. package/dist/transaction-builder/ordinal-transaction-builder.d.ts.map +1 -1
  72. package/dist/transaction-builder/ordinal-transaction-builder.js +51 -48
  73. package/dist/transaction-builder/ordinal-transaction-builder.js.map +1 -1
  74. package/dist/transaction-builder/transaction-builder.d.ts +57 -54
  75. package/dist/transaction-builder/transaction-builder.d.ts.map +1 -1
  76. package/dist/transaction-builder/transaction-builder.js +394 -308
  77. package/dist/transaction-builder/transaction-builder.js.map +1 -1
  78. package/dist/utils/networks.js +17 -7
  79. package/dist/utils/networks.js.map +1 -1
  80. package/dist/utils/tools.js +24 -35
  81. package/dist/utils/tools.js.map +1 -1
  82. package/package.json +4 -4
  83. package/src/bitcoin-interface-utils.ts +17 -8
  84. package/src/bitcoin-interface-wallet.ts +15 -1
  85. package/src/bitcoin-utils.ts +52 -3
  86. package/src/bitcoin-wallet-base.ts +67 -23
  87. package/src/helper/ordinal-helper.ts +0 -83
  88. package/src/index.ts +2 -0
  89. package/src/multisig-wallet-helper.ts +296 -0
  90. package/src/sign/sign-transaction.ts +5 -7
  91. package/src/transaction-builder/coin-select.ts +633 -0
  92. package/src/transaction-builder/index.ts +1 -0
  93. package/src/transaction-builder/transaction-builder.ts +424 -279
  94. package/dist/bitcoin-wallet-base copy.d.ts +0 -122
  95. package/dist/bitcoin-wallet-base copy.d.ts.map +0 -1
  96. package/dist/bitcoin-wallet-base copy.js +0 -279
  97. package/dist/bitcoin-wallet-base copy.js.map +0 -1
  98. package/dist/multisig-coordinator-wallet.d.ts +0 -2
  99. package/dist/multisig-coordinator-wallet.d.ts.map +0 -1
  100. package/dist/multisig-coordinator-wallet.js +0 -6
  101. package/dist/multisig-coordinator-wallet.js.map +0 -1
  102. package/dist/multisig-wallet.d.ts +0 -20
  103. package/dist/multisig-wallet.d.ts.map +0 -1
  104. package/dist/multisig-wallet.js +0 -119
  105. package/dist/multisig-wallet.js.map +0 -1
@@ -0,0 +1,633 @@
1
+ import * as bitcoin from "bitcoinjs-lib"
2
+ import { ExtendedUtxo, Target } from "./transaction-builder"
3
+ import { getAddressType } from "../bitcoin-utils"
4
+
5
+ export const componentBytes = {
6
+ bytePerInput: {
7
+ p2pkh: 152, // 149
8
+ p2wpkh: 72, // 68
9
+ "p2sh-p2wpkh": 92, // 91
10
+ p2tr: 62, // 58
11
+ default: 92,
12
+ },
13
+ baseTxBytes: 10 + 5, // +5 extra bytes to be sure
14
+ bytePerOutput: {
15
+ p2pkh: 34, // 33
16
+ p2wpkh: 31, // 30
17
+ p2sh: 32, // 31
18
+ p2tr: 43, // 42
19
+ p2wsh: 44, // 43
20
+ default: 35,
21
+ max: 45,
22
+ },
23
+
24
+ scriptExtraBytes: {
25
+ lessThan255: 12,
26
+ moreThan255: 15,
27
+ },
28
+ }
29
+
30
+ // https://bitcoin.stackexchange.com/questions/84004/how-do-virtual-size-stripped-size-and-raw-size-compare-between-legacy-address-f
31
+ // export const componentBytes = {
32
+ // bytePerInput: {
33
+ // p2pkh: 148,
34
+ // p2wpkh: 70, // 68
35
+ // "p2sh-p2wpkh": 91,
36
+ // p2tr: 60, // actual 58
37
+ // },
38
+ // baseTxBytes: 10 + 5, // +5 extra bytes to be sure
39
+ // bytePerOutput: {
40
+ // p2pkh: 35, // 34
41
+ // p2wpkh: 35, // 31
42
+ // p2sh: 35, // 32
43
+ // p2tr: 45, // 43
44
+ // p2tr: 45, // 43
45
+ // default: 35,
46
+ // },
47
+ //
48
+ // }
49
+
50
+ // P2SH | 46 + 74n + 34m
51
+ // P2SH-P2WSH | (306 + 76n + 34m) / 4
52
+ // P2WSH | (166 + 76n + 34m) / 4
53
+
54
+ export const DUST = 600
55
+
56
+ export function getInputSize(
57
+ addressType: string,
58
+ details?: {
59
+ // op return or other script
60
+ script?: string
61
+ // n of m multisig
62
+ n?: number
63
+ m?: number
64
+ },
65
+ ) {
66
+ let { m = 3 } = details || {}
67
+ const n = details?.n || m || 2
68
+ if (addressType === "p2sh") {
69
+ return 46 + 74 * n + 34 * m
70
+ }
71
+ if (addressType === "p2sh-p2wsh") {
72
+ return +((306 + 76 * n + 34 * m) / 4).toFixed()
73
+ }
74
+ if (addressType === "p2wsh") {
75
+ return +((166 + 76 * n + 34 * m) / 4).toFixed()
76
+ }
77
+
78
+ return (
79
+ componentBytes.bytePerInput[addressType as keyof typeof componentBytes.bytePerInput] ||
80
+ componentBytes.bytePerInput.default
81
+ )
82
+ }
83
+
84
+ export function getOutputSize(
85
+ output: {
86
+ addressType?: string
87
+ address?: string
88
+ script?: Buffer
89
+ },
90
+ // use network to decode address
91
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
92
+ ) {
93
+ if (output.addressType) {
94
+ return (
95
+ componentBytes.bytePerOutput[
96
+ output.addressType as keyof typeof componentBytes.bytePerOutput
97
+ ] || componentBytes.bytePerOutput.default
98
+ )
99
+ }
100
+ if (output.address) {
101
+ let addressType = "max"
102
+ try {
103
+ addressType = getAddressType(output.address, network)
104
+ } catch {
105
+ addressType = "max"
106
+ }
107
+ return (
108
+ componentBytes.bytePerOutput[addressType as keyof typeof componentBytes.bytePerOutput] ||
109
+ componentBytes.bytePerOutput.max
110
+ )
111
+ }
112
+
113
+ if (output.script) {
114
+ if (output.script.byteLength < 255) {
115
+ return output.script.byteLength + componentBytes.scriptExtraBytes.lessThan255
116
+ }
117
+ return output.script.byteLength + componentBytes.scriptExtraBytes.moreThan255
118
+ }
119
+ throw new Error("invalid output")
120
+ }
121
+
122
+ export function calculateTxSize(
123
+ inputTypes: {
124
+ addressType: string
125
+ n?: number
126
+ m?: number
127
+ script?: string
128
+ }[],
129
+ outputs: {
130
+ script?: Buffer
131
+ address?: string
132
+ value: number
133
+ }[],
134
+ changeAddressType = "default",
135
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
136
+ ) {
137
+ const inputsSizes = inputTypes.map(({ addressType, n, m }) => getInputSize(addressType, { n, m }))
138
+ const outputSizes = outputs.map((outP: any) => {
139
+ if (!outP.address && !outP.script) {
140
+ return getOutputSize(
141
+ {
142
+ addressType: changeAddressType,
143
+ },
144
+ network,
145
+ )
146
+ }
147
+ return getOutputSize(outP, network)
148
+ })
149
+
150
+ const txSize: number =
151
+ componentBytes.baseTxBytes +
152
+ inputsSizes.reduce((a, c) => a + c, 0) +
153
+ outputSizes.reduce((a, c) => a + c, 0)
154
+
155
+ return txSize
156
+ }
157
+
158
+ function sumValues(outputs: { value: number }[]): number {
159
+ return outputs.reduce((sum: number, o) => sum + o.value, 0)
160
+ }
161
+
162
+ function fixNumberToInteger(value: number): number {
163
+ return Math.ceil(value)
164
+ }
165
+
166
+ function finalCheckAndAddChangeAndExtraInput(
167
+ inputs: ExtendedUtxo[],
168
+ outputs: Target[],
169
+ outputValue: number,
170
+ outputSize: number,
171
+ feeRate: number,
172
+ changeSize: number,
173
+ highFeeThd: number,
174
+ // for optimization
175
+ extraInput?: ExtendedUtxo,
176
+ smallUtxoAggregate?: ExtendedUtxo[],
177
+ ) {
178
+ const selectedInputs = inputs.concat(smallUtxoAggregate || [])
179
+ let selectedInputsValue = sumValues(selectedInputs)
180
+ const selectedInputsSize = selectedInputs.reduce(
181
+ (sum, i) =>
182
+ sum +
183
+ getInputSize(i.signerInfo.addressType, {
184
+ n: i.signerInfo.numberOfSigners,
185
+ m: i.signerInfo.publicKeys?.length,
186
+ }),
187
+ 0,
188
+ )
189
+ let txSize = selectedInputsSize + outputSize + componentBytes.baseTxBytes
190
+ let txFee = fixNumberToInteger(txSize * feeRate)
191
+
192
+ let totalNeeded = txFee + outputValue
193
+ let changeValue = 0
194
+ const changeFee = fixNumberToInteger(changeSize * feeRate)
195
+
196
+ if (extraInput && (!smallUtxoAggregate || smallUtxoAggregate.length === 0)) {
197
+ const extraInputSize = fixNumberToInteger(
198
+ getInputSize(extraInput.signerInfo.addressType, {
199
+ n: extraInput.signerInfo.numberOfSigners,
200
+ m: extraInput.signerInfo.publicKeys?.length,
201
+ }),
202
+ )
203
+ const extraInputFee = fixNumberToInteger(extraInputSize * feeRate)
204
+ if (
205
+ selectedInputsValue - totalNeeded > extraInputFee + changeFee &&
206
+ selectedInputsValue - totalNeeded < (feeRate > highFeeThd ? DUST : 2 * DUST)
207
+ ) {
208
+ selectedInputs.push(extraInput)
209
+ selectedInputsValue += extraInput.value
210
+ txSize = txSize + extraInputSize
211
+ txFee = fixNumberToInteger(txSize * feeRate)
212
+ totalNeeded = txFee + outputValue
213
+ }
214
+ }
215
+
216
+ if (selectedInputsValue - totalNeeded > DUST) {
217
+ txSize = txSize + changeSize
218
+ txFee = fixNumberToInteger(txSize * feeRate)
219
+ totalNeeded = txFee + outputValue
220
+ changeValue = selectedInputsValue - totalNeeded
221
+ }
222
+ let needed = totalNeeded - selectedInputsValue
223
+
224
+ // fee absorption for feeRate > 2
225
+ // for feeRate < 2, it could cause the tx to be rejected by the mempool (due to small tx fee per byte)
226
+ if (feeRate >= 2 && needed > 0) {
227
+ // If needed amount is < 10% of fee, absorb it into fee instead of failing
228
+ if (needed < txFee * 0.1) {
229
+ txFee -= needed
230
+ needed = 0
231
+ }
232
+ }
233
+ return {
234
+ success: needed <= 0,
235
+ inputs: selectedInputs,
236
+ outputs,
237
+ change: changeValue > 0 ? { value: changeValue } : undefined,
238
+ fee: txFee,
239
+ bytes: txSize,
240
+ totalInputValue: selectedInputsValue,
241
+ totalOutputValue: outputValue + changeValue,
242
+ needed: needed > 0 ? needed : 0,
243
+ feeRate,
244
+ effectiveFeeRate: +(txFee / txSize).toFixed(2),
245
+ }
246
+ }
247
+
248
+ export function coinSelectNormal(
249
+ inputs: ExtendedUtxo[],
250
+ outputs: Target[],
251
+ feeRate: number,
252
+ changeAddressType = "max",
253
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
254
+ highFeeThd = 5,
255
+ requiredUtxo?: ExtendedUtxo[],
256
+ smallUtxoAggregate?: {
257
+ minInputCount: number
258
+ maxInputValue: number
259
+ },
260
+ ): {
261
+ success: boolean
262
+ inputs: ExtendedUtxo[]
263
+ outputs: Target[]
264
+ fee: number
265
+ bytes: number
266
+ totalInputValue: number
267
+ totalOutputValue: number
268
+ needed: number
269
+ change?: { value: number }
270
+ } {
271
+ // Sort inputs descending
272
+ const totalOutputValue = sumValues(outputs)
273
+ const sortedInputs = [...inputs].sort((a, b) => b.value - a.value)
274
+ const numberOfInputsPerOutputs = feeRate > highFeeThd ? 1 : 2
275
+ const minRequiredInputCount = requiredUtxo ? requiredUtxo.length : 1
276
+
277
+ // randomize priority inputs to another array
278
+ let priorityInputs = sortedInputs.filter(
279
+ (inp) => inp.value >= totalOutputValue / outputs.length / numberOfInputsPerOutputs,
280
+ )
281
+
282
+ priorityInputs = priorityInputs.sort(() => Math.random() - 0.5)
283
+
284
+ // select rest of the inputs from sorted inputs
285
+ const otherInputs = sortedInputs.slice(priorityInputs.length)
286
+ let allInputs = priorityInputs.concat(otherInputs)
287
+
288
+ if (requiredUtxo) {
289
+ // remove required utxo from all inputs
290
+ allInputs = allInputs.filter(
291
+ (inp) => !requiredUtxo.some((r) => r.hash === inp.hash && r.index === inp.index),
292
+ )
293
+
294
+ // check if its better to put require utxo in utxo list or not
295
+ // if (sortedInputs.length - allInputs.length !== requiredUtxo.length) {
296
+ // throw new Error("required utxo is not exist in utxo list")
297
+ // }
298
+ //
299
+ allInputs = requiredUtxo.concat(allInputs)
300
+ }
301
+
302
+ const outputSize = outputs.reduce((sum, o) => sum + getOutputSize(o, network), 0)
303
+ const changeSize = getOutputSize({ addressType: changeAddressType }, network)
304
+ const baseTxSize = componentBytes.baseTxBytes
305
+
306
+ let txSize = outputSize + baseTxSize
307
+ let txFee = fixNumberToInteger(txSize * feeRate)
308
+ const selected: ExtendedUtxo[] = []
309
+
310
+ for (let index = 0; index < allInputs.length; index += 1) {
311
+ const input = allInputs[index]
312
+ selected.push(input)
313
+ const inputSize = getInputSize(input.signerInfo.addressType, {
314
+ n: input.signerInfo.numberOfSigners,
315
+ m: input.signerInfo.publicKeys?.length,
316
+ })
317
+ const selectedValue = sumValues(selected)
318
+
319
+ txSize += inputSize
320
+ txFee = txSize * feeRate
321
+ let totalNeeded = txFee + totalOutputValue
322
+
323
+ if (selectedValue >= totalNeeded && selected.length >= minRequiredInputCount) {
324
+ let smallUtxo: ExtendedUtxo[] | undefined
325
+
326
+ if (
327
+ feeRate < 1.3 &&
328
+ smallUtxoAggregate &&
329
+ selected.length < smallUtxoAggregate.minInputCount
330
+ ) {
331
+ const selectedInputIds = new Set(selected.map((inp) => `${inp.hash}:${inp.index}`))
332
+ smallUtxo = allInputs
333
+ .filter((_, inpIndex) => inpIndex > index)
334
+ .filter((inp) => inp.value < smallUtxoAggregate.maxInputValue)
335
+ // todo: check if we need check if the input is already selected because we already filter it using index
336
+ .filter((inp) => !selectedInputIds.has(`${inp.hash}:${inp.index}`))
337
+
338
+ smallUtxo = smallUtxo.sort((a, b) => a.value - b.value)
339
+ smallUtxo = smallUtxo.slice(0, smallUtxoAggregate.minInputCount - selected.length)
340
+ }
341
+
342
+ return finalCheckAndAddChangeAndExtraInput(
343
+ selected,
344
+ outputs,
345
+ totalOutputValue,
346
+ outputSize,
347
+ feeRate,
348
+ changeSize,
349
+ highFeeThd,
350
+ smallUtxo && smallUtxo.length > 0 ? undefined : allInputs[index + 1],
351
+ smallUtxo, // if there is small utxo, extraInput will be ignored so its ok if extraInput exists in small utxo
352
+ )
353
+ }
354
+ }
355
+ return finalCheckAndAddChangeAndExtraInput(
356
+ allInputs,
357
+ outputs,
358
+ totalOutputValue,
359
+ outputSize,
360
+ feeRate,
361
+ changeSize,
362
+ highFeeThd,
363
+ undefined,
364
+ )
365
+ }
366
+
367
+ // Branch & Bound coin selection
368
+ export function coinSelectBnB(
369
+ inputs: ExtendedUtxo[],
370
+ outputs: Target[],
371
+ feeRate: number,
372
+ changeAddressType = "max",
373
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
374
+ highFeeThd = 5,
375
+ smallUtxoAggregate?: {
376
+ minInputCount: number
377
+ maxInputValue: number
378
+ },
379
+ ): {
380
+ success: boolean
381
+ inputs: ExtendedUtxo[]
382
+ outputs: Target[]
383
+ fee: number
384
+ bytes: number
385
+ totalInputValue: number
386
+ totalOutputValue: number
387
+ needed: number
388
+ change?: { value: number }
389
+ } {
390
+ if (inputs.length > 20) {
391
+ return coinSelectNormal(
392
+ inputs,
393
+ outputs,
394
+ feeRate,
395
+ changeAddressType,
396
+ network,
397
+ highFeeThd,
398
+ undefined,
399
+ smallUtxoAggregate,
400
+ )
401
+ }
402
+
403
+ // Sort inputs descending
404
+ const totalOutputValue = sumValues(outputs)
405
+
406
+ const outputSize = outputs.reduce((sum, o) => sum + getOutputSize(o, network), 0)
407
+ const changeSize = getOutputSize({ addressType: changeAddressType }, network)
408
+ const baseTxSize = componentBytes.baseTxBytes
409
+
410
+ const sortedInputs = [...inputs].sort((a, b) => b.value - a.value)
411
+ const numberOfInputsPerOutputs = feeRate > highFeeThd ? 1 : 2
412
+
413
+ let lastInput: ExtendedUtxo | undefined = sortedInputs[sortedInputs.length - 1]
414
+ let bnbInputs = sortedInputs
415
+ .slice(0, -1)
416
+ .filter((inp) => inp.value > totalOutputValue / outputs.length / numberOfInputsPerOutputs)
417
+
418
+ let bestWaste = Infinity
419
+ let bestSelection: ExtendedUtxo[] | null = null
420
+ function bnb(idx: number, selected: ExtendedUtxo[]) {
421
+ const selectedValue = sumValues(selected)
422
+ const inputSize = selected.reduce(
423
+ (sum, i) =>
424
+ sum +
425
+ getInputSize(i.signerInfo.addressType, {
426
+ n: i.signerInfo.numberOfSigners,
427
+ m: i.signerInfo.publicKeys?.length,
428
+ }),
429
+ 0,
430
+ )
431
+ const txSize = outputSize + baseTxSize + inputSize
432
+ const txFee = fixNumberToInteger(txSize * feeRate)
433
+ let totalNeeded = txFee + totalOutputValue
434
+
435
+ if (selectedValue - totalNeeded > DUST) {
436
+ totalNeeded = totalNeeded + fixNumberToInteger(changeSize * feeRate)
437
+ }
438
+
439
+ // Return if over target+fee+bestWaste
440
+ if (selectedValue > totalNeeded + bestWaste) return
441
+
442
+ // If enough, check if best
443
+ if (selectedValue >= totalNeeded) {
444
+ const waste = selectedValue - totalNeeded
445
+ if (waste < bestWaste) {
446
+ bestWaste = waste
447
+ bestSelection = selected.slice()
448
+ }
449
+ return
450
+ }
451
+ // If out of inputs, stop
452
+ if (idx >= bnbInputs.length) return
453
+ // Branch: include current input
454
+ bnb(idx + 1, [...selected, bnbInputs[idx]])
455
+ // Branch: exclude current input
456
+ bnb(idx + 1, selected)
457
+ }
458
+ bnb(0, [])
459
+
460
+ if (!bestSelection) {
461
+ bnbInputs = sortedInputs
462
+ bestWaste = Infinity
463
+ bestSelection = null
464
+ bnb(0, [])
465
+ }
466
+
467
+ // aggregate small utxo
468
+ const selectedInputs = bestSelection || inputs
469
+ let smallUtxo: ExtendedUtxo[] | undefined
470
+ if (
471
+ feeRate < 1.3 &&
472
+ smallUtxoAggregate &&
473
+ selectedInputs.length < smallUtxoAggregate.minInputCount
474
+ ) {
475
+ const selectedInputIds = new Set(selectedInputs.map((input) => `${input.hash}:${input.index}`))
476
+ smallUtxo = inputs
477
+ .filter((input) => !selectedInputIds.has(`${input.hash}:${input.index}`))
478
+ .filter((input) => input.value < smallUtxoAggregate.maxInputValue)
479
+ smallUtxo = smallUtxo.sort((a, b) => a.value - b.value)
480
+ smallUtxo = smallUtxo.slice(0, smallUtxoAggregate.minInputCount - selectedInputs.length)
481
+ }
482
+
483
+ return finalCheckAndAddChangeAndExtraInput(
484
+ selectedInputs,
485
+ outputs,
486
+ totalOutputValue,
487
+ outputSize,
488
+ feeRate,
489
+ changeSize,
490
+ highFeeThd,
491
+ lastInput,
492
+ smallUtxo,
493
+ )
494
+ }
495
+
496
+ export function coinSelectAccumulative(
497
+ inputs: ExtendedUtxo[],
498
+ outputs: Target[],
499
+ feeRate: number,
500
+ changeAddressType = "max",
501
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
502
+ highFeeThd = 5,
503
+ ): {
504
+ success: boolean
505
+ inputs: ExtendedUtxo[]
506
+ outputs: Target[]
507
+ fee: number
508
+ totalInputValue: number
509
+ totalOutputValue: number
510
+ needed: number
511
+ change?: { value: number }
512
+ } {
513
+ const totalOutputValue = sumValues(outputs)
514
+
515
+ const outputSize = outputs.reduce((sum, o) => sum + getOutputSize(o, network), 0)
516
+ const changeSize = getOutputSize({ addressType: changeAddressType }, network)
517
+ const baseTxSize = componentBytes.baseTxBytes
518
+
519
+ const selected: ExtendedUtxo[] = []
520
+
521
+ let txSize = outputSize + baseTxSize
522
+ let txFee = fixNumberToInteger(txSize * feeRate)
523
+ for (let index = 0; index < inputs.length; index += 1) {
524
+ const input = inputs[index]
525
+ selected.push(input)
526
+ const inputSize = getInputSize(input.signerInfo.addressType, {
527
+ n: input.signerInfo.numberOfSigners,
528
+ m: input.signerInfo.publicKeys?.length,
529
+ })
530
+ const selectedValue = sumValues(selected)
531
+
532
+ txSize += inputSize
533
+ txFee = txSize * feeRate
534
+
535
+ let totalNeeded = txFee + totalOutputValue
536
+
537
+ if (selectedValue >= totalNeeded) {
538
+ return finalCheckAndAddChangeAndExtraInput(
539
+ selected,
540
+ outputs,
541
+ totalOutputValue,
542
+ outputSize,
543
+ feeRate,
544
+ changeSize,
545
+ highFeeThd,
546
+ inputs[index + 1] || undefined,
547
+ )
548
+ }
549
+ }
550
+ return finalCheckAndAddChangeAndExtraInput(
551
+ selected,
552
+ outputs,
553
+ totalOutputValue,
554
+ outputSize,
555
+ feeRate,
556
+ changeSize,
557
+ highFeeThd,
558
+ undefined,
559
+ )
560
+ }
561
+
562
+ export function coinSelectAll(
563
+ inputs: ExtendedUtxo[],
564
+ targets: Target[],
565
+ feeRate: number,
566
+ changeAddressType = "max",
567
+ network: bitcoin.Network = bitcoin.networks.bitcoin,
568
+ ): {
569
+ success: boolean
570
+ inputs: ExtendedUtxo[]
571
+ outputs: Target[]
572
+ fee: number
573
+ totalInputValue: number
574
+ totalOutputValue: number
575
+ needed: number
576
+ change?: { value: number }
577
+ } {
578
+ if (!targets[0].address) {
579
+ throw new Error("target address is required")
580
+ }
581
+ const changeSize = getOutputSize({ addressType: changeAddressType }, network)
582
+ return finalCheckAndAddChangeAndExtraInput(inputs, [], 0, 0, feeRate, changeSize, 0)
583
+ }
584
+
585
+ // todo : remove --- Sample usage/demo ---
586
+ if (require.main === module) {
587
+ let utxos: ExtendedUtxo[] = [
588
+ {
589
+ signerInfo: { address: "A", publicKey: "", addressType: "p2pkh" },
590
+ hash: "h1",
591
+ value: 50000,
592
+ index: 0,
593
+ },
594
+ {
595
+ signerInfo: { address: "B", publicKey: "", addressType: "p2pkh" },
596
+ hash: "h2",
597
+ value: 30000,
598
+ index: 1,
599
+ },
600
+ {
601
+ signerInfo: { address: "C", publicKey: "", addressType: "p2pkh" },
602
+ hash: "h3",
603
+ value: 4000,
604
+ index: 2,
605
+ },
606
+ {
607
+ signerInfo: { address: "D", publicKey: "", addressType: "p2pkh" },
608
+ hash: "h4",
609
+ value: 10000,
610
+ index: 3,
611
+ },
612
+ ]
613
+ // Mock outputs
614
+
615
+ const outputs: Target[] = [
616
+ { address: "X", value: 6640 },
617
+ // { address: "Y", value: 15000 },
618
+ // { address: "Y", value: 15000 },
619
+ ]
620
+ const outputs2: Target[] = [
621
+ { address: "X", value: 40150 },
622
+ { address: "Y", value: 2484 },
623
+ { address: "Z", value: 50000 },
624
+ ]
625
+ const feeRate = 2 // sats/byte
626
+
627
+ console.log("Coin selection result:", coinSelectBnB(utxos, outputs, feeRate))
628
+ console.log("Coin selection result:", coinSelectAccumulative(utxos, outputs, feeRate))
629
+ console.log("Coin selection result:", coinSelectAll(utxos, outputs, feeRate))
630
+
631
+ console.log("Selection failed:", coinSelectBnB(utxos, outputs2, feeRate))
632
+ console.log("Selection failed:", coinSelectAccumulative(utxos, outputs2, feeRate))
633
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./bitcoin-transaction-builder"
2
2
  export * from "./ordinal-transaction-builder"
3
3
  export * from "./transaction-builder"
4
+ export * from "./coin-select"