@arkade-os/sdk 0.3.7 → 0.3.9

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 (41) hide show
  1. package/README.md +78 -1
  2. package/dist/cjs/identity/singleKey.js +33 -1
  3. package/dist/cjs/index.js +17 -2
  4. package/dist/cjs/intent/index.js +31 -2
  5. package/dist/cjs/providers/ark.js +9 -3
  6. package/dist/cjs/providers/indexer.js +2 -2
  7. package/dist/cjs/wallet/batch.js +183 -0
  8. package/dist/cjs/wallet/index.js +15 -0
  9. package/dist/cjs/wallet/serviceWorker/request.js +0 -2
  10. package/dist/cjs/wallet/serviceWorker/wallet.js +98 -34
  11. package/dist/cjs/wallet/serviceWorker/worker.js +169 -69
  12. package/dist/cjs/wallet/utils.js +2 -2
  13. package/dist/cjs/wallet/vtxo-manager.js +5 -0
  14. package/dist/cjs/wallet/wallet.js +399 -356
  15. package/dist/esm/identity/singleKey.js +31 -0
  16. package/dist/esm/index.js +12 -7
  17. package/dist/esm/intent/index.js +31 -2
  18. package/dist/esm/providers/ark.js +9 -3
  19. package/dist/esm/providers/indexer.js +2 -2
  20. package/dist/esm/wallet/batch.js +180 -0
  21. package/dist/esm/wallet/index.js +14 -0
  22. package/dist/esm/wallet/serviceWorker/request.js +0 -2
  23. package/dist/esm/wallet/serviceWorker/wallet.js +96 -33
  24. package/dist/esm/wallet/serviceWorker/worker.js +171 -71
  25. package/dist/esm/wallet/utils.js +2 -2
  26. package/dist/esm/wallet/vtxo-manager.js +6 -1
  27. package/dist/esm/wallet/wallet.js +400 -359
  28. package/dist/types/identity/index.d.ts +5 -3
  29. package/dist/types/identity/singleKey.d.ts +20 -1
  30. package/dist/types/index.d.ts +11 -8
  31. package/dist/types/intent/index.d.ts +19 -2
  32. package/dist/types/providers/ark.d.ts +9 -8
  33. package/dist/types/providers/indexer.d.ts +2 -2
  34. package/dist/types/wallet/batch.d.ts +87 -0
  35. package/dist/types/wallet/index.d.ts +75 -16
  36. package/dist/types/wallet/serviceWorker/request.d.ts +5 -1
  37. package/dist/types/wallet/serviceWorker/wallet.d.ts +46 -15
  38. package/dist/types/wallet/serviceWorker/worker.d.ts +6 -3
  39. package/dist/types/wallet/utils.d.ts +8 -3
  40. package/dist/types/wallet/wallet.d.ts +96 -35
  41. package/package.json +123 -113
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceWorkerWallet = void 0;
3
+ exports.ServiceWorkerWallet = exports.ServiceWorkerReadonlyWallet = void 0;
4
4
  const response_1 = require("./response");
5
5
  const base_1 = require("@scure/base");
6
6
  const indexedDB_1 = require("../../storage/indexedDB");
@@ -16,7 +16,16 @@ class UnexpectedResponseError extends Error {
16
16
  this.name = "UnexpectedResponseError";
17
17
  }
18
18
  }
