@teleportdao/bitcoin 1.4.0 → 1.4.4

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 (43) hide show
  1. package/dist/bitcoin-base.d.ts +17 -16
  2. package/dist/bitcoin-base.d.ts.map +1 -1
  3. package/dist/bitcoin-base.js +12 -7
  4. package/dist/bitcoin-base.js.map +1 -1
  5. package/dist/bitcoin-utils.d.ts +1 -1
  6. package/dist/bitcoin-utils.d.ts.map +1 -1
  7. package/dist/bitcoin-utils.js +3 -0
  8. package/dist/bitcoin-utils.js.map +1 -1
  9. package/dist/bundle.js +4 -0
  10. package/dist/index.d.ts +5 -5
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +14 -12
  13. package/dist/index.js.map +1 -1
  14. package/dist/sign/sign-transaction.d.ts +9 -5
  15. package/dist/sign/sign-transaction.d.ts.map +1 -1
  16. package/dist/sign/sign-transaction.js +14 -11
  17. package/dist/sign/sign-transaction.js.map +1 -1
  18. package/dist/teleport-dao-payments.d.ts +17 -6
  19. package/dist/teleport-dao-payments.d.ts.map +1 -1
  20. package/dist/teleport-dao-payments.js +4 -3
  21. package/dist/teleport-dao-payments.js.map +1 -1
  22. package/dist/transaction-builder/bitcoin-transaction-builder.d.ts +26 -11
  23. package/dist/transaction-builder/bitcoin-transaction-builder.d.ts.map +1 -1
  24. package/dist/transaction-builder/bitcoin-transaction-builder.js +36 -8
  25. package/dist/transaction-builder/bitcoin-transaction-builder.js.map +1 -1
  26. package/dist/transaction-builder/transaction-builder.d.ts +148 -9
  27. package/dist/transaction-builder/transaction-builder.d.ts.map +1 -1
  28. package/dist/transaction-builder/transaction-builder.js +230 -30
  29. package/dist/transaction-builder/transaction-builder.js.map +1 -1
  30. package/package.json +7 -5
  31. package/src/bitcoin-base.js +220 -219
  32. package/src/bitcoin-utils.js +487 -483
  33. package/src/helper/teleport-request-helper.js +179 -179
  34. package/src/index.ts +8 -0
  35. package/src/sign/sign-transaction.ts +96 -0
  36. package/src/teleport-dao-payments.js +280 -280
  37. package/src/transaction-builder/bitcoin-transaction-builder.ts +57 -0
  38. package/src/transaction-builder/transaction-builder.ts +490 -0
  39. package/src/index.js +0 -15
  40. package/src/sign/sign-transaction.js +0 -36
  41. package/src/transaction-builder/bitcoin-transaction-builder.js +0 -37
  42. package/src/transaction-builder/transaction-builder-common.js +0 -236
  43. package/src/transaction-builder/transaction-builder.js +0 -159
