@arkade-os/sdk 0.3.1-alpha.2 → 0.3.1-alpha.3

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.
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
3
+ exports.ArkError = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
4
+ exports.maybeArkError = void 0;
4
5
  const transaction_js_1 = require("@scure/btc-signer/transaction.js");
5
6
  Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_js_1.Transaction; } });
6
7
  const singleKey_1 = require("./identity/singleKey");
@@ -80,3 +81,6 @@ const walletRepository_1 = require("./repositories/walletRepository");
80
81
  Object.defineProperty(exports, "WalletRepositoryImpl", { enumerable: true, get: function () { return walletRepository_1.WalletRepositoryImpl; } });
81
82
  const contractRepository_1 = require("./repositories/contractRepository");
82
83
  Object.defineProperty(exports, "ContractRepositoryImpl", { enumerable: true, get: function () { return contractRepository_1.ContractRepositoryImpl; } });
84
+ const errors_1 = require("./providers/errors");
85
+ Object.defineProperty(exports, "ArkError", { enumerable: true, get: function () { return errors_1.ArkError; } });
86
+ Object.defineProperty(exports, "maybeArkError", { enumerable: true, get: function () { return errors_1.maybeArkError; } });
@@ -4,6 +4,7 @@ exports.RestArkProvider = exports.SettlementEventType = void 0;
4
4
  exports.isFetchTimeoutError = isFetchTimeoutError;
5
5
  const base_1 = require("@scure/base");
6
6
  const utils_1 = require("./utils");
7
+ const errors_1 = require("./errors");
7
8
  var SettlementEventType;
8
9
  (function (SettlementEventType) {
9
10
  SettlementEventType["BatchStarted"] = "batch_started";
@@ -32,7 +33,8 @@ class RestArkProvider {
32
33
  const url = `${this.serverUrl}/v1/info`;
33
34
  const response = await fetch(url);
34
35
  if (!response.ok) {
35
- throw new Error(`Failed to get server info: ${response.statusText}`);
36
+ const errorText = await response.text();
37
+ handleError(errorText, `Failed to get server info: ${response.statusText}`);
36
38
  }
37
39
  const fromServer = await response.json();
38
40
  return {
@@ -82,16 +84,7 @@ class RestArkProvider {
82
84
  });
83
85
  if (!response.ok) {
84
86
  const errorText = await response.text();
85
- try {
86
- const grpcError = JSON.parse(errorText);
87
- // gRPC errors usually have a message and code field
88
- throw new Error(`Failed to submit virtual transaction: ${grpcError.message || grpcError.error || errorText}`);
89
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
90
- }
91
- catch (_) {
92
- // If JSON parse fails, use the raw error text
93
- throw new Error(`Failed to submit virtual transaction: ${errorText}`);
94
- }
87
+ handleError(errorText, `Failed to submit virtual transaction: ${errorText}`);
95
88
  }
96
89
  const data = await response.json();
97
90
  return {
@@ -114,7 +107,7 @@ class RestArkProvider {
114
107
  });
115
108
  if (!response.ok) {
116
109
  const errorText = await response.text();
117
- throw new Error(`Failed to finalize offchain transaction: ${errorText}`);
110
+ handleError(errorText, `Failed to finalize offchain transaction: ${errorText}`);
118
111
  }
119
112
  }
120
113
  async registerIntent(intent) {
@@ -133,7 +126,7 @@ class RestArkProvider {
133
126
  });
134
127
  if (!response.ok) {
135
128
  const errorText = await response.text();
136
- throw new Error(`Failed to register intent: ${errorText}`);
129
+ handleError(errorText, `Failed to register intent: ${errorText}`);
137
130
  }
138
131
  const data = await response.json();
139
132
  return data.intentId;
@@ -154,7 +147,7 @@ class RestArkProvider {
154
147
  });
155
148
  if (!response.ok) {
156
149
  const errorText = await response.text();
157
- throw new Error(`Failed to delete intent: ${errorText}`);
150
+ handleError(errorText, `Failed to delete intent: ${errorText}`);
158
151
  }
159
152
  }
