@anywalletkit/extension-kit 0.1.2 → 0.1.4

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/index.js CHANGED
@@ -40,6 +40,22 @@ var VAULT_DEFAULT_ITERATIONS = 6e5;
40
40
  var VAULT_SCRYPT = { N: 1 << 17, r: 8, p: 1 };
41
41
  var VAULT_MIN_SCRYPT = { N: 1 << 14, r: 8, p: 1 };
42
42
  var VAULT_MIN_PASSWORD_LENGTH = 8;
43
+ var VAULT_LIMITS = Object.freeze({
44
+ /** scrypt cost: 2^20 × 16 = 2 GiB is the most any envelope may ask for. */
45
+ scrypt: Object.freeze({ N: 1 << 20, r: 16, p: 4 }),
46
+ /** PBKDF2-SHA256 iterations (v1 envelopes). */
47
+ iterations: 5e6,
48
+ /** Sealed secrets in one vault. */
49
+ secrets: 1e3,
50
+ /** Registered passkeys. */
51
+ passkeys: 32,
52
+ /** Plaintext bytes of one secret (`setSecret`). */
53
+ secretBytes: 16 * 1024,
54
+ /** base64 characters of one sealed box (iv or data). */
55
+ boxChars: 32 * 1024,
56
+ /** JSON characters of the whole envelope. */
57
+ envelopeChars: 4 * 1024 * 1024
58
+ });
43
59
  var SALT_BYTES = 32;
44
60
  var IV_BYTES = 12;
45
61
  var DEK_BYTES = 32;
@@ -51,6 +67,8 @@ var aadSecret = (id) => `forgewallet-vault/secret/${id}`;
51
67
  var HKDF_INFO = "forgewallet-vault/2/passkey";
