@teleportdao/bitcoin 1.8.9 → 1.9.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.
@@ -1,748 +1,748 @@
1
- import BigNumber from "bignumber.js"
2
- import { bitcoin as bitcoinProviders } from "@teleportdao/providers"
3
- import { OrdinalTransactionBuilder } from "./transaction-builder/ordinal-transaction-builder"
4
- import BitcoinSign from "./sign/sign-transaction"
5
- //
6
- import { BitcoinInterfaceOrdinal } from "./bitcoin-interface-ordinal"
7
- import { generateBrc2OpReturn } from "./helper/brc20-helper"
8
- import { runWithRetries, sleep } from "./utils/tools"
9
- import { BitcoinConnectionInfo } from "./type"
10
- import { ChangeTarget, ExtendedUtxo, SignerInfo, Target } from "./transaction-builder"
11
- import { BitcoinBaseWallet } from "./bitcoin-wallet-base"
12
-
13
- class OrdinalWallet extends BitcoinBaseWallet {
14
- unisat: bitcoinProviders.UniSat
15
- transactionBuilder: OrdinalTransactionBuilder
16
- btcInterface: BitcoinInterfaceOrdinal
17
- signer: BitcoinSign
18
- constructor(
19
- networkName: string,
20
- uniSatToken: string,
21
- connectionInfo: BitcoinConnectionInfo = {
22
- api: {
23
- provider: "MempoolSpace",
24
- },
25
- },
26
- ) {
27
- super(networkName, connectionInfo)
28
- if (!connectionInfo.rpc?.enabled) {
29
- throw new Error("rpc is required")
30
- }
31
- this.transactionBuilder = new OrdinalTransactionBuilder(connectionInfo, networkName)
32
- this.signer = new BitcoinSign(this.network)
33
- this.btcInterface = new BitcoinInterfaceOrdinal(connectionInfo, networkName, uniSatToken)
34
- this.unisat = this.btcInterface.unisat
35
- }
36
-
37
- async sendSignedPsbtWithRetry(signedPsbt: string, { maxTries = 5, retrySleep = 5000 } = {}) {
38
- return runWithRetries(() => this.sendSignedPsbt(signedPsbt), {
39
- retrySleep,
40
- maxTries,
41
- })
42
- }
43
-
44
- static deployBRC20Data(tickName: string, max: number | string, limit: number | string) {
45
- let data = {
46
- p: "brc-20",
47
- op: "deploy",
48
- tick: tickName,
49
- max: `${max}`,
50
- lim: `${limit}`,
51
- }
52
- return {
53
- buffer: Buffer.from(JSON.stringify(data), "utf8"),
54
- type: "text/plain",
55
- // type: "application/json",
56
- }
57
- }
58
-
59
- static mintBRC20Data(tickName: string, amount: string | number) {
60
- if (BigNumber(amount).isLessThanOrEqualTo(0)) throw new Error("amount should be greater than 0")
61
- let data = {
62
- p: "brc-20",
63
- op: "mint",
64
- tick: tickName,
65
- amt: `${amount}`,
66
- }
67
- return {
68
- buffer: Buffer.from(JSON.stringify(data), "utf8"),
69
- type: "text/plain",
70
- // type: "application/json",
71
- }
72
- }
73
-
74
- static transferBRC20Data(tickName: string, amount: string | number) {
75
- if (BigNumber(amount).isLessThanOrEqualTo(0)) throw new Error("amount should be greater than 0")
76
- let data = {
77
- p: "brc-20",
78
- op: "transfer",
79
- tick: tickName,
80
- amt: `${amount}`,
81
- }
82
- return {
83
- buffer: Buffer.from(JSON.stringify(data), "utf8"),
84
- type: "text/plain",
85
- // type: "application/json",
86
- }
87
- }
88
-
89
- async sendUsingUtxosUnsigned({
90
- receivers,
91
- speed = "normal",
92
- utxo,
93
- changeAddress,
94
- staticFeeRate,
95
- }: {
96
- receivers: {
97
- address: string
98
- value: number
99
- }[]
100
- changeAddress: string
101
- speed?: "normal" | "fast" | "slow"
102
- staticFeeRate?: number
103
- utxo: ExtendedUtxo[]
104
- }) {
105
- let extendedUtxo: ExtendedUtxo[] = utxo
106
-
107
- receivers.forEach(({ value }) => {
108
- if (value - +value.toFixed(0) !== 0)
109
- throw new Error("incorrect amount. amount should be in satoshi")
110
- })
111
-
112
- // eslint-disable-next-line no-underscore-dangle
113
- let feeRate = staticFeeRate || (await this.transactionBuilder._getFeeRate(speed))
114
- let unsignedTx = await this.transactionBuilder.processUnsignedTransaction({
115
- extendedUtxo,
116
- targets: receivers,
117
- changeAddress,
118
- feeRate,
119
- fullAmount: false,
120
- })
121
-
122
- return {
123
- ...unsignedTx,
124
- possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(unsignedTx.unsignedTransaction),
125
- }
126
- }
127
-
128
- async sendUsingUtxos({
129
- receivers,
130
- speed = "normal",
131
- utxo,
132
- }: {
133
- receivers: {
134
- address: string
135
- value: number
136
- }[]
137
- speed?: "normal" | "fast" | "slow"
138
- utxo?: ExtendedUtxo[]
139
- }) {
140
- if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
141
- throw new Error("account not initialized")
142
- }
143
-
144
- let extendedUtxo: ExtendedUtxo[] = utxo
145
- ? utxo.filter((u) => u.signerInfo.address === this.currentAccount!)
146
- : await this.getExtendedUtxo({
147
- address: this.currentAccount,
148
- addressType: this.currentAccountType,
149
- publicKey: this.publicKey.toString("hex"),
150
- })
151
-
152
- let unsignedTx = await this.sendUsingUtxosUnsigned({
153
- receivers,
154
- speed,
155
- utxo: extendedUtxo,
156
- changeAddress: this.bitcoinAddress!,
157
- })
158
- let signedPsbt = await this.signer.signPsbt(unsignedTx, this.privateKey)
159
- let signedTx = this.signer.finalizePsbts([signedPsbt])
160
- let txId = await this.transactionBuilder.sendTx(signedTx)
161
- const { inputs, outputs, change, fee } = unsignedTx
162
- return { txId, inputs, outputs, change, fee }
163
- }
164
-
165
- async inscribeOrdinalDepositUnsigned(
166
- file: {
167
- buffer: Buffer
168
- type: string
169
- },
170
- signer: SignerInfo,
171
- ordinalSigner?: SignerInfo,
172
- extendedUtxo?: ExtendedUtxo[],
173
- staticFeeRate?: number,
174
- ) {
175
- const ordinalSignerPublicKey = ordinalSigner?.publicKey || signer.publicKey
176
- const publicKey = Buffer.from(ordinalSignerPublicKey, "hex")
177
- let transferOrdinal = this.transactionBuilder.createOrdinalAddress(file, publicKey)
178
- const leafScript = transferOrdinal.redeem.output
179
- const { ordinalAddress } = transferOrdinal
180
-
181
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
182
- let fee = +(((400 + leafScript.length) / 4) * feeRate * 1.5).toFixed(0)
183
- let ordinalAmount = 600
184
-
185
- let utxo1: ExtendedUtxo[] =
186
- extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
187
-
188
- let inscribeDepositUnsignedInfo = await this.sendUsingUtxosUnsigned({
189
- receivers: [
190
- {
191
- address: ordinalAddress,
192
- value: ordinalAmount + fee,
193
- },
194
- ],
195
- utxo: utxo1,
196
- changeAddress: signer.address,
197
- })
198
-
199
- return {
200
- inscribeDepositUnsignedInfo,
201
- transferOrdinal,
202
- }
203
- }
204
-
205
- async inscribeOrdinalUnsigned(
206
- file: {
207
- buffer: Buffer
208
- type: string
209
- },
210
- signer: SignerInfo,
211
- ordinalSigner?: SignerInfo,
212
- extendedUtxo?: ExtendedUtxo[],
213
- staticFeeRate?: number,
214
- ) {
215
- const receiverAddress = ordinalSigner?.address || signer.address
216
- const { inscribeDepositUnsignedInfo, transferOrdinal } =
217
- await this.inscribeOrdinalDepositUnsigned(
218
- file,
219
- signer,
220
- ordinalSigner,
221
- extendedUtxo,
222
- staticFeeRate,
223
- )
224
- const { ordinalAddress } = transferOrdinal
225
- let ordinalAmount = 600
226
- let inscribeDeposit: {
227
- hash: string
228
- value: number
229
- index: number
230
- } = {
231
- hash: inscribeDepositUnsignedInfo.possibleTxId,
232
- value: inscribeDepositUnsignedInfo.outputs[0].value,
233
- index: 0,
234
- }
235
- let inscribeUnsignedInfo = this.transactionBuilder.createInscribeUnsignedTx(
236
- transferOrdinal,
237
- inscribeDeposit,
238
- receiverAddress,
239
- ordinalAmount,
240
- )
241
- return {
242
- inscribeDepositUnsignedInfo: {
243
- ...inscribeDepositUnsignedInfo,
244
- possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(
245
- inscribeDepositUnsignedInfo.unsignedTransaction,
246
- ),
247
- },
248
- inscribeUnsignedInfo: {
249
- ...inscribeUnsignedInfo,
250
- possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(
251
- inscribeUnsignedInfo.unsignedTransaction,
252
- ),
253
- },
254
- inscribeAddress: ordinalAddress,
255
- receiverAddress,
256
- }
257
- }
258
-
259
- async inscribeOrdinal(
260
- file: {
261
- buffer: Buffer
262
- type: string
263
- },
264
- extendedUtxo?: ExtendedUtxo[],
265
- staticFeeRate?: number,
266
- ordinalReceiverAddress?: string,
267
- ) {
268
- if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
269
- throw new Error("account not initialized")
270
- }
271
- const receiverAddress = ordinalReceiverAddress || this.bitcoinAddress!
272
-
273
- const { inscribeDepositUnsignedInfo, transferOrdinal } =
274
- await this.inscribeOrdinalDepositUnsigned(
275
- file,
276
- this.signerInfo!,
277
- undefined,
278
- extendedUtxo,
279
- staticFeeRate,
280
- )
281
- let ordinalUtxo = await this.btcInterface.getBTCUtxo(
282
- transferOrdinal.ordinalAddress,
283
- this.signerInfo!,
284
- )
285
-
286
- let inscribeDeposit: {
287
- inputs: {
288
- hash: string
289
- value: number
290
- index: number
291
- signerInfo: SignerInfo
292
- }[]
293
- hash: string
294
- value: number
295
- index: number
296
- change?: ChangeTarget
297
- changeIndex?: number
298
- }
299
-
300
- if (ordinalUtxo.length > 1) {
301
- throw new Error("multiple deposit found for this ordinal address")
302
- }
303
-
304
- if (
305
- ordinalUtxo.length === 0 ||
306
- ordinalUtxo[0].value <
307
- inscribeDepositUnsignedInfo.outputs[0].value -
308
- inscribeDepositUnsignedInfo.outputs[0].value * 0.15
309
- ) {
310
- const signedPsbt1 = await this.signer.signPsbt(inscribeDepositUnsignedInfo, this.privateKey!)
311
- console.log("inscribe deposit tx ...")
312
- const inscribeDepositTxId = await this.sendSignedPsbt(signedPsbt1)
313
- console.log(`inscribe deposit txId : ${inscribeDepositTxId}`)
314
-
315
- inscribeDeposit = {
316
- hash: inscribeDepositTxId,
317
- value: inscribeDepositUnsignedInfo.outputs[0].value,
318
- index: 0,
319
- inputs: inscribeDepositUnsignedInfo.inputs,
320
- change: inscribeDepositUnsignedInfo.change,
321
- changeIndex: inscribeDepositUnsignedInfo.change
322
- ? inscribeDepositUnsignedInfo.outputs.length
323
- : undefined,
324
- }
325
- } else {
326
- inscribeDeposit = {
327
- hash: ordinalUtxo[0].hash,
328
- value: ordinalUtxo[0].value,
329
- index: ordinalUtxo[0].index,
330
- inputs: [],
331
- }
332
- console.log("no need to deposit", inscribeDeposit)
333
- }
334
-
335
- const ordinalAmount = 600
336
- let inscribeUnsignedInfo = this.transactionBuilder.createInscribeUnsignedTx(
337
- transferOrdinal,
338
- inscribeDeposit,
339
- receiverAddress,
340
- ordinalAmount,
341
- )
342
-
343
- const signedPsbt2 = await this.signer.signPsbt(
344
- inscribeUnsignedInfo,
345
- this.privateKey!,
346
- undefined,
347
- false,
348
- )
349
-
350
- console.log("inscribeTxId ...")
351
- await sleep(10 * 1000)
352
- let inscribeTxId = await this.sendSignedPsbtWithRetry(signedPsbt2)
353
- console.log("inscribeTxId", inscribeTxId)
354
- return {
355
- inscribeTx: { hash: inscribeTxId, index: 0, value: ordinalAmount },
356
- inscribeDepositTx: inscribeDeposit,
357
- inscribeAddress: transferOrdinal.ordinalAddress,
358
- }
359
- }
360
-
361
- async deployBrc20(
362
- brc: { tick: string; max: number; limit: number },
363
- extendedUtxo?: ExtendedUtxo[],
364
- staticFeeRate?: number,
365
- ) {
366
- let file = OrdinalWallet.deployBRC20Data(brc.tick, brc.max, brc.limit)
367
- return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
368
- }
369
-
370
- async mintBrc20(
371
- brc: { tick: string; amount: number | string },
372
- extendedUtxo?: ExtendedUtxo[],
373
- staticFeeRate?: number,
374
- ) {
375
- let file = OrdinalWallet.mintBRC20Data(brc.tick, brc.amount)
376
- return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
377
- }
378
-
379
- async inscribeBrc20Unsigned(
380
- brc: { tick: string; amount: number | string },
381
- signer: SignerInfo,
382
- ordinalSigner?: SignerInfo,
383
- extendedUtxo?: ExtendedUtxo[],
384
- staticFeeRate?: number,
385
- ) {
386
- let file = OrdinalWallet.transferBRC20Data(brc.tick, brc.amount)
387
- return this.inscribeOrdinalUnsigned(file, signer, ordinalSigner, extendedUtxo, staticFeeRate)
388
- }
389
-
390
- async inscribeBrc20(
391
- brc: { tick: string; amount: number | string },
392
- extendedUtxo?: ExtendedUtxo[],
393
- staticFeeRate?: number,
394
- ) {
395
- let file = OrdinalWallet.transferBRC20Data(brc.tick, brc.amount)
396
- return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
397
- }
398
-
399
- async transferBrc20Unsigned(
400
- receiver: string,
401
- brcInscribeUtxo: {
402
- hash: string
403
- value: number
404
- index: number
405
- },
406
- signer: SignerInfo,
407
- ordinalSigner?: SignerInfo,
408
- extendedUtxo?: ExtendedUtxo[],
409
- staticFeeRate?: number,
410
- otherTargets?: Target[],
411
- ) {
412
- const ordinalSignerInfo = ordinalSigner || signer
413
- let utxo = extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
414
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
415
- let unsignedTx = await this.transactionBuilder.createNftPsbt({
416
- extendedUtxo: utxo,
417
- nftExtendedUtxo: {
418
- hash: brcInscribeUtxo.hash,
419
- index: 0,
420
- value: brcInscribeUtxo.value,
421
- signerInfo: ordinalSignerInfo,
422
- },
423
- feeRate,
424
- receiverAddress: receiver,
425
- changeAddress: signer.address,
426
- otherTargets,
427
- })
428
- return {
429
- ...unsignedTx,
430
- possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(unsignedTx.unsignedTransaction),
431
- }
432
- }
433
-
434
- async transferBrc20(
435
- receiver: string,
436
- brcInscribeUtxo: {
437
- hash: string
438
- value: number
439
- index: number
440
- },
441
- extendedUtxo: ExtendedUtxo[],
442
- staticFeeRate?: number,
443
- otherTargets?: Target[],
444
- ) {
445
- if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
446
- throw new Error("account not initialized")
447
- }
448
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
449
- let unsignedTx = await this.transactionBuilder.createNftPsbt({
450
- extendedUtxo,
451
- nftExtendedUtxo: {
452
- hash: brcInscribeUtxo.hash,
453
- index: 0,
454
- value: brcInscribeUtxo.value,
455
- signerInfo: this.signerInfo!,
456
- },
457
- feeRate,
458
- receiverAddress: receiver,
459
- changeAddress: this.bitcoinAddress!,
460
- otherTargets,
461
- })
462
-
463
- let signedTx = await this.signer.signPsbt(unsignedTx, this.privateKey!)
464
- let finalTransferTxId = await this.sendSignedPsbtWithRetry(signedTx)
465
- return {
466
- hash: finalTransferTxId,
467
- index: 0,
468
- value: brcInscribeUtxo.value,
469
- inputs: unsignedTx.inputs,
470
- change: unsignedTx.change,
471
- changeIndex: unsignedTx.change ? unsignedTx.outputs.length : undefined,
472
- }
473
- }
474
-
475
- async inscribeAndTransferBrc20Unsigned(
476
- receiver: string,
477
- brc: { tick: string; amount: number | string },
478
- signer: SignerInfo,
479
- ordinalSigner?: SignerInfo,
480
- otherTargets?: Target[],
481
- extendedUtxo?: ExtendedUtxo[],
482
- staticFeeRate?: number,
483
- ) {
484
- const ordinalSignerInfo = ordinalSigner || signer
485
- // check all fee before process transaction
486
- let brc20Balance = await this.unisat.getBrc20AddressBalanceForTicker(signer.address, brc.tick)
487
-
488
- if (BigNumber(brc20Balance.transferableBalance).gte(brc.amount)) {
489
- let transferrableInscription = brc20Balance.transferableInscriptions.find(
490
- (insc) => insc.data.tick === brc.tick && BigNumber(insc.data.amt).isEqualTo(brc.amount),
491
- )
492
-
493
- console.log("transferrableInscription", transferrableInscription)
494
- }
495
-
496
- if (BigNumber(brc20Balance.availableBalanceSafe).isLessThan(brc.amount)) {
497
- throw new Error("insufficient balance")
498
- }
499
-
500
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
501
-
502
- let utxo1: ExtendedUtxo[] =
503
- extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
504
- let utxo2: ExtendedUtxo[] = []
505
- const { inscribeAddress, inscribeDepositUnsignedInfo, inscribeUnsignedInfo } =
506
- await this.inscribeBrc20Unsigned(brc, signer, ordinalSignerInfo, utxo1, feeRate)
507
-
508
- utxo2 = utxo1.filter(
509
- (u) =>
510
- inscribeDepositUnsignedInfo.inputs.findIndex(
511
- (i) => i.hash === u.hash && i.index === u.index,
512
- ) === -1,
513
- )
514
-
515
- if (inscribeDepositUnsignedInfo.change) {
516
- utxo2.push({
517
- hash: inscribeDepositUnsignedInfo.possibleTxId,
518
- index: inscribeDepositUnsignedInfo.outputs.length,
519
- value: inscribeDepositUnsignedInfo.change.value,
520
- signerInfo: signer,
521
- })
522
- }
523
- let transferTxUnsignedInfo = await this.transferBrc20Unsigned(
524
- receiver,
525
- {
526
- hash: inscribeUnsignedInfo.possibleTxId,
527
- value: inscribeUnsignedInfo.outputs[0].value,
528
- index: 0,
529
- },
530
- signer,
531
- ordinalSignerInfo,
532
- utxo2,
533
- feeRate,
534
- otherTargets,
535
- )
536
- return {
537
- inscribeDepositUnsignedInfo,
538
- inscribeUnsignedInfo,
539
- transferTxUnsignedInfo,
540
- inscribeAddress,
541
- }
542
- }
543
-
544
- async inscribeAndTransferBrc20(
545
- receiver: string,
546
- brc: { tick: string; amount: number | string },
547
- otherTargets?: Target[],
548
- extendedUtxo?: ExtendedUtxo[],
549
- staticFeeRate?: number,
550
- ) {
551
- // check all fee before process transaction
552
- let brc20Balance = await this.unisat.getBrc20AddressBalanceForTicker(
553
- this.bitcoinAddress!,
554
- brc.tick,
555
- )
556
-
557
- if (BigNumber(brc20Balance.transferableBalance).gte(brc.amount)) {
558
- let transferrableInscription = brc20Balance.transferableInscriptions.find(
559
- (insc) => insc.data.tick === brc.tick && BigNumber(insc.data.amt).isEqualTo(brc.amount),
560
- )
561
- console.log("transferrableInscription", transferrableInscription)
562
-
563
- if (transferrableInscription) {
564
- let ins = await this.btcInterface.unisat.getInscriptionInfo(
565
- transferrableInscription.inscriptionId,
566
- )
567
-
568
- if (ins) {
569
- let transferTx = await this.transferBrc20(
570
- receiver,
571
- {
572
- hash: ins.utxo.txid!,
573
- index: ins.utxo.vout!,
574
- value: ins.utxo.satoshi!,
575
- },
576
- extendedUtxo ||
577
- (await this.btcInterface.getBTCUtxo(this.bitcoinAddress!, this.signerInfo!)),
578
- staticFeeRate || (await this.btcInterface.getFeeRate("normal")),
579
- otherTargets,
580
- )
581
- return {
582
- transferTx,
583
- }
584
- }
585
- }
586
- }
587
-
588
- if (BigNumber(brc20Balance.availableBalanceSafe).isLessThan(brc.amount)) {
589
- throw new Error(`insufficient balance ${brc20Balance.availableBalanceSafe} ${brc.amount}`)
590
- }
591
-
592
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
593
-
594
- let utxo1: ExtendedUtxo[] =
595
- extendedUtxo || (await this.btcInterface.getBTCUtxo(this.bitcoinAddress!, this.signerInfo!))
596
- let utxo2: ExtendedUtxo[] = []
597
- const { inscribeAddress, inscribeDepositTx, inscribeTx } = await this.inscribeBrc20(
598
- brc,
599
- utxo1,
600
- feeRate,
601
- )
602
-
603
- utxo2 = utxo1.filter(
604
- (u) =>
605
- inscribeDepositTx.inputs.findIndex((i) => i.hash === u.hash && i.index === u.index) === -1,
606
- )
607
-
608
- if (inscribeDepositTx.change) {
609
- utxo2.push({
610
- hash: inscribeDepositTx.hash,
611
- index: inscribeDepositTx.changeIndex!,
612
- value: inscribeDepositTx.change.value,
613
- signerInfo: this.signerInfo!,
614
- })
615
- }
616
- await sleep(10 * 1000)
617
- let transferTx = await this.transferBrc20(receiver, inscribeTx, utxo2, feeRate, otherTargets)
618
- return {
619
- inscribeTx,
620
- inscribeAddress,
621
- inscribeDepositTx,
622
- transferTx,
623
- }
624
- }
625
-
626
- async wrapBrc20Unsigned(
627
- recipientAddress: string,
628
- brc: { tick: string; amount: number | string },
629
- brc20TokenId: number,
630
- signer: SignerInfo,
631
- lockerAddress: string,
632
- exchange?: {
633
- outputToken: string
634
- outputAmount: string
635
- },
636
- ordinalSigner?: SignerInfo,
637
- extendedUtxo?: ExtendedUtxo[],
638
- staticFeeRate?: number,
639
- { chainId = 137, appId = exchange ? 1 : 0 } = {},
640
- ) {
641
- const isExchange = !!exchange
642
- let dataHex = generateBrc2OpReturn({
643
- chainId,
644
- appId,
645
- brc20TokenId,
646
- inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
647
- recipientAddress,
648
- isExchange,
649
- outputToken: exchange?.outputToken,
650
- outputAmount: exchange?.outputAmount,
651
- })
652
- let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
653
- return this.inscribeAndTransferBrc20Unsigned(
654
- lockerAddress,
655
- brc,
656
- signer,
657
- ordinalSigner,
658
- [opTarget],
659
- extendedUtxo,
660
- staticFeeRate,
661
- )
662
- }
663
-
664
- async wrapBrc20OnlyTransferUnsigned(
665
- recipientAddress: string,
666
- brcInscribeTx: {
667
- hash: string
668
- value: number
669
- index: number
670
- },
671
- brc: { tick: string; amount: number | string },
672
- brc20TokenId: number,
673
- signer: SignerInfo,
674
- lockerAddress: string,
675
- exchange?: {
676
- outputToken: string
677
- outputAmount: string
678
- },
679
- ordinalSigner?: SignerInfo,
680
- extendedUtxo?: ExtendedUtxo[],
681
- staticFeeRate?: number,
682
- { chainId = 137, appId = exchange ? 1 : 0 } = {},
683
- ) {
684
- let utxo: ExtendedUtxo[] =
685
- extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
686
-
687
- const isExchange = !!exchange
688
- let dataHex = generateBrc2OpReturn({
689
- chainId,
690
- appId,
691
- brc20TokenId,
692
- inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
693
- recipientAddress,
694
- isExchange,
695
- outputToken: exchange?.outputToken,
696
- outputAmount: exchange?.outputAmount,
697
- })
698
- let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
699
- let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
700
- let transferTxUnsignedInfo = await this.transferBrc20Unsigned(
701
- lockerAddress,
702
- brcInscribeTx,
703
- signer,
704
- ordinalSigner,
705
- utxo,
706
- feeRate,
707
- [opTarget],
708
- )
709
- return transferTxUnsignedInfo
710
- }
711
-
712
- async wrapBrc20(
713
- recipientAddress: string,
714
- brc: { tick: string; amount: number | string },
715
- brc20TokenId: number,
716
- lockerAddress: string,
717
- exchange?: {
718
- outputToken: string
719
- outputAmount: string
720
- },
721
- extendedUtxo?: ExtendedUtxo[],
722
- staticFeeRate?: number,
723
- { chainId = 137, appId = exchange ? 1 : 0 } = {},
724
- ) {
725
- const isExchange = !!exchange
726
-
727
- let dataHex = generateBrc2OpReturn({
728
- chainId,
729
- appId,
730
- brc20TokenId,
731
- inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
732
- recipientAddress,
733
- isExchange,
734
- outputToken: exchange?.outputToken,
735
- outputAmount: exchange?.outputAmount,
736
- })
737
- let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
738
- return this.inscribeAndTransferBrc20(
739
- lockerAddress,
740
- brc,
741
- [opTarget],
742
- extendedUtxo,
743
- staticFeeRate,
744
- )
745
- }
746
- }
747
-
748
- export default OrdinalWallet
1
+ import BigNumber from "bignumber.js"
2
+ import { bitcoin as bitcoinProviders } from "@teleportdao/providers"
3
+ import { OrdinalTransactionBuilder } from "./transaction-builder/ordinal-transaction-builder"
4
+ import BitcoinSign from "./sign/sign-transaction"
5
+ //
6
+ import { BitcoinInterfaceOrdinal } from "./bitcoin-interface-ordinal"
7
+ import { generateBrc2OpReturn } from "./helper/brc20-helper"
8
+ import { runWithRetries, sleep } from "./utils/tools"
9
+ import { BitcoinConnectionInfo } from "./type"
10
+ import { ChangeTarget, ExtendedUtxo, SignerInfo, Target } from "./transaction-builder"
11
+ import { BitcoinBaseWallet } from "./bitcoin-wallet-base"
12
+
13
+ class OrdinalWallet extends BitcoinBaseWallet {
14
+ unisat: bitcoinProviders.UniSat
15
+ transactionBuilder: OrdinalTransactionBuilder
16
+ btcInterface: BitcoinInterfaceOrdinal
17
+ signer: BitcoinSign
18
+ constructor(
19
+ networkName: string,
20
+ uniSatToken: string,
21
+ connectionInfo: BitcoinConnectionInfo = {
22
+ api: {
23
+ provider: "MempoolSpace",
24
+ },
25
+ },
26
+ ) {
27
+ super(networkName, connectionInfo)
28
+ if (!connectionInfo.rpc?.enabled) {
29
+ throw new Error("rpc is required")
30
+ }
31
+ this.transactionBuilder = new OrdinalTransactionBuilder(connectionInfo, networkName)
32
+ this.signer = new BitcoinSign(this.network)
33
+ this.btcInterface = new BitcoinInterfaceOrdinal(connectionInfo, networkName, uniSatToken)
34
+ this.unisat = this.btcInterface.unisat
35
+ }
36
+
37
+ async sendSignedPsbtWithRetry(signedPsbt: string, { maxTries = 5, retrySleep = 5000 } = {}) {
38
+ return runWithRetries(() => this.sendSignedPsbt(signedPsbt), {
39
+ retrySleep,
40
+ maxTries,
41
+ })
42
+ }
43
+
44
+ static deployBRC20Data(tickName: string, max: number | string, limit: number | string) {
45
+ let data = {
46
+ p: "brc-20",
47
+ op: "deploy",
48
+ tick: tickName,
49
+ max: `${max}`,
50
+ lim: `${limit}`,
51
+ }
52
+ return {
53
+ buffer: Buffer.from(JSON.stringify(data), "utf8"),
54
+ type: "text/plain",
55
+ // type: "application/json",
56
+ }
57
+ }
58
+
59
+ static mintBRC20Data(tickName: string, amount: string | number) {
60
+ if (BigNumber(amount).isLessThanOrEqualTo(0)) throw new Error("amount should be greater than 0")
61
+ let data = {
62
+ p: "brc-20",
63
+ op: "mint",
64
+ tick: tickName,
65
+ amt: `${amount}`,
66
+ }
67
+ return {
68
+ buffer: Buffer.from(JSON.stringify(data), "utf8"),
69
+ type: "text/plain",
70
+ // type: "application/json",
71
+ }
72
+ }
73
+
74
+ static transferBRC20Data(tickName: string, amount: string | number) {
75
+ if (BigNumber(amount).isLessThanOrEqualTo(0)) throw new Error("amount should be greater than 0")
76
+ let data = {
77
+ p: "brc-20",
78
+ op: "transfer",
79
+ tick: tickName,
80
+ amt: `${amount}`,
81
+ }
82
+ return {
83
+ buffer: Buffer.from(JSON.stringify(data), "utf8"),
84
+ type: "text/plain",
85
+ // type: "application/json",
86
+ }
87
+ }
88
+
89
+ async sendUsingUtxosUnsigned({
90
+ receivers,
91
+ speed = "normal",
92
+ utxo,
93
+ changeAddress,
94
+ staticFeeRate,
95
+ }: {
96
+ receivers: {
97
+ address: string
98
+ value: number
99
+ }[]
100
+ changeAddress: string
101
+ speed?: "normal" | "fast" | "slow"
102
+ staticFeeRate?: number
103
+ utxo: ExtendedUtxo[]
104
+ }) {
105
+ let extendedUtxo: ExtendedUtxo[] = utxo
106
+
107
+ receivers.forEach(({ value }) => {
108
+ if (value - +value.toFixed(0) !== 0)
109
+ throw new Error("incorrect amount. amount should be in satoshi")
110
+ })
111
+
112
+ // eslint-disable-next-line no-underscore-dangle
113
+ let feeRate = staticFeeRate || (await this.transactionBuilder._getFeeRate(speed))
114
+ let unsignedTx = await this.transactionBuilder.processUnsignedTransaction({
115
+ extendedUtxo,
116
+ targets: receivers,
117
+ changeAddress,
118
+ feeRate,
119
+ fullAmount: false,
120
+ })
121
+
122
+ return {
123
+ ...unsignedTx,
124
+ possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(unsignedTx.unsignedTransaction),
125
+ }
126
+ }
127
+
128
+ async sendUsingUtxos({
129
+ receivers,
130
+ speed = "normal",
131
+ utxo,
132
+ }: {
133
+ receivers: {
134
+ address: string
135
+ value: number
136
+ }[]
137
+ speed?: "normal" | "fast" | "slow"
138
+ utxo?: ExtendedUtxo[]
139
+ }) {
140
+ if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
141
+ throw new Error("account not initialized")
142
+ }
143
+
144
+ let extendedUtxo: ExtendedUtxo[] = utxo
145
+ ? utxo.filter((u) => u.signerInfo.address === this.currentAccount!)
146
+ : await this.getExtendedUtxo({
147
+ address: this.currentAccount,
148
+ addressType: this.currentAccountType,
149
+ publicKey: this.publicKey.toString("hex"),
150
+ })
151
+
152
+ let unsignedTx = await this.sendUsingUtxosUnsigned({
153
+ receivers,
154
+ speed,
155
+ utxo: extendedUtxo,
156
+ changeAddress: this.bitcoinAddress!,
157
+ })
158
+ let signedPsbt = await this.signer.signPsbt(unsignedTx, this.privateKey)
159
+ let signedTx = this.signer.finalizePsbts([signedPsbt])
160
+ let txId = await this.transactionBuilder.sendTx(signedTx)
161
+ const { inputs, outputs, change, fee } = unsignedTx
162
+ return { txId, inputs, outputs, change, fee }
163
+ }
164
+
165
+ async inscribeOrdinalDepositUnsigned(
166
+ file: {
167
+ buffer: Buffer
168
+ type: string
169
+ },
170
+ signer: SignerInfo,
171
+ ordinalSigner?: SignerInfo,
172
+ extendedUtxo?: ExtendedUtxo[],
173
+ staticFeeRate?: number,
174
+ ) {
175
+ const ordinalSignerPublicKey = ordinalSigner?.publicKey || signer.publicKey
176
+ const publicKey = Buffer.from(ordinalSignerPublicKey, "hex")
177
+ let transferOrdinal = this.transactionBuilder.createOrdinalAddress(file, publicKey)
178
+ const leafScript = transferOrdinal.redeem.output
179
+ const { ordinalAddress } = transferOrdinal
180
+
181
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
182
+ let fee = +(((400 + leafScript.length) / 4) * feeRate * 1.5).toFixed(0)
183
+ let ordinalAmount = 600
184
+
185
+ let utxo1: ExtendedUtxo[] =
186
+ extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
187
+
188
+ let inscribeDepositUnsignedInfo = await this.sendUsingUtxosUnsigned({
189
+ receivers: [
190
+ {
191
+ address: ordinalAddress,
192
+ value: ordinalAmount + fee,
193
+ },
194
+ ],
195
+ utxo: utxo1,
196
+ changeAddress: signer.address,
197
+ })
198
+
199
+ return {
200
+ inscribeDepositUnsignedInfo,
201
+ transferOrdinal,
202
+ }
203
+ }
204
+
205
+ async inscribeOrdinalUnsigned(
206
+ file: {
207
+ buffer: Buffer
208
+ type: string
209
+ },
210
+ signer: SignerInfo,
211
+ ordinalSigner?: SignerInfo,
212
+ extendedUtxo?: ExtendedUtxo[],
213
+ staticFeeRate?: number,
214
+ ) {
215
+ const receiverAddress = ordinalSigner?.address || signer.address
216
+ const { inscribeDepositUnsignedInfo, transferOrdinal } =
217
+ await this.inscribeOrdinalDepositUnsigned(
218
+ file,
219
+ signer,
220
+ ordinalSigner,
221
+ extendedUtxo,
222
+ staticFeeRate,
223
+ )
224
+ const { ordinalAddress } = transferOrdinal
225
+ let ordinalAmount = 600
226
+ let inscribeDeposit: {
227
+ hash: string
228
+ value: number
229
+ index: number
230
+ } = {
231
+ hash: inscribeDepositUnsignedInfo.possibleTxId,
232
+ value: inscribeDepositUnsignedInfo.outputs[0].value,
233
+ index: 0,
234
+ }
235
+ let inscribeUnsignedInfo = this.transactionBuilder.createInscribeUnsignedTx(
236
+ transferOrdinal,
237
+ inscribeDeposit,
238
+ receiverAddress,
239
+ ordinalAmount,
240
+ )
241
+ return {
242
+ inscribeDepositUnsignedInfo: {
243
+ ...inscribeDepositUnsignedInfo,
244
+ possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(
245
+ inscribeDepositUnsignedInfo.unsignedTransaction,
246
+ ),
247
+ },
248
+ inscribeUnsignedInfo: {
249
+ ...inscribeUnsignedInfo,
250
+ possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(
251
+ inscribeUnsignedInfo.unsignedTransaction,
252
+ ),
253
+ },
254
+ inscribeAddress: ordinalAddress,
255
+ receiverAddress,
256
+ }
257
+ }
258
+
259
+ async inscribeOrdinal(
260
+ file: {
261
+ buffer: Buffer
262
+ type: string
263
+ },
264
+ extendedUtxo?: ExtendedUtxo[],
265
+ staticFeeRate?: number,
266
+ ordinalReceiverAddress?: string,
267
+ ) {
268
+ if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
269
+ throw new Error("account not initialized")
270
+ }
271
+ const receiverAddress = ordinalReceiverAddress || this.bitcoinAddress!
272
+
273
+ const { inscribeDepositUnsignedInfo, transferOrdinal } =
274
+ await this.inscribeOrdinalDepositUnsigned(
275
+ file,
276
+ this.signerInfo!,
277
+ undefined,
278
+ extendedUtxo,
279
+ staticFeeRate,
280
+ )
281
+ let ordinalUtxo = await this.btcInterface.getBTCUtxo(
282
+ transferOrdinal.ordinalAddress,
283
+ this.signerInfo!,
284
+ )
285
+
286
+ let inscribeDeposit: {
287
+ inputs: {
288
+ hash: string
289
+ value: number
290
+ index: number
291
+ signerInfo: SignerInfo
292
+ }[]
293
+ hash: string
294
+ value: number
295
+ index: number
296
+ change?: ChangeTarget
297
+ changeIndex?: number
298
+ }
299
+
300
+ if (ordinalUtxo.length > 1) {
301
+ throw new Error("multiple deposit found for this ordinal address")
302
+ }
303
+
304
+ if (
305
+ ordinalUtxo.length === 0 ||
306
+ ordinalUtxo[0].value <
307
+ inscribeDepositUnsignedInfo.outputs[0].value -
308
+ inscribeDepositUnsignedInfo.outputs[0].value * 0.15
309
+ ) {
310
+ const signedPsbt1 = await this.signer.signPsbt(inscribeDepositUnsignedInfo, this.privateKey!)
311
+ console.log("inscribe deposit tx ...")
312
+ const inscribeDepositTxId = await this.sendSignedPsbt(signedPsbt1)
313
+ console.log(`inscribe deposit txId : ${inscribeDepositTxId}`)
314
+
315
+ inscribeDeposit = {
316
+ hash: inscribeDepositTxId,
317
+ value: inscribeDepositUnsignedInfo.outputs[0].value,
318
+ index: 0,
319
+ inputs: inscribeDepositUnsignedInfo.inputs,
320
+ change: inscribeDepositUnsignedInfo.change,
321
+ changeIndex: inscribeDepositUnsignedInfo.change
322
+ ? inscribeDepositUnsignedInfo.outputs.length
323
+ : undefined,
324
+ }
325
+ } else {
326
+ inscribeDeposit = {
327
+ hash: ordinalUtxo[0].hash,
328
+ value: ordinalUtxo[0].value,
329
+ index: ordinalUtxo[0].index,
330
+ inputs: [],
331
+ }
332
+ console.log("no need to deposit", inscribeDeposit)
333
+ }
334
+
335
+ const ordinalAmount = 600
336
+ let inscribeUnsignedInfo = this.transactionBuilder.createInscribeUnsignedTx(
337
+ transferOrdinal,
338
+ inscribeDeposit,
339
+ receiverAddress,
340
+ ordinalAmount,
341
+ )
342
+
343
+ const signedPsbt2 = await this.signer.signPsbt(
344
+ inscribeUnsignedInfo,
345
+ this.privateKey!,
346
+ undefined,
347
+ false,
348
+ )
349
+
350
+ console.log("inscribeTxId ...")
351
+ await sleep(10 * 1000)
352
+ let inscribeTxId = await this.sendSignedPsbtWithRetry(signedPsbt2)
353
+ console.log("inscribeTxId", inscribeTxId)
354
+ return {
355
+ inscribeTx: { hash: inscribeTxId, index: 0, value: ordinalAmount },
356
+ inscribeDepositTx: inscribeDeposit,
357
+ inscribeAddress: transferOrdinal.ordinalAddress,
358
+ }
359
+ }
360
+
361
+ async deployBrc20(
362
+ brc: { tick: string; max: number; limit: number },
363
+ extendedUtxo?: ExtendedUtxo[],
364
+ staticFeeRate?: number,
365
+ ) {
366
+ let file = OrdinalWallet.deployBRC20Data(brc.tick, brc.max, brc.limit)
367
+ return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
368
+ }
369
+
370
+ async mintBrc20(
371
+ brc: { tick: string; amount: number | string },
372
+ extendedUtxo?: ExtendedUtxo[],
373
+ staticFeeRate?: number,
374
+ ) {
375
+ let file = OrdinalWallet.mintBRC20Data(brc.tick, brc.amount)
376
+ return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
377
+ }
378
+
379
+ async inscribeBrc20Unsigned(
380
+ brc: { tick: string; amount: number | string },
381
+ signer: SignerInfo,
382
+ ordinalSigner?: SignerInfo,
383
+ extendedUtxo?: ExtendedUtxo[],
384
+ staticFeeRate?: number,
385
+ ) {
386
+ let file = OrdinalWallet.transferBRC20Data(brc.tick, brc.amount)
387
+ return this.inscribeOrdinalUnsigned(file, signer, ordinalSigner, extendedUtxo, staticFeeRate)
388
+ }
389
+
390
+ async inscribeBrc20(
391
+ brc: { tick: string; amount: number | string },
392
+ extendedUtxo?: ExtendedUtxo[],
393
+ staticFeeRate?: number,
394
+ ) {
395
+ let file = OrdinalWallet.transferBRC20Data(brc.tick, brc.amount)
396
+ return this.inscribeOrdinal(file, extendedUtxo, staticFeeRate)
397
+ }
398
+
399
+ async transferBrc20Unsigned(
400
+ receiver: string,
401
+ brcInscribeUtxo: {
402
+ hash: string
403
+ value: number
404
+ index: number
405
+ },
406
+ signer: SignerInfo,
407
+ ordinalSigner?: SignerInfo,
408
+ extendedUtxo?: ExtendedUtxo[],
409
+ staticFeeRate?: number,
410
+ otherTargets?: Target[],
411
+ ) {
412
+ const ordinalSignerInfo = ordinalSigner || signer
413
+ let utxo = extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
414
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
415
+ let unsignedTx = await this.transactionBuilder.createNftPsbt({
416
+ extendedUtxo: utxo,
417
+ nftExtendedUtxo: {
418
+ hash: brcInscribeUtxo.hash,
419
+ index: 0,
420
+ value: brcInscribeUtxo.value,
421
+ signerInfo: ordinalSignerInfo,
422
+ },
423
+ feeRate,
424
+ receiverAddress: receiver,
425
+ changeAddress: signer.address,
426
+ otherTargets,
427
+ })
428
+ return {
429
+ ...unsignedTx,
430
+ possibleTxId: this.transactionBuilder.getUnsignedPsbtTxId(unsignedTx.unsignedTransaction),
431
+ }
432
+ }
433
+
434
+ async transferBrc20(
435
+ receiver: string,
436
+ brcInscribeUtxo: {
437
+ hash: string
438
+ value: number
439
+ index: number
440
+ },
441
+ extendedUtxo: ExtendedUtxo[],
442
+ staticFeeRate?: number,
443
+ otherTargets?: Target[],
444
+ ) {
445
+ if (!this.currentAccount || !this.currentAccountType || !this.publicKey || !this.privateKey) {
446
+ throw new Error("account not initialized")
447
+ }
448
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
449
+ let unsignedTx = await this.transactionBuilder.createNftPsbt({
450
+ extendedUtxo,
451
+ nftExtendedUtxo: {
452
+ hash: brcInscribeUtxo.hash,
453
+ index: 0,
454
+ value: brcInscribeUtxo.value,
455
+ signerInfo: this.signerInfo!,
456
+ },
457
+ feeRate,
458
+ receiverAddress: receiver,
459
+ changeAddress: this.bitcoinAddress!,
460
+ otherTargets,
461
+ })
462
+
463
+ let signedTx = await this.signer.signPsbt(unsignedTx, this.privateKey!)
464
+ let finalTransferTxId = await this.sendSignedPsbtWithRetry(signedTx)
465
+ return {
466
+ hash: finalTransferTxId,
467
+ index: 0,
468
+ value: brcInscribeUtxo.value,
469
+ inputs: unsignedTx.inputs,
470
+ change: unsignedTx.change,
471
+ changeIndex: unsignedTx.change ? unsignedTx.outputs.length : undefined,
472
+ }
473
+ }
474
+
475
+ async inscribeAndTransferBrc20Unsigned(
476
+ receiver: string,
477
+ brc: { tick: string; amount: number | string },
478
+ signer: SignerInfo,
479
+ ordinalSigner?: SignerInfo,
480
+ otherTargets?: Target[],
481
+ extendedUtxo?: ExtendedUtxo[],
482
+ staticFeeRate?: number,
483
+ ) {
484
+ const ordinalSignerInfo = ordinalSigner || signer
485
+ // check all fee before process transaction
486
+ let brc20Balance = await this.unisat.getBrc20AddressBalanceForTicker(signer.address, brc.tick)
487
+
488
+ if (BigNumber(brc20Balance.transferableBalance).gte(brc.amount)) {
489
+ let transferrableInscription = brc20Balance.transferableInscriptions.find(
490
+ (insc) => insc.data.tick === brc.tick && BigNumber(insc.data.amt).isEqualTo(brc.amount),
491
+ )
492
+
493
+ console.log("transferrableInscription", transferrableInscription)
494
+ }
495
+
496
+ if (BigNumber(brc20Balance.availableBalanceSafe).isLessThan(brc.amount)) {
497
+ throw new Error("insufficient balance")
498
+ }
499
+
500
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
501
+
502
+ let utxo1: ExtendedUtxo[] =
503
+ extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
504
+ let utxo2: ExtendedUtxo[] = []
505
+ const { inscribeAddress, inscribeDepositUnsignedInfo, inscribeUnsignedInfo } =
506
+ await this.inscribeBrc20Unsigned(brc, signer, ordinalSignerInfo, utxo1, feeRate)
507
+
508
+ utxo2 = utxo1.filter(
509
+ (u) =>
510
+ inscribeDepositUnsignedInfo.inputs.findIndex(
511
+ (i) => i.hash === u.hash && i.index === u.index,
512
+ ) === -1,
513
+ )
514
+
515
+ if (inscribeDepositUnsignedInfo.change) {
516
+ utxo2.push({
517
+ hash: inscribeDepositUnsignedInfo.possibleTxId,
518
+ index: inscribeDepositUnsignedInfo.outputs.length,
519
+ value: inscribeDepositUnsignedInfo.change.value,
520
+ signerInfo: signer,
521
+ })
522
+ }
523
+ let transferTxUnsignedInfo = await this.transferBrc20Unsigned(
524
+ receiver,
525
+ {
526
+ hash: inscribeUnsignedInfo.possibleTxId,
527
+ value: inscribeUnsignedInfo.outputs[0].value,
528
+ index: 0,
529
+ },
530
+ signer,
531
+ ordinalSignerInfo,
532
+ utxo2,
533
+ feeRate,
534
+ otherTargets,
535
+ )
536
+ return {
537
+ inscribeDepositUnsignedInfo,
538
+ inscribeUnsignedInfo,
539
+ transferTxUnsignedInfo,
540
+ inscribeAddress,
541
+ }
542
+ }
543
+
544
+ async inscribeAndTransferBrc20(
545
+ receiver: string,
546
+ brc: { tick: string; amount: number | string },
547
+ otherTargets?: Target[],
548
+ extendedUtxo?: ExtendedUtxo[],
549
+ staticFeeRate?: number,
550
+ ) {
551
+ // check all fee before process transaction
552
+ let brc20Balance = await this.unisat.getBrc20AddressBalanceForTicker(
553
+ this.bitcoinAddress!,
554
+ brc.tick,
555
+ )
556
+
557
+ if (BigNumber(brc20Balance.transferableBalance).gte(brc.amount)) {
558
+ let transferrableInscription = brc20Balance.transferableInscriptions.find(
559
+ (insc) => insc.data.tick === brc.tick && BigNumber(insc.data.amt).isEqualTo(brc.amount),
560
+ )
561
+ console.log("transferrableInscription", transferrableInscription)
562
+
563
+ if (transferrableInscription) {
564
+ let ins = await this.btcInterface.unisat.getInscriptionInfo(
565
+ transferrableInscription.inscriptionId,
566
+ )
567
+
568
+ if (ins) {
569
+ let transferTx = await this.transferBrc20(
570
+ receiver,
571
+ {
572
+ hash: ins.utxo.txid!,
573
+ index: ins.utxo.vout!,
574
+ value: ins.utxo.satoshi!,
575
+ },
576
+ extendedUtxo ||
577
+ (await this.btcInterface.getBTCUtxo(this.bitcoinAddress!, this.signerInfo!)),
578
+ staticFeeRate || (await this.btcInterface.getFeeRate("normal")),
579
+ otherTargets,
580
+ )
581
+ return {
582
+ transferTx,
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ if (BigNumber(brc20Balance.availableBalanceSafe).isLessThan(brc.amount)) {
589
+ throw new Error(`insufficient balance ${brc20Balance.availableBalanceSafe} ${brc.amount}`)
590
+ }
591
+
592
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
593
+
594
+ let utxo1: ExtendedUtxo[] =
595
+ extendedUtxo || (await this.btcInterface.getBTCUtxo(this.bitcoinAddress!, this.signerInfo!))
596
+ let utxo2: ExtendedUtxo[] = []
597
+ const { inscribeAddress, inscribeDepositTx, inscribeTx } = await this.inscribeBrc20(
598
+ brc,
599
+ utxo1,
600
+ feeRate,
601
+ )
602
+
603
+ utxo2 = utxo1.filter(
604
+ (u) =>
605
+ inscribeDepositTx.inputs.findIndex((i) => i.hash === u.hash && i.index === u.index) === -1,
606
+ )
607
+
608
+ if (inscribeDepositTx.change) {
609
+ utxo2.push({
610
+ hash: inscribeDepositTx.hash,
611
+ index: inscribeDepositTx.changeIndex!,
612
+ value: inscribeDepositTx.change.value,
613
+ signerInfo: this.signerInfo!,
614
+ })
615
+ }
616
+ await sleep(10 * 1000)
617
+ let transferTx = await this.transferBrc20(receiver, inscribeTx, utxo2, feeRate, otherTargets)
618
+ return {
619
+ inscribeTx,
620
+ inscribeAddress,
621
+ inscribeDepositTx,
622
+ transferTx,
623
+ }
624
+ }
625
+
626
+ async wrapBrc20Unsigned(
627
+ recipientAddress: string,
628
+ brc: { tick: string; amount: number | string },
629
+ brc20TokenId: number,
630
+ signer: SignerInfo,
631
+ lockerAddress: string,
632
+ exchange?: {
633
+ outputToken: string
634
+ outputAmount: string
635
+ },
636
+ ordinalSigner?: SignerInfo,
637
+ extendedUtxo?: ExtendedUtxo[],
638
+ staticFeeRate?: number,
639
+ { chainId = 137, appId = exchange ? 1 : 0 } = {},
640
+ ) {
641
+ const isExchange = !!exchange
642
+ let dataHex = generateBrc2OpReturn({
643
+ chainId,
644
+ appId,
645
+ brc20TokenId,
646
+ inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
647
+ recipientAddress,
648
+ isExchange,
649
+ outputToken: exchange?.outputToken,
650
+ outputAmount: exchange?.outputAmount,
651
+ })
652
+ let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
653
+ return this.inscribeAndTransferBrc20Unsigned(
654
+ lockerAddress,
655
+ brc,
656
+ signer,
657
+ ordinalSigner,
658
+ [opTarget],
659
+ extendedUtxo,
660
+ staticFeeRate,
661
+ )
662
+ }
663
+
664
+ async wrapBrc20OnlyTransferUnsigned(
665
+ recipientAddress: string,
666
+ brcInscribeTx: {
667
+ hash: string
668
+ value: number
669
+ index: number
670
+ },
671
+ brc: { tick: string; amount: number | string },
672
+ brc20TokenId: number,
673
+ signer: SignerInfo,
674
+ lockerAddress: string,
675
+ exchange?: {
676
+ outputToken: string
677
+ outputAmount: string
678
+ },
679
+ ordinalSigner?: SignerInfo,
680
+ extendedUtxo?: ExtendedUtxo[],
681
+ staticFeeRate?: number,
682
+ { chainId = 137, appId = exchange ? 1 : 0 } = {},
683
+ ) {
684
+ let utxo: ExtendedUtxo[] =
685
+ extendedUtxo || (await this.btcInterface.getBTCUtxo(signer.address, signer))
686
+
687
+ const isExchange = !!exchange
688
+ let dataHex = generateBrc2OpReturn({
689
+ chainId,
690
+ appId,
691
+ brc20TokenId,
692
+ inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
693
+ recipientAddress,
694
+ isExchange,
695
+ outputToken: exchange?.outputToken,
696
+ outputAmount: exchange?.outputAmount,
697
+ })
698
+ let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
699
+ let feeRate = staticFeeRate || (await this.btcInterface.getFeeRate("normal"))
700
+ let transferTxUnsignedInfo = await this.transferBrc20Unsigned(
701
+ lockerAddress,
702
+ brcInscribeTx,
703
+ signer,
704
+ ordinalSigner,
705
+ utxo,
706
+ feeRate,
707
+ [opTarget],
708
+ )
709
+ return transferTxUnsignedInfo
710
+ }
711
+
712
+ async wrapBrc20(
713
+ recipientAddress: string,
714
+ brc: { tick: string; amount: number | string },
715
+ brc20TokenId: number,
716
+ lockerAddress: string,
717
+ exchange?: {
718
+ outputToken: string
719
+ outputAmount: string
720
+ },
721
+ extendedUtxo?: ExtendedUtxo[],
722
+ staticFeeRate?: number,
723
+ { chainId = 137, appId = exchange ? 1 : 0 } = {},
724
+ ) {
725
+ const isExchange = !!exchange
726
+
727
+ let dataHex = generateBrc2OpReturn({
728
+ chainId,
729
+ appId,
730
+ brc20TokenId,
731
+ inputAmount: BigNumber(brc.amount).multipliedBy(1e18).toFixed(0),
732
+ recipientAddress,
733
+ isExchange,
734
+ outputToken: exchange?.outputToken,
735
+ outputAmount: exchange?.outputAmount,
736
+ })
737
+ let opTarget = this.transactionBuilder.getOpReturnTarget(dataHex)
738
+ return this.inscribeAndTransferBrc20(
739
+ lockerAddress,
740
+ brc,
741
+ [opTarget],
742
+ extendedUtxo,
743
+ staticFeeRate,
744
+ )
745
+ }
746
+ }
747
+
748
+ export default OrdinalWallet