160
153
  async confirmRegistration(intentId) {
@@ -170,7 +163,7 @@ class RestArkProvider {
170
163
  });
171
164
  if (!response.ok) {
172
165
  const errorText = await response.text();
173
- throw new Error(`Failed to confirm registration: ${errorText}`);
166
+ handleError(errorText, `Failed to confirm registration: ${errorText}`);
174
167
  }
175
168
  }
176
169
  async submitTreeNonces(batchId, pubkey, nonces) {
@@ -188,7 +181,7 @@ class RestArkProvider {
188
181
  });
189
182
  if (!response.ok) {
190
183
  const errorText = await response.text();
191
- throw new Error(`Failed to submit tree nonces: ${errorText}`);
184
+ handleError(errorText, `Failed to submit tree nonces: ${errorText}`);
192
185
  }
193
186
  }
194
187
  async submitTreeSignatures(batchId, pubkey, signatures) {
@@ -206,7 +199,7 @@ class RestArkProvider {
206
199
  });
207
200
  if (!response.ok) {
208
201
  const errorText = await response.text();
209
- throw new Error(`Failed to submit tree signatures: ${errorText}`);
202
+ handleError(errorText, `Failed to submit tree signatures: ${errorText}`);
210
203
  }
211
204
  }
212
205
  async submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
@@ -222,7 +215,8 @@ class RestArkProvider {
222
215
  }),
223
216
  });
224
217
  if (!response.ok) {
225
- throw new Error(`Failed to submit forfeit transactions: ${response.statusText}`);
218
+ const errorText = await response.text();
219
+ handleError(errorText, `Failed to submit forfeit transactions: ${response.statusText}`);
226
220
  }
227
221
  }
228
222
  async *getEventStream(signal, topics) {
@@ -331,16 +325,7 @@ class RestArkProvider {
331
325
  });
332
326
  if (!response.ok) {
333
327
  const errorText = await response.text();
334
- try {
335
- const grpcError = JSON.parse(errorText);
336
- // gRPC errors usually have a message and code field
337
- throw new Error(`Failed to get pending transactions: ${grpcError.message || grpcError.error || errorText}`);
338
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
339
- }
340
- catch (_) {
341
- // If JSON parse fails, use the raw error text
342
- throw new Error(`Failed to get pending transactions: ${errorText}`);
343
- }
328
+ handleError(errorText, `Failed to get pending transactions: ${errorText}`);
344
329
  }
345
330
  const data = await response.json();
346
331
  return data.pendingTxs;
@@ -524,3 +509,8 @@ function mapVtxo(vtxo) {
524
509
  arkTxid: vtxo.arkTxid,
525
510
  };
526
511
  }
512
+ function handleError(errorText, defaultMessage) {
513
+ const error = new Error(errorText);
514
+ const arkError = (0, errors_1.maybeArkError)(error);
515
+ throw arkError ?? new Error(defaultMessage);
516
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ArkError = void 0;
4
+ exports.maybeArkError = maybeArkError;
5
+ class ArkError extends Error {
6
+ constructor(code, message, name, metadata) {
7
+ super(message);
8
+ this.code = code;
9
+ this.message = message;
10
+ this.name = name;
11
+ this.metadata = metadata;
12
+ }
13
+ }
14
+ exports.ArkError = ArkError;
15
+ /**
16
+ * Try to convert an error to an ArkError class, returning undefined if the error is not an ArkError
17
+ * @param error - The error to parse
18
+ * @returns The parsed ArkError, or undefined if the error is not an ArkError
19
+ */
20
+ function maybeArkError(error) {
21
+ try {
22
+ if (!(error instanceof Error))
23
+ return undefined;
24
+ const decoded = JSON.parse(error.message);
25
+ if (!("details" in decoded))
26
+ return undefined;
27
+ if (!Array.isArray(decoded.details))
28
+ return undefined;
29
+ // search for a valid details object with the correct type
30
+ for (const details of decoded.details) {
31
+ if (!("@type" in details))
32
+ continue;
33
+ const type = details["@type"];
34
+ if (type !== "type.googleapis.com/ark.v1.ErrorDetails")
35
+ continue;
36
+ if (!("code" in details))
37
+ continue;
38
+ const code = details.code;
39
+ if (!("message" in details))
40
+ continue;
41
+ const message = details.message;
42
+ if (!("name" in details))
43
+ continue;
44
+ const name = details.name;
45
+ let metadata;
46
+ if ("metadata" in details && isMetadata(details.metadata)) {
47
+ metadata = details.metadata;
48
+ }
49
+ return new ArkError(code, message, name, metadata);
50
+ }
51
+ return undefined;
52
+ }
53
+ catch (e) {
54
+ return undefined;
55
+ }
56
+ }
57
+ function isMetadata(value) {
58
+ return typeof value === "object" && value !== null && !Array.isArray(value);
59
+ }
@@ -61,18 +61,6 @@ class WalletRepositoryImpl {
61
61
  return [];
62
62
  }
63
63
  }
