@qrlwallet/connect 2.0.0 → 3.0.0

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.mjs CHANGED
@@ -145,15 +145,13 @@ var SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
145
145
  var STORAGE_KEY_PREFIX = "@qrlwallet/connect";
146
146
  var REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
147
147
  var WALLET_UNRESPONSIVE_MS = 30 * 1e3;
148
+ var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
148
149
  var RESTRICTED_METHODS = /* @__PURE__ */ new Set([
149
150
  "qrl_requestAccounts",
150
151
  "qrl_sendTransaction",
151
152
  "qrl_signTransaction",
152
- "qrl_sign",
153
- "personal_sign",
153
+ "qrl_signMessage",
154
154
  "qrl_signTypedData",
155
- "qrl_signTypedData_v3",
156
- "qrl_signTypedData_v4",
157
155
  "wallet_addQrlChain",
158
156
  "wallet_switchQrlChain"
159
157
  ]);
@@ -516,7 +514,10 @@ var SocketClient = class extends EventEmitter2 {
516
514
  * Connect to the relay server.
517
515
  */
518
516
  connect() {
519
- if (this.socket?.connected) return;
517
+ if (this.socket) {
518
+ if (!this.socket.connected) this.socket.connect();
519
+ return;
520
+ }
520
521
  log("Socket", `Connecting to ${this.relayUrl}`);
521
522
  this.socket = io(this.relayUrl, {
522
523
  path: RELAY_PATH,
@@ -534,7 +535,7 @@ var SocketClient = class extends EventEmitter2 {
534
535
  const reconnectChannelId = this.channelId;
535
536
  this.joinChannelNow(reconnectChannelId).then((result) => {
536
537
  this.settlePendingJoin(reconnectChannelId, { result });
537
- this.emit("reconnected");
538
+ this.emit("reconnected", result);
538
539
  }).catch((err) => {
539
540
  const reconnectErr = err instanceof Error ? err : new Error(String(err));
540
541
  this.settlePendingJoin(reconnectChannelId, { error: reconnectErr });
@@ -634,7 +635,9 @@ var SocketClient = class extends EventEmitter2 {
634
635
  log("Socket", `Joined channel ${channelId}`);
635
636
  resolve({
636
637
  bufferedMessages: response.bufferedMessages || [],
637
- channelPublicKey: response.channelPublicKey ?? null
638
+ channelPublicKey: response.channelPublicKey ?? null,
639
+ participants: response.participants || [],
640
+ terminated: response.terminated === true
638
641
  });
639
642
  } else {
640
643
  error("Socket", `Failed to join channel: ${response.error}`);
@@ -913,6 +916,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
913
916
  this.status = "disconnected" /* DISCONNECTED */;
914
917
  this.connectedAccounts = [];
915
918
  this.unresponsiveTimer = null;
919
+ this.reconnectProbeTimer = null;
916
920
  this.failedReconnects = 0;
917
921
  this.walletPresent = false;
918
922
  this.pendingRestore = null;
@@ -942,13 +946,23 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
942
946
  this.failedReconnects = 0;
943
947
  }
944
948
  });
945
- this.socketClient.on("reconnected", () => {
949
+ this.socketClient.on("reconnected", (result) => {
950
+ if (result?.terminated) {
951
+ log("ConnectionManager", "Channel terminated, observed on auto-reconnect");
952
+ this.handleSessionTerminated();
953
+ return;
954
+ }
955
+ if (result) {
956
+ this.walletPresent = result.participants.includes("wallet");
957
+ }
946
958
  if (this.keyExchange?.areKeysExchanged()) {
947
959
  if (this.walletPresent) {
960
+ this.clearReconnectProbe();
948
961
  this.setStatus("connected" /* CONNECTED */);
949
962
  this.failedReconnects = 0;
950
963
  } else {
951
964
  this.setStatus("waiting" /* WAITING */);
965
+ this.armReconnectProbe();
952
966
  }
953
967
  } else {
954
968
  this.setStatus("waiting" /* WAITING */);
@@ -975,11 +989,17 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
975
989
  this.walletPresent = true;
976
990
  log("ConnectionManager", "Wallet joined channel");
977
991
  this.clearUnresponsiveTimer();
992
+ this.clearReconnectProbe();
978
993
  if (this.keyExchange?.areKeysExchanged()) {
979
994
  this.failedReconnects = 0;
980
995
  this.setStatus("connected" /* CONNECTED */);
981
996
  }
982
997
  }
998
+ if (data.event === "close") {
999
+ log("ConnectionManager", "Wallet closed the channel (explicit terminate)");
1000
+ this.handleSessionTerminated();
1001
+ return;
1002
+ }
983
1003
  if (data.event === "disconnect" || data.event === "leave") {
984
1004
  if (data.clientType === "wallet" || !data.clientType) {
985
1005
  this.walletPresent = false;
@@ -995,6 +1015,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
995
1015
  if (!this.keyExchange) return;
996
1016
  this.keyExchange.on("keys_exchanged", () => {
997
1017
  log("ConnectionManager", "Key exchange complete");
1018
+ this.clearReconnectProbe();
998
1019
  this.setStatus("connected" /* CONNECTED */);
999
1020
  this.walletPresent = true;
1000
1021
  this.failedReconnects = 0;
@@ -1015,6 +1036,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1015
1036
  */
1016
1037
  async getConnectionURI(retryOnConflict = true) {
1017
1038
  this.setStatus("connecting" /* CONNECTING */);
1039
+ this.clearReconnectProbe();
1018
1040
  this.pendingRestore = null;
1019
1041
  this.walletPresent = false;
1020
1042
  if (this.keyExchange) {
@@ -1069,17 +1091,39 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1069
1091
  this.setStatus("disconnected" /* DISCONNECTED */);
1070
1092
  return false;
1071
1093
  }
1094
+ return this.joinAndSettle();
1095
+ }
1096
+ /**
1097
+ * (Re)open the socket, join the persisted channel, drain buffered relay
1098
+ * messages, and settle status. Shared by the cold-restore reconnect() and
1099
+ * the warm resume() path whose socket was torn down by the reconnect probe.
1100
+ * Assumes channelId and a hydrated keyExchange are already in place, and
1101
+ * that the caller has reset walletPresent + set RECONNECTING status.
1102
+ */
1103
+ async joinAndSettle() {
1072
1104
  this.socketClient.connect();
1073
1105
  try {
1074
- const { bufferedMessages } = await this.socketClient.joinChannel(this.channelId);
1106
+ const { bufferedMessages, participants, terminated } = await this.socketClient.joinChannel(
1107
+ this.channelId
1108
+ );
1109
+ if (terminated) {
1110
+ log("ConnectionManager", "Stored session was terminated by the wallet; dropping it");
1111
+ this.handleSessionTerminated();
1112
+ return false;
1113
+ }
1114
+ if (participants.includes("wallet")) {
1115
+ this.walletPresent = true;
1116
+ }
1075
1117
  for (const msg of bufferedMessages) {
1076
1118
  this.enqueueRelayMessage(msg);
1077
1119
  }
1078
1120
  await this.messageQueue;
1079
- if (this.keyExchange?.areKeysExchanged()) {
1080
- this.setStatus(this.walletPresent ? "connected" /* CONNECTED */ : "waiting" /* WAITING */);
1121
+ if (this.keyExchange?.areKeysExchanged() && this.walletPresent) {
1122
+ this.clearReconnectProbe();
1123
+ this.setStatus("connected" /* CONNECTED */);
1081
1124
  } else {
1082
1125
  this.setStatus("waiting" /* WAITING */);
1126
+ this.armReconnectProbe();
1083
1127
  }
1084
1128
  return true;
1085
1129
  } catch (err) {
@@ -1088,6 +1132,65 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1088
1132
  return false;
1089
1133
  }
1090
1134
  }
1135
+ /**
1136
+ * Nudge the connection back to life after the dApp tab / wallet app was
1137
+ * backgrounded. Idempotent and safe to call from visibilitychange / online
1138
+ * / pageshow handlers. If a restored session has not yet been hydrated,
1139
+ * runs the full reconnect(); otherwise just re-opens the socket, whose
1140
+ * own connect handler re-joins the channel and drains the relay buffer.
1141
+ */
1142
+ resume() {
1143
+ if (this.status === "connected" /* CONNECTED */) return;
1144
+ if (this.pendingRestore && !this.keyExchange?.areKeysExchanged()) {
1145
+ void this.reconnect();
1146
+ return;
1147
+ }
1148
+ if (this.channelId && this.keyExchange?.areKeysExchanged()) {
1149
+ if (this.socketClient.getChannelId()) {
1150
+ this.socketClient.connect();
1151
+ } else {
1152
+ this.setStatus("reconnecting" /* RECONNECTING */);
1153
+ this.walletPresent = false;
1154
+ void this.joinAndSettle();
1155
+ }
1156
+ }
1157
+ }
1158
+ /**
1159
+ * A session was terminated for good (wallet sent a relay 'close', or the
1160
+ * join ack reported a tombstone). Clear local state and surface
1161
+ * DISCONNECTED so the consumer drops to a fresh-pairing UI.
1162
+ */
1163
+ handleSessionTerminated() {
1164
+ this.clearReconnectProbe();
1165
+ this.clearUnresponsiveTimer();
1166
+ this.walletPresent = false;
1167
+ this.pendingRestore = null;
1168
+ this.keyExchange = null;
1169
+ this.socketClient.leaveChannel();
1170
+ this.socketClient.disconnect();
1171
+ this.clearSession();
1172
+ this.setStatus("disconnected" /* DISCONNECTED */);
1173
+ }
1174
+ armReconnectProbe() {
1175
+ this.clearReconnectProbe();
1176
+ this.reconnectProbeTimer = setTimeout(() => {
1177
+ this.reconnectProbeTimer = null;
1178
+ if (this.walletPresent) return;
1179
+ warn(
1180
+ "ConnectionManager",
1181
+ `No wallet rejoined within ${RECONNECT_WALLET_PROBE_MS}ms; treating reconnect as dead`
1182
+ );
1183
+ this.socketClient.leaveChannel();
1184
+ this.socketClient.disconnect();
1185
+ this.setStatus("disconnected" /* DISCONNECTED */);
1186
+ }, RECONNECT_WALLET_PROBE_MS);
1187
+ }
1188
+ clearReconnectProbe() {
1189
+ if (this.reconnectProbeTimer) {
1190
+ clearTimeout(this.reconnectProbeTimer);
1191
+ this.reconnectProbeTimer = null;
1192
+ }
1193
+ }
1091
1194
  /**
1092
1195
  * Send a JSON-RPC request to the wallet (async, fire-and-forget).
1093
1196
  */
@@ -1152,6 +1255,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1152
1255
  */
1153
1256
  async resetForNewChannel() {
1154
1257
  this.clearUnresponsiveTimer();
1258
+ this.clearReconnectProbe();
1155
1259
  this.walletPresent = false;
1156
1260
  await this.flushTerminate();
1157
1261
  this.socketClient.leaveChannel();
@@ -1167,6 +1271,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1167
1271
  }
1168
1272
  async disconnect() {
1169
1273
  this.clearUnresponsiveTimer();
1274
+ this.clearReconnectProbe();
1170
1275
  this.walletPresent = false;
1171
1276
  await this.flushTerminate();
1172
1277
  this.socketClient.leaveChannel();
@@ -1174,6 +1279,8 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1174
1279
  this.setStatus("disconnected" /* DISCONNECTED */);
1175
1280
  this.clearSession();
1176
1281
  this.connectedAccounts = [];
1282
+ this.pendingRestore = null;
1283
+ this.keyExchange = null;
1177
1284
  }
1178
1285
  // ── Internals ──────────────────────────────────────────────
1179
1286
  enqueueRelayMessage(data) {
@@ -1182,6 +1289,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
1182
1289
  async handleRelayMessage(data) {
1183
1290
  if (data.clientType === "wallet") {
1184
1291
  this.walletPresent = true;
1292
+ this.clearReconnectProbe();
1185
1293
  if (this.keyExchange?.areKeysExchanged() && this.status !== "connected" /* CONNECTED */) {
1186
1294
  this.failedReconnects = 0;
1187
1295
  this.setStatus("connected" /* CONNECTED */);
@@ -1371,10 +1479,29 @@ function getAppStoreUrl() {
1371
1479
 
1372
1480
  // src/QRLConnectProvider.ts
1373
1481
  var requestCounter = 0;
1482
+ var QRL_CONNECT_PROVIDER_INFO = {
1483
+ name: "MyQRLWallet (Mobile)",
1484
+ rdns: "com.qrlwallet.connect",
1485
+ icon: "data:image/png;base64,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"
1486
+ };
1487
+ var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
1488
+ var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
1489
+ function generateUuid() {
1490
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
1491
+ return globalThis.crypto.randomUUID();
1492
+ }
1493
+ const r = (n) => Math.floor(Math.random() * n);
1494
+ const hex = (n, len) => n.toString(16).padStart(len, "0");
1495
+ return `${hex(r(4294967296), 8)}-${hex(r(65536), 4)}-${hex(16384 | r(4096), 4)}-${hex(32768 | r(16384), 4)}-${hex(r(4294967296), 8)}${hex(r(65536), 4)}`;
1496
+ }
1374
1497
  var QRLConnectProvider = class extends EventEmitter4 {
1375
1498
  constructor(options) {
1376
1499
  super();
1377
1500
  this.pendingRequests = /* @__PURE__ */ new Map();
1501
+ this.eip6963Detail = null;
1502
+ this.eip6963RequestListener = null;
1503
+ this.resumeListener = null;
1504
+ this.resumeDebounce = null;
1378
1505
  this.isQRLConnect = true;
1379
1506
  this.options = options;
1380
1507
  if (options.debug) {
@@ -1390,6 +1517,81 @@ var QRLConnectProvider = class extends EventEmitter4 {
1390
1517
  if (options.autoReconnect !== false) {
1391
1518
  this.connectionManager.reconnect();
1392
1519
  }
1520
+ this.setupResumeListeners();
1521
+ if (options.announceProvider !== false) {
1522
+ this.startEip6963Announce();
1523
+ }
1524
+ }
1525
+ startEip6963Announce() {
1526
+ if (typeof window === "undefined" || typeof CustomEvent === "undefined") {
1527
+ return;
1528
+ }
1529
+ const overrides = this.options.providerInfo ?? {};
1530
+ this.eip6963Detail = Object.freeze({
1531
+ info: Object.freeze({
1532
+ uuid: generateUuid(),
1533
+ name: overrides.name ?? QRL_CONNECT_PROVIDER_INFO.name,
1534
+ icon: overrides.icon ?? QRL_CONNECT_PROVIDER_INFO.icon,
1535
+ rdns: overrides.rdns ?? QRL_CONNECT_PROVIDER_INFO.rdns
1536
+ }),
1537
+ provider: this
1538
+ });
1539
+ const announce = () => {
1540
+ if (!this.eip6963Detail) return;
1541
+ window.dispatchEvent(new CustomEvent(EIP6963_ANNOUNCE_EVENT, { detail: this.eip6963Detail }));
1542
+ };
1543
+ this.eip6963RequestListener = announce;
1544
+ window.addEventListener(EIP6963_REQUEST_EVENT, announce);
1545
+ announce();
1546
+ }
1547
+ /**
1548
+ * Re-open the relay socket when the dApp tab regains focus or the network
1549
+ * comes back. Debounced because visibilitychange + online can fire together.
1550
+ * No-op when already connected (ConnectionManager.resume guards that).
1551
+ */
1552
+ setupResumeListeners() {
1553
+ if (typeof window === "undefined" || typeof document === "undefined") return;
1554
+ if (this.resumeListener) return;
1555
+ const resume = () => {
1556
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
1557
+ if (this.resumeDebounce) clearTimeout(this.resumeDebounce);
1558
+ this.resumeDebounce = setTimeout(() => {
1559
+ this.resumeDebounce = null;
1560
+ this.connectionManager.resume();
1561
+ }, 300);
1562
+ };
1563
+ this.resumeListener = resume;
1564
+ document.addEventListener("visibilitychange", resume);
1565
+ window.addEventListener("online", resume);
1566
+ window.addEventListener("pageshow", resume);
1567
+ }
1568
+ /**
1569
+ * Remove the foreground/online resume listeners. Called on disconnect so a
1570
+ * torn-down session can't be silently revived by a later tab focus; the
1571
+ * listeners are re-armed by getConnectionURI()/newConnection() if the same
1572
+ * provider is re-paired. Safe in any env.
1573
+ */
1574
+ teardownResumeListeners() {
1575
+ if (typeof window !== "undefined" && typeof document !== "undefined" && this.resumeListener) {
1576
+ document.removeEventListener("visibilitychange", this.resumeListener);
1577
+ window.removeEventListener("online", this.resumeListener);
1578
+ window.removeEventListener("pageshow", this.resumeListener);
1579
+ }
1580
+ this.resumeListener = null;
1581
+ if (this.resumeDebounce) {
1582
+ clearTimeout(this.resumeDebounce);
1583
+ this.resumeDebounce = null;
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Stop announcing this provider over EIP-6963. Safe to call from any env.
1588
+ */
1589
+ stopEip6963Announce() {
1590
+ if (typeof window !== "undefined" && this.eip6963RequestListener) {
1591
+ window.removeEventListener(EIP6963_REQUEST_EVENT, this.eip6963RequestListener);
1592
+ }
1593
+ this.eip6963RequestListener = null;
1594
+ this.eip6963Detail = null;
1393
1595
  }
1394
1596
  setupConnectionListeners() {
1395
1597
  this.connectionManager.on("status_changed", (status) => {
@@ -1447,6 +1649,7 @@ var QRLConnectProvider = class extends EventEmitter4 {
1447
1649
  * Generate a connection URI for QR code display or deep link redirect.
1448
1650
  */
1449
1651
  async getConnectionURI() {
1652
+ this.setupResumeListeners();
1450
1653
  return this.connectionManager.getConnectionURI();
1451
1654
  }
1452
1655
  /**
@@ -1568,15 +1771,373 @@ var QRLConnectProvider = class extends EventEmitter4 {
1568
1771
  pending.reject(new Error("Disconnected"));
1569
1772
  }
1570
1773
  this.pendingRequests.clear();
1774
+ this.teardownResumeListeners();
1571
1775
  await this.connectionManager.disconnect();
1572
1776
  }
1573
1777
  };
1778
+
1779
+ // src/signing/ctx.ts
1780
+ var SCHEME_VERSION_MSG = "QRL-SIGN-MSG-v1";
1781
+ var SCHEME_VERSION_TYPED = "QRL-SIGN-TYPED-v1";
1782
+ var SCHEME_TAG_MSG = new TextEncoder().encode(SCHEME_VERSION_MSG);
1783
+ var SCHEME_TAG_TYPED = new TextEncoder().encode(SCHEME_VERSION_TYPED);
1784
+ var DIGEST_LEN = 64;
1785
+
1786
+ // src/signing/messageDigest.ts
1787
+ import { shake256 } from "@noble/hashes/sha3.js";
1788
+
1789
+ // src/signing/bytes.ts
1790
+ function hexToBytes(hex) {
1791
+ if (typeof hex !== "string") throw new Error("hex must be a string");
1792
+ if (!/^0x([0-9a-fA-F]{2})*$/.test(hex)) {
1793
+ throw new Error("hex must be 0x-prefixed even-length");
1794
+ }
1795
+ const n = (hex.length - 2) / 2;
1796
+ const out = new Uint8Array(n);
1797
+ for (let i = 0; i < n; i++) {
1798
+ out[i] = parseInt(hex.slice(2 + i * 2, 4 + i * 2), 16);
1799
+ }
1800
+ return out;
1801
+ }
1802
+ function bytesToHex(bytes) {
1803
+ if (!(bytes instanceof Uint8Array)) {
1804
+ throw new Error("bytes must be a Uint8Array");
1805
+ }
1806
+ let s = "0x";
1807
+ for (const v of bytes) s += v.toString(16).padStart(2, "0");
1808
+ return s;
1809
+ }
1810
+ function concatBytes(...parts) {
1811
+ return concatBytesArr(parts);
1812
+ }
1813
+ function concatBytesArr(parts) {
1814
+ let total = 0;
1815
+ for (const p of parts) total += p.length;
1816
+ const out = new Uint8Array(total);
1817
+ let off = 0;
1818
+ for (const p of parts) {
1819
+ out.set(p, off);
1820
+ off += p.length;
1821
+ }
1822
+ return out;
1823
+ }
1824
+
1825
+ // src/signing/messageDigest.ts
1826
+ function computeMessageDigest(messageBytes) {
1827
+ if (!(messageBytes instanceof Uint8Array)) {
1828
+ throw new Error("messageBytes must be a Uint8Array");
1829
+ }
1830
+ return shake256(concatBytes(SCHEME_TAG_MSG, messageBytes), { dkLen: DIGEST_LEN });
1831
+ }
1832
+
1833
+ // src/signing/typedData.ts
1834
+ import { shake256 as shake2562 } from "@noble/hashes/sha3.js";
1835
+ var SLOT = 32;
1836
+ var ATOMIC_RE = /^(?:(address|bool|string|bytes)|(u?int)(\d+)|bytes(\d+)|(.+?)\[(\d*)\])$/;
1837
+ var MAX_TYPE_DEPTH = 12;
1838
+ function parseFieldType(type, types, depth = 0) {
1839
+ if (depth > MAX_TYPE_DEPTH) throw new Error(`type nesting too deep: ${type}`);
1840
+ if (Object.prototype.hasOwnProperty.call(types, type)) {
1841
+ return { kind: "ref", name: type };
1842
+ }
1843
+ const m = ATOMIC_RE.exec(type);
1844
+ if (!m) throw new Error(`unknown type: ${type}`);
1845
+ const [, atomic, intKind, intWidthStr, bytesWidthStr, innerType, sizeStr] = m;
1846
+ if (atomic) return { kind: atomic };
1847
+ if (intKind) {
1848
+ const width = Number(intWidthStr);
1849
+ if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
1850
+ throw new Error(`invalid int width: ${type}`);
1851
+ }
1852
+ return { kind: intKind === "uint" ? "uintN" : "intN", width };
1853
+ }
1854
+ if (bytesWidthStr) {
1855
+ const width = Number(bytesWidthStr);
1856
+ if (!Number.isInteger(width) || width < 1 || width > 32) {
1857
+ throw new Error(`invalid bytesN width: ${type}`);
1858
+ }
1859
+ return { kind: "bytesN", width };
1860
+ }
1861
+ if (innerType !== void 0) {
1862
+ parseFieldType(innerType, types, depth + 1);
1863
+ const size = sizeStr ? Number(sizeStr) : void 0;
1864
+ if (sizeStr && (!Number.isInteger(size) || size <= 0)) {
1865
+ throw new Error(`invalid array size: ${type}`);
1866
+ }
1867
+ return { kind: "array", inner: innerType, size };
1868
+ }
1869
+ throw new Error(`unhandled type: ${type}`);
1870
+ }
1871
+ function baseTypeName(type) {
1872
+ return type.replace(/(\[\d*\])+$/, "");
1873
+ }
1874
+ function collectDependencies(primary, types) {
1875
+ if (!Object.prototype.hasOwnProperty.call(types, primary)) {
1876
+ throw new Error(`unknown primary type: ${primary}`);
1877
+ }
1878
+ const visited = /* @__PURE__ */ new Set();
1879
+ const visit = (name, path) => {
1880
+ if (path.includes(name)) {
1881
+ throw new Error(`cyclic type reference: ${[...path, name].join(" -> ")}`);
1882
+ }
1883
+ if (visited.has(name)) return;
1884
+ visited.add(name);
1885
+ for (const f of types[name]) {
1886
+ const base = baseTypeName(f.type);
1887
+ if (Object.prototype.hasOwnProperty.call(types, base)) {
1888
+ visit(base, [...path, name]);
1889
+ }
1890
+ parseFieldType(f.type, types);
1891
+ }
1892
+ };
1893
+ visit(primary, []);
1894
+ return visited;
1895
+ }
1896
+ function validateTypeMap(types) {
1897
+ for (const [name, def] of Object.entries(types)) {
1898
+ if (!Array.isArray(def) || def.length === 0) {
1899
+ throw new Error(`empty or invalid struct: ${name}`);
1900
+ }
1901
+ const seen = /* @__PURE__ */ new Set();
1902
+ for (const f of def) {
1903
+ if (!f || typeof f.name !== "string" || !f.name) {
1904
+ throw new Error(`bad field in ${name}`);
1905
+ }
1906
+ if (typeof f.type !== "string" || !f.type) {
1907
+ throw new Error(`bad field type in ${name}.${f.name}`);
1908
+ }
1909
+ if (seen.has(f.name)) {
1910
+ throw new Error(`duplicate field "${f.name}" in ${name}`);
1911
+ }
1912
+ seen.add(f.name);
1913
+ }
1914
+ }
1915
+ }
1916
+ function encodeType(primary, types) {
1917
+ const deps = collectDependencies(primary, types);
1918
+ const others = [...deps].filter((n) => n !== primary).sort();
1919
+ return [primary, ...others].map((name) => {
1920
+ const fields = types[name];
1921
+ const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
1922
+ return `${name}(${inner})`;
1923
+ }).join("");
1924
+ }
1925
+ function typeHash(primary, types) {
1926
+ return shake2562(new TextEncoder().encode(encodeType(primary, types)), { dkLen: DIGEST_LEN });
1927
+ }
1928
+ function parseQAddress(addr) {
1929
+ if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
1930
+ throw new Error(`invalid Q-address: ${String(addr)}`);
1931
+ }
1932
+ return hexToBytes("0x" + addr.slice(1).toLowerCase());
1933
+ }
1934
+ function padLeft32(bytes) {
1935
+ if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
1936
+ const out = new Uint8Array(SLOT);
1937
+ out.set(bytes, SLOT - bytes.length);
1938
+ return out;
1939
+ }
1940
+ function padRight32(bytes) {
1941
+ if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
1942
+ const out = new Uint8Array(SLOT);
1943
+ out.set(bytes, 0);
1944
+ return out;
1945
+ }
1946
+ function bigIntToSlot(value, width, signed) {
1947
+ if (signed) {
1948
+ const limit = 1n << BigInt(width - 1);
1949
+ if (value >= limit || value < -limit) {
1950
+ throw new Error(`int${width} out of range: ${value}`);
1951
+ }
1952
+ } else {
1953
+ if (value < 0n) throw new Error(`uint${width} negative: ${value}`);
1954
+ const limit = 1n << BigInt(width);
1955
+ if (value >= limit) throw new Error(`uint${width} out of range: ${value}`);
1956
+ }
1957
+ const slotMask = (1n << 256n) - 1n;
1958
+ const repr = value < 0n ? value + (1n << 256n) & slotMask : value;
1959
+ const out = new Uint8Array(SLOT);
1960
+ let v = repr;
1961
+ for (let i = SLOT - 1; i >= 0; i--) {
1962
+ out[i] = Number(v & 0xffn);
1963
+ v >>= 8n;
1964
+ }
1965
+ return out;
1966
+ }
1967
+ function parseIntValue(v, typeLabel) {
1968
+ if (typeof v === "bigint") return v;
1969
+ if (typeof v === "string") {
1970
+ if (/^-?0x[0-9a-fA-F]+$/i.test(v)) {
1971
+ const isNegative = v.startsWith("-");
1972
+ const abs = BigInt(isNegative ? v.slice(1) : v);
1973
+ return isNegative ? -abs : abs;
1974
+ }
1975
+ if (!/^-?(0|[1-9]\d*)$/.test(v)) {
1976
+ throw new Error(`invalid ${typeLabel} string: ${v}`);
1977
+ }
1978
+ return BigInt(v);
1979
+ }
1980
+ if (typeof v === "number") {
1981
+ if (!Number.isInteger(v) || !Number.isSafeInteger(v)) {
1982
+ throw new Error(`unsafe ${typeLabel} number: ${v}, pass as string`);
1983
+ }
1984
+ return BigInt(v);
1985
+ }
1986
+ throw new Error(`unsupported ${typeLabel} value: ${typeof v}`);
1987
+ }
1988
+ function encodeField(type, value, types) {
1989
+ const parsed = parseFieldType(type, types);
1990
+ switch (parsed.kind) {
1991
+ case "ref":
1992
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1993
+ throw new Error(`struct field expects object: ${type}`);
1994
+ }
1995
+ return hashStruct(parsed.name, value, types);
1996
+ case "address":
1997
+ if (typeof value !== "string") {
1998
+ throw new Error(`address field expects string: ${typeof value}`);
1999
+ }
2000
+ return padLeft32(parseQAddress(value));
2001
+ case "bool":
2002
+ if (typeof value !== "boolean") {
2003
+ throw new Error(`bool field expects boolean: ${typeof value}`);
2004
+ }
2005
+ return padLeft32(new Uint8Array([value ? 1 : 0]));
2006
+ case "string":
2007
+ if (typeof value !== "string") {
2008
+ throw new Error(`string field expects string: ${typeof value}`);
2009
+ }
2010
+ return shake2562(new TextEncoder().encode(value), { dkLen: DIGEST_LEN });
2011
+ case "bytes":
2012
+ if (typeof value !== "string") {
2013
+ throw new Error(`bytes field expects 0x-hex string: ${typeof value}`);
2014
+ }
2015
+ return shake2562(hexToBytes(value), { dkLen: DIGEST_LEN });
2016
+ case "uintN":
2017
+ return bigIntToSlot(parseIntValue(value, `uint${parsed.width}`), parsed.width, false);
2018
+ case "intN":
2019
+ return bigIntToSlot(parseIntValue(value, `int${parsed.width}`), parsed.width, true);
2020
+ case "bytesN": {
2021
+ if (typeof value !== "string") {
2022
+ throw new Error(`bytes${parsed.width} expects 0x-hex string: ${typeof value}`);
2023
+ }
2024
+ const raw = hexToBytes(value);
2025
+ if (raw.length !== parsed.width) {
2026
+ throw new Error(`bytes${parsed.width} requires ${parsed.width} bytes, got ${raw.length}`);
2027
+ }
2028
+ return padRight32(raw);
2029
+ }
2030
+ case "array": {
2031
+ if (!Array.isArray(value)) {
2032
+ throw new Error(`array field expects array: ${typeof value}`);
2033
+ }
2034
+ if (parsed.size !== void 0 && value.length !== parsed.size) {
2035
+ throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
2036
+ }
2037
+ const chunks = value.map((v) => encodeField(parsed.inner, v, types));
2038
+ return shake2562(concatBytesArr(chunks), { dkLen: DIGEST_LEN });
2039
+ }
2040
+ }
2041
+ }
2042
+ function hashStruct(primary, data, types) {
2043
+ const fields = types[primary];
2044
+ if (!fields) throw new Error(`unknown struct: ${primary}`);
2045
+ const expected = new Set(fields.map((f) => f.name));
2046
+ for (const k of Object.keys(data)) {
2047
+ if (!expected.has(k)) throw new Error(`unknown field in ${primary}: ${k}`);
2048
+ }
2049
+ const parts = [typeHash(primary, types)];
2050
+ for (const f of fields) {
2051
+ if (!(f.name in data)) throw new Error(`missing field ${primary}.${f.name}`);
2052
+ parts.push(encodeField(f.type, data[f.name], types));
2053
+ }
2054
+ return shake2562(concatBytesArr(parts), { dkLen: DIGEST_LEN });
2055
+ }
2056
+ var RESERVED_DOMAIN_FIELDS = {
2057
+ name: "string",
2058
+ version: "string",
2059
+ chainId: "uint256",
2060
+ verifyingContract: "address",
2061
+ salt: "bytes32"
2062
+ };
2063
+ function validateDomainTypes(types) {
2064
+ const def = types.QRLDomain;
2065
+ if (!def) throw new Error("QRLDomain type is required");
2066
+ let hasName = false;
2067
+ for (const f of def) {
2068
+ const expected = RESERVED_DOMAIN_FIELDS[f.name];
2069
+ if (!expected) {
2070
+ throw new Error(
2071
+ `QRLDomain field "${f.name}" not in reserved set (name, version, chainId, verifyingContract, salt)`
2072
+ );
2073
+ }
2074
+ if (f.type !== expected) {
2075
+ throw new Error(`QRLDomain field "${f.name}" must be type "${expected}", got "${f.type}"`);
2076
+ }
2077
+ if (f.name === "name") hasName = true;
2078
+ }
2079
+ if (!hasName) throw new Error("QRLDomain.name is required");
2080
+ }
2081
+ function validatePayloadReachability(primary, types) {
2082
+ const reachable = /* @__PURE__ */ new Set();
2083
+ for (const root of ["QRLDomain", primary]) {
2084
+ for (const t of collectDependencies(root, types)) reachable.add(t);
2085
+ }
2086
+ for (const k of Object.keys(types)) {
2087
+ if (!reachable.has(k)) throw new Error(`unused referenced type: ${k}`);
2088
+ }
2089
+ }
2090
+ function computeTypedDataDigest(payload) {
2091
+ if (!payload || typeof payload !== "object") {
2092
+ throw new Error("invalid typed data payload");
2093
+ }
2094
+ validateTypeMap(payload.types);
2095
+ validateDomainTypes(payload.types);
2096
+ validatePayloadReachability(payload.primaryType, payload.types);
2097
+ const domainHash = hashStruct("QRLDomain", payload.domain, payload.types);
2098
+ const messageHash = hashStruct(payload.primaryType, payload.message, payload.types);
2099
+ return shake2562(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), { dkLen: DIGEST_LEN });
2100
+ }
2101
+
2102
+ // src/signing/verify.ts
2103
+ import * as mldsa from "@theqrl/mldsa87";
2104
+ function bytesOrHex(v) {
2105
+ if (v instanceof Uint8Array) return v;
2106
+ if (typeof v === "string") return hexToBytes(v);
2107
+ throw new Error("expected Uint8Array or 0x-hex string");
2108
+ }
2109
+ function verifyMessage({
2110
+ signature,
2111
+ publicKey,
2112
+ messageBytes
2113
+ }) {
2114
+ try {
2115
+ const sig = bytesOrHex(signature);
2116
+ const pk = bytesOrHex(publicKey);
2117
+ const msg = bytesOrHex(messageBytes);
2118
+ const digest = computeMessageDigest(msg);
2119
+ return mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_MSG);
2120
+ } catch {
2121
+ return false;
2122
+ }
2123
+ }
2124
+ function verifyTypedData({ signature, publicKey, payload }) {
2125
+ try {
2126
+ const sig = bytesOrHex(signature);
2127
+ const pk = bytesOrHex(publicKey);
2128
+ const digest = computeTypedDataDigest(payload);
2129
+ return mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2130
+ } catch {
2131
+ return false;
2132
+ }
2133
+ }
1574
2134
  export {
1575
2135
  BLOB_LEN,
1576
2136
  CID_LEN,
1577
2137
  ConnectionManager,
1578
2138
  ConnectionStatus,
1579
2139
  DEFAULT_RELAY_URL,
2140
+ DIGEST_LEN,
1580
2141
  FP_LEN,
1581
2142
  KeyExchange,
1582
2143
  KeyExchangeMessageType,
@@ -1584,16 +2145,32 @@ export {
1584
2145
  PROTOCOL_VERSION,
1585
2146
  QRLConnectProvider as QRLConnect,
1586
2147
  QRLConnectProvider,
2148
+ QRL_CONNECT_PROVIDER_INFO,
1587
2149
  RESTRICTED_METHODS,
2150
+ SCHEME_TAG_MSG,
2151
+ SCHEME_TAG_TYPED,
2152
+ SCHEME_VERSION_MSG,
2153
+ SCHEME_VERSION_TYPED,
1588
2154
  SocketClient,
1589
2155
  UNRESTRICTED_METHODS,
2156
+ bytesToHex,
1590
2157
  cidFromString,
1591
2158
  cidRandom,
1592
2159
  cidToString,
1593
2160
  computeFingerprint,
2161
+ computeMessageDigest,
2162
+ computeTypedDataDigest,
2163
+ concatBytes,
2164
+ encodeField,
2165
+ encodeType,
1594
2166
  fingerprintEquals,
1595
2167
  generateConnectionURI,
1596
2168
  getAppStoreUrl,
2169
+ hashStruct,
2170
+ hexToBytes,
1597
2171
  isMobileBrowser,
1598
- parseConnectionURI
2172
+ parseConnectionURI,
2173
+ typeHash,
2174
+ verifyMessage,
2175
+ verifyTypedData
1599
2176
  };