@arkade-os/sdk 0.4.7 → 0.4.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 (39) hide show
  1. package/dist/cjs/contracts/contractManager.js +59 -11
  2. package/dist/cjs/contracts/contractWatcher.js +21 -2
  3. package/dist/cjs/identity/seedIdentity.js +2 -2
  4. package/dist/cjs/index.js +9 -2
  5. package/dist/cjs/providers/expoIndexer.js +1 -0
  6. package/dist/cjs/providers/indexer.js +1 -0
  7. package/dist/cjs/utils/transactionHistory.js +2 -1
  8. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +249 -36
  9. package/dist/cjs/wallet/serviceWorker/wallet.js +286 -34
  10. package/dist/cjs/wallet/vtxo-manager.js +123 -86
  11. package/dist/cjs/wallet/wallet.js +140 -68
  12. package/dist/cjs/worker/errors.js +17 -0
  13. package/dist/cjs/worker/messageBus.js +14 -2
  14. package/dist/esm/contracts/contractManager.js +59 -11
  15. package/dist/esm/contracts/contractWatcher.js +21 -2
  16. package/dist/esm/identity/seedIdentity.js +2 -2
  17. package/dist/esm/index.js +3 -2
  18. package/dist/esm/providers/expoIndexer.js +1 -0
  19. package/dist/esm/providers/indexer.js +1 -0
  20. package/dist/esm/utils/transactionHistory.js +2 -1
  21. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +245 -35
  22. package/dist/esm/wallet/serviceWorker/wallet.js +286 -34
  23. package/dist/esm/wallet/vtxo-manager.js +123 -86
  24. package/dist/esm/wallet/wallet.js +140 -68
  25. package/dist/esm/worker/errors.js +12 -0
  26. package/dist/esm/worker/messageBus.js +14 -2
  27. package/dist/types/contracts/contractManager.d.ts +10 -0
  28. package/dist/types/identity/seedIdentity.d.ts +5 -2
  29. package/dist/types/index.d.ts +5 -4
  30. package/dist/types/repositories/serialization.d.ts +1 -0
  31. package/dist/types/utils/transactionHistory.d.ts +1 -1
  32. package/dist/types/wallet/index.d.ts +2 -0
  33. package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +101 -7
  34. package/dist/types/wallet/serviceWorker/wallet.d.ts +16 -0
  35. package/dist/types/wallet/vtxo-manager.d.ts +29 -2
  36. package/dist/types/wallet/wallet.d.ts +10 -0
  37. package/dist/types/worker/errors.d.ts +6 -0
  38. package/dist/types/worker/messageBus.d.ts +6 -0
  39. package/package.json +1 -1
@@ -6,6 +6,37 @@ const utils_1 = require("../../worker/browser/utils");
6
6
  const repositories_1 = require("../../repositories");
7
7
  const wallet_message_handler_1 = require("./wallet-message-handler");
8
8
  const utils_2 = require("../utils");
9
+ const errors_1 = require("../../worker/errors");
10
+ // Check by error name instead of instanceof because postMessage uses the
11
+ // structured clone algorithm which strips the prototype chain — the page
12
+ // receives a plain Error, not the original MessageBusNotInitializedError.
13
+ function isMessageBusNotInitializedError(error) {
14
+ return (error instanceof Error && error.name === "MessageBusNotInitializedError");
15
+ }
16
+ const DEDUPABLE_REQUEST_TYPES = new Set([
17
+ "GET_ADDRESS",
18
+ "GET_BALANCE",
19
+ "GET_BOARDING_ADDRESS",
20
+ "GET_BOARDING_UTXOS",
21
+ "GET_STATUS",
22
+ "GET_TRANSACTION_HISTORY",
23
+ "IS_CONTRACT_MANAGER_WATCHING",
24
+ "GET_DELEGATE_INFO",
25
+ "GET_RECOVERABLE_BALANCE",
26
+ "GET_EXPIRED_BOARDING_UTXOS",
27
+ "GET_VTXOS",
28
+ "GET_CONTRACTS",
29
+ "GET_CONTRACTS_WITH_VTXOS",
30
+ "GET_SPENDABLE_PATHS",
31
+ "GET_ALL_SPENDING_PATHS",
32
+ "GET_ASSET_DETAILS",
33
+ "GET_EXPIRING_VTXOS",
34
+ "RELOAD_WALLET",
35
+ ]);
36
+ function getRequestDedupKey(request) {
37
+ const { id, tag, ...rest } = request;
38
+ return JSON.stringify(rest);
39
+ }
9
40
  const isPrivateKeyIdentity = (identity) => {
10
41
  return typeof identity.toHex === "function";
11
42
  };