64
- async saveVtxo(address, vtxo) {
65
- const vtxos = await this.getVtxos(address);
66
- const existing = vtxos.findIndex((v) => v.txid === vtxo.txid && v.vout === vtxo.vout);
67
- if (existing !== -1) {
68
- vtxos[existing] = vtxo;
69
- }
70
- else {
71
- vtxos.push(vtxo);
72
- }
73
- this.cache.vtxos.set(address, vtxos.slice());
74
- await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
75
- }
76
64
  async saveVtxos(address, vtxos) {
77
65
  const storedVtxos = await this.getVtxos(address);
78
66
  for (const vtxo of vtxos) {
@@ -119,20 +107,6 @@ class WalletRepositoryImpl {
119
107
  return [];
120
108
  }
121
109
  }
122
- async saveTransaction(address, tx) {
123
- const transactions = await this.getTransactionHistory(address);
124
- const existing = transactions.findIndex((t) => t.key === tx.key);
125
- if (existing !== -1) {
126
- transactions[existing] = tx;
127
- }
128
- else {
129
- transactions.push(tx);
130
- }
131
- // Sort by createdAt descending
132
- transactions.sort((a, b) => b.createdAt - a.createdAt);
133
- this.cache.transactions.set(address, transactions);
134
- await this.storage.setItem(`tx:${address}`, JSON.stringify(transactions));
135
- }
136
110
  async saveTransactions(address, txs) {
137
111
  const storedTransactions = await this.getTransactionHistory(address);
138
112
  for (const tx of txs) {
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Response = void 0;
4
+ const base_1 = require("@scure/base");
5
+ function getRandomId() {
6
+ const randomValue = crypto.getRandomValues(new Uint8Array(16));
7
+ return base_1.hex.encode(randomValue);
8
+ }
4
9
  /**
5
10
  * Response is the namespace that contains the response types for the service worker.
6
11
  */
@@ -187,4 +192,31 @@ var Response;
187
192
  };
188
193
  }
189
194
  Response.walletReloaded = walletReloaded;
195
+ function isVtxoUpdate(response) {
196
+ return response.type === "VTXO_UPDATE";
197
+ }
198
+ Response.isVtxoUpdate = isVtxoUpdate;
199
+ function vtxoUpdate(newVtxos, spentVtxos) {
200
+ return {
201
+ type: "VTXO_UPDATE",
202
+ id: getRandomId(), // spontaneous update, not tied to a request
203
+ success: true,
204
+ spentVtxos,
205
+ newVtxos,
206
+ };
207
+ }
208
+ Response.vtxoUpdate = vtxoUpdate;
209
+ function isUtxoUpdate(response) {
210
+ return response.type === "UTXO_UPDATE";
211
+ }
212
+ Response.isUtxoUpdate = isUtxoUpdate;
213
+ function utxoUpdate(coins) {
214
+ return {
215
+ type: "UTXO_UPDATE",
216
+ id: getRandomId(), // spontaneous update, not tied to a request
217
+ success: true,
218
+ coins,
219
+ };
220
+ }
221
+ Response.utxoUpdate = utxoUpdate;
190
222
  })(Response || (exports.Response = Response = {}));
