@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.js CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  ConnectionManager: () => ConnectionManager,
36
36
  ConnectionStatus: () => ConnectionStatus,
37
37
  DEFAULT_RELAY_URL: () => DEFAULT_RELAY_URL,
38
+ DIGEST_LEN: () => DIGEST_LEN,
38
39
  FP_LEN: () => FP_LEN,
39
40
  KeyExchange: () => KeyExchange,
40
41
  KeyExchangeMessageType: () => KeyExchangeMessageType,
@@ -42,18 +43,34 @@ __export(index_exports, {
42
43
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
43
44
  QRLConnect: () => QRLConnectProvider,
44
45
  QRLConnectProvider: () => QRLConnectProvider,
46
+ QRL_CONNECT_PROVIDER_INFO: () => QRL_CONNECT_PROVIDER_INFO,
45
47
  RESTRICTED_METHODS: () => RESTRICTED_METHODS,
48
+ SCHEME_TAG_MSG: () => SCHEME_TAG_MSG,
49
+ SCHEME_TAG_TYPED: () => SCHEME_TAG_TYPED,
50
+ SCHEME_VERSION_MSG: () => SCHEME_VERSION_MSG,
51
+ SCHEME_VERSION_TYPED: () => SCHEME_VERSION_TYPED,
46
52
  SocketClient: () => SocketClient,
47
53
  UNRESTRICTED_METHODS: () => UNRESTRICTED_METHODS,
54
+ bytesToHex: () => bytesToHex,
48
55
  cidFromString: () => cidFromString,
49
56
  cidRandom: () => cidRandom,
50
57
  cidToString: () => cidToString,
51
58
  computeFingerprint: () => computeFingerprint,
59
+ computeMessageDigest: () => computeMessageDigest,
60
+ computeTypedDataDigest: () => computeTypedDataDigest,
61
+ concatBytes: () => concatBytes,
62
+ encodeField: () => encodeField,
63
+ encodeType: () => encodeType,
52
64
  fingerprintEquals: () => fingerprintEquals,
53
65
  generateConnectionURI: () => generateConnectionURI,
54
66
  getAppStoreUrl: () => getAppStoreUrl,
67
+ hashStruct: () => hashStruct,
68
+ hexToBytes: () => hexToBytes,
55
69
  isMobileBrowser: () => isMobileBrowser,
56
- parseConnectionURI: () => parseConnectionURI
70
+ parseConnectionURI: () => parseConnectionURI,
71
+ typeHash: () => typeHash,
72
+ verifyMessage: () => verifyMessage,
73
+ verifyTypedData: () => verifyTypedData
57
74
  });
58
75
  module.exports = __toCommonJS(index_exports);
59
76
 
@@ -204,15 +221,13 @@ var SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
204
221
  var STORAGE_KEY_PREFIX = "@qrlwallet/connect";
205
222
  var REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
206
223
  var WALLET_UNRESPONSIVE_MS = 30 * 1e3;
224
+ var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
207
225
  var RESTRICTED_METHODS = /* @__PURE__ */ new Set([
208
226
  "qrl_requestAccounts",
209
227
  "qrl_sendTransaction",
210
228
  "qrl_signTransaction",
211
- "qrl_sign",
212
- "personal_sign",
229
+ "qrl_signMessage",
213
230
  "qrl_signTypedData",
214
- "qrl_signTypedData_v3",
215
- "qrl_signTypedData_v4",
216
231
  "wallet_addQrlChain",
217
232
  "wallet_switchQrlChain"
218
233
  ]);
