@arkade-os/sdk 0.3.0-alpha.7 → 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 (109) hide show
  1. package/README.md +99 -14
  2. package/dist/cjs/adapters/expo.js +8 -0
  3. package/dist/cjs/arknote/index.js +3 -3
  4. package/dist/cjs/forfeit.js +2 -2
  5. package/dist/cjs/identity/singleKey.js +8 -8
  6. package/dist/cjs/index.js +14 -5
  7. package/dist/cjs/{bip322 → intent}/index.js +38 -61
  8. package/dist/cjs/musig2/index.js +2 -1
  9. package/dist/cjs/musig2/nonces.js +4 -0
  10. package/dist/cjs/providers/ark.js +76 -45
  11. package/dist/cjs/providers/errors.js +59 -0
  12. package/dist/cjs/providers/expoArk.js +82 -0
  13. package/dist/cjs/providers/expoIndexer.js +105 -0
  14. package/dist/cjs/providers/expoUtils.js +124 -0
  15. package/dist/cjs/providers/indexer.js +3 -1
  16. package/dist/cjs/providers/onchain.js +19 -20
  17. package/dist/cjs/repositories/walletRepository.js +64 -28
  18. package/dist/cjs/script/base.js +15 -7
  19. package/dist/cjs/script/tapscript.js +20 -21
  20. package/dist/cjs/script/vhtlc.js +2 -2
  21. package/dist/cjs/tree/signingSession.js +44 -11
  22. package/dist/cjs/tree/txTree.js +3 -4
  23. package/dist/cjs/tree/validation.js +2 -3
  24. package/dist/cjs/utils/arkTransaction.js +118 -15
  25. package/dist/cjs/utils/transaction.js +28 -0
  26. package/dist/cjs/utils/unknownFields.js +7 -7
  27. package/dist/cjs/wallet/index.js +1 -1
  28. package/dist/cjs/wallet/onchain.js +6 -7
  29. package/dist/cjs/wallet/serviceWorker/response.js +32 -0
  30. package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
  32. package/dist/cjs/wallet/serviceWorker/worker.js +48 -32
  33. package/dist/cjs/wallet/unroll.js +7 -9
  34. package/dist/cjs/wallet/utils.js +20 -0
  35. package/dist/cjs/wallet/vtxo-manager.js +323 -0
  36. package/dist/cjs/wallet/wallet.js +165 -174
  37. package/dist/esm/adapters/expo.js +3 -0
  38. package/dist/esm/arknote/index.js +2 -2
  39. package/dist/esm/forfeit.js +1 -1
  40. package/dist/esm/identity/singleKey.js +9 -9
  41. package/dist/esm/index.js +14 -10
  42. package/dist/esm/{bip322 → intent}/index.js +32 -54
  43. package/dist/esm/musig2/index.js +1 -1
  44. package/dist/esm/musig2/nonces.js +3 -0
  45. package/dist/esm/providers/ark.js +76 -45
  46. package/dist/esm/providers/errors.js +54 -0
  47. package/dist/esm/providers/expoArk.js +78 -0
  48. package/dist/esm/providers/expoIndexer.js +101 -0
  49. package/dist/esm/providers/expoUtils.js +87 -0
  50. package/dist/esm/providers/indexer.js +3 -1
  51. package/dist/esm/providers/onchain.js +19 -20
  52. package/dist/esm/repositories/walletRepository.js +64 -28
  53. package/dist/esm/script/base.js +12 -4
  54. package/dist/esm/script/tapscript.js +1 -2
  55. package/dist/esm/script/vhtlc.js +1 -1
  56. package/dist/esm/tree/signingSession.js +45 -12
  57. package/dist/esm/tree/txTree.js +3 -4
  58. package/dist/esm/tree/validation.js +2 -3
  59. package/dist/esm/utils/arkTransaction.js +110 -9
  60. package/dist/esm/utils/transaction.js +24 -0
  61. package/dist/esm/utils/unknownFields.js +3 -3
  62. package/dist/esm/wallet/index.js +1 -1
  63. package/dist/esm/wallet/onchain.js +3 -4
  64. package/dist/esm/wallet/serviceWorker/response.js +32 -0
  65. package/dist/esm/wallet/serviceWorker/utils.js +1 -8
  66. package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
  67. package/dist/esm/wallet/serviceWorker/worker.js +49 -33
  68. package/dist/esm/wallet/unroll.js +5 -7
  69. package/dist/esm/wallet/utils.js +16 -0
  70. package/dist/esm/wallet/vtxo-manager.js +317 -0
  71. package/dist/esm/wallet/wallet.js +159 -168
  72. package/dist/types/adapters/expo.d.ts +4 -0
  73. package/dist/types/arknote/index.d.ts +1 -1
  74. package/dist/types/forfeit.d.ts +2 -2
  75. package/dist/types/identity/index.d.ts +2 -2
  76. package/dist/types/identity/singleKey.d.ts +2 -2
  77. package/dist/types/index.d.ts +11 -9
  78. package/dist/types/intent/index.d.ts +41 -0
  79. package/dist/types/musig2/index.d.ts +1 -1
  80. package/dist/types/musig2/nonces.d.ts +1 -0
  81. package/dist/types/providers/ark.d.ts +197 -27
  82. package/dist/types/providers/errors.d.ts +13 -0
  83. package/dist/types/providers/expoArk.d.ts +22 -0
  84. package/dist/types/providers/expoIndexer.d.ts +18 -0
  85. package/dist/types/providers/expoUtils.d.ts +18 -0
  86. package/dist/types/providers/indexer.d.ts +8 -8
  87. package/dist/types/providers/onchain.d.ts +6 -2
  88. package/dist/types/repositories/walletRepository.d.ts +9 -5
  89. package/dist/types/script/base.d.ts +5 -2
  90. package/dist/types/tree/signingSession.d.ts +16 -11
  91. package/dist/types/utils/anchor.d.ts +2 -2
  92. package/dist/types/utils/arkTransaction.d.ts +15 -5
  93. package/dist/types/utils/transaction.d.ts +13 -0
  94. package/dist/types/utils/unknownFields.d.ts +4 -4
  95. package/dist/types/wallet/index.d.ts +47 -7
  96. package/dist/types/wallet/onchain.d.ts +1 -1
  97. package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
  98. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
  99. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  100. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
  101. package/dist/types/wallet/unroll.d.ts +1 -1
  102. package/dist/types/wallet/utils.d.ts +3 -0
  103. package/dist/types/wallet/vtxo-manager.d.ts +179 -0
  104. package/dist/types/wallet/wallet.d.ts +17 -5
  105. package/package.json +11 -3
  106. package/dist/cjs/bip322/errors.js +0 -13
  107. package/dist/esm/bip322/errors.js +0 -9
  108. package/dist/types/bip322/errors.d.ts +0 -6
  109. package/dist/types/bip322/index.d.ts +0 -57