@@ -82,7 +113,7 @@ const initializeMessageBus = (serviceWorker, config, timeoutMs = 2000) => {
82
113
  };
83
114
  const timeoutId = setTimeout(() => {
84
115
  cleanup();
85
- reject(new Error("MessageBus timed out!"));
116
+ reject(new errors_1.ServiceWorkerTimeoutError("MessageBus timed out"));
86
117
  }, timeoutMs);
87
118
  navigator.serviceWorker.addEventListener("message", onMessage);
88
119
  serviceWorker.postMessage(initCmd);
@@ -98,6 +129,8 @@ class ServiceWorkerReadonlyWallet {
98
129
  this.initConfig = null;
99
130
  this.initWalletPayload = null;
100
131
  this.reinitPromise = null;
132
+ this.pingPromise = null;
133
+ this.inflightRequests = new Map();
101
134
  this.identity = identity;
102
135
  this.walletRepository = walletRepository;
103
136
  this.contractRepository = contractRepository;
@@ -128,7 +161,10 @@ class ServiceWorkerReadonlyWallet {
128
161
  publicKey: initConfig.arkServerPublicKey,
129
162
  },
130
163
  delegatorUrl: initConfig.delegatorUrl,
164
+ indexerUrl: options.indexerUrl,
165
+ esploraUrl: options.esploraUrl,
131
166
  timeoutMs: options.messageBusTimeoutMs,
167
+ watcherConfig: options.watcherConfig,
132
168
  }, options.messageBusTimeoutMs);
133
169
  // Initialize the wallet handler
134
170
  const initMessage = {
@@ -145,6 +181,9 @@ class ServiceWorkerReadonlyWallet {
145
181
  publicKey: initConfig.arkServerPublicKey,
146
182
  },
147
183
  delegatorUrl: initConfig.delegatorUrl,
184
+ indexerUrl: options.indexerUrl,
185
+ esploraUrl: options.esploraUrl,
186
+ watcherConfig: options.watcherConfig,
148
187
  };
149
188
  wallet.initWalletPayload = initConfig;
150
189
  wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
@@ -191,7 +230,7 @@ class ServiceWorkerReadonlyWallet {
191
230
  };
192
231
  const timeoutId = setTimeout(() => {
193
232
  cleanup();
194
- reject(new Error(`Service worker message timed out (${request.type})`));
233
+ reject(new errors_1.ServiceWorkerTimeoutError(`Service worker message timed out (${request.type})`));
195
234
  }, 30000);
196
235
  const messageHandler = (event) => {
197
236
  const response = event.data;
@@ -210,18 +249,137 @@ class ServiceWorkerReadonlyWallet {
210
249
  this.serviceWorker.postMessage(request);
211
250
  });
212
251
  }