@@ -575,7 +590,10 @@ var SocketClient = class extends import_eventemitter32.default {
575
590
  * Connect to the relay server.
576
591
  */
577
592
  connect() {
578
- if (this.socket?.connected) return;
593
+ if (this.socket) {
594
+ if (!this.socket.connected) this.socket.connect();
595
+ return;
596
+ }
579
597
  log("Socket", `Connecting to ${this.relayUrl}`);
580
598
  this.socket = (0, import_socket.io)(this.relayUrl, {
581
599
  path: RELAY_PATH,
@@ -593,7 +611,7 @@ var SocketClient = class extends import_eventemitter32.default {
593
611
  const reconnectChannelId = this.channelId;
594
612
  this.joinChannelNow(reconnectChannelId).then((result) => {
595
613
  this.settlePendingJoin(reconnectChannelId, { result });
596
- this.emit("reconnected");
614
+ this.emit("reconnected", result);
597
615
  }).catch((err) => {
598
616
  const reconnectErr = err instanceof Error ? err : new Error(String(err));
599
617
  this.settlePendingJoin(reconnectChannelId, { error: reconnectErr });
@@ -693,7 +711,9 @@ var SocketClient = class extends import_eventemitter32.default {
693
711
  log("Socket", `Joined channel ${channelId}`);
694
712
  resolve({
695
713
  bufferedMessages: response.bufferedMessages || [],
696
- channelPublicKey: response.channelPublicKey ?? null
714
+ channelPublicKey: response.channelPublicKey ?? null,
715
+ participants: response.participants || [],
716
+ terminated: response.terminated === true
697
717
  });
698
718
  } else {
699
719
  error("Socket", `Failed to join channel: ${response.error}`);
@@ -972,6 +992,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
972
992
  this.status = "disconnected" /* DISCONNECTED */;
973
993
  this.connectedAccounts = [];
974
994
  this.unresponsiveTimer = null;
995
+ this.reconnectProbeTimer = null;
975
996
  this.failedReconnects = 0;
976
997
  this.walletPresent = false;
977
998
  this.pendingRestore = null;
@@ -1001,13 +1022,23 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1001
1022
  this.failedReconnects = 0;
1002
1023
  }
1003
1024
  });
1004
- this.socketClient.on("reconnected", () => {
1025
+ this.socketClient.on("reconnected", (result) => {
1026
+ if (result?.terminated) {
1027
+ log("ConnectionManager", "Channel terminated, observed on auto-reconnect");
1028
+ this.handleSessionTerminated();
1029
+ return;
1030
+ }
1031
+ if (result) {
1032
+ this.walletPresent = result.participants.includes("wallet");
1033
+ }
1005
1034
  if (this.keyExchange?.areKeysExchanged()) {
1006
1035
  if (this.walletPresent) {
1036
+ this.clearReconnectProbe();
1007
1037
  this.setStatus("connected" /* CONNECTED */);
1008
1038
  this.failedReconnects = 0;
1009
1039
  } else {
1010
1040
  this.setStatus("waiting" /* WAITING */);
1041
+ this.armReconnectProbe();
1011
1042
  }
1012
1043
  } else {
1013
1044
  this.setStatus("waiting" /* WAITING */);
@@ -1034,11 +1065,17 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1034
1065
  this.walletPresent = true;
1035
1066
  log("ConnectionManager", "Wallet joined channel");
1036
1067
  this.clearUnresponsiveTimer();
1068
+ this.clearReconnectProbe();
1037
1069
  if (this.keyExchange?.areKeysExchanged()) {
1038
1070
  this.failedReconnects = 0;
1039
1071
  this.setStatus("connected" /* CONNECTED */);
1040
1072
  }
1041
1073
  }
1074
+ if (data.event === "close") {
1075
+ log("ConnectionManager", "Wallet closed the channel (explicit terminate)");
1076
+ this.handleSessionTerminated();
1077
+ return;
1078
+ }
1042
1079
  if (data.event === "disconnect" || data.event === "leave") {
1043
1080
  if (data.clientType === "wallet" || !data.clientType) {
1044
1081
  this.walletPresent = false;
@@ -1054,6 +1091,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1054
1091
  if (!this.keyExchange) return;
1055
1092
  this.keyExchange.on("keys_exchanged", () => {
1056
1093
  log("ConnectionManager", "Key exchange complete");
1094
+ this.clearReconnectProbe();
1057
1095
  this.setStatus("connected" /* CONNECTED */);
1058
1096
  this.walletPresent = true;
1059
1097
  this.failedReconnects = 0;
@@ -1074,6 +1112,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1074
1112
  */
1075
1113
  async getConnectionURI(retryOnConflict = true) {
1076
1114
  this.setStatus("connecting" /* CONNECTING */);
1115
+ this.clearReconnectProbe();
1077
1116
  this.pendingRestore = null;
1078
1117
  this.walletPresent = false;
1079
1118
  if (this.keyExchange) {
@@ -1128,17 +1167,39 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1128
1167
  this.setStatus("disconnected" /* DISCONNECTED */);
1129
1168
  return false;
1130
1169
  }
1170
+ return this.joinAndSettle();
1171
+ }
1172
+ /**
1173
+ * (Re)open the socket, join the persisted channel, drain buffered relay
1174
+ * messages, and settle status. Shared by the cold-restore reconnect() and
1175
+ * the warm resume() path whose socket was torn down by the reconnect probe.
1176
+ * Assumes channelId and a hydrated keyExchange are already in place, and
1177
+ * that the caller has reset walletPresent + set RECONNECTING status.
1178
+ */
1179
+ async joinAndSettle() {
1131
1180
  this.socketClient.connect();
1132
1181
  try {
1133
- const { bufferedMessages } = await this.socketClient.joinChannel(this.channelId);
1182
+ const { bufferedMessages, participants, terminated } = await this.socketClient.joinChannel(
1183
+ this.channelId
1184
+ );
1185
+ if (terminated) {
1186
+ log("ConnectionManager", "Stored session was terminated by the wallet; dropping it");
1187
+ this.handleSessionTerminated();
1188
+ return false;
1189
+ }
1190
+ if (participants.includes("wallet")) {
1191
+ this.walletPresent = true;
1192
+ }
1134
1193
  for (const msg of bufferedMessages) {
1135
1194
  this.enqueueRelayMessage(msg);
1136
1195
  }
1137
1196
  await this.messageQueue;
1138
- if (this.keyExchange?.areKeysExchanged()) {
1139
- this.setStatus(this.walletPresent ? "connected" /* CONNECTED */ : "waiting" /* WAITING */);
1197
+ if (this.keyExchange?.areKeysExchanged() && this.walletPresent) {
1198
+ this.clearReconnectProbe();
1199
+ this.setStatus("connected" /* CONNECTED */);
1140
1200
  } else {
1141
1201
  this.setStatus("waiting" /* WAITING */);
1202
+ this.armReconnectProbe();
1142
1203
  }
1143
1204
  return true;
1144
1205
  } catch (err) {
@@ -1147,6 +1208,65 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1147
1208
  return false;
1148
1209
  }
1149
1210
  }
1211
+ /**
1212
+ * Nudge the connection back to life after the dApp tab / wallet app was
1213
+ * backgrounded. Idempotent and safe to call from visibilitychange / online
1214
+ * / pageshow handlers. If a restored session has not yet been hydrated,
1215
+ * runs the full reconnect(); otherwise just re-opens the socket, whose
1216
+ * own connect handler re-joins the channel and drains the relay buffer.
1217
+ */
1218
+ resume() {
1219
+ if (this.status === "connected" /* CONNECTED */) return;
1220
+ if (this.pendingRestore && !this.keyExchange?.areKeysExchanged()) {
1221
+ void this.reconnect();
1222
+ return;
1223
+ }
1224
+ if (this.channelId && this.keyExchange?.areKeysExchanged()) {
1225
+ if (this.socketClient.getChannelId()) {
1226
+ this.socketClient.connect();
1227
+ } else {
1228
+ this.setStatus("reconnecting" /* RECONNECTING */);
1229
+ this.walletPresent = false;
1230
+ void this.joinAndSettle();
1231
+ }
1232
+ }
1233
+ }
1234
+ /**
1235
+ * A session was terminated for good (wallet sent a relay 'close', or the
1236
+ * join ack reported a tombstone). Clear local state and surface
1237
+ * DISCONNECTED so the consumer drops to a fresh-pairing UI.
1238
+ */
1239
+ handleSessionTerminated() {
1240
+ this.clearReconnectProbe();
1241
+ this.clearUnresponsiveTimer();
1242
+ this.walletPresent = false;
1243
+ this.pendingRestore = null;
1244
+ this.keyExchange = null;
1245
+ this.socketClient.leaveChannel();
1246
+ this.socketClient.disconnect();
1247
+ this.clearSession();
1248
+ this.setStatus("disconnected" /* DISCONNECTED */);
1249
+ }
1250
+ armReconnectProbe() {
1251
+ this.clearReconnectProbe();
1252
+ this.reconnectProbeTimer = setTimeout(() => {
1253
+ this.reconnectProbeTimer = null;
1254
+ if (this.walletPresent) return;
1255
+ warn(
1256
+ "ConnectionManager",
1257
+ `No wallet rejoined within ${RECONNECT_WALLET_PROBE_MS}ms; treating reconnect as dead`
1258
+ );
1259
+ this.socketClient.leaveChannel();
1260
+ this.socketClient.disconnect();
1261
+ this.setStatus("disconnected" /* DISCONNECTED */);
1262
+ }, RECONNECT_WALLET_PROBE_MS);
1263
+ }
1264
+ clearReconnectProbe() {
1265
+ if (this.reconnectProbeTimer) {
1266
+ clearTimeout(this.reconnectProbeTimer);
1267
+ this.reconnectProbeTimer = null;
1268
+ }
1269
+ }
1150
1270
  /**
1151
1271
  * Send a JSON-RPC request to the wallet (async, fire-and-forget).
1152
1272
  */
@@ -1211,6 +1331,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1211
1331
  */
1212
1332
  async resetForNewChannel() {
1213
1333
  this.clearUnresponsiveTimer();
1334
+ this.clearReconnectProbe();
1214
1335
  this.walletPresent = false;
1215
1336
  await this.flushTerminate();
1216
1337
  this.socketClient.leaveChannel();
@@ -1226,6 +1347,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1226
1347
  }
1227
1348
  async disconnect() {
1228
1349
  this.clearUnresponsiveTimer();
1350
+ this.clearReconnectProbe();
1229
1351
  this.walletPresent = false;
1230
1352
  await this.flushTerminate();
1231
1353
  this.socketClient.leaveChannel();
@@ -1233,6 +1355,8 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1233
1355
  this.setStatus("disconnected" /* DISCONNECTED */);
1234
1356
  this.clearSession();
1235
1357
  this.connectedAccounts = [];
1358
+ this.pendingRestore = null;
1359
+ this.keyExchange = null;
1236
1360
  }
1237
1361
  // ── Internals ──────────────────────────────────────────────
1238
1362
  enqueueRelayMessage(data) {
@@ -1241,6 +1365,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
1241
1365
  async handleRelayMessage(data) {
1242
1366
  if (data.clientType === "wallet") {
1243
1367
  this.walletPresent = true;
1368
+ this.clearReconnectProbe();
1244
1369
  if (this.keyExchange?.areKeysExchanged() && this.status !== "connected" /* CONNECTED */) {
1245
1370
  this.failedReconnects = 0;
1246
1371
  this.setStatus("connected" /* CONNECTED */);
@@ -1430,10 +1555,29 @@ function getAppStoreUrl() {
1430
1555
 
1431
1556
  // src/QRLConnectProvider.ts
1432
1557
  var requestCounter = 0;
1558
+ var QRL_CONNECT_PROVIDER_INFO = {
1559
+ name: "MyQRLWallet (Mobile)",
1560
+ rdns: "com.qrlwallet.connect",
1561
+ icon: "data:image/png;base64,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"
1562
+ };
1563
+ var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
1564
+ var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
1565
+ function generateUuid() {
1566
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
1567
+ return globalThis.crypto.randomUUID();
1568
+ }
1569
+ const r = (n) => Math.floor(Math.random() * n);
1570
+ const hex = (n, len) => n.toString(16).padStart(len, "0");
1571
+ 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)}`;
1572
+ }
1433
1573
  var QRLConnectProvider = class extends import_eventemitter34.default {
1434
1574
  constructor(options) {
1435
1575
  super();
1436
1576
  this.pendingRequests = /* @__PURE__ */ new Map();
1577
+ this.eip6963Detail = null;
1578
+ this.eip6963RequestListener = null;
1579
+ this.resumeListener = null;
1580
+ this.resumeDebounce = null;
1437
1581
  this.isQRLConnect = true;
1438
1582
  this.options = options;
1439
1583
  if (options.debug) {
@@ -1449,6 +1593,81 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1449
1593
  if (options.autoReconnect !== false) {
1450
1594
  this.connectionManager.reconnect();
1451
1595
  }
1596
+ this.setupResumeListeners();
1597
+ if (options.announceProvider !== false) {
1598
+ this.startEip6963Announce();
1599
+ }
1600
+ }
1601
+ startEip6963Announce() {
1602
+ if (typeof window === "undefined" || typeof CustomEvent === "undefined") {
1603
+ return;
1604
+ }
1605
+ const overrides = this.options.providerInfo ?? {};
1606
+ this.eip6963Detail = Object.freeze({
1607
+ info: Object.freeze({
1608
+ uuid: generateUuid(),
1609
+ name: overrides.name ?? QRL_CONNECT_PROVIDER_INFO.name,
1610
+ icon: overrides.icon ?? QRL_CONNECT_PROVIDER_INFO.icon,
1611
+ rdns: overrides.rdns ?? QRL_CONNECT_PROVIDER_INFO.rdns
1612
+ }),
1613
+ provider: this
1614
+ });
1615
+ const announce = () => {
1616
+ if (!this.eip6963Detail) return;
1617
+ window.dispatchEvent(new CustomEvent(EIP6963_ANNOUNCE_EVENT, { detail: this.eip6963Detail }));
1618
+ };
1619
+ this.eip6963RequestListener = announce;
1620
+ window.addEventListener(EIP6963_REQUEST_EVENT, announce);
1621
+ announce();
1622
+ }
1623
+ /**
1624
+ * Re-open the relay socket when the dApp tab regains focus or the network
1625
+ * comes back. Debounced because visibilitychange + online can fire together.
1626
+ * No-op when already connected (ConnectionManager.resume guards that).
1627
+ */
1628
+ setupResumeListeners() {
1629
+ if (typeof window === "undefined" || typeof document === "undefined") return;
1630
+ if (this.resumeListener) return;
1631
+ const resume = () => {
1632
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
1633
+ if (this.resumeDebounce) clearTimeout(this.resumeDebounce);
1634
+ this.resumeDebounce = setTimeout(() => {
1635
+ this.resumeDebounce = null;
1636
+ this.connectionManager.resume();
1637
+ }, 300);
1638
+ };
1639
+ this.resumeListener = resume;
1640
+ document.addEventListener("visibilitychange", resume);
1641
+ window.addEventListener("online", resume);
1642
+ window.addEventListener("pageshow", resume);
1643
+ }
1644
+ /**
1645
+ * Remove the foreground/online resume listeners. Called on disconnect so a
1646
+ * torn-down session can't be silently revived by a later tab focus; the
1647
+ * listeners are re-armed by getConnectionURI()/newConnection() if the same
1648
+ * provider is re-paired. Safe in any env.
1649
+ */
1650
+ teardownResumeListeners() {
1651
+ if (typeof window !== "undefined" && typeof document !== "undefined" && this.resumeListener) {
1652
+ document.removeEventListener("visibilitychange", this.resumeListener);
1653
+ window.removeEventListener("online", this.resumeListener);
1654
+ window.removeEventListener("pageshow", this.resumeListener);
1655
+ }
1656
+ this.resumeListener = null;
1657
+ if (this.resumeDebounce) {
1658
+ clearTimeout(this.resumeDebounce);
1659
+ this.resumeDebounce = null;
1660
+ }
1661
+ }
1662
+ /**
1663
+ * Stop announcing this provider over EIP-6963. Safe to call from any env.
1664
+ */
1665
+ stopEip6963Announce() {
1666
+ if (typeof window !== "undefined" && this.eip6963RequestListener) {
1667
+ window.removeEventListener(EIP6963_REQUEST_EVENT, this.eip6963RequestListener);
1668
+ }
1669
+ this.eip6963RequestListener = null;
1670
+ this.eip6963Detail = null;
1452
1671
  }
1453
1672
  setupConnectionListeners() {
1454
1673
  this.connectionManager.on("status_changed", (status) => {
@@ -1506,6 +1725,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1506
1725
  * Generate a connection URI for QR code display or deep link redirect.
1507
1726
  */
1508
1727
  async getConnectionURI() {
1728
+ this.setupResumeListeners();
1509
1729
  return this.connectionManager.getConnectionURI();
1510
1730
  }
1511
1731
  /**
@@ -1627,9 +1847,366 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1627
1847
  pending.reject(new Error("Disconnected"));
1628
1848
  }
1629
1849
  this.pendingRequests.clear();
1850
+ this.teardownResumeListeners();
1630
1851
  await this.connectionManager.disconnect();
1631
1852
  }
1632
1853
  };
1854
+
1855
+ // src/signing/ctx.ts
1856
+ var SCHEME_VERSION_MSG = "QRL-SIGN-MSG-v1";
1857
+ var SCHEME_VERSION_TYPED = "QRL-SIGN-TYPED-v1";
1858
+ var SCHEME_TAG_MSG = new TextEncoder().encode(SCHEME_VERSION_MSG);
1859
+ var SCHEME_TAG_TYPED = new TextEncoder().encode(SCHEME_VERSION_TYPED);
1860
+ var DIGEST_LEN = 64;
1861
+
1862
+ // src/signing/messageDigest.ts
1863
+ var import_sha3 = require("@noble/hashes/sha3.js");
1864
+
1865
+ // src/signing/bytes.ts
1866
+ function hexToBytes(hex) {
1867
+ if (typeof hex !== "string") throw new Error("hex must be a string");
1868
+ if (!/^0x([0-9a-fA-F]{2})*$/.test(hex)) {
1869
+ throw new Error("hex must be 0x-prefixed even-length");
1870
+ }
1871
+ const n = (hex.length - 2) / 2;
1872
+ const out = new Uint8Array(n);
1873
+ for (let i = 0; i < n; i++) {
1874
+ out[i] = parseInt(hex.slice(2 + i * 2, 4 + i * 2), 16);
1875
+ }
1876
+ return out;
1877
+ }
1878
+ function bytesToHex(bytes) {
1879
+ if (!(bytes instanceof Uint8Array)) {
1880
+ throw new Error("bytes must be a Uint8Array");
1881
+ }
1882
+ let s = "0x";
1883
+ for (const v of bytes) s += v.toString(16).padStart(2, "0");
1884
+ return s;
1885
+ }
1886
+ function concatBytes(...parts) {
1887
+ return concatBytesArr(parts);
1888
+ }
1889
+ function concatBytesArr(parts) {
1890
+ let total = 0;
1891
+ for (const p of parts) total += p.length;
1892
+ const out = new Uint8Array(total);
1893
+ let off = 0;
1894
+ for (const p of parts) {
1895
+ out.set(p, off);
1896
+ off += p.length;
1897
+ }
1898
+ return out;
1899
+ }
1900
+
1901
+ // src/signing/messageDigest.ts
1902
+ function computeMessageDigest(messageBytes) {
1903
+ if (!(messageBytes instanceof Uint8Array)) {
1904
+ throw new Error("messageBytes must be a Uint8Array");
1905
+ }
1906
+ return (0, import_sha3.shake256)(concatBytes(SCHEME_TAG_MSG, messageBytes), { dkLen: DIGEST_LEN });
1907
+ }
1908
+
1909
+ // src/signing/typedData.ts
1910
+ var import_sha32 = require("@noble/hashes/sha3.js");
1911
+ var SLOT = 32;
1912
+ var ATOMIC_RE = /^(?:(address|bool|string|bytes)|(u?int)(\d+)|bytes(\d+)|(.+?)\[(\d*)\])$/;
1913
+ var MAX_TYPE_DEPTH = 12;
1914
+ function parseFieldType(type, types, depth = 0) {
1915
+ if (depth > MAX_TYPE_DEPTH) throw new Error(`type nesting too deep: ${type}`);
1916
+ if (Object.prototype.hasOwnProperty.call(types, type)) {
1917
+ return { kind: "ref", name: type };
1918
+ }
1919
+ const m = ATOMIC_RE.exec(type);
1920
+ if (!m) throw new Error(`unknown type: ${type}`);
1921
+ const [, atomic, intKind, intWidthStr, bytesWidthStr, innerType, sizeStr] = m;
1922
+ if (atomic) return { kind: atomic };
1923
+ if (intKind) {
1924
+ const width = Number(intWidthStr);
1925
+ if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
1926
+ throw new Error(`invalid int width: ${type}`);
1927
+ }
1928
+ return { kind: intKind === "uint" ? "uintN" : "intN", width };
1929
+ }
1930
+ if (bytesWidthStr) {
1931
+ const width = Number(bytesWidthStr);
1932
+ if (!Number.isInteger(width) || width < 1 || width > 32) {
1933
+ throw new Error(`invalid bytesN width: ${type}`);
1934
+ }
1935
+ return { kind: "bytesN", width };
1936
+ }
1937
+ if (innerType !== void 0) {
1938
+ parseFieldType(innerType, types, depth + 1);
1939
+ const size = sizeStr ? Number(sizeStr) : void 0;
1940
+ if (sizeStr && (!Number.isInteger(size) || size <= 0)) {
1941
+ throw new Error(`invalid array size: ${type}`);
1942
+ }
1943
+ return { kind: "array", inner: innerType, size };
1944
+ }
1945
+ throw new Error(`unhandled type: ${type}`);
1946
+ }
1947
+ function baseTypeName(type) {
1948
+ return type.replace(/(\[\d*\])+$/, "");
1949
+ }
1950
+ function collectDependencies(primary, types) {
1951
+ if (!Object.prototype.hasOwnProperty.call(types, primary)) {
1952
+ throw new Error(`unknown primary type: ${primary}`);
1953
+ }
1954
+ const visited = /* @__PURE__ */ new Set();
1955
+ const visit = (name, path) => {
1956
+ if (path.includes(name)) {
1957
+ throw new Error(`cyclic type reference: ${[...path, name].join(" -> ")}`);
1958
+ }
1959
+ if (visited.has(name)) return;
1960
+ visited.add(name);
1961
+ for (const f of types[name]) {
1962
+ const base = baseTypeName(f.type);
1963
+ if (Object.prototype.hasOwnProperty.call(types, base)) {
1964
+ visit(base, [...path, name]);
1965
+ }
1966
+ parseFieldType(f.type, types);
1967
+ }
1968
+ };
1969
+ visit(primary, []);
1970
+ return visited;
1971
+ }
1972
+ function validateTypeMap(types) {
1973
+ for (const [name, def] of Object.entries(types)) {
1974
+ if (!Array.isArray(def) || def.length === 0) {
1975
+ throw new Error(`empty or invalid struct: ${name}`);
1976
+ }
1977
+ const seen = /* @__PURE__ */ new Set();
1978
+ for (const f of def) {
1979
+ if (!f || typeof f.name !== "string" || !f.name) {
1980
+ throw new Error(`bad field in ${name}`);
1981
+ }
1982
+ if (typeof f.type !== "string" || !f.type) {
1983
+ throw new Error(`bad field type in ${name}.${f.name}`);
1984
+ }
1985
+ if (seen.has(f.name)) {
1986
+ throw new Error(`duplicate field "${f.name}" in ${name}`);
1987
+ }
1988
+ seen.add(f.name);
1989
+ }
1990
+ }
1991
+ }
1992
+ function encodeType(primary, types) {
1993
+ const deps = collectDependencies(primary, types);
1994
+ const others = [...deps].filter((n) => n !== primary).sort();
1995
+ return [primary, ...others].map((name) => {
1996
+ const fields = types[name];
1997
+ const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
1998
+ return `${name}(${inner})`;
1999
+ }).join("");
2000
+ }
2001
+ function typeHash(primary, types) {
2002
+ return (0, import_sha32.shake256)(new TextEncoder().encode(encodeType(primary, types)), { dkLen: DIGEST_LEN });
2003
+ }
2004
+ function parseQAddress(addr) {
2005
+ if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
2006
+ throw new Error(`invalid Q-address: ${String(addr)}`);
2007
+ }
2008
+ return hexToBytes("0x" + addr.slice(1).toLowerCase());
2009
+ }
2010
+ function padLeft32(bytes) {
2011
+ if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
2012
+ const out = new Uint8Array(SLOT);
2013
+ out.set(bytes, SLOT - bytes.length);
2014
+ return out;
2015
+ }
2016
+ function padRight32(bytes) {
2017
+ if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
2018
+ const out = new Uint8Array(SLOT);
2019
+ out.set(bytes, 0);
2020
+ return out;
2021
+ }
2022
+ function bigIntToSlot(value, width, signed) {
2023
+ if (signed) {
2024
+ const limit = 1n << BigInt(width - 1);
2025
+ if (value >= limit || value < -limit) {
2026
+ throw new Error(`int${width} out of range: ${value}`);
2027
+ }
2028
+ } else {
2029
+ if (value < 0n) throw new Error(`uint${width} negative: ${value}`);
2030
+ const limit = 1n << BigInt(width);
2031
+ if (value >= limit) throw new Error(`uint${width} out of range: ${value}`);
2032
+ }
2033
+ const slotMask = (1n << 256n) - 1n;
2034
+ const repr = value < 0n ? value + (1n << 256n) & slotMask : value;
2035
+ const out = new Uint8Array(SLOT);
2036
+ let v = repr;
2037
+ for (let i = SLOT - 1; i >= 0; i--) {
2038
+ out[i] = Number(v & 0xffn);
2039
+ v >>= 8n;
2040
+ }
2041
+ return out;
2042
+ }
2043
+ function parseIntValue(v, typeLabel) {
2044
+ if (typeof v === "bigint") return v;
2045
+ if (typeof v === "string") {
2046
+ if (/^-?0x[0-9a-fA-F]+$/i.test(v)) {
2047
+ const isNegative = v.startsWith("-");
2048
+ const abs = BigInt(isNegative ? v.slice(1) : v);
2049
+ return isNegative ? -abs : abs;
2050
+ }
2051
+ if (!/^-?(0|[1-9]\d*)$/.test(v)) {
2052
+ throw new Error(`invalid ${typeLabel} string: ${v}`);
2053
+ }
2054
+ return BigInt(v);
2055
+ }
2056
+ if (typeof v === "number") {
2057
+ if (!Number.isInteger(v) || !Number.isSafeInteger(v)) {
2058
+ throw new Error(`unsafe ${typeLabel} number: ${v}, pass as string`);
2059
+ }
2060
+ return BigInt(v);
2061
+ }
2062
+ throw new Error(`unsupported ${typeLabel} value: ${typeof v}`);
2063
+ }
2064
+ function encodeField(type, value, types) {
2065
+ const parsed = parseFieldType(type, types);
2066
+ switch (parsed.kind) {
2067
+ case "ref":
2068
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2069
+ throw new Error(`struct field expects object: ${type}`);
2070
+ }
2071
+ return hashStruct(parsed.name, value, types);
2072
+ case "address":
2073
+ if (typeof value !== "string") {
2074
+ throw new Error(`address field expects string: ${typeof value}`);
2075
+ }
2076
+ return padLeft32(parseQAddress(value));
2077
+ case "bool":
2078
+ if (typeof value !== "boolean") {
2079
+ throw new Error(`bool field expects boolean: ${typeof value}`);
2080
+ }
2081
+ return padLeft32(new Uint8Array([value ? 1 : 0]));
2082
+ case "string":
2083
+ if (typeof value !== "string") {
2084
+ throw new Error(`string field expects string: ${typeof value}`);
2085
+ }
2086
+ return (0, import_sha32.shake256)(new TextEncoder().encode(value), { dkLen: DIGEST_LEN });
2087
+ case "bytes":
2088
+ if (typeof value !== "string") {
2089
+ throw new Error(`bytes field expects 0x-hex string: ${typeof value}`);
2090
+ }
2091
+ return (0, import_sha32.shake256)(hexToBytes(value), { dkLen: DIGEST_LEN });
2092
+ case "uintN":
2093
+ return bigIntToSlot(parseIntValue(value, `uint${parsed.width}`), parsed.width, false);
2094
+ case "intN":
2095
+ return bigIntToSlot(parseIntValue(value, `int${parsed.width}`), parsed.width, true);
2096
+ case "bytesN": {
2097
+ if (typeof value !== "string") {
2098
+ throw new Error(`bytes${parsed.width} expects 0x-hex string: ${typeof value}`);
2099
+ }
2100
+ const raw = hexToBytes(value);
2101
+ if (raw.length !== parsed.width) {
2102
+ throw new Error(`bytes${parsed.width} requires ${parsed.width} bytes, got ${raw.length}`);
2103
+ }
2104
+ return padRight32(raw);
2105
+ }
2106
+ case "array": {
2107
+ if (!Array.isArray(value)) {
2108
+ throw new Error(`array field expects array: ${typeof value}`);
2109
+ }
2110
+ if (parsed.size !== void 0 && value.length !== parsed.size) {
2111
+ throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
2112
+ }
2113
+ const chunks = value.map((v) => encodeField(parsed.inner, v, types));
2114
+ return (0, import_sha32.shake256)(concatBytesArr(chunks), { dkLen: DIGEST_LEN });
2115
+ }
2116
+ }
2117
+ }
2118
+ function hashStruct(primary, data, types) {
2119
+ const fields = types[primary];
2120
+ if (!fields) throw new Error(`unknown struct: ${primary}`);
2121
+ const expected = new Set(fields.map((f) => f.name));
2122
+ for (const k of Object.keys(data)) {
2123
+ if (!expected.has(k)) throw new Error(`unknown field in ${primary}: ${k}`);
2124
+ }
2125
+ const parts = [typeHash(primary, types)];
2126
+ for (const f of fields) {
2127
+ if (!(f.name in data)) throw new Error(`missing field ${primary}.${f.name}`);
2128
+ parts.push(encodeField(f.type, data[f.name], types));
2129
+ }
2130
+ return (0, import_sha32.shake256)(concatBytesArr(parts), { dkLen: DIGEST_LEN });
2131
+ }
2132
+ var RESERVED_DOMAIN_FIELDS = {
2133
+ name: "string",
2134
+ version: "string",
2135
+ chainId: "uint256",
2136
+ verifyingContract: "address",
2137
+ salt: "bytes32"
2138
+ };
2139
+ function validateDomainTypes(types) {
2140
+ const def = types.QRLDomain;
2141
+ if (!def) throw new Error("QRLDomain type is required");
2142
+ let hasName = false;
2143
+ for (const f of def) {
2144
+ const expected = RESERVED_DOMAIN_FIELDS[f.name];
2145
+ if (!expected) {
2146
+ throw new Error(
2147
+ `QRLDomain field "${f.name}" not in reserved set (name, version, chainId, verifyingContract, salt)`
2148
+ );
2149
+ }
2150
+ if (f.type !== expected) {
2151
+ throw new Error(`QRLDomain field "${f.name}" must be type "${expected}", got "${f.type}"`);
2152
+ }
2153
+ if (f.name === "name") hasName = true;
2154
+ }
2155
+ if (!hasName) throw new Error("QRLDomain.name is required");
2156
+ }
2157
+ function validatePayloadReachability(primary, types) {
2158
+ const reachable = /* @__PURE__ */ new Set();
2159
+ for (const root of ["QRLDomain", primary]) {
2160
+ for (const t of collectDependencies(root, types)) reachable.add(t);
2161
+ }
2162
+ for (const k of Object.keys(types)) {
2163
+ if (!reachable.has(k)) throw new Error(`unused referenced type: ${k}`);
2164
+ }
2165
+ }
2166
+ function computeTypedDataDigest(payload) {
2167
+ if (!payload || typeof payload !== "object") {
2168
+ throw new Error("invalid typed data payload");
2169
+ }
2170
+ validateTypeMap(payload.types);
2171
+ validateDomainTypes(payload.types);
2172
+ validatePayloadReachability(payload.primaryType, payload.types);
2173
+ const domainHash = hashStruct("QRLDomain", payload.domain, payload.types);
2174
+ const messageHash = hashStruct(payload.primaryType, payload.message, payload.types);
2175
+ return (0, import_sha32.shake256)(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), { dkLen: DIGEST_LEN });
2176
+ }
2177
+
2178
+ // src/signing/verify.ts
2179
+ var mldsa = __toESM(require("@theqrl/mldsa87"));
2180
+ function bytesOrHex(v) {
2181
+ if (v instanceof Uint8Array) return v;
2182
+ if (typeof v === "string") return hexToBytes(v);
2183
+ throw new Error("expected Uint8Array or 0x-hex string");
2184
+ }
2185
+ function verifyMessage({
2186
+ signature,
2187
+ publicKey,
2188
+ messageBytes
2189
+ }) {
2190
+ try {
2191
+ const sig = bytesOrHex(signature);
2192
+ const pk = bytesOrHex(publicKey);
2193
+ const msg = bytesOrHex(messageBytes);
2194
+ const digest = computeMessageDigest(msg);
2195
+ return mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_MSG);
2196
+ } catch {
2197
+ return false;
2198
+ }
2199
+ }
2200
+ function verifyTypedData({ signature, publicKey, payload }) {
2201
+ try {
2202
+ const sig = bytesOrHex(signature);
2203
+ const pk = bytesOrHex(publicKey);
2204
+ const digest = computeTypedDataDigest(payload);
2205
+ return mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2206
+ } catch {
2207
+ return false;
2208
+ }
2209
+ }
1633
2210
  // Annotate the CommonJS export names for ESM import in node:
1634
2211
  0 && (module.exports = {
1635
2212
  BLOB_LEN,
@@ -1637,6 +2214,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1637
2214
  ConnectionManager,
1638
2215
  ConnectionStatus,
1639
2216
  DEFAULT_RELAY_URL,
2217
+ DIGEST_LEN,
1640
2218
  FP_LEN,
1641
2219
  KeyExchange,
1642
2220
  KeyExchangeMessageType,
@@ -1644,16 +2222,32 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
1644
2222
  PROTOCOL_VERSION,
1645
2223
  QRLConnect,
1646
2224
  QRLConnectProvider,
2225
+ QRL_CONNECT_PROVIDER_INFO,
1647
2226
  RESTRICTED_METHODS,
2227
+ SCHEME_TAG_MSG,
2228
+ SCHEME_TAG_TYPED,
2229
+ SCHEME_VERSION_MSG,
2230
+ SCHEME_VERSION_TYPED,
1648
2231
  SocketClient,
1649
2232
  UNRESTRICTED_METHODS,
2233
+ bytesToHex,
1650
2234
  cidFromString,
1651
2235
  cidRandom,
1652
2236
  cidToString,
1653
2237
  computeFingerprint,
2238
+ computeMessageDigest,
2239
+ computeTypedDataDigest,
2240
+ concatBytes,
2241
+ encodeField,
2242
+ encodeType,
1654
2243
  fingerprintEquals,
1655
2244
  generateConnectionURI,
1656
2245
  getAppStoreUrl,
2246
+ hashStruct,
2247
+ hexToBytes,
1657
2248
  isMobileBrowser,
1658
- parseConnectionURI
2249
+ parseConnectionURI,
2250
+ typeHash,
2251
+ verifyMessage,
2252
+ verifyTypedData
1659
2253
  });