@arkade-os/sdk 0.2.3 → 0.3.0-alpha.1

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 (121) hide show
  1. package/README.md +114 -43
  2. package/dist/cjs/adapters/asyncStorage.js +5 -0
  3. package/dist/cjs/adapters/fileSystem.js +5 -0
  4. package/dist/cjs/adapters/indexedDB.js +5 -0
  5. package/dist/cjs/adapters/localStorage.js +5 -0
  6. package/dist/cjs/arknote/index.js +4 -4
  7. package/dist/cjs/bip322/index.js +11 -9
  8. package/dist/cjs/forfeit.js +2 -2
  9. package/dist/cjs/identity/index.js +15 -0
  10. package/dist/cjs/identity/singleKey.js +24 -5
  11. package/dist/cjs/index.js +7 -5
  12. package/dist/cjs/musig2/keys.js +7 -7
  13. package/dist/cjs/musig2/nonces.js +1 -1
  14. package/dist/cjs/musig2/sign.js +6 -6
  15. package/dist/cjs/networks.js +6 -6
  16. package/dist/cjs/repositories/contractRepository.js +130 -0
  17. package/dist/cjs/repositories/index.js +18 -0
  18. package/dist/cjs/repositories/walletRepository.js +136 -0
  19. package/dist/cjs/script/address.js +3 -3
  20. package/dist/cjs/script/base.js +7 -7
  21. package/dist/cjs/script/tapscript.js +21 -21
  22. package/dist/cjs/script/vhtlc.js +2 -2
  23. package/dist/cjs/storage/asyncStorage.js +47 -0
  24. package/dist/cjs/storage/fileSystem.js +138 -0
  25. package/dist/cjs/storage/inMemory.js +21 -0
  26. package/dist/cjs/storage/indexedDB.js +97 -0
  27. package/dist/cjs/storage/localStorage.js +48 -0
  28. package/dist/cjs/tree/signingSession.js +12 -11
  29. package/dist/cjs/tree/txTree.js +6 -6
  30. package/dist/cjs/tree/validation.js +5 -5
  31. package/dist/cjs/utils/arkTransaction.js +5 -5
  32. package/dist/cjs/utils/unknownFields.js +5 -5
  33. package/dist/cjs/wallet/onchain.js +16 -10
  34. package/dist/cjs/wallet/serviceWorker/request.js +4 -14
  35. package/dist/cjs/wallet/serviceWorker/response.js +0 -13
  36. package/dist/cjs/wallet/serviceWorker/wallet.js +124 -130
  37. package/dist/cjs/wallet/serviceWorker/worker.js +84 -53
  38. package/dist/cjs/wallet/unroll.js +6 -6
  39. package/dist/cjs/wallet/wallet.js +37 -20
  40. package/dist/esm/adapters/asyncStorage.js +1 -0
  41. package/dist/esm/adapters/fileSystem.js +1 -0
  42. package/dist/esm/adapters/indexedDB.js +1 -0
  43. package/dist/esm/adapters/localStorage.js +1 -0
  44. package/dist/esm/arknote/index.js +2 -2
  45. package/dist/esm/bip322/index.js +4 -2
  46. package/dist/esm/forfeit.js +1 -1
  47. package/dist/esm/identity/index.js +1 -1
  48. package/dist/esm/identity/singleKey.js +22 -3
  49. package/dist/esm/index.js +5 -4
  50. package/dist/esm/musig2/keys.js +7 -7
  51. package/dist/esm/musig2/nonces.js +1 -1
  52. package/dist/esm/musig2/sign.js +5 -5
  53. package/dist/esm/networks.js +1 -1
  54. package/dist/esm/repositories/contractRepository.js +126 -0
  55. package/dist/esm/repositories/index.js +2 -0
  56. package/dist/esm/repositories/walletRepository.js +132 -0
  57. package/dist/esm/script/address.js +1 -1
  58. package/dist/esm/script/base.js +3 -3
  59. package/dist/esm/script/tapscript.js +2 -2
  60. package/dist/esm/script/vhtlc.js +1 -1
  61. package/dist/esm/storage/asyncStorage.js +43 -0
  62. package/dist/esm/storage/fileSystem.js +101 -0
  63. package/dist/esm/storage/inMemory.js +17 -0
  64. package/dist/esm/storage/indexedDB.js +93 -0
  65. package/dist/esm/storage/localStorage.js +44 -0
  66. package/dist/esm/tree/signingSession.js +4 -3
  67. package/dist/esm/tree/txTree.js +2 -2
  68. package/dist/esm/tree/validation.js +2 -2
  69. package/dist/esm/utils/arkTransaction.js +2 -2
  70. package/dist/esm/utils/unknownFields.js +1 -1
  71. package/dist/esm/wallet/onchain.js +14 -8
  72. package/dist/esm/wallet/serviceWorker/request.js +4 -14
  73. package/dist/esm/wallet/serviceWorker/response.js +0 -13
  74. package/dist/esm/wallet/serviceWorker/wallet.js +125 -131
  75. package/dist/esm/wallet/serviceWorker/worker.js +85 -54
  76. package/dist/esm/wallet/unroll.js +2 -2
  77. package/dist/esm/wallet/wallet.js +25 -8
  78. package/dist/types/adapters/asyncStorage.d.ts +2 -0
  79. package/dist/types/adapters/fileSystem.d.ts +2 -0
  80. package/dist/types/adapters/indexedDB.d.ts +2 -0
  81. package/dist/types/adapters/localStorage.d.ts +2 -0
  82. package/dist/types/arknote/index.d.ts +1 -1
  83. package/dist/types/bip322/index.d.ts +2 -2
  84. package/dist/types/forfeit.d.ts +1 -1
  85. package/dist/types/identity/index.d.ts +4 -2
  86. package/dist/types/identity/singleKey.d.ts +13 -2
  87. package/dist/types/index.d.ts +5 -5
  88. package/dist/types/repositories/contractRepository.d.ts +20 -0
  89. package/dist/types/repositories/index.d.ts +2 -0
  90. package/dist/types/repositories/walletRepository.d.ts +38 -0
  91. package/dist/types/script/address.d.ts +1 -1
  92. package/dist/types/script/base.d.ts +1 -1
  93. package/dist/types/script/default.d.ts +1 -1
  94. package/dist/types/script/tapscript.d.ts +1 -1
  95. package/dist/types/script/vhtlc.d.ts +1 -1
  96. package/dist/types/storage/asyncStorage.d.ts +9 -0
  97. package/dist/types/storage/fileSystem.d.ts +11 -0
  98. package/dist/types/storage/inMemory.d.ts +8 -0
  99. package/dist/types/storage/index.d.ts +6 -0
  100. package/dist/types/storage/indexedDB.d.ts +12 -0
  101. package/dist/types/storage/localStorage.d.ts +8 -0
  102. package/dist/types/tree/txTree.d.ts +1 -1
  103. package/dist/types/tree/validation.d.ts +1 -1
  104. package/dist/types/utils/anchor.d.ts +1 -1
  105. package/dist/types/utils/arkTransaction.d.ts +3 -3
  106. package/dist/types/utils/unknownFields.d.ts +1 -1
  107. package/dist/types/wallet/index.d.ts +4 -1
  108. package/dist/types/wallet/onchain.d.ts +5 -4
  109. package/dist/types/wallet/serviceWorker/request.d.ts +1 -7
  110. package/dist/types/wallet/serviceWorker/response.d.ts +1 -8
  111. package/dist/types/wallet/serviceWorker/wallet.d.ts +67 -21
  112. package/dist/types/wallet/serviceWorker/worker.d.ts +16 -4
  113. package/dist/types/wallet/unroll.d.ts +1 -1
  114. package/dist/types/wallet/wallet.d.ts +5 -1
  115. package/package.json +39 -15
  116. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +0 -185
  117. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +0 -181
  118. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +0 -20
  119. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +0 -14
  120. /package/dist/cjs/{wallet/serviceWorker/db/vtxo → storage}/index.js +0 -0
  121. /package/dist/esm/{wallet/serviceWorker/db/vtxo → storage}/index.js +0 -0