252
+ // Like sendMessageDirect but supports streaming responses: intermediate
253
+ // messages are forwarded via onEvent while the promise resolves on the
254
+ // first response for which isComplete returns true. The timeout resets
255
+ // on every intermediate event so long-running but progressing operations
256
+ // don't time out prematurely.
257
+ sendMessageStreaming(request, onEvent, isComplete) {
258
+ return new Promise((resolve, reject) => {
259
+ const resetTimeout = () => {
260
+ clearTimeout(timeoutId);
261
+ timeoutId = setTimeout(() => {
262
+ cleanup();
263
+ reject(new errors_1.ServiceWorkerTimeoutError(`Service worker message timed out (${request.type})`));
264
+ }, 30000);
265
+ };
266
+ const cleanup = () => {
267
+ clearTimeout(timeoutId);
268
+ navigator.serviceWorker.removeEventListener("message", messageHandler);
269
+ };
270
+ let timeoutId;
271
+ resetTimeout();
272
+ const messageHandler = (event) => {
273
+ const response = event.data;
274
+ if (request.id !== response.id)
275
+ return;
276
+ if (response.error) {
277
+ cleanup();
278
+ reject(response.error);
279
+ return;
280
+ }
281
+ if (isComplete(response)) {
282
+ cleanup();
283
+ resolve(response);
284
+ }
285
+ else {
286
+ resetTimeout();
287
+ onEvent(response);
288
+ }
289
+ };
290
+ navigator.serviceWorker.addEventListener("message", messageHandler);
291
+ this.serviceWorker.postMessage(request);
292
+ });
293
+ }
294
+ async sendMessage(request) {
295
+ if (!DEDUPABLE_REQUEST_TYPES.has(request.type)) {
296
+ return this.sendMessageWithRetry(request);
297
+ }
298
+ const key = getRequestDedupKey(request);
299
+ const existing = this.inflightRequests.get(key);
300
+ if (existing)
301
+ return existing;
302
+ const promise = this.sendMessageWithRetry(request).finally(() => {
303
+ this.inflightRequests.delete(key);
304
+ });
305
+ this.inflightRequests.set(key, promise);
306
+ return promise;
307
+ }
308
+ pingServiceWorker() {
309
+ if (this.pingPromise)
310
+ return this.pingPromise;
311
+ this.pingPromise = new Promise((resolve, reject) => {
312
+ const pingId = (0, utils_2.getRandomId)();
313
+ const cleanup = () => {
314
+ clearTimeout(timeoutId);
315
+ navigator.serviceWorker.removeEventListener("message", onMessage);
316
+ };
317
+ const timeoutId = setTimeout(() => {
318
+ cleanup();
319
+ reject(new errors_1.ServiceWorkerTimeoutError("Service worker ping timed out"));
320
+ }, 2000);
321
+ const onMessage = (event) => {
322
+ if (event.data?.id === pingId && event.data?.tag === "PONG") {
323
+ cleanup();
324
+ resolve();
325
+ }
326
+ };
327
+ navigator.serviceWorker.addEventListener("message", onMessage);
328
+ this.serviceWorker.postMessage({
329
+ id: pingId,
330
+ tag: "PING",
331
+ });
332
+ }).finally(() => {
333
+ this.pingPromise = null;
334
+ });
335
+ return this.pingPromise;
336
+ }
213
337
  // send a message, retrying up to 2 times if the service worker was
214
338
  // killed and restarted by the OS (mobile browsers do this aggressively)
