@arkade-os/sdk 0.4.0-next.6 → 0.4.0-next.8

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.
@@ -95,6 +95,9 @@ class ServiceWorkerReadonlyWallet {
95
95
  constructor(serviceWorker, identity, walletRepository, contractRepository, messageTag) {
96
96
  this.serviceWorker = serviceWorker;
97
97
  this.messageTag = messageTag;
98
+ this.initConfig = null;
99
+ this.initWalletPayload = null;
100
+ this.reinitPromise = null;
98
101
  this.identity = identity;
99
102
  this.walletRepository = walletRepository;
100
103
  this.contractRepository = contractRepository;
@@ -135,6 +138,16 @@ class ServiceWorkerReadonlyWallet {
135
138
  payload: initConfig,
136
139
  };
137
140
  await wallet.sendMessage(initMessage);
141
+ wallet.initConfig = {
142
+ wallet: initConfig.key,
143
+ arkServer: {
144
+ url: initConfig.arkServerUrl,
145
+ publicKey: initConfig.arkServerPublicKey,
146
+ },
147
+ delegatorUrl: initConfig.delegatorUrl,
148
+ };
149
+ wallet.initWalletPayload = initConfig;
150
+ wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
138
151
  return wallet;
139
152
  }
140
153
  /**
@@ -160,15 +173,17 @@ class ServiceWorkerReadonlyWallet {
160
173
  */
161
174
  static async setup(options) {
162
175
  // Register and setup the service worker
163
- const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
176
+ const serviceWorker = await (0, utils_1.setupServiceWorker)({
177
+ path: options.serviceWorkerPath,
178
+ activationTimeoutMs: options.serviceWorkerActivationTimeoutMs,
179
+ });
164
180
  // Use the existing create method
165
181
  return await ServiceWorkerReadonlyWallet.create({
166
182
  ...options,
167
183
  serviceWorker,
168
184
  });
169
185
  }
170
- // send a message and wait for a response
171
- async sendMessage(request) {
186
+ sendMessageDirect(request) {
172
187
  return new Promise((resolve, reject) => {
173
188
  const cleanup = () => {
174
189
  clearTimeout(timeoutId);
@@ -195,6 +210,44 @@ class ServiceWorkerReadonlyWallet {
195
210
  this.serviceWorker.postMessage(request);
196
211
  });
197
212
  }
213
+ // send a message, retrying up to 2 times if the service worker was
214
+ // killed and restarted by the OS (mobile browsers do this aggressively)
215
+ async sendMessage(request) {
216
+ const maxRetries = 2;
217
+ for (let attempt = 0;; attempt++) {
218
+ try {
219
+ return await this.sendMessageDirect(request);
220
+ }
221
+ catch (error) {
222
+ const isNotInitialized = typeof error?.message === "string" &&
223
+ error.message.includes("MessageBus not initialized");
224
+ if (!isNotInitialized || attempt >= maxRetries) {
225
+ throw error;
226
+ }
227
+ await this.reinitialize();
228
+ }
229
+ }
230
+ }
231
+ async reinitialize() {
232
+ if (this.reinitPromise)
233
+ return this.reinitPromise;
234
+ this.reinitPromise = (async () => {
235
+ if (!this.initConfig || !this.initWalletPayload) {
236
+ throw new Error("Cannot re-initialize: missing configuration");
237
+ }
238
+ await initializeMessageBus(this.serviceWorker, this.initConfig, this.messageBusTimeoutMs);
239
+ const initMessage = {
240
+ tag: this.messageTag,
241
+ type: "INIT_WALLET",
242
+ id: (0, utils_2.getRandomId)(),
243
+ payload: this.initWalletPayload,
244
+ };
245
+ await this.sendMessageDirect(initMessage);
246
+ })().finally(() => {
247
+ this.reinitPromise = null;
248
+ });
249
+ return this.reinitPromise;
250
+ }
198
251
  async clear() {
199
252
  const message = {
200
253
  id: (0, utils_2.getRandomId)(),
@@ -554,6 +607,16 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
554
607
  };
555
608
  // Initialize the service worker
556
609
  await wallet.sendMessage(initMessage);
610
+ wallet.initConfig = {
611
+ wallet: initConfig.key,
612
+ arkServer: {
613
+ url: initConfig.arkServerUrl,
614
+ publicKey: initConfig.arkServerPublicKey,
615
+ },
616
+ delegatorUrl: initConfig.delegatorUrl,
617
+ };
618
+ wallet.initWalletPayload = initConfig;
619
+ wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
557
620
  return wallet;
558
621
  }
559
622
  /**
@@ -579,7 +642,10 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
579
642
  */
580
643
  static async setup(options) {
581
644
  // Register and setup the service worker
582
- const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
645
+ const serviceWorker = await (0, utils_1.setupServiceWorker)({
646
+ path: options.serviceWorkerPath,
647
+ activationTimeoutMs: options.serviceWorkerActivationTimeoutMs,
648
+ });
583
649
  // Use the existing create method
584
650
  return ServiceWorkerWallet.create({
585
651
  ...options,
@@ -273,21 +273,6 @@ class ReadonlyWallet {
273
273
  await this.walletRepository.saveVtxos(address, allExtended);
274
274
  return allExtended;
275
275
  }
276
- async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
277
- const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
278
- const response = await this.indexerProvider.getVtxos({ scripts });
279
- const allVtxos = response.vtxos;
280
- let vtxos = allVtxos.filter(_1.isSpendable);
281
- // all recoverable vtxos are spendable by definition
282
- if (!filter.withRecoverable) {
283
- vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo) && !(0, _1.isExpired)(vtxo));
284
- }
285
- if (filter.withUnrolled) {
286
- const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
287
- vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
288
- }
289
- return vtxos;
290
- }
291
276
  async getTransactionHistory() {
292
277
  const scripts = await this.getWalletScripts();
293
278
  const response = await this.indexerProvider.getVtxos({ scripts });
@@ -738,6 +723,10 @@ class Wallet extends ReadonlyWallet {
738
723
  async getDelegatorManager() {
739
724
  return this._delegatorManager;
740
725
  }
726
+ /**
727
+ * @deprecated Use `send`
728
+ * @param params
729
+ */
741
730
  async sendBitcoin(params) {
742
731
  if (params.amount <= 0) {
743
732
  throw new Error("Amount must be positive");
@@ -971,7 +960,7 @@ class Wallet extends ReadonlyWallet {
971
960
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
972
961
  // the signed forfeits transactions to submit
973
962
  const signedForfeits = [];
974
- const vtxos = await this.getVirtualCoins();
963
+ const vtxos = await this.getVtxos();
975
964
  let settlementPsbt = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
976
965
  let hasBoardingUtxos = false;
977
966
  let connectorIndex = 0;
@@ -1290,7 +1279,7 @@ class Wallet extends ReadonlyWallet {
1290
1279
  const recipients = (0, utils_1.validateRecipients)(args, Number(this.dustAmount));
1291
1280
  const address = await this.getAddress();
1292
1281
  const outputAddress = address_1.ArkAddress.decode(address);
1293
- const virtualCoins = await this.getVirtualCoins({
1282
+ const virtualCoins = await this.getVtxos({
1294
1283
  withRecoverable: false,
1295
1284
  });
1296
1285
  // keep track of asset changes
@@ -1425,16 +1414,12 @@ class Wallet extends ReadonlyWallet {
1425
1414
  * @returns The ark transaction id and server-signed checkpoint PSBTs (for bookkeeping)
1426
1415
  */
1427
1416
  async buildAndSubmitOffchainTx(inputs, outputs) {
1428
- const tapLeafScript = this.offchainTapscript.forfeit();
1429
- if (!tapLeafScript) {
1430
- throw new Error("Selected leaf not found");
1431
- }
1432
- const tapTree = this.offchainTapscript.encode();
1433
- const offchainTx = (0, arkTransaction_1.buildOffchainTx)(inputs.map((input) => ({
1434
- ...input,
1435
- tapLeafScript,
1436
- tapTree,
1437
- })), outputs, this.serverUnrollScript);
1417
+ const offchainTx = (0, arkTransaction_1.buildOffchainTx)(inputs.map((input) => {
1418
+ return {
1419
+ ...input,
1420
+ tapLeafScript: input.forfeitTapLeafScript,
1421
+ };
1422
+ }), outputs, this.serverUnrollScript);
1438
1423
  const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
1439
1424
  const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
1440
1425
  const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
@@ -1,22 +1,53 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_DB_NAME = void 0;
3
+ exports.DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = exports.DEFAULT_DB_NAME = void 0;
4
4
  exports.setupServiceWorker = setupServiceWorker;
5
5
  exports.DEFAULT_DB_NAME = "arkade-service-worker";
6
+ exports.DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 10000;
7
+ function normalizeOptions(pathOrOptions) {
8
+ if (typeof pathOrOptions === "string") {
9
+ return {
10
+ path: pathOrOptions,
11
+ activationTimeoutMs: exports.DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS,
12
+ };
13
+ }
14
+ return {
15
+ path: pathOrOptions.path,
16
+ activationTimeoutMs: pathOrOptions.activationTimeoutMs ??
17
+ exports.DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS,
18
+ };
19
+ }
20
+ function waitForServiceWorkerReady(timeoutMs) {
21
+ return new Promise((resolve, reject) => {
22
+ const timeoutId = setTimeout(() => {
23
+ reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
24
+ }, timeoutMs);
25
+ navigator.serviceWorker.ready
26
+ .then((registration) => {
27
+ clearTimeout(timeoutId);
28
+ resolve(registration);
29
+ })
30
+ .catch((error) => {
31
+ clearTimeout(timeoutId);
32
+ reject(error);
33
+ });
34
+ });
35
+ }
6
36
  /**
7
37
  * setupServiceWorker sets up the service worker.
8
- * @param path - the path to the service worker script
38
+ * @param pathOrOptions - the path to the service worker script or setup options
9
39
  * @throws if service workers are not supported or activation fails
10
40
  * @example
11
41
  * ```typescript
12
42
  * const worker = await setupServiceWorker("/service-worker.js");
13
43
  * ```
14
44
  */
15
- async function setupServiceWorker(path) {
45
+ async function setupServiceWorker(pathOrOptions) {
16
46
  // check if service workers are supported
17
47
  if (!("serviceWorker" in navigator)) {
18
48
  throw new Error("Service workers are not supported in this browser");
19
49
  }
50
+ const { path, activationTimeoutMs } = normalizeOptions(pathOrOptions);
20
51
  // register service worker
21
52
  const registration = await navigator.serviceWorker.register(path);
22
53
  // force update to ensure the service worker is active
@@ -25,28 +56,12 @@ async function setupServiceWorker(path) {
25
56
  if (!serviceWorker) {
26
57
  throw new Error("Failed to get service worker instance");
27
58
  }
28
- // wait for the service worker to be ready
29
- return new Promise((resolve, reject) => {
30
- if (serviceWorker.state === "activated")
31
- return resolve(serviceWorker);
32
- const onActivate = () => {
33
- cleanup();
34
- resolve(serviceWorker);
35
- };
36
- const onError = () => {
37
- cleanup();
38
- reject(new Error("Service worker failed to activate"));
39
- };
40
- const timeout = setTimeout(() => {
41
- cleanup();
42
- reject(new Error("Service worker activation timed out"));
43
- }, 10000);
44
- const cleanup = () => {
45
- navigator.serviceWorker.removeEventListener("activate", onActivate);
46
- navigator.serviceWorker.removeEventListener("error", onError);
47
- clearTimeout(timeout);
48
- };
49
- navigator.serviceWorker.addEventListener("activate", onActivate);
50
- navigator.serviceWorker.addEventListener("error", onError);
51
- });
59
+ if (serviceWorker.state === "activated") {
60
+ return serviceWorker;
61
+ }
62
+ const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
63
+ if (!readyRegistration.active) {
64
+ throw new Error("Service worker registration is ready but has no active worker");
65
+ }
66
+ return readyRegistration.active;
52
67
  }
@@ -92,6 +92,9 @@ export class ServiceWorkerReadonlyWallet {
92
92
  constructor(serviceWorker, identity, walletRepository, contractRepository, messageTag) {
93
93
  this.serviceWorker = serviceWorker;
94
94
  this.messageTag = messageTag;
95
+ this.initConfig = null;
96
+ this.initWalletPayload = null;
97
+ this.reinitPromise = null;
95
98
  this.identity = identity;
96
99
  this.walletRepository = walletRepository;
97
100
  this.contractRepository = contractRepository;
@@ -132,6 +135,16 @@ export class ServiceWorkerReadonlyWallet {
132
135
  payload: initConfig,
133
136
  };
134
137
  await wallet.sendMessage(initMessage);
138
+ wallet.initConfig = {
139
+ wallet: initConfig.key,
140
+ arkServer: {
141
+ url: initConfig.arkServerUrl,
142
+ publicKey: initConfig.arkServerPublicKey,
143
+ },
144
+ delegatorUrl: initConfig.delegatorUrl,
145
+ };
146
+ wallet.initWalletPayload = initConfig;
147
+ wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
135
148
  return wallet;
136
149
  }
137
150
  /**
@@ -157,15 +170,17 @@ export class ServiceWorkerReadonlyWallet {
157
170
  */
158
171
  static async setup(options) {
159
172
  // Register and setup the service worker
160
- const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
173
+ const serviceWorker = await setupServiceWorker({
174
+ path: options.serviceWorkerPath,
175
+ activationTimeoutMs: options.serviceWorkerActivationTimeoutMs,
176
+ });
161
177
  // Use the existing create method
162
178
  return await ServiceWorkerReadonlyWallet.create({
163
179
  ...options,
164
180
  serviceWorker,
165
181
  });
166
182
  }
167
- // send a message and wait for a response
168
- async sendMessage(request) {
183
+ sendMessageDirect(request) {
169
184
  return new Promise((resolve, reject) => {
170
185
  const cleanup = () => {
171
186
  clearTimeout(timeoutId);
@@ -192,6 +207,44 @@ export class ServiceWorkerReadonlyWallet {
192
207
  this.serviceWorker.postMessage(request);
193
208
  });
194
209
  }
210
+ // send a message, retrying up to 2 times if the service worker was
211
+ // killed and restarted by the OS (mobile browsers do this aggressively)
212
+ async sendMessage(request) {
213
+ const maxRetries = 2;
214
+ for (let attempt = 0;; attempt++) {
215
+ try {
216
+ return await this.sendMessageDirect(request);
217
+ }
218
+ catch (error) {
219
+ const isNotInitialized = typeof error?.message === "string" &&
220
+ error.message.includes("MessageBus not initialized");
221
+ if (!isNotInitialized || attempt >= maxRetries) {
222
+ throw error;
223
+ }
224
+ await this.reinitialize();
225
+ }
226
+ }
227
+ }
228
+ async reinitialize() {
229
+ if (this.reinitPromise)
230
+ return this.reinitPromise;
231
+ this.reinitPromise = (async () => {
232
+ if (!this.initConfig || !this.initWalletPayload) {
233
+ throw new Error("Cannot re-initialize: missing configuration");
234
+ }
235
+ await initializeMessageBus(this.serviceWorker, this.initConfig, this.messageBusTimeoutMs);
236
+ const initMessage = {
237
+ tag: this.messageTag,
238
+ type: "INIT_WALLET",
239
+ id: getRandomId(),
240
+ payload: this.initWalletPayload,
241
+ };
242
+ await this.sendMessageDirect(initMessage);
243
+ })().finally(() => {
244
+ this.reinitPromise = null;
245
+ });
246
+ return this.reinitPromise;
247
+ }
195
248
  async clear() {
196
249
  const message = {
197
250
  id: getRandomId(),
@@ -550,6 +603,16 @@ export class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
550
603
  };
551
604
  // Initialize the service worker
552
605
  await wallet.sendMessage(initMessage);
606
+ wallet.initConfig = {
607
+ wallet: initConfig.key,
608
+ arkServer: {
609
+ url: initConfig.arkServerUrl,
610
+ publicKey: initConfig.arkServerPublicKey,
611
+ },
612
+ delegatorUrl: initConfig.delegatorUrl,
613
+ };
614
+ wallet.initWalletPayload = initConfig;
615
+ wallet.messageBusTimeoutMs = options.messageBusTimeoutMs;
553
616
  return wallet;
554
617
  }
555
618
  /**
@@ -575,7 +638,10 @@ export class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
575
638
  */
576
639
  static async setup(options) {
577
640
  // Register and setup the service worker
578
- const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
641
+ const serviceWorker = await setupServiceWorker({
642
+ path: options.serviceWorkerPath,
643
+ activationTimeoutMs: options.serviceWorkerActivationTimeoutMs,
644
+ });
579
645
  // Use the existing create method
580
646
  return ServiceWorkerWallet.create({
581
647
  ...options,
@@ -268,21 +268,6 @@ export class ReadonlyWallet {
268
268
  await this.walletRepository.saveVtxos(address, allExtended);
269
269
  return allExtended;
270
270
  }
271
- async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
272
- const scripts = [hex.encode(this.offchainTapscript.pkScript)];
273
- const response = await this.indexerProvider.getVtxos({ scripts });
274
- const allVtxos = response.vtxos;
275
- let vtxos = allVtxos.filter(isSpendable);
276
- // all recoverable vtxos are spendable by definition
277
- if (!filter.withRecoverable) {
278
- vtxos = vtxos.filter((vtxo) => !isRecoverable(vtxo) && !isExpired(vtxo));
279
- }
280
- if (filter.withUnrolled) {
281
- const spentVtxos = allVtxos.filter((vtxo) => !isSpendable(vtxo));
282
- vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
283
- }
284
- return vtxos;
285
- }
286
271
  async getTransactionHistory() {
287
272
  const scripts = await this.getWalletScripts();
288
273
  const response = await this.indexerProvider.getVtxos({ scripts });
@@ -732,6 +717,10 @@ export class Wallet extends ReadonlyWallet {
732
717
  async getDelegatorManager() {
733
718
  return this._delegatorManager;
734
719
  }
720
+ /**
721
+ * @deprecated Use `send`
722
+ * @param params
723
+ */
735
724
  async sendBitcoin(params) {
736
725
  if (params.amount <= 0) {
737
726
  throw new Error("Amount must be positive");
@@ -965,7 +954,7 @@ export class Wallet extends ReadonlyWallet {
965
954
  async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
966
955
  // the signed forfeits transactions to submit
967
956
  const signedForfeits = [];
968
- const vtxos = await this.getVirtualCoins();
957
+ const vtxos = await this.getVtxos();
969
958
  let settlementPsbt = Transaction.fromPSBT(base64.decode(event.commitmentTx));
970
959
  let hasBoardingUtxos = false;
971
960
  let connectorIndex = 0;
@@ -1284,7 +1273,7 @@ export class Wallet extends ReadonlyWallet {
1284
1273
  const recipients = validateRecipients(args, Number(this.dustAmount));
1285
1274
  const address = await this.getAddress();
1286
1275
  const outputAddress = ArkAddress.decode(address);
1287
- const virtualCoins = await this.getVirtualCoins({
1276
+ const virtualCoins = await this.getVtxos({
1288
1277
  withRecoverable: false,
1289
1278
  });
1290
1279
  // keep track of asset changes
@@ -1419,16 +1408,12 @@ export class Wallet extends ReadonlyWallet {
1419
1408
  * @returns The ark transaction id and server-signed checkpoint PSBTs (for bookkeeping)
1420
1409
  */
1421
1410
  async buildAndSubmitOffchainTx(inputs, outputs) {
1422
- const tapLeafScript = this.offchainTapscript.forfeit();
1423
- if (!tapLeafScript) {
1424
- throw new Error("Selected leaf not found");
1425
- }
1426
- const tapTree = this.offchainTapscript.encode();
1427
- const offchainTx = buildOffchainTx(inputs.map((input) => ({
1428
- ...input,
1429
- tapLeafScript,
1430
- tapTree,
1431
- })), outputs, this.serverUnrollScript);
1411
+ const offchainTx = buildOffchainTx(inputs.map((input) => {
1412
+ return {
1413
+ ...input,
1414
+ tapLeafScript: input.forfeitTapLeafScript,
1415
+ };
1416
+ }), outputs, this.serverUnrollScript);
1432
1417
  const signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
1433
1418
  const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT())));
1434
1419
  const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
@@ -1,18 +1,49 @@
1
1
  export const DEFAULT_DB_NAME = "arkade-service-worker";
2
+ export const DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 10000;
3
+ function normalizeOptions(pathOrOptions) {
4
+ if (typeof pathOrOptions === "string") {
5
+ return {
6
+ path: pathOrOptions,
7
+ activationTimeoutMs: DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS,
8
+ };
9
+ }
10
+ return {
11
+ path: pathOrOptions.path,
12
+ activationTimeoutMs: pathOrOptions.activationTimeoutMs ??
13
+ DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS,
14
+ };
15
+ }
16
+ function waitForServiceWorkerReady(timeoutMs) {
17
+ return new Promise((resolve, reject) => {
18
+ const timeoutId = setTimeout(() => {
19
+ reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
20
+ }, timeoutMs);
21
+ navigator.serviceWorker.ready
22
+ .then((registration) => {
23
+ clearTimeout(timeoutId);
24
+ resolve(registration);
25
+ })
26
+ .catch((error) => {
27
+ clearTimeout(timeoutId);
28
+ reject(error);
29
+ });
30
+ });
31
+ }
2
32
  /**
3
33
  * setupServiceWorker sets up the service worker.
4
- * @param path - the path to the service worker script
34
+ * @param pathOrOptions - the path to the service worker script or setup options
5
35
  * @throws if service workers are not supported or activation fails
6
36
  * @example
7
37
  * ```typescript
8
38
  * const worker = await setupServiceWorker("/service-worker.js");
9
39
  * ```
10
40
  */
11
- export async function setupServiceWorker(path) {
41
+ export async function setupServiceWorker(pathOrOptions) {
12
42
  // check if service workers are supported
13
43
  if (!("serviceWorker" in navigator)) {
14
44
  throw new Error("Service workers are not supported in this browser");
15
45
  }
46
+ const { path, activationTimeoutMs } = normalizeOptions(pathOrOptions);
16
47
  // register service worker
17
48
  const registration = await navigator.serviceWorker.register(path);
18
49
  // force update to ensure the service worker is active
@@ -21,28 +52,12 @@ export async function setupServiceWorker(path) {
21
52
  if (!serviceWorker) {
22
53
  throw new Error("Failed to get service worker instance");
23
54
  }
24
- // wait for the service worker to be ready
25
- return new Promise((resolve, reject) => {
26
- if (serviceWorker.state === "activated")
27
- return resolve(serviceWorker);
28
- const onActivate = () => {
29
- cleanup();
30
- resolve(serviceWorker);
31
- };
32
- const onError = () => {
33
- cleanup();
34
- reject(new Error("Service worker failed to activate"));
35
- };
36
- const timeout = setTimeout(() => {
37
- cleanup();
38
- reject(new Error("Service worker activation timed out"));
39
- }, 10000);
40
- const cleanup = () => {
41
- navigator.serviceWorker.removeEventListener("activate", onActivate);
42
- navigator.serviceWorker.removeEventListener("error", onError);
43
- clearTimeout(timeout);
44
- };
45
- navigator.serviceWorker.addEventListener("activate", onActivate);
46
- navigator.serviceWorker.addEventListener("error", onError);
47
- });
55
+ if (serviceWorker.state === "activated") {
56
+ return serviceWorker;
57
+ }
58
+ const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
59
+ if (!readyRegistration.active) {
60
+ throw new Error("Service worker registration is ready but has no active worker");
61
+ }
62
+ return readyRegistration.active;
48
63
  }
@@ -1,5 +1,5 @@
1
1
  import { Packet } from "../extension/asset";
2
- import { Asset, Recipient, VirtualCoin } from "./index";
2
+ import { Asset, ExtendedVirtualCoin, Recipient, VirtualCoin } from "./index";
3
3
  /**
4
4
  * Creates an asset packet from asset inputs and receivers.
5
5
  * Groups inputs and outputs by asset ID and creates the Packet object
@@ -13,8 +13,8 @@ export declare function createAssetPacket(assetInputs: Map<number, Asset[]>, rec
13
13
  * Selects coins that contain a specific asset.
14
14
  * Returns coins sorted by amount (smallest first for better coin selection).
15
15
  */
16
- export declare function selectCoinsWithAsset(coins: VirtualCoin[], assetId: string, requiredAmount: bigint): {
17
- selected: VirtualCoin[];
16
+ export declare function selectCoinsWithAsset(coins: ExtendedVirtualCoin[], assetId: string, requiredAmount: bigint): {
17
+ selected: ExtendedVirtualCoin[];
18
18
  totalAssetAmount: bigint;
19
19
  };
20
20
  export declare function computeAssetChange(inputAssets: Map<string, bigint>, outputAssets: Map<string, bigint>): Map<string, bigint>;
@@ -140,7 +140,7 @@ export interface SendBitcoinParams {
140
140
  amount: number;
141
141
  feeRate?: number;
142
142
  memo?: string;
143
- selectedVtxos?: VirtualCoin[];
143
+ selectedVtxos?: ExtendedVirtualCoin[];
144
144
  }
145
145
  export interface Asset {
146
146
  assetId: string;
@@ -3,7 +3,7 @@ import { SettlementEvent } from "../../providers/ark";
3
3
  import { Identity, ReadonlyIdentity } from "../../identity";
4
4
  import { WalletRepository } from "../../repositories/walletRepository";
5
5
  import { ContractRepository } from "../../repositories/contractRepository";
6
- import { ResponseGetStatus, WalletUpdaterRequest, WalletUpdaterResponse } from "./wallet-message-handler";
6
+ import { RequestInitWallet, ResponseGetStatus, WalletUpdaterRequest, WalletUpdaterResponse } from "./wallet-message-handler";
7
7
  import type { IContractManager } from "../../contracts/contractManager";
8
8
  import type { IDelegatorManager } from "../delegator";
9
9
  type PrivateKeyIdentity = Identity & {
@@ -56,6 +56,20 @@ export type ServiceWorkerWalletCreateOptions = ServiceWorkerWalletOptions & {
56
56
  };
57
57
  export type ServiceWorkerWalletSetupOptions = ServiceWorkerWalletOptions & {
58
58
  serviceWorkerPath: string;
59
+ serviceWorkerActivationTimeoutMs?: number;
60
+ };
61
+ type MessageBusInitConfig = {
62
+ wallet: {
63
+ privateKey: string;
64
+ } | {
65
+ publicKey: string;
66
+ };
67
+ arkServer: {
68
+ url: string;
69
+ publicKey?: string;
70
+ };
71
+ delegatorUrl?: string;
72
+ timeoutMs?: number;
59
73
  };
60
74
  export declare class ServiceWorkerReadonlyWallet implements IReadonlyWallet {
61
75
  readonly serviceWorker: ServiceWorker;
@@ -64,6 +78,10 @@ export declare class ServiceWorkerReadonlyWallet implements IReadonlyWallet {
64
78
  readonly contractRepository: ContractRepository;
65
79
  readonly identity: ReadonlyIdentity;
66
80
  private readonly _readonlyAssetManager;
81
+ protected initConfig: MessageBusInitConfig | null;
82
+ protected initWalletPayload: RequestInitWallet["payload"] | null;
83
+ protected messageBusTimeoutMs?: number;
84
+ private reinitPromise;
67
85
  get assetManager(): IReadonlyAssetManager;
68
86
  protected constructor(serviceWorker: ServiceWorker, identity: ReadonlyIdentity, walletRepository: WalletRepository, contractRepository: ContractRepository, messageTag: string);
69
87
  static create(options: ServiceWorkerWalletCreateOptions): Promise<ServiceWorkerReadonlyWallet>;
@@ -89,7 +107,9 @@ export declare class ServiceWorkerReadonlyWallet implements IReadonlyWallet {
89
107
  * ```
90
108
  */
91
109
  static setup(options: ServiceWorkerWalletSetupOptions): Promise<ServiceWorkerReadonlyWallet>;
110
+ private sendMessageDirect;
92
111
  protected sendMessage(request: WalletUpdaterRequest): Promise<WalletUpdaterResponse>;
112
+ private reinitialize;
93
113
  clear(): Promise<void>;
94
114
  getAddress(): Promise<string>;
95
115
  getBoardingAddress(): Promise<string>;
@@ -7,7 +7,7 @@ import { OnchainProvider } from "../providers/onchain";
7
7
  import { ArkProvider, SettlementEvent, SignedIntent } from "../providers/ark";
8
8
  import { SignerSession } from "../tree/signingSession";
9
9
  import { Identity, ReadonlyIdentity } from "../identity";
10
- import { ArkTransaction, Recipient, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IReadonlyWallet, IWallet, ReadonlyWalletConfig, SendBitcoinParams, SettleParams, VirtualCoin, WalletBalance, WalletConfig, IAssetManager, IReadonlyAssetManager } from ".";
10
+ import { ArkTransaction, Recipient, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IReadonlyWallet, IWallet, ReadonlyWalletConfig, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig, IAssetManager, IReadonlyAssetManager } from ".";
11
11
  import { CSVMultisigTapscript } from "../script/tapscript";
12
12
  import { Intent } from "../intent";
13
13
  import { IndexerProvider } from "../providers/indexer";
@@ -74,7 +74,6 @@ export declare class ReadonlyWallet implements IReadonlyWallet {
74
74
  getBoardingAddress(): Promise<string>;
75
75
  getBalance(): Promise<WalletBalance>;
76
76
  getVtxos(filter?: GetVtxosFilter): Promise<ExtendedVirtualCoin[]>;
77
- protected getVirtualCoins(filter?: GetVtxosFilter): Promise<VirtualCoin[]>;
78
77
  getTransactionHistory(): Promise<ArkTransaction[]>;
79
78
  getBoardingTxs(): Promise<{
80
79
  boardingTxs: ArkTransaction[];
@@ -193,6 +192,10 @@ export declare class Wallet extends ReadonlyWallet implements IWallet {
193
192
  */
194
193
  toReadonly(): Promise<ReadonlyWallet>;
195
194
  getDelegatorManager(): Promise<IDelegatorManager | undefined>;
195
+ /**
196
+ * @deprecated Use `send`
197
+ * @param params
198
+ */
196
199
  sendBitcoin(params: SendBitcoinParams): Promise<string>;
197
200
  settle(params?: SettleParams, eventCallback?: (event: SettlementEvent) => void): Promise<string>;
198
201
  private handleSettlementFinalizationEvent;
@@ -238,7 +241,7 @@ export declare class Wallet extends ReadonlyWallet implements IWallet {
238
241
  * sign it, submit to the ark provider, and finalize.
239
242
  * @returns The ark transaction id and server-signed checkpoint PSBTs (for bookkeeping)
240
243
  */
241
- buildAndSubmitOffchainTx(inputs: VirtualCoin[], outputs: TransactionOutput[]): Promise<{
244
+ buildAndSubmitOffchainTx(inputs: ExtendedVirtualCoin[], outputs: TransactionOutput[]): Promise<{
242
245
  arkTxid: string;
243
246
  signedCheckpointTxs: string[];
244
247
  }>;
@@ -251,8 +254,8 @@ export declare class Wallet extends ReadonlyWallet implements IWallet {
251
254
  * @param targetAmount Target amount to reach in satoshis
252
255
  * @returns Selected coins and change amount
253
256
  */
254
- export declare function selectVirtualCoins(coins: VirtualCoin[], targetAmount: number): {
255
- inputs: VirtualCoin[];
257
+ export declare function selectVirtualCoins(coins: ExtendedVirtualCoin[], targetAmount: number): {
258
+ inputs: ExtendedVirtualCoin[];
256
259
  changeAmount: bigint;
257
260
  };
258
261
  /**
@@ -1,11 +1,17 @@
1
1
  export declare const DEFAULT_DB_NAME = "arkade-service-worker";
2
+ export declare const DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 10000;
3
+ type SetupServiceWorkerOptions = {
4
+ path: string;
5
+ activationTimeoutMs?: number;
6
+ };
2
7
  /**
3
8
  * setupServiceWorker sets up the service worker.
4
- * @param path - the path to the service worker script
9
+ * @param pathOrOptions - the path to the service worker script or setup options
5
10
  * @throws if service workers are not supported or activation fails
6
11
  * @example
7
12
  * ```typescript
8
13
  * const worker = await setupServiceWorker("/service-worker.js");
9
14
  * ```
10
15
  */
11
- export declare function setupServiceWorker(path: string): Promise<ServiceWorker>;
16
+ export declare function setupServiceWorker(pathOrOptions: string | SetupServiceWorkerOptions): Promise<ServiceWorker>;
17
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/sdk",
3
- "version": "0.4.0-next.6",
3
+ "version": "0.4.0-next.8",
4
4
  "description": "Bitcoin wallet SDK with Taproot and Ark integration",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",