@@ -1,92 +1,90 @@
1
1
  import { Response } from './response.js';
2
- import { base64, hex } from "@scure/base";
3
- import { SingleKey } from '../../identity/singleKey.js';
4
- import { TreeSignerSession } from '../../tree/signingSession.js';
5
- import { Transaction } from "@scure/btc-signer";
2
+ import { hex } from "@scure/base";
3
+ import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
4
+ import { WalletRepositoryImpl } from '../../repositories/walletRepository.js';
5
+ import { ContractRepositoryImpl } from '../../repositories/contractRepository.js';
6
+ import { setupServiceWorker } from './utils.js';
7
+ const isPrivateKeyIdentity = (identity) => {
8
+ return (identity.toHex !== undefined &&
9
+ typeof identity.toHex === "function" &&
10
+ typeof identity.toHex() === "string" &&
11
+ identity.toHex().length > 0);
12
+ };
6
13
  class UnexpectedResponseError extends Error {
7
14
  constructor(response) {
8
15
  super(`Unexpected response type. Got: ${JSON.stringify(response, null, 2)}`);
9
16
  this.name = "UnexpectedResponseError";
10
17
  }
11
18
  }
12
- /**
13
- * Service Worker-based wallet implementation for browser environments.
14
- *
15
- * This wallet uses a service worker as a backend to handle wallet logic,
16
- * providing secure key storage and transaction signing in web applications.
17
- * The service worker runs in a separate thread and can persist data between
18
- * browser sessions.
19
- *
20
- * @example
21
- * ```typescript
22
- * // Create and initialize the service worker wallet
23
- * const serviceWorker = await setupServiceWorker("/service-worker.js");
24
- * const wallet = new ServiceWorkerWallet(serviceWorker);
25
- * await wallet.init({
26
- * privateKey: 'your_private_key_hex',
27
- * arkServerUrl: 'https://ark.example.com'
28
- * });
29
- *
30
- * // Use like any other wallet
31
- * const address = await wallet.getAddress();
32
- * const balance = await wallet.getBalance();
33
- * ```
34
- */
35
19
  export class ServiceWorkerWallet {
36
- constructor(serviceWorker) {
20
+ constructor(serviceWorker, identity, walletRepository, contractRepository) {
37
21
  this.serviceWorker = serviceWorker;
22
+ this.identity = identity;
23
+ this.walletRepository = walletRepository;
24
+ this.contractRepository = contractRepository;
38
25
  }
39
- async getStatus() {
40
- const message = {
41
- type: "GET_STATUS",
42
- id: getRandomId(),
43
- };
44
- const response = await this.sendMessage(message);
45
- if (Response.isWalletStatus(response)) {
46
- const { walletInitialized, xOnlyPublicKey } = response.status;
47
- if (walletInitialized)
48
- this.cachedXOnlyPublicKey = xOnlyPublicKey;
49
- return response.status;
50
- }
51
- throw new UnexpectedResponseError(response);
52
- }
53
- async init(config, failIfInitialized = false) {
54
- // Check if wallet is already initialized
55
- const statusMessage = {
56
- type: "GET_STATUS",
57
- id: getRandomId(),
58
- };
59
- const response = await this.sendMessage(statusMessage);
60
- if (Response.isWalletStatus(response) &&
61
- response.status.walletInitialized) {
62
- if (failIfInitialized) {
63
- throw new Error("Wallet already initialized");
64
- }
65
- this.cachedXOnlyPublicKey = response.status.xOnlyPublicKey;
66
- return;
26
+ static async create(options) {
27
+ // Default to IndexedDB for service worker context
28
+ const storage = options.storage || new IndexedDBStorageAdapter("wallet-db");
29
+ // Create repositories
30
+ const walletRepo = new WalletRepositoryImpl(storage);
31
+ const contractRepo = new ContractRepositoryImpl(storage);
32
+ // Extract identity and check if it can expose private key
33
+ const identity = isPrivateKeyIdentity(options.identity)
34
+ ? options.identity
35
+ : null;
36
+ if (!identity) {
37
+ throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose its private key");
67
38
  }
68
- // If not initialized, proceed with initialization
69
- const message = {
39
+ // Extract private key for service worker initialization
40
+ const privateKey = identity.toHex();
41
+ // Create the wallet instance
42
+ const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
43
+ // Initialize the service worker with the config
44
+ const initMessage = {
70
45
  type: "INIT_WALLET",
71
46
  id: getRandomId(),
72
- privateKey: config.privateKey,
73
- arkServerUrl: config.arkServerUrl,
74
- arkServerPublicKey: config.arkServerPublicKey,
47
+ privateKey,
48
+ arkServerUrl: options.arkServerUrl,
49
+ arkServerPublicKey: options.arkServerPublicKey,
75
50
  };
76
- await this.sendMessage(message);
77
- const privKeyBytes = hex.decode(config.privateKey);
78
- // cache the identity xOnlyPublicKey
79
- this.cachedXOnlyPublicKey =
80
- SingleKey.fromPrivateKey(privKeyBytes).xOnlyPublicKey();
51
+ // Initialize the service worker
52
+ await wallet.sendMessage(initMessage);
53
+ return wallet;
81
54
  }
82
- async clear() {
83
- const message = {
84
- type: "CLEAR",
85
- id: getRandomId(),
86
- };
87
- await this.sendMessage(message);
88
- // clear the cached xOnlyPublicKey
89
- this.cachedXOnlyPublicKey = undefined;
55
+ /**
56
+ * Simplified setup method that handles service worker registration,
57
+ * identity creation, and wallet initialization automatically.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // One-liner setup - handles everything automatically!
62
+ * const wallet = await ServiceWorkerWallet.setup({
63
+ * serviceWorkerPath: '/service-worker.js',
64
+ * arkServerUrl: 'https://mutinynet.arkade.sh'
65
+ * });
66
+ *
67
+ * // With custom identity
68
+ * const identity = SingleKey.fromHex('your_private_key_hex');
69
+ * const wallet = await ServiceWorkerWallet.setup({
70
+ * serviceWorkerPath: '/service-worker.js',
71
+ * arkServerUrl: 'https://mutinynet.arkade.sh',
72
+ * identity
73
+ * });
74
+ * ```
75
+ */
76
+ static async setup(options) {
77
+ // Register and setup the service worker
78
+ const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
79
+ // Use the existing create method
80
+ return await ServiceWorkerWallet.create({
81
+ arkServerPublicKey: options.arkServerPublicKey,
82
+ arkServerUrl: options.arkServerUrl,
83
+ esploraUrl: options.esploraUrl,
84
+ identity: options.identity,
85
+ serviceWorker,
86
+ storage: options.storage,
87
+ });
90
88
  }
91
89
  // send a message and wait for a response
92
90
  async sendMessage(message) {
@@ -112,6 +110,21 @@ export class ServiceWorkerWallet {
112
110
  this.serviceWorker.postMessage(message);
113
111
  });
114
112
  }
113
+ async clear() {
114
+ const message = {
115
+ type: "CLEAR",
116
+ id: getRandomId(),
117
+ };
118
+ // Clear page-side storage to maintain parity with SW
119
+ try {
120
+ const address = await this.getAddress();
121
+ await this.walletRepository.clearVtxos(address);
122
+ }
123
+ catch (_) {
124
+ console.warn("Failed to clear vtxos from wallet repository");
125
+ }
126
+ await this.sendMessage(message);
127
+ }
115
128
  async getAddress() {
116
129
  const message = {
117
130
  type: "GET_ADDRESS",
@@ -160,37 +173,64 @@ export class ServiceWorkerWallet {
160
173
  throw new Error(`Failed to get balance: ${error}`);
161
174
  }
162
175
  }
163
- async getVtxos(filter) {
176
+ async getBoardingUtxos() {
164
177
  const message = {
165
- type: "GET_VTXOS",
178
+ type: "GET_BOARDING_UTXOS",
166
179
  id: getRandomId(),
167
- filter,
168
180
  };
169
181
  try {
170
182
  const response = await this.sendMessage(message);
171
- if (Response.isVtxos(response)) {
172
- return response.vtxos;
183
+ if (Response.isBoardingUtxos(response)) {
184
+ return response.boardingUtxos;
173
185
  }
174
186
  throw new UnexpectedResponseError(response);
175
187
  }
176
188
  catch (error) {
177
- throw new Error(`Failed to get vtxos: ${error}`);
189
+ throw new Error(`Failed to get boarding UTXOs: ${error}`);
178
190
  }
179
191
  }
180
- async getBoardingUtxos() {
192
+ async getStatus() {
181
193
  const message = {
182
- type: "GET_BOARDING_UTXOS",
194
+ type: "GET_STATUS",
195
+ id: getRandomId(),
196
+ };
197
+ const response = await this.sendMessage(message);
198
+ if (Response.isWalletStatus(response)) {
199
+ return response.status;
200
+ }
201
+ throw new UnexpectedResponseError(response);
202
+ }
203
+ async getTransactionHistory() {
204
+ const message = {
205
+ type: "GET_TRANSACTION_HISTORY",
183
206
  id: getRandomId(),
184
207
  };
185
208
  try {
186
209
  const response = await this.sendMessage(message);
187
- if (Response.isBoardingUtxos(response)) {
188
- return response.boardingUtxos;
210
+ if (Response.isTransactionHistory(response)) {
211
+ return response.transactions;
189
212
  }
190
213
  throw new UnexpectedResponseError(response);
191
214
  }
192
215
  catch (error) {
193
- throw new Error(`Failed to get boarding UTXOs: ${error}`);
216
+ throw new Error(`Failed to get transaction history: ${error}`);
217
+ }
218
+ }
219
+ async getVtxos(filter) {
220
+ const message = {
221
+ type: "GET_VTXOS",
222
+ id: getRandomId(),
223
+ filter,
224
+ };
225
+ try {
226
+ const response = await this.sendMessage(message);
227
+ if (Response.isVtxos(response)) {
228
+ return response.vtxos;
229
+ }
230
+ throw new UnexpectedResponseError(response);
231
+ }
232
+ catch (error) {
233
+ throw new Error(`Failed to get vtxos: ${error}`);
194
234
  }
195
235
  }
196
236
  async sendBitcoin(params) {
@@ -247,52 +287,6 @@ export class ServiceWorkerWallet {
247
287
  throw new Error(`Settlement failed: ${error}`);
248
288
  }
249
289
  }
250
- async getTransactionHistory() {
251
- const message = {
252
- type: "GET_TRANSACTION_HISTORY",
253
- id: getRandomId(),
254
- };
255
- try {
256
- const response = await this.sendMessage(message);
257
- if (Response.isTransactionHistory(response)) {
258
- return response.transactions;
259
- }
260
- throw new UnexpectedResponseError(response);
261
- }
262
- catch (error) {
263
- throw new Error(`Failed to get transaction history: ${error}`);
264
- }
265
- }
266
- xOnlyPublicKey() {
267
- if (!this.cachedXOnlyPublicKey) {
268
- throw new Error("Wallet not initialized");
269
- }
270
- return this.cachedXOnlyPublicKey;
271
- }
272
- signerSession() {
273
- return TreeSignerSession.random();
274
- }
275
- async sign(tx, inputIndexes) {
276
- const message = {
277
- type: "SIGN",
278
- tx: base64.encode(tx.toPSBT()),
279
- inputIndexes,
280
- id: getRandomId(),
281
- };
282
- try {
283
- const response = await this.sendMessage(message);
284
- if (Response.isSignSuccess(response)) {
285
- return Transaction.fromPSBT(base64.decode(response.tx), {
286
- allowUnknown: true,
287
- allowUnknownInputs: true,
288
- });
289
- }
290
- throw new UnexpectedResponseError(response);
291
- }
292
- catch (error) {
293
- throw new Error(`Failed to sign: ${error}`);
294
- }
295
- }
296
290
  }
297
291
  function getRandomId() {
298
292
  const randomValue = crypto.getRandomValues(new Uint8Array(16));
@@ -5,19 +5,53 @@ import { Wallet } from '../wallet.js';
5
5
  import { Request } from './request.js';
6
6
  import { Response } from './response.js';
7
7
  import { RestArkProvider } from '../../providers/ark.js';
8
- import { IndexedDBVtxoRepository } from './db/vtxo/idb.js';
9
8
  import { vtxosToTxs } from '../../utils/transactionHistory.js';
10
9
  import { RestIndexerProvider } from '../../providers/indexer.js';
11
- import { base64, hex } from "@scure/base";
12
- import { Transaction } from "@scure/btc-signer";
10
+ import { hex } from "@scure/base";
11
+ import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
12
+ import { WalletRepositoryImpl, } from '../../repositories/walletRepository.js';
13
13
  /**
14
14
  * Worker is a class letting to interact with ServiceWorkerWallet from the client
15
15
  * it aims to be run in a service worker context
16
16
  */
17
17
  export class Worker {
18
- constructor(vtxoRepository = new IndexedDBVtxoRepository(), messageCallback = () => { }) {
19
- this.vtxoRepository = vtxoRepository;
18
+ constructor(messageCallback = () => { }) {
20
19
  this.messageCallback = messageCallback;
20
+ this.storage = new IndexedDBStorageAdapter("arkade-service-worker", 1);
21
+ this.walletRepository = new WalletRepositoryImpl(this.storage);
22
+ }
23
+ /**
24
+ * Get spendable vtxos for the current wallet address
25
+ */
26
+ async getSpendableVtxos() {
27
+ if (!this.wallet)
28
+ return [];
29
+ const address = await this.wallet.getAddress();
30
+ const allVtxos = await this.walletRepository.getVtxos(address);
31
+ return allVtxos.filter(isSpendable);
32
+ }
33
+ /**
34
+ * Get swept vtxos for the current wallet address
35
+ */
36
+ async getSweptVtxos() {
37
+ if (!this.wallet)
38
+ return [];
39
+ const address = await this.wallet.getAddress();
40
+ const allVtxos = await this.walletRepository.getVtxos(address);
41
+ return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept" && isSpendable(vtxo));
42
+ }
43
+ /**
44
+ * Get all vtxos categorized by type
45
+ */
46
+ async getAllVtxos() {
47
+ if (!this.wallet)
48
+ return { spendable: [], spent: [] };
49
+ const address = await this.wallet.getAddress();
50
+ const allVtxos = await this.walletRepository.getVtxos(address);
51
+ return {
52
+ spendable: allVtxos.filter(isSpendable),
53
+ spent: allVtxos.filter((vtxo) => !isSpendable(vtxo)),
54
+ };
21
55
  }
22
56
  async start(withServiceWorkerUpdate = true) {
23
57
  self.addEventListener("message", async (event) => {
@@ -38,7 +72,8 @@ export class Worker {
38
72
  if (this.vtxoSubscription) {
39
73
  this.vtxoSubscription.abort();
40
74
  }
41
- await this.vtxoRepository.close();
75
+ // Clear storage - this replaces vtxoRepository.close()
76
+ await this.storage.clear();
42
77
  this.wallet = undefined;
43
78
  this.arkProvider = undefined;
44
79
  this.indexerProvider = undefined;
@@ -57,8 +92,6 @@ export class Worker {
57
92
  !this.wallet.boardingTapscript) {
58
93
  return;
59
94
  }
60
- // subscribe to address updates
61
- await this.vtxoRepository.open();
62
95
  const encodedOffchainTapscript = this.wallet.offchainTapscript.encode();
63
96
  const forfeit = this.wallet.offchainTapscript.forfeit();
64
97
  const exit = this.wallet.offchainTapscript.exit();
@@ -73,7 +106,9 @@ export class Worker {
73
106
  intentTapLeafScript: exit,
74
107
  tapTree: encodedOffchainTapscript,
75
108
  }));
76
- await this.vtxoRepository.addOrUpdate(vtxos);
109
+ // Get wallet address and save vtxos using unified repository
110
+ const address = await this.wallet.getAddress();
111
+ await this.walletRepository.saveVtxos(address, vtxos);
77
112
  this.processVtxoSubscription({
78
113
  script,
79
114
  vtxoScript: this.wallet.offchainTapscript,
@@ -99,7 +134,11 @@ export class Worker {
99
134
  intentTapLeafScript,
100
135
  tapTree,
101
136
  }));
102
- await this.vtxoRepository.addOrUpdate(extendedVtxos);
137
+ // Get wallet address and save vtxos using unified repository
138
+ const address = await this.wallet.getAddress();
139
+ await this.walletRepository.saveVtxos(address, extendedVtxos);
140
+ // Notify all clients about the vtxo update
141
+ this.sendMessageToAllClients("VTXO_UPDATE", "");
103
142
  }
104
143
  }
105
144
  catch (error) {
@@ -107,7 +146,7 @@ export class Worker {
107
146
  }
108
147
  }
109
148
  async handleClear(event) {
110
- this.clear();
149
+ await this.clear();
111
150
  if (Request.isBase(event.data)) {
112
151
  event.source?.postMessage(Response.clearResponse(event.data.id, true));
113
152
  }
@@ -119,13 +158,22 @@ export class Worker {
119
158
  event.source?.postMessage(Response.error(message.id, "Invalid INIT_WALLET message format"));
120
159
  return;
121
160
  }
161
+ if (!message.privateKey) {
162
+ const err = "Missing privateKey";
163
+ event.source?.postMessage(Response.error(message.id, err));
164
+ console.error(err);
165
+ return;
166
+ }
122
167
  try {
123
- this.arkProvider = new RestArkProvider(message.arkServerUrl);
124
- this.indexerProvider = new RestIndexerProvider(message.arkServerUrl);
168
+ const { arkServerPublicKey, arkServerUrl, privateKey } = message;
169
+ const identity = SingleKey.fromHex(privateKey);
170
+ this.arkProvider = new RestArkProvider(arkServerUrl);
171
+ this.indexerProvider = new RestIndexerProvider(arkServerUrl);
125
172
  this.wallet = await Wallet.create({
126
- identity: SingleKey.fromHex(message.privateKey),
127
- arkServerUrl: message.arkServerUrl,
128
- arkServerPublicKey: message.arkServerPublicKey,
173
+ identity,
174
+ arkServerUrl,
175
+ arkServerPublicKey,
176
+ storage: this.storage, // Use unified storage for wallet too
129
177
  });
130
178
  event.source?.postMessage(Response.walletInitialized(message.id));
131
179
  await this.onWalletInitialized();
@@ -251,8 +299,8 @@ export class Worker {
251
299
  try {
252
300
  const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
253
301
  this.wallet.getBoardingUtxos(),
254
- this.vtxoRepository.getSpendableVtxos(),
255
- this.vtxoRepository.getSweptVtxos(),
302
+ this.getSpendableVtxos(),
303
+ this.getSweptVtxos(),
256
304
  ]);
257
305
  // boarding
258
306
  let confirmed = 0;
@@ -318,7 +366,7 @@ export class Worker {
318
366
  return;
319
367
  }
320
368
  try {
321
- let vtxos = await this.vtxoRepository.getSpendableVtxos();
369
+ let vtxos = await this.getSpendableVtxos();
322
370
  if (!message.filter?.withRecoverable) {
323
371
  if (!this.wallet)
324
372
  throw new Error("Wallet not initialized");
@@ -327,7 +375,7 @@ export class Worker {
327
375
  }
328
376
  if (message.filter?.withRecoverable) {
329
377
  // get also swept and spendable vtxos
330
- const sweptVtxos = await this.vtxoRepository.getSweptVtxos();
378
+ const sweptVtxos = await this.getSweptVtxos();
331
379
  vtxos.push(...sweptVtxos.filter(isSpendable));
332
380
  }
333
381
  event.source?.postMessage(Response.vtxos(message.id, vtxos));
@@ -378,7 +426,7 @@ export class Worker {
378
426
  }
379
427
  try {
380
428
  const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.wallet.getBoardingTxs();
381
- const { spendable, spent } = await this.vtxoRepository.getAllVtxos();
429
+ const { spendable, spent } = await this.getAllVtxos();
382
430
  // convert VTXOs to offchain transactions
383
431
  const offchainTxs = vtxosToTxs(spendable, spent, roundsToIgnore);
384
432
  const txs = [...boardingTxs, ...offchainTxs];
@@ -409,35 +457,10 @@ export class Worker {
409
457
  event.source?.postMessage(Response.error(message.id, "Invalid GET_STATUS message format"));
410
458
  return;
411
459
  }
412
- event.source?.postMessage(Response.walletStatus(message.id, this.wallet !== undefined, this.wallet?.identity.xOnlyPublicKey()));
413
- }
414
- async handleSign(event) {
415
- const message = event.data;
416
- if (!Request.isSign(message)) {
417
- console.error("Invalid SIGN message format", message);
418
- event.source?.postMessage(Response.error(message.id, "Invalid SIGN message format"));
419
- return;
420
- }
421
- if (!this.wallet) {
422
- console.error("Wallet not initialized");
423
- event.source?.postMessage(Response.error(message.id, "Wallet not initialized"));
424
- return;
425
- }
426
- try {
427
- const tx = Transaction.fromPSBT(base64.decode(message.tx), {
428
- allowUnknown: true,
429
- allowUnknownInputs: true,
430
- });
431
- const signedTx = await this.wallet.identity.sign(tx, message.inputIndexes);
432
- event.source?.postMessage(Response.signSuccess(message.id, base64.encode(signedTx.toPSBT())));
433
- }
434
- catch (error) {
435
- console.error("Error signing:", error);
436
- const errorMessage = error instanceof Error
437
- ? error.message
438
- : "Unknown error occurred";
439
- event.source?.postMessage(Response.error(message.id, errorMessage));
440
- }
460
+ const pubKey = this.wallet
461
+ ? await this.wallet.identity.xOnlyPublicKey()
462
+ : undefined;
463
+ event.source?.postMessage(Response.walletStatus(message.id, this.wallet !== undefined, pubKey));
441
464
  }
442
465
  async handleMessage(event) {
443
466
  this.messageCallback(event);
@@ -492,12 +515,20 @@ export class Worker {
492
515
  await this.handleClear(event);
493
516
  break;
494
517
  }
495
- case "SIGN": {
496
- await this.handleSign(event);
497
- break;
498
- }
499
518
  default:
500
519
  event.source?.postMessage(Response.error(message.id, "Unknown message type"));
501
520
  }
502
521
  }
522
+ async sendMessageToAllClients(type, message) {
523
+ self.clients
524
+ .matchAll({ includeUncontrolled: true, type: "window" })
525
+ .then((clients) => {
526
+ clients.forEach((client) => {
527
+ client.postMessage({
528
+ type,
529
+ message,
530
+ });
531
+ });
532
+ });
533
+ }
503
534
  }
@@ -1,8 +1,8 @@
1
- import { SigHash, Transaction } from "@scure/btc-signer";
1
+ import { SigHash, Transaction } from "@scure/btc-signer/transaction.js";
2
2
  import { ChainTxType } from '../providers/indexer.js';
3
3
  import { base64, hex } from "@scure/base";
4
4
  import { VtxoScript } from '../script/base.js';
5
- import { TaprootControlBlock, } from "@scure/btc-signer/psbt";
5
+ import { TaprootControlBlock, } from "@scure/btc-signer/psbt.js";
6
6
  import { TxWeightEstimator } from '../utils/txSizeEstimator.js';
7
7
  import { Wallet } from './wallet.js';
8
8
  export var Unroll;
@@ -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";
4
- import { SigHash, Transaction } from "@scure/btc-signer";
5
- import { TaprootControlBlock, } from "@scure/btc-signer/psbt";
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";
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,7 +12,7 @@ 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";
15
+ import { sha256, sha256x2 } from "@scure/btc-signer/utils.js";
16
16
  import { VtxoScript } from '../script/base.js';
17
17
  import { CSVMultisigTapscript } from '../script/tapscript.js';
18
18
  import { buildOffchainTx } from '../utils/arkTransaction.js';
@@ -20,6 +20,9 @@ import { ArkNote } from '../arknote/index.js';
20
20
  import { BIP322 } from '../bip322/index.js';
21
21
  import { RestIndexerProvider } from '../providers/indexer.js';
22
22
  import { TxTree } from '../tree/txTree.js';
23
+ import { InMemoryStorageAdapter } from '../storage/inMemory.js';
24
+ import { WalletRepositoryImpl, } from '../repositories/walletRepository.js';
25
+ import { ContractRepositoryImpl, } from '../repositories/contractRepository.js';
23
26
  /**
24
27
  * Main wallet implementation for Bitcoin transactions with Ark protocol support.
25
28
  * The wallet does not store any data locally and relies on Ark and onchain
@@ -46,7 +49,7 @@ import { TxTree } from '../tree/txTree.js';
46
49
  * ```
47
50
  */
48
51
  export class Wallet {
49
- constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount) {
52
+ constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
50
53
  this.identity = identity;
51
54
  this.network = network;
52
55
  this.networkName = networkName;
@@ -59,9 +62,11 @@ export class Wallet {
59
62
  this.serverUnrollScript = serverUnrollScript;
60
63
  this.forfeitOutputScript = forfeitOutputScript;
61
64
  this.dustAmount = dustAmount;
65
+ this.walletRepository = walletRepository;
66
+ this.contractRepository = contractRepository;
62
67
  }
63
68
  static async create(config) {
64
- const pubkey = config.identity.xOnlyPublicKey();
69
+ const pubkey = await config.identity.xOnlyPublicKey();
65
70
  if (!pubkey) {
66
71
  throw new Error("Invalid configured public key");
67
72
  }
@@ -101,7 +106,11 @@ export class Wallet {
101
106
  // server is expecting funds to be sent to this address
102
107
  const forfeitAddress = Address(network).decode(info.forfeitAddress);
103
108
  const forfeitOutputScript = OutScript.encode(forfeitAddress);
104
- return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust);
109
+ // Set up storage and repositories
110
+ const storage = config.storage || new InMemoryStorageAdapter();
111
+ const walletRepository = new WalletRepositoryImpl(storage);
112
+ const contractRepository = new ContractRepositoryImpl(storage);
113
+ return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
105
114
  }
106
115
  get arkAddress() {
107
116
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -157,16 +166,24 @@ export class Wallet {
157
166
  };
158
167
  }
159
168
  async getVtxos(filter) {
169
+ const address = await this.getAddress();
170
+ // Try to get from cache first
171
+ const cachedVtxos = await this.walletRepository.getVtxos(address);
172
+ // For now, always fetch fresh data from provider and update cache
173
+ // In future, we can add cache invalidation logic based on timestamps
160
174
  const spendableVtxos = await this.getVirtualCoins(filter);
161
175
  const encodedOffchainTapscript = this.offchainTapscript.encode();
162
176
  const forfeit = this.offchainTapscript.forfeit();
163
177
  const exit = this.offchainTapscript.exit();
164
- return spendableVtxos.map((vtxo) => ({
178
+ const extendedVtxos = spendableVtxos.map((vtxo) => ({
165
179
  ...vtxo,
166
180
  forfeitTapLeafScript: forfeit,
167
181
  intentTapLeafScript: exit,
168
182
  tapTree: encodedOffchainTapscript,
169
183
  }));
184
+ // Update cache with fresh data
185
+ await this.walletRepository.saveVtxos(address, extendedVtxos);
186
+ return extendedVtxos;
170
187
  }
171
188
  async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
172
189
  const scripts = [hex.encode(this.offchainTapscript.pkScript)];
@@ -0,0 +1,2 @@
1
+ export { AsyncStorageAdapter } from "../storage/asyncStorage";
2
+ export type { StorageAdapter } from "../storage";
@@ -0,0 +1,2 @@
1
+ export { FileSystemStorageAdapter } from "../storage/fileSystem";
2
+ export type { StorageAdapter } from "../storage";
@@ -0,0 +1,2 @@
1
+ export { IndexedDBStorageAdapter } from "../storage/indexedDB";
2
+ export type { StorageAdapter } from "../storage";
@@ -0,0 +1,2 @@
1
+ export { LocalStorageAdapter } from "../storage/localStorage";
2
+ export type { StorageAdapter } from "../storage";