215
- async sendMessage(request) {
339
+ async sendMessageWithRetry(request) {
340
+ // Skip the preflight ping during the initial INIT_WALLET call:
341
+ // create() hasn't set initConfig yet, so reinitialize() would throw.
342
+ if (this.initConfig) {
343
+ try {
344
+ await this.pingServiceWorker();
345
+ }
346
+ catch {
347
+ await this.reinitialize();
348
+ }
349
+ }
216
350
  const maxRetries = 2;
217
351
  for (let attempt = 0;; attempt++) {
218
352
  try {
219
353
  return await this.sendMessageDirect(request);
220
354
  }
221
355
  catch (error) {
222
- const isNotInitialized = typeof error?.message === "string" &&
223
- error.message.includes("MessageBus not initialized");
224
- if (!isNotInitialized || attempt >= maxRetries) {
356
+ if (!isMessageBusNotInitializedError(error) ||
357
+ attempt >= maxRetries) {
358
+ throw error;
359
+ }
360
+ await this.reinitialize();
361
+ }
362
+ }
363
+ }
364
+ // Like sendMessage but for streaming responses — retries with
365
+ // reinitialize when the service worker has been killed/restarted.
366
+ async sendMessageWithEvents(request, onEvent, isComplete) {
367
+ if (this.initConfig) {
368
+ try {
369
+ await this.pingServiceWorker();
370
+ }
371
+ catch {
372
+ await this.reinitialize();
373
+ }
374
+ }
375
+ const maxRetries = 2;
376
+ for (let attempt = 0;; attempt++) {
377
+ try {
378
+ return await this.sendMessageStreaming(request, onEvent, isComplete);
379
+ }
380
+ catch (error) {
381
+ if (!isMessageBusNotInitializedError(error) ||
382
+ attempt >= maxRetries) {
225
383
  throw error;
226
384
  }
227
385
  await this.reinitialize();
@@ -526,6 +684,14 @@ class ServiceWorkerReadonlyWallet {
526
684
  navigator.serviceWorker.removeEventListener("message", messageHandler);
527
685
  };
528
686
  },
687
+ async refreshVtxos() {
688
+ const message = {
689
+ type: "REFRESH_VTXOS",
690
+ id: (0, utils_2.getRandomId)(),
691
+ tag: messageTag,
692
+ };
693
+ await sendContractMessage(message);
694
+ },
529
695
  async isWatching() {
530
696
  const message = {
531
697
  type: "IS_CONTRACT_MANAGER_WATCHING",
@@ -596,7 +762,11 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
596
762
  publicKey: initConfig.arkServerPublicKey,
597
763
  },
598
764
  delegatorUrl: initConfig.delegatorUrl,
765
+ indexerUrl: options.indexerUrl,
766
+ esploraUrl: options.esploraUrl,
599
767
  timeoutMs: options.messageBusTimeoutMs,
768
+ settlementConfig: options.settlementConfig,
769
+ watcherConfig: options.watcherConfig,
600
770
  }, options.messageBusTimeoutMs);
601
771
  // Initialize the service worker with the config
602
772
  const initMessage = {
@@ -614,6 +784,10 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
614
784
  publicKey: initConfig.arkServerPublicKey,
615
785
  },
616
786
  delegatorUrl: initConfig.delegatorUrl,
787
+ indexerUrl: options.indexerUrl,
788
+ esploraUrl: options.esploraUrl,
789
+ settlementConfig: options.settlementConfig,
790
+ watcherConfig: options.watcherConfig,
617
791
  };
618
792
  wallet.initWalletPayload = initConfig;
619
793
  wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
@@ -675,34 +849,8 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
675
849
  payload: { params },
676
850
  };
677
851
  try {
678
- return new Promise((resolve, reject) => {
679
- const messageHandler = (event) => {
680
- const response = event.data;
681
- if (response.id !== message.id) {
682
- return;
683
- }
684
- if (response.error) {
685
- navigator.serviceWorker.removeEventListener("message", messageHandler);
686
- reject(response.error);
687
- return;
688
- }
689
- switch (response.type) {
690
- case "SETTLE_EVENT":
691
- if (callback) {
692
- callback(response.payload);
693
- }
694
- break;
695
- case "SETTLE_SUCCESS":
696
- navigator.serviceWorker.removeEventListener("message", messageHandler);
697
- resolve(response.payload.txid);
698
- break;
699
- default:
700
- console.error(`Unexpected response type for SETTLE request: ${response.type}`);
701
- }
702
- };
703
- navigator.serviceWorker.addEventListener("message", messageHandler);
704
- this.serviceWorker.postMessage(message);
705
- });
852
+ const response = await this.sendMessageWithEvents(message, (resp) => callback?.(resp.payload), (resp) => resp.type === "SETTLE_SUCCESS");
853
+ return response.payload.txid;
706
854
  }
707
855
  catch (error) {
708
856
  throw new Error(`Settlement failed: ${error}`);
@@ -776,5 +924,109 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
776
924
  };
777
925
  return manager;
778
926
  }
