@arkade-os/sdk 0.3.0-alpha.7 → 0.3.1-alpha.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 (88) hide show
  1. package/README.md +115 -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 +5 -2
  5. package/dist/cjs/identity/singleKey.js +5 -4
  6. package/dist/cjs/index.js +7 -3
  7. package/dist/cjs/{bip322 → intent}/index.js +37 -55
  8. package/dist/cjs/providers/ark.js +62 -23
  9. package/dist/cjs/providers/expoArk.js +82 -0
  10. package/dist/cjs/providers/expoIndexer.js +105 -0
  11. package/dist/cjs/providers/indexer.js +3 -1
  12. package/dist/cjs/providers/utils.js +122 -0
  13. package/dist/cjs/script/base.js +1 -2
  14. package/dist/cjs/script/tapscript.js +20 -21
  15. package/dist/cjs/script/vhtlc.js +2 -2
  16. package/dist/cjs/tree/signingSession.js +7 -8
  17. package/dist/cjs/tree/txTree.js +3 -4
  18. package/dist/cjs/tree/validation.js +2 -3
  19. package/dist/cjs/utils/arkTransaction.js +117 -12
  20. package/dist/cjs/utils/unknownFields.js +5 -5
  21. package/dist/cjs/wallet/index.js +1 -1
  22. package/dist/cjs/wallet/onchain.js +4 -5
  23. package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
  24. package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
  25. package/dist/cjs/wallet/serviceWorker/worker.js +25 -23
  26. package/dist/cjs/wallet/unroll.js +6 -7
  27. package/dist/cjs/wallet/utils.js +11 -0
  28. package/dist/cjs/wallet/vtxo-manager.js +381 -0
  29. package/dist/cjs/wallet/wallet.js +130 -143
  30. package/dist/esm/adapters/expo.js +3 -0
  31. package/dist/esm/arknote/index.js +2 -2
  32. package/dist/esm/forfeit.js +4 -1
  33. package/dist/esm/identity/singleKey.js +7 -6
  34. package/dist/esm/index.js +7 -6
  35. package/dist/esm/{bip322 → intent}/index.js +31 -48
  36. package/dist/esm/providers/ark.js +62 -23
  37. package/dist/esm/providers/expoArk.js +78 -0
  38. package/dist/esm/providers/expoIndexer.js +101 -0
  39. package/dist/esm/providers/indexer.js +3 -1
  40. package/dist/esm/providers/utils.js +87 -0
  41. package/dist/esm/script/base.js +1 -2
  42. package/dist/esm/script/tapscript.js +1 -2
  43. package/dist/esm/script/vhtlc.js +1 -1
  44. package/dist/esm/tree/signingSession.js +8 -9
  45. package/dist/esm/tree/txTree.js +3 -4
  46. package/dist/esm/tree/validation.js +2 -3
  47. package/dist/esm/utils/arkTransaction.js +108 -5
  48. package/dist/esm/utils/unknownFields.js +1 -1
  49. package/dist/esm/wallet/index.js +1 -1
  50. package/dist/esm/wallet/onchain.js +1 -2
  51. package/dist/esm/wallet/serviceWorker/utils.js +1 -8
  52. package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
  53. package/dist/esm/wallet/serviceWorker/worker.js +26 -24
  54. package/dist/esm/wallet/unroll.js +2 -3
  55. package/dist/esm/wallet/utils.js +8 -0
  56. package/dist/esm/wallet/vtxo-manager.js +372 -0
  57. package/dist/esm/wallet/wallet.js +124 -137
  58. package/dist/types/adapters/expo.d.ts +4 -0
  59. package/dist/types/arknote/index.d.ts +1 -1
  60. package/dist/types/forfeit.d.ts +2 -2
  61. package/dist/types/identity/index.d.ts +1 -1
  62. package/dist/types/identity/singleKey.d.ts +1 -1
  63. package/dist/types/index.d.ts +8 -7
  64. package/dist/types/intent/index.d.ts +41 -0
  65. package/dist/types/providers/ark.d.ts +190 -22
  66. package/dist/types/providers/expoArk.d.ts +22 -0
  67. package/dist/types/providers/expoIndexer.d.ts +18 -0
  68. package/dist/types/providers/indexer.d.ts +8 -8
  69. package/dist/types/providers/utils.d.ts +18 -0
  70. package/dist/types/script/base.d.ts +3 -2
  71. package/dist/types/tree/signingSession.d.ts +10 -10
  72. package/dist/types/utils/anchor.d.ts +2 -2
  73. package/dist/types/utils/arkTransaction.d.ts +16 -4
  74. package/dist/types/utils/unknownFields.d.ts +2 -2
  75. package/dist/types/wallet/index.d.ts +47 -7
  76. package/dist/types/wallet/onchain.d.ts +1 -1
  77. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
  78. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  79. package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
  80. package/dist/types/wallet/unroll.d.ts +1 -1
  81. package/dist/types/wallet/utils.d.ts +2 -0
  82. package/dist/types/wallet/vtxo-manager.d.ts +207 -0
  83. package/dist/types/wallet/wallet.d.ts +16 -4
  84. package/package.json +11 -3
  85. package/dist/cjs/bip322/errors.js +0 -13
  86. package/dist/esm/bip322/errors.js +0 -9
  87. package/dist/types/bip322/errors.d.ts +0 -6
  88. 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 { 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];
