@teleportdao/bitcoin 1.7.2 → 1.7.6

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 (52) hide show
  1. package/dist/bitcoin-base.d.ts +93 -0
  2. package/dist/bitcoin-base.d.ts.map +1 -0
  3. package/dist/bitcoin-base.js +236 -0
  4. package/dist/bitcoin-base.js.map +1 -0
  5. package/dist/bitcoin-interface-teleswap.d.ts +6 -1
  6. package/dist/bitcoin-interface-teleswap.d.ts.map +1 -1
  7. package/dist/bitcoin-interface-teleswap.js +2 -4
  8. package/dist/bitcoin-interface-teleswap.js.map +1 -1
  9. package/dist/helper/burn-request-helper.d.ts +7 -0
  10. package/dist/helper/burn-request-helper.d.ts.map +1 -0
  11. package/dist/helper/burn-request-helper.js +26 -0
  12. package/dist/helper/burn-request-helper.js.map +1 -0
  13. package/dist/helper/teleport-request-helper.d.ts +47 -0
  14. package/dist/helper/teleport-request-helper.d.ts.map +1 -0
  15. package/dist/helper/teleport-request-helper.js +146 -0
  16. package/dist/helper/teleport-request-helper.js.map +1 -0
  17. package/dist/helper/teleswap-helper.d.ts +20 -8
  18. package/dist/helper/teleswap-helper.d.ts.map +1 -1
  19. package/dist/helper/teleswap-helper.js +55 -50
  20. package/dist/helper/teleswap-helper.js.map +1 -1
  21. package/dist/teleport-dao-payments.d.ts +76 -0
  22. package/dist/teleport-dao-payments.d.ts.map +1 -0
  23. package/dist/teleport-dao-payments.js +217 -0
  24. package/dist/teleport-dao-payments.js.map +1 -0
  25. package/dist/teleswap-wallet.d.ts +4 -6
  26. package/dist/teleswap-wallet.d.ts.map +1 -1
  27. package/dist/teleswap-wallet.js +7 -8
  28. package/dist/teleswap-wallet.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/bitcoin-interface-ordinal.ts +181 -181
  31. package/src/bitcoin-interface-teleswap.ts +252 -255
  32. package/src/bitcoin-interface-utils.ts +59 -59
  33. package/src/bitcoin-interface.ts +247 -247
  34. package/src/bitcoin-utils.ts +591 -591
  35. package/src/bitcoin-wallet-base.ts +314 -314
  36. package/src/helper/brc20-helper.ts +181 -181
  37. package/src/helper/ordinal-helper.ts +118 -118
  38. package/src/helper/teleswap-helper.ts +77 -101
  39. package/src/index.ts +15 -15
  40. package/src/ordinal-wallet.ts +738 -738
  41. package/src/sign/index.ts +1 -1
  42. package/src/sign/sign-transaction.ts +108 -108
  43. package/src/teleswap-wallet.ts +152 -155
  44. package/src/transaction-builder/bitcoin-transaction-builder.ts +44 -44
  45. package/src/transaction-builder/index.ts +3 -3
  46. package/src/transaction-builder/ordinal-transaction-builder.ts +147 -147
  47. package/src/transaction-builder/transaction-builder.ts +705 -705
  48. package/src/type.ts +43 -43
  49. package/src/utils/networks.ts +33 -33
  50. package/src/utils/tools.ts +89 -89
  51. package/tsconfig.json +9 -9
  52. package/webpack.config.js +16 -16