@@ -38,8 +38,8 @@ exports.waitForIncomingFunds = waitForIncomingFunds;
38
38
  const base_1 = require("@scure/base");
39
39
  const bip68 = __importStar(require("bip68"));
40
40
  const payment_js_1 = require("@scure/btc-signer/payment.js");
41
- const transaction_js_1 = require("@scure/btc-signer/transaction.js");
42
- const psbt_js_1 = require("@scure/btc-signer/psbt.js");
41
+ const btc_signer_1 = require("@scure/btc-signer");
42
+ const utils_js_1 = require("@scure/btc-signer/utils.js");
43
43
  const transactionHistory_1 = require("../utils/transactionHistory");
44
44
  const address_1 = require("../script/address");
45
45
  const default_1 = require("../script/default");
@@ -49,18 +49,19 @@ const ark_1 = require("../providers/ark");
49
49
  const forfeit_1 = require("../forfeit");
50
50
  const validation_1 = require("../tree/validation");
51
51
  const _1 = require(".");
52
- const utils_js_1 = require("@scure/btc-signer/utils.js");
53
52
  const base_2 = require("../script/base");
54
53
  const tapscript_1 = require("../script/tapscript");
55
54
  const arkTransaction_1 = require("../utils/arkTransaction");
55
+ const vtxo_manager_1 = require("./vtxo-manager");
56
56
  const arknote_1 = require("../arknote");
57
- const bip322_1 = require("../bip322");
57
+ const intent_1 = require("../intent");
58
58
  const indexer_1 = require("../providers/indexer");
59
59
  const txTree_1 = require("../tree/txTree");
60
+ const unknownFields_1 = require("../utils/unknownFields");
60
61
  const inMemory_1 = require("../storage/inMemory");
61
62
  const walletRepository_1 = require("../repositories/walletRepository");
62
63
  const contractRepository_1 = require("../repositories/contractRepository");