52
68
  var Vault = class {
53
69
  _storage;
70
+ /** Every read-modify-write of the envelope runs here, one after another (a parallel write would drop the other's change). */
71
+ _queue = Promise.resolve();
54
72
  _kdf;
55
73
  _iterations;
56
74
  _scrypt;
@@ -63,10 +81,11 @@ var Vault = class {
63
81
  this._scrypt = options.scrypt ?? VAULT_SCRYPT;
64
82
  this._onProgress = options.onProgress;
65
83
  this._crypto = options.crypto ?? globalThis.crypto;
66
- if (!Number.isInteger(this._iterations) || this._iterations < VAULT_MIN_ITERATIONS) {
67
- throw new VaultError(`PBKDF2 iterations must be an integer \u2265 ${VAULT_MIN_ITERATIONS}`, "invalid-input");
84
+ if (!Number.isInteger(this._iterations) || this._iterations < VAULT_MIN_ITERATIONS || this._iterations > VAULT_LIMITS.iterations) {
85
+ throw new VaultError(`PBKDF2 iterations must be an integer from ${VAULT_MIN_ITERATIONS} to ${VAULT_LIMITS.iterations}`, "invalid-input");
68
86
  }
69
87
  if (!isStrongScrypt(this._scrypt)) throw new VaultError(`scrypt cost must be at least N=${VAULT_MIN_SCRYPT.N} r=${VAULT_MIN_SCRYPT.r} p=${VAULT_MIN_SCRYPT.p} (N a power of two)`, "invalid-input");
88
+ if (!isBoundedScrypt(this._scrypt)) throw new VaultError(`scrypt cost must be at most N=${VAULT_LIMITS.scrypt.N} r=${VAULT_LIMITS.scrypt.r} p=${VAULT_LIMITS.scrypt.p}`, "invalid-input");
70
89
  }
71
90
  async exists() {
72
91
  return await this._storage.get() !== null;
@@ -85,10 +104,12 @@ var Vault = class {
85
104
  */
86
105
  async create(password) {
87
106
  requirePassword(password, VAULT_MIN_PASSWORD_LENGTH);
88
- if (await this.exists()) throw new VaultError("A vault already exists; unlock it or reset explicitly.", "exists");
89
- const dek = this._random(DEK_BYTES);
90
- await this._storage.set(await this._sealEnvelope(password, dek, {}, [], Date.now()));
91
- return dek;
107
+ return this._exclusive(async () => {
108
+ if (await this.exists()) throw new VaultError("A vault already exists; unlock it or reset explicitly.", "exists");
109
+ const dek = this._random(DEK_BYTES);
110
+ await this._write(await this._sealEnvelope(password, dek, {}, [], Date.now()));
111
+ return dek;
112
+ });
92
113
  }
93
114
  /**
94
115
  * Derive the KEK from `password`, unseal and return the DEK. `wrong-password` on
@@ -97,6 +118,9 @@ var Vault = class {
97
118
  */
98
119
  async unlock(password) {
99
120
  requirePassword(password);
121
+ return this._exclusive(() => this._unlock(password));
122
+ }
123
+ async _unlock(password) {
100
124
  const envelope = await this._load();
101
125
  if (isEnvelopeV1(envelope)) {
102
126
  if (envelope.kdf.iterations < VAULT_MIN_ITERATIONS) throw new VaultError(`The vault's KDF (${envelope.kdf.iterations} iterations) is below the floor this build accepts.`, "weak-kdf");
@@ -104,7 +128,7 @@ var Vault = class {
104
128
  const dek = await this._open(kek2, envelope.dek, aadDekV1(envelope.kdf), "wrong-password");
105
129
  if (this._kdf === "scrypt") {
106
130
  const next = await this._sealEnvelope(password, dek, envelope.secrets, [], envelope.createdAt);
107
- await this._storage.set(isEnvelopeV2(next) ? { ...next, upgradedAt: Date.now() } : next);
131
+ await this._write(isEnvelopeV2(next) ? { ...next, upgradedAt: Date.now() } : next);
108
132
  }
109
133
  return dek;
110
134
  }
@@ -115,15 +139,18 @@ var Vault = class {
115
139
  /** Re-wrap the DEK under a new password (fresh salt, current KDF). Secrets and passkeys are untouched. */
116
140
  async changePassword(oldPassword, newPassword) {
117
141
  requirePassword(newPassword, VAULT_MIN_PASSWORD_LENGTH);
118
- const dek = await this.unlock(oldPassword);
119
- try {
120
- const envelope = await this._load();
121
- const passkeys = isEnvelopeV2(envelope) ? envelope.passkeys : [];
122
- const next = await this._sealEnvelope(newPassword, dek, envelope.secrets, passkeys, envelope.createdAt);
123
- await this._storage.set(isEnvelopeV2(envelope) && envelope.upgradedAt !== void 0 && isEnvelopeV2(next) ? { ...next, upgradedAt: envelope.upgradedAt } : next);
124
- } finally {
125
- zeroize(dek);
126
- }
142
+ requirePassword(oldPassword);
143
+ return this._exclusive(async () => {
144
+ const dek = await this._unlock(oldPassword);
145
+ try {
146
+ const envelope = await this._load();
147
+ const passkeys = isEnvelopeV2(envelope) ? envelope.passkeys : [];
148
+ const next = await this._sealEnvelope(newPassword, dek, envelope.secrets, passkeys, envelope.createdAt);
149
+ await this._write(isEnvelopeV2(envelope) && envelope.upgradedAt !== void 0 && isEnvelopeV2(next) ? { ...next, upgradedAt: envelope.upgradedAt } : next);
150
+ } finally {
151
+ zeroize(dek);
152
+ }
153
+ });
127
154
  }
128
155
  // ---- passkeys (v2) ----
129
156
  /** Public view of the registered passkeys (ids, labels; whether each can verify assertions). */
@@ -143,27 +170,30 @@ var Vault = class {
143
170
  * Register a passkey as a second unlock path: the DEK (from the unlocked session)
144
171
  * is sealed under HKDF-SHA256(`prf`, salt = credential id). The password stays
145
172
  * the recovery path; `prf` is zeroized here. A v1 envelope must be unlocked (and
146
- * thereby upgraded) first.
173
+ * thereby upgraded) first, and the vault must hold a secret (it proves the DEK).
147
174
  */
148
175
  async addPasskey(dek, passkey) {
149
176
  try {
150
177
  requireId(passkey.id);
151
178
  if (passkey.prf.length !== PRF_BYTES) throw new VaultError(`A PRF output is ${PRF_BYTES} bytes.`, "invalid-input");
152
- const envelope = await this._load();
153
- if (!isEnvelopeV2(envelope)) throw new VaultError("Unlock with the password once so the vault is upgraded before adding a passkey.", "unsupported-envelope");
154
- if (envelope.passkeys.some((p) => p.id === passkey.id)) throw new VaultError("This passkey is already registered.", "exists");
155
- await this._requireDek(dek, envelope);
156
- const key = await this._derivePasskeyKek(passkey.prf, passkey.id);
157
- const entry = { id: passkey.id, label: passkey.label.slice(0, 64), createdAt: Date.now(), dek: await this._seal(key, dek, aadPasskey(passkey.id)) };
158
- if (passkey.publicKey && passkey.alg !== void 0) {
159
- entry.publicKey = passkey.publicKey;
160
- entry.alg = passkey.alg;
161
- }
162
- await this._storage.set({ ...envelope, passkeys: [...envelope.passkeys, entry] });
179
+ await this._exclusive(() => this._addPasskey(dek, passkey));
163
180
  } finally {
164
181
  zeroize(passkey.prf);
165
182
  }
166
183
  }
184
+ async _addPasskey(dek, passkey) {
185
+ const envelope = await this._load();
186
+ if (!isEnvelopeV2(envelope)) throw new VaultError("Unlock with the password once so the vault is upgraded before adding a passkey.", "unsupported-envelope");
187
+ if (envelope.passkeys.some((p) => p.id === passkey.id)) throw new VaultError("This passkey is already registered.", "exists");
188
+ await this._requireDek(dek, envelope);
189
+ const key = await this._derivePasskeyKek(passkey.prf, passkey.id);
190
+ const entry = { id: passkey.id, label: passkey.label.slice(0, 64), createdAt: Date.now(), dek: await this._seal(key, dek, aadPasskey(passkey.id)) };
191
+ if (passkey.publicKey && passkey.alg !== void 0) {
192
+ entry.publicKey = passkey.publicKey;
193
+ entry.alg = passkey.alg;
194
+ }
195
+ await this._write({ ...envelope, passkeys: [...envelope.passkeys, entry] });
196
+ }
167
197
  /** Unseal the DEK with a passkey's PRF output. `wrong-password` when the box does not open, `missing` when the id is unknown. */
168
198
  async unlockWithPasskey(id, prf) {
169
199
  try {
@@ -181,9 +211,11 @@ var Vault = class {
181
211
  }
182
212
  async removePasskey(id) {
183
213
  requireId(id);
184
- const envelope = await this._load();
185
- if (!isEnvelopeV2(envelope)) return;
186
- await this._storage.set({ ...envelope, passkeys: envelope.passkeys.filter((p) => p.id !== id) });
214
+ return this._exclusive(async () => {
215
+ const envelope = await this._load();
216
+ if (!isEnvelopeV2(envelope)) return;
217
+ await this._write({ ...envelope, passkeys: envelope.passkeys.filter((p) => p.id !== id) });
218
+ });
187
219
  }
188
220
  // ---- secrets ----
189
221
  async listSecretIds() {
@@ -193,10 +225,16 @@ var Vault = class {
193
225
  /** Seal `secret` under the DEK as `sourceId` (overwrites an entry with the same id). */
194
226
  async setSecret(dek, sourceId, secret) {
195
227
  requireId(sourceId);
196
- const envelope = await this._load();
228
+ if (secret.length > VAULT_LIMITS.secretBytes) throw new VaultError(`A secret is at most ${VAULT_LIMITS.secretBytes} bytes.`, "invalid-input");
197
229
  const key = await this._importDek(dek);
198
- const box = await this._seal(key, secret, aadSecret(sourceId));
199
- await this._storage.set({ ...envelope, secrets: { ...envelope.secrets, [sourceId]: box } });
230
+ return this._exclusive(async () => {
231
+ const envelope = await this._load();
232
+ if (!(sourceId in envelope.secrets) && Object.keys(envelope.secrets).length >= VAULT_LIMITS.secrets) {
233
+ throw new VaultError(`A vault holds at most ${VAULT_LIMITS.secrets} secrets.`, "invalid-input");
234
+ }
235
+ const box = await this._seal(key, secret, aadSecret(sourceId));
236
+ await this._write({ ...envelope, secrets: { ...envelope.secrets, [sourceId]: box } });
237
+ });
200
238
  }
201
239
  /** Unseal one secret. `null` when the id is unknown; the caller zeroizes the result after use. */
202
240
  async getSecret(dek, sourceId) {
@@ -209,15 +247,29 @@ var Vault = class {
209
247
  }
210
248
  async deleteSecret(sourceId) {
211
249
  requireId(sourceId);
212
- const envelope = await this._load();
213
- if (!(sourceId in envelope.secrets)) return;
214
- const secrets = { ...envelope.secrets };
215
- delete secrets[sourceId];
216
- await this._storage.set({ ...envelope, secrets });
250
+ return this._exclusive(async () => {
251
+ const envelope = await this._load();
252
+ if (!(sourceId in envelope.secrets)) return;
253
+ const secrets = { ...envelope.secrets };
254
+ delete secrets[sourceId];
255
+ await this._write({ ...envelope, secrets });
256
+ });
217
257
  }
218
258
  /** Remove the whole vault. The explicit destructive flow only (back up → wipe → create). */
219
259
  async destroy() {
220
- await this._storage.clear();
260
+ return this._exclusive(() => this._storage.clear());
261
+ }
262
+ // ---- serialized writes ----
263
+ _exclusive(fn) {
264
+ const run = this._queue.then(fn);
265
+ this._queue = run.catch(() => void 0);
266
+ return run;
267
+ }
268
+ /** Store an envelope this build will open again (bounds checked before it is written). */
269
+ async _write(envelope) {
270
+ const problem = envelopeProblem(envelope);
271
+ if (problem) throw new VaultError(`The vault would exceed its limits (${problem}); nothing was written.`, "invalid-input");
272
+ await this._storage.set(envelope);
221
273
  }
222
274
  // ---- crypto ----
223
275
  _random(n) {
@@ -235,11 +287,14 @@ var Vault = class {
235
287
  const kek = await this._deriveScrypt(password, salt, kdf);
236
288
  return { v: 2, kdf, dek: await this._seal(kek, dek, aadDekV2(kdf)), secrets, passkeys, createdAt };
237
289
  }
238
- /** The caller's DEK must open this envelope's secrets — it is the key the password protects, not any 32 bytes. */
290
+ /**
291
+ * The caller's DEK must open this envelope's secrets — it is the key the password protects, not
292
+ * any 32 bytes. An empty vault has nothing to prove it with, so it is refused rather than trusted.
293
+ */
239
294
  async _requireDek(dek, envelope) {
240
295
  const ids = Object.keys(envelope.secrets);
241
296
  const key = await this._importDek(dek);
242
- if (ids.length === 0) return;
297
+ if (ids.length === 0) throw new VaultError("Add a secret to the vault before a passkey: an empty vault cannot prove the key is its own.", "invalid-input");
243
298
  const probe = await this._open(key, envelope.secrets[ids[0]], aadSecret(ids[0]), "locked");
244
299
  zeroize(probe);
245
300
  }
@@ -293,6 +348,8 @@ var Vault = class {
293
348
  const envelope = await this._storage.get();
294
349
  if (!envelope) throw new VaultError("No vault in this storage area.", "missing");
295
350
  if (!isEnvelopeV1(envelope) && !isEnvelopeV2(envelope)) throw new VaultError("Unsupported vault envelope.", "unsupported-envelope");
351
+ const problem = envelopeProblem(envelope);
352
+ if (problem) throw new VaultError(`The vault envelope is outside the limits this build opens (${problem}).`, "unsupported-envelope");
296
353
  return envelope;
297
354
  }
298
355
  };
@@ -311,6 +368,22 @@ function isEnvelopeV2(x) {
311
368
  function isBox(x) {
312
369
  return typeof x === "object" && x !== null && typeof x.iv === "string" && typeof x.data === "string";
313
370
  }
371
+ function isBoundedScrypt(c) {
372
+ return c.N <= VAULT_LIMITS.scrypt.N && c.r <= VAULT_LIMITS.scrypt.r && c.p <= VAULT_LIMITS.scrypt.p;
373
+ }
374
+ function envelopeProblem(e) {
375
+ if (e.v === 1 && e.kdf.iterations > VAULT_LIMITS.iterations) return `PBKDF2 iterations ${e.kdf.iterations} > ${VAULT_LIMITS.iterations}`;
376
+ if (e.v === 2 && !isBoundedScrypt(e.kdf)) return `scrypt N=${e.kdf.N} r=${e.kdf.r} p=${e.kdf.p} above N=${VAULT_LIMITS.scrypt.N} r=${VAULT_LIMITS.scrypt.r} p=${VAULT_LIMITS.scrypt.p}`;
377
+ const secrets = Object.values(e.secrets);
378
+ if (secrets.length > VAULT_LIMITS.secrets) return `${secrets.length} secrets > ${VAULT_LIMITS.secrets}`;
379
+ const passkeys = e.v === 2 ? e.passkeys : [];
380
+ if (passkeys.length > VAULT_LIMITS.passkeys) return `${passkeys.length} passkeys > ${VAULT_LIMITS.passkeys}`;
381
+ for (const box of [e.dek, ...secrets, ...passkeys.map((p) => p.dek)]) {
382
+ if (box.iv.length > VAULT_LIMITS.boxChars || box.data.length > VAULT_LIMITS.boxChars) return `a sealed box above ${VAULT_LIMITS.boxChars} characters`;
383
+ }
384
+ if (JSON.stringify(e).length > VAULT_LIMITS.envelopeChars) return `envelope above ${VAULT_LIMITS.envelopeChars} characters`;
385
+ return null;
386
+ }
314
387
  function isStrongScrypt(c) {
315
388
  return Number.isInteger(c.N) && c.N >= VAULT_MIN_SCRYPT.N && (c.N & c.N - 1) === 0 && Number.isInteger(c.r) && c.r >= VAULT_MIN_SCRYPT.r && Number.isInteger(c.p) && c.p >= VAULT_MIN_SCRYPT.p;
316
389
  }
@@ -355,6 +428,13 @@ var Session = class {
355
428
  _alarm;
356
429
  _now;
357
430
  _onLock = /* @__PURE__ */ new Set();
431
+ /**
432
+ * Every operation on the stored record runs here, one after another: a `touch` that read the
433
+ * record before a `lock` removed it would otherwise write it back and undo the lock.
434
+ */
435
+ _queue = Promise.resolve();
436
+ /** Bumped by every lock; a `touch` writes only when no lock happened since it read the record. */
437
+ _epoch = 0;
358
438
  constructor(options) {
359
439
  this._storage = options.storage;
360
440
  this._alarms = options.alarms;
@@ -370,20 +450,19 @@ var Session = class {
370
450
  /** Store the DEK and arm the auto-lock. The caller's copy is zeroized. */
371
451
  async unlock(dek) {
372
452
  if (dek.length !== 32) throw new VaultError("Vault key must be 32 bytes.", "invalid-input");
373
- const expiresAt = this._expiry();
374
- await this._storage.set(this._key, { key: toBase64(dek), expiresAt });
375
- zeroize2(dek);
376
- await this._arm(expiresAt);
453
+ return this._serial(async () => {
454
+ const expiresAt = this._expiry();
455
+ await this._storage.set(this._key, { key: toBase64(dek), expiresAt });
456
+ zeroize2(dek);
457
+ await this._arm(expiresAt);
458
+ });
377
459
  }
378
460
  /** Forget the DEK and cancel the alarm. Safe to call when already locked. */
379
461
  async lock() {
380
- const was = await this._storage.get(this._key);
381
- await this._storage.remove(this._key);
382
- await this._alarms.clear(this._alarm);
383
- if (was !== void 0 && was !== null) for (const cb of this._onLock) cb();
462
+ return this._serial(() => this._lockNow());
384
463
  }
385
464
  async isUnlocked() {
386
- return await this._read() !== null;
465
+ return this._serial(async () => await this._read() !== null);
387
466
  }
388
467
  /**
389
468
  * The DEK for one operation (zeroize it after). `locked` when there is none or the
@@ -391,17 +470,22 @@ var Session = class {
391
470
  * a missed alarm cannot keep the key alive.
392
471
  */
393
472
  async getKey() {
394
- const record = await this._read();
395
- if (!record) throw new VaultError("Wallet is locked.", "locked");
396
- return fromBase64(record.key);
473
+ return this._serial(async () => {
474
+ const record = await this._read();
475
+ if (!record) throw new VaultError("Wallet is locked.", "locked");
476
+ return fromBase64(record.key);
477
+ });
397
478
  }
398
479
  /** User activity: push the auto-lock out again. No-op when locked. */
399
480
  async touch() {
400
- const record = await this._read();
401
- if (!record) return;
402
- const expiresAt = this._expiry();
403
- await this._storage.set(this._key, { ...record, expiresAt });
404
- await this._arm(expiresAt);
481
+ return this._serial(async () => {
482
+ const epoch = this._epoch;
483
+ const record = await this._read();
484
+ if (!record || epoch !== this._epoch) return;
485
+ const expiresAt = this._expiry();
486
+ await this._storage.set(this._key, { ...record, expiresAt });
487
+ await this._arm(expiresAt);
488
+ });
405
489
  }
406
490
  get autoLockMs() {
407
491
  return this._autoLockMs;
@@ -416,6 +500,19 @@ var Session = class {
416
500
  this._onLock.add(callback);
417
501
  return () => this._onLock.delete(callback);
418
502
  }
503
+ _serial(fn) {
504
+ const run = this._queue.then(fn);
505
+ this._queue = run.catch(() => void 0);
506
+ return run;
507
+ }
508
+ /** The lock itself; callers are already in the queue. */
509
+ async _lockNow() {
510
+ this._epoch++;
511
+ const was = await this._storage.get(this._key);
512
+ await this._storage.remove(this._key);
513
+ await this._alarms.clear(this._alarm);
514
+ if (was !== void 0 && was !== null) for (const cb of this._onLock) cb();
515
+ }
419
516
  _expiry() {
420
517
  return this._autoLockMs > 0 ? this._now() + this._autoLockMs : Number.POSITIVE_INFINITY;
421
518
  }
@@ -427,7 +524,7 @@ var Session = class {
427
524
  const raw = await this._storage.get(this._key);
428
525
  if (!raw || typeof raw.key !== "string" || typeof raw.expiresAt !== "number") return null;
429
526
  if (raw.expiresAt <= this._now()) {
430
- await this.lock();
527
+ await this._lockNow();
431
528
  return null;
432
529
  }
433
530
  return raw;
@@ -1074,6 +1171,7 @@ function memoryApprovalWindow() {
1074
1171
  }
1075
1172
 
1076
1173
  // src/router.ts
1174
+ import { UserRejectedError } from "@anywalletkit/core";
1077
1175
  var RouterError = class _RouterError extends Error {
1078
1176
  code;
1079
1177
  data;
@@ -1087,11 +1185,35 @@ var RouterError = class _RouterError extends Error {
1087
1185
  return new _RouterError(e.code, e.message, e.data);
1088
1186
  }
1089
1187
  };
1090
- function toRelayError(e) {
1091
- if (e instanceof RouterError) return e.data === void 0 ? { code: e.code, message: e.message } : { code: e.code, message: e.message, data: e.data };
1092
- if (e instanceof ReviewDeclined) return { code: e.code, message: e.message };
1093
- const code = typeof e?.code === "number" ? e.code : RELAY_ERRORS.internal;
1094
- return { code, message: e instanceof Error ? e.message : String(e) };
1188
+ var PROVIDER_CODES = /* @__PURE__ */ new Set([4001, 4100, 4200, 4900, 4901, 4902]);
1189
+ var isNodeCode = (code) => typeof code === "number" && Number.isInteger(code) && (code === 3 || code >= -32768 && code <= -32e3);
1190
+ var URL_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>()]+/gi;
1191
+ var pageText = (text) => text.replace(URL_RE, "[url]").split("\n")[0].slice(0, 300);
1192
+ var isHexData = (v) => typeof v === "string" && /^0x[0-9a-fA-F]*$/.test(v) && v.length <= 2 + 2 * 8192;
1193
+ function nodeError(e) {
1194
+ for (let cur = e, depth = 0; cur && typeof cur === "object" && depth < 8; cur = cur.cause, depth++) {
1195
+ const o = cur;
1196
+ if (o.name === "RpcRequestError" && isNodeCode(o.code)) {
1197
+ const text = typeof o.details === "string" && o.details ? o.details : typeof o.shortMessage === "string" ? o.shortMessage : "RPC error";
1198
+ return { code: o.code, message: pageText(text), ...isHexData(o.data) ? { data: o.data } : {} };
1199
+ }
1200
+ }
1201
+ if (!(e instanceof Error) && typeof e === "object" && e !== null) {
1202
+ const o = e;
1203
+ if (isNodeCode(o.code) && typeof o.message === "string") return { code: o.code, message: pageText(o.message), ...isHexData(o.data) ? { data: o.data } : {} };
1204
+ }
1205
+ return void 0;
1206
+ }
1207
+ function toRelayError(e, options = {}) {
1208
+ if (e instanceof RouterError) return e.data === void 0 ? { code: e.code, message: pageText(e.message) } : { code: e.code, message: pageText(e.message), data: e.data };
1209
+ if (e instanceof ReviewDeclined) return { code: e.code, message: pageText(e.message) };
1210
+ if (e instanceof UserRejectedError) return { code: RELAY_ERRORS.userRejected, message: pageText(e.message) };
1211
+ const node = nodeError(e);
1212
+ if (node) return node;
1213
+ const code = e?.code;
1214
+ if (e instanceof Error && typeof code === "number" && PROVIDER_CODES.has(code)) return { code, message: pageText(e.message) };
1215
+ options.log?.(e);
1216
+ return { code: RELAY_ERRORS.internal, message: "Internal error" };
1095
1217
  }
1096
1218
  var READ_METHODS = /* @__PURE__ */ new Set([
1097
1219
  "eth_blockNumber",
@@ -1344,6 +1466,10 @@ function describeSiwe(text, ctx) {
1344
1466
  }
1345
1467
 
1346
1468
  // src/signing.ts
1469
+ var safeNonce = (n) => {
1470
+ if (n < 0n || n > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`The RPC returned an impossible nonce (${n}).`);
1471
+ return Number(n.toString());
1472
+ };
1347
1473
  var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
1348
1474
  var HEX = /^0x([0-9a-fA-F]{2})*$/;
1349
1475
  var lc = (s) => s.toLowerCase();
@@ -1439,13 +1565,13 @@ async function prepareTransaction(deps, network, from, tx) {
1439
1565
  const base = block?.baseFeePerGas != null ? big(block.baseFeePerGas) : null;
1440
1566
  if (base === null) {
1441
1567
  const gasPrice = big(await deps.rpc(network, "eth_gasPrice", []));
1442
- return { chainId: network.chainId, nonce: Number(big(nonceHex)), gasLimit: hex(gasLimit), gasPrice: hex(gasPrice), maxFee: (gasLimit * gasPrice).toString() };
1568
+ return { chainId: network.chainId, nonce: safeNonce(big(nonceHex)), gasLimit: hex(gasLimit), gasPrice: hex(gasPrice), maxFee: (gasLimit * gasPrice).toString() };
1443
1569
  }
1444
1570
  const floor = deps.tipFloor?.(network.chainId) ?? MIN_TIP_WEI;
1445
1571
  const nodeTip = await deps.rpc(network, "eth_maxPriorityFeePerGas", []).then(big).catch(() => 0n);
1446
1572
  const tip = nodeTip > floor ? nodeTip : floor;
1447
1573
  const maxFeePerGas = base * 2n + tip;
1448
- return { chainId: network.chainId, nonce: Number(big(nonceHex)), gasLimit: hex(gasLimit), maxFeePerGas: hex(maxFeePerGas), maxPriorityFeePerGas: hex(tip), maxFee: (gasLimit * maxFeePerGas).toString() };
1574
+ return { chainId: network.chainId, nonce: safeNonce(big(nonceHex)), gasLimit: hex(gasLimit), maxFeePerGas: hex(maxFeePerGas), maxPriorityFeePerGas: hex(tip), maxFee: (gasLimit * maxFeePerGas).toString() };
1449
1575
  }
1450
1576
  function createSignHandler(deps) {
1451
1577
  return async ({ origin, network, method, params, accounts }) => {
@@ -2201,6 +2327,7 @@ export {
2201
2327
  TRON_PROVIDERS,
2202
2328
  TonConnectError,
2203
2329
  VAULT_DEFAULT_ITERATIONS,
2330
+ VAULT_LIMITS,
2204
2331
  VAULT_MIN_ITERATIONS,
2205
2332
  VAULT_MIN_PASSWORD_LENGTH,
2206
2333
  VAULT_MIN_SCRYPT,