@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
@@ -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
- import { buildOffchainTx } from '../utils/arkTransaction.js';
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 './serviceWorker/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
@@ -31,13 +32,21 @@ import { extendVirtualCoin } from './serviceWorker/utils.js';
31
32
  *
32
33
  * @example
33
34
  * ```typescript
34
- * // Create a wallet
35
+ * // Create a wallet with URL configuration
35
36
  * const wallet = await Wallet.create({
36
37
  * identity: SingleKey.fromHex('your_private_key'),
37
38
  * arkServerUrl: 'https://ark.example.com',
38
39
  * esploraUrl: 'https://mempool.space/api'
39
40
  * });
40
41
  *
42
+ * // Or with custom provider instances (e.g., for Expo/React Native)
43
+ * const wallet = await Wallet.create({
44
+ * identity: SingleKey.fromHex('your_private_key'),
45
+ * arkProvider: new ExpoArkProvider('https://ark.example.com'),
46
+ * indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
47
+ * esploraUrl: 'https://mempool.space/api'
48
+ * });
49
+ *
41
50
  * // Get addresses
42
51
  * const arkAddress = await wallet.getAddress();
43
52
  * const boardingAddress = await wallet.getBoardingAddress();
@@ -50,7 +59,7 @@ import { extendVirtualCoin } from './serviceWorker/utils.js';
50
59
  * ```
51
60
  */