63
- const utils_1 = require("./serviceWorker/utils");
64
+ const utils_1 = require("./utils");
64
65
  /**
65
66
  * Main wallet implementation for Bitcoin transactions with Ark protocol support.
66
67
  * The wallet does not store any data locally and relies on Ark and onchain
@@ -68,13 +69,21 @@ const utils_1 = require("./serviceWorker/utils");
68
69
  *
69
70
  * @example
70
71
  * ```typescript
71
- * // Create a wallet
72
+ * // Create a wallet with URL configuration
72
73
  * const wallet = await Wallet.create({
73
74
  * identity: SingleKey.fromHex('your_private_key'),
74
75
  * arkServerUrl: 'https://ark.example.com',
75
76
  * esploraUrl: 'https://mempool.space/api'
76
77
  * });
77
78
  *
79
+ * // Or with custom provider instances (e.g., for Expo/React Native)
80
+ * const wallet = await Wallet.create({
81
+ * identity: SingleKey.fromHex('your_private_key'),
82
+ * arkProvider: new ExpoArkProvider('https://ark.example.com'),
83
+ * indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
84
+ * esploraUrl: 'https://mempool.space/api'
85
+ * });
86
+ *
78
87
  * // Get addresses
79
88
  * const arkAddress = await wallet.getAddress();
80
89
  * const boardingAddress = await wallet.getBoardingAddress();
@@ -87,7 +96,7 @@ const utils_1 = require("./serviceWorker/utils");
87
96
  * ```
88
97
  */
89
98
  class Wallet {
90
- constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
99
+ constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
91
100
  this.identity = identity;
92
101
  this.network = network;
93
102
  this.networkName = networkName;
@@ -99,20 +108,45 @@ class Wallet {
99
108
  this.boardingTapscript = boardingTapscript;
100
109
  this.serverUnrollScript = serverUnrollScript;
101
110
  this.forfeitOutputScript = forfeitOutputScript;
111
+ this.forfeitPubkey = forfeitPubkey;
102
112
  this.dustAmount = dustAmount;
103
113
  this.walletRepository = walletRepository;
104
114
  this.contractRepository = contractRepository;
115
+ this.renewalConfig = {
116
+ enabled: renewalConfig?.enabled ?? false,
117
+ ...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
118
+ ...renewalConfig,
119
+ };
105
120
  }
106
121
  static async create(config) {
107
122
  const pubkey = await config.identity.xOnlyPublicKey();
108
123
  if (!pubkey) {
109
124
  throw new Error("Invalid configured public key");
110
125
  }
111
- const arkProvider = new ark_1.RestArkProvider(config.arkServerUrl);
112
- const indexerProvider = new indexer_1.RestIndexerProvider(config.arkServerUrl);
126
+ // Use provided arkProvider instance or create a new one from arkServerUrl
127
+ const arkProvider = config.arkProvider ||
128
+ (() => {
129
+ if (!config.arkServerUrl) {
130
+ throw new Error("Either arkProvider or arkServerUrl must be provided");
131
+ }
132
+ return new ark_1.RestArkProvider(config.arkServerUrl);
133
+ })();
134
+ // Extract arkServerUrl from provider if not explicitly provided
135
+ const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
136
+ if (!arkServerUrl) {
137
+ throw new Error("Could not determine arkServerUrl from provider");
138
+ }
139
+ // Use provided indexerProvider instance or create a new one
140
+ // indexerUrl defaults to arkServerUrl if not provided
141
+ const indexerUrl = config.indexerUrl || arkServerUrl;
142
+ const indexerProvider = config.indexerProvider || new indexer_1.RestIndexerProvider(indexerUrl);
113
143
  const info = await arkProvider.getInfo();
114
144
  const network = (0, networks_1.getNetwork)(info.network);
115
- const onchainProvider = new onchain_1.EsploraProvider(config.esploraUrl || onchain_1.ESPLORA_URL[info.network]);
145
+ // Extract esploraUrl from provider if not explicitly provided
146
+ const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
147
+ // Use provided onchainProvider instance or create a new one
148
+ const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
149
+ // Generate timelocks
116
150
  const exitTimelock = {
117
151
  value: info.unilateralExitDelay,
118
152
  type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
@@ -136,17 +170,24 @@ class Wallet {
136
170
  // Save tapscripts
137
171
  const offchainTapscript = bareVtxoTapscript;
138
172
  // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
139
- const rawCheckpointExitClosure = base_1.hex.decode(info.checkpointExitClosure);
140
- const serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(rawCheckpointExitClosure);
173
+ let serverUnrollScript;
174
+ try {
175
+ const raw = base_1.hex.decode(info.checkpointTapscript);
176
+ serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
177
+ }
178
+ catch (e) {
179
+ throw new Error("Invalid checkpointTapscript from server");
180
+ }
141
181
  // parse the server forfeit address
142
182
  // server is expecting funds to be sent to this address
143
- const forfeitAddress = (0, payment_js_1.Address)(network).decode(info.forfeitAddress);
144
- const forfeitOutputScript = payment_js_1.OutScript.encode(forfeitAddress);
183
+ const forfeitPubkey = base_1.hex.decode(info.forfeitPubkey).slice(1);
184
+ const forfeitAddress = (0, btc_signer_1.Address)(network).decode(info.forfeitAddress);
185
+ const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
145
186
  // Set up storage and repositories
146
187
  const storage = config.storage || new inMemory_1.InMemoryStorageAdapter();
147
188
  const walletRepository = new walletRepository_1.WalletRepositoryImpl(storage);
148
189
  const contractRepository = new contractRepository_1.ContractRepositoryImpl(storage);
149
- return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
190
+ return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, info.dust, walletRepository, contractRepository, config.renewalConfig);
150
191
  }
151
192
  get arkAddress() {
152
193
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -208,40 +249,24 @@ class Wallet {
208
249
  // if (cachedVtxos.length) return cachedVtxos;
209
250
  // For now, always fetch fresh data from provider and update cache
210
251
  // In future, we can add cache invalidation logic based on timestamps
211
- const spendableVtxos = await this.getVirtualCoins(filter);
212
- const encodedOffchainTapscript = this.offchainTapscript.encode();
213
- const forfeit = this.offchainTapscript.forfeit();
214
- const exit = this.offchainTapscript.exit();
215
- const extendedVtxos = spendableVtxos.map((vtxo) => ({
216
- ...vtxo,
217
- forfeitTapLeafScript: forfeit,
218
- intentTapLeafScript: exit,
219
- tapTree: encodedOffchainTapscript,
220
- }));
252
+ const vtxos = await this.getVirtualCoins(filter);
253
+ const extendedVtxos = vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo));
221
254
  // Update cache with fresh data
222
255
  await this.walletRepository.saveVtxos(address, extendedVtxos);
223
256
  return extendedVtxos;
224
257
  }
225
258
  async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
226
259
  const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
227
- const response = await this.indexerProvider.getVtxos({
228
- scripts,
229
- spendableOnly: true,
230
- });
231
- const vtxos = response.vtxos;
232
- if (filter.withRecoverable) {
233
- const response = await this.indexerProvider.getVtxos({
234
- scripts,
235
- recoverableOnly: true,
236
- });
237
- vtxos.push(...response.vtxos);
260
+ const response = await this.indexerProvider.getVtxos({ scripts });
261
+ const allVtxos = response.vtxos;
262
+ let vtxos = allVtxos.filter(_1.isSpendable);
263
+ // all recoverable vtxos are spendable by definition
264
+ if (!filter.withRecoverable) {
265
+ vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo));
238
266
  }