@@ -256,6 +256,9 @@ class ServiceWorkerWallet {
256
256
  return new Promise((resolve, reject) => {
257
257
  const messageHandler = (event) => {
258
258
  const response = event.data;
259
+ if (response.id !== message.id) {
260
+ return;
261
+ }
259
262
  if (!response.success) {
260
263
  navigator.serviceWorker.removeEventListener("message", messageHandler);
261
264
  reject(new Error(response.message));
@@ -130,11 +130,11 @@ class Worker {
130
130
  ...spentVtxos,
131
131
  ]);
132
132
  // notify all clients about the vtxo update
133
- this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
133
+ this.sendMessageToAllClients(response_1.Response.vtxoUpdate(newVtxos, spentVtxos));
134
134
  }
135
135
  if (funds.type === "utxo") {
136
136
  // notify all clients about the utxo update
137
- this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
137
+ this.sendMessageToAllClients(response_1.Response.utxoUpdate(funds.coins));
138
138
  }
139
139
  });
140
140
  }
@@ -518,15 +518,12 @@ class Worker {
518
518
  event.source?.postMessage(response_1.Response.error(message.id, "Unknown message type"));
519
519
  }
520
520
  }
521
- async sendMessageToAllClients(type, message) {
521
+ async sendMessageToAllClients(message) {
522
522
  self.clients
523
523
  .matchAll({ includeUncontrolled: true, type: "window" })
524
524
  .then((clients) => {
525
525
  clients.forEach((client) => {
526
- client.postMessage({
527
- type,
528
- message,
529
- });
526
+ client.postMessage(message);
530
527
  });
531
528
  });
532
529
  }
package/dist/esm/index.js CHANGED
@@ -28,6 +28,7 @@ import { P2A } from './utils/anchor.js';
28
28
  import { Unroll } from './wallet/unroll.js';
29
29
  import { WalletRepositoryImpl } from './repositories/walletRepository.js';
30
30
  import { ContractRepositoryImpl } from './repositories/contractRepository.js';
31
+ import { ArkError, maybeArkError } from './providers/errors.js';
31
32
  export {
32
33
  // Wallets
33
34
  Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager,
@@ -56,4 +57,6 @@ Intent,
56
57
  // TxTree
57
58
  TxTree,
58
59
  // Anchor
59
- P2A, Unroll, Transaction, };
60
+ P2A, Unroll, Transaction,
61
+ // Errors
62
+ ArkError, maybeArkError, };
@@ -1,5 +1,6 @@
1
1
  import { hex } from "@scure/base";
2
2
  import { eventSourceIterator } from './utils.js';
3
+ import { maybeArkError } from './errors.js';
3
4
  export var SettlementEventType;