@@ -386,13 +411,15 @@ export class Wallet {
386
411
  }
387
412
  }
388
413
  }
389
- // if no params are provided, use all boarding and offchain utxos as inputs
414
+ // if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
390
415
  // and send all to the offchain address
391
416
  if (!params) {
392
417
  let amount = 0;
393
- const boardingUtxos = await this.getBoardingUtxos();
418
+ const exitScript = CSVMultisigTapscript.decode(hex.decode(this.boardingTapscript.exitScript));
419
+ const boardingTimelock = exitScript.params.timelock;
420
+ const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !hasBoardingTxExpired(utxo, boardingTimelock));
394
421
  amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
395
- const vtxos = await this.getVtxos();
422
+ const vtxos = await this.getVtxos({ withRecoverable: true });
396
423
  amount += vtxos.reduce((sum, input) => sum + input.value, 0);
397
424
  const inputs = [...boardingUtxos, ...vtxos];
398
425
  if (inputs.length === 0) {
@@ -435,7 +462,7 @@ export class Wallet {
435
462
  const signingPublicKeys = [];
436
463
  if (hasOffchainOutputs) {
437
464
  session = this.identity.signerSession();
438
- signingPublicKeys.push(hex.encode(session.getPublicKey()));
465
+ signingPublicKeys.push(hex.encode(await session.getPublicKey()));
439
466
  }
440
467
  const [intent, deleteIntent] = await Promise.all([
441
468
  this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
@@ -474,7 +501,7 @@ export class Wallet {
474
501
  if (step !== undefined) {
475
502
  continue;
476
503
  }
477
- const res = await this.handleBatchStartedEvent(event, intentId, this.arkServerPublicKey, this.forfeitOutputScript);
504
+ const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
478
505
  if (!res.skip) {
479
506
  step = event.type;
480
507
  sweepTapTreeRoot = res.sweepTapTreeRoot;
@@ -602,22 +629,22 @@ export class Wallet {
602
629
  let onchainStopFunc;
603
630
  let indexerStopFunc;
604
631
  if (this.onchainProvider && boardingAddress) {
632
+ const findVoutOnTx = (tx) => {
633
+ return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
634
+ };
605
635
  onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
636
+ // find all utxos belonging to our boarding address
606
637
  const coins = txs
638
+ // filter txs where address is in output
639
+ .filter((tx) => findVoutOnTx(tx) !== -1)
640
+ // return utxo as Coin
607
641
  .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);
642
+ const { txid, status } = tx;
643
+ const vout = findVoutOnTx(tx);
644
+ const value = Number(tx.vout[vout].value);
645
+ return { txid, vout, value, status };
646
+ });
647
+ // and notify via callback
621
648
  eventCallback({
622
649
  type: "utxo",
623
650
  coins,
@@ -659,10 +686,10 @@ export class Wallet {
659
686
  };
660
687
  return stopFunc;
661
688
  }
662
- async handleBatchStartedEvent(event, intentId, serverPubKey, forfeitOutputScript) {
689
+ async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
663
690
  const utf8IntentId = new TextEncoder().encode(intentId);
664
691
  const intentIdHash = sha256(utf8IntentId);
665
- const intentIdHashStr = hex.encode(new Uint8Array(intentIdHash));
692
+ const intentIdHashStr = hex.encode(intentIdHash);
666
693
  let skip = true;
667
694
  // check if our intent ID hash matches any in the event
668
695
  for (const idHash of event.intentIdHashes) {
@@ -682,7 +709,7 @@ export class Wallet {
682
709
  value: event.batchExpiry,
683
710
  type: event.batchExpiry >= 512n ? "seconds" : "blocks",
684
711
  },
685
- pubkeys: [serverPubKey],
712
+ pubkeys: [forfeitPubKey],
686
713
  }).script;
687
714
  const sweepTapTreeRoot = tapLeafHash(sweepTapscript);
688
715
  return {
@@ -703,12 +730,15 @@ export class Wallet {
703
730
  throw new Error("Shared output not found");
704
731
  }
705
732
  session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
706
- await this.arkProvider.submitTreeNonces(event.id, hex.encode(session.getPublicKey()), session.getNonces());
733
+ const pubkey = hex.encode(await session.getPublicKey());
734
+ const nonces = await session.getNonces();
735
+ await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
707
736
  }
708
737
  async handleSettlementSigningNoncesGeneratedEvent(event, session) {
709
738
  session.setAggregatedNonces(event.treeNonces);
710
- const signatures = session.sign();
711
- await this.arkProvider.submitTreeSignatures(event.id, hex.encode(session.getPublicKey()), signatures);
739
+ const signatures = await session.sign();
740
+ const pubkey = hex.encode(await session.getPublicKey());
741
+ await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
712
742
  }
713
743
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
714
744
  // the signed forfeits transactions to submit
@@ -756,7 +786,7 @@ export class Wallet {
756
786
  throw new Error("not enough connectors received");
757
787
  }
758
788
  const connectorLeaf = connectorsLeaves[connectorIndex];
759
- const connectorTxId = hex.encode(sha256x2(connectorLeaf.toBytes(true)).reverse());
789
+ const connectorTxId = connectorLeaf.id;
760
790
  const connectorOutput = connectorLeaf.getOutput(0);
761
791
  if (!connectorOutput) {
762
792
  throw new Error("connector output not found");
@@ -797,111 +827,68 @@ export class Wallet {
797
827
  : undefined);
798
828
  }
799
829
  }
800
- async makeRegisterIntentSignature(bip322Inputs, outputs, onchainOutputsIndexes, cosignerPubKeys) {
830
+ async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
801
831
  const nowSeconds = Math.floor(Date.now() / 1000);
802
- const { inputs, inputTapTrees, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
832
+ const inputs = this.prepareIntentProofInputs(coins);
803
833
  const message = {
804
834
  type: "register",
805
- input_tap_trees: inputTapTrees,
806
835
  onchain_output_indexes: onchainOutputsIndexes,
807
836
  valid_at: nowSeconds,
808
837
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
809
838
  cosigners_public_keys: cosignerPubKeys,
810
839
  };
811
840
  const encodedMessage = JSON.stringify(message, null, 0);
812
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer, outputs);
841
+ const proof = Intent.create(encodedMessage, inputs, outputs);
842
+ const signedProof = await this.identity.sign(proof);
813
843
  return {
814
- signature,
844
+ proof: base64.encode(signedProof.toPSBT()),
815
845
  message: encodedMessage,
816
846
  };
817
847
  }
818
- async makeDeleteIntentSignature(bip322Inputs) {
848
+ async makeDeleteIntentSignature(coins) {
819
849
  const nowSeconds = Math.floor(Date.now() / 1000);
820
- const { inputs, finalizer } = this.prepareBIP322Inputs(bip322Inputs);
850
+ const inputs = this.prepareIntentProofInputs(coins);
821
851
  const message = {
822
852
  type: "delete",
823
853
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
824
854
  };
825
855
  const encodedMessage = JSON.stringify(message, null, 0);
826
- const signature = await this.makeBIP322Signature(encodedMessage, inputs, finalizer);
856
+ const proof = Intent.create(encodedMessage, inputs, []);
857
+ const signedProof = await this.identity.sign(proof);
827
858
  return {
828
- signature,
859
+ proof: base64.encode(signedProof.toPSBT()),
829
860
  message: encodedMessage,
830
861
  };
831
862
  }
832
- prepareBIP322Inputs(bip322Inputs) {
863
+ prepareIntentProofInputs(coins) {
833
864
  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);
865
+ for (const input of coins) {
866
+ const vtxoScript = VtxoScript.decode(input.tapTree);
867
+ const sequence = getSequence(input);
868
+ const unknown = [VtxoTaprootTree.encode(input.tapTree)];
869
+ if (input.extraWitness) {
870
+ unknown.push(ConditionWitness.encode(input.extraWitness));
871
+ }
839
872
  inputs.push({
840
- txid: hex.decode(bip322Input.txid),
841
- index: bip322Input.vout,
873
+ txid: hex.decode(input.txid),
874
+ index: input.vout,
842
875
  witnessUtxo: {
843
- amount: BigInt(bip322Input.value),
876
+ amount: BigInt(input.value),
844
877
  script: vtxoScript.pkScript,
845
878
  },
846
879
  sequence,
847
- tapLeafScript: [bip322Input.intentTapLeafScript],
880
+ tapLeafScript: [input.intentTapLeafScript],
881
+ unknown,
848
882
  });
849
- inputTapTrees.push(hex.encode(bip322Input.tapTree));
850
- inputExtraWitnesses.push(bip322Input.extraWitness || []);
851
883
  }
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);
884
+ return inputs;
862
885
  }
863
886
  }
864
887
  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) {
888
+ function getSequence(coin) {
902
889
  let sequence = undefined;
903
890
  try {
904
- const scriptWithLeafVersion = bip322Input.intentTapLeafScript[1];
891
+ const scriptWithLeafVersion = coin.intentTapLeafScript[1];
905
892
  const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
906
893
  const params = CSVMultisigTapscript.decode(script).params;
907
894
  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 "@scure/btc-signer";
2
+ import { TransactionInputUpdate } from "@scure/btc-signer/psbt.js";
3
3
  export declare function buildForfeitTx(inputs: TransactionInputUpdate[], forfeitPkScript: Uint8Array, txLocktime?: number): Transaction;
@@ -4,7 +4,7 @@ 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";
@@ -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
  }
@@ -5,11 +5,12 @@ import { ArkAddress } from "./script/address";
5
5
  import { VHTLC } from "./script/vhtlc";
6
6
  import { DefaultVtxo } from "./script/default";
7
7
  import { VtxoScript, EncodedVtxoScript, TapLeafScript } from "./script/base";
8
- import { TxType, IWallet, WalletConfig, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, GetVtxosFilter, TapLeaves } from "./wallet";
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,19 +18,19 @@ 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, TreeNoncesAggregatedEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
21
22
  import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
22
- import { 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
- import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse } from "./providers/indexer";
28
+ import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent } from "./providers/indexer";
28
29
  import { Nonces } from "./musig2/nonces";
29
30
  import { PartialSig } from "./musig2/sign";
30
31
  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, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, BIP322, TxTree, P2A, Unroll, Transaction, };
35
- export type { Identity, IWallet, WalletConfig, 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, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
35
+ 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, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, };
36
+ 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, TreeNoncesAggregatedEvent, 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 { Transaction } from "@scure/btc-signer";
2
+ import { TransactionInput, TransactionOutput } from "@scure/btc-signer/psbt.js";
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
+ }