@@ -0,0 +1,490 @@
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
+ const TX_EMPTY_SIZE = 4 + 1 + 1 + 4
11
+ const TX_INPUT_BASE = 32 + 4 + 1 + 4
12
+ const TX_INPUT_P2PKH = 107
13
+ const TX_INPUT_P2SH_P2PKH = 50
14
+ const TX_INPUT_P2WPKH = 47
15
+ const TX_OUTPUT_BASE = 8 + 1
16
+ const TX_OUTPUT_P2PKH = 25
17
+
18
+ const componentBytes = {
19
+ bytePerInput: {
20
+ p2pkh: TX_INPUT_BASE + TX_INPUT_P2PKH,
21
+ p2wpkh: TX_INPUT_BASE + TX_INPUT_P2WPKH,
22
+ p2shp2wpkh: TX_INPUT_BASE + TX_INPUT_P2SH_P2PKH,
23
+ },
24
+ baseTxBytes: TX_EMPTY_SIZE,
25
+ bytePerOutput: TX_OUTPUT_BASE + TX_OUTPUT_P2PKH,
26
+ }
27
+
28
+ export type Utxo = {
29
+ hash: string
30
+ value: number
31
+ index: number
32
+ }
33
+ export type SignerInfo = {
34
+ address: string
35
+ publicKey: string
36
+ addressType: string
37
+ derivationPath?: string
38
+ masterFingerprint?: string
39
+ }
40
+ export type ExtendedUtxo = {
41
+ signerInfo: SignerInfo
42
+ hash: string
43
+ value: number
44
+ index: number
45
+ }
46
+
47
+ export type TargetAddress = {
48
+ address: string
49
+ value: number
50
+ }
51
+
52
+ export type TargetScript = {
53
+ script: Buffer
54
+ value: number
55
+ }
56
+ export type Target = TargetAddress | TargetScript
57
+
58
+ export type BitcoinJSInputInfo = ExtendedUtxo & {
59
+ bip32Derivation?: {
60
+ path: string
61
+ pubkey: Buffer
62
+ masterFingerprint: Buffer
63
+ }[]
64
+ nonWitnessUtxo?: Buffer
65
+ witnessUtxo?: {
66
+ script: Buffer
67
+ value: number
68
+ }
69
+ redeemScript?: Buffer
70
+ tapInternalKey?: Buffer
71
+ }
72
+
73
+ export type ExtendedUnsignedTransaction = {
74
+ unsignedTransaction: string
75
+ outputs: Target[]
76
+ inputs: {
77
+ hash: string
78
+ value: number
79
+ index: number
80
+ signerInfo: SignerInfo
81
+ }[]
82
+ fee: number
83
+ change: TargetAddress | undefined
84
+ }
85
+
86
+ class BaseBitcoinLikeTransaction {
87
+ testnet: boolean
88
+ network: bitcoin.Network
89
+ maximumNumberOfOutputsInTransaction: number
90
+ feeMin: number
91
+ dustLimit: number
92
+ // abstract
93
+ constructor({
94
+ network,
95
+ testnet,
96
+ feeMin = 0,
97
+ dustLimit,
98
+ maximumNumberOfOutputsInTransaction = 50,
99
+ }: {
100
+ network: bitcoin.Network
101
+ testnet: boolean
102
+ feeMin?: number
103
+ dustLimit?: number
104
+ maximumNumberOfOutputsInTransaction?: number
105
+ }) {
106
+ this.testnet = testnet
107
+ this.network = network
108
+ this.maximumNumberOfOutputsInTransaction = maximumNumberOfOutputsInTransaction
109
+ this.feeMin = feeMin
110
+ this.dustLimit = dustLimit || 1 * 2 * componentBytes.bytePerInput.p2pkh
111
+ }
112
+
113
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
114
+ async _getUtxo(userAddress: string): Promise<Utxo[]> {
115
+ // The child has implemented this method.
116
+ throw new Error("Do not call abstract method directly")
117
+ // return utxo
118
+ }
119
+
120
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
121
+ async _getTransactionHex(transactionId: string): Promise<string> {
122
+ // The child has implemented this method.
123
+ throw new Error("Do not call abstract method directly")
124
+ // return utxo
125
+ }
126
+
127
+ // eslint-disable-next-line no-unused-vars, class-methods-use-this
128
+ createAddressObject(input: { addressType: string; publicKey: Buffer }) {
129
+ return createAddressObjectByPublicKey(input, this.network)
130
+ }
131
+
132
+ // methods
133
+ validateAddress(address: string) {
134
+ try {
135
+ getAddressType(address)
136
+ return true
137
+ } catch (error) {
138
+ return false
139
+ }
140
+ }
141
+
142
+ getOpReturnTarget(dataHex: string) {
143
+ if (!(dataHex.length > 0)) throw new Error("invalid data in hex")
144
+ const embed = bitcoin.payments.embed({
145
+ data: [Buffer.from(dataHex, "hex")],
146
+ network: this.network,
147
+ })
148
+ return {
149
+ script: embed.output,
150
+ value: 0,
151
+ }
152
+ }
153
+
154
+ async getExtendedUtxo(signerInfo: SignerInfo) {
155
+ let utxo = await this._getUtxo(signerInfo.address)
156
+ const extendedUtxo = utxo.map((input) => ({
157
+ ...input,
158
+ signerInfo,
159
+ }))
160
+ if (!extendedUtxo || extendedUtxo.length === 0) {
161
+ throw new Error("no utxo found")
162
+ }
163
+ return extendedUtxo
164
+ }
165
+
166
+ static helperHandleInputsAndOutputs({
167
+ targets,
168
+ extendedUtxo,
169
+ feeRate,
170
+ changeAddress,
171
+ selectType = "normal", // "accumulative" | "normal" | "full"
172
+ }: {
173
+ extendedUtxo: ExtendedUtxo[]
174
+ targets: Target[]
175
+ feeRate: number
176
+ changeAddress?: string
177
+ selectType?: "normal" | "accumulative" | "full"
178
+ }) {
179
+ let selectResponse
180
+ switch (selectType) {
181
+ case "normal":
182
+ selectResponse = coinselect(extendedUtxo, targets, Math.round(feeRate))
183
+
184
+ break
185
+ case "accumulative":
186
+ selectResponse = coinselectAccumulative(extendedUtxo, targets, Math.round(feeRate))
187
+ break
188
+ case "full":
189
+ if (!(targets[0] as TargetAddress).address) {
190
+ throw new Error()
191
+ }
192
+ selectResponse = coinselectSplit(
193
+ extendedUtxo,
194
+ [{ address: (targets[0] as TargetAddress).address }],
195
+ Math.round(feeRate),
196
+ )
197
+ break
198
+
199
+ default:
200
+ break
201
+ }
202
+ let {
203
+ inputs,
204
+ outputs,
205
+ fee,
206
+ }: {
207
+ inputs?: ExtendedUtxo[]
208
+ outputs?: {
209
+ script?: Buffer
210
+ address?: Buffer
211
+ value: number
212
+ }[]
213
+ fee: number
214
+ } = selectResponse
215
+
216
+ if (!inputs || !outputs) {
217
+ throw new Error("not enough balance")
218
+ }
219
+ let changeIndex = outputs.findIndex((x) => !x?.address && !x.script && (x.value || 0) > 0)
220
+ let change: Target | undefined
221
+ if (changeIndex >= 0) {
222
+ if (!changeAddress) throw new Error("change address not exist")
223
+ change = {
224
+ address: changeAddress,
225
+ value: outputs[changeIndex].value,
226
+ }
227
+ outputs.splice(changeIndex, 1)
228
+ }
229
+
230
+ return {
231
+ inputs,
232
+ fee,
233
+ outputs: outputs as Target[],
234
+ change: change as TargetAddress,
235
+ }
236
+ }
237
+
238
+ async filterAndConvertTxDataToStandardFormat({
239
+ extendedUtxo,
240
+ targets,
241
+ changeAddress,
242
+ feeRate,
243
+ selectType,
244
+ }: {
245
+ extendedUtxo: ExtendedUtxo[]
246
+ targets: Target[]
247
+ feeRate: number
248
+ changeAddress?: string
249
+ selectType?: "normal" | "accumulative" | "full"
250
+ }) {
251
+ let {
252
+ inputs: filteredInputs,
253
+ outputs,
254
+ change,
255
+ fee,
256
+ } = BaseBitcoinLikeTransaction.helperHandleInputsAndOutputs({
257
+ targets,
258
+ extendedUtxo,
259
+ feeRate,
260
+ changeAddress,
261
+ selectType,
262
+ })
263
+
264
+ let inputs = await this.convertExtendedUtxoToInputs(filteredInputs)
265
+
266
+ return {
267
+ inputs,
268
+ outputs,
269
+ change,
270
+ fee,
271
+ feeRate,
272
+ }
273
+ }
274
+
275
+ // ?note : we can extend this class and change this method for network other than bitcoin
276
+ async convertExtendedUtxoToInputs(baseInputs: ExtendedUtxo[] = []) {
277
+ let inputs: BitcoinJSInputInfo[] = baseInputs
278
+ let transactionId: string | null = null
279
+ let transactionHex: string | null = null
280
+ for (let i in inputs) {
281
+ let { address, publicKey, derivationPath, masterFingerprint, addressType } =
282
+ inputs[i].signerInfo
283
+ // todo : support without publicKey
284
+ let addressObject = this.createAddressObject({
285
+ publicKey: Buffer.from(publicKey, "hex"),
286
+ addressType,
287
+ })
288
+ if (derivationPath && masterFingerprint && addressObject.pubkey) {
289
+ inputs[i].bip32Derivation = [
290
+ {
291
+ path: derivationPath,
292
+ pubkey: addressObject.pubkey,
293
+ masterFingerprint: Buffer.from(masterFingerprint, "hex"),
294
+ },
295
+ ]
296
+ }
297
+ if (addressType === "p2pkh") {
298
+ // add p2pkh data
299
+ if (transactionHex && transactionId === inputs[i].hash) {
300
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
301
+ } else {
302
+ transactionHex = await this._getTransactionHex(inputs[i].hash)
303
+ transactionId = inputs[i].hash
304
+ inputs[i].nonWitnessUtxo = Buffer.from(transactionHex, "hex")
305
+ }
306
+ } else if (addressType === "p2wpkh") {
307
+ // add p2wpkh data
308
+ if (!addressObject.output) throw new Error("invalid signer info")
309
+ inputs[i].witnessUtxo = {
310
+ script: addressObject.output,
311
+ value: inputs[i].value,
312
+ }
313
+ } else if (addressType === "p2sh-p2wpkh") {
314
+ // add p2sh-p2wpkh data
315
+ if (!addressObject.output) throw new Error("invalid signer info")
316
+ inputs[i].witnessUtxo = {
317
+ script: addressObject.output,
318
+ value: inputs[i].value,
319
+ }
320
+ if (!addressObject?.redeem?.output) throw new Error("invalid signer info for p2sh address")
321
+ inputs[i].redeemScript = addressObject.redeem.output
322
+ } else if (addressType === "p2tr") {
323
+ if (!addressObject.output) throw new Error("invalid signer info")
324
+ inputs[i].witnessUtxo = {
325
+ script: addressObject.output,
326
+ value: inputs[i].value,
327
+ }
328
+ if (!addressObject.pubkey) throw new Error("invalid signer info for p2tr address (pubkey)")
329
+ // todo: fix after update to p2tr
330
+ inputs[i].tapInternalKey = Buffer.from(publicKey, "hex")
331
+ }
332
+ }
333
+
334
+ return inputs
335
+ }
336
+
337
+ // ?note : we can extend this class and change this method for network other than bitcoin
338
+ createUnsignedTransaction({
339
+ inputs,
340
+ outputs,
341
+ change,
342
+ fee, // not used in this section - just returned
343
+ feeRate,
344
+ }: {
345
+ inputs: BitcoinJSInputInfo[]
346
+ outputs: Target[]
347
+ change?: TargetAddress
348
+ fee: number
349
+ feeRate: number
350
+ }) {
351
+ const { network } = this
352
+ const newPsbt = new bitcoin.Psbt({ network })
353
+ newPsbt.setMaximumFeeRate(+(feeRate + feeRate / 100).toFixed())
354
+ // add input
355
+ for (const input of inputs) {
356
+ let { addressType } = input.signerInfo
357
+ switch (addressType) {
358
+ case "p2pkh": {
359
+ let i = {
360
+ hash: input.hash,
361
+ index: Number(input.index),
362
+ nonWitnessUtxo: input.nonWitnessUtxo,
363
+ sequence: 0xffffffff - 1,
364
+ bip32Derivation: input.bip32Derivation,
365
+ }
366
+ if (!i.bip32Derivation) delete i.bip32Derivation
367
+ newPsbt.addInput(i)
368
+ break
369
+ }
370
+ case "p2wpkh": {
371
+ let i = {
372
+ hash: input.hash,
373
+ index: Number(input.index),
374
+ witnessUtxo: input.witnessUtxo,
375
+ sequence: 0xffffffff - 1,
376
+ bip32Derivation: input.bip32Derivation,
377
+ }
378
+ if (!i.bip32Derivation) delete i.bip32Derivation
379
+ newPsbt.addInput(i)
380
+ break
381
+ }
382
+ case "p2sh-p2wpkh": {
383
+ let i = {
384
+ hash: input.hash,
385
+ index: Number(input.index),
386
+ witnessUtxo: input.witnessUtxo,
387
+ redeemScript: input.redeemScript,
388
+ sequence: 0xffffffff - 1,
389
+ bip32Derivation: input.bip32Derivation,
390
+ }
391
+ if (!i.bip32Derivation) delete i.bip32Derivation
392
+ newPsbt.addInput(i)
393
+ break
394
+ }
395
+ case "p2tr": {
396
+ let i = {
397
+ hash: input.hash,
398
+ index: Number(input.index),
399
+ witnessUtxo: input.witnessUtxo,
400
+ tapInternalKey: input.tapInternalKey,
401
+ sequence: 0xffffffff - 1,
402
+ bip32Derivation: input.bip32Derivation,
403
+ }
404
+ if (!i.bip32Derivation) delete i.bip32Derivation
405
+ newPsbt.addInput(i)
406
+ break
407
+ }
408
+ default:
409
+ throw new Error("address type is incorrect")
410
+ }
411
+ }
412
+
413
+ // add outputs
414
+ for (const target of outputs) {
415
+ newPsbt.addOutput(target)
416
+ }
417
+
418
+ // add changeAddress
419
+ if (change && Object.keys(change).length !== 0) {
420
+ newPsbt.addOutput({
421
+ address: change.address,
422
+ value: Number(change.value),
423
+ })
424
+ }
425
+
426
+ // check created outputs with targets
427
+ if (change && Object.keys(change).length !== 0) {
428
+ if (newPsbt.txOutputs[outputs.length].address !== change.address) {
429
+ throw new Error("error change address")
430
+ }
431
+ if (newPsbt.txOutputs[outputs.length].value !== change.value) {
432
+ throw new Error("error change value")
433
+ }
434
+ }
435
+
436
+ const unsignedPsbtBaseText = newPsbt.toBase64()
437
+ return {
438
+ unsignedTransaction: unsignedPsbtBaseText,
439
+ outputs,
440
+ inputs: inputs.map((utx) => ({
441
+ hash: utx.hash,
442
+ value: Number(utx.value),
443
+ index: utx.index,
444
+ signerInfo: utx.signerInfo,
445
+ })),
446
+ fee,
447
+ change,
448
+ }
449
+ }
450
+
451
+ async processUnsignedTransaction({
452
+ extendedUtxo,
453
+ targets = [],
454
+ changeAddress = undefined,
455
+ fullAmount = false,
456
+ feeRate,
457
+ selfTransaction = false,
458
+ selectType = "normal",
459
+ }: {
460
+ extendedUtxo: ExtendedUtxo[]
461
+ targets: Target[]
462
+ feeRate: number
463
+
464
+ changeAddress?: string
465
+ fullAmount?: boolean
466
+ selfTransaction?: boolean
467
+ selectType?: "normal" | "accumulative" | "full"
468
+ }) {
469
+ if (!selfTransaction && targets.length === 0) throw new Error("no target")
470
+
471
+ const { inputs, outputs, change, fee } = await this.filterAndConvertTxDataToStandardFormat({
472
+ extendedUtxo,
473
+ targets,
474
+ changeAddress,
475
+ feeRate,
476
+ selectType: fullAmount ? "full" : selectType,
477
+ })
478
+ let unsignedTransaction = this.createUnsignedTransaction({
479
+ inputs,
480
+ outputs,
481
+ change,
482
+ fee,
483
+ feeRate,
484
+ })
485
+
486
+ return unsignedTransaction
487
+ }
488
+ }
489
+
490
+ export default BaseBitcoinLikeTransaction
package/src/index.js DELETED
@@ -1,15 +0,0 @@
1
- // eslint-disable-next-line global-require
2
- global.Buffer = global.Buffer || require("buffer").Buffer
3
- const TeleportDaoPayment = require("./teleport-dao-payments")
4
- const bitcoinUtils = require("./bitcoin-utils")
5
- const BitcoinInterface = require("./bitcoin-interface")
6
- const BitcoinInterfaceUtils = require("./bitcoin-interface-utils")
7
- const BitcoinBase = require("./bitcoin-base")
8
-
9
- module.exports = {
10
- TeleportDaoPayment,
11
- BitcoinInterface,
12
- BitcoinInterfaceUtils,
13
- bitcoinUtils,
14
- BitcoinBase,
15
- }
@@ -1,36 +0,0 @@
1
- const bitcoin = require("bitcoinjs-lib")
2
-
3
- class BitcoinLikeSignTransaction {
4
- constructor(network) {
5
- this.network = network
6
- }
7
-
8
- async signPsbt(extendedUnsignedTransaction, privateKey) {
9
- const { network } = this
10
- const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, {
11
- network,
12
- compressed: true,
13
- })
14
- const psbt = bitcoin.Psbt.fromBase64(extendedUnsignedTransaction.unsignedTransaction, {
15
- network,
16
- })
17
- psbt.signAllInputs(keyPair)
18
-
19
- const partialSigendPsbt = psbt.toBase64()
20
- return partialSigendPsbt
21
- }
22
-
23
- finalizePsbts(psbtsBase64 = []) {
24
- const finals = psbtsBase64.map((psbtBase64) =>
25
- bitcoin.Psbt.fromBase64(psbtBase64, { network: this.network }),
26
- )
27
- const psbt =
28
- finals.length === 1
29
- ? finals[0]
30
- : new bitcoin.Psbt({ network: this.network }).combine(...finals)
31
- psbt.finalizeAllInputs()
32
- return psbt.extractTransaction().toHex()
33
- }
34
- }
35
-
36
- module.exports = BitcoinLikeSignTransaction
@@ -1,37 +0,0 @@
1
- const BaseTransactionBuilder = require("./transaction-builder")
2
- const BitcoinInterface = require("../bitcoin-interface")
3
-
4
- class BitcoinTransactionBuilder extends BaseTransactionBuilder {
5
- constructor(connectionInfo, networkName, network) {
6
- super({
7
- network,
8
- testnet: networkName?.includes("_testnet"),
9
- dustLimit: 1000,
10
- })
11
- this.btcInterface = new BitcoinInterface(connectionInfo, networkName)
12
- }
13
-
14
- async _getUtxo(userAddress) {
15
- let utxos = await this.btcInterface.getAddressesUtxo([userAddress])
16
- return utxos.map((tx) => ({
17
- hash: tx.txId,
18
- value: tx.value,
19
- index: tx.index,
20
- }))
21
- }
22
-
23
- async _getFeeRate(speed) {
24
- return this.btcInterface.getFeeRate(speed)
25
- }
26
-
27
- async _getTransactionHex(transactionId) {
28
- return this.btcInterface.provider.getRawTransaction(transactionId)
29
- }
30
-
31
- async sendTx(txHex) {
32
- let txId = await this.btcInterface.provider.sendRawTransaction(txHex)
33
- return txId
34
- }
35
- }
36
-
37
- module.exports = BitcoinTransactionBuilder