239
267
  if (filter.withUnrolled) {
240
- const response = await this.indexerProvider.getVtxos({
241
- scripts,
242
- spentOnly: true,
243
- });
244
- vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
268
+ const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
269
+ vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
245
270
  }
246
271
  return vtxos;
247
272
  }
@@ -279,10 +304,10 @@ class Wallet {
279
304
  return txs;
280
305
  }
281
306
  async getBoardingTxs() {
282
- const boardingAddress = await this.getBoardingAddress();
283
- const txs = await this.onchainProvider.getTransactions(boardingAddress);
284
307
  const utxos = [];
285
308
  const commitmentsToIgnore = new Set();
309
+ const boardingAddress = await this.getBoardingAddress();
310
+ const txs = await this.onchainProvider.getTransactions(boardingAddress);
286
311
  for (const tx of txs) {
287
312
  for (let i = 0; i < tx.vout.length; i++) {
288
313
  const vout = tx.vout[i];
@@ -345,15 +370,12 @@ class Wallet {
345
370
  async getBoardingUtxos() {
346
371
  const boardingAddress = await this.getBoardingAddress();
347
372
  const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
348
- const encodedBoardingTapscript = this.boardingTapscript.encode();
349
- const forfeit = this.boardingTapscript.forfeit();
350
- const exit = this.boardingTapscript.exit();
351
- return boardingUtxos.map((utxo) => ({
352
- ...utxo,
353
- forfeitTapLeafScript: forfeit,
354
- intentTapLeafScript: exit,
355
- tapTree: encodedBoardingTapscript,
356
- }));
373
+ const utxos = boardingUtxos.map((utxo) => {
374
+ return (0, utils_1.extendCoin)(this, utxo);
375
+ });
376
+ // Save boardingUtxos using unified repository
377
+ await this.walletRepository.saveUtxos(boardingAddress, utxos);
378
+ return utxos;
357
379
  }
358
380
  async sendBitcoin(params) {
359
381
  if (params.amount <= 0) {
@@ -402,7 +424,7 @@ class Wallet {
402
424
  // TODO persist final virtual tx and checkpoints to repository
403
425
  // sign the checkpoints
404
426
  const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
405
- const tx = transaction_js_1.Transaction.fromPSBT(base_1.base64.decode(c));
427
+ const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
406
428
  const signedCheckpoint = await this.identity.sign(tx);
407
429
  return base_1.base64.encode(signedCheckpoint.toPSBT());
408
430
  }));
@@ -423,13 +445,15 @@ class Wallet {
423
445
  }
424
446
  }
425
447
  }
426
- // if no params are provided, use all boarding and offchain utxos as inputs
448
+ // if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
427
449
  // and send all to the offchain address
428
450
  if (!params) {
429
451
  let amount = 0;
430
- const boardingUtxos = await this.getBoardingUtxos();
452
+ const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
453
+ const boardingTimelock = exitScript.params.timelock;
454
+ const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
431
455
  amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
432
- const vtxos = await this.getVtxos();
456
+ const vtxos = await this.getVtxos({ withRecoverable: true });
433
457
  amount += vtxos.reduce((sum, input) => sum + input.value, 0);
434
458
  const inputs = [...boardingUtxos, ...vtxos];
435
459
  if (inputs.length === 0) {
@@ -458,8 +482,8 @@ class Wallet {
458
482
  }
459
483
  catch {
460
484
  // onchain
461
- const addr = (0, payment_js_1.Address)(this.network).decode(output.address);
462
- script = payment_js_1.OutScript.encode(addr);
485
+ const addr = (0, btc_signer_1.Address)(this.network).decode(output.address);
486
+ script = btc_signer_1.OutScript.encode(addr);
463
487
  onchainOutputIndexes.push(index);
464
488
  }
465
489
  outputs.push({
@@ -472,7 +496,7 @@ class Wallet {
472
496
  const signingPublicKeys = [];
473
497
  if (hasOffchainOutputs) {
474
498
  session = this.identity.signerSession();
475
- signingPublicKeys.push(base_1.hex.encode(session.getPublicKey()));
499
+ signingPublicKeys.push(base_1.hex.encode(await session.getPublicKey()));
476
500
  }
477
501
  const [intent, deleteIntent] = await Promise.all([
478
502
  this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
@@ -488,8 +512,8 @@ class Wallet {
488
512
  ...params.inputs.map((input) => `${input.txid}:${input.vout}`),
489
513
  ];
490
514
  const settlementStream = this.arkProvider.getEventStream(abortController.signal, topics);
491
- // roundId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
492
- let roundId;
515
+ // batchId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
516
+ let batchId;
493
517
  let sweepTapTreeRoot;
494
518
  const vtxoChunks = [];
495
519
  const connectorsChunks = [];
@@ -502,30 +526,26 @@ class Wallet {
502
526
  switch (event.type) {
503
527
  // the settlement failed
504
528
  case ark_1.SettlementEventType.BatchFailed:
505
- // fail if the roundId is the one joined
506
- if (event.id === roundId) {
507
- throw new Error(event.reason);
508
- }
509
- break;
529
+ throw new Error(event.reason);
510
530
  case ark_1.SettlementEventType.BatchStarted:
511
531
  if (step !== undefined) {
512
532
  continue;
513
533
  }
514
- const res = await this.handleBatchStartedEvent(event, intentId, this.arkServerPublicKey, this.forfeitOutputScript);
534
+ const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
515
535
  if (!res.skip) {
516
536
  step = event.type;
517
537
  sweepTapTreeRoot = res.sweepTapTreeRoot;
518
- roundId = res.roundId;
538
+ batchId = res.roundId;
519
539
  if (!hasOffchainOutputs) {
520
540
  // if there are no offchain outputs, we don't have to handle musig2 tree signatures
521
541
  // we can directly advance to the finalization step
522
- step = ark_1.SettlementEventType.TreeNoncesAggregated;
542
+ step = ark_1.SettlementEventType.TreeNonces;
523
543
  }
524
544
  }
525
545
  break;
526
546
  case ark_1.SettlementEventType.TreeTx:
527
547
  if (step !== ark_1.SettlementEventType.BatchStarted &&
528
- step !== ark_1.SettlementEventType.TreeNoncesAggregated) {
548
+ step !== ark_1.SettlementEventType.TreeNonces) {
529
549
  continue;
530
550
  }
531
551
  // index 0 = vtxo tree
@@ -541,7 +561,7 @@ class Wallet {
541
561
  }
542
562
  break;
543
563
  case ark_1.SettlementEventType.TreeSignature:
544
- if (step !== ark_1.SettlementEventType.TreeNoncesAggregated) {
564
+ if (step !== ark_1.SettlementEventType.TreeNonces) {
545
565
  continue;
546
566
  }
547
567
  if (!hasOffchainOutputs) {
@@ -583,7 +603,7 @@ class Wallet {
583
603
  break;
584
604
  // the musig2 nonces of the vtxo tree transactions are generated
585
605
  // the server expects now the partial musig2 signatures
586
- case ark_1.SettlementEventType.TreeNoncesAggregated:
606
+ case ark_1.SettlementEventType.TreeNonces:
587
607
  if (step !== ark_1.SettlementEventType.TreeSigningStarted) {
588
608
  continue;
589
609
  }
@@ -591,14 +611,18 @@ class Wallet {
591
611
  if (!session) {
592
612
  throw new Error("Signing session not set");
593
613
  }
594
- await this.handleSettlementSigningNoncesGeneratedEvent(event, session);
614
+ const signed = await this.handleSettlementTreeNoncesEvent(event, session);
615
+ if (signed) {
616
+ step = event.type;
617
+ }
618
+ break;
595
619
  }
596
620
  step = event.type;
597
621
  break;
598
622
  // the vtxo tree is signed, craft, sign and submit forfeit transactions
599
623
  // if any boarding utxos are involved, the settlement tx is also signed
600
624
  case ark_1.SettlementEventType.BatchFinalization:
601
- if (step !== ark_1.SettlementEventType.TreeNoncesAggregated) {
625
+ if (step !== ark_1.SettlementEventType.TreeNonces) {
602
626
  continue;
603
627
  }
604
628
  if (!this.forfeitOutputScript) {
@@ -616,8 +640,10 @@ class Wallet {
616
640
  if (step !== ark_1.SettlementEventType.BatchFinalization) {
617
641
  continue;
618
642
  }
619
- abortController.abort();
620
- return event.commitmentTxid;
643
+ if (event.id === batchId) {
644
+ abortController.abort();
645
+ return event.commitmentTxid;
646
+ }
621
647
  }
622
648
  }
623
649
  }
@@ -639,22 +665,22 @@ class Wallet {
639
665
  let onchainStopFunc;
640
666
  let indexerStopFunc;
641
667
  if (this.onchainProvider && boardingAddress) {
668
+ const findVoutOnTx = (tx) => {
669
+ return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
670
+ };
642
671
  onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
672
+ // find all utxos belonging to our boarding address
643
673
  const coins = txs
674
+ // filter txs where address is in output
675
+ .filter((tx) => findVoutOnTx(tx) !== -1)
676
+ // return utxo as Coin
644
677
  .map((tx) => {
645
- const vout = tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
646
- if (vout === -1) {
647
- console.warn(`No vout found for address ${boardingAddress} in transaction ${tx.txid}`);
648
- return null;
649
- }
650
- return {
651
- txid: tx.txid,
652
- vout,
653
- value: Number(tx.vout[vout].value),
654
- status: tx.status,
655
- };
656
- })
657
- .filter((coin) => coin !== null);
678
+ const { txid, status } = tx;
679
+ const vout = findVoutOnTx(tx);
680
+ const value = Number(tx.vout[vout].value);
681
+ return { txid, vout, value, status };
682
+ });
683
+ // and notify via callback
658
684
  eventCallback({
659
685
  type: "utxo",
660
686
  coins,
@@ -696,10 +722,10 @@ class Wallet {
696
722
  };
697
723
  return stopFunc;
698
724
  }
699
- async handleBatchStartedEvent(event, intentId, serverPubKey, forfeitOutputScript) {
725
+ async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
700
726
  const utf8IntentId = new TextEncoder().encode(intentId);
701
727
  const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
702
- const intentIdHashStr = base_1.hex.encode(new Uint8Array(intentIdHash));
728
+ const intentIdHashStr = base_1.hex.encode(intentIdHash);
703
729
  let skip = true;
704
730
  // check if our intent ID hash matches any in the event
705
731
  for (const idHash of event.intentIdHashes) {
@@ -719,7 +745,7 @@ class Wallet {
719
745
  value: event.batchExpiry,
720
746
  type: event.batchExpiry >= 512n ? "seconds" : "blocks",
721
747
  },
722
- pubkeys: [serverPubKey],
748
+ pubkeys: [forfeitPubKey],
723
749
  }).script;
724
750
  const sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
725
751
  return {
@@ -732,7 +758,7 @@ class Wallet {
732
758
  // validates the vtxo tree, creates a signing session and generates the musig2 nonces
733
759
  async handleSettlementSigningEvent(event, sweepTapTreeRoot, session, vtxoGraph) {
734
760
  // validate the unsigned vtxo tree
735
- const commitmentTx = transaction_js_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
761
+ const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
736
762
  (0, validation_1.validateVtxoTxGraph)(vtxoGraph, commitmentTx, sweepTapTreeRoot);
737
763
  // TODO check if our registered outputs are in the vtxo tree
738
764
  const sharedOutput = commitmentTx.getOutput(0);
@@ -740,18 +766,25 @@ class Wallet {
740
766
  throw new Error("Shared output not found");
741
767
  }
742
768
  session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
743
- await this.arkProvider.submitTreeNonces(event.id, base_1.hex.encode(session.getPublicKey()), session.getNonces());
769
+ const pubkey = base_1.hex.encode(await session.getPublicKey());
770
+ const nonces = await session.getNonces();
771
+ await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
744
772
  }
745
- async handleSettlementSigningNoncesGeneratedEvent(event, session) {
746
- session.setAggregatedNonces(event.treeNonces);
747
- const signatures = session.sign();
748
- await this.arkProvider.submitTreeSignatures(event.id, base_1.hex.encode(session.getPublicKey()), signatures);
773
+ async handleSettlementTreeNoncesEvent(event, session) {
774
+ const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
775
+ // wait to receive and aggregate all nonces before sending signatures
776
+ if (!hasAllNonces)
777
+ return false;
778
+ const signatures = await session.sign();
779
+ const pubkey = base_1.hex.encode(await session.getPublicKey());
780
+ await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
781
+ return true;
749
782
  }
750
783
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
751
784
  // the signed forfeits transactions to submit
752
785
  const signedForfeits = [];
753
786
  const vtxos = await this.getVirtualCoins();
754
- let settlementPsbt = transaction_js_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
787
+ let settlementPsbt = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
755
788
  let hasBoardingUtxos = false;
756
789
  let connectorIndex = 0;
757
790
  const connectorsLeaves = connectorsGraph?.leaves() || [];
@@ -760,8 +793,6 @@ class Wallet {
760
793
  const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
761
794
  // boarding utxo, we need to sign the settlement tx
762
795
  if (!vtxo) {
763
- hasBoardingUtxos = true;
764
- const inputIndexes = [];
765
796
  for (let i = 0; i < settlementPsbt.inputsLength; i++) {
766
797
  const settlementInput = settlementPsbt.getInput(i);
767
798
  if (!settlementInput.txid ||
@@ -777,9 +808,12 @@ class Wallet {
777
808
  settlementPsbt.updateInput(i, {
778
809
  tapLeafScript: [input.forfeitTapLeafScript],
779
810
  });
780
- inputIndexes.push(i);
811
+ settlementPsbt = await this.identity.sign(settlementPsbt, [
812
+ i,
813
+ ]);
814
+ hasBoardingUtxos = true;
815
+ break;
781
816
  }
782
- settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
783
817
  continue;
784
818
  }
785
819
  if ((0, _1.isRecoverable)(vtxo) || (0, _1.isSubdust)(vtxo, this.dustAmount)) {
@@ -793,7 +827,7 @@ class Wallet {
793
827
  throw new Error("not enough connectors received");
794
828
  }
795
829
  const connectorLeaf = connectorsLeaves[connectorIndex];
796
- const connectorTxId = base_1.hex.encode((0, utils_js_1.sha256x2)(connectorLeaf.toBytes(true)).reverse());
830
+ const connectorTxId = connectorLeaf.id;
797
831
  const connectorOutput = connectorLeaf.getOutput(0);
798
832
  if (!connectorOutput) {
799
833
  throw new Error("connector output not found");
@@ -812,7 +846,7 @@ class Wallet {
812
846
  amount: BigInt(vtxo.value),
813
847
  script: base_2.VtxoScript.decode(input.tapTree).pkScript,
814
848
  },
815
- sighashType: transaction_js_1.SigHash.DEFAULT,
849
+ sighashType: btc_signer_1.SigHash.DEFAULT,
816
850
  tapLeafScript: [input.forfeitTapLeafScript],
817
851
  },
818
852
  {
@@ -834,112 +868,69 @@ class Wallet {
834
868
  : undefined);
835
869
  }
836
870
  }
837
- async makeRegisterIntentSignature(bip322Inputs, outputs, onchainOutputsIndexes, cosignerPubKeys) {
871
+ async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
838
872
  const nowSeconds = Math.floor(Date.now() / 1000);
839
- const { inputs, inputTapTrees, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
873
+ const inputs = this.prepareIntentProofInputs(coins);
840
874
  const message = {
841
875
  type: "register",
842
- input_tap_trees: inputTapTrees,
843
876
  onchain_output_indexes: onchainOutputsIndexes,
844
877
  valid_at: nowSeconds,
845
878
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
846
879
  cosigners_public_keys: cosignerPubKeys,
847
880
  };
848
881
  const encodedMessage = JSON.stringify(message, null, 0);
849
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer, outputs);
882
+ const proof = intent_1.Intent.create(encodedMessage, inputs, outputs);
883
+ const signedProof = await this.identity.sign(proof);
850
884
  return {
851
- signature,
885
+ proof: base_1.base64.encode(signedProof.toPSBT()),
852
886
  message: encodedMessage,
853
887
  };
854
888
  }
855
- async makeDeleteIntentSignature(bip322Inputs) {
889
+ async makeDeleteIntentSignature(coins) {
856
890
  const nowSeconds = Math.floor(Date.now() / 1000);
857
- const { inputs, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
891
+ const inputs = this.prepareIntentProofInputs(coins);
858
892
  const message = {
859
893
  type: "delete",
860
894
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
861
895
  };
862
896
  const encodedMessage = JSON.stringify(message, null, 0);
863
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer);
897
+ const proof = intent_1.Intent.create(encodedMessage, inputs, []);
898
+ const signedProof = await this.identity.sign(proof);
864
899
  return {
865
- signature,
900
+ proof: base_1.base64.encode(signedProof.toPSBT()),
866
901
  message: encodedMessage,
867
902
  };
868
903
  }
869
- prepareBIP322Inputs(bip322Inputs) {
904
+ prepareIntentProofInputs(coins) {
870
905
  const inputs = [];
871
- const inputTapTrees = [];
872
- const inputExtraWitnesses = [];
873
- for (const bip322Input of bip322Inputs) {
874
- const vtxoScript = base_2.VtxoScript.decode(bip322Input.tapTree);
875
- const sequence = getSequence(bip322Input);
906
+ for (const input of coins) {
907
+ const vtxoScript = base_2.VtxoScript.decode(input.tapTree);
908
+ const sequence = getSequence(input);
909
+ const unknown = [unknownFields_1.VtxoTaprootTree.encode(input.tapTree)];
910
+ if (input.extraWitness) {
911
+ unknown.push(unknownFields_1.ConditionWitness.encode(input.extraWitness));
912
+ }
876
913
  inputs.push({
877
- txid: base_1.hex.decode(bip322Input.txid),
878
- index: bip322Input.vout,
914
+ txid: base_1.hex.decode(input.txid),
915
+ index: input.vout,
879
916
  witnessUtxo: {
880
- amount: BigInt(bip322Input.value),
917
+ amount: BigInt(input.value),
881
918
  script: vtxoScript.pkScript,
882
919
  },
883
920
  sequence,
884
- tapLeafScript: [bip322Input.intentTapLeafScript],
921
+ tapLeafScript: [input.intentTapLeafScript],
922
+ unknown,
885
923
  });
886
- inputTapTrees.push(base_1.hex.encode(bip322Input.tapTree));
887
- inputExtraWitnesses.push(bip322Input.extraWitness || []);
888
924
  }
889
- return {
890
- inputs,
891
- inputTapTrees,
892
- finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
893
- };
894
- }
895
- async makeBIP322Signature(message, inputs, finalizer, outputs) {
896
- const proof = bip322_1.BIP322.create(message, inputs, outputs);
897
- const signedProof = await this.identity.sign(proof);
898
- return bip322_1.BIP322.signature(signedProof, finalizer);
925
+ return inputs;
899
926
  }
900
927
  }
901
928
  exports.Wallet = Wallet;
902
929
  Wallet.MIN_FEE_RATE = 1; // sats/vbyte
903
- function finalizeWithExtraWitnesses(inputExtraWitnesses) {
904
- return function (tx) {
905
- for (let i = 0; i < tx.inputsLength; i++) {
906
- try {
907
- tx.finalizeIdx(i);
908
- }
909
- catch (e) {
910
- // handle empty witness error
911
- if (e instanceof Error &&
912
- e.message.includes("finalize/taproot: empty witness")) {
913
- const tapLeaves = tx.getInput(i).tapLeafScript;
914
- if (!tapLeaves || tapLeaves.length <= 0)
915
- throw e;
916
- const [cb, s] = tapLeaves[0];
917
- const script = s.slice(0, -1);
918
- tx.updateInput(i, {
919
- finalScriptWitness: [
920
- script,
921
- psbt_js_1.TaprootControlBlock.encode(cb),
922
- ],
923
- });
924
- }
925
- }
926
- const finalScriptWitness = tx.getInput(i).finalScriptWitness;
927
- if (!finalScriptWitness)
928
- throw new Error("input not finalized");
929
- // input 0 and 1 spend the same pkscript
930
- const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
931
- if (extra && extra.length > 0) {
932
- tx.updateInput(i, {
933
- finalScriptWitness: [...extra, ...finalScriptWitness],
934
- });
935
- }
936
- }
937
- };
938
- }
939
- function getSequence(bip322Input) {
930
+ function getSequence(coin) {
940
931
  let sequence = undefined;
941
932
  try {
942
- const scriptWithLeafVersion = bip322Input.intentTapLeafScript[1];
933
+ const scriptWithLeafVersion = coin.intentTapLeafScript[1];
943
934
  const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
944
935
  const params = tapscript_1.CSVMultisigTapscript.decode(script).params;
945
936
  sequence = bip68.encode(params.timelock.type === "blocks"
@@ -0,0 +1,3 @@
1
+ // Expo adapter for React Native/Expo environments
2
+ export { ExpoArkProvider } from '../providers/expoArk.js';
3
+ export { ExpoIndexerProvider } from '../providers/expoIndexer.js';