@qrlwallet/connect 5.1.0 → 5.1.2

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 CHANGED
@@ -286,6 +286,9 @@ npm run dev
286
286
  # Type-check
287
287
  npm run typecheck
288
288
 
289
+ # Type-check tests
290
+ npm run typecheck:test
291
+
289
292
  # Unit tests (vitest)
290
293
  npm test
291
294
 
@@ -296,6 +299,8 @@ npm run lint
296
299
  node test-e2e.mjs
297
300
  ```
298
301
 
302
+ CI runs `node scripts/audit-gate.mjs` after the SDK install and `node scripts/audit-gate.mjs --dir ui` after the UI install; both read `audit-allowlist.json` at the repository root. The gate blocks high and critical production advisories unless a matching, dated exception in `audit-allowlist.json` explains why the advisory cannot reach users.
303
+
299
304
  ## Running the example dApp
300
305
 
301
306
  The `example/` directory contains a Vite test dApp with QR code generation, transaction sending, message signing, and read-only RPC calls.
package/dist/index.js CHANGED
@@ -121,7 +121,8 @@ var import_ml_kem = require("@noble/post-quantum/ml-kem.js");
121
121
  var import_sha3 = require("@noble/hashes/sha3.js");
122
122
  var mldsa = __toESM(require("@theqrl/mldsa87"));
123
123
  function webCrypto() {
124
- const c = globalThis.crypto;
124
+ const environment = globalThis;
125
+ const c = environment.crypto;
125
126
  if (!c) {
126
127
  throw new Error("crypto: WebCrypto is not available in this environment");
127
128
  }
@@ -641,7 +642,8 @@ var KeyExchange = class extends import_eventemitter3.default {
641
642
  this.lastAck = null;
642
643
  this.isOriginator = isOriginator;
643
644
  if (restored) {
644
- if (restored.protocolVersion !== PROTOCOL_VERSION) {
645
+ const protocolVersion = restored.protocolVersion;
646
+ if (protocolVersion !== PROTOCOL_VERSION) {
645
647
  throw new Error("KeyExchange: cannot restore a pre-v3 session");
646
648
  }
647
649
  this.session = snapshotSession(restored);
@@ -975,7 +977,8 @@ var KeyExchange = class extends import_eventemitter3.default {
975
977
  }
976
978
  /** Rehydrate a Session from its persisted form. */
977
979
  static async sessionFromPersisted(p) {
978
- if (p.protocolVersion !== PROTOCOL_VERSION) {
980
+ const protocolVersion = p.protocolVersion;
981
+ if (protocolVersion !== PROTOCOL_VERSION) {
979
982
  throw new Error("KeyExchange: cannot hydrate a pre-v3 session");
980
983
  }
981
984
  const kAeadRaw = fromBase64Exact(p.kAeadRaw, 32, "persisted AEAD key");
@@ -1553,7 +1556,12 @@ function startsWith(buf, prefix) {
1553
1556
  }
1554
1557
  return true;
1555
1558
  }
1556
- async function parseConnectionURI(uri) {
1559
+ function parseConnectionURI(uri) {
1560
+ return new Promise((resolve) => {
1561
+ resolve(parseURI(uri));
1562
+ });
1563
+ }
1564
+ function parseURI(uri) {
1557
1565
  if (typeof uri !== "string" || uri.length === 0) {
1558
1566
  throw new Error("qrUri: empty URI");
1559
1567
  }
@@ -1598,7 +1606,7 @@ async function parseConnectionURI(uri) {
1598
1606
  blob = base45Decode(q);
1599
1607
  } catch (err) {
1600
1608
  const msg = err instanceof Error ? err.message : String(err);
1601
- throw new Error(`qrUri: base45 decode failed: ${msg}`);
1609
+ throw new Error(`qrUri: base45 decode failed: ${msg}`, { cause: err });
1602
1610
  }
1603
1611
  try {
1604
1612
  if (blob.length !== BLOB_LEN) {
@@ -1671,7 +1679,8 @@ function getBrowserStorage() {
1671
1679
  function getBrowserLockManager() {
1672
1680
  try {
1673
1681
  if (typeof navigator === "undefined") return null;
1674
- return navigator.locks ?? null;
1682
+ const browserNavigator = navigator;
1683
+ return browserNavigator.locks ?? null;
1675
1684
  } catch {
1676
1685
  return null;
1677
1686
  }
@@ -2012,7 +2021,8 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
2012
2021
  });
2013
2022
  socketClient.on("participants_changed", (data) => {
2014
2023
  if (!isCurrentSocket()) return;
2015
- if (data.clientType !== "wallet") return;
2024
+ const clientType = data.clientType;
2025
+ if (clientType !== "wallet") return;
2016
2026
  if (data.event === "join" && data.clientType === "wallet") {
2017
2027
  this.walletPresent = true;
2018
2028
  log("ConnectionManager", "Wallet joined channel");
@@ -2179,7 +2189,8 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
2179
2189
  log("ConnectionManager", `Generated v3 connection URI for channel ${context.channelId}`);
2180
2190
  return uri;
2181
2191
  } catch (err) {
2182
- if (context ? this.isSessionWorkCurrent(context) : this.keyExchange !== null) {
2192
+ const keyExchange = this.keyExchange;
2193
+ if (context ? this.isSessionWorkCurrent(context) : keyExchange !== null) {
2183
2194
  this.invalidateSessionWork();
2184
2195
  this.keyExchange = null;
2185
2196
  this.socketClient.leaveChannel();
@@ -3001,7 +3012,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
3001
3012
  const persistedKex = await context.keyExchange.exportPersisted();
3002
3013
  if (!persistedKex) throw new Error("Cannot export established AEAD session");
3003
3014
  this.assertSessionWorkCurrent(context);
3004
- if (!this.sessionOwnership?.isOwned()) {
3015
+ if (!this.sessionOwnership.isOwned()) {
3005
3016
  throw new Error("Browser-tab ownership changed while persisting AEAD counters");
3006
3017
  }
3007
3018
  const session = {
@@ -3020,7 +3031,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
3020
3031
  this.persistenceBroken = true;
3021
3032
  this.removeStoredSession();
3022
3033
  error("ConnectionManager", "Failed to persist AEAD counters:", err);
3023
- throw new Error("Failed to persist AEAD counters");
3034
+ throw new Error("Failed to persist AEAD counters", { cause: err });
3024
3035
  }
3025
3036
  }
3026
3037
  readStoredSession() {
@@ -3197,17 +3208,20 @@ function consumeValue(budget) {
3197
3208
  budget.remainingValues--;
3198
3209
  if (budget.remainingValues < 0) throw new Error("typed data contains too many encoded values");
3199
3210
  }
3211
+ function inputPreview(value) {
3212
+ return value.length > 32 ? `${value.slice(0, 32)}... (length ${value.length})` : value;
3213
+ }
3200
3214
  function assertIdentifier(value, label) {
3201
3215
  if (!IDENTIFIER_RE.test(value) || value.length > TYPED_DATA_LIMITS.maxIdentifierLength || UNSAFE_OBJECT_KEYS.has(value)) {
3202
- throw new Error(`invalid ${label}: ${value}`);
3216
+ throw new Error(`invalid ${label}: ${inputPreview(value)}`);
3203
3217
  }
3204
3218
  }
3205
3219
  function parseFieldType(type, types, depth = 0) {
3206
3220
  if (depth > TYPED_DATA_LIMITS.maxArrayNesting) {
3207
- throw new Error(`type nesting too deep: ${type}`);
3221
+ throw new Error(`type nesting too deep: ${inputPreview(type)}`);
3208
3222
  }
3209
3223
  if (type.length > TYPED_DATA_LIMITS.maxFieldTypeLength) {
3210
- throw new Error(`field type too long: ${type}`);
3224
+ throw new Error(`field type too long: ${inputPreview(type)}`);
3211
3225
  }
3212
3226
  const m = ATOMIC_RE.exec(type);
3213
3227
  if (m) {
@@ -3218,14 +3232,14 @@ function parseFieldType(type, types, depth = 0) {
3218
3232
  if (intKind) {
3219
3233
  const width = Number(intWidthStr);
3220
3234
  if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
3221
- throw new Error(`invalid int width: ${type}`);
3235
+ throw new Error(`invalid int width: ${inputPreview(type)}`);
3222
3236
  }
3223
3237
  return { kind: intKind === "uint" ? "uintN" : "intN", width };
3224
3238
  }
3225
3239
  if (bytesWidthStr) {
3226
3240
  const width = Number(bytesWidthStr);
3227
3241
  if (!Number.isInteger(width) || width < 1 || width > 32) {
3228
- throw new Error(`invalid bytesN width: ${type}`);
3242
+ throw new Error(`invalid bytesN width: ${inputPreview(type)}`);
3229
3243
  }
3230
3244
  return { kind: "bytesN", width };
3231
3245
  }
@@ -3234,7 +3248,7 @@ function parseFieldType(type, types, depth = 0) {
3234
3248
  if (sizeStr) {
3235
3249
  const size = Number(sizeStr);
3236
3250
  if (!Number.isSafeInteger(size) || size <= 0 || size > TYPED_DATA_LIMITS.maxArrayLength) {
3237
- throw new Error(`invalid array size: ${type}`);
3251
+ throw new Error(`invalid array size: ${inputPreview(type)}`);
3238
3252
  }
3239
3253
  return { kind: "array", inner: innerType, size };
3240
3254
  }
@@ -3244,7 +3258,7 @@ function parseFieldType(type, types, depth = 0) {
3244
3258
  if (Object.prototype.hasOwnProperty.call(types, type)) {
3245
3259
  return { kind: "ref", name: type };
3246
3260
  }
3247
- throw new Error(`unknown type: ${type}`);
3261
+ throw new Error(`unknown type: ${inputPreview(type)}`);
3248
3262
  }
3249
3263
  function isReservedAtomicTypeName(name) {
3250
3264
  if (/^(?:address|bool|string|bytes)$/.test(name)) return true;
@@ -3261,24 +3275,35 @@ function isReservedAtomicTypeName(name) {
3261
3275
  return false;
3262
3276
  }
3263
3277
  function baseTypeName(type) {
3264
- return type.replace(/(\[\d*\])+$/, "");
3278
+ let end = type.length;
3279
+ while (end > 0 && type[end - 1] === "]") {
3280
+ let start = end - 2;
3281
+ while (start >= 0) {
3282
+ const code = type.charCodeAt(start);
3283
+ if (code < 48 || code > 57) break;
3284
+ start--;
3285
+ }
3286
+ if (start < 0 || type[start] !== "[") break;
3287
+ end = start;
3288
+ }
3289
+ return type.slice(0, end);
3265
3290
  }
3266
3291
  function collectDependencies(primary, types) {
3267
3292
  if (!Object.prototype.hasOwnProperty.call(types, primary)) {
3268
- throw new Error(`unknown primary type: ${primary}`);
3293
+ throw new Error(`unknown primary type: ${inputPreview(primary)}`);
3269
3294
  }
3270
3295
  const visited = /* @__PURE__ */ new Set();
3271
3296
  const visit = (name, path) => {
3272
3297
  if (path.length > TYPED_DATA_LIMITS.maxTypeGraphDepth) {
3273
- throw new Error(`type graph nesting too deep: ${[...path, name].join(" -> ")}`);
3298
+ throw new Error(`type graph nesting too deep: ${inputPreview([...path, name].join(" -> "))}`);
3274
3299
  }
3275
3300
  if (path.includes(name)) {
3276
- throw new Error(`cyclic type reference: ${[...path, name].join(" -> ")}`);
3301
+ throw new Error(`cyclic type reference: ${inputPreview([...path, name].join(" -> "))}`);
3277
3302
  }
3278
3303
  if (visited.has(name)) return;
3279
3304
  visited.add(name);
3280
3305
  const def = types[name];
3281
- if (!def) throw new Error(`unknown type: ${name}`);
3306
+ if (!def) throw new Error(`unknown type: ${inputPreview(name)}`);
3282
3307
  for (const f of def) {
3283
3308
  const base = baseTypeName(f.type);
3284
3309
  if (Object.prototype.hasOwnProperty.call(types, base)) {
@@ -3299,11 +3324,11 @@ function validateTypeMap(types) {
3299
3324
  for (const [name, def] of entries) {
3300
3325
  assertIdentifier(name, "struct name");
3301
3326
  if (name !== "QRLDomain" && isReservedAtomicTypeName(name)) {
3302
- throw new Error(`struct name is reserved by an atomic type: ${name}`);
3327
+ throw new Error(`struct name is reserved by an atomic type: ${inputPreview(name)}`);
3303
3328
  }
3304
3329
  const defUnknown = def;
3305
3330
  if (!Array.isArray(defUnknown) || def.length === 0 || def.length > TYPED_DATA_LIMITS.maxFieldsPerType) {
3306
- throw new Error(`empty or invalid struct: ${name}`);
3331
+ throw new Error(`empty or invalid struct: ${inputPreview(name)}`);
3307
3332
  }
3308
3333
  totalFields += def.length;
3309
3334
  if (totalFields > TYPED_DATA_LIMITS.maxTotalFields) {
@@ -3312,14 +3337,17 @@ function validateTypeMap(types) {
3312
3337
  const seen = /* @__PURE__ */ new Set();
3313
3338
  for (const f of def) {
3314
3339
  if (!f || typeof f.name !== "string" || !f.name) {
3315
- throw new Error(`bad field in ${name}`);
3340
+ throw new Error(`bad field in ${inputPreview(name)}`);
3316
3341
  }
3317
- assertIdentifier(f.name, `field name in ${name}`);
3342
+ assertIdentifier(f.name, `field name in ${inputPreview(name)}`);
3318
3343
  if (typeof f.type !== "string" || !f.type) {
3319
- throw new Error(`bad field type in ${name}.${f.name}`);
3344
+ throw new Error(`bad field type in ${inputPreview(name)}.${inputPreview(f.name)}`);
3345
+ }
3346
+ if (f.type.length > TYPED_DATA_LIMITS.maxFieldTypeLength) {
3347
+ throw new Error(`field type too long: ${inputPreview(f.type)}`);
3320
3348
  }
3321
3349
  if (seen.has(f.name)) {
3322
- throw new Error(`duplicate field "${f.name}" in ${name}`);
3350
+ throw new Error(`duplicate field "${inputPreview(f.name)}" in ${inputPreview(name)}`);
3323
3351
  }
3324
3352
  seen.add(f.name);
3325
3353
  }
@@ -3331,7 +3359,7 @@ function encodeType(primary, types) {
3331
3359
  const others = [...deps].filter((n) => n !== primary).sort();
3332
3360
  return [primary, ...others].map((name) => {
3333
3361
  const fields = types[name];
3334
- if (!fields) throw new Error(`unknown type: ${name}`);
3362
+ if (!fields) throw new Error(`unknown type: ${inputPreview(name)}`);
3335
3363
  const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
3336
3364
  return `${name}(${inner})`;
3337
3365
  }).join("");
@@ -3341,7 +3369,7 @@ function typeHash(primary, types) {
3341
3369
  }
3342
3370
  function parseQAddress(addr, slot) {
3343
3371
  if (!isCurrentQrlAddress(addr)) {
3344
- throw new Error(`invalid Q-address: ${String(addr)}`);
3372
+ throw new Error(`invalid Q-address: ${inputPreview(String(addr))}`);
3345
3373
  }
3346
3374
  const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
3347
3375
  if (bytes.length > slot) {
@@ -3365,12 +3393,13 @@ function bigIntToSlot(value, width, signed, slot) {
3365
3393
  if (signed) {
3366
3394
  const limit = 1n << BigInt(width - 1);
3367
3395
  if (value >= limit || value < -limit) {
3368
- throw new Error(`int${width} out of range: ${value}`);
3396
+ throw new Error(`int${width} out of range: ${inputPreview(String(value))}`);
3369
3397
  }
3370
3398
  } else {
3371
- if (value < 0n) throw new Error(`uint${width} negative: ${value}`);
3399
+ if (value < 0n) throw new Error(`uint${width} negative: ${inputPreview(String(value))}`);
3372
3400
  const limit = 1n << BigInt(width);
3373
- if (value >= limit) throw new Error(`uint${width} out of range: ${value}`);
3401
+ if (value >= limit)
3402
+ throw new Error(`uint${width} out of range: ${inputPreview(String(value))}`);
3374
3403
  }
3375
3404
  const slotBits = BigInt(slot * 8);
3376
3405
  const slotMask = (1n << slotBits) - 1n;
@@ -3393,7 +3422,7 @@ function parseIntValue(v, typeLabel) {
3393
3422
  return isNegative ? -abs : abs;
3394
3423
  }
3395
3424
  if (!/^-?(0|[1-9]\d*)$/.test(v)) {
3396
- throw new Error(`invalid ${typeLabel} string: ${v}`);
3425
+ throw new Error(`invalid ${typeLabel} string: ${inputPreview(v)}`);
3397
3426
  }
3398
3427
  return BigInt(v);
3399
3428
  }
@@ -3407,14 +3436,14 @@ function parseIntValue(v, typeLabel) {
3407
3436
  }
3408
3437
  function encodeFieldWithBudget(type, value, types, budget, depth) {
3409
3438
  if (depth > TYPED_DATA_LIMITS.maxTypeGraphDepth + TYPED_DATA_LIMITS.maxArrayNesting) {
3410
- throw new Error(`value nesting too deep: ${type}`);
3439
+ throw new Error(`value nesting too deep: ${inputPreview(type)}`);
3411
3440
  }
3412
3441
  consumeValue(budget);
3413
3442
  const parsed = parseFieldType(type, types);
3414
3443
  switch (parsed.kind) {
3415
3444
  case "ref":
3416
3445
  if (!isMessageObject(value)) {
3417
- throw new Error(`struct field expects object: ${type}`);
3446
+ throw new Error(`struct field expects object: ${inputPreview(type)}`);
3418
3447
  }
3419
3448
  return hashStructWithBudget(parsed.name, value, types, budget, depth + 1);
3420
3449
  case "address":
@@ -3480,10 +3509,12 @@ function encodeFieldWithBudget(type, value, types, budget, depth) {
3480
3509
  throw new Error(`array field expects array: ${typeof value}`);
3481
3510
  }
3482
3511
  if (value.length > TYPED_DATA_LIMITS.maxArrayLength) {
3483
- throw new Error(`array ${type} exceeds length limit`);
3512
+ throw new Error(`array ${inputPreview(type)} exceeds length limit`);
3484
3513
  }
3485
3514
  if (parsed.size !== void 0 && value.length !== parsed.size) {
3486
- throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
3515
+ throw new Error(
3516
+ `fixed array ${inputPreview(type)} requires length ${parsed.size}, got ${value.length}`
3517
+ );
3487
3518
  }
3488
3519
  const chunks = value.map(
3489
3520
  (v) => encodeFieldWithBudget(parsed.inner, v, types, budget, depth + 1)
@@ -3500,7 +3531,7 @@ function encodeField(type, value, types, schemeVersion) {
3500
3531
  function hashStructWithBudget(primary, data, types, budget, depth) {
3501
3532
  consumeValue(budget);
3502
3533
  const fields = types[primary];
3503
- if (!fields) throw new Error(`unknown struct: ${primary}`);
3534
+ if (!fields) throw new Error(`unknown struct: ${inputPreview(primary)}`);
3504
3535
  const expected = new Set(fields.map((f) => f.name));
3505
3536
  let ownFieldCount = 0;
3506
3537
  for (const k in data) {
@@ -3509,12 +3540,14 @@ function hashStructWithBudget(primary, data, types, budget, depth) {
3509
3540
  if (ownFieldCount > TYPED_DATA_LIMITS.maxEncodedValues) {
3510
3541
  throw new Error("typed data object contains too many fields");
3511
3542
  }
3512
- if (!expected.has(k)) throw new Error(`unknown field in ${primary}: ${k}`);
3543
+ if (!expected.has(k)) {
3544
+ throw new Error(`unknown field in ${inputPreview(primary)}: ${inputPreview(k)}`);
3545
+ }
3513
3546
  }
3514
3547
  const parts = [typeHash(primary, types)];
3515
3548
  for (const f of fields) {
3516
3549
  if (!Object.prototype.hasOwnProperty.call(data, f.name)) {
3517
- throw new Error(`missing field ${primary}.${f.name}`);
3550
+ throw new Error(`missing field ${inputPreview(primary)}.${inputPreview(f.name)}`);
3518
3551
  }
3519
3552
  parts.push(encodeFieldWithBudget(f.type, data[f.name], types, budget, depth + 1));
3520
3553
  }
@@ -3540,11 +3573,13 @@ function validateDomainTypes(types) {
3540
3573
  const expected = RESERVED_DOMAIN_FIELDS[f.name];
3541
3574
  if (!expected) {
3542
3575
  throw new Error(
3543
- `QRLDomain field "${f.name}" not in reserved set (name, version, chainId, verifyingContract, salt)`
3576
+ `QRLDomain field "${inputPreview(f.name)}" not in reserved set (name, version, chainId, verifyingContract, salt)`
3544
3577
  );
3545
3578
  }
3546
3579
  if (f.type !== expected) {
3547
- throw new Error(`QRLDomain field "${f.name}" must be type "${expected}", got "${f.type}"`);
3580
+ throw new Error(
3581
+ `QRLDomain field "${inputPreview(f.name)}" must be type "${expected}", got "${inputPreview(f.type)}"`
3582
+ );
3548
3583
  }
3549
3584
  if (f.name === "name") hasName = true;
3550
3585
  }
@@ -3556,7 +3591,7 @@ function validatePayloadReachability(primary, types) {
3556
3591
  for (const t of collectDependencies(root, types)) reachable.add(t);
3557
3592
  }
3558
3593
  for (const k of Object.keys(types)) {
3559
- if (!reachable.has(k)) throw new Error(`unused referenced type: ${k}`);
3594
+ if (!reachable.has(k)) throw new Error(`unused referenced type: ${inputPreview(k)}`);
3560
3595
  }
3561
3596
  }
3562
3597
  function parseAndValidatePayload(payload) {
@@ -3678,7 +3713,8 @@ function canonicalChainId2(value) {
3678
3713
  return `0x${BigInt(value).toString(16)}`;
3679
3714
  }
3680
3715
  function requestedSwitchChainId(params) {
3681
- if (params?.length !== 1) {
3716
+ const parameterCount = params?.length;
3717
+ if (parameterCount !== 1) {
3682
3718
  throw new Error("wallet_switchQrlChain requires one chain configuration object");
3683
3719
  }
3684
3720
  const request = params?.[0];
@@ -3744,7 +3780,7 @@ function snapshotRequestParams(params) {
3744
3780
  return snapshot;
3745
3781
  } catch (error2) {
3746
3782
  const detail = error2 instanceof Error ? error2.message : String(error2);
3747
- throw new Error(`Request params must be JSON-serializable: ${detail}`);
3783
+ throw new Error(`Request params must be JSON-serializable: ${detail}`, { cause: error2 });
3748
3784
  }
3749
3785
  }
3750
3786
  function validateRestrictedRequest(method, params, authorizedAccounts, currentChainId) {
package/dist/index.mjs CHANGED
@@ -12,7 +12,8 @@ import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
12
12
  import { shake256 } from "@noble/hashes/sha3.js";
13
13
  import * as mldsa from "@theqrl/mldsa87";
14
14
  function webCrypto() {
15
- const c = globalThis.crypto;
15
+ const environment = globalThis;
16
+ const c = environment.crypto;
16
17
  if (!c) {
17
18
  throw new Error("crypto: WebCrypto is not available in this environment");
18
19
  }
@@ -532,7 +533,8 @@ var KeyExchange = class extends EventEmitter {
532
533
  this.lastAck = null;
533
534
  this.isOriginator = isOriginator;
534
535
  if (restored) {
535
- if (restored.protocolVersion !== PROTOCOL_VERSION) {
536
+ const protocolVersion = restored.protocolVersion;
537
+ if (protocolVersion !== PROTOCOL_VERSION) {
536
538
  throw new Error("KeyExchange: cannot restore a pre-v3 session");
537
539
  }
538
540
  this.session = snapshotSession(restored);
@@ -866,7 +868,8 @@ var KeyExchange = class extends EventEmitter {
866
868
  }
867
869
  /** Rehydrate a Session from its persisted form. */
868
870
  static async sessionFromPersisted(p) {
869
- if (p.protocolVersion !== PROTOCOL_VERSION) {
871
+ const protocolVersion = p.protocolVersion;
872
+ if (protocolVersion !== PROTOCOL_VERSION) {
870
873
  throw new Error("KeyExchange: cannot hydrate a pre-v3 session");
871
874
  }
872
875
  const kAeadRaw = fromBase64Exact(p.kAeadRaw, 32, "persisted AEAD key");
@@ -1444,7 +1447,12 @@ function startsWith(buf, prefix) {
1444
1447
  }
1445
1448
  return true;
1446
1449
  }
1447
- async function parseConnectionURI(uri) {
1450
+ function parseConnectionURI(uri) {
1451
+ return new Promise((resolve) => {
1452
+ resolve(parseURI(uri));
1453
+ });
1454
+ }
1455
+ function parseURI(uri) {
1448
1456
  if (typeof uri !== "string" || uri.length === 0) {
1449
1457
  throw new Error("qrUri: empty URI");
1450
1458
  }
@@ -1489,7 +1497,7 @@ async function parseConnectionURI(uri) {
1489
1497
  blob = base45Decode(q);
1490
1498
  } catch (err) {
1491
1499
  const msg = err instanceof Error ? err.message : String(err);
1492
- throw new Error(`qrUri: base45 decode failed: ${msg}`);
1500
+ throw new Error(`qrUri: base45 decode failed: ${msg}`, { cause: err });
1493
1501
  }
1494
1502
  try {
1495
1503
  if (blob.length !== BLOB_LEN) {
@@ -1562,7 +1570,8 @@ function getBrowserStorage() {
1562
1570
  function getBrowserLockManager() {
1563
1571
  try {
1564
1572
  if (typeof navigator === "undefined") return null;
1565
- return navigator.locks ?? null;
1573
+ const browserNavigator = navigator;
1574
+ return browserNavigator.locks ?? null;
1566
1575
  } catch {
1567
1576
  return null;
1568
1577
  }
@@ -1903,7 +1912,8 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1903
1912
  });
1904
1913
  socketClient.on("participants_changed", (data) => {
1905
1914
  if (!isCurrentSocket()) return;
1906
- if (data.clientType !== "wallet") return;
1915
+ const clientType = data.clientType;
1916
+ if (clientType !== "wallet") return;
1907
1917
  if (data.event === "join" && data.clientType === "wallet") {
1908
1918
  this.walletPresent = true;
1909
1919
  log("ConnectionManager", "Wallet joined channel");
@@ -2070,7 +2080,8 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
2070
2080
  log("ConnectionManager", `Generated v3 connection URI for channel ${context.channelId}`);
2071
2081
  return uri;
2072
2082
  } catch (err) {
2073
- if (context ? this.isSessionWorkCurrent(context) : this.keyExchange !== null) {
2083
+ const keyExchange = this.keyExchange;
2084
+ if (context ? this.isSessionWorkCurrent(context) : keyExchange !== null) {
2074
2085
  this.invalidateSessionWork();
2075
2086
  this.keyExchange = null;
2076
2087
  this.socketClient.leaveChannel();
@@ -2892,7 +2903,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
2892
2903
  const persistedKex = await context.keyExchange.exportPersisted();
2893
2904
  if (!persistedKex) throw new Error("Cannot export established AEAD session");
2894
2905
  this.assertSessionWorkCurrent(context);
2895
- if (!this.sessionOwnership?.isOwned()) {
2906
+ if (!this.sessionOwnership.isOwned()) {
2896
2907
  throw new Error("Browser-tab ownership changed while persisting AEAD counters");
2897
2908
  }
2898
2909
  const session = {
@@ -2911,7 +2922,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
2911
2922
  this.persistenceBroken = true;
2912
2923
  this.removeStoredSession();
2913
2924
  error("ConnectionManager", "Failed to persist AEAD counters:", err);
2914
- throw new Error("Failed to persist AEAD counters");
2925
+ throw new Error("Failed to persist AEAD counters", { cause: err });
2915
2926
  }
2916
2927
  }
2917
2928
  readStoredSession() {
@@ -3088,17 +3099,20 @@ function consumeValue(budget) {
3088
3099
  budget.remainingValues--;
3089
3100
  if (budget.remainingValues < 0) throw new Error("typed data contains too many encoded values");
3090
3101
  }
3102
+ function inputPreview(value) {
3103
+ return value.length > 32 ? `${value.slice(0, 32)}... (length ${value.length})` : value;
3104
+ }
3091
3105
  function assertIdentifier(value, label) {
3092
3106
  if (!IDENTIFIER_RE.test(value) || value.length > TYPED_DATA_LIMITS.maxIdentifierLength || UNSAFE_OBJECT_KEYS.has(value)) {
3093
- throw new Error(`invalid ${label}: ${value}`);
3107
+ throw new Error(`invalid ${label}: ${inputPreview(value)}`);
3094
3108
  }
3095
3109
  }
3096
3110
  function parseFieldType(type, types, depth = 0) {
3097
3111
  if (depth > TYPED_DATA_LIMITS.maxArrayNesting) {
3098
- throw new Error(`type nesting too deep: ${type}`);
3112
+ throw new Error(`type nesting too deep: ${inputPreview(type)}`);
3099
3113
  }
3100
3114
  if (type.length > TYPED_DATA_LIMITS.maxFieldTypeLength) {
3101
- throw new Error(`field type too long: ${type}`);
3115
+ throw new Error(`field type too long: ${inputPreview(type)}`);
3102
3116
  }
3103
3117
  const m = ATOMIC_RE.exec(type);
3104
3118
  if (m) {
@@ -3109,14 +3123,14 @@ function parseFieldType(type, types, depth = 0) {
3109
3123
  if (intKind) {
3110
3124
  const width = Number(intWidthStr);
3111
3125
  if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
3112
- throw new Error(`invalid int width: ${type}`);
3126
+ throw new Error(`invalid int width: ${inputPreview(type)}`);
3113
3127
  }
3114
3128
  return { kind: intKind === "uint" ? "uintN" : "intN", width };
3115
3129
  }
3116
3130
  if (bytesWidthStr) {
3117
3131
  const width = Number(bytesWidthStr);
3118
3132
  if (!Number.isInteger(width) || width < 1 || width > 32) {
3119
- throw new Error(`invalid bytesN width: ${type}`);
3133
+ throw new Error(`invalid bytesN width: ${inputPreview(type)}`);
3120
3134
  }
3121
3135
  return { kind: "bytesN", width };
3122
3136
  }
@@ -3125,7 +3139,7 @@ function parseFieldType(type, types, depth = 0) {
3125
3139
  if (sizeStr) {
3126
3140
  const size = Number(sizeStr);
3127
3141
  if (!Number.isSafeInteger(size) || size <= 0 || size > TYPED_DATA_LIMITS.maxArrayLength) {
3128
- throw new Error(`invalid array size: ${type}`);
3142
+ throw new Error(`invalid array size: ${inputPreview(type)}`);
3129
3143
  }
3130
3144
  return { kind: "array", inner: innerType, size };
3131
3145
  }
@@ -3135,7 +3149,7 @@ function parseFieldType(type, types, depth = 0) {
3135
3149
  if (Object.prototype.hasOwnProperty.call(types, type)) {
3136
3150
  return { kind: "ref", name: type };
3137
3151
  }
3138
- throw new Error(`unknown type: ${type}`);
3152
+ throw new Error(`unknown type: ${inputPreview(type)}`);
3139
3153
  }
3140
3154
  function isReservedAtomicTypeName(name) {
3141
3155
  if (/^(?:address|bool|string|bytes)$/.test(name)) return true;
@@ -3152,24 +3166,35 @@ function isReservedAtomicTypeName(name) {
3152
3166
  return false;
3153
3167
  }
3154
3168
  function baseTypeName(type) {
3155
- return type.replace(/(\[\d*\])+$/, "");
3169
+ let end = type.length;
3170
+ while (end > 0 && type[end - 1] === "]") {
3171
+ let start = end - 2;
3172
+ while (start >= 0) {
3173
+ const code = type.charCodeAt(start);
3174
+ if (code < 48 || code > 57) break;
3175
+ start--;
3176
+ }
3177
+ if (start < 0 || type[start] !== "[") break;
3178
+ end = start;
3179
+ }
3180
+ return type.slice(0, end);
3156
3181
  }
3157
3182
  function collectDependencies(primary, types) {
3158
3183
  if (!Object.prototype.hasOwnProperty.call(types, primary)) {
3159
- throw new Error(`unknown primary type: ${primary}`);
3184
+ throw new Error(`unknown primary type: ${inputPreview(primary)}`);
3160
3185
  }
3161
3186
  const visited = /* @__PURE__ */ new Set();
3162
3187
  const visit = (name, path) => {
3163
3188
  if (path.length > TYPED_DATA_LIMITS.maxTypeGraphDepth) {
3164
- throw new Error(`type graph nesting too deep: ${[...path, name].join(" -> ")}`);
3189
+ throw new Error(`type graph nesting too deep: ${inputPreview([...path, name].join(" -> "))}`);
3165
3190
  }
3166
3191
  if (path.includes(name)) {
3167
- throw new Error(`cyclic type reference: ${[...path, name].join(" -> ")}`);
3192
+ throw new Error(`cyclic type reference: ${inputPreview([...path, name].join(" -> "))}`);
3168
3193
  }
3169
3194
  if (visited.has(name)) return;
3170
3195
  visited.add(name);
3171
3196
  const def = types[name];
3172
- if (!def) throw new Error(`unknown type: ${name}`);
3197
+ if (!def) throw new Error(`unknown type: ${inputPreview(name)}`);
3173
3198
  for (const f of def) {
3174
3199
  const base = baseTypeName(f.type);
3175
3200
  if (Object.prototype.hasOwnProperty.call(types, base)) {
@@ -3190,11 +3215,11 @@ function validateTypeMap(types) {
3190
3215
  for (const [name, def] of entries) {
3191
3216
  assertIdentifier(name, "struct name");
3192
3217
  if (name !== "QRLDomain" && isReservedAtomicTypeName(name)) {
3193
- throw new Error(`struct name is reserved by an atomic type: ${name}`);
3218
+ throw new Error(`struct name is reserved by an atomic type: ${inputPreview(name)}`);
3194
3219
  }
3195
3220
  const defUnknown = def;
3196
3221
  if (!Array.isArray(defUnknown) || def.length === 0 || def.length > TYPED_DATA_LIMITS.maxFieldsPerType) {
3197
- throw new Error(`empty or invalid struct: ${name}`);
3222
+ throw new Error(`empty or invalid struct: ${inputPreview(name)}`);
3198
3223
  }
3199
3224
  totalFields += def.length;
3200
3225
  if (totalFields > TYPED_DATA_LIMITS.maxTotalFields) {
@@ -3203,14 +3228,17 @@ function validateTypeMap(types) {
3203
3228
  const seen = /* @__PURE__ */ new Set();
3204
3229
  for (const f of def) {
3205
3230
  if (!f || typeof f.name !== "string" || !f.name) {
3206
- throw new Error(`bad field in ${name}`);
3231
+ throw new Error(`bad field in ${inputPreview(name)}`);
3207
3232
  }
3208
- assertIdentifier(f.name, `field name in ${name}`);
3233
+ assertIdentifier(f.name, `field name in ${inputPreview(name)}`);
3209
3234
  if (typeof f.type !== "string" || !f.type) {
3210
- throw new Error(`bad field type in ${name}.${f.name}`);
3235
+ throw new Error(`bad field type in ${inputPreview(name)}.${inputPreview(f.name)}`);
3236
+ }
3237
+ if (f.type.length > TYPED_DATA_LIMITS.maxFieldTypeLength) {
3238
+ throw new Error(`field type too long: ${inputPreview(f.type)}`);
3211
3239
  }
3212
3240
  if (seen.has(f.name)) {
3213
- throw new Error(`duplicate field "${f.name}" in ${name}`);
3241
+ throw new Error(`duplicate field "${inputPreview(f.name)}" in ${inputPreview(name)}`);
3214
3242
  }
3215
3243
  seen.add(f.name);
3216
3244
  }
@@ -3222,7 +3250,7 @@ function encodeType(primary, types) {
3222
3250
  const others = [...deps].filter((n) => n !== primary).sort();
3223
3251
  return [primary, ...others].map((name) => {
3224
3252
  const fields = types[name];
3225
- if (!fields) throw new Error(`unknown type: ${name}`);
3253
+ if (!fields) throw new Error(`unknown type: ${inputPreview(name)}`);
3226
3254
  const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
3227
3255
  return `${name}(${inner})`;
3228
3256
  }).join("");
@@ -3232,7 +3260,7 @@ function typeHash(primary, types) {
3232
3260
  }
3233
3261
  function parseQAddress(addr, slot) {
3234
3262
  if (!isCurrentQrlAddress(addr)) {
3235
- throw new Error(`invalid Q-address: ${String(addr)}`);
3263
+ throw new Error(`invalid Q-address: ${inputPreview(String(addr))}`);
3236
3264
  }
3237
3265
  const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
3238
3266
  if (bytes.length > slot) {
@@ -3256,12 +3284,13 @@ function bigIntToSlot(value, width, signed, slot) {
3256
3284
  if (signed) {
3257
3285
  const limit = 1n << BigInt(width - 1);
3258
3286
  if (value >= limit || value < -limit) {
3259
- throw new Error(`int${width} out of range: ${value}`);
3287
+ throw new Error(`int${width} out of range: ${inputPreview(String(value))}`);
3260
3288
  }
3261
3289
  } else {
3262
- if (value < 0n) throw new Error(`uint${width} negative: ${value}`);
3290
+ if (value < 0n) throw new Error(`uint${width} negative: ${inputPreview(String(value))}`);
3263
3291
  const limit = 1n << BigInt(width);
3264
- if (value >= limit) throw new Error(`uint${width} out of range: ${value}`);
3292
+ if (value >= limit)
3293
+ throw new Error(`uint${width} out of range: ${inputPreview(String(value))}`);
3265
3294
  }
3266
3295
  const slotBits = BigInt(slot * 8);
3267
3296
  const slotMask = (1n << slotBits) - 1n;
@@ -3284,7 +3313,7 @@ function parseIntValue(v, typeLabel) {
3284
3313
  return isNegative ? -abs : abs;
3285
3314
  }
3286
3315
  if (!/^-?(0|[1-9]\d*)$/.test(v)) {
3287
- throw new Error(`invalid ${typeLabel} string: ${v}`);
3316
+ throw new Error(`invalid ${typeLabel} string: ${inputPreview(v)}`);
3288
3317
  }
3289
3318
  return BigInt(v);
3290
3319
  }
@@ -3298,14 +3327,14 @@ function parseIntValue(v, typeLabel) {
3298
3327
  }
3299
3328
  function encodeFieldWithBudget(type, value, types, budget, depth) {
3300
3329
  if (depth > TYPED_DATA_LIMITS.maxTypeGraphDepth + TYPED_DATA_LIMITS.maxArrayNesting) {
3301
- throw new Error(`value nesting too deep: ${type}`);
3330
+ throw new Error(`value nesting too deep: ${inputPreview(type)}`);
3302
3331
  }
3303
3332
  consumeValue(budget);
3304
3333
  const parsed = parseFieldType(type, types);
3305
3334
  switch (parsed.kind) {
3306
3335
  case "ref":
3307
3336
  if (!isMessageObject(value)) {
3308
- throw new Error(`struct field expects object: ${type}`);
3337
+ throw new Error(`struct field expects object: ${inputPreview(type)}`);
3309
3338
  }
3310
3339
  return hashStructWithBudget(parsed.name, value, types, budget, depth + 1);
3311
3340
  case "address":
@@ -3371,10 +3400,12 @@ function encodeFieldWithBudget(type, value, types, budget, depth) {
3371
3400
  throw new Error(`array field expects array: ${typeof value}`);
3372
3401
  }
3373
3402
  if (value.length > TYPED_DATA_LIMITS.maxArrayLength) {
3374
- throw new Error(`array ${type} exceeds length limit`);
3403
+ throw new Error(`array ${inputPreview(type)} exceeds length limit`);
3375
3404
  }
3376
3405
  if (parsed.size !== void 0 && value.length !== parsed.size) {
3377
- throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
3406
+ throw new Error(
3407
+ `fixed array ${inputPreview(type)} requires length ${parsed.size}, got ${value.length}`
3408
+ );
3378
3409
  }
3379
3410
  const chunks = value.map(
3380
3411
  (v) => encodeFieldWithBudget(parsed.inner, v, types, budget, depth + 1)
@@ -3391,7 +3422,7 @@ function encodeField(type, value, types, schemeVersion) {
3391
3422
  function hashStructWithBudget(primary, data, types, budget, depth) {
3392
3423
  consumeValue(budget);
3393
3424
  const fields = types[primary];
3394
- if (!fields) throw new Error(`unknown struct: ${primary}`);
3425
+ if (!fields) throw new Error(`unknown struct: ${inputPreview(primary)}`);
3395
3426
  const expected = new Set(fields.map((f) => f.name));
3396
3427
  let ownFieldCount = 0;
3397
3428
  for (const k in data) {
@@ -3400,12 +3431,14 @@ function hashStructWithBudget(primary, data, types, budget, depth) {
3400
3431
  if (ownFieldCount > TYPED_DATA_LIMITS.maxEncodedValues) {
3401
3432
  throw new Error("typed data object contains too many fields");
3402
3433
  }
3403
- if (!expected.has(k)) throw new Error(`unknown field in ${primary}: ${k}`);
3434
+ if (!expected.has(k)) {
3435
+ throw new Error(`unknown field in ${inputPreview(primary)}: ${inputPreview(k)}`);
3436
+ }
3404
3437
  }
3405
3438
  const parts = [typeHash(primary, types)];
3406
3439
  for (const f of fields) {
3407
3440
  if (!Object.prototype.hasOwnProperty.call(data, f.name)) {
3408
- throw new Error(`missing field ${primary}.${f.name}`);
3441
+ throw new Error(`missing field ${inputPreview(primary)}.${inputPreview(f.name)}`);
3409
3442
  }
3410
3443
  parts.push(encodeFieldWithBudget(f.type, data[f.name], types, budget, depth + 1));
3411
3444
  }
@@ -3431,11 +3464,13 @@ function validateDomainTypes(types) {
3431
3464
  const expected = RESERVED_DOMAIN_FIELDS[f.name];
3432
3465
  if (!expected) {
3433
3466
  throw new Error(
3434
- `QRLDomain field "${f.name}" not in reserved set (name, version, chainId, verifyingContract, salt)`
3467
+ `QRLDomain field "${inputPreview(f.name)}" not in reserved set (name, version, chainId, verifyingContract, salt)`
3435
3468
  );
3436
3469
  }
3437
3470
  if (f.type !== expected) {
3438
- throw new Error(`QRLDomain field "${f.name}" must be type "${expected}", got "${f.type}"`);
3471
+ throw new Error(
3472
+ `QRLDomain field "${inputPreview(f.name)}" must be type "${expected}", got "${inputPreview(f.type)}"`
3473
+ );
3439
3474
  }
3440
3475
  if (f.name === "name") hasName = true;
3441
3476
  }
@@ -3447,7 +3482,7 @@ function validatePayloadReachability(primary, types) {
3447
3482
  for (const t of collectDependencies(root, types)) reachable.add(t);
3448
3483
  }
3449
3484
  for (const k of Object.keys(types)) {
3450
- if (!reachable.has(k)) throw new Error(`unused referenced type: ${k}`);
3485
+ if (!reachable.has(k)) throw new Error(`unused referenced type: ${inputPreview(k)}`);
3451
3486
  }
3452
3487
  }
3453
3488
  function parseAndValidatePayload(payload) {
@@ -3569,7 +3604,8 @@ function canonicalChainId2(value) {
3569
3604
  return `0x${BigInt(value).toString(16)}`;
3570
3605
  }
3571
3606
  function requestedSwitchChainId(params) {
3572
- if (params?.length !== 1) {
3607
+ const parameterCount = params?.length;
3608
+ if (parameterCount !== 1) {
3573
3609
  throw new Error("wallet_switchQrlChain requires one chain configuration object");
3574
3610
  }
3575
3611
  const request = params?.[0];
@@ -3635,7 +3671,7 @@ function snapshotRequestParams(params) {
3635
3671
  return snapshot;
3636
3672
  } catch (error2) {
3637
3673
  const detail = error2 instanceof Error ? error2.message : String(error2);
3638
- throw new Error(`Request params must be JSON-serializable: ${detail}`);
3674
+ throw new Error(`Request params must be JSON-serializable: ${detail}`, { cause: error2 });
3639
3675
  }
3640
3676
  }
3641
3677
  function validateRestrictedRequest(method, params, authorizedAccounts, currentChainId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrlwallet/connect",
3
- "version": "5.1.0",
3
+ "version": "5.1.2",
4
4
  "packageManager": "npm@10.9.8",
5
5
  "description": "Connect your dApp to QRL Wallet via QR code or deep link",
6
6
  "main": "dist/index.js",
@@ -13,16 +13,19 @@
13
13
  "scripts": {
14
14
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
15
15
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
16
- "typecheck": "tsc --noEmit",
16
+ "typecheck": "tsc --noEmit && npm run typecheck:test",
17
+ "typecheck:test": "tsc --noEmit -p tsconfig.eslint.json",
17
18
  "test": "vitest run",
18
19
  "test:watch": "vitest",
19
- "lint": "eslint src/ test/ --ext .ts",
20
- "lint:fix": "eslint src/ test/ --ext .ts --fix",
20
+ "lint": "eslint --config eslint.config.mjs src test scripts \"*.mjs\" vitest.config.ts --report-unused-disable-directives --max-warnings 0",
21
+ "lint:fix": "eslint --config eslint.config.mjs src test scripts \"*.mjs\" vitest.config.ts --report-unused-disable-directives --max-warnings 0 --fix",
21
22
  "format": "prettier --write src/",
22
23
  "format:check": "prettier --check src/",
23
24
  "size": "size-limit",
24
25
  "size:check": "size-limit --highlight-less",
25
- "prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run build"
26
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run build",
27
+ "lint:ratchet": "node scripts/lint-ratchet.mjs --check",
28
+ "lint:ratchet:update": "node scripts/lint-ratchet.mjs"
26
29
  },
27
30
  "keywords": [
28
31
  "qrl",
@@ -49,13 +52,15 @@
49
52
  "socket.io-client": "^4.8.1"
50
53
  },
51
54
  "devDependencies": {
55
+ "@eslint/js": "^10.0.1",
52
56
  "@size-limit/file": "^11.0.0",
53
57
  "@theqrl/wallet.js": "^6.3.0",
54
58
  "@types/node": "^25.3.3",
55
59
  "@typescript-eslint/eslint-plugin": "^8.0.0",
56
60
  "@typescript-eslint/parser": "^8.0.0",
57
- "eslint": "^8.57.0",
61
+ "eslint": "^10.12.0",
58
62
  "eslint-config-prettier": "^9.1.0",
63
+ "globals": "^17.13.0",
59
64
  "prettier": "^3.4.0",
60
65
  "size-limit": "^11.0.0",
61
66
  "tsup": "^8.4.0",