4
5
  (function (SettlementEventType) {
5
6
  SettlementEventType["BatchStarted"] = "batch_started";
@@ -28,7 +29,8 @@ export class RestArkProvider {
28
29
  const url = `${this.serverUrl}/v1/info`;
29
30
  const response = await fetch(url);
30
31
  if (!response.ok) {
31
- throw new Error(`Failed to get server info: ${response.statusText}`);
32
+ const errorText = await response.text();
33
+ handleError(errorText, `Failed to get server info: ${response.statusText}`);
32
34
  }
33
35
  const fromServer = await response.json();
34
36
  return {
@@ -78,16 +80,7 @@ export class RestArkProvider {
78
80
  });
79
81
  if (!response.ok) {
80
82
  const errorText = await response.text();
81
- try {
82
- const grpcError = JSON.parse(errorText);
83
- // gRPC errors usually have a message and code field
84
- throw new Error(`Failed to submit virtual transaction: ${grpcError.message || grpcError.error || errorText}`);
85
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
86
- }
87
- catch (_) {
88
- // If JSON parse fails, use the raw error text
89
- throw new Error(`Failed to submit virtual transaction: ${errorText}`);
90
- }
83
+ handleError(errorText, `Failed to submit virtual transaction: ${errorText}`);
91
84
  }
92
85
  const data = await response.json();
93
86
  return {
@@ -110,7 +103,7 @@ export class RestArkProvider {
110
103
  });
111
104
  if (!response.ok) {
112
105
  const errorText = await response.text();
113
- throw new Error(`Failed to finalize offchain transaction: ${errorText}`);
106
+ handleError(errorText, `Failed to finalize offchain transaction: ${errorText}`);
114
107
  }
115
108
  }
116
109
  async registerIntent(intent) {
@@ -129,7 +122,7 @@ export class RestArkProvider {
129
122
  });
130
123
  if (!response.ok) {
131
124
  const errorText = await response.text();
132
- throw new Error(`Failed to register intent: ${errorText}`);
125
+ handleError(errorText, `Failed to register intent: ${errorText}`);
133
126
  }
134
127
  const data = await response.json();
135
128
  return data.intentId;
@@ -150,7 +143,7 @@ export class RestArkProvider {
150
143
  });
151
144
  if (!response.ok) {
152
145
  const errorText = await response.text();
153
- throw new Error(`Failed to delete intent: ${errorText}`);
146
+ handleError(errorText, `Failed to delete intent: ${errorText}`);
154
147
  }
155
148
  }
156
149
  async confirmRegistration(intentId) {
@@ -166,7 +159,7 @@ export class RestArkProvider {
166
159
  });
167
160
  if (!response.ok) {
168
161
  const errorText = await response.text();
169
- throw new Error(`Failed to confirm registration: ${errorText}`);
162
+ handleError(errorText, `Failed to confirm registration: ${errorText}`);
170
163
  }
171
164
  }
172
165
  async submitTreeNonces(batchId, pubkey, nonces) {
@@ -184,7 +177,7 @@ export class RestArkProvider {
184
177
  });
185
178
  if (!response.ok) {
186
179
  const errorText = await response.text();
187
- throw new Error(`Failed to submit tree nonces: ${errorText}`);
180
+ handleError(errorText, `Failed to submit tree nonces: ${errorText}`);
188
181
  }
189
182
  }
190
183
  async submitTreeSignatures(batchId, pubkey, signatures) {
@@ -202,7 +195,7 @@ export class RestArkProvider {
202
195
  });
203
196
  if (!response.ok) {
204
197
  const errorText = await response.text();
205
- throw new Error(`Failed to submit tree signatures: ${errorText}`);
198
+ handleError(errorText, `Failed to submit tree signatures: ${errorText}`);
206
199
  }
207
200
  }
208
201
  async submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
@@ -218,7 +211,8 @@ export class RestArkProvider {
218
211
  }),
219
212
  });
220
213
  if (!response.ok) {
221
- throw new Error(`Failed to submit forfeit transactions: ${response.statusText}`);
214
+ const errorText = await response.text();
215
+ handleError(errorText, `Failed to submit forfeit transactions: ${response.statusText}`);
222
216
  }
223
217
  }
224
218
  async *getEventStream(signal, topics) {
@@ -327,16 +321,7 @@ export class RestArkProvider {
327
321
  });
328
322
  if (!response.ok) {
329
323
  const errorText = await response.text();
330
- try {
331
- const grpcError = JSON.parse(errorText);
332
- // gRPC errors usually have a message and code field
333
- throw new Error(`Failed to get pending transactions: ${grpcError.message || grpcError.error || errorText}`);
334
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
335
- }
336
- catch (_) {
337
- // If JSON parse fails, use the raw error text
338
- throw new Error(`Failed to get pending transactions: ${errorText}`);
339
- }
324
+ handleError(errorText, `Failed to get pending transactions: ${errorText}`);
340
325
  }
341
326
  const data = await response.json();
342
327
  return data.pendingTxs;
@@ -519,3 +504,8 @@ function mapVtxo(vtxo) {
519
504
  arkTxid: vtxo.arkTxid,
520
505
  };
521
506
  }