19
- class ServiceWorkerWallet {
19
+ const createCommon = (options) => {
20
+ // Default to IndexedDB for service worker context
21
+ const storage = new indexedDB_1.IndexedDBStorageAdapter(options.dbName || utils_1.DEFAULT_DB_NAME, options.dbVersion);
22
+ // Create repositories
23
+ return {
24
+ walletRepo: new walletRepository_1.WalletRepositoryImpl(storage),
25
+ contractRepo: new contractRepository_1.ContractRepositoryImpl(storage),
26
+ };
27
+ };
28
+ class ServiceWorkerReadonlyWallet {
20
29
  constructor(serviceWorker, identity, walletRepository, contractRepository) {
21
30
  this.serviceWorker = serviceWorker;
22
31
  this.identity = identity;
@@ -24,27 +33,17 @@ class ServiceWorkerWallet {
24
33
  this.contractRepository = contractRepository;
25
34
  }
26
35
  static async create(options) {
27
- // Default to IndexedDB for service worker context
28
- const storage = new indexedDB_1.IndexedDBStorageAdapter(options.dbName || utils_1.DEFAULT_DB_NAME, options.dbVersion);
29
- // Create repositories
30
- const walletRepo = new walletRepository_1.WalletRepositoryImpl(storage);
31
- const contractRepo = new contractRepository_1.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 a single private key");
38
- }
39
- // Extract private key for service worker initialization
40
- const privateKey = identity.toHex();
36
+ const { walletRepo, contractRepo } = createCommon(options);
41
37
  // Create the wallet instance
42
- const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
38
+ const wallet = new ServiceWorkerReadonlyWallet(options.serviceWorker, options.identity, walletRepo, contractRepo);
39
+ const publicKey = await options.identity
40
+ .compressedPublicKey()
41
+ .then(base_1.hex.encode);
43
42
  // Initialize the service worker with the config
44
43
  const initMessage = {
45
44
  type: "INIT_WALLET",
46
45
  id: getRandomId(),
47
- privateKey,
46
+ key: { publicKey },
48
47
  arkServerUrl: options.arkServerUrl,
49
48
  arkServerPublicKey: options.arkServerPublicKey,
50
49
  };
@@ -59,14 +58,14 @@ class ServiceWorkerWallet {
59
58
  * @example
60
59
  * ```typescript
61
60
  * // One-liner setup - handles everything automatically!
62
- * const wallet = await ServiceWorkerWallet.setup({
61
+ * const wallet = await ServiceWorkerReadonlyWallet.setup({
63
62
  * serviceWorkerPath: '/service-worker.js',
64
63
  * arkServerUrl: 'https://mutinynet.arkade.sh'
65
64
  * });
66
65
  *
67
- * // With custom identity
68
- * const identity = SingleKey.fromHex('your_private_key_hex');
69
- * const wallet = await ServiceWorkerWallet.setup({
66
+ * // With custom readonly identity
67
+ * const identity = ReadonlySingleKey.fromPublicKey('your_public_key_hex');
68
+ * const wallet = await ServiceWorkerReadonlyWallet.setup({
70
69
  * serviceWorkerPath: '/service-worker.js',
71
70
  * arkServerUrl: 'https://mutinynet.arkade.sh',
72
71
  * identity
@@ -77,7 +76,7 @@ class ServiceWorkerWallet {
77
76
  // Register and setup the service worker
78
77
  const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
79
78
  // Use the existing create method
80
- return ServiceWorkerWallet.create({
79
+ return ServiceWorkerReadonlyWallet.create({
81
80
  ...options,
82
81
  serviceWorker,
83
82
  });
@@ -229,6 +228,82 @@ class ServiceWorkerWallet {
229
228
  throw new Error(`Failed to get vtxos: ${error}`);
230
229
  }
231
230
  }
231
+ async reload() {
232
+ const message = {
233
+ type: "RELOAD_WALLET",
234
+ id: getRandomId(),
235
+ };
236
+ const response = await this.sendMessage(message);
237
+ if (response_1.Response.isWalletReloaded(response)) {
238
+ return response.success;
239
+ }
240
+ throw new UnexpectedResponseError(response);
241
+ }
242
+ }
243
+ exports.ServiceWorkerReadonlyWallet = ServiceWorkerReadonlyWallet;
244
+ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
245
+ constructor(serviceWorker, identity, walletRepository, contractRepository) {
246
+ super(serviceWorker, identity, walletRepository, contractRepository);
247
+ this.serviceWorker = serviceWorker;
248
+ this.identity = identity;
249
+ this.walletRepository = walletRepository;
250
+ this.contractRepository = contractRepository;
251
+ }
252
+ static async create(options) {
253
+ const { walletRepo, contractRepo } = createCommon(options);
254
+ // Extract identity and check if it can expose private key
255
+ const identity = isPrivateKeyIdentity(options.identity)
256
+ ? options.identity
257
+ : null;
258
+ if (!identity) {
259
+ throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
260
+ }
261
+ // Extract private key for service worker initialization
262
+ const privateKey = identity.toHex();
263
+ // Create the wallet instance
264
+ const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
265
+ // Initialize the service worker with the config
266
+ const initMessage = {
267
+ type: "INIT_WALLET",
268
+ id: getRandomId(),
269
+ key: { privateKey },
270
+ arkServerUrl: options.arkServerUrl,
271
+ arkServerPublicKey: options.arkServerPublicKey,
272
+ };
273
+ // Initialize the service worker
274
+ await wallet.sendMessage(initMessage);
275
+ return wallet;
276
+ }
277
+ /**
278
+ * Simplified setup method that handles service worker registration,
279
+ * identity creation, and wallet initialization automatically.
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * // One-liner setup - handles everything automatically!
284
+ * const wallet = await ServiceWorkerWallet.setup({
285
+ * serviceWorkerPath: '/service-worker.js',
286
+ * arkServerUrl: 'https://mutinynet.arkade.sh'
287
+ * });
288
+ *
289
+ * // With custom identity
290
+ * const identity = SingleKey.fromHex('your_private_key_hex');
291
+ * const wallet = await ServiceWorkerWallet.setup({
292
+ * serviceWorkerPath: '/service-worker.js',
293
+ * arkServerUrl: 'https://mutinynet.arkade.sh',
294
+ * identity
295
+ * });
296
+ * ```
297
+ */
298
+ static async setup(options) {
299
+ // Register and setup the service worker
300
+ const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
301
+ // Use the existing create method
302
+ return ServiceWorkerWallet.create({
303
+ ...options,
304
+ serviceWorker,
305
+ });
306
+ }
232
307
  async sendBitcoin(params) {
233
308
  const message = {
234
309
  type: "SEND_BITCOIN",
@@ -286,17 +361,6 @@ class ServiceWorkerWallet {
286
361
  throw new Error(`Settlement failed: ${error}`);
287
362
  }
288
363
  }
289
- async reload() {
290
- const message = {
291
- type: "RELOAD_WALLET",
292
- id: getRandomId(),
293
- };
294
- const response = await this.sendMessage(message);
295
- if (response_1.Response.isWalletReloaded(response)) {
296
- return response.success;
297
- }
298
- throw new UnexpectedResponseError(response);
299
- }
300
364
  }
301
365
  exports.ServiceWorkerWallet = ServiceWorkerWallet;
302
366
  function getRandomId() {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
+ /// <reference lib="webworker" />
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.Worker = void 0;
4
- /// <reference lib="webworker" />
5
5
  const singleKey_1 = require("../../identity/singleKey");
6
6
  const __1 = require("..");
7
7
  const wallet_1 = require("../wallet");
@@ -15,9 +15,70 @@ const indexedDB_1 = require("../../storage/indexedDB");
15
15
  const walletRepository_1 = require("../../repositories/walletRepository");
16
16
  const utils_1 = require("../utils");
17
17
  const utils_2 = require("./utils");
18
+ class ReadonlyHandler {
19
+ constructor(wallet) {
20
+ this.wallet = wallet;
21
+ }
22
+ get offchainTapscript() {
23
+ return this.wallet.offchainTapscript;
24
+ }
25
+ get boardingTapscript() {
26
+ return this.wallet.boardingTapscript;
27
+ }
28
+ get onchainProvider() {
29
+ return this.wallet.onchainProvider;
30
+ }
31
+ get dustAmount() {
32
+ return this.wallet.dustAmount;
33
+ }
34
+ get identity() {
35
+ return this.wallet.identity;
36
+ }
37
+ notifyIncomingFunds(...args) {
38
+ return this.wallet.notifyIncomingFunds(...args);
39
+ }
40
+ getAddress() {
41
+ return this.wallet.getAddress();
42
+ }
43
+ getBoardingAddress() {
44
+ return this.wallet.getBoardingAddress();
45
+ }
46
+ getBoardingTxs() {
47
+ return this.wallet.getBoardingTxs();
48
+ }
49
+ async handleReload(_) {
50
+ const pending = await this.wallet.fetchPendingTxs();
51
+ return { pending, finalized: [] };
52
+ }
53
+ async handleSettle(..._) {
54
+ return undefined;
55
+ }
56
+ async handleSendBitcoin(..._) {
57
+ return undefined;
58
+ }
59
+ }
60
+ class Handler extends ReadonlyHandler {
61
+ constructor(wallet) {
62
+ super(wallet);
63
+ this.wallet = wallet;
64
+ }
65
+ async handleReload(vtxos) {
66
+ return this.wallet.finalizePendingTxs(vtxos.filter((vtxo) => vtxo.virtualStatus.state !== "swept" &&
67
+ vtxo.virtualStatus.state !== "settled"));
68
+ }
69
+ async handleSettle(...args) {
70
+ return this.wallet.settle(...args);
71
+ }
72
+ async handleSendBitcoin(...args) {
73
+ return this.wallet.sendBitcoin(...args);
74
+ }
75
+ }
18
76
  /**
19
- * Worker is a class letting to interact with ServiceWorkerWallet from the client
20
- * it aims to be run in a service worker context
77
+ * Worker is a class letting to interact with ServiceWorkerWallet and ServiceWorkerReadonlyWallet from
78
+ * the client; it aims to be run in a service worker context.
79
+ *
80
+ * The messages requiring a Wallet rather than a ReadonlyWallet result in no-op
81
+ * without errors.
21
82
  */
22
83
  class Worker {
23
84
  constructor(dbName = utils_2.DEFAULT_DB_NAME, dbVersion = 1, messageCallback = () => { }) {
@@ -31,9 +92,9 @@ class Worker {
31
92
  * Get spendable vtxos for the current wallet address
32
93
  */
33
94
  async getSpendableVtxos() {
34
- if (!this.wallet)
95
+ if (!this.handler)
35
96
  return [];
36
- const address = await this.wallet.getAddress();
97
+ const address = await this.handler.getAddress();
37
98
  const allVtxos = await this.walletRepository.getVtxos(address);
38
99
  return allVtxos.filter(__1.isSpendable);
39
100
  }
@@ -41,9 +102,9 @@ class Worker {
41
102
  * Get swept vtxos for the current wallet address
42
103
  */
43
104
  async getSweptVtxos() {
44
- if (!this.wallet)
105
+ if (!this.handler)
45
106
  return [];
46
- const address = await this.wallet.getAddress();
107
+ const address = await this.handler.getAddress();
47
108
  const allVtxos = await this.walletRepository.getVtxos(address);
48
109
  return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept");
49
110
  }
@@ -51,9 +112,9 @@ class Worker {
51
112
  * Get all vtxos categorized by type
52
113
  */
53
114
  async getAllVtxos() {
54
- if (!this.wallet)
115
+ if (!this.handler)
55
116
  return { spendable: [], spent: [] };
56
- const address = await this.wallet.getAddress();
117
+ const address = await this.handler.getAddress();
57
118
  const allVtxos = await this.walletRepository.getVtxos(address);
58
119
  return {
59
120
  spendable: allVtxos.filter(__1.isSpendable),
@@ -64,17 +125,17 @@ class Worker {
64
125
  * Get all boarding utxos from wallet repository
65
126
  */
66
127
  async getAllBoardingUtxos() {
67
- if (!this.wallet)
128
+ if (!this.handler)
68
129
  return [];
69
- const address = await this.wallet.getBoardingAddress();
130
+ const address = await this.handler.getBoardingAddress();
70
131
  return await this.walletRepository.getUtxos(address);
71
132
  }
72
133
  async getTransactionHistory() {
73
- if (!this.wallet)
134
+ if (!this.handler)
74
135
  return [];
75
136
  let txs = [];
76
137
  try {
77
- const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.wallet.getBoardingTxs();
138
+ const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.handler.getBoardingTxs();
78
139
  const { spendable, spent } = await this.getAllVtxos();
79
140
  // convert VTXOs to offchain transactions
80
141
  const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendable, spent, roundsToIgnore);
@@ -117,7 +178,7 @@ class Worker {
117
178
  await this.storage.clear();
118
179
  // Reset in-memory caches by recreating the repository
119
180
  this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
120
- this.wallet = undefined;
181
+ this.handler = undefined;
121
182
  this.arkProvider = undefined;
122
183
  this.indexerProvider = undefined;
123
184
  }
@@ -125,26 +186,34 @@ class Worker {
125
186
  await this.onWalletInitialized();
126
187
  }
127
188
  async onWalletInitialized() {
128
- if (!this.wallet ||
189
+ if (!this.handler ||
129
190
  !this.arkProvider ||
130
191
  !this.indexerProvider ||
131
- !this.wallet.offchainTapscript ||
132
- !this.wallet.boardingTapscript) {
192
+ !this.handler.offchainTapscript ||
193
+ !this.handler.boardingTapscript) {
133
194
  return;
134
195
  }
135
196
  // Get public key script and set the initial vtxos state
136
- const script = base_1.hex.encode(this.wallet.offchainTapscript.pkScript);
197
+ const script = base_1.hex.encode(this.handler.offchainTapscript.pkScript);
137
198
  const response = await this.indexerProvider.getVtxos({
138
199
  scripts: [script],
139
200
  });
140
- const vtxos = response.vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo));
201
+ const vtxos = response.vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.handler, vtxo));
202
+ try {
203
+ // recover pending transactions if possible
204
+ const { pending, finalized } = await this.handler.handleReload(vtxos);
205
+ console.info(`Recovered ${finalized.length}/${pending.length} pending transactions: ${finalized.join(", ")}`);
206
+ }
207
+ catch (error) {
208
+ console.error("Error recovering pending transactions:", error);
209
+ }
141
210
  // Get wallet address and save vtxos using unified repository
142
- const address = await this.wallet.getAddress();
211
+ const address = await this.handler.getAddress();
143
212
  await this.walletRepository.saveVtxos(address, vtxos);
144
213
  // Fetch boarding utxos and save using unified repository
145
- const boardingAddress = await this.wallet.getBoardingAddress();
146
- const coins = await this.wallet.onchainProvider.getCoins(boardingAddress);
147
- await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => (0, utils_1.extendCoin)(this.wallet, utxo)));
214
+ const boardingAddress = await this.handler.getBoardingAddress();
215
+ const coins = await this.handler.onchainProvider.getCoins(boardingAddress);
216
+ await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => (0, utils_1.extendCoin)(this.handler, utxo)));
148
217
  // Get transaction history to cache boarding txs
149
218
  const txs = await this.getTransactionHistory();
150
219
  if (txs)
@@ -153,13 +222,13 @@ class Worker {
153
222
  if (this.incomingFundsSubscription)
154
223
  this.incomingFundsSubscription();
155
224
  // subscribe for incoming funds and notify all clients when new funds arrive
156
- this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
225
+ this.incomingFundsSubscription = await this.handler.notifyIncomingFunds(async (funds) => {
157
226
  if (funds.type === "vtxo") {
158
227
  const newVtxos = funds.newVtxos.length > 0
159
- ? funds.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo))
228
+ ? funds.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.handler, vtxo))
160
229
  : [];
161
230
  const spentVtxos = funds.spentVtxos.length > 0
162
- ? funds.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo))
231
+ ? funds.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.handler, vtxo))
163
232
  : [];
164
233
  if ([...newVtxos, ...spentVtxos].length === 0)
165
234
  return;
@@ -172,8 +241,8 @@ class Worker {
172
241
  await this.sendMessageToAllClients(response_1.Response.vtxoUpdate(newVtxos, spentVtxos));
173
242
  }
174
243
  if (funds.type === "utxo") {
175
- const utxos = funds.coins.map((utxo) => (0, utils_1.extendCoin)(this.wallet, utxo));
176
- const boardingAddress = await this.wallet?.getBoardingAddress();
244
+ const utxos = funds.coins.map((utxo) => (0, utils_1.extendCoin)(this.handler, utxo));
245
+ const boardingAddress = await this.handler?.getBoardingAddress();
177
246
  // save utxos using unified repository
178
247
  await this.walletRepository.clearUtxos(boardingAddress);
179
248
  await this.walletRepository.saveUtxos(boardingAddress, utxos);
@@ -189,31 +258,46 @@ class Worker {
189
258
  }
190
259
  }
191
260
  async handleInitWallet(event) {
192
- const message = event.data;
193
- if (!request_1.Request.isInitWallet(message)) {
194
- console.error("Invalid INIT_WALLET message format", message);
195
- event.source?.postMessage(response_1.Response.error(message.id, "Invalid INIT_WALLET message format"));
196
- return;
197
- }
198
- if (!message.privateKey) {
199
- const err = "Missing privateKey";
200
- event.source?.postMessage(response_1.Response.error(message.id, err));
201
- console.error(err);
261
+ if (!request_1.Request.isInitWallet(event.data)) {
262
+ console.error("Invalid INIT_WALLET message format", event.data);
263
+ event.source?.postMessage(response_1.Response.error(event.data.id, "Invalid INIT_WALLET message format"));
202
264
  return;
203
265
  }
266
+ const message = event.data;
267
+ const { arkServerPublicKey, arkServerUrl } = message;
268
+ this.arkProvider = new ark_1.RestArkProvider(arkServerUrl);
269
+ this.indexerProvider = new indexer_1.RestIndexerProvider(arkServerUrl);
204
270
  try {
205
- const { arkServerPublicKey, arkServerUrl, privateKey } = message;
206
- const identity = singleKey_1.SingleKey.fromHex(privateKey);
207
- this.arkProvider = new ark_1.RestArkProvider(arkServerUrl);
208
- this.indexerProvider = new indexer_1.RestIndexerProvider(arkServerUrl);
209
- this.wallet = await wallet_1.Wallet.create({
210
- identity,
211
- arkServerUrl,
212
- arkServerPublicKey,
213
- storage: this.storage, // Use unified storage for wallet too
214
- });
215
- event.source?.postMessage(response_1.Response.walletInitialized(message.id));
216
- await this.onWalletInitialized();
271
+ if ("privateKey" in message.key &&
272
+ typeof message.key.privateKey === "string") {
273
+ const { key: { privateKey }, } = message;
274
+ const identity = singleKey_1.SingleKey.fromHex(privateKey);
275
+ const wallet = await wallet_1.Wallet.create({
276
+ identity,
277
+ arkServerUrl,
278
+ arkServerPublicKey,
279
+ storage: this.storage, // Use unified storage for wallet too
280
+ });
281
+ this.handler = new Handler(wallet);
282
+ }
283
+ else if ("publicKey" in message.key &&
284
+ typeof message.key.publicKey === "string") {
285
+ const { key: { publicKey }, } = message;
286
+ const identity = singleKey_1.ReadonlySingleKey.fromPublicKey(base_1.hex.decode(publicKey));
287
+ const wallet = await wallet_1.ReadonlyWallet.create({
288
+ identity,
289
+ arkServerUrl,
290
+ arkServerPublicKey,
291
+ storage: this.storage, // Use unified storage for wallet too
292
+ });
293
+ this.handler = new ReadonlyHandler(wallet);
294
+ }
295
+ else {
296
+ const err = "Missing privateKey or publicKey in key object";
297
+ event.source?.postMessage(response_1.Response.error(message.id, err));
298
+ console.error(err);
299
+ return;
300
+ }
217
301
  }
218
302
  catch (error) {
219
303
  console.error("Error initializing wallet:", error);
@@ -221,7 +305,10 @@ class Worker {
221
305
  ? error.message
222
306
  : "Unknown error occurred";
223
307
  event.source?.postMessage(response_1.Response.error(message.id, errorMessage));
308
+ return;
224
309
  }
310
+ event.source?.postMessage(response_1.Response.walletInitialized(message.id));
311
+ await this.onWalletInitialized();
225
312
  }
226
313
  async handleSettle(event) {
227
314
  const message = event.data;
@@ -231,15 +318,20 @@ class Worker {
231
318
  return;
232
319
  }
233
320
  try {
234
- if (!this.wallet) {
321
+ if (!this.handler) {
235
322
  console.error("Wallet not initialized");
236
323
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
237
324
  return;
238
325
  }
239
- const txid = await this.wallet.settle(message.params, (e) => {
326
+ const txid = await this.handler.handleSettle(message.params, (e) => {
240
327
  event.source?.postMessage(response_1.Response.settleEvent(message.id, e));
241
328
  });
242
- event.source?.postMessage(response_1.Response.settleSuccess(message.id, txid));
329
+ if (txid) {
330
+ event.source?.postMessage(response_1.Response.settleSuccess(message.id, txid));
331
+ }
332
+ else {
333
+ event.source?.postMessage(response_1.Response.error(message.id, "Operation not supported in readonly mode"));
334
+ }
243
335
  }
244
336
  catch (error) {
245
337
  console.error("Error settling:", error);
@@ -256,14 +348,19 @@ class Worker {
256
348
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid SEND_BITCOIN message format"));
257
349
  return;
258
350
  }
259
- if (!this.wallet) {
351
+ if (!this.handler) {
260
352
  console.error("Wallet not initialized");
261
353
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
262
354
  return;
263
355
  }
264
356
  try {
265
- const txid = await this.wallet.sendBitcoin(message.params);
266
- event.source?.postMessage(response_1.Response.sendBitcoinSuccess(message.id, txid));
357
+ const txid = await this.handler.handleSendBitcoin(message.params);
358
+ if (txid) {
359
+ event.source?.postMessage(response_1.Response.sendBitcoinSuccess(message.id, txid));
360
+ }
361
+ else {
362
+ event.source?.postMessage(response_1.Response.error(message.id, "Operation not supported in readonly mode"));
363
+ }
267
364
  }
268
365
  catch (error) {
269
366
  console.error("Error sending bitcoin:", error);
@@ -280,13 +377,13 @@ class Worker {
280
377
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_ADDRESS message format"));
281
378
  return;
282
379
  }
283
- if (!this.wallet) {
380
+ if (!this.handler) {
284
381
  console.error("Wallet not initialized");
285
382
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
286
383
  return;
287
384
  }
288
385
  try {
289
- const address = await this.wallet.getAddress();
386
+ const address = await this.handler.getAddress();
290
387
  event.source?.postMessage(response_1.Response.address(message.id, address));
291
388
  }
292
389
  catch (error) {
@@ -304,13 +401,13 @@ class Worker {
304
401
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_BOARDING_ADDRESS message format"));
305
402
  return;
306
403
  }
307
- if (!this.wallet) {
404
+ if (!this.handler) {
308
405
  console.error("Wallet not initialized");
309
406
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
310
407
  return;
311
408
  }
312
409
  try {
313
- const address = await this.wallet.getBoardingAddress();
410
+ const address = await this.handler.getBoardingAddress();
314
411
  event.source?.postMessage(response_1.Response.boardingAddress(message.id, address));
315
412
  }
316
413
  catch (error) {
@@ -328,7 +425,7 @@ class Worker {
328
425
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_BALANCE message format"));
329
426
  return;
330
427
  }
331
- if (!this.wallet) {
428
+ if (!this.handler) {
332
429
  console.error("Wallet not initialized");
333
430
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
334
431
  return;
@@ -397,14 +494,14 @@ class Worker {
397
494
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_VTXOS message format"));
398
495
  return;
399
496
  }
400
- if (!this.wallet) {
497
+ if (!this.handler) {
401
498
  console.error("Wallet not initialized");
402
499
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
403
500
  return;
404
501
  }
405
502
  try {
406
503
  const vtxos = await this.getSpendableVtxos();
407
- const dustAmount = this.wallet.dustAmount;
504
+ const dustAmount = this.handler.dustAmount;
408
505
  const includeRecoverable = message.filter?.withRecoverable ?? false;
409
506
  const filteredVtxos = includeRecoverable
410
507
  ? vtxos
@@ -415,6 +512,9 @@ class Worker {
415
512
  if ((0, __1.isRecoverable)(v)) {
416
513
  return false;
417
514
  }
515
+ if ((0, __1.isExpired)(v)) {
516
+ return false;
517
+ }
418
518
  return true;
419
519
  });
420
520
  event.source?.postMessage(response_1.Response.vtxos(message.id, filteredVtxos));
@@ -434,7 +534,7 @@ class Worker {
434
534
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_BOARDING_UTXOS message format"));
435
535
  return;
436
536
  }
437
- if (!this.wallet) {
537
+ if (!this.handler) {
438
538
  console.error("Wallet not initialized");
439
539
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
440
540
  return;
@@ -458,7 +558,7 @@ class Worker {
458
558
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_TRANSACTION_HISTORY message format"));
459
559
  return;
460
560
  }
461
- if (!this.wallet) {
561
+ if (!this.handler) {
462
562
  console.error("Wallet not initialized");
463
563
  event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
464
564
  return;
@@ -482,10 +582,10 @@ class Worker {
482
582
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_STATUS message format"));
483
583
  return;
484
584
  }
485
- const pubKey = this.wallet
486
- ? await this.wallet.identity.xOnlyPublicKey()
585
+ const pubKey = this.handler
586
+ ? await this.handler.identity.xOnlyPublicKey()
487
587
  : undefined;
488
- event.source?.postMessage(response_1.Response.walletStatus(message.id, this.wallet !== undefined, pubKey));
588
+ event.source?.postMessage(response_1.Response.walletStatus(message.id, this.handler !== undefined, pubKey));
489
589
  }
490
590
  async handleMessage(event) {
491
591
  this.messageCallback(event);
@@ -564,7 +664,7 @@ class Worker {
564
664
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid RELOAD_WALLET message format"));
565
665
  return;
566
666
  }
567
- if (!this.wallet) {
667
+ if (!this.handler) {
568
668
  console.error("Wallet not initialized");
569
669
  event.source?.postMessage(response_1.Response.walletReloaded(message.id, false));
570
670
  return;
@@ -6,7 +6,7 @@ function extendVirtualCoin(wallet, vtxo) {
6
6
  return {
7
7
  ...vtxo,
8
8
  forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
9
- intentTapLeafScript: wallet.offchainTapscript.exit(),
9
+ intentTapLeafScript: wallet.offchainTapscript.forfeit(),
10
10
  tapTree: wallet.offchainTapscript.encode(),
11
11
  };
12
12
  }
@@ -14,7 +14,7 @@ function extendCoin(wallet, utxo) {
14
14
  return {
15
15
  ...utxo,
16
16
  forfeitTapLeafScript: wallet.boardingTapscript.forfeit(),
17
- intentTapLeafScript: wallet.boardingTapscript.exit(),
17
+ intentTapLeafScript: wallet.boardingTapscript.forfeit(),
18
18
  tapTree: wallet.boardingTapscript.encode(),
19
19
  };
20
20
  }
@@ -31,6 +31,10 @@ function getRecoverableVtxos(vtxos, dustAmount) {
31
31
  if ((0, _1.isRecoverable)(vtxo)) {
32
32
  return true;
33
33
  }
34
+ // also include vtxos that are not swept but expired
35
+ if ((0, _1.isSpendable)(vtxo) && (0, _1.isExpired)(vtxo)) {
36
+ return true;
37
+ }
34
38
  // Recover preconfirmed subdust to consolidate small amounts
35
39
  if (vtxo.virtualStatus.state === "preconfirmed" &&
36
40
  (0, _1.isSubdust)(vtxo, dustAmount)) {
@@ -105,6 +109,7 @@ function isVtxoExpiringSoon(vtxo, thresholdMs // in milliseconds
105
109
  function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
106
110
  return vtxos.filter((vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) ||
107
111
  (0, _1.isRecoverable)(vtxo) ||
112
+ ((0, _1.isSpendable)(vtxo) && (0, _1.isExpired)(vtxo)) ||
108
113
  (0, _1.isSubdust)(vtxo, dustAmount));
109
114
  }
110
115
  /**