@arkade-os/sdk 0.3.0-alpha.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +48 -14
  2. package/dist/cjs/arknote/index.js +3 -3
  3. package/dist/cjs/forfeit.js +2 -2
  4. package/dist/cjs/identity/singleKey.js +8 -8
  5. package/dist/cjs/index.js +13 -5
  6. package/dist/cjs/{bip322 → intent}/index.js +38 -61
  7. package/dist/cjs/musig2/index.js +2 -1
  8. package/dist/cjs/musig2/nonces.js +4 -0
  9. package/dist/cjs/providers/ark.js +76 -45
  10. package/dist/cjs/providers/errors.js +59 -0
  11. package/dist/cjs/providers/expoArk.js +15 -170
  12. package/dist/cjs/providers/expoIndexer.js +22 -111
  13. package/dist/cjs/providers/expoUtils.js +124 -0
  14. package/dist/cjs/providers/onchain.js +19 -20
  15. package/dist/cjs/repositories/walletRepository.js +64 -28
  16. package/dist/cjs/script/base.js +15 -7
  17. package/dist/cjs/script/tapscript.js +20 -21
  18. package/dist/cjs/script/vhtlc.js +2 -2
  19. package/dist/cjs/tree/signingSession.js +44 -11
  20. package/dist/cjs/tree/txTree.js +3 -4
  21. package/dist/cjs/tree/validation.js +2 -3
  22. package/dist/cjs/utils/arkTransaction.js +105 -15
  23. package/dist/cjs/utils/transaction.js +28 -0
  24. package/dist/cjs/utils/unknownFields.js +7 -7
  25. package/dist/cjs/wallet/onchain.js +6 -7
  26. package/dist/cjs/wallet/serviceWorker/response.js +32 -0
  27. package/dist/cjs/wallet/serviceWorker/utils.js +2 -0
  28. package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
  29. package/dist/cjs/wallet/serviceWorker/worker.js +46 -27
  30. package/dist/cjs/wallet/unroll.js +7 -9
  31. package/dist/cjs/wallet/utils.js +9 -0
  32. package/dist/cjs/wallet/vtxo-manager.js +323 -0
  33. package/dist/cjs/wallet/wallet.js +98 -125
  34. package/dist/esm/arknote/index.js +2 -2
  35. package/dist/esm/forfeit.js +1 -1
  36. package/dist/esm/identity/singleKey.js +9 -9
  37. package/dist/esm/index.js +14 -10
  38. package/dist/esm/{bip322 → intent}/index.js +32 -54
  39. package/dist/esm/musig2/index.js +1 -1
  40. package/dist/esm/musig2/nonces.js +3 -0
  41. package/dist/esm/providers/ark.js +76 -45
  42. package/dist/esm/providers/errors.js +54 -0
  43. package/dist/esm/providers/expoArk.js +15 -137
  44. package/dist/esm/providers/expoIndexer.js +22 -78
  45. package/dist/esm/providers/expoUtils.js +87 -0
  46. package/dist/esm/providers/onchain.js +19 -20
  47. package/dist/esm/repositories/walletRepository.js +64 -28
  48. package/dist/esm/script/base.js +12 -4
  49. package/dist/esm/script/tapscript.js +1 -2
  50. package/dist/esm/script/vhtlc.js +1 -1
  51. package/dist/esm/tree/signingSession.js +45 -12
  52. package/dist/esm/tree/txTree.js +3 -4
  53. package/dist/esm/tree/validation.js +2 -3
  54. package/dist/esm/utils/arkTransaction.js +97 -8
  55. package/dist/esm/utils/transaction.js +24 -0
  56. package/dist/esm/utils/unknownFields.js +3 -3
  57. package/dist/esm/wallet/onchain.js +3 -4
  58. package/dist/esm/wallet/serviceWorker/response.js +32 -0
  59. package/dist/esm/wallet/serviceWorker/utils.js +1 -0
  60. package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
  61. package/dist/esm/wallet/serviceWorker/worker.js +48 -29
  62. package/dist/esm/wallet/unroll.js +5 -7
  63. package/dist/esm/wallet/utils.js +8 -0
  64. package/dist/esm/wallet/vtxo-manager.js +317 -0
  65. package/dist/esm/wallet/wallet.js +92 -119
  66. package/dist/types/arknote/index.d.ts +1 -1
  67. package/dist/types/forfeit.d.ts +2 -2
  68. package/dist/types/identity/index.d.ts +2 -2
  69. package/dist/types/identity/singleKey.d.ts +2 -2
  70. package/dist/types/index.d.ts +9 -7
  71. package/dist/types/intent/index.d.ts +41 -0
  72. package/dist/types/musig2/index.d.ts +1 -1
  73. package/dist/types/musig2/nonces.d.ts +1 -0
  74. package/dist/types/providers/ark.d.ts +62 -26
  75. package/dist/types/providers/errors.d.ts +13 -0
  76. package/dist/types/providers/expoIndexer.d.ts +2 -10
  77. package/dist/types/providers/expoUtils.d.ts +18 -0
  78. package/dist/types/providers/indexer.d.ts +1 -9
  79. package/dist/types/providers/onchain.d.ts +6 -2
  80. package/dist/types/repositories/walletRepository.d.ts +9 -5
  81. package/dist/types/script/base.d.ts +5 -2
  82. package/dist/types/tree/signingSession.d.ts +16 -11
  83. package/dist/types/utils/anchor.d.ts +2 -2
  84. package/dist/types/utils/arkTransaction.d.ts +12 -4
  85. package/dist/types/utils/transaction.d.ts +13 -0
  86. package/dist/types/utils/unknownFields.d.ts +4 -4
  87. package/dist/types/wallet/index.d.ts +6 -4
  88. package/dist/types/wallet/onchain.d.ts +1 -1
  89. package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
  90. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
  91. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  92. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
  93. package/dist/types/wallet/unroll.d.ts +1 -1
  94. package/dist/types/wallet/utils.d.ts +2 -1
  95. package/dist/types/wallet/vtxo-manager.d.ts +179 -0
  96. package/dist/types/wallet/wallet.d.ts +8 -4
  97. package/package.json +1 -2
  98. package/dist/cjs/bip322/errors.js +0 -13
  99. package/dist/esm/bip322/errors.js +0 -9
  100. package/dist/types/bip322/errors.d.ts +0 -6
  101. package/dist/types/bip322/index.d.ts +0 -57