507
+ function handleError(errorText, defaultMessage) {
508
+ const error = new Error(errorText);
509
+ const arkError = maybeArkError(error);
510
+ throw arkError ?? new Error(defaultMessage);
511
+ }
@@ -0,0 +1,54 @@
1
+ export class ArkError extends Error {
2
+ constructor(code, message, name, metadata) {
3
+ super(message);
4
+ this.code = code;
5
+ this.message = message;
6
+ this.name = name;
7
+ this.metadata = metadata;
8
+ }
9
+ }
10
+ /**
11
+ * Try to convert an error to an ArkError class, returning undefined if the error is not an ArkError
12
+ * @param error - The error to parse
13
+ * @returns The parsed ArkError, or undefined if the error is not an ArkError
14
+ */
15
+ export function maybeArkError(error) {
16
+ try {
17
+ if (!(error instanceof Error))
18
+ return undefined;
19
+ const decoded = JSON.parse(error.message);
20
+ if (!("details" in decoded))
21
+ return undefined;
22
+ if (!Array.isArray(decoded.details))
23
+ return undefined;
24
+ // search for a valid details object with the correct type
25
+ for (const details of decoded.details) {
26
+ if (!("@type" in details))
27
+ continue;
28
+ const type = details["@type"];
29
+ if (type !== "type.googleapis.com/ark.v1.ErrorDetails")
30
+ continue;
31
+ if (!("code" in details))
32
+ continue;
33
+ const code = details.code;
34
+ if (!("message" in details))
35
+ continue;
36
+ const message = details.message;
37
+ if (!("name" in details))
38
+ continue;
39
+ const name = details.name;
40
+ let metadata;
41
+ if ("metadata" in details && isMetadata(details.metadata)) {
42
+ metadata = details.metadata;
43
+ }
44
+ return new ArkError(code, message, name, metadata);
45
+ }
46
+ return undefined;
47
+ }
48
+ catch (e) {
49
+ return undefined;
50
+ }
51
+ }
52
+ function isMetadata(value) {
53
+ return typeof value === "object" && value !== null && !Array.isArray(value);
54
+ }
@@ -58,18 +58,6 @@ export class WalletRepositoryImpl {
58
58
  return [];
59
59
  }
60
60
  }
61
- async saveVtxo(address, vtxo) {
62
- const vtxos = await this.getVtxos(address);
63
- const existing = vtxos.findIndex((v) => v.txid === vtxo.txid && v.vout === vtxo.vout);
64
- if (existing !== -1) {
65
- vtxos[existing] = vtxo;
66
- }
67
- else {
68
- vtxos.push(vtxo);
69
- }
70
- this.cache.vtxos.set(address, vtxos.slice());
71
- await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
72
- }
73
61
  async saveVtxos(address, vtxos) {
74
62
  const storedVtxos = await this.getVtxos(address);
75
63
  for (const vtxo of vtxos) {
@@ -116,20 +104,6 @@ export class WalletRepositoryImpl {
116
104
  return [];
117
105
  }
118
106
  }
119
- async saveTransaction(address, tx) {
120
- const transactions = await this.getTransactionHistory(address);
121
- const existing = transactions.findIndex((t) => t.key === tx.key);
122
- if (existing !== -1) {
123
- transactions[existing] = tx;
124
- }
125
- else {
126
- transactions.push(tx);
127
- }
128
- // Sort by createdAt descending
129
- transactions.sort((a, b) => b.createdAt - a.createdAt);
130
- this.cache.transactions.set(address, transactions);
131
- await this.storage.setItem(`tx:${address}`, JSON.stringify(transactions));
132
- }
133
107
  async saveTransactions(address, txs) {
134
108
  const storedTransactions = await this.getTransactionHistory(address);
135
109
  for (const tx of txs) {
@@ -1,3 +1,8 @@
1
+ import { hex } from "@scure/base";
2
+ function getRandomId() {
3
+ const randomValue = crypto.getRandomValues(new Uint8Array(16));
4
+ return hex.encode(randomValue);
5
+ }
1
6
  /**
2
7
  * Response is the namespace that contains the response types for the service worker.
3
8
  */
@@ -184,4 +189,31 @@ export var Response;
184
189
  };
185
190
  }
186
191
  Response.walletReloaded = walletReloaded;
192
+ function isVtxoUpdate(response) {
193
+ return response.type === "VTXO_UPDATE";
194
+ }
195
+ Response.isVtxoUpdate = isVtxoUpdate;
196
+ function vtxoUpdate(newVtxos, spentVtxos) {
197
+ return {
198
+ type: "VTXO_UPDATE",
199
+ id: getRandomId(), // spontaneous update, not tied to a request
200
+ success: true,
201
+ spentVtxos,
202
+ newVtxos,
203
+ };
204
+ }
205
+ Response.vtxoUpdate = vtxoUpdate;
206
+ function isUtxoUpdate(response) {
207
+ return response.type === "UTXO_UPDATE";
208
+ }
209
+ Response.isUtxoUpdate = isUtxoUpdate;
210
+ function utxoUpdate(coins) {
211
+ return {
212
+ type: "UTXO_UPDATE",
213
+ id: getRandomId(), // spontaneous update, not tied to a request
214
+ success: true,
215
+ coins,
216
+ };
217
+ }
218
+ Response.utxoUpdate = utxoUpdate;
187
219
  })(Response || (Response = {}));