927
+ async getVtxoManager() {
928
+ const wallet = this;
929
+ const messageTag = this.messageTag;
930
+ const manager = {
931
+ async recoverVtxos(eventCallback) {
932
+ const message = {
933
+ tag: messageTag,
934
+ type: "RECOVER_VTXOS",
935
+ id: (0, utils_2.getRandomId)(),
936
+ };
937
+ try {
938
+ const response = await wallet.sendMessageWithEvents(message, (resp) => eventCallback?.(resp.payload), (resp) => resp.type === "RECOVER_VTXOS_SUCCESS");
939
+ return response.payload.txid;
940
+ }
941
+ catch (e) {
942
+ throw new Error(`Failed to recover vtxos: ${e}`);
943
+ }
944
+ },
945
+ async getRecoverableBalance() {
946
+ const message = {
947
+ tag: messageTag,
948
+ type: "GET_RECOVERABLE_BALANCE",
949
+ id: (0, utils_2.getRandomId)(),
950
+ };
951
+ try {
952
+ const response = await wallet.sendMessage(message);
953
+ const payload = response
954
+ .payload;
955
+ return {
956
+ recoverable: BigInt(payload.recoverable),
957
+ subdust: BigInt(payload.subdust),
958
+ includesSubdust: payload.includesSubdust,
959
+ vtxoCount: payload.vtxoCount,
960
+ };
961
+ }
962
+ catch (e) {
963
+ throw new Error(`Failed to get recoverable balance: ${e}`);
964
+ }
965
+ },
966
+ async getExpiringVtxos(thresholdMs) {
967
+ const message = {
968
+ tag: messageTag,
969
+ type: "GET_EXPIRING_VTXOS",
970
+ id: (0, utils_2.getRandomId)(),
971
+ payload: { thresholdMs },
972
+ };
973
+ try {
974
+ const response = await wallet.sendMessage(message);
975
+ return response.payload.vtxos;
976
+ }
977
+ catch (e) {
978
+ throw new Error(`Failed to get expiring vtxos: ${e}`);
979
+ }
980
+ },
981
+ async renewVtxos(eventCallback) {
982
+ const message = {
983
+ tag: messageTag,
984
+ type: "RENEW_VTXOS",
985
+ id: (0, utils_2.getRandomId)(),
986
+ };
987
+ try {
988
+ const response = await wallet.sendMessageWithEvents(message, (resp) => eventCallback?.(resp.payload), (resp) => resp.type === "RENEW_VTXOS_SUCCESS");
989
+ return response.payload.txid;
990
+ }
991
+ catch (e) {
992
+ throw new Error(`Failed to renew vtxos: ${e}`);
993
+ }
994
+ },
995
+ async getExpiredBoardingUtxos() {
996
+ const message = {
997
+ tag: messageTag,
998
+ type: "GET_EXPIRED_BOARDING_UTXOS",
999
+ id: (0, utils_2.getRandomId)(),
1000
+ };
1001
+ try {
1002
+ const response = await wallet.sendMessage(message);
1003
+ return response.payload
1004
+ .utxos;
1005
+ }
1006
+ catch (e) {
1007
+ throw new Error(`Failed to get expired boarding utxos: ${e}`);
1008
+ }
1009
+ },
1010
+ async sweepExpiredBoardingUtxos() {
1011
+ const message = {
1012
+ tag: messageTag,
1013
+ type: "SWEEP_EXPIRED_BOARDING_UTXOS",
1014
+ id: (0, utils_2.getRandomId)(),
1015
+ };
1016
+ try {
1017
+ const response = await wallet.sendMessage(message);
1018
+ return response
1019
+ .payload.txid;
1020
+ }
1021
+ catch (e) {
1022
+ throw new Error(`Failed to sweep expired boarding utxos: ${e}`);
1023
+ }
1024
+ },
1025
+ async dispose() {
1026
+ return;
1027
+ },
1028
+ };
1029
+ return manager;
1030
+ }
779
1031
  }
780
1032
  exports.ServiceWorkerWallet = ServiceWorkerWallet;