@arkade-os/sdk 0.4.19 → 0.4.21

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 (61) hide show
  1. package/dist/cjs/contracts/contractWatcher.js +33 -3
  2. package/dist/cjs/contracts/handlers/default.js +10 -3
  3. package/dist/cjs/contracts/handlers/helpers.js +47 -5
  4. package/dist/cjs/contracts/handlers/vhtlc.js +4 -2
  5. package/dist/cjs/identity/descriptor.js +98 -0
  6. package/dist/cjs/identity/descriptorProvider.js +2 -0
  7. package/dist/cjs/identity/index.js +15 -1
  8. package/dist/cjs/identity/seedIdentity.js +91 -6
  9. package/dist/cjs/identity/serialize.js +166 -0
  10. package/dist/cjs/identity/staticDescriptorProvider.js +65 -0
  11. package/dist/cjs/index.js +6 -3
  12. package/dist/cjs/providers/ark.js +71 -46
  13. package/dist/cjs/providers/electrum.js +663 -0
  14. package/dist/cjs/providers/indexer.js +60 -43
  15. package/dist/cjs/providers/utils.js +62 -12
  16. package/dist/cjs/wallet/ramps.js +1 -1
  17. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +10 -0
  18. package/dist/cjs/wallet/serviceWorker/wallet.js +137 -91
  19. package/dist/cjs/wallet/vtxo-manager.js +56 -8
  20. package/dist/cjs/wallet/wallet.js +130 -156
  21. package/dist/cjs/worker/messageBus.js +200 -56
  22. package/dist/esm/contracts/contractWatcher.js +33 -3
  23. package/dist/esm/contracts/handlers/default.js +10 -3
  24. package/dist/esm/contracts/handlers/helpers.js +47 -5
  25. package/dist/esm/contracts/handlers/vhtlc.js +4 -2
  26. package/dist/esm/identity/descriptor.js +92 -0
  27. package/dist/esm/identity/descriptorProvider.js +1 -0
  28. package/dist/esm/identity/index.js +6 -1
  29. package/dist/esm/identity/seedIdentity.js +89 -6
  30. package/dist/esm/identity/serialize.js +159 -0
  31. package/dist/esm/identity/staticDescriptorProvider.js +61 -0
  32. package/dist/esm/index.js +2 -1
  33. package/dist/esm/providers/ark.js +72 -47
  34. package/dist/esm/providers/electrum.js +658 -0
  35. package/dist/esm/providers/indexer.js +61 -44
  36. package/dist/esm/providers/utils.js +61 -12
  37. package/dist/esm/wallet/ramps.js +1 -1
  38. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +10 -0
  39. package/dist/esm/wallet/serviceWorker/wallet.js +137 -91
  40. package/dist/esm/wallet/vtxo-manager.js +56 -8
  41. package/dist/esm/wallet/wallet.js +130 -156
  42. package/dist/esm/worker/messageBus.js +201 -57
  43. package/dist/types/contracts/contractWatcher.d.ts +3 -0
  44. package/dist/types/contracts/handlers/default.d.ts +1 -1
  45. package/dist/types/contracts/handlers/helpers.d.ts +1 -1
  46. package/dist/types/contracts/types.d.ts +11 -3
  47. package/dist/types/identity/descriptor.d.ts +35 -0
  48. package/dist/types/identity/descriptorProvider.d.ts +28 -0
  49. package/dist/types/identity/index.d.ts +7 -1
  50. package/dist/types/identity/seedIdentity.d.ts +41 -4
  51. package/dist/types/identity/serialize.d.ts +84 -0
  52. package/dist/types/identity/staticDescriptorProvider.d.ts +18 -0
  53. package/dist/types/index.d.ts +4 -2
  54. package/dist/types/providers/electrum.d.ts +212 -0
  55. package/dist/types/providers/utils.d.ts +10 -5
  56. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +11 -2
  57. package/dist/types/wallet/serviceWorker/wallet.d.ts +27 -10
  58. package/dist/types/wallet/vtxo-manager.d.ts +2 -0
  59. package/dist/types/wallet/wallet.d.ts +7 -6
  60. package/dist/types/worker/messageBus.d.ts +68 -8
  61. package/package.json +3 -2