@@ -253,6 +253,9 @@ export class ServiceWorkerWallet {
253
253
  return new Promise((resolve, reject) => {
254
254
  const messageHandler = (event) => {
255
255
  const response = event.data;
256
+ if (response.id !== message.id) {
257
+ return;
258
+ }
256
259
  if (!response.success) {
257
260
  navigator.serviceWorker.removeEventListener("message", messageHandler);
258
261
  reject(new Error(response.message));
@@ -127,11 +127,11 @@ export class Worker {
127
127
  ...spentVtxos,
128
128
  ]);
129
129
  // notify all clients about the vtxo update
130
- this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
130
+ this.sendMessageToAllClients(Response.vtxoUpdate(newVtxos, spentVtxos));
131
131
  }
132
132
  if (funds.type === "utxo") {
133
133
  // notify all clients about the utxo update
134
- this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
134
+ this.sendMessageToAllClients(Response.utxoUpdate(funds.coins));
135
135
  }
136
136
  });
137
137
  }
@@ -515,15 +515,12 @@ export class Worker {
515
515
  event.source?.postMessage(Response.error(message.id, "Unknown message type"));
516
516
  }
517
517
  }
518
- async sendMessageToAllClients(type, message) {
518
+ async sendMessageToAllClients(message) {
519
519
  self.clients
520
520
  .matchAll({ includeUncontrolled: true, type: "window" })
521
521
  .then((clients) => {
522
522
  clients.forEach((client) => {
523
- client.postMessage({
524
- type,
525
- message,
526
- });
523
+ client.postMessage(message);
527
524
  });
528
525
  });
529
526
  }
@@ -32,5 +32,6 @@ import { AnchorBumper, P2A } from "./utils/anchor";
32
32
  import { Unroll } from "./wallet/unroll";
33
33
  import { WalletRepositoryImpl } from "./repositories/walletRepository";
34
34
  import { ContractRepositoryImpl } from "./repositories/contractRepository";
35
- export { Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, };
35
+ import { ArkError, maybeArkError } from "./providers/errors";
36
+ export { Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, ArkError, maybeArkError, };
36
37
  export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo, SignedIntent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
@@ -0,0 +1,13 @@
1
+ export declare class ArkError extends Error {
2
+ readonly code: number;
3
+ readonly message: string;
4
+ readonly name: string;
5
+ readonly metadata?: Record<string, string> | undefined;
6
+ constructor(code: number, message: string, name: string, metadata?: Record<string, string> | undefined);
7
+ }
8
+ /**
9
+ * Try to convert an error to an ArkError class, returning undefined if the error is not an ArkError
10
+ * @param error - The error to parse
11
+ * @returns The parsed ArkError, or undefined if the error is not an ArkError
12
+ */
13
+ export declare function maybeArkError(error: any): ArkError | undefined;
@@ -6,12 +6,10 @@ export interface WalletState {
6
6
  }
7
7
  export interface WalletRepository {
8
8
  getVtxos(address: string): Promise<ExtendedVirtualCoin[]>;
9
- saveVtxo(address: string, vtxo: ExtendedVirtualCoin): Promise<void>;
10
9
  saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
11
10
  removeVtxo(address: string, vtxoId: string): Promise<void>;
12
11
  clearVtxos(address: string): Promise<void>;
13
12
  getTransactionHistory(address: string): Promise<ArkTransaction[]>;
14
- saveTransaction(address: string, tx: ArkTransaction): Promise<void>;
15
13
  saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
16
14
  clearTransactions(address: string): Promise<void>;
17
15
  getWalletState(): Promise<WalletState | null>;
@@ -22,12 +20,10 @@ export declare class WalletRepositoryImpl implements WalletRepository {
22
20
  private cache;
23
21
  constructor(storage: StorageAdapter);
24
22
  getVtxos(address: string): Promise<ExtendedVirtualCoin[]>;
25
- saveVtxo(address: string, vtxo: ExtendedVirtualCoin): Promise<void>;
26
23
  saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
27
24
  removeVtxo(address: string, vtxoId: string): Promise<void>;
28
25
  clearVtxos(address: string): Promise<void>;
29
26
  getTransactionHistory(address: string): Promise<ArkTransaction[]>;
30
- saveTransaction(address: string, tx: ArkTransaction): Promise<void>;
31
27
  saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
32
28
  clearTransactions(address: string): Promise<void>;
33
29
  getWalletState(): Promise<WalletState | null>;
@@ -1,10 +1,11 @@
1
- import { WalletBalance, VirtualCoin, ArkTransaction, IWallet } from "..";
1
+ import { WalletBalance, VirtualCoin, ArkTransaction, IWallet, Coin } from "..";
2
+ import { ExtendedVirtualCoin } from "../..";
2
3
  import { SettlementEvent } from "../../providers/ark";
3
4
  /**
4
5
  * Response is the namespace that contains the response types for the service worker.
5
6
  */
6
7
  export declare namespace Response {
7
- type Type = "WALLET_INITIALIZED" | "WALLET_RELOADED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "BOARDING_ADDRESS" | "BALANCE" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE";
8
+ type Type = "WALLET_INITIALIZED" | "WALLET_RELOADED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "BOARDING_ADDRESS" | "BALANCE" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE" | "VTXO_UPDATE" | "UTXO_UPDATE";
8
9
  interface Base {
9
10
  type: Type;
10
11
  success: boolean;
@@ -106,4 +107,17 @@ export declare namespace Response {
106
107
  }
107
108
  function isWalletReloaded(response: Base): response is WalletReloaded;
108
109
  function walletReloaded(id: string, success: boolean): WalletReloaded;
110
+ interface VtxoUpdate extends Base {
111
+ type: "VTXO_UPDATE";
112
+ spentVtxos: ExtendedVirtualCoin[];
113
+ newVtxos: ExtendedVirtualCoin[];
114
+ }
115
+ function isVtxoUpdate(response: Base): response is VtxoUpdate;
116
+ function vtxoUpdate(newVtxos: ExtendedVirtualCoin[], spentVtxos: ExtendedVirtualCoin[]): VtxoUpdate;
117
+ interface UtxoUpdate extends Base {
118
+ type: "UTXO_UPDATE";
119
+ coins: Coin[];
120
+ }
121
+ function isUtxoUpdate(response: Base): response is UtxoUpdate;
122
+ function utxoUpdate(coins: Coin[]): UtxoUpdate;
109
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/sdk",
3
- "version": "0.3.1-alpha.2",
3
+ "version": "0.3.1-alpha.3",
4
4
  "description": "Bitcoin wallet SDK with Taproot and Ark integration",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",