@@ -1,705 +1,705 @@
1
- /* eslint-disable no-underscore-dangle */
2
- import * as bitcoin from "bitcoinjs-lib"
3
-
4
- import { createAddressObjectByPublicKey, getAddressType } from "../bitcoin-utils"
5
-
6
- const coinselect = require("coinselect")
7
- const coinselectSplit = require("coinselect/split")
8
- const coinselectAccumulative = require("coinselect/accumulative")
9
-
10
- // https://bitcoin.stackexchange.com/questions/84004/how-do-virtual-size-stripped-size-and-raw-size-compare-between-legacy-address-f
11
- // export const componentBytes = {
12
- // bytePerInput: {
13
- // p2pkh: 148,
14
- // p2wpkh: 70, // 68
15
- // "p2sh-p2wpkh": 91,
16
- // p2tr: 60, // actual 58
17
- // },
18
- // baseTxBytes: 10 + 5, // +5 extra bytes to be sure
19
- // bytePerOutput: {
20
- // p2pkh: 35, // 34
21
- // p2wpkh: 35, // 31
22
- // p2sh: 35, // 32
23
- // p2tr: 45, // 43
24
- // default: 35,
25
- // },
26
- //
27
- // }
28
- export const componentBytes = {
29
- bytePerInput: {
30
- p2pkh: 148,
31
- p2wpkh: 68, // 68
32
- "p2sh-p2wpkh": 91,
33
- p2tr: 58, // actual 58
34
- default: 100,
35
- },
36
- baseTxBytes: 10 + 5, // +5 extra bytes to be sure
37
- bytePerOutput: {
38
- p2pkh: 34, // 34
39
- p2wpkh: 31, // 31
40
- p2sh: 32, // 32
41
- p2tr: 43, // 43
42
- default: 35,
43
- max: 45,
44
- },
45
-
46
- scriptExtraBytes: {
47
- lessThan255: 12,
48
- moreThan255: 15,
49
- },
50
- }
51
-
52
- export const DUST = 1000
53
-
54
- export type Utxo = {
55
- hash: string
56
- value: number
57
- index: number
58
- }
59
- export type SignerInfo = {
60
- address: string
61
- publicKey: string
62
- addressType: string
63
- derivationPath?: string
64
- masterFingerprint?: string
65
- includeHex?: boolean
66
- }
67
- export type ExtendedUtxo = {
68
- signerInfo: SignerInfo
69
- hash: string
70
- value: number
71
- index: number
72
- }
73
-
74
- export type TargetAddress = {
75
- address: string
76
- value: number
77
- }
78
-
79
- export type TargetScript = {
80
- script: Buffer
81
- value: number
82
- }
83
- export type Target = TargetAddress | TargetScript
84
- export type ChangeTarget = TargetAddress & {
85
- bip32Derivation?: {
86
- path: string
87
- pubkey: Buffer
88
- masterFingerprint: Buffer
89
- }[]
90
- }
91
-
92
- export type BitcoinJSInputInfo = ExtendedUtxo & {
93
- bip32Derivation?: {
94
- path: string
95
- pubkey: Buffer
96
- masterFingerprint: Buffer
97
- }[]
98
- nonWitnessUtxo?: Buffer
99
- witnessUtxo?: {
100
- script: Buffer
101
- value: number
102
- }
103
- redeemScript?: Buffer
104
- tapInternalKey?: Buffer
105
- }
106
-
107
- export type ExtendedUnsignedTransaction = {
108
- unsignedTransaction: string
109
- outputs: Target[]
110
- inputs: {
111
- hash: string
112
- value: number
113
- index: number
114
- signerInfo: SignerInfo
115
- }[]
116
- fee: number
117
- change: TargetAddress | undefined
118
- }
119
-
120
- function coinSelectInOrder(
121
- utxos: { hash: string; index: number; value: number }[],
122
- outputs: {
123
- address?: string
124
- script?: Buffer
125
- value: number
126
- }[],
127
- feeRate: number,
128
- ) {
129
- let response = coinselectAccumulative(utxos, outputs, 1)
130
- return {
131
- ...response,
132
- fee: +(+response.fee * feeRate).toFixed(),
133
- }
134
- }
135
-
136
- export class BaseTransactionBuilder {
137
- testnet: boolean
138
- network: bitcoin.Network
139
- maximumNumberOfOutputsInTransaction: number
140
- feeMin: number
141
- dustLimit: number
142
- // abstract
143
- constructor({
144
- network,
145
- testnet,
146
- feeMin = 0,
147
- dustLimit,
148
- maximumNumberOfOutputsInTransaction = 50,
149
- }: {
150
- network: bitcoin.Network
151
- testnet: boolean
152
- feeMin?: number
153
- dustLimit?: number
154
- maximumNumberOfOutputsInTransaction?: number
155
- }) {
156
- this.testnet = testnet
157
- this.network = network
158
- this.maximumNumberOfOutputsInTransaction = maximumNumberOfOutputsInTransaction
159
- this.feeMin = feeMin
160
- this.dustLimit = dustLimit || 1 * 2 * componentBytes.bytePerInput.p2pkh
161
- }
162
-
163
- // eslint-disable-next-line no-unused-vars, class-methods-use-this
164
- async _getUtxo(userAddress: string): Promise<Utxo[]> {
165
- // The child has implemented this method.
166
- throw new Error("Do not call abstract method directly")
167
- // return utxo
168
- }
169
-
170
- // eslint-disable-next-line no-unused-vars, class-methods-use-this
171
- async _getTransactionHex(transactionId: string): Promise<string> {
172
- // The child has implemented this method.
173
- throw new Error("Do not call abstract method directly")
174
- // return utxo
175
- }
176
-
177
- // eslint-disable-next-line no-unused-vars, class-methods-use-this
178
- createAddressObject(input: { addressType: string; publicKey: Buffer }) {
179
- return createAddressObjectByPublicKey(input, this.network)
180
- }
181
-
182
- // methods
183
- validateAddress(address: string) {
184
- try {
185
- getAddressType(address, this.network)
186
- return true
187
- } catch (error) {
188
- return false
189
- }
190
- }
191
-
192
- getOpReturnTarget(dataHex: string) {
193
- if (!(dataHex.length > 0)) throw new Error("invalid data in hex")
194
- const embed = bitcoin.payments.embed({
195
- data: [Buffer.from(dataHex, "hex")],
196
- network: this.network,
197
- })
198
- return {
199
- script: embed.output!,
200
- value: 0,
201
- }
202
- }
203
-
204
- async getExtendedUtxo(signerInfo: SignerInfo) {
205
- let utxo = await this._getUtxo(signerInfo.address)
206
- const extendedUtxo = utxo.map((input) => ({
207
- ...input,
208
- signerInfo,
209
- }))
210
- if (!extendedUtxo || extendedUtxo.length === 0) {
211
- throw new Error("no utxo found")
212
- }
213
- return extendedUtxo
214
- }
215
-
216
- calculateTxSize(
217
- inputTypes: string[],
218
- outputs: {
219
- script?: Buffer
220
- address?: string
221
- value: number
222
- }[],
223
- changeAddressType = "default",
224
- ) {
225
- const inputsSizes = inputTypes.map(
226
- (addressType) =>
227
- componentBytes.bytePerInput[addressType as keyof typeof componentBytes.bytePerInput],
228
- )
229
- const outputSizes = outputs.map((outP: any) => {
230
- if (outP.address) {
231
- let addressType = "default"
232
- try {
233
- addressType = getAddressType(outP.address, this.network)
234
- } catch {
235
- addressType = "default"
236
- }
237
- return componentBytes.bytePerOutput[
238
- addressType as keyof typeof componentBytes.bytePerOutput
239
- ]
240
- }
241
-
242
- if (outP.script) {
243
- if (outP.script.byteLength < 255) {
244
- return outP.script.byteLength + componentBytes.scriptExtraBytes.lessThan255
245
- }
246
- return outP.script.byteLength + componentBytes.scriptExtraBytes.moreThan255
247
- }
248
-
249
- return componentBytes.bytePerOutput[
250
- changeAddressType as keyof typeof componentBytes.bytePerOutput
251
- ]
252
- })
253
-
254
- const txSize =
255
- componentBytes.baseTxBytes +
256
- inputsSizes.reduce((a, c) => a + c, 0) +
257
- outputSizes.reduce((a, c) => a + c, 0)
258
-
259
- return txSize
260
- }
261
-
262
- helperHandleInputsAndOutputs({
263
- targets,
264
- extendedUtxo,
265
- feeRate,
266
- changeObject,
267
- selectType = "normal", // "accumulative" | "normal" | "full"
268
- }: {
269
- extendedUtxo: ExtendedUtxo[]
270
- targets: Target[]
271
- feeRate: number
272
- changeObject?: {
273
- address: string
274
- publicKey?: string
275
- addressType?: string
276
- derivationPath?: string
277
- masterFingerprint?: string
278
- }
279
- selectType?: "normal" | "accumulative" | "full" | "inOrder"
280
- }) {
281
- const filteredUtxo = extendedUtxo.filter(
282
- (u) =>
283
- u.value >
284
- +feeRate *
285
- componentBytes.bytePerInput[
286
- u.signerInfo.addressType as keyof typeof componentBytes.bytePerInput
287
- ],
288
- )
289
- let selectResponse
290
- switch (selectType) {
291
- case "normal":
292
- selectResponse = coinselect(filteredUtxo, targets, Math.round(feeRate))
293
- break
294
- case "accumulative":
295
- selectResponse = coinselectAccumulative(filteredUtxo, targets, Math.round(feeRate))
296
- break
297
- case "inOrder":
298
- selectResponse = coinSelectInOrder(filteredUtxo, targets, Math.round(feeRate))
299
- break
300
- case "full":
301
- if (!(targets[0] as TargetAddress).address) {
302
- throw new Error()
303
- }
304
- selectResponse = coinselectSplit(
305
- filteredUtxo,
306
- [{ address: (targets[0] as TargetAddress).address }],
307
- Math.round(feeRate),
308
- )
309
- break
310
- default:
311
- break
312
- }
313
-
314
- let {
315
- inputs,
316
- outputs,
317
- fee,
318
- }: {
319
- inputs?: ExtendedUtxo[]
320
- outputs?: {
321
- script?: Buffer
322
- address?: string
323
- value: number
324
- }[]
325
- fee: number
326
- } = selectResponse
327
-
328
- if (!inputs || !outputs) {
329
- inputs = filteredUtxo
330
- outputs = targets
331
- fee = inputs.reduce((a, b) => a + b.value, 0) - outputs.reduce((a, b) => a + b.value, 0)
332
- }
333
-
334
- let changeAddressType = "default"
335
- try {
336
- changeAddressType = getAddressType(changeObject?.address || "", this.network)
337
- } catch {
338
- changeAddressType = "default"
339
- }
340
-
341
- const txSize =
342
- this.calculateTxSize(
343
- inputs.map((i) => i.signerInfo.addressType),
344
- outputs,
345
- changeAddressType,
346
- ) + componentBytes.bytePerOutput.default
347
-
348
- let txFee = Math.round(txSize * feeRate)
349
- if (Math.round(feeRate) === 1) {
350
- txFee = Math.round(txFee + txFee * 0.1)
351
- }
352
- if (
353
- inputs.reduce((a, b) => a + b.value, 0) -
354
- outputs.filter((o) => o.address || o.script).reduce((a, b) => a + b.value, 0) -
355
- txFee <
356
- 0
357
- ) {
358
- let spendableBalance = inputs.reduce((a, b) => a + b.value, 0)
359
- let totalOutputAmount = outputs
360
- .filter((o) => o.address || o.script)
361
- .reduce((a, b) => a + b.value, 0)
362
- let need = spendableBalance - totalOutputAmount - txFee
363
- throw new Error(
364
- `not enough balance. details: ${JSON.stringify(
365
- { spendableBalance, totalOutputAmount, txFee, need },
366
- null,
367
- 2,
368
- )}`,
369
- )
370
- }
371
- let diff = fee - txFee
372
- let changeIndex = outputs.findIndex((x) => !x?.address && !x.script && (x.value || 0) > 0)
373
- let change: ChangeTarget | undefined
374
- if (changeIndex >= 0 || diff > DUST) {
375
- if (changeIndex >= 0) {
376
- diff = diff + componentBytes.bytePerOutput.default * Math.round(feeRate)
377
- }
378
-
379
- if (diff < 0) {
380
- diff = 0
381
- }
382
-
383
- if (selectType === "full") {
384
- outputs[0].value = outputs[0].value + diff
385
- fee = fee - diff
386
- } else {
387
- if (!changeObject) throw new Error("change not exist")
388
- change = {
389
- address: changeObject.address,
390
- value: changeIndex >= 0 ? outputs[changeIndex].value + diff : diff,
391
- }
392
- fee = fee - diff
393
- }
394
-
395
- if (changeIndex >= 0) {
396
- outputs.splice(changeIndex, 1)
397
- }
398
- }
399
-
400
- return {
401
- inputs,
402
- fee,
403
- outputs: outputs as Target[],
404
- change,
405
- }
406
- }
407
-
408
- async filterAndConvertTxDataToStandardFormat({
409
- extendedUtxo,
410
- targets,
411
- changeObject,
412
- feeRate,
413
- selectType,
414
- }: {
415
- extendedUtxo: ExtendedUtxo[]
416
- targets: Target[]
417
- feeRate: number
418
- changeObject?: {
419
- address: string
420
- publicKey?: string
421
- addressType?: string
422
- derivationPath?: string
423
- masterFingerprint?: string
424
- }
425
- selectType?: "normal" | "accumulative" | "full" | "inOrder"
426
- }) {
427
- let {
428
- inputs: filteredInputs,
429
- outputs,
430
- change,
431
- fee,
432
- } = this.helperHandleInputsAndOutputs({
433
- targets,
434
- extendedUtxo,
435
- feeRate,
436
- changeObject,
437
- selectType,
438
- })
439
-
440
- let inputs = await this.convertExtendedUtxoToInputs(filteredInputs)
441
-
442
- return {
443
- inputs,
444
- outputs,
445
- change,
446
- fee,
447
- feeRate,
448
- }
449
- }
450
-
451
- // ?note : we can extend this class and change this method for network other than bitcoin
452
- async convertExtendedUtxoToInputs(baseInputs: ExtendedUtxo[] = []) {
453
- let inputs: (BitcoinJSInputInfo & {
454
- signerInfo: SignerInfo
455
- })[] = baseInputs
456
- let transactionId: string | null = null
457
- let transactionHex: string | null = null
458
- for (let i in inputs) {
459
- let { address, publicKey, derivationPath, masterFingerprint, addressType } =
460
- inputs[i].signerInfo
461
- // todo : support without publicKey
462
- let addressObject = this.createAddressObject({
463
- publicKey: Buffer.from(publicKey, "hex"),
464
- addressType,
465
- })
466
- if (derivationPath && masterFingerprint && addressObject.pubkey) {
467
- inputs[i].bip32Derivation = [
468
- {
469
- path: derivationPath,
470
- pubkey: addressObject.pubkey,
471
- masterFingerprint: Buffer.from(masterFingerprint, "hex"),
472
- },
473
- ]
474
- }
475
- if (addressType === "p2pkh") {
476
- // add p2pkh data
477
- if (transactionHex && transactionId === inputs[i].hash) {
478
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
479
- } else {
480
- transactionHex = await this._getTransactionHex(inputs[i].hash)
481
- transactionId = inputs[i].hash
482
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
483
- }
484
- } else if (addressType === "p2wpkh") {
485
- // add p2wpkh data
486
- if (!addressObject.output) throw new Error("invalid signer info")
487
- inputs[i].witnessUtxo = {
488
- script: addressObject.output,
489
- value: inputs[i].value,
490
- }
491
-
492
- if (inputs[i].signerInfo.includeHex) {
493
- if (transactionHex && transactionId === inputs[i].hash) {
494
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
495
- } else {
496
- transactionHex = await this._getTransactionHex(inputs[i].hash)
497
- transactionId = inputs[i].hash
498
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
499
- }
500
- }
501
- } else if (addressType === "p2sh-p2wpkh") {
502
- // add p2sh-p2wpkh data
503
- if (!addressObject.output) throw new Error("invalid signer info")
504
- inputs[i].witnessUtxo = {
505
- script: addressObject.output,
506
- value: inputs[i].value,
507
- }
508
- if (!addressObject?.redeem?.output) throw new Error("invalid signer info for p2sh address")
509
- inputs[i].redeemScript = addressObject.redeem.output
510
-
511
- if (inputs[i].signerInfo.includeHex) {
512
- if (transactionHex && transactionId === inputs[i].hash) {
513
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
514
- } else {
515
- transactionHex = await this._getTransactionHex(inputs[i].hash)
516
- transactionId = inputs[i].hash
517
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
518
- }
519
- }
520
- } else if (addressType === "p2tr") {
521
- if (!addressObject.output) throw new Error("invalid signer info")
522
- inputs[i].witnessUtxo = {
523
- script: addressObject.output,
524
- value: inputs[i].value,
525
- }
526
- if (!addressObject.pubkey) throw new Error("invalid signer info for p2tr address (pubkey)")
527
- inputs[i].tapInternalKey = addressObject.internalPubkey
528
-
529
- if (inputs[i].signerInfo.includeHex) {
530
- if (transactionHex && transactionId === inputs[i].hash) {
531
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
532
- } else {
533
- transactionHex = await this._getTransactionHex(inputs[i].hash)
534
- transactionId = inputs[i].hash
535
- inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
536
- }
537
- }
538
- }
539
- }
540
-
541
- return inputs
542
- }
543
-
544
- // ?note : we can extend this class and change this method for network other than bitcoin
545
- createUnsignedTransaction({
546
- inputs,
547
- outputs,
548
- change,
549
- fee, // not used in this section - just returned
550
- feeRate,
551
- }: {
552
- inputs: BitcoinJSInputInfo[]
553
- outputs: Target[]
554
- change?: ChangeTarget
555
- fee: number
556
- feeRate: number
557
- }) {
558
- const sequence = 0xffffffff - 2
559
- const { network } = this
560
- const newPsbt = new bitcoin.Psbt({ network })
561
- newPsbt.setMaximumFeeRate(+(feeRate + feeRate / 100).toFixed())
562
- // add input
563
- for (const input of inputs) {
564
- let { addressType } = input.signerInfo
565
- switch (addressType) {
566
- case "p2pkh": {
567
- let i = {
568
- hash: input.hash,
569
- index: Number(input.index),
570
- nonWitnessUtxo: input.nonWitnessUtxo,
571
- sequence,
572
- bip32Derivation: input.bip32Derivation,
573
- }
574
- if (!i.bip32Derivation) delete i.bip32Derivation
575
- newPsbt.addInput(i)
576
- break
577
- }
578
- case "p2wpkh": {
579
- let i = {
580
- hash: input.hash,
581
- index: Number(input.index),
582
- witnessUtxo: input.witnessUtxo,
583
- // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
584
- nonWitnessUtxo: input.nonWitnessUtxo,
585
- sequence,
586
- bip32Derivation: input.bip32Derivation,
587
- }
588
- if (!i.bip32Derivation) delete i.bip32Derivation
589
- if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
590
- newPsbt.addInput(i)
591
- break
592
- }
593
- case "p2sh-p2wpkh": {
594
- let i = {
595
- hash: input.hash,
596
- index: Number(input.index),
597
- witnessUtxo: input.witnessUtxo,
598
- // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
599
- nonWitnessUtxo: input.nonWitnessUtxo,
600
- redeemScript: input.redeemScript,
601
- sequence,
602
- bip32Derivation: input.bip32Derivation,
603
- }
604
- if (!i.bip32Derivation) delete i.bip32Derivation
605
- if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
606
- newPsbt.addInput(i)
607
- break
608
- }
609
- case "p2tr": {
610
- let i = {
611
- hash: input.hash,
612
- index: Number(input.index),
613
- witnessUtxo: input.witnessUtxo,
614
- // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
615
- nonWitnessUtxo: input.nonWitnessUtxo,
616
- tapInternalKey: input.tapInternalKey,
617
- sequence,
618
- bip32Derivation: input.bip32Derivation,
619
- }
620
- if (!i.bip32Derivation) delete i.bip32Derivation
621
- if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
622
- newPsbt.addInput(i)
623
- break
624
- }
625
- default:
626
- throw new Error("address type is incorrect")
627
- }
628
- }
629
-
630
- // add outputs
631
- for (const target of outputs) {
632
- newPsbt.addOutput(target)
633
- }
634
-
635
- // add changeAddress
636
- if (change && Object.keys(change).length !== 0) {
637
- newPsbt.addOutput(change)
638
- }
639
-
640
- // check created outputs with targets
641
- if (change && Object.keys(change).length !== 0) {
642
- if (newPsbt.txOutputs[outputs.length].address !== change.address) {
643
- throw new Error("error change address")
644
- }
645
- // if (newPsbt.txOutputs[outputs.length].value !== change.value) {
646
- // throw new Error("error change value")
647
- // }
648
- }
649
-
650
- const unsignedPsbtBaseText = newPsbt.toBase64()
651
- return {
652
- unsignedTransaction: unsignedPsbtBaseText,
653
- outputs,
654
- inputs: inputs.map((utx) => ({
655
- hash: utx.hash,
656
- value: Number(utx.value),
657
- index: utx.index,
658
- signerInfo: utx.signerInfo,
659
- })),
660
- fee,
661
- change,
662
- }
663
- }
664
-
665
- async processUnsignedTransaction({
666
- extendedUtxo,
667
- targets = [],
668
- changeAddress = undefined,
669
- fullAmount = false,
670
- feeRate,
671
- selectType = "normal",
672
- }: {
673
- extendedUtxo: ExtendedUtxo[]
674
- targets: Target[]
675
- feeRate: number
676
-
677
- changeAddress?: string | SignerInfo
678
- fullAmount?: boolean
679
- selectType?: "normal" | "accumulative" | "full" | "inOrder"
680
- }) {
681
- if (!changeAddress && targets.length === 0) throw new Error("no target")
682
- let changeObject =
683
- typeof changeAddress === "string"
684
- ? {
685
- address: changeAddress,
686
- }
687
- : changeAddress
688
- const { inputs, outputs, change, fee } = await this.filterAndConvertTxDataToStandardFormat({
689
- extendedUtxo,
690
- targets,
691
- changeObject,
692
- feeRate,
693
- selectType: fullAmount ? "full" : selectType,
694
- })
695
- let unsignedTransaction = this.createUnsignedTransaction({
696
- inputs,
697
- outputs,
698
- change,
699
- fee,
700
- feeRate,
701
- })
702
-
703
- return unsignedTransaction
704
- }
705
- }
1
+ /* eslint-disable no-underscore-dangle */
2
+ import * as bitcoin from "bitcoinjs-lib"
3
+
4
+ import { createAddressObjectByPublicKey, getAddressType } from "../bitcoin-utils"
5
+
6
+ const coinselect = require("coinselect")
7
+ const coinselectSplit = require("coinselect/split")
8
+ const coinselectAccumulative = require("coinselect/accumulative")
9
+
10
+ // https://bitcoin.stackexchange.com/questions/84004/how-do-virtual-size-stripped-size-and-raw-size-compare-between-legacy-address-f
11
+ // export const componentBytes = {
12
+ // bytePerInput: {
13
+ // p2pkh: 148,
14
+ // p2wpkh: 70, // 68
15
+ // "p2sh-p2wpkh": 91,
16
+ // p2tr: 60, // actual 58
17
+ // },
18
+ // baseTxBytes: 10 + 5, // +5 extra bytes to be sure
19
+ // bytePerOutput: {
20
+ // p2pkh: 35, // 34
21
+ // p2wpkh: 35, // 31
22
+ // p2sh: 35, // 32
23
+ // p2tr: 45, // 43
24
+ // default: 35,
25
+ // },
26
+ //
27
+ // }
28
+ export const componentBytes = {
29
+ bytePerInput: {
30
+ p2pkh: 148,
31
+ p2wpkh: 68, // 68
32
+ "p2sh-p2wpkh": 91,
33
+ p2tr: 58, // actual 58
34
+ default: 100,
35
+ },
36
+ baseTxBytes: 10 + 5, // +5 extra bytes to be sure
37
+ bytePerOutput: {
38
+ p2pkh: 34, // 34
39
+ p2wpkh: 31, // 31
40
+ p2sh: 32, // 32
41
+ p2tr: 43, // 43
42
+ default: 35,
43
+ max: 45,
44
+ },
45
+
46
+ scriptExtraBytes: {
47
+ lessThan255: 12,
48
+ moreThan255: 15,
49
+ },
50
+ }
51
+
52
+ export const DUST = 1000
53
+
54
+ export type Utxo = {
55
+ hash: string
56
+ value: number
57
+ index: number
58
+ }
59
+ export type SignerInfo = {
60
+ address: string
61
+ publicKey: string
62
+ addressType: string
63
+ derivationPath?: string
64
+ masterFingerprint?: string
65
+ includeHex?: boolean
66
+ }
67
+ export type ExtendedUtxo = {
68
+ signerInfo: SignerInfo
69
+ hash: string
70
+ value: number
71
+ index: number
72
+ }
73
+
74
+ export type TargetAddress = {
75
+ address: string
76
+ value: number
77
+ }
78
+
79
+ export type TargetScript = {
80
+ script: Buffer
81
+ value: number
82
+ }
83
+ export type Target = TargetAddress | TargetScript
84
+ export type ChangeTarget = TargetAddress & {
85
+ bip32Derivation?: {
86
+ path: string
87
+ pubkey: Buffer
88
+ masterFingerprint: Buffer
89
+ }[]
90
+ }
91
+
92
+ export type BitcoinJSInputInfo = ExtendedUtxo & {
93
+ bip32Derivation?: {
94
+ path: string
95
+ pubkey: Buffer
96
+ masterFingerprint: Buffer
97
+ }[]
98
+ nonWitnessUtxo?: Buffer
99
+ witnessUtxo?: {
100
+ script: Buffer
101
+ value: number
102
+ }
103
+ redeemScript?: Buffer
104
+ tapInternalKey?: Buffer
105
+ }
106
+
107
+ export type ExtendedUnsignedTransaction = {
108
+ unsignedTransaction: string
109
+ outputs: Target[]
110
+ inputs: {
111
+ hash: string
112
+ value: number
113
+ index: number
114
+ signerInfo: SignerInfo
115
+ }[]
116
+ fee: number
117
+ change: TargetAddress | undefined
118
+ }
119
+
120
+ function coinSelectInOrder(
121
+ utxos: { hash: string; index: number; value: number }[],
122
+ outputs: {
123
+ address?: string
124
+ script?: Buffer
125
+ value: number
126
+ }[],
127
+ feeRate: number,
128
+ ) {
129
+ let response = coinselectAccumulative(utxos, outputs, 1)
130
+ return {
131
+ ...response,
132
+ fee: +(+response.fee * feeRate).toFixed(),
133
+ }
134
+ }
135
+
136
+ export class BaseTransactionBuilder {
137
+ testnet: boolean
138
+ network: bitcoin.Network
139
+ maximumNumberOfOutputsInTransaction: number
140
+ feeMin: number
141
+ dustLimit: number
142
+ // abstract
143
+ constructor({
144
+ network,
145
+ testnet,
146
+ feeMin = 0,
147
+ dustLimit,
148
+ maximumNumberOfOutputsInTransaction = 50,
149
+ }: {
150
+ network: bitcoin.Network
151
+ testnet: boolean
152
+ feeMin?: number
153
+ dustLimit?: number
154
+ maximumNumberOfOutputsInTransaction?: number
155
+ }) {
156
+ this.testnet = testnet
157
+ this.network = network
158
+ this.maximumNumberOfOutputsInTransaction = maximumNumberOfOutputsInTransaction
159
+ this.feeMin = feeMin
160
+ this.dustLimit = dustLimit || 1 * 2 * componentBytes.bytePerInput.p2pkh
161
+ }
162
+
163
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
164
+ async _getUtxo(userAddress: string): Promise<Utxo[]> {
165
+ // The child has implemented this method.
166
+ throw new Error("Do not call abstract method directly")
167
+ // return utxo
168
+ }
169
+
170
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
171
+ async _getTransactionHex(transactionId: string): Promise<string> {
172
+ // The child has implemented this method.
173
+ throw new Error("Do not call abstract method directly")
174
+ // return utxo
175
+ }
176
+
177
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
178
+ createAddressObject(input: { addressType: string; publicKey: Buffer }) {
179
+ return createAddressObjectByPublicKey(input, this.network)
180
+ }
181
+
182
+ // methods
183
+ validateAddress(address: string) {
184
+ try {
185
+ getAddressType(address, this.network)
186
+ return true
187
+ } catch (error) {
188
+ return false
189
+ }
190
+ }
191
+
192
+ getOpReturnTarget(dataHex: string) {
193
+ if (!(dataHex.length > 0)) throw new Error("invalid data in hex")
194
+ const embed = bitcoin.payments.embed({
195
+ data: [Buffer.from(dataHex, "hex")],
196
+ network: this.network,
197
+ })
198
+ return {
199
+ script: embed.output!,
200
+ value: 0,
201
+ }
202
+ }
203
+
204
+ async getExtendedUtxo(signerInfo: SignerInfo) {
205
+ let utxo = await this._getUtxo(signerInfo.address)
206
+ const extendedUtxo = utxo.map((input) => ({
207
+ ...input,
208
+ signerInfo,
209
+ }))
210
+ if (!extendedUtxo || extendedUtxo.length === 0) {
211
+ throw new Error("no utxo found")
212
+ }
213
+ return extendedUtxo
214
+ }
215
+
216
+ calculateTxSize(
217
+ inputTypes: string[],
218
+ outputs: {
219
+ script?: Buffer
220
+ address?: string
221
+ value: number
222
+ }[],
223
+ changeAddressType = "default",
224
+ ) {
225
+ const inputsSizes = inputTypes.map(
226
+ (addressType) =>
227
+ componentBytes.bytePerInput[addressType as keyof typeof componentBytes.bytePerInput],
228
+ )
229
+ const outputSizes = outputs.map((outP: any) => {
230
+ if (outP.address) {
231
+ let addressType = "default"
232
+ try {
233
+ addressType = getAddressType(outP.address, this.network)
234
+ } catch {
235
+ addressType = "default"
236
+ }
237
+ return componentBytes.bytePerOutput[
238
+ addressType as keyof typeof componentBytes.bytePerOutput
239
+ ]
240
+ }
241
+
242
+ if (outP.script) {
243
+ if (outP.script.byteLength < 255) {
244
+ return outP.script.byteLength + componentBytes.scriptExtraBytes.lessThan255
245
+ }
246
+ return outP.script.byteLength + componentBytes.scriptExtraBytes.moreThan255
247
+ }
248
+
249
+ return componentBytes.bytePerOutput[
250
+ changeAddressType as keyof typeof componentBytes.bytePerOutput
251
+ ]
252
+ })
253
+
254
+ const txSize =
255
+ componentBytes.baseTxBytes +
256
+ inputsSizes.reduce((a, c) => a + c, 0) +
257
+ outputSizes.reduce((a, c) => a + c, 0)
258
+
259
+ return txSize
260
+ }
261
+
262
+ helperHandleInputsAndOutputs({
263
+ targets,
264
+ extendedUtxo,
265
+ feeRate,
266
+ changeObject,
267
+ selectType = "normal", // "accumulative" | "normal" | "full"
268
+ }: {
269
+ extendedUtxo: ExtendedUtxo[]
270
+ targets: Target[]
271
+ feeRate: number
272
+ changeObject?: {
273
+ address: string
274
+ publicKey?: string
275
+ addressType?: string
276
+ derivationPath?: string
277
+ masterFingerprint?: string
278
+ }
279
+ selectType?: "normal" | "accumulative" | "full" | "inOrder"
280
+ }) {
281
+ const filteredUtxo = extendedUtxo.filter(
282
+ (u) =>
283
+ u.value >
284
+ +feeRate *
285
+ componentBytes.bytePerInput[
286
+ u.signerInfo.addressType as keyof typeof componentBytes.bytePerInput
287
+ ],
288
+ )
289
+ let selectResponse
290
+ switch (selectType) {
291
+ case "normal":
292
+ selectResponse = coinselect(filteredUtxo, targets, Math.round(feeRate))
293
+ break
294
+ case "accumulative":
295
+ selectResponse = coinselectAccumulative(filteredUtxo, targets, Math.round(feeRate))
296
+ break
297
+ case "inOrder":
298
+ selectResponse = coinSelectInOrder(filteredUtxo, targets, Math.round(feeRate))
299
+ break
300
+ case "full":
301
+ if (!(targets[0] as TargetAddress).address) {
302
+ throw new Error()
303
+ }
304
+ selectResponse = coinselectSplit(
305
+ filteredUtxo,
306
+ [{ address: (targets[0] as TargetAddress).address }],
307
+ Math.round(feeRate),
308
+ )
309
+ break
310
+ default:
311
+ break
312
+ }
313
+
314
+ let {
315
+ inputs,
316
+ outputs,
317
+ fee,
318
+ }: {
319
+ inputs?: ExtendedUtxo[]
320
+ outputs?: {
321
+ script?: Buffer
322
+ address?: string
323
+ value: number
324
+ }[]
325
+ fee: number
326
+ } = selectResponse
327
+
328
+ if (!inputs || !outputs) {
329
+ inputs = filteredUtxo
330
+ outputs = targets
331
+ fee = inputs.reduce((a, b) => a + b.value, 0) - outputs.reduce((a, b) => a + b.value, 0)
332
+ }
333
+
334
+ let changeAddressType = "default"
335
+ try {
336
+ changeAddressType = getAddressType(changeObject?.address || "", this.network)
337
+ } catch {
338
+ changeAddressType = "default"
339
+ }
340
+
341
+ const txSize =
342
+ this.calculateTxSize(
343
+ inputs.map((i) => i.signerInfo.addressType),
344
+ outputs,
345
+ changeAddressType,
346
+ ) + componentBytes.bytePerOutput.default
347
+
348
+ let txFee = Math.round(txSize * feeRate)
349
+ if (Math.round(feeRate) === 1) {
350
+ txFee = Math.round(txFee + txFee * 0.1)
351
+ }
352
+ if (
353
+ inputs.reduce((a, b) => a + b.value, 0) -
354
+ outputs.filter((o) => o.address || o.script).reduce((a, b) => a + b.value, 0) -
355
+ txFee <
356
+ 0
357
+ ) {
358
+ let spendableBalance = inputs.reduce((a, b) => a + b.value, 0)
359
+ let totalOutputAmount = outputs
360
+ .filter((o) => o.address || o.script)
361
+ .reduce((a, b) => a + b.value, 0)
362
+ let need = spendableBalance - totalOutputAmount - txFee
363
+ throw new Error(
364
+ `not enough balance. details: ${JSON.stringify(
365
+ { spendableBalance, totalOutputAmount, txFee, need },
366
+ null,
367
+ 2,
368
+ )}`,
369
+ )
370
+ }
371
+ let diff = fee - txFee
372
+ let changeIndex = outputs.findIndex((x) => !x?.address && !x.script && (x.value || 0) > 0)
373
+ let change: ChangeTarget | undefined
374
+ if (changeIndex >= 0 || diff > DUST) {
375
+ if (changeIndex >= 0) {
376
+ diff = diff + componentBytes.bytePerOutput.default * Math.round(feeRate)
377
+ }
378
+
379
+ if (diff < 0) {
380
+ diff = 0
381
+ }
382
+
383
+ if (selectType === "full") {
384
+ outputs[0].value = outputs[0].value + diff
385
+ fee = fee - diff
386
+ } else {
387
+ if (!changeObject) throw new Error("change not exist")
388
+ change = {
389
+ address: changeObject.address,
390
+ value: changeIndex >= 0 ? outputs[changeIndex].value + diff : diff,
391
+ }
392
+ fee = fee - diff
393
+ }
394
+
395
+ if (changeIndex >= 0) {
396
+ outputs.splice(changeIndex, 1)
397
+ }
398
+ }
399
+
400
+ return {
401
+ inputs,
402
+ fee,
403
+ outputs: outputs as Target[],
404
+ change,
405
+ }
406
+ }
407
+
408
+ async filterAndConvertTxDataToStandardFormat({
409
+ extendedUtxo,
410
+ targets,
411
+ changeObject,
412
+ feeRate,
413
+ selectType,
414
+ }: {
415
+ extendedUtxo: ExtendedUtxo[]
416
+ targets: Target[]
417
+ feeRate: number
418
+ changeObject?: {
419
+ address: string
420
+ publicKey?: string
421
+ addressType?: string
422
+ derivationPath?: string
423
+ masterFingerprint?: string
424
+ }
425
+ selectType?: "normal" | "accumulative" | "full" | "inOrder"
426
+ }) {
427
+ let {
428
+ inputs: filteredInputs,
429
+ outputs,
430
+ change,
431
+ fee,
432
+ } = this.helperHandleInputsAndOutputs({
433
+ targets,
434
+ extendedUtxo,
435
+ feeRate,
436
+ changeObject,
437
+ selectType,
438
+ })
439
+
440
+ let inputs = await this.convertExtendedUtxoToInputs(filteredInputs)
441
+
442
+ return {
443
+ inputs,
444
+ outputs,
445
+ change,
446
+ fee,
447
+ feeRate,
448
+ }
449
+ }
450
+
451
+ // ?note : we can extend this class and change this method for network other than bitcoin
452
+ async convertExtendedUtxoToInputs(baseInputs: ExtendedUtxo[] = []) {
453
+ let inputs: (BitcoinJSInputInfo & {
454
+ signerInfo: SignerInfo
455
+ })[] = baseInputs
456
+ let transactionId: string | null = null
457
+ let transactionHex: string | null = null
458
+ for (let i in inputs) {
459
+ let { address, publicKey, derivationPath, masterFingerprint, addressType } =
460
+ inputs[i].signerInfo
461
+ // todo : support without publicKey
462
+ let addressObject = this.createAddressObject({
463
+ publicKey: Buffer.from(publicKey, "hex"),
464
+ addressType,
465
+ })
466
+ if (derivationPath && masterFingerprint && addressObject.pubkey) {
467
+ inputs[i].bip32Derivation = [
468
+ {
469
+ path: derivationPath,
470
+ pubkey: addressObject.pubkey,
471
+ masterFingerprint: Buffer.from(masterFingerprint, "hex"),
472
+ },
473
+ ]
474
+ }
475
+ if (addressType === "p2pkh") {
476
+ // add p2pkh data
477
+ if (transactionHex && transactionId === inputs[i].hash) {
478
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
479
+ } else {
480
+ transactionHex = await this._getTransactionHex(inputs[i].hash)
481
+ transactionId = inputs[i].hash
482
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
483
+ }
484
+ } else if (addressType === "p2wpkh") {
485
+ // add p2wpkh data
486
+ if (!addressObject.output) throw new Error("invalid signer info")
487
+ inputs[i].witnessUtxo = {
488
+ script: addressObject.output,
489
+ value: inputs[i].value,
490
+ }
491
+
492
+ if (inputs[i].signerInfo.includeHex) {
493
+ if (transactionHex && transactionId === inputs[i].hash) {
494
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
495
+ } else {
496
+ transactionHex = await this._getTransactionHex(inputs[i].hash)
497
+ transactionId = inputs[i].hash
498
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
499
+ }
500
+ }
501
+ } else if (addressType === "p2sh-p2wpkh") {
502
+ // add p2sh-p2wpkh data
503
+ if (!addressObject.output) throw new Error("invalid signer info")
504
+ inputs[i].witnessUtxo = {
505
+ script: addressObject.output,
506
+ value: inputs[i].value,
507
+ }
508
+ if (!addressObject?.redeem?.output) throw new Error("invalid signer info for p2sh address")
509
+ inputs[i].redeemScript = addressObject.redeem.output
510
+
511
+ if (inputs[i].signerInfo.includeHex) {
512
+ if (transactionHex && transactionId === inputs[i].hash) {
513
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
514
+ } else {
515
+ transactionHex = await this._getTransactionHex(inputs[i].hash)
516
+ transactionId = inputs[i].hash
517
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
518
+ }
519
+ }
520
+ } else if (addressType === "p2tr") {
521
+ if (!addressObject.output) throw new Error("invalid signer info")
522
+ inputs[i].witnessUtxo = {
523
+ script: addressObject.output,
524
+ value: inputs[i].value,
525
+ }
526
+ if (!addressObject.pubkey) throw new Error("invalid signer info for p2tr address (pubkey)")
527
+ inputs[i].tapInternalKey = addressObject.internalPubkey
528
+
529
+ if (inputs[i].signerInfo.includeHex) {
530
+ if (transactionHex && transactionId === inputs[i].hash) {
531
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
532
+ } else {
533
+ transactionHex = await this._getTransactionHex(inputs[i].hash)
534
+ transactionId = inputs[i].hash
535
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ return inputs
542
+ }
543
+
544
+ // ?note : we can extend this class and change this method for network other than bitcoin
545
+ createUnsignedTransaction({
546
+ inputs,
547
+ outputs,
548
+ change,
549
+ fee, // not used in this section - just returned
550
+ feeRate,
551
+ }: {
552
+ inputs: BitcoinJSInputInfo[]
553
+ outputs: Target[]
554
+ change?: ChangeTarget
555
+ fee: number
556
+ feeRate: number
557
+ }) {
558
+ const sequence = 0xffffffff - 2
559
+ const { network } = this
560
+ const newPsbt = new bitcoin.Psbt({ network })
561
+ newPsbt.setMaximumFeeRate(+(feeRate + feeRate / 100).toFixed())
562
+ // add input
563
+ for (const input of inputs) {
564
+ let { addressType } = input.signerInfo
565
+ switch (addressType) {
566
+ case "p2pkh": {
567
+ let i = {
568
+ hash: input.hash,
569
+ index: Number(input.index),
570
+ nonWitnessUtxo: input.nonWitnessUtxo,
571
+ sequence,
572
+ bip32Derivation: input.bip32Derivation,
573
+ }
574
+ if (!i.bip32Derivation) delete i.bip32Derivation
575
+ newPsbt.addInput(i)
576
+ break
577
+ }
578
+ case "p2wpkh": {
579
+ let i = {
580
+ hash: input.hash,
581
+ index: Number(input.index),
582
+ witnessUtxo: input.witnessUtxo,
583
+ // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
584
+ nonWitnessUtxo: input.nonWitnessUtxo,
585
+ sequence,
586
+ bip32Derivation: input.bip32Derivation,
587
+ }
588
+ if (!i.bip32Derivation) delete i.bip32Derivation
589
+ if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
590
+ newPsbt.addInput(i)
591
+ break
592
+ }
593
+ case "p2sh-p2wpkh": {
594
+ let i = {
595
+ hash: input.hash,
596
+ index: Number(input.index),
597
+ witnessUtxo: input.witnessUtxo,
598
+ // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
599
+ nonWitnessUtxo: input.nonWitnessUtxo,
600
+ redeemScript: input.redeemScript,
601
+ sequence,
602
+ bip32Derivation: input.bip32Derivation,
603
+ }
604
+ if (!i.bip32Derivation) delete i.bip32Derivation
605
+ if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
606
+ newPsbt.addInput(i)
607
+ break
608
+ }
609
+ case "p2tr": {
610
+ let i = {
611
+ hash: input.hash,
612
+ index: Number(input.index),
613
+ witnessUtxo: input.witnessUtxo,
614
+ // we dont need nonWitnessUtxo. bud some application force nonWitnessUtxo
615
+ nonWitnessUtxo: input.nonWitnessUtxo,
616
+ tapInternalKey: input.tapInternalKey,
617
+ sequence,
618
+ bip32Derivation: input.bip32Derivation,
619
+ }
620
+ if (!i.bip32Derivation) delete i.bip32Derivation
621
+ if (!i.nonWitnessUtxo) delete i.nonWitnessUtxo
622
+ newPsbt.addInput(i)
623
+ break
624
+ }
625
+ default:
626
+ throw new Error("address type is incorrect")
627
+ }
628
+ }
629
+
630
+ // add outputs
631
+ for (const target of outputs) {
632
+ newPsbt.addOutput(target)
633
+ }
634
+
635
+ // add changeAddress
636
+ if (change && Object.keys(change).length !== 0) {
637
+ newPsbt.addOutput(change)
638
+ }
639
+
640
+ // check created outputs with targets
641
+ if (change && Object.keys(change).length !== 0) {
642
+ if (newPsbt.txOutputs[outputs.length].address !== change.address) {
643
+ throw new Error("error change address")
644
+ }
645
+ // if (newPsbt.txOutputs[outputs.length].value !== change.value) {
646
+ // throw new Error("error change value")
647
+ // }
648
+ }
649
+
650
+ const unsignedPsbtBaseText = newPsbt.toBase64()
651
+ return {
652
+ unsignedTransaction: unsignedPsbtBaseText,
653
+ outputs,
654
+ inputs: inputs.map((utx) => ({
655
+ hash: utx.hash,
656
+ value: Number(utx.value),
657
+ index: utx.index,
658
+ signerInfo: utx.signerInfo,
659
+ })),
660
+ fee,
661
+ change,
662
+ }
663
+ }
664
+
665
+ async processUnsignedTransaction({
666
+ extendedUtxo,
667
+ targets = [],
668
+ changeAddress = undefined,
669
+ fullAmount = false,
670
+ feeRate,
671
+ selectType = "normal",
672
+ }: {
673
+ extendedUtxo: ExtendedUtxo[]
674
+ targets: Target[]
675
+ feeRate: number
676
+
677
+ changeAddress?: string | SignerInfo
678
+ fullAmount?: boolean
679
+ selectType?: "normal" | "accumulative" | "full" | "inOrder"
680
+ }) {
681
+ if (!changeAddress && targets.length === 0) throw new Error("no target")
682
+ let changeObject =
683
+ typeof changeAddress === "string"
684
+ ? {
685
+ address: changeAddress,
686
+ }
687
+ : changeAddress
688
+ const { inputs, outputs, change, fee } = await this.filterAndConvertTxDataToStandardFormat({
689
+ extendedUtxo,
690
+ targets,
691
+ changeObject,
692
+ feeRate,
693
+ selectType: fullAmount ? "full" : selectType,
694
+ })
695
+ let unsignedTransaction = this.createUnsignedTransaction({
696
+ inputs,
697
+ outputs,
698
+ change,
699
+ fee,
700
+ feeRate,
701
+ })
702
+
703
+ return unsignedTransaction
704
+ }
705
+ }