52
61
  export class Wallet {
53
- 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) {
54
63
  this.identity = identity;
55
64
  this.network = network;
56
65
  this.networkName = networkName;
@@ -62,20 +71,45 @@ export class Wallet {
62
71
  this.boardingTapscript = boardingTapscript;
63
72
  this.serverUnrollScript = serverUnrollScript;
64
73
  this.forfeitOutputScript = forfeitOutputScript;
74
+ this.forfeitPubkey = forfeitPubkey;
65
75
  this.dustAmount = dustAmount;
66
76
  this.walletRepository = walletRepository;
67
77
  this.contractRepository = contractRepository;
78
+ this.renewalConfig = {
79
+ enabled: renewalConfig?.enabled ?? false,
80
+ ...DEFAULT_RENEWAL_CONFIG,
81
+ ...renewalConfig,
82
+ };
68
83
  }
69
84
  static async create(config) {
70
85
  const pubkey = await config.identity.xOnlyPublicKey();
71
86
  if (!pubkey) {
72
87
  throw new Error("Invalid configured public key");
73
88
  }
74
- const arkProvider = new RestArkProvider(config.arkServerUrl);
75
- const indexerProvider = new RestIndexerProvider(config.arkServerUrl);
89
+ // Use provided arkProvider instance or create a new one from arkServerUrl
90
+ const arkProvider = config.arkProvider ||
91
+ (() => {
92
+ if (!config.arkServerUrl) {
93
+ throw new Error("Either arkProvider or arkServerUrl must be provided");
94
+ }
95
+ return new RestArkProvider(config.arkServerUrl);
96
+ })();
97
+ // Extract arkServerUrl from provider if not explicitly provided
98
+ const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
99
+ if (!arkServerUrl) {
100
+ throw new Error("Could not determine arkServerUrl from provider");
101
+ }
102
+ // Use provided indexerProvider instance or create a new one
103
+ // indexerUrl defaults to arkServerUrl if not provided
104
+ const indexerUrl = config.indexerUrl || arkServerUrl;
105
+ const indexerProvider = config.indexerProvider || new RestIndexerProvider(indexerUrl);
76
106
  const info = await arkProvider.getInfo();
77
107
  const network = getNetwork(info.network);
78
- const onchainProvider = new EsploraProvider(config.esploraUrl || ESPLORA_URL[info.network]);
108
+ // Extract esploraUrl from provider if not explicitly provided
109
+ const esploraUrl = config.esploraUrl || ESPLORA_URL[info.network];
110
+ // Use provided onchainProvider instance or create a new one
111
+ const onchainProvider = config.onchainProvider || new EsploraProvider(esploraUrl);
112
+ // Generate timelocks
79
113
  const exitTimelock = {
80
114
  value: info.unilateralExitDelay,
81
115
  type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
@@ -99,17 +133,24 @@ export class Wallet {
99
133
  // Save tapscripts
100
134
  const offchainTapscript = bareVtxoTapscript;
101
135
  // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
102
- const rawCheckpointExitClosure = hex.decode(info.checkpointExitClosure);
103
- const serverUnrollScript = CSVMultisigTapscript.decode(rawCheckpointExitClosure);
136
+ let serverUnrollScript;
137
+ try {
138
+ const raw = hex.decode(info.checkpointTapscript);
139
+ serverUnrollScript = CSVMultisigTapscript.decode(raw);
140
+ }
141
+ catch (e) {
142
+ throw new Error("Invalid checkpointTapscript from server");
143
+ }
104
144
  // parse the server forfeit address
105
145
  // server is expecting funds to be sent to this address
146
+ const forfeitPubkey = hex.decode(info.forfeitPubkey).slice(1);
106
147
  const forfeitAddress = Address(network).decode(info.forfeitAddress);
107
148
  const forfeitOutputScript = OutScript.encode(forfeitAddress);
108
149
  // Set up storage and repositories
109
150
  const storage = config.storage || new InMemoryStorageAdapter();
110
151
  const walletRepository = new WalletRepositoryImpl(storage);
111
152
  const contractRepository = new ContractRepositoryImpl(storage);
112
- 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);
113
154
  }
114
155
  get arkAddress() {
115
156
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -171,40 +212,24 @@ export class Wallet {
171
212
  // if (cachedVtxos.length) return cachedVtxos;
172
213
  // For now, always fetch fresh data from provider and update cache
173
214
  // In future, we can add cache invalidation logic based on timestamps
174
- const spendableVtxos = await this.getVirtualCoins(filter);
175
- const encodedOffchainTapscript = this.offchainTapscript.encode();
176
- const forfeit = this.offchainTapscript.forfeit();
177
- const exit = this.offchainTapscript.exit();
178
- const extendedVtxos = spendableVtxos.map((vtxo) => ({
179
- ...vtxo,
180
- forfeitTapLeafScript: forfeit,
181
- intentTapLeafScript: exit,
182
- tapTree: encodedOffchainTapscript,
183
- }));
215
+ const vtxos = await this.getVirtualCoins(filter);
216
+ const extendedVtxos = vtxos.map((vtxo) => extendVirtualCoin(this, vtxo));
184
217
  // Update cache with fresh data
185
218
  await this.walletRepository.saveVtxos(address, extendedVtxos);
186
219
  return extendedVtxos;
187
220
  }
188
221
  async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
189
222
  const scripts = [hex.encode(this.offchainTapscript.pkScript)];
190
- const response = await this.indexerProvider.getVtxos({
191
- scripts,
192
- spendableOnly: true,
193
- });
194
- const vtxos = response.vtxos;
195
- if (filter.withRecoverable) {
196
- const response = await this.indexerProvider.getVtxos({
197
- scripts,
198
- recoverableOnly: true,
199
- });
200
- vtxos.push(...response.vtxos);
223
+ const response = await this.indexerProvider.getVtxos({ scripts });
224
+ const allVtxos = response.vtxos;
225
+ let vtxos = allVtxos.filter(isSpendable);
226
+ // all recoverable vtxos are spendable by definition
227
+ if (!filter.withRecoverable) {
228
+ vtxos = vtxos.filter((vtxo) => !isRecoverable(vtxo));
201
229
  }
202
230
  if (filter.withUnrolled) {
203
- const response = await this.indexerProvider.getVtxos({
204
- scripts,
205
- spentOnly: true,
206
- });
207
- vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
231
+ const spentVtxos = allVtxos.filter((vtxo) => !isSpendable(vtxo));
232
+ vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
208
233
  }
209
234
  return vtxos;
210
235
  }
@@ -242,10 +267,10 @@ export class Wallet {
242
267
  return txs;
243
268
  }
244
269
  async getBoardingTxs() {
245
- const boardingAddress = await this.getBoardingAddress();
246
- const txs = await this.onchainProvider.getTransactions(boardingAddress);
247
270
  const utxos = [];
248
271
  const commitmentsToIgnore = new Set();
272
+ const boardingAddress = await this.getBoardingAddress();
273
+ const txs = await this.onchainProvider.getTransactions(boardingAddress);
249
274
  for (const tx of txs) {
250
275
  for (let i = 0; i < tx.vout.length; i++) {
251
276
  const vout = tx.vout[i];
@@ -308,15 +333,12 @@ export class Wallet {
308
333
  async getBoardingUtxos() {
309
334
  const boardingAddress = await this.getBoardingAddress();
310
335
  const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
311
- const encodedBoardingTapscript = this.boardingTapscript.encode();
312
- const forfeit = this.boardingTapscript.forfeit();
313
- const exit = this.boardingTapscript.exit();
314
- return boardingUtxos.map((utxo) => ({
315
- ...utxo,
316
- forfeitTapLeafScript: forfeit,
317
- intentTapLeafScript: exit,
318
- tapTree: encodedBoardingTapscript,
319
- }));
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;
320
342
  }
321
343
  async sendBitcoin(params) {
322
344
  if (params.amount <= 0) {
@@ -386,13 +408,15 @@ export class Wallet {
386
408
  }
387
409
  }
388
410
  }
389
- // if no params are provided, use all boarding and offchain utxos as inputs
411
+ // if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
390
412
  // and send all to the offchain address
391
413
  if (!params) {
392
414
  let amount = 0;
393
- const boardingUtxos = await this.getBoardingUtxos();
415
+ const exitScript = CSVMultisigTapscript.decode(hex.decode(this.boardingTapscript.exitScript));
416
+ const boardingTimelock = exitScript.params.timelock;
417
+ const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !hasBoardingTxExpired(utxo, boardingTimelock));
394
418
  amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
395
- const vtxos = await this.getVtxos();
419
+ const vtxos = await this.getVtxos({ withRecoverable: true });
396
420
  amount += vtxos.reduce((sum, input) => sum + input.value, 0);
397
421
  const inputs = [...boardingUtxos, ...vtxos];
398
422
  if (inputs.length === 0) {
@@ -435,7 +459,7 @@ export class Wallet {
435
459
  const signingPublicKeys = [];
436
460
  if (hasOffchainOutputs) {
437
461
  session = this.identity.signerSession();
438
- signingPublicKeys.push(hex.encode(session.getPublicKey()));
462
+ signingPublicKeys.push(hex.encode(await session.getPublicKey()));
439
463
  }
440
464
  const [intent, deleteIntent] = await Promise.all([
441
465
  this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
@@ -451,8 +475,8 @@ export class Wallet {
451
475
  ...params.inputs.map((input) => `${input.txid}:${input.vout}`),
452
476
  ];
453
477
  const settlementStream = this.arkProvider.getEventStream(abortController.signal, topics);
454
- // roundId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
455
- let roundId;
478
+ // batchId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
479
+ let batchId;
456
480
  let sweepTapTreeRoot;
457
481
  const vtxoChunks = [];
458
482
  const connectorsChunks = [];
@@ -465,30 +489,26 @@ export class Wallet {
465
489
  switch (event.type) {
466
490
  // the settlement failed
467
491
  case SettlementEventType.BatchFailed:
468
- // fail if the roundId is the one joined
469
- if (event.id === roundId) {
470
- throw new Error(event.reason);
471
- }
472
- break;
492
+ throw new Error(event.reason);
473
493
  case SettlementEventType.BatchStarted:
474
494
  if (step !== undefined) {
475
495
  continue;
476
496
  }
477
- const res = await this.handleBatchStartedEvent(event, intentId, this.arkServerPublicKey, this.forfeitOutputScript);
497
+ const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
478
498
  if (!res.skip) {
479
499
  step = event.type;
480
500
  sweepTapTreeRoot = res.sweepTapTreeRoot;
481
- roundId = res.roundId;
501
+ batchId = res.roundId;
482
502
  if (!hasOffchainOutputs) {
483
503
  // if there are no offchain outputs, we don't have to handle musig2 tree signatures
484
504
  // we can directly advance to the finalization step
485
- step = SettlementEventType.TreeNoncesAggregated;
505
+ step = SettlementEventType.TreeNonces;
486
506
  }
487
507
  }
488
508
  break;
489
509
  case SettlementEventType.TreeTx:
490
510
  if (step !== SettlementEventType.BatchStarted &&
491
- step !== SettlementEventType.TreeNoncesAggregated) {
511
+ step !== SettlementEventType.TreeNonces) {
492
512
  continue;
493
513
  }
494
514
  // index 0 = vtxo tree
@@ -504,7 +524,7 @@ export class Wallet {
504
524
  }
505
525
  break;
506
526
  case SettlementEventType.TreeSignature:
507
- if (step !== SettlementEventType.TreeNoncesAggregated) {
527
+ if (step !== SettlementEventType.TreeNonces) {
508
528
  continue;
509
529
  }
510
530
  if (!hasOffchainOutputs) {
@@ -546,7 +566,7 @@ export class Wallet {
546
566
  break;
547
567
  // the musig2 nonces of the vtxo tree transactions are generated
548
568
  // the server expects now the partial musig2 signatures
549
- case SettlementEventType.TreeNoncesAggregated:
569
+ case SettlementEventType.TreeNonces:
550
570
  if (step !== SettlementEventType.TreeSigningStarted) {
551
571
  continue;
552
572
  }
@@ -554,14 +574,18 @@ export class Wallet {
554
574
  if (!session) {
555
575
  throw new Error("Signing session not set");
556
576
  }
557
- await this.handleSettlementSigningNoncesGeneratedEvent(event, session);
577
+ const signed = await this.handleSettlementTreeNoncesEvent(event, session);
578
+ if (signed) {
579
+ step = event.type;
580
+ }
581
+ break;
558
582
  }
559
583
  step = event.type;
560
584
  break;
561
585
  // the vtxo tree is signed, craft, sign and submit forfeit transactions
562
586
  // if any boarding utxos are involved, the settlement tx is also signed
563
587
  case SettlementEventType.BatchFinalization:
564
- if (step !== SettlementEventType.TreeNoncesAggregated) {
588
+ if (step !== SettlementEventType.TreeNonces) {
565
589
  continue;
566
590
  }
567
591
  if (!this.forfeitOutputScript) {
@@ -579,8 +603,10 @@ export class Wallet {
579
603
  if (step !== SettlementEventType.BatchFinalization) {
580
604
  continue;
581
605
  }
582
- abortController.abort();
583
- return event.commitmentTxid;
606
+ if (event.id === batchId) {
607
+ abortController.abort();
608
+ return event.commitmentTxid;
609
+ }
584
610
  }
585
611
  }
586
612
  }
@@ -602,22 +628,22 @@ export class Wallet {
602
628
  let onchainStopFunc;
603
629
  let indexerStopFunc;
604
630
  if (this.onchainProvider && boardingAddress) {
631
+ const findVoutOnTx = (tx) => {
632
+ return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
633
+ };
605
634
  onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
635
+ // find all utxos belonging to our boarding address
606
636
  const coins = txs
637
+ // filter txs where address is in output
638
+ .filter((tx) => findVoutOnTx(tx) !== -1)
639
+ // return utxo as Coin
607
640
  .map((tx) => {
608
- const vout = tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
609
- if (vout === -1) {
610
- console.warn(`No vout found for address ${boardingAddress} in transaction ${tx.txid}`);
611
- return null;
612
- }
613
- return {
614
- txid: tx.txid,
615
- vout,
616
- value: Number(tx.vout[vout].value),
617
- status: tx.status,
618
- };
619
- })
620
- .filter((coin) => coin !== null);
641
+ const { txid, status } = tx;
642
+ const vout = findVoutOnTx(tx);
643
+ const value = Number(tx.vout[vout].value);
644
+ return { txid, vout, value, status };
645
+ });
646
+ // and notify via callback
621
647
  eventCallback({
622
648
  type: "utxo",
623
649
  coins,
@@ -659,10 +685,10 @@ export class Wallet {
659
685
  };
660
686
  return stopFunc;
661
687
  }
662
- async handleBatchStartedEvent(event, intentId, serverPubKey, forfeitOutputScript) {
688
+ async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
663
689
  const utf8IntentId = new TextEncoder().encode(intentId);
664
690
  const intentIdHash = sha256(utf8IntentId);
665
- const intentIdHashStr = hex.encode(new Uint8Array(intentIdHash));
691
+ const intentIdHashStr = hex.encode(intentIdHash);
666
692
  let skip = true;
667
693
  // check if our intent ID hash matches any in the event
668
694
  for (const idHash of event.intentIdHashes) {
@@ -682,7 +708,7 @@ export class Wallet {
682
708
  value: event.batchExpiry,
683
709
  type: event.batchExpiry >= 512n ? "seconds" : "blocks",
684
710
  },
685
- pubkeys: [serverPubKey],
711
+ pubkeys: [forfeitPubKey],
686
712
  }).script;
687
713
  const sweepTapTreeRoot = tapLeafHash(sweepTapscript);
688
714
  return {
@@ -703,12 +729,19 @@ export class Wallet {
703
729
  throw new Error("Shared output not found");
704
730
  }
705
731
  session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
706
- 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);
707
735
  }
708
- async handleSettlementSigningNoncesGeneratedEvent(event, session) {
709
- session.setAggregatedNonces(event.treeNonces);
710
- const signatures = session.sign();
711
- 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;
712
745
  }
713
746
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
714
747
  // the signed forfeits transactions to submit
@@ -723,8 +756,6 @@ export class Wallet {
723
756
  const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
724
757
  // boarding utxo, we need to sign the settlement tx
725
758
  if (!vtxo) {
726
- hasBoardingUtxos = true;
727
- const inputIndexes = [];
728
759
  for (let i = 0; i < settlementPsbt.inputsLength; i++) {
729
760
  const settlementInput = settlementPsbt.getInput(i);
730
761
  if (!settlementInput.txid ||
@@ -740,9 +771,12 @@ export class Wallet {
740
771
  settlementPsbt.updateInput(i, {
741
772
  tapLeafScript: [input.forfeitTapLeafScript],
742
773
  });
743
- inputIndexes.push(i);
774
+ settlementPsbt = await this.identity.sign(settlementPsbt, [
775
+ i,
776
+ ]);
777
+ hasBoardingUtxos = true;
778
+ break;
744
779
  }
745
- settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
746
780
  continue;
747
781
  }
748
782
  if (isRecoverable(vtxo) || isSubdust(vtxo, this.dustAmount)) {
@@ -756,7 +790,7 @@ export class Wallet {
756
790
  throw new Error("not enough connectors received");
757
791
  }
758
792
  const connectorLeaf = connectorsLeaves[connectorIndex];
759
- const connectorTxId = hex.encode(sha256x2(connectorLeaf.toBytes(true)).reverse());
793
+ const connectorTxId = connectorLeaf.id;
760
794
  const connectorOutput = connectorLeaf.getOutput(0);
761
795
  if (!connectorOutput) {
762
796
  throw new Error("connector output not found");
@@ -797,111 +831,68 @@ export class Wallet {
797
831
  : undefined);
798
832
  }
799
833
  }
800
- async makeRegisterIntentSignature(bip322Inputs, outputs, onchainOutputsIndexes, cosignerPubKeys) {
834
+ async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
801
835
  const nowSeconds = Math.floor(Date.now() / 1000);
802
- const { inputs, inputTapTrees, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
836
+ const inputs = this.prepareIntentProofInputs(coins);
803
837
  const message = {
804
838
  type: "register",
805
- input_tap_trees: inputTapTrees,
806
839
  onchain_output_indexes: onchainOutputsIndexes,
807
840
  valid_at: nowSeconds,
808
841
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
809
842
  cosigners_public_keys: cosignerPubKeys,
810
843
  };
811
844
  const encodedMessage = JSON.stringify(message, null, 0);
812
- 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);
813
847
  return {
814
- signature,
848
+ proof: base64.encode(signedProof.toPSBT()),
815
849
  message: encodedMessage,
816
850
  };
817
851
  }
818
- async makeDeleteIntentSignature(bip322Inputs) {
852
+ async makeDeleteIntentSignature(coins) {
819
853
  const nowSeconds = Math.floor(Date.now() / 1000);
820
- const { inputs, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
854
+ const inputs = this.prepareIntentProofInputs(coins);
821
855
  const message = {
822
856
  type: "delete",
823
857
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
824
858
  };
825
859
  const encodedMessage = JSON.stringify(message, null, 0);
826
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer);
860
+ const proof = Intent.create(encodedMessage, inputs, []);
861
+ const signedProof = await this.identity.sign(proof);
827
862
  return {
828
- signature,
863
+ proof: base64.encode(signedProof.toPSBT()),
829
864
  message: encodedMessage,
830
865
  };
831
866
  }
832
- prepareBIP322Inputs(bip322Inputs) {
867
+ prepareIntentProofInputs(coins) {
833
868
  const inputs = [];
834
- const inputTapTrees = [];
835
- const inputExtraWitnesses = [];
836
- for (const bip322Input of bip322Inputs) {
837
- const vtxoScript = VtxoScript.decode(bip322Input.tapTree);
838
- 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
+ }
839
876
  inputs.push({
840
- txid: hex.decode(bip322Input.txid),
841
- index: bip322Input.vout,
877
+ txid: hex.decode(input.txid),
878
+ index: input.vout,
842
879
  witnessUtxo: {
843
- amount: BigInt(bip322Input.value),
880
+ amount: BigInt(input.value),
844
881
  script: vtxoScript.pkScript,
845
882
  },
846
883
  sequence,
847
- tapLeafScript: [bip322Input.intentTapLeafScript],
884
+ tapLeafScript: [input.intentTapLeafScript],
885
+ unknown,
848
886
  });
849
- inputTapTrees.push(hex.encode(bip322Input.tapTree));
850
- inputExtraWitnesses.push(bip322Input.extraWitness || []);
851
887
  }
852
- return {
853
- inputs,
854
- inputTapTrees,
855
- finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
856
- };
857
- }
858
- async makeBIP322Signature(message, inputs, finalizer, outputs) {
859
- const proof = BIP322.create(message, inputs, outputs);
860
- const signedProof = await this.identity.sign(proof);
861
- return BIP322.signature(signedProof, finalizer);
888
+ return inputs;
862
889
  }
863
890
  }
864
891
  Wallet.MIN_FEE_RATE = 1; // sats/vbyte
865
- function finalizeWithExtraWitnesses(inputExtraWitnesses) {
866
- return function (tx) {
867
- for (let i = 0; i < tx.inputsLength; i++) {
868
- try {
869
- tx.finalizeIdx(i);
870
- }
871
- catch (e) {
872
- // handle empty witness error
873
- if (e instanceof Error &&
874
- e.message.includes("finalize/taproot: empty witness")) {
875
- const tapLeaves = tx.getInput(i).tapLeafScript;
876
- if (!tapLeaves || tapLeaves.length <= 0)
877
- throw e;
878
- const [cb, s] = tapLeaves[0];
879
- const script = s.slice(0, -1);
880
- tx.updateInput(i, {
881
- finalScriptWitness: [
882
- script,
883
- TaprootControlBlock.encode(cb),
884
- ],
885
- });
886
- }
887
- }
888
- const finalScriptWitness = tx.getInput(i).finalScriptWitness;
889
- if (!finalScriptWitness)
890
- throw new Error("input not finalized");
891
- // input 0 and 1 spend the same pkscript
892
- const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
893
- if (extra && extra.length > 0) {
894
- tx.updateInput(i, {
895
- finalScriptWitness: [...extra, ...finalScriptWitness],
896
- });
897
- }
898
- }
899
- };
900
- }
901
- function getSequence(bip322Input) {
892
+ function getSequence(coin) {
902
893
  let sequence = undefined;
903
894
  try {
904
- const scriptWithLeafVersion = bip322Input.intentTapLeafScript[1];
895
+ const scriptWithLeafVersion = coin.intentTapLeafScript[1];
905
896
  const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
906
897
  const params = CSVMultisigTapscript.decode(script).params;
907
898
  sequence = bip68.encode(params.timelock.type === "blocks"
@@ -0,0 +1,4 @@
1
+ export { ExpoArkProvider } from "../providers/expoArk";
2
+ export { ExpoIndexerProvider } from "../providers/expoIndexer";
3
+ export type { ArkProvider } from "../providers/ark";
4
+ export type { IndexerProvider } from "../providers/indexer";
@@ -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
  }