@@ -1,8 +1,8 @@
1
1
  import { base64, hex } from "@scure/base";
2
2
  import * as bip68 from "bip68";
3
- import { Address, OutScript, tapLeafHash } from "@scure/btc-signer/payment.js";
4
- import { SigHash, Transaction } from "@scure/btc-signer/transaction.js";
5
- import { TaprootControlBlock, } from "@scure/btc-signer/psbt.js";
3
+ import { tapLeafHash } from "@scure/btc-signer/payment.js";
4
+ import { SigHash, Transaction, Address, OutScript, } from "@scure/btc-signer";
5
+ import { sha256 } from "@scure/btc-signer/utils.js";
6
6
  import { vtxosToTxs } from '../utils/transactionHistory.js';
7
7
  import { ArkAddress } from '../script/address.js';
8
8
  import { DefaultVtxo } from '../script/default.js';
@@ -12,18 +12,19 @@ import { SettlementEventType, RestArkProvider, } from '../providers/ark.js';
12
12
  import { buildForfeitTx } from '../forfeit.js';
13
13
  import { validateConnectorsTxGraph, validateVtxoTxGraph, } from '../tree/validation.js';
14
14
  import { isRecoverable, isSpendable, isSubdust, TxType, } from './index.js';
15
- import { sha256, sha256x2 } from "@scure/btc-signer/utils.js";
16
15
  import { VtxoScript } from '../script/base.js';
17
16
  import { CSVMultisigTapscript } from '../script/tapscript.js';
18
17
  import { buildOffchainTx, hasBoardingTxExpired } from '../utils/arkTransaction.js';
18
+ import { DEFAULT_RENEWAL_CONFIG } from './vtxo-manager.js';
19
19
  import { ArkNote } from '../arknote/index.js';
20
- import { BIP322 } from '../bip322/index.js';
20
+ import { Intent } from '../intent/index.js';
21
21
  import { RestIndexerProvider } from '../providers/indexer.js';
22
22
  import { TxTree } from '../tree/txTree.js';
23
+ import { ConditionWitness, VtxoTaprootTree } from '../utils/unknownFields.js';
23
24
  import { InMemoryStorageAdapter } from '../storage/inMemory.js';
24
25
  import { WalletRepositoryImpl, } from '../repositories/walletRepository.js';
25
26
  import { ContractRepositoryImpl, } from '../repositories/contractRepository.js';