@@ -156,58 +156,75 @@ class RestIndexerProvider {
156
156
  }
157
157
  return data;
158
158
  }
159
- async *getSubscription(subscriptionId, abortSignal) {
159
+ getSubscription(subscriptionId, abortSignal) {
160
160
  const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
161
- while (!abortSignal?.aborted) {
161
+ let iterator = null;
162
+ const closeIterator = () => iterator?.close();
163
+ const gen = (async function* () {
164
+ const abortHandler = closeIterator;
165
+ abortSignal?.addEventListener("abort", abortHandler);
162
166
  try {
163
- const eventSource = new EventSource(url);
164
- // Set up abort handling
165
- const abortHandler = () => {
166
- eventSource.close();
167
- };
168
- abortSignal?.addEventListener("abort", abortHandler);
169
- try {
170
- for await (const event of (0, utils_1.eventSourceIterator)(eventSource)) {
171
- if (abortSignal?.aborted)
172
- break;
173
- try {
174
- const data = JSON.parse(event.data);
175
- if (data.event) {
176
- yield {
177
- txid: data.event.txid,
178
- scripts: data.event.scripts || [],
179
- newVtxos: (data.event.newVtxos || []).map(convertVtxo),
180
- spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
181
- sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
182
- tx: data.event.tx,
183
- checkpointTxs: data.event.checkpointTxs,
184
- };
167
+ while (!abortSignal?.aborted) {
168
+ try {
169
+ const currentIterator = (0, utils_1.eventSourceIterator)(new EventSource(url));
170
+ iterator = currentIterator;
171
+ for await (const event of currentIterator) {
172
+ if (abortSignal?.aborted)
173
+ break;
174
+ try {
175
+ const data = JSON.parse(event.data);
176
+ if (data.event) {
177
+ yield {
178
+ txid: data.event.txid,
179
+ scripts: data.event.scripts || [],
180
+ newVtxos: (data.event.newVtxos || []).map(convertVtxo),
181
+ spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
182
+ sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
183
+ tx: data.event.tx,
184
+ checkpointTxs: data.event.checkpointTxs,
185
+ };
186
+ }
187
+ }
188
+ catch (err) {
189
+ console.error("Failed to parse subscription event:", err);
190
+ throw err;
185
191
  }
186
192
  }
187
- catch (err) {
188
- console.error("Failed to parse subscription event:", err);
189
- throw err;
193
+ }
194
+ catch (error) {
195
+ if (abortSignal?.aborted ||
196
+ (error instanceof Error &&
197
+ error.name === "AbortError")) {
198
+ break;
199
+ }
200
+ // ignore timeout errors, they're expected when the server is not sending anything for 5 min
201
+ if ((0, ark_1.isFetchTimeoutError)(error)) {
202
+ console.debug("Timeout error ignored");
203
+ continue;
204
+ }
205
+ if ((0, utils_1.isEventSourceError)(error)) {
206
+ throw error;
190
207
  }
208
+ console.error("Subscription error:", error);
209
+ throw error;
210
+ }
211
+ finally {
212
+ closeIterator();
213
+ iterator = null;
191
214
  }
192
- }
193
- finally {
194
- abortSignal?.removeEventListener("abort", abortHandler);
195
- eventSource.close();
196
215
  }
197
216
  }
198
- catch (error) {
199
- if (error instanceof Error && error.name === "AbortError") {
200
- break;
201
- }
202
- // ignore timeout errors, they're expected when the server is not sending anything for 5 min
203
- if ((0, ark_1.isFetchTimeoutError)(error)) {
204
- console.debug("Timeout error ignored");
205
- continue;
206
- }
207
- console.error("Subscription error:", error);
208
- throw error;
217
+ finally {
218
+ abortSignal?.removeEventListener("abort", abortHandler);
219
+ closeIterator();
209
220
  }
210
- }
221
+ })();
222
+ const origReturn = gen.return.bind(gen);
223
+ gen.return = (value) => {
224
+ closeIterator();
225
+ return origReturn(value);
226
+ };
227
+ return gen;
211
228
  }
212
229
  async getVirtualTxs(txids, opts) {
213
230
  let url = `${this.serverUrl}/v1/indexer/virtualTx/${txids.join(",")}`;
@@ -1,32 +1,71 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.eventSourceIterator = eventSourceIterator;
4
+ exports.isEventSourceError = isEventSourceError;
5
+ function createAbortError() {
6
+ const error = new Error("EventSource closed");
7
+ error.name = "AbortError";
8
+ return error;
9
+ }
4
10
  /**
5
- * Creates an async iterator over EventSource messages that attaches listeners
6
- * eagerly (at call time) rather than lazily (at first .next() call).
7
- * This ensures events are buffered immediately, preventing race conditions
8
- * where events arrive before iteration begins.
11
+ * Creates a close-aware EventSource async iterator.
12
+ *
13
+ * Listeners attach eagerly so events are buffered before the first next() call.
14
+ * close() closes the EventSource, removes listeners, and wakes any pending
15
+ * next() even when the browser does not emit an error from EventSource.close().
9
16
  */
10
17
  function eventSourceIterator(eventSource) {
11
18
  const messageQueue = [];
12
19
  const errorQueue = [];
13
20
  let messageResolve = null;
14
21
  let errorResolve = null;
22
+ let closed = false;
23
+ let cleanedUp = false;
24
+ const cleanup = () => {
25
+ if (cleanedUp)
26
+ return;
27
+ cleanedUp = true;
28
+ eventSource.removeEventListener("message", messageHandler);
29
+ eventSource.removeEventListener("error", errorHandler);
30
+ };
31
+ const close = () => {
32
+ if (closed)
33
+ return;
34
+ closed = true;
35
+ messageQueue.length = 0;
36
+ errorQueue.length = 0;
37
+ eventSource.close();
38
+ cleanup();
39
+ if (errorResolve) {
40
+ const reject = errorResolve;
41
+ messageResolve = null;
42
+ errorResolve = null;
43
+ reject(createAbortError());
44
+ }
45
+ };
15
46
  const messageHandler = (event) => {
47
+ if (closed)
48
+ return;
16
49
  if (messageResolve) {
17
- messageResolve(event);
50
+ const resolve = messageResolve;
18
51
  messageResolve = null;
52
+ errorResolve = null;
53
+ resolve(event);
19
54
  }
20
55
  else {
21
56
  messageQueue.push(event);
22
57
  }
23
58
  };
24
59
  const errorHandler = () => {
60
+ if (closed)
61
+ return;
25
62
  const error = new Error("EventSource error");
26
63
  error.name = "EventSourceError";
27
64
  if (errorResolve) {
28
- errorResolve(error);
65
+ const reject = errorResolve;
66
+ messageResolve = null;
29
67
  errorResolve = null;
68
+ reject(error);
30
69
  }
31
70
  else {
32
71
  errorQueue.push(error);
@@ -36,9 +75,9 @@ function eventSourceIterator(eventSource) {
36
75
  // even before the caller starts iterating
37
76
  eventSource.addEventListener("message", messageHandler);
38
77
  eventSource.addEventListener("error", errorHandler);
39
- return (async function* () {
78
+ const gen = (async function* () {
40
79
  try {
41
- while (true) {
80
+ while (!closed) {
42
81
  // if we have queued messages, yield the first one, remove it from the queue
43
82
  if (messageQueue.length > 0) {
44
83
  yield messageQueue.shift();
@@ -57,15 +96,26 @@ function eventSourceIterator(eventSource) {
57
96
  messageResolve = null;
58
97
  errorResolve = null;
59
98
  });
60
- if (result) {
99
+ if (!closed && result) {
61
100
  yield result;
62
101
  }
63
102
  }
64
103
  }
65
104
  finally {
66
- // clean up
67
- eventSource.removeEventListener("message", messageHandler);
68
- eventSource.removeEventListener("error", errorHandler);
105
+ closed = true;
106
+ cleanup();
107
+ eventSource.close();
69
108
  }
70
109
  })();
110
+ const origReturn = gen.return.bind(gen);
111
+ const managed = gen;
112
+ managed.close = close;
113
+ managed.return = (value) => {
114
+ close();
115
+ return origReturn(value);
116
+ };
117
+ return managed;
118
+ }
119
+ function isEventSourceError(error) {
120
+ return error instanceof Error && error.name === "EventSourceError";
71
121
  }
@@ -142,7 +142,7 @@ class Ramps {
142
142
  weight: 0,
143
143
  birth: vtxo.createdAt,
144
144
  expiry: vtxo.virtualStatus.batchExpiry
145
- ? new Date(vtxo.virtualStatus.batchExpiry * 1000)
145
+ ? new Date(vtxo.virtualStatus.batchExpiry)
146
146
  : undefined,
147
147
  });
148
148
  if (inputFee.satoshis >= vtxo.value) {
@@ -103,6 +103,16 @@ class WalletMessageHandler {
103
103
  tag: this.messageTag,
104
104
  };
105
105
  }
106
+ // Flows that surrender control to the Ark server and the other participants
107
+ // in a batch round: quiet gaps between protocol events can easily exceed
108
+ // the bus-level messageTimeoutMs. Liveness is covered out-of-band by the
109
+ // page-side PING / MESSAGE_BUS_NOT_INITIALIZED path triggered by concurrent
110
+ // short requests (GET_STATUS, GET_BALANCE, ...).
111
+ isLongRunning(message) {
112
+ return (message.type === "SETTLE" ||
113
+ message.type === "RECOVER_VTXOS" ||
114
+ message.type === "RENEW_VTXOS");
115
+ }
106
116
  async handleMessage(message) {
107
117
  const id = message.id;
108
118
  if (message.type === "INIT_WALLET") {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ServiceWorkerWallet = exports.ServiceWorkerReadonlyWallet = exports.DEFAULT_MESSAGE_TIMEOUTS = void 0;
4
4
  const base_1 = require("@scure/base");
5
+ const identity_1 = require("../../identity");
5
6
  const utils_1 = require("../../worker/browser/utils");
6
7
  const repositories_1 = require("../../repositories");
7
8
  const wallet_message_handler_1 = require("./wallet-message-handler");
@@ -36,7 +37,10 @@ exports.DEFAULT_MESSAGE_TIMEOUTS = {
36
37
  GET_EXPIRED_BOARDING_UTXOS: 20000,
37
38
  GET_RECOVERABLE_BALANCE: 20000,
38
39
  RELOAD_WALLET: 20000,
39
- // Transactions — need more headroom
40
+ // Transactions — need more headroom.
41
+ // SETTLE / RECOVER_VTXOS / RENEW_VTXOS go through the streaming path and
42
+ // are treated as long-running on both sides of the bus: the values below
43
+ // are retained only for type completeness and are never enforced.
40
44
  SEND_BITCOIN: 50000,
41
45
  SEND: 50000,
42
46
  SETTLE: 50000,
@@ -81,9 +85,12 @@ function getRequestDedupKey(request) {
81
85
  const { id, tag, ...rest } = request;
82
86
  return JSON.stringify(rest);
83
87
  }
84
- const isPrivateKeyIdentity = (identity) => {
85
- return typeof identity.toHex === "function";
86
- };
88
+ function isSigningCapable(identity) {
89
+ const candidate = identity;
90
+ return (typeof candidate.signMessage === "function" &&
91
+ typeof candidate.sign === "function" &&
92
+ typeof candidate.signerSession === "function");
93
+ }
87
94
  class ServiceWorkerReadonlyAssetManager {
88
95
  constructor(sendMessage, messageTag) {
89
96
  this.sendMessage = sendMessage;
@@ -199,55 +206,54 @@ class ServiceWorkerReadonlyWallet {
199
206
  const messageTag = options.walletUpdaterTag ?? wallet_message_handler_1.DEFAULT_MESSAGE_TAG;
200
207
  // Create the wallet instance
201
208
  const wallet = new ServiceWorkerReadonlyWallet(options.serviceWorker, options.identity, walletRepository, contractRepository, messageTag);
209
+ const serializedWallet = await (0, identity_1.serializeReadonlyIdentity)(options.identity);
210
+ // INIT_WALLET retains the legacy `key` payload for wire compatibility
211
+ // with older workers; the current handler does not read it.
202
212
  const publicKey = await options.identity
203
213
  .compressedPublicKey()
204
214
  .then(base_1.hex.encode);
205
- const initConfig = {
215
+ const initWalletPayload = {
206
216
  key: { publicKey },
207
217
  arkServerUrl: options.arkServerUrl,
208
218
  arkServerPublicKey: options.arkServerPublicKey,
209
219
  delegatorUrl: options.delegatorUrl,
210
220
  };
211
- // Bootstrap the MessageBus in the service worker
212
- await initializeMessageBus(options.serviceWorker, {
213
- wallet: initConfig.key,
221
+ // Precompute the merged timeout map so page-side waiting and
222
+ // worker-side enforcement are derived from the same source.
223
+ const messageTimeouts = options.messageTimeouts
224
+ ? {
225
+ ...exports.DEFAULT_MESSAGE_TIMEOUTS,
226
+ ...options.messageTimeouts,
227
+ }
228
+ : exports.DEFAULT_MESSAGE_TIMEOUTS;
229
+ const busInitConfig = {
230
+ wallet: serializedWallet,
214
231
  arkServer: {
215
- url: initConfig.arkServerUrl,
216
- publicKey: initConfig.arkServerPublicKey,
232
+ url: options.arkServerUrl,
233
+ publicKey: options.arkServerPublicKey,
217
234
  },
218
- delegatorUrl: initConfig.delegatorUrl,
235
+ delegatorUrl: options.delegatorUrl,
219
236
  indexerUrl: options.indexerUrl,
220
237
  esploraUrl: options.esploraUrl,
221
- timeoutMs: options.messageBusTimeoutMs,
222
238
  watcherConfig: options.watcherConfig,
223
- }, options.messageBusTimeoutMs);
239
+ messageTimeouts,
240
+ };
241
+ // Bootstrap the MessageBus in the service worker
242
+ await initializeMessageBus(options.serviceWorker, { ...busInitConfig, timeoutMs: options.messageBusTimeoutMs }, options.messageBusTimeoutMs);
224
243
  // Initialize the wallet handler
225
244
  const initMessage = {
226
245
  tag: messageTag,
227
246
  type: "INIT_WALLET",
228
247
  id: (0, utils_2.getRandomId)(),
229
- payload: initConfig,
248
+ payload: initWalletPayload,
230
249
  };
231
250
  await wallet.sendMessage(initMessage);
232
- wallet.initConfig = {
233
- wallet: initConfig.key,
234
- arkServer: {
235
- url: initConfig.arkServerUrl,
236
- publicKey: initConfig.arkServerPublicKey,
237
- },
238
- delegatorUrl: initConfig.delegatorUrl,
239
- indexerUrl: options.indexerUrl,
240
- esploraUrl: options.esploraUrl,
241
- watcherConfig: options.watcherConfig,
242
- };
243
- wallet.initWalletPayload = initConfig;
251
+ // Persist the full init config (including messageTimeouts) so
252
+ // reinitialize() re-sends the same map to a restarted worker.
253
+ wallet.initConfig = busInitConfig;
254
+ wallet.initWalletPayload = initWalletPayload;
244
255
  wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
245
- if (options.messageTimeouts) {
246
- wallet.messageTimeouts = {
247
- ...exports.DEFAULT_MESSAGE_TIMEOUTS,
248
- ...options.messageTimeouts,
249
- };
250
- }
256
+ wallet.messageTimeouts = messageTimeouts;
251
257
  return wallet;
252
258
  }
253
259
  /**
@@ -306,24 +312,16 @@ class ServiceWorkerReadonlyWallet {
306
312
  }
307
313
  // Like sendMessageDirect but supports streaming responses: intermediate
308
314
  // messages are forwarded via onEvent while the promise resolves on the
309
- // first response for which isComplete returns true. The timeout resets
310
- // on every intermediate event so long-running but progressing operations
311
- // don't time out prematurely.
312
- sendMessageStreaming(request, onEvent, isComplete, timeoutMs) {
315
+ // first response for which isComplete returns true. No inactivity deadline:
316
+ // settlement-class flows surrender control to remote peers and can sit
317
+ // idle for long stretches between protocol events. Service-worker death
318
+ // is detected out-of-band via concurrent short requests that surface
319
+ // MESSAGE_BUS_NOT_INITIALIZED.
320
+ sendMessageStreaming(request, onEvent, isComplete) {
313
321
  return new Promise((resolve, reject) => {
314
- const resetTimeout = () => {
315
- clearTimeout(timeoutId);
316
- timeoutId = setTimeout(() => {
317
- cleanup();
318
- reject(new errors_1.ServiceWorkerTimeoutError(`Service worker message timed out (${request.type})`));
319
- }, timeoutMs);
320
- };
321
322
  const cleanup = () => {
322
- clearTimeout(timeoutId);
323
323
  navigator.serviceWorker.removeEventListener("message", messageHandler);
324
324
  };
325
- let timeoutId;
326
- resetTimeout();
327
325
  const messageHandler = (event) => {
328
326
  const response = event.data;
329
327
  if (request.id !== response.id)
@@ -338,7 +336,6 @@ class ServiceWorkerReadonlyWallet {
338
336
  resolve(response);
339
337
  }
340
338
  else {
341
- resetTimeout();
342
339
  onEvent(response);
343
340
  }
344
341
  };
@@ -428,11 +425,10 @@ class ServiceWorkerReadonlyWallet {
428
425
  await this.reinitialize();
429
426
  }
430
427
  }
431
- const timeoutMs = this.getTimeoutForRequest(request);
432
428
  const maxRetries = 2;
433
429
  for (let attempt = 0;; attempt++) {
434
430
  try {
435
- return await this.sendMessageStreaming(request, onEvent, isComplete, timeoutMs);
431
+ return await this.sendMessageStreaming(request, onEvent, isComplete);
436
432
  }
437
433
  catch (error) {
438
434
  if (!isMessageBusNotInitializedError(error) ||
@@ -443,19 +439,68 @@ class ServiceWorkerReadonlyWallet {
443
439
  }
444
440
  }
445
441
  }
442
+ /**
443
+ * Produce a serialized envelope for the wallet's identity. The base
444
+ * class always emits a readonly envelope; `ServiceWorkerWallet`
445
+ * overrides to emit a signing envelope.
446
+ */
447
+ async serializeIdentity() {
448
+ return (0, identity_1.serializeReadonlyIdentity)(this.identity);
449
+ }
450
+ /**
451
+ * Return the cached init config, or rebuild one from live instance
452
+ * state when the cache was never populated. Recovery path for
453
+ * SDK-factory-created wallets; manual constructor bypasses do not
454
+ * retain enough state here and will hit the "never initialized" throw.
455
+ */
456
+ async buildInitConfig() {
457
+ if (this.initConfig)
458
+ return this.initConfig;
459
+ if (!this.arkServerUrl) {
460
+ throw new Error("Cannot re-initialize: wallet was not initialized via the SDK factory");
461
+ }
462
+ const wallet = await this.serializeIdentity();
463
+ this.initConfig = {
464
+ wallet,
465
+ arkServer: {
466
+ url: this.arkServerUrl,
467
+ publicKey: this.arkServerPublicKey,
468
+ },
469
+ delegatorUrl: this.delegatorUrl,
470
+ indexerUrl: this.indexerUrl,
471
+ esploraUrl: this.esploraUrl,
472
+ watcherConfig: this.watcherConfig,
473
+ settlementConfig: this.settlementConfig,
474
+ };
475
+ return this.initConfig;
476
+ }
477
+ /** Minimal INIT_WALLET payload used on reinitialize when the cache is gone. */
478
+ buildInitWalletPayload() {
479
+ if (this.initWalletPayload)
480
+ return this.initWalletPayload;
481
+ if (!this.arkServerUrl) {
482
+ throw new Error("Cannot re-initialize: wallet was not initialized via the SDK factory");
483
+ }
484
+ this.initWalletPayload = {
485
+ // `key` is deprecated and ignored by the current handler.
486
+ key: {},
487
+ arkServerUrl: this.arkServerUrl,
488
+ arkServerPublicKey: this.arkServerPublicKey,
489
+ };
490
+ return this.initWalletPayload;
491
+ }
446
492
  async reinitialize() {
447
493
  if (this.reinitPromise)
448
494
  return this.reinitPromise;
449
495
  this.reinitPromise = (async () => {
450
- if (!this.initConfig || !this.initWalletPayload) {
451
- throw new Error("Cannot re-initialize: missing configuration");
452
- }
453
- await initializeMessageBus(this.serviceWorker, this.initConfig, this.messageBusTimeoutMs);
496
+ const config = await this.buildInitConfig();
497
+ const payload = this.buildInitWalletPayload();
498
+ await initializeMessageBus(this.serviceWorker, config, this.messageBusTimeoutMs);
454
499
  const initMessage = {
455
500
  tag: this.messageTag,
456
501
  type: "INIT_WALLET",
457
502
  id: (0, utils_2.getRandomId)(),
458
- payload: this.initWalletPayload,
503
+ payload,
459
504
  };
460
505
  await this.sendMessageDirect(initMessage, this.getTimeoutForRequest(initMessage));
461
506
  })().finally(() => {
@@ -818,71 +863,72 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
818
863
  get assetManager() {
819
864
  return this._assetManager;
820
865
  }
866
+ async serializeIdentity() {
867
+ return (0, identity_1.serializeSigningIdentity)(this.identity);
868
+ }
821
869
  static async create(options) {
822
870
  const walletRepository = options.storage?.walletRepository ??
823
871
  new repositories_1.IndexedDBWalletRepository();
824
872
  const contractRepository = options.storage?.contractRepository ??
825
873
  new repositories_1.IndexedDBContractRepository();
826
- // Extract identity and check if it can expose private key
827
- const identity = isPrivateKeyIdentity(options.identity)
828
- ? options.identity
829
- : null;
830
- if (!identity) {
831
- throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
874
+ if (!isSigningCapable(options.identity)) {
875
+ throw new Error("ServiceWorkerWallet.create() requires a signing Identity; got a ReadonlyIdentity");
832
876
  }
833
- // Extract private key for service worker initialization
834
- const privateKey = identity.toHex();
877
+ const identity = options.identity;
878
+ const serializedWallet = (0, identity_1.serializeSigningIdentity)(identity);
835
879
  const messageTag = options.walletUpdaterTag ?? wallet_message_handler_1.DEFAULT_MESSAGE_TAG;
836
880
  // Create the wallet instance
837
881
  const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepository, contractRepository, messageTag, !!options.delegatorUrl);
838
- const initConfig = {
839
- key: { privateKey },
882
+ // INIT_WALLET retains the legacy `key` payload for wire compatibility
883
+ // with older workers; the current handler does not read it, and only
884
+ // SingleKey-style identities can populate it. Kept optional so seed /
885
+ // mnemonic identities simply omit it.
886
+ const legacyPrivateKey = serializedWallet.type === "single-key"
887
+ ? serializedWallet.privateKey
888
+ : null;
889
+ const initWalletPayload = {
890
+ key: legacyPrivateKey ? { privateKey: legacyPrivateKey } : {},
840
891
  arkServerUrl: options.arkServerUrl,
841
892
  arkServerPublicKey: options.arkServerPublicKey,
842
893
  delegatorUrl: options.delegatorUrl,
843
894
  };
844
- await initializeMessageBus(options.serviceWorker, {
845
- wallet: initConfig.key,
895
+ // Precompute the merged timeout map so page-side waiting and
896
+ // worker-side enforcement are derived from the same source.
897
+ const messageTimeouts = options.messageTimeouts
898
+ ? {
899
+ ...exports.DEFAULT_MESSAGE_TIMEOUTS,
900
+ ...options.messageTimeouts,
901
+ }
902
+ : exports.DEFAULT_MESSAGE_TIMEOUTS;
903
+ const busInitConfig = {
904
+ wallet: serializedWallet,
846
905
  arkServer: {
847
- url: initConfig.arkServerUrl,
848
- publicKey: initConfig.arkServerPublicKey,
906
+ url: options.arkServerUrl,
907
+ publicKey: options.arkServerPublicKey,
849
908
  },
850
- delegatorUrl: initConfig.delegatorUrl,
909
+ delegatorUrl: options.delegatorUrl,
851
910
  indexerUrl: options.indexerUrl,
852
911
  esploraUrl: options.esploraUrl,
853
- timeoutMs: options.messageBusTimeoutMs,
854
912
  settlementConfig: options.settlementConfig,
855
913
  watcherConfig: options.watcherConfig,
856
- }, options.messageBusTimeoutMs);
914
+ messageTimeouts,
915
+ };
916
+ await initializeMessageBus(options.serviceWorker, { ...busInitConfig, timeoutMs: options.messageBusTimeoutMs }, options.messageBusTimeoutMs);
857
917
  // Initialize the service worker with the config
858
918
  const initMessage = {
859
919
  tag: messageTag,
860
920
  type: "INIT_WALLET",
861
921
  id: (0, utils_2.getRandomId)(),
862
- payload: initConfig,
922
+ payload: initWalletPayload,
863
923
  };
864
924
  // Initialize the service worker
865
925
  await wallet.sendMessage(initMessage);
866
- wallet.initConfig = {
867
- wallet: initConfig.key,
868
- arkServer: {
869
- url: initConfig.arkServerUrl,
870
- publicKey: initConfig.arkServerPublicKey,
871
- },
872
- delegatorUrl: initConfig.delegatorUrl,
873
- indexerUrl: options.indexerUrl,
874
- esploraUrl: options.esploraUrl,
875
- settlementConfig: options.settlementConfig,
876
- watcherConfig: options.watcherConfig,
877
- };
878
- wallet.initWalletPayload = initConfig;
926
+ // Persist the full init config (including messageTimeouts) so
927
+ // reinitialize() re-sends the same map to a restarted worker.
928
+ wallet.initConfig = busInitConfig;
929
+ wallet.initWalletPayload = initWalletPayload;
879
930
  wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
880
- if (options.messageTimeouts) {
881
- wallet.messageTimeouts = {
882
- ...exports.DEFAULT_MESSAGE_TIMEOUTS,
883
- ...options.messageTimeouts,
884
- };
885
- }
931
+ wallet.messageTimeouts = messageTimeouts;
886
932
  return wallet;
887
933
  }
888
934
  /**