@anywalletkit/extension-kit 0.1.1 → 0.1.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/README.md +23 -3
- package/dist/index.cjs +188 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -22
- package/dist/index.d.ts +60 -22
- package/dist/index.js +188 -64
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
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
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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). */
|
|
@@ -149,21 +176,24 @@ var Vault = class {
|
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -293,6 +345,8 @@ var Vault = class {
|
|
|
293
345
|
const envelope = await this._storage.get();
|
|
294
346
|
if (!envelope) throw new VaultError("No vault in this storage area.", "missing");
|
|
295
347
|
if (!isEnvelopeV1(envelope) && !isEnvelopeV2(envelope)) throw new VaultError("Unsupported vault envelope.", "unsupported-envelope");
|
|
348
|
+
const problem = envelopeProblem(envelope);
|
|
349
|
+
if (problem) throw new VaultError(`The vault envelope is outside the limits this build opens (${problem}).`, "unsupported-envelope");
|
|
296
350
|
return envelope;
|
|
297
351
|
}
|
|
298
352
|
};
|
|
@@ -311,6 +365,22 @@ function isEnvelopeV2(x) {
|
|
|
311
365
|
function isBox(x) {
|
|
312
366
|
return typeof x === "object" && x !== null && typeof x.iv === "string" && typeof x.data === "string";
|
|
313
367
|
}
|
|
368
|
+
function isBoundedScrypt(c) {
|
|
369
|
+
return c.N <= VAULT_LIMITS.scrypt.N && c.r <= VAULT_LIMITS.scrypt.r && c.p <= VAULT_LIMITS.scrypt.p;
|
|
370
|
+
}
|
|
371
|
+
function envelopeProblem(e) {
|
|
372
|
+
if (e.v === 1 && e.kdf.iterations > VAULT_LIMITS.iterations) return `PBKDF2 iterations ${e.kdf.iterations} > ${VAULT_LIMITS.iterations}`;
|
|
373
|
+
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}`;
|
|
374
|
+
const secrets = Object.values(e.secrets);
|
|
375
|
+
if (secrets.length > VAULT_LIMITS.secrets) return `${secrets.length} secrets > ${VAULT_LIMITS.secrets}`;
|
|
376
|
+
const passkeys = e.v === 2 ? e.passkeys : [];
|
|
377
|
+
if (passkeys.length > VAULT_LIMITS.passkeys) return `${passkeys.length} passkeys > ${VAULT_LIMITS.passkeys}`;
|
|
378
|
+
for (const box of [e.dek, ...secrets, ...passkeys.map((p) => p.dek)]) {
|
|
379
|
+
if (box.iv.length > VAULT_LIMITS.boxChars || box.data.length > VAULT_LIMITS.boxChars) return `a sealed box above ${VAULT_LIMITS.boxChars} characters`;
|
|
380
|
+
}
|
|
381
|
+
if (JSON.stringify(e).length > VAULT_LIMITS.envelopeChars) return `envelope above ${VAULT_LIMITS.envelopeChars} characters`;
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
314
384
|
function isStrongScrypt(c) {
|
|
315
385
|
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
386
|
}
|
|
@@ -355,6 +425,13 @@ var Session = class {
|
|
|
355
425
|
_alarm;
|
|
356
426
|
_now;
|
|
357
427
|
_onLock = /* @__PURE__ */ new Set();
|
|
428
|
+
/**
|
|
429
|
+
* Every operation on the stored record runs here, one after another: a `touch` that read the
|
|
430
|
+
* record before a `lock` removed it would otherwise write it back and undo the lock.
|
|
431
|
+
*/
|
|
432
|
+
_queue = Promise.resolve();
|
|
433
|
+
/** Bumped by every lock; a `touch` writes only when no lock happened since it read the record. */
|
|
434
|
+
_epoch = 0;
|
|
358
435
|
constructor(options) {
|
|
359
436
|
this._storage = options.storage;
|
|
360
437
|
this._alarms = options.alarms;
|
|
@@ -370,20 +447,19 @@ var Session = class {
|
|
|
370
447
|
/** Store the DEK and arm the auto-lock. The caller's copy is zeroized. */
|
|
371
448
|
async unlock(dek) {
|
|
372
449
|
if (dek.length !== 32) throw new VaultError("Vault key must be 32 bytes.", "invalid-input");
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
450
|
+
return this._serial(async () => {
|
|
451
|
+
const expiresAt = this._expiry();
|
|
452
|
+
await this._storage.set(this._key, { key: toBase64(dek), expiresAt });
|
|
453
|
+
zeroize2(dek);
|
|
454
|
+
await this._arm(expiresAt);
|
|
455
|
+
});
|
|
377
456
|
}
|
|
378
457
|
/** Forget the DEK and cancel the alarm. Safe to call when already locked. */
|
|
379
458
|
async lock() {
|
|
380
|
-
|
|
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();
|
|
459
|
+
return this._serial(() => this._lockNow());
|
|
384
460
|
}
|
|
385
461
|
async isUnlocked() {
|
|
386
|
-
return await this._read() !== null;
|
|
462
|
+
return this._serial(async () => await this._read() !== null);
|
|
387
463
|
}
|
|
388
464
|
/**
|
|
389
465
|
* The DEK for one operation (zeroize it after). `locked` when there is none or the
|
|
@@ -391,17 +467,22 @@ var Session = class {
|
|
|
391
467
|
* a missed alarm cannot keep the key alive.
|
|
392
468
|
*/
|
|
393
469
|
async getKey() {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
470
|
+
return this._serial(async () => {
|
|
471
|
+
const record = await this._read();
|
|
472
|
+
if (!record) throw new VaultError("Wallet is locked.", "locked");
|
|
473
|
+
return fromBase64(record.key);
|
|
474
|
+
});
|
|
397
475
|
}
|
|
398
476
|
/** User activity: push the auto-lock out again. No-op when locked. */
|
|
399
477
|
async touch() {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
478
|
+
return this._serial(async () => {
|
|
479
|
+
const epoch = this._epoch;
|
|
480
|
+
const record = await this._read();
|
|
481
|
+
if (!record || epoch !== this._epoch) return;
|
|
482
|
+
const expiresAt = this._expiry();
|
|
483
|
+
await this._storage.set(this._key, { ...record, expiresAt });
|
|
484
|
+
await this._arm(expiresAt);
|
|
485
|
+
});
|
|
405
486
|
}
|
|
406
487
|
get autoLockMs() {
|
|
407
488
|
return this._autoLockMs;
|
|
@@ -416,6 +497,19 @@ var Session = class {
|
|
|
416
497
|
this._onLock.add(callback);
|
|
417
498
|
return () => this._onLock.delete(callback);
|
|
418
499
|
}
|
|
500
|
+
_serial(fn) {
|
|
501
|
+
const run = this._queue.then(fn);
|
|
502
|
+
this._queue = run.catch(() => void 0);
|
|
503
|
+
return run;
|
|
504
|
+
}
|
|
505
|
+
/** The lock itself; callers are already in the queue. */
|
|
506
|
+
async _lockNow() {
|
|
507
|
+
this._epoch++;
|
|
508
|
+
const was = await this._storage.get(this._key);
|
|
509
|
+
await this._storage.remove(this._key);
|
|
510
|
+
await this._alarms.clear(this._alarm);
|
|
511
|
+
if (was !== void 0 && was !== null) for (const cb of this._onLock) cb();
|
|
512
|
+
}
|
|
419
513
|
_expiry() {
|
|
420
514
|
return this._autoLockMs > 0 ? this._now() + this._autoLockMs : Number.POSITIVE_INFINITY;
|
|
421
515
|
}
|
|
@@ -427,7 +521,7 @@ var Session = class {
|
|
|
427
521
|
const raw = await this._storage.get(this._key);
|
|
428
522
|
if (!raw || typeof raw.key !== "string" || typeof raw.expiresAt !== "number") return null;
|
|
429
523
|
if (raw.expiresAt <= this._now()) {
|
|
430
|
-
await this.
|
|
524
|
+
await this._lockNow();
|
|
431
525
|
return null;
|
|
432
526
|
}
|
|
433
527
|
return raw;
|
|
@@ -1074,6 +1168,7 @@ function memoryApprovalWindow() {
|
|
|
1074
1168
|
}
|
|
1075
1169
|
|
|
1076
1170
|
// src/router.ts
|
|
1171
|
+
import { UserRejectedError } from "@anywalletkit/core";
|
|
1077
1172
|
var RouterError = class _RouterError extends Error {
|
|
1078
1173
|
code;
|
|
1079
1174
|
data;
|
|
@@ -1087,11 +1182,35 @@ var RouterError = class _RouterError extends Error {
|
|
|
1087
1182
|
return new _RouterError(e.code, e.message, e.data);
|
|
1088
1183
|
}
|
|
1089
1184
|
};
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1185
|
+
var PROVIDER_CODES = /* @__PURE__ */ new Set([4001, 4100, 4200, 4900, 4901, 4902]);
|
|
1186
|
+
var isNodeCode = (code) => typeof code === "number" && Number.isInteger(code) && (code === 3 || code >= -32768 && code <= -32e3);
|
|
1187
|
+
var URL_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>()]+/gi;
|
|
1188
|
+
var pageText = (text) => text.replace(URL_RE, "[url]").split("\n")[0].slice(0, 300);
|
|
1189
|
+
var isHexData = (v) => typeof v === "string" && /^0x[0-9a-fA-F]*$/.test(v) && v.length <= 2 + 2 * 8192;
|
|
1190
|
+
function nodeError(e) {
|
|
1191
|
+
for (let cur = e, depth = 0; cur && typeof cur === "object" && depth < 8; cur = cur.cause, depth++) {
|
|
1192
|
+
const o = cur;
|
|
1193
|
+
if (o.name === "RpcRequestError" && isNodeCode(o.code)) {
|
|
1194
|
+
const text = typeof o.details === "string" && o.details ? o.details : typeof o.shortMessage === "string" ? o.shortMessage : "RPC error";
|
|
1195
|
+
return { code: o.code, message: pageText(text), ...isHexData(o.data) ? { data: o.data } : {} };
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (!(e instanceof Error) && typeof e === "object" && e !== null) {
|
|
1199
|
+
const o = e;
|
|
1200
|
+
if (isNodeCode(o.code) && typeof o.message === "string") return { code: o.code, message: pageText(o.message), ...isHexData(o.data) ? { data: o.data } : {} };
|
|
1201
|
+
}
|
|
1202
|
+
return void 0;
|
|
1203
|
+
}
|
|
1204
|
+
function toRelayError(e, options = {}) {
|
|
1205
|
+
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 };
|
|
1206
|
+
if (e instanceof ReviewDeclined) return { code: e.code, message: pageText(e.message) };
|
|
1207
|
+
if (e instanceof UserRejectedError) return { code: RELAY_ERRORS.userRejected, message: pageText(e.message) };
|
|
1208
|
+
const node = nodeError(e);
|
|
1209
|
+
if (node) return node;
|
|
1210
|
+
const code = e?.code;
|
|
1211
|
+
if (e instanceof Error && typeof code === "number" && PROVIDER_CODES.has(code)) return { code, message: pageText(e.message) };
|
|
1212
|
+
options.log?.(e);
|
|
1213
|
+
return { code: RELAY_ERRORS.internal, message: "Internal error" };
|
|
1095
1214
|
}
|
|
1096
1215
|
var READ_METHODS = /* @__PURE__ */ new Set([
|
|
1097
1216
|
"eth_blockNumber",
|
|
@@ -1344,6 +1463,10 @@ function describeSiwe(text, ctx) {
|
|
|
1344
1463
|
}
|
|
1345
1464
|
|
|
1346
1465
|
// src/signing.ts
|
|
1466
|
+
var safeNonce = (n) => {
|
|
1467
|
+
if (n < 0n || n > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`The RPC returned an impossible nonce (${n}).`);
|
|
1468
|
+
return Number(n.toString());
|
|
1469
|
+
};
|
|
1347
1470
|
var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
1348
1471
|
var HEX = /^0x([0-9a-fA-F]{2})*$/;
|
|
1349
1472
|
var lc = (s) => s.toLowerCase();
|
|
@@ -1439,13 +1562,13 @@ async function prepareTransaction(deps, network, from, tx) {
|
|
|
1439
1562
|
const base = block?.baseFeePerGas != null ? big(block.baseFeePerGas) : null;
|
|
1440
1563
|
if (base === null) {
|
|
1441
1564
|
const gasPrice = big(await deps.rpc(network, "eth_gasPrice", []));
|
|
1442
|
-
return { chainId: network.chainId, nonce:
|
|
1565
|
+
return { chainId: network.chainId, nonce: safeNonce(big(nonceHex)), gasLimit: hex(gasLimit), gasPrice: hex(gasPrice), maxFee: (gasLimit * gasPrice).toString() };
|
|
1443
1566
|
}
|
|
1444
1567
|
const floor = deps.tipFloor?.(network.chainId) ?? MIN_TIP_WEI;
|
|
1445
1568
|
const nodeTip = await deps.rpc(network, "eth_maxPriorityFeePerGas", []).then(big).catch(() => 0n);
|
|
1446
1569
|
const tip = nodeTip > floor ? nodeTip : floor;
|
|
1447
1570
|
const maxFeePerGas = base * 2n + tip;
|
|
1448
|
-
return { chainId: network.chainId, nonce:
|
|
1571
|
+
return { chainId: network.chainId, nonce: safeNonce(big(nonceHex)), gasLimit: hex(gasLimit), maxFeePerGas: hex(maxFeePerGas), maxPriorityFeePerGas: hex(tip), maxFee: (gasLimit * maxFeePerGas).toString() };
|
|
1449
1572
|
}
|
|
1450
1573
|
function createSignHandler(deps) {
|
|
1451
1574
|
return async ({ origin, network, method, params, accounts }) => {
|
|
@@ -2201,6 +2324,7 @@ export {
|
|
|
2201
2324
|
TRON_PROVIDERS,
|
|
2202
2325
|
TonConnectError,
|
|
2203
2326
|
VAULT_DEFAULT_ITERATIONS,
|
|
2327
|
+
VAULT_LIMITS,
|
|
2204
2328
|
VAULT_MIN_ITERATIONS,
|
|
2205
2329
|
VAULT_MIN_PASSWORD_LENGTH,
|
|
2206
2330
|
VAULT_MIN_SCRYPT,
|