26
- import { extendVirtualCoin } from './utils.js';
27
+ import { extendCoin, extendVirtualCoin } from './utils.js';
27
28
  /**
28
29
  * Main wallet implementation for Bitcoin transactions with Ark protocol support.
29
30
  * The wallet does not store any data locally and relies on Ark and onchain
@@ -58,7 +59,7 @@ import { extendVirtualCoin } from './utils.js';
58
59
  * ```
59
60
  */
60
61
  export class Wallet {
61
- constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
62
+ constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
62
63
  this.identity = identity;
63
64
  this.network = network;
64
65
  this.networkName = networkName;
@@ -70,9 +71,15 @@ export class Wallet {
70
71
  this.boardingTapscript = boardingTapscript;
71
72
  this.serverUnrollScript = serverUnrollScript;
72
73
  this.forfeitOutputScript = forfeitOutputScript;
74
+ this.forfeitPubkey = forfeitPubkey;
73
75
  this.dustAmount = dustAmount;
74
76
  this.walletRepository = walletRepository;
75
77
  this.contractRepository = contractRepository;
78
+ this.renewalConfig = {
79
+ enabled: renewalConfig?.enabled ?? false,
80
+ ...DEFAULT_RENEWAL_CONFIG,
81
+ ...renewalConfig,
82
+ };
76
83
  }
77
84
  static async create(config) {
78
85
  const pubkey = await config.identity.xOnlyPublicKey();
@@ -102,6 +109,7 @@ export class Wallet {
102
109
  const esploraUrl = config.esploraUrl || ESPLORA_URL[info.network];
103
110
  // Use provided onchainProvider instance or create a new one
104
111
  const onchainProvider = config.onchainProvider || new EsploraProvider(esploraUrl);
112
+ // Generate timelocks
105
113
  const exitTimelock = {
106
114
  value: info.unilateralExitDelay,
107
115
  type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
@@ -127,21 +135,22 @@ export class Wallet {
127
135
  // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
128
136
  let serverUnrollScript;
129
137
  try {
130
- const raw = hex.decode(info.checkpointExitClosure);
138
+ const raw = hex.decode(info.checkpointTapscript);
131
139
  serverUnrollScript = CSVMultisigTapscript.decode(raw);
132
140
  }
133
141
  catch (e) {
134
- throw new Error("Invalid checkpointExitClosure from server");
142
+ throw new Error("Invalid checkpointTapscript from server");
135
143
  }
136
144
  // parse the server forfeit address
137
145
  // server is expecting funds to be sent to this address
146
+ const forfeitPubkey = hex.decode(info.forfeitPubkey).slice(1);
138
147
  const forfeitAddress = Address(network).decode(info.forfeitAddress);
139
148
  const forfeitOutputScript = OutScript.encode(forfeitAddress);
140
149
  // Set up storage and repositories
141
150
  const storage = config.storage || new InMemoryStorageAdapter();
142
151
  const walletRepository = new WalletRepositoryImpl(storage);
143
152
  const contractRepository = new ContractRepositoryImpl(storage);
144
- return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
153
+ return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, info.dust, walletRepository, contractRepository, config.renewalConfig);
145
154
  }
146
155
  get arkAddress() {
147
156
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -324,15 +333,12 @@ export class Wallet {
324
333
  async getBoardingUtxos() {
325
334
  const boardingAddress = await this.getBoardingAddress();
326
335
  const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
327
- const encodedBoardingTapscript = this.boardingTapscript.encode();
328
- const forfeit = this.boardingTapscript.forfeit();
329
- const exit = this.boardingTapscript.exit();
330
- return boardingUtxos.map((utxo) => ({
331
- ...utxo,
332
- forfeitTapLeafScript: forfeit,
333
- intentTapLeafScript: exit,
334
- tapTree: encodedBoardingTapscript,
335
- }));
336
+ const utxos = boardingUtxos.map((utxo) => {
337
+ return extendCoin(this, utxo);
338
+ });
339
+ // Save boardingUtxos using unified repository
340
+ await this.walletRepository.saveUtxos(boardingAddress, utxos);
341
+ return utxos;
336
342
  }
337
343
  async sendBitcoin(params) {
338
344
  if (params.amount <= 0) {
@@ -453,7 +459,7 @@ export class Wallet {
453
459
  const signingPublicKeys = [];
454
460
  if (hasOffchainOutputs) {
455
461
  session = this.identity.signerSession();
456
- signingPublicKeys.push(hex.encode(session.getPublicKey()));
462
+ signingPublicKeys.push(hex.encode(await session.getPublicKey()));
457
463
  }
458
464
  const [intent, deleteIntent] = await Promise.all([
459
465
  this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
@@ -469,8 +475,8 @@ export class Wallet {
469
475
  ...params.inputs.map((input) => `${input.txid}:${input.vout}`),
470
476
  ];
471
477
  const settlementStream = this.arkProvider.getEventStream(abortController.signal, topics);
472
- // roundId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
473
- let roundId;
478
+ // batchId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
479
+ let batchId;
474
480
  let sweepTapTreeRoot;
475
481
  const vtxoChunks = [];
476
482
  const connectorsChunks = [];
@@ -483,30 +489,26 @@ export class Wallet {
483
489
  switch (event.type) {
484
490
  // the settlement failed
485
491
  case SettlementEventType.BatchFailed:
486
- // fail if the roundId is the one joined
487
- if (event.id === roundId) {
488
- throw new Error(event.reason);
489
- }
490
- break;
492
+ throw new Error(event.reason);
491
493
  case SettlementEventType.BatchStarted:
492
494
  if (step !== undefined) {
493
495
  continue;
494
496
  }
495
- const res = await this.handleBatchStartedEvent(event, intentId, this.arkServerPublicKey, this.forfeitOutputScript);
497
+ const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
496
498
  if (!res.skip) {
497
499
  step = event.type;
498
500
  sweepTapTreeRoot = res.sweepTapTreeRoot;
499
- roundId = res.roundId;
501
+ batchId = res.roundId;
500
502
  if (!hasOffchainOutputs) {
501
503
  // if there are no offchain outputs, we don't have to handle musig2 tree signatures
502
504
  // we can directly advance to the finalization step
503
- step = SettlementEventType.TreeNoncesAggregated;
505
+ step = SettlementEventType.TreeNonces;
504
506
  }
505
507
  }
506
508
  break;
507
509
  case SettlementEventType.TreeTx:
508
510
  if (step !== SettlementEventType.BatchStarted &&
509
- step !== SettlementEventType.TreeNoncesAggregated) {
511
+ step !== SettlementEventType.TreeNonces) {
510
512
  continue;
511
513
  }
512
514
  // index 0 = vtxo tree
@@ -522,7 +524,7 @@ export class Wallet {
522
524
  }
523
525
  break;
524
526
  case SettlementEventType.TreeSignature:
525
- if (step !== SettlementEventType.TreeNoncesAggregated) {
527
+ if (step !== SettlementEventType.TreeNonces) {
526
528
  continue;
527
529
  }
528
530
  if (!hasOffchainOutputs) {
@@ -564,7 +566,7 @@ export class Wallet {
564
566
  break;
565
567
  // the musig2 nonces of the vtxo tree transactions are generated
566
568
  // the server expects now the partial musig2 signatures
567
- case SettlementEventType.TreeNoncesAggregated:
569
+ case SettlementEventType.TreeNonces:
568
570
  if (step !== SettlementEventType.TreeSigningStarted) {
569
571
  continue;
570
572
  }
@@ -572,14 +574,18 @@ export class Wallet {
572
574
  if (!session) {
573
575
  throw new Error("Signing session not set");
574
576
  }
575
- await this.handleSettlementSigningNoncesGeneratedEvent(event, session);
577
+ const signed = await this.handleSettlementTreeNoncesEvent(event, session);
578
+ if (signed) {
579
+ step = event.type;
580
+ }
581
+ break;
576
582
  }
577
583
  step = event.type;
578
584
  break;
579
585
  // the vtxo tree is signed, craft, sign and submit forfeit transactions
580
586
  // if any boarding utxos are involved, the settlement tx is also signed
581
587
  case SettlementEventType.BatchFinalization:
582
- if (step !== SettlementEventType.TreeNoncesAggregated) {
588
+ if (step !== SettlementEventType.TreeNonces) {
583
589
  continue;
584
590
  }
585
591
  if (!this.forfeitOutputScript) {
@@ -597,8 +603,10 @@ export class Wallet {
597
603
  if (step !== SettlementEventType.BatchFinalization) {
598
604
  continue;
599
605
  }
600
- abortController.abort();
601
- return event.commitmentTxid;
606
+ if (event.id === batchId) {
607
+ abortController.abort();
608
+ return event.commitmentTxid;
609
+ }
602
610
  }
603
611
  }
604
612
  }
@@ -677,10 +685,10 @@ export class Wallet {
677
685
  };
678
686
  return stopFunc;
679
687
  }
680
- async handleBatchStartedEvent(event, intentId, serverPubKey, forfeitOutputScript) {
688
+ async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
681
689
  const utf8IntentId = new TextEncoder().encode(intentId);
682
690
  const intentIdHash = sha256(utf8IntentId);
683
- const intentIdHashStr = hex.encode(new Uint8Array(intentIdHash));
691
+ const intentIdHashStr = hex.encode(intentIdHash);
684
692
  let skip = true;
685
693
  // check if our intent ID hash matches any in the event
686
694
  for (const idHash of event.intentIdHashes) {
@@ -700,7 +708,7 @@ export class Wallet {
700
708
  value: event.batchExpiry,
701
709
  type: event.batchExpiry >= 512n ? "seconds" : "blocks",
702
710
  },
703
- pubkeys: [serverPubKey],
711
+ pubkeys: [forfeitPubKey],
704
712
  }).script;
705
713
  const sweepTapTreeRoot = tapLeafHash(sweepTapscript);
706
714
  return {
@@ -721,12 +729,19 @@ export class Wallet {
721
729
  throw new Error("Shared output not found");
722
730
  }
723
731
  session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
724
- await this.arkProvider.submitTreeNonces(event.id, hex.encode(session.getPublicKey()), session.getNonces());
732
+ const pubkey = hex.encode(await session.getPublicKey());
733
+ const nonces = await session.getNonces();
734
+ await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
725
735
  }
726
- async handleSettlementSigningNoncesGeneratedEvent(event, session) {
727
- session.setAggregatedNonces(event.treeNonces);
728
- const signatures = session.sign();
729
- await this.arkProvider.submitTreeSignatures(event.id, hex.encode(session.getPublicKey()), signatures);
736
+ async handleSettlementTreeNoncesEvent(event, session) {
737
+ const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
738
+ // wait to receive and aggregate all nonces before sending signatures
739
+ if (!hasAllNonces)
740
+ return false;
741
+ const signatures = await session.sign();
742
+ const pubkey = hex.encode(await session.getPublicKey());
743
+ await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
744
+ return true;
730
745
  }
731
746
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
732
747
  // the signed forfeits transactions to submit
@@ -741,8 +756,6 @@ export class Wallet {
741
756
  const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
742
757
  // boarding utxo, we need to sign the settlement tx
743
758
  if (!vtxo) {
744
- hasBoardingUtxos = true;
745
- const inputIndexes = [];
746
759
  for (let i = 0; i < settlementPsbt.inputsLength; i++) {
747
760
  const settlementInput = settlementPsbt.getInput(i);
748
761
  if (!settlementInput.txid ||
@@ -758,9 +771,12 @@ export class Wallet {
758
771
  settlementPsbt.updateInput(i, {
759
772
  tapLeafScript: [input.forfeitTapLeafScript],
760
773
  });
761
- inputIndexes.push(i);
774
+ settlementPsbt = await this.identity.sign(settlementPsbt, [
775
+ i,
776
+ ]);
777
+ hasBoardingUtxos = true;
778
+ break;
762
779
  }
763
- settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
764
780
  continue;
765
781
  }
766
782
  if (isRecoverable(vtxo) || isSubdust(vtxo, this.dustAmount)) {
@@ -774,7 +790,7 @@ export class Wallet {
774
790
  throw new Error("not enough connectors received");
775
791
  }
776
792
  const connectorLeaf = connectorsLeaves[connectorIndex];
777
- const connectorTxId = hex.encode(sha256x2(connectorLeaf.toBytes(true)).reverse());
793
+ const connectorTxId = connectorLeaf.id;
778
794
  const connectorOutput = connectorLeaf.getOutput(0);
779
795
  if (!connectorOutput) {
780
796
  throw new Error("connector output not found");
@@ -815,111 +831,68 @@ export class Wallet {
815
831
  : undefined);
816
832
  }
817
833
  }
818
- async makeRegisterIntentSignature(bip322Inputs, outputs, onchainOutputsIndexes, cosignerPubKeys) {
834
+ async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
819
835
  const nowSeconds = Math.floor(Date.now() / 1000);
820
- const { inputs, inputTapTrees, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
836
+ const inputs = this.prepareIntentProofInputs(coins);
821
837
  const message = {
822
838
  type: "register",
823
- input_tap_trees: inputTapTrees,
824
839
  onchain_output_indexes: onchainOutputsIndexes,
825
840
  valid_at: nowSeconds,
826
841
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
827
842
  cosigners_public_keys: cosignerPubKeys,
828
843
  };
829
844
  const encodedMessage = JSON.stringify(message, null, 0);
830
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer, outputs);
845
+ const proof = Intent.create(encodedMessage, inputs, outputs);
846
+ const signedProof = await this.identity.sign(proof);
831
847
  return {
832
- signature,
848
+ proof: base64.encode(signedProof.toPSBT()),
833
849
  message: encodedMessage,
834
850
  };
835
851
  }
836
- async makeDeleteIntentSignature(bip322Inputs) {
852
+ async makeDeleteIntentSignature(coins) {
837
853
  const nowSeconds = Math.floor(Date.now() / 1000);
838
- const { inputs, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
854
+ const inputs = this.prepareIntentProofInputs(coins);
839
855
  const message = {
840
856
  type: "delete",
841
857
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
842
858
  };
843
859
  const encodedMessage = JSON.stringify(message, null, 0);
844
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer);
860
+ const proof = Intent.create(encodedMessage, inputs, []);
861
+ const signedProof = await this.identity.sign(proof);
845
862
  return {
846
- signature,
863
+ proof: base64.encode(signedProof.toPSBT()),
847
864
  message: encodedMessage,
848
865
  };
849
866
  }
850
- prepareBIP322Inputs(bip322Inputs) {
867
+ prepareIntentProofInputs(coins) {
851
868
  const inputs = [];
852
- const inputTapTrees = [];
853
- const inputExtraWitnesses = [];
854
- for (const bip322Input of bip322Inputs) {
855
- const vtxoScript = VtxoScript.decode(bip322Input.tapTree);
856
- const sequence = getSequence(bip322Input);
869
+ for (const input of coins) {
870
+ const vtxoScript = VtxoScript.decode(input.tapTree);
871
+ const sequence = getSequence(input);
872
+ const unknown = [VtxoTaprootTree.encode(input.tapTree)];
873
+ if (input.extraWitness) {
874
+ unknown.push(ConditionWitness.encode(input.extraWitness));
875
+ }
857
876
  inputs.push({
858
- txid: hex.decode(bip322Input.txid),
859
- index: bip322Input.vout,
877
+ txid: hex.decode(input.txid),
878
+ index: input.vout,
860
879
  witnessUtxo: {
861
- amount: BigInt(bip322Input.value),
880
+ amount: BigInt(input.value),
862
881
  script: vtxoScript.pkScript,
863
882
  },
864
883
  sequence,
865
- tapLeafScript: [bip322Input.intentTapLeafScript],
884
+ tapLeafScript: [input.intentTapLeafScript],
885
+ unknown,
866
886
  });
867
- inputTapTrees.push(hex.encode(bip322Input.tapTree));
868
- inputExtraWitnesses.push(bip322Input.extraWitness || []);
869
887
  }
870
- return {
871
- inputs,
872
- inputTapTrees,
873
- finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
874
- };
875
- }
876
- async makeBIP322Signature(message, inputs, finalizer, outputs) {
877
- const proof = BIP322.create(message, inputs, outputs);
878
- const signedProof = await this.identity.sign(proof);
879
- return BIP322.signature(signedProof, finalizer);
888
+ return inputs;
880
889
  }
881
890
  }
882
891
  Wallet.MIN_FEE_RATE = 1; // sats/vbyte
883
- function finalizeWithExtraWitnesses(inputExtraWitnesses) {
884
- return function (tx) {
885
- for (let i = 0; i < tx.inputsLength; i++) {
886
- try {
887
- tx.finalizeIdx(i);
888
- }
889
- catch (e) {
890
- // handle empty witness error
891
- if (e instanceof Error &&
892
- e.message.includes("finalize/taproot: empty witness")) {
893
- const tapLeaves = tx.getInput(i).tapLeafScript;
894
- if (!tapLeaves || tapLeaves.length <= 0)
895
- throw e;
896
- const [cb, s] = tapLeaves[0];
897
- const script = s.slice(0, -1);
898
- tx.updateInput(i, {
899
- finalScriptWitness: [
900
- script,
901
- TaprootControlBlock.encode(cb),
902
- ],
903
- });
904
- }
905
- }
906
- const finalScriptWitness = tx.getInput(i).finalScriptWitness;
907
- if (!finalScriptWitness)
908
- throw new Error("input not finalized");
909
- // input 0 and 1 spend the same pkscript
910
- const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
911
- if (extra && extra.length > 0) {
912
- tx.updateInput(i, {
913
- finalScriptWitness: [...extra, ...finalScriptWitness],
914
- });
915
- }
916
- }
917
- };
918
- }
919
- function getSequence(bip322Input) {
892
+ function getSequence(coin) {
920
893
  let sequence = undefined;
921
894
  try {
922
- const scriptWithLeafVersion = bip322Input.intentTapLeafScript[1];
895
+ const scriptWithLeafVersion = coin.intentTapLeafScript[1];
923
896
  const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
924
897
  const params = CSVMultisigTapscript.decode(script).params;
925
898
  sequence = bip68.encode(params.timelock.type === "blocks"
@@ -1,5 +1,5 @@
1
- import { TapLeafScript, VtxoScript } from "../script/base";
2
1
  import { Bytes } from "@scure/btc-signer/utils.js";
2
+ import { TapLeafScript, VtxoScript } from "../script/base";
3
3
  import { ExtendedCoin, Status } from "../wallet";
4
4
  /**
5
5
  * ArkNotes are special virtual coins in the Ark protocol that can be created
@@ -1,3 +1,3 @@
1
- import { Transaction } from "@scure/btc-signer/transaction.js";
2
- import { TransactionInputUpdate } from "@scure/btc-signer/psbt";
1
+ import { Transaction } from "./utils/transaction";
2
+ import { TransactionInputUpdate } from "@scure/btc-signer/psbt.js";
3
3
  export declare function buildForfeitTx(inputs: TransactionInputUpdate[], forfeitPkScript: Uint8Array, txLocktime?: number): Transaction;
@@ -1,10 +1,10 @@
1
- import { Transaction } from "@scure/btc-signer/transaction.js";
1
+ import { Transaction } from "../utils/transaction";
2
2
  import { SignerSession } from "../tree/signingSession";
3
3
  export interface Identity {
4
4
  signerSession(): SignerSession;
5
5
  xOnlyPublicKey(): Promise<Uint8Array>;
6
6
  compressedPublicKey(): Promise<Uint8Array>;
7
- signMessage(message: string): Promise<Uint8Array>;
7
+ signMessage(message: Uint8Array, signatureType: "schnorr" | "ecdsa"): Promise<Uint8Array>;
8
8
  sign(tx: Transaction, inputIndexes?: number[]): Promise<Transaction>;
9
9
  }
10
10
  export * from "./singleKey";
@@ -1,5 +1,5 @@
1
- import { Transaction } from "@scure/btc-signer/transaction.js";
2
1
  import { Identity } from ".";
2
+ import { Transaction } from "../utils/transaction";
3
3
  import { SignerSession } from "../tree/signingSession";
4
4
  /**
5
5
  * In-memory single key implementation for Bitcoin transaction signing.
@@ -35,5 +35,5 @@ export declare class SingleKey implements Identity {
35
35
  compressedPublicKey(): Promise<Uint8Array>;
36
36
  xOnlyPublicKey(): Promise<Uint8Array>;
37
37
  signerSession(): SignerSession;
38
- signMessage(message: string): Promise<Uint8Array>;
38
+ signMessage(message: Uint8Array, signatureType?: "schnorr" | "ecdsa"): Promise<Uint8Array>;
39
39
  }
@@ -1,15 +1,16 @@
1
- import { Transaction } from "@scure/btc-signer/transaction.js";
1
+ import { Transaction } from "./utils/transaction";
2
2
  import { SingleKey } from "./identity/singleKey";
3
3
  import { Identity } from "./identity";
4
4
  import { ArkAddress } from "./script/address";
5
5
  import { VHTLC } from "./script/vhtlc";
6
6
  import { DefaultVtxo } from "./script/default";
7
- import { VtxoScript, EncodedVtxoScript, TapLeafScript } from "./script/base";
7
+ import { VtxoScript, EncodedVtxoScript, TapLeafScript, TapTreeCoder } from "./script/base";
8
8
  import { TxType, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, GetVtxosFilter, TapLeaves } from "./wallet";
9
9
  import { Wallet, waitForIncomingFunds, IncomingFunds } from "./wallet/wallet";
10
10
  import { TxTree, TxTreeNode } from "./tree/txTree";
11
11
  import { SignerSession, TreeNonces, TreePartialSigs } from "./tree/signingSession";
12
12
  import { Ramps } from "./wallet/ramps";
13
+ import { VtxoManager } from "./wallet/vtxo-manager";
13
14
  import { ServiceWorkerWallet } from "./wallet/serviceWorker/wallet";
14
15
  import { OnchainWallet } from "./wallet/onchain";
15
16
  import { setupServiceWorker } from "./wallet/serviceWorker/utils";
@@ -17,11 +18,11 @@ import { Worker } from "./wallet/serviceWorker/worker";
17
18
  import { Request } from "./wallet/serviceWorker/request";
18
19
  import { Response } from "./wallet/serviceWorker/response";
19
20
  import { ESPLORA_URL, EsploraProvider, OnchainProvider, ExplorerTransaction } from "./providers/onchain";
20
- import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, Intent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesAggregatedEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, MarketHour } from "./providers/ark";
21
+ import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, SignedIntent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
21
22
  import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
22
- import { hasBoardingTxExpired, buildOffchainTx, ArkTxInput, OffchainTx } from "./utils/arkTransaction";
23
+ import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, ArkTxInput, OffchainTx } from "./utils/arkTransaction";
23
24
  import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry } from "./utils/unknownFields";
24
- import { BIP322 } from "./bip322";
25
+ import { Intent } from "./intent";
25
26
  import { ArkNote } from "./arknote";
26
27
  import { networks, Network, NetworkName } from "./networks";
27
28
  import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent } from "./providers/indexer";
@@ -31,5 +32,6 @@ import { AnchorBumper, P2A } from "./utils/anchor";
31
32
  import { Unroll } from "./wallet/unroll";
32
33
  import { WalletRepositoryImpl } from "./repositories/walletRepository";
33
34
  import { ContractRepositoryImpl } from "./repositories/contractRepository";
34
- export { Wallet, SingleKey, OnchainWallet, Ramps, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, BIP322, TxTree, P2A, Unroll, Transaction, };
35
- export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo, Intent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesAggregatedEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, MarketHour, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
35
+ import { ArkError, maybeArkError } from "./providers/errors";
36
+ export { Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, TapTreeCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, ArkError, maybeArkError, };
37
+ export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo, SignedIntent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
@@ -0,0 +1,41 @@
1
+ import { TransactionInput, TransactionOutput } from "@scure/btc-signer/psbt.js";
2
+ import { Transaction } from "../utils/transaction";
3
+ /**
4
+ * Intent proof implementation for Bitcoin message signing.
5
+ *
6
+ * Intent proof defines a standard for signing Bitcoin messages as well as proving
7
+ * ownership of coins. This namespace provides utilities for creating and
8
+ * validating Intent proof.
9
+ *
10
+ * it is greatly inspired by BIP322.
11
+ * @see https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Create a Intent proof
16
+ * const proof = Intent.create(
17
+ * "Hello Bitcoin!",
18
+ * [input],
19
+ * [output]
20
+ * );
21
+ *
22
+ * // Sign the proof
23
+ * const signedProof = await identity.sign(proof);
24
+ *
25
+ */
26
+ export declare namespace Intent {
27
+ type Proof = Transaction;
28
+ /**
29
+ * Creates a new Intent proof unsigned transaction.
30
+ *
31
+ * This function constructs a special transaction that can be signed to prove
32
+ * ownership of VTXOs and UTXOs. The proof includes the message to be
33
+ * signed and the inputs/outputs that demonstrate ownership.
34
+ *
35
+ * @param message - The Intent message to be signed
36
+ * @param inputs - Array of transaction inputs to prove ownership of
37
+ * @param outputs - Optional array of transaction outputs
38
+ * @returns An unsigned Intent proof transaction
39
+ */
40
+ function create(message: string, inputs: TransactionInput[], outputs?: TransactionOutput[]): Proof;
41
+ }
@@ -1,4 +1,4 @@
1
1
  export type { Nonces } from "./nonces";
2
- export { generateNonces } from "./nonces";
2
+ export { generateNonces, aggregateNonces } from "./nonces";
3
3
  export { PartialSig, sign } from "./sign";
4
4
  export { aggregateKeys } from "./keys";
@@ -11,3 +11,4 @@ export type Nonces = {
11
11
  * Generates a pair of public and secret nonces for MuSig2 signing
12
12
  */
13
13
  export declare function generateNonces(publicKey: Uint8Array): Nonces;
14
+ export declare function aggregateNonces(pubNonces: Uint8Array[]): Uint8Array;