@qrlwallet/connect 4.0.0 → 5.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/README.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Connect your dApp to QRL Wallet. Users scan a QR code (desktop) or tap a button (mobile) to pair their wallet, then approve transactions right from the app.
4
4
 
5
+ ## Version 5 migration
6
+
7
+ Version 5 requires QIP-55 addresses: `Q` followed by 128 hexadecimal characters.
8
+ Account validation and message-signature verification use the complete 64-byte
9
+ address. Applications using legacy 20-byte addresses must remain on version 4
10
+ until their network and wallet support QIP-55.
11
+
12
+ Wallet typed-data requests are disabled pending a versioned 64-byte address
13
+ encoding. The local verifier remains available for address-free typed data.
14
+ The private v3 network is a test network; its availability does not establish
15
+ compatibility with legacy deployments or contracts.
16
+
5
17
  ## How it works
6
18
 
7
19
  1. Your dApp generates a connection URI and shows it as a QR code
@@ -78,7 +90,7 @@ const txHash = await qrl.request({
78
90
  params: [
79
91
  {
80
92
  from: accounts[0],
81
- to: 'Q20E7Bde67f00EA38ABb2aC57e1B0DD93f518446c',
93
+ to: 'Q22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222',
82
94
  value: '0x2386F26FC10000', // 0.01 QRL
83
95
  },
84
96
  ],
@@ -161,15 +173,16 @@ The main class. Creates a connection manager and EIP-1193 provider.
161
173
  **Require user approval:**
162
174
  `qrl_requestAccounts`, `qrl_sendTransaction`, `qrl_signTransaction`, `qrl_signMessage`, `qrl_signTypedData`, `wallet_switchQrlChain`
163
175
 
164
- `qrl_signMessage` and `qrl_signTypedData` (v3.0.0) replace the Ethereum-flavored `personal_sign` / `qrl_sign` / `qrl_signTypedData_v3` / `qrl_signTypedData_v4`. Both use SHAKE256 + native ML-DSA-87 ctx and return a rich `{ signature, publicKey, descriptor, signer, digest, schemeVersion }` object.
176
+ `qrl_signMessage` uses SHAKE256 plus native ML-DSA-87 ctx and returns a rich `{ signature, publicKey, descriptor, signer, digest, schemeVersion }` object. During the QIP-55 port, `qrl_signTypedData` stays in the approval policy and fails locally before relay use. Its v1 encoding fixes addresses to a 32-byte slot, so a new 64-byte encoding needs a new scheme version and regenerated wallet parity vectors.
165
177
 
166
178
  `request()` returns `unknown`. First validate the exact response shape with
167
179
  `isQrlSignedMessageResult` or `isQrlSignedTypedDataResult`, then require
168
180
  `hasSigningDescriptor`. PQP3 authenticates the paired transport session. It
169
181
  does not prove that the returned ML-DSA signature is valid or that its public
170
182
  key belongs to the claimed signer. Authentication and authorization flows must
171
- call `verifyMessageForSigner` or `verifyTypedDataForSigner` with the original
172
- challenge or payload and the expected current Q + 40 hex address.
183
+ call `verifyMessageForSigner` or the historical v1 `verifyTypedDataForSigner`
184
+ with the original challenge or payload and the expected QIP-55 Q + 128 hex
185
+ address.
173
186
 
174
187
  The lower-level `verifyMessageSignature` / `verifyTypedDataSignature` helpers
175
188
  verify only the supplied key and signature; they do not prove that the key
@@ -195,6 +208,18 @@ field must exactly match that approved address.
195
208
  An account string is not proof of control. For authentication, request a fresh
196
209
  challenge with `qrl_signMessage` and verify it with `verifyMessageForSigner`.
197
210
 
211
+ ## Address display
212
+
213
+ Use `formatQrlAddressFingerprint(address)` for compact QRL account labels. It
214
+ preserves checksum case and shows the first, exact middle, and final 8 hex
215
+ characters. Keep the complete raw address in links, clipboard values, QR codes,
216
+ RPC requests, and signing payloads.
217
+
218
+ ```typescript
219
+ const label = formatQrlAddressFingerprint(account);
220
+ // Q11111111...33333333...55555555
221
+ ```
222
+
198
223
  ## Sessions
199
224
 
200
225
  Established v5 sessions persist in `localStorage` for 7 days. When a user
package/dist/index.d.mts CHANGED
@@ -269,10 +269,10 @@ interface EIP6963ProviderInfoOverride {
269
269
  rdns?: string;
270
270
  }
271
271
  /**
272
- * Rich response object returned by both `qrl_signMessage` and
273
- * `qrl_signTypedData`. The signature alone is not enough to verify since
274
- * ML-DSA-87 public keys cannot be recovered from a signature, so the
275
- * wallet always returns the public key explicitly. Stateless verifiers need
272
+ * Rich response object returned by `qrl_signMessage` and retained for locked
273
+ * `qrl_signTypedData` v1 fixtures. The signature alone is not enough to
274
+ * verify since ML-DSA-87 public keys cannot be recovered from a signature,
275
+ * so the wallet returns the public key explicitly. Stateless verifiers need
276
276
  * the original payload plus the signature fields below.
277
277
  */
278
278
  interface QrlSignedResult {
@@ -286,7 +286,7 @@ interface QrlSignedResult {
286
286
  * requires it.
287
287
  */
288
288
  descriptor?: string;
289
- /** Current 41-character checksummed Q-address derived from descriptor + key. */
289
+ /** QIP-55 129-character checksummed Q-address derived from descriptor + key. */
290
290
  signer: string;
291
291
  /** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
292
292
  digest: string;
@@ -303,8 +303,8 @@ interface QrlSignedMessageResult extends QrlSignedResult {
303
303
  schemeVersion: 'QRL-SIGN-MSG-v1';
304
304
  }
305
305
  /**
306
- * Response from `qrl_signTypedData`. Echoes `domain` so a stateless
307
- * verifier doesn't need to be told the domain out-of-band.
306
+ * Historical v1 response from `qrl_signTypedData`. It echoes `domain` so a
307
+ * stateless verifier does not need to be told the domain out-of-band.
308
308
  */
309
309
  interface QrlSignedTypedDataResult extends QrlSignedResult {
310
310
  schemeVersion: 'QRL-SIGN-TYPED-v1';
@@ -338,10 +338,10 @@ declare function hasSigningDescriptor(result: unknown): result is QrlSigningResu
338
338
  */
339
339
  type QrlSignMessageParams = [string, string];
340
340
  /**
341
- * `qrl_signTypedData` params: `[signer, payload]`. Payload mirrors EIP-712
342
- * shape: `{ types, primaryType, domain, message }`, but with `QRLDomain`
343
- * in place of `EIP712Domain` and SHAKE256-based hashing. See `signing/`
344
- * for the full encoder.
341
+ * Reserved `qrl_signTypedData` params: `[signer, payload]`. This interface
342
+ * describes the historical v1 payload retained by the verifier exports. The
343
+ * QIP-55 provider rejects requests until a versioned 64-byte encoding is
344
+ * defined.
345
345
  */
346
346
  interface QrlTypedDataPayload {
347
347
  types: Record<string, readonly {
@@ -887,13 +887,17 @@ declare const DIGEST_LEN = 64;
887
887
  declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
888
888
 
889
889
  /**
890
- * EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
890
+ * Historical EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
891
891
  *
892
892
  * Byte-identical algorithm with the wallet's signing/typedData.ts; this
893
893
  * file is the SDK port (only the import block differs: the SDK routes
894
894
  * SHAKE256 through src/crypto/primitives.ts, its crypto boundary). See
895
895
  * the wallet copy for the spec-level comments; behavior must stay in
896
896
  * lock-step or the cross-repo parity test fails on next CI.
897
+ *
898
+ * The v1 address slot is 32 bytes. QIP-55 address fields fail closed until a
899
+ * 64-byte encoding receives a new scheme tag and lockstep wallet fixtures.
900
+ * Address-free digests retain their existing encoding.
897
901
  */
898
902
  declare const TYPED_DATA_LIMITS: Readonly<{
899
903
  maxTypes: 32;
@@ -965,7 +969,7 @@ interface VerifyTypedDataParams {
965
969
  */
966
970
  declare function verifyTypedDataSignature({ signature, publicKey, payload, }: VerifyTypedDataParams): boolean;
967
971
  interface VerifyMessageForSignerParams extends VerifyMessageParams {
968
- /** Current Q + 40 hex address expected by the dApp. */
972
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
969
973
  expectedSigner: string;
970
974
  /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
971
975
  descriptor?: Uint8Array | string | undefined;
@@ -973,7 +977,7 @@ interface VerifyMessageForSignerParams extends VerifyMessageParams {
973
977
  /** Verify both the message signature and public-key-to-signer binding. */
974
978
  declare function verifyMessageForSigner({ expectedSigner, descriptor, signature, publicKey, messageBytes, }: VerifyMessageForSignerParams): boolean;
975
979
  interface VerifyTypedDataForSignerParams extends VerifyTypedDataParams {
976
- /** Current Q + 40 hex address expected by the dApp. */
980
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
977
981
  expectedSigner: string;
978
982
  /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
979
983
  descriptor?: Uint8Array | string | undefined;
@@ -1093,6 +1097,19 @@ declare function getAppStoreUrl(): string;
1093
1097
  */
1094
1098
  declare function attemptWalletRedirect(uri: string, timeoutMs?: number): Promise<boolean>;
1095
1099
 
1100
+ declare const QRL_ADDRESS_BYTES = 64;
1101
+ declare const QRL_ADDRESS_HEX_LENGTH: number;
1102
+ /**
1103
+ * Validate the QRL 2.0 text form. The prefix is the network's uppercase Q.
1104
+ * A case-uniform hex body is accepted for compatibility. Mixed-case input
1105
+ * must carry the canonical SHAKE256 QIP-55 checksum.
1106
+ */
1107
+ declare function isCurrentQrlAddress(value: unknown): value is string;
1108
+ /** Stable visual fingerprint for long QRL addresses. */
1109
+ declare function formatQrlAddressFingerprint(address: string): string;
1110
+ /** Format exactly 64 address bytes as the canonical mixed-case QIP-55 form. */
1111
+ declare function qip55AddressFromBytes(address: Uint8Array): string;
1112
+
1096
1113
  declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
1097
1114
  declare const PROTOCOL_VERSION = 3;
1098
1115
  declare const PAIRING_CAPABILITY_LEN = 32;
@@ -1118,4 +1135,4 @@ declare function isValidJsonRpcMethod(value: unknown): value is string;
1118
1135
  */
1119
1136
  declare function classifyRpcMethod(method: string): RpcMethodPolicy;
1120
1137
 
1121
- export { type AckMessage, BLOB_LEN, CAP_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, EXPLICITLY_UNSUPPORTED_METHODS, FP_LEN, type InitiatedKeyExchange, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, MAX_CONNECTION_URI_LENGTH, MAX_JSON_RPC_ID_LENGTH, MAX_JSON_RPC_METHOD_LENGTH, MAX_RELAY_URL_LENGTH, ML_DSA_87_PUBLIC_KEY_BYTES, ML_DSA_87_SIGNATURE_BYTES, ML_DSA_DESCRIPTOR_BYTES, type Message, MessageType, PAIRING_CAPABILITY_LEN, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedMessageResult, type QrlSignedResult, type QrlSignedResultWithDescriptor, type QrlSignedTypedDataResult, type QrlSigningResult, type QrlSigningSchemeVersion, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, type RpcMethodPolicy, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, TYPED_DATA_LIMITS, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageForSignerParams, type VerifyMessageParams, type VerifyTypedDataForSignerParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, classifyRpcMethod, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
1138
+ export { type AckMessage, BLOB_LEN, CAP_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, EXPLICITLY_UNSUPPORTED_METHODS, FP_LEN, type InitiatedKeyExchange, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, MAX_CONNECTION_URI_LENGTH, MAX_JSON_RPC_ID_LENGTH, MAX_JSON_RPC_METHOD_LENGTH, MAX_RELAY_URL_LENGTH, ML_DSA_87_PUBLIC_KEY_BYTES, ML_DSA_87_SIGNATURE_BYTES, ML_DSA_DESCRIPTOR_BYTES, type Message, MessageType, PAIRING_CAPABILITY_LEN, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_ADDRESS_BYTES, QRL_ADDRESS_HEX_LENGTH, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedMessageResult, type QrlSignedResult, type QrlSignedResultWithDescriptor, type QrlSignedTypedDataResult, type QrlSigningResult, type QrlSigningSchemeVersion, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, type RpcMethodPolicy, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, TYPED_DATA_LIMITS, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageForSignerParams, type VerifyMessageParams, type VerifyTypedDataForSignerParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, classifyRpcMethod, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, formatQrlAddressFingerprint, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isCurrentQrlAddress, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, qip55AddressFromBytes, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
package/dist/index.d.ts CHANGED
@@ -269,10 +269,10 @@ interface EIP6963ProviderInfoOverride {
269
269
  rdns?: string;
270
270
  }
271
271
  /**
272
- * Rich response object returned by both `qrl_signMessage` and
273
- * `qrl_signTypedData`. The signature alone is not enough to verify since
274
- * ML-DSA-87 public keys cannot be recovered from a signature, so the
275
- * wallet always returns the public key explicitly. Stateless verifiers need
272
+ * Rich response object returned by `qrl_signMessage` and retained for locked
273
+ * `qrl_signTypedData` v1 fixtures. The signature alone is not enough to
274
+ * verify since ML-DSA-87 public keys cannot be recovered from a signature,
275
+ * so the wallet returns the public key explicitly. Stateless verifiers need
276
276
  * the original payload plus the signature fields below.
277
277
  */
278
278
  interface QrlSignedResult {
@@ -286,7 +286,7 @@ interface QrlSignedResult {
286
286
  * requires it.
287
287
  */
288
288
  descriptor?: string;
289
- /** Current 41-character checksummed Q-address derived from descriptor + key. */
289
+ /** QIP-55 129-character checksummed Q-address derived from descriptor + key. */
290
290
  signer: string;
291
291
  /** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
292
292
  digest: string;
@@ -303,8 +303,8 @@ interface QrlSignedMessageResult extends QrlSignedResult {
303
303
  schemeVersion: 'QRL-SIGN-MSG-v1';
304
304
  }
305
305
  /**
306
- * Response from `qrl_signTypedData`. Echoes `domain` so a stateless
307
- * verifier doesn't need to be told the domain out-of-band.
306
+ * Historical v1 response from `qrl_signTypedData`. It echoes `domain` so a
307
+ * stateless verifier does not need to be told the domain out-of-band.
308
308
  */
309
309
  interface QrlSignedTypedDataResult extends QrlSignedResult {
310
310
  schemeVersion: 'QRL-SIGN-TYPED-v1';
@@ -338,10 +338,10 @@ declare function hasSigningDescriptor(result: unknown): result is QrlSigningResu
338
338
  */
339
339
  type QrlSignMessageParams = [string, string];
340
340
  /**
341
- * `qrl_signTypedData` params: `[signer, payload]`. Payload mirrors EIP-712
342
- * shape: `{ types, primaryType, domain, message }`, but with `QRLDomain`
343
- * in place of `EIP712Domain` and SHAKE256-based hashing. See `signing/`
344
- * for the full encoder.
341
+ * Reserved `qrl_signTypedData` params: `[signer, payload]`. This interface
342
+ * describes the historical v1 payload retained by the verifier exports. The
343
+ * QIP-55 provider rejects requests until a versioned 64-byte encoding is
344
+ * defined.
345
345
  */
346
346
  interface QrlTypedDataPayload {
347
347
  types: Record<string, readonly {
@@ -887,13 +887,17 @@ declare const DIGEST_LEN = 64;
887
887
  declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
888
888
 
889
889
  /**
890
- * EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
890
+ * Historical EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
891
891
  *
892
892
  * Byte-identical algorithm with the wallet's signing/typedData.ts; this
893
893
  * file is the SDK port (only the import block differs: the SDK routes
894
894
  * SHAKE256 through src/crypto/primitives.ts, its crypto boundary). See
895
895
  * the wallet copy for the spec-level comments; behavior must stay in
896
896
  * lock-step or the cross-repo parity test fails on next CI.
897
+ *
898
+ * The v1 address slot is 32 bytes. QIP-55 address fields fail closed until a
899
+ * 64-byte encoding receives a new scheme tag and lockstep wallet fixtures.
900
+ * Address-free digests retain their existing encoding.
897
901
  */
898
902
  declare const TYPED_DATA_LIMITS: Readonly<{
899
903
  maxTypes: 32;
@@ -965,7 +969,7 @@ interface VerifyTypedDataParams {
965
969
  */
966
970
  declare function verifyTypedDataSignature({ signature, publicKey, payload, }: VerifyTypedDataParams): boolean;
967
971
  interface VerifyMessageForSignerParams extends VerifyMessageParams {
968
- /** Current Q + 40 hex address expected by the dApp. */
972
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
969
973
  expectedSigner: string;
970
974
  /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
971
975
  descriptor?: Uint8Array | string | undefined;
@@ -973,7 +977,7 @@ interface VerifyMessageForSignerParams extends VerifyMessageParams {
973
977
  /** Verify both the message signature and public-key-to-signer binding. */
974
978
  declare function verifyMessageForSigner({ expectedSigner, descriptor, signature, publicKey, messageBytes, }: VerifyMessageForSignerParams): boolean;
975
979
  interface VerifyTypedDataForSignerParams extends VerifyTypedDataParams {
976
- /** Current Q + 40 hex address expected by the dApp. */
980
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
977
981
  expectedSigner: string;
978
982
  /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
979
983
  descriptor?: Uint8Array | string | undefined;
@@ -1093,6 +1097,19 @@ declare function getAppStoreUrl(): string;
1093
1097
  */
1094
1098
  declare function attemptWalletRedirect(uri: string, timeoutMs?: number): Promise<boolean>;
1095
1099
 
1100
+ declare const QRL_ADDRESS_BYTES = 64;
1101
+ declare const QRL_ADDRESS_HEX_LENGTH: number;
1102
+ /**
1103
+ * Validate the QRL 2.0 text form. The prefix is the network's uppercase Q.
1104
+ * A case-uniform hex body is accepted for compatibility. Mixed-case input
1105
+ * must carry the canonical SHAKE256 QIP-55 checksum.
1106
+ */
1107
+ declare function isCurrentQrlAddress(value: unknown): value is string;
1108
+ /** Stable visual fingerprint for long QRL addresses. */
1109
+ declare function formatQrlAddressFingerprint(address: string): string;
1110
+ /** Format exactly 64 address bytes as the canonical mixed-case QIP-55 form. */
1111
+ declare function qip55AddressFromBytes(address: Uint8Array): string;
1112
+
1096
1113
  declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
1097
1114
  declare const PROTOCOL_VERSION = 3;
1098
1115
  declare const PAIRING_CAPABILITY_LEN = 32;
@@ -1118,4 +1135,4 @@ declare function isValidJsonRpcMethod(value: unknown): value is string;
1118
1135
  */
1119
1136
  declare function classifyRpcMethod(method: string): RpcMethodPolicy;
1120
1137
 
1121
- export { type AckMessage, BLOB_LEN, CAP_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, EXPLICITLY_UNSUPPORTED_METHODS, FP_LEN, type InitiatedKeyExchange, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, MAX_CONNECTION_URI_LENGTH, MAX_JSON_RPC_ID_LENGTH, MAX_JSON_RPC_METHOD_LENGTH, MAX_RELAY_URL_LENGTH, ML_DSA_87_PUBLIC_KEY_BYTES, ML_DSA_87_SIGNATURE_BYTES, ML_DSA_DESCRIPTOR_BYTES, type Message, MessageType, PAIRING_CAPABILITY_LEN, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedMessageResult, type QrlSignedResult, type QrlSignedResultWithDescriptor, type QrlSignedTypedDataResult, type QrlSigningResult, type QrlSigningSchemeVersion, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, type RpcMethodPolicy, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, TYPED_DATA_LIMITS, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageForSignerParams, type VerifyMessageParams, type VerifyTypedDataForSignerParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, classifyRpcMethod, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
1138
+ export { type AckMessage, BLOB_LEN, CAP_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, EXPLICITLY_UNSUPPORTED_METHODS, FP_LEN, type InitiatedKeyExchange, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, MAX_CONNECTION_URI_LENGTH, MAX_JSON_RPC_ID_LENGTH, MAX_JSON_RPC_METHOD_LENGTH, MAX_RELAY_URL_LENGTH, ML_DSA_87_PUBLIC_KEY_BYTES, ML_DSA_87_SIGNATURE_BYTES, ML_DSA_DESCRIPTOR_BYTES, type Message, MessageType, PAIRING_CAPABILITY_LEN, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_ADDRESS_BYTES, QRL_ADDRESS_HEX_LENGTH, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedMessageResult, type QrlSignedResult, type QrlSignedResultWithDescriptor, type QrlSignedTypedDataResult, type QrlSigningResult, type QrlSigningSchemeVersion, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, type RpcMethodPolicy, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, TYPED_DATA_LIMITS, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageForSignerParams, type VerifyMessageParams, type VerifyTypedDataForSignerParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, classifyRpcMethod, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, formatQrlAddressFingerprint, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isCurrentQrlAddress, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, qip55AddressFromBytes, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
package/dist/index.js CHANGED
@@ -53,6 +53,8 @@ __export(index_exports, {
53
53
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
54
54
  QRLConnect: () => QRLConnectProvider,
55
55
  QRLConnectProvider: () => QRLConnectProvider,
56
+ QRL_ADDRESS_BYTES: () => QRL_ADDRESS_BYTES,
57
+ QRL_ADDRESS_HEX_LENGTH: () => QRL_ADDRESS_HEX_LENGTH,
56
58
  QRL_CONNECT_PROVIDER_INFO: () => QRL_CONNECT_PROVIDER_INFO,
57
59
  RESTRICTED_METHODS: () => RESTRICTED_METHODS,
58
60
  SCHEME_TAG_MSG: () => SCHEME_TAG_MSG,
@@ -75,11 +77,13 @@ __export(index_exports, {
75
77
  encodeField: () => encodeField,
76
78
  encodeType: () => encodeType,
77
79
  fingerprintEquals: () => fingerprintEquals,
80
+ formatQrlAddressFingerprint: () => formatQrlAddressFingerprint,
78
81
  generateConnectionURI: () => generateConnectionURI,
79
82
  getAppStoreUrl: () => getAppStoreUrl,
80
83
  hasSigningDescriptor: () => hasSigningDescriptor,
81
84
  hashStruct: () => hashStruct,
82
85
  hexToBytes: () => hexToBytes,
86
+ isCurrentQrlAddress: () => isCurrentQrlAddress,
83
87
  isMobileBrowser: () => isMobileBrowser,
84
88
  isQrlSignedMessageResult: () => isQrlSignedMessageResult,
85
89
  isQrlSignedResult: () => isQrlSignedResult,
@@ -88,6 +92,7 @@ __export(index_exports, {
88
92
  isValidJsonRpcMethod: () => isValidJsonRpcMethod,
89
93
  normalizeRelayUrl: () => normalizeRelayUrl,
90
94
  parseConnectionURI: () => parseConnectionURI,
95
+ qip55AddressFromBytes: () => qip55AddressFromBytes,
91
96
  typeHash: () => typeHash,
92
97
  verifyMessage: () => verifyMessage,
93
98
  verifyMessageForSigner: () => verifyMessageForSigner,
@@ -209,6 +214,54 @@ function constantTimeEquals(a, b) {
209
214
  return d === 0;
210
215
  }
211
216
 
217
+ // src/utils/qrlAddress.ts
218
+ var QRL_ADDRESS_BYTES = 64;
219
+ var QRL_ADDRESS_HEX_LENGTH = QRL_ADDRESS_BYTES * 2;
220
+ var QRL_ADDRESS_RE = new RegExp(`^Q[0-9a-fA-F]{${QRL_ADDRESS_HEX_LENGTH}}$`);
221
+ var textEncoder = new TextEncoder();
222
+ function checksummedHex(lowerHex) {
223
+ const hash = shake256Digest(textEncoder.encode(lowerHex), QRL_ADDRESS_BYTES);
224
+ let result = "";
225
+ for (let index = 0; index < lowerHex.length; index++) {
226
+ const char = lowerHex[index] ?? "";
227
+ if (char >= "a" && char <= "f") {
228
+ const byte = hash[index >> 1] ?? 0;
229
+ const nibble = (index & 1) === 0 ? byte >> 4 : byte & 15;
230
+ result += nibble >= 8 ? char.toUpperCase() : char;
231
+ } else {
232
+ result += char;
233
+ }
234
+ }
235
+ return result;
236
+ }
237
+ function isCurrentQrlAddress(value) {
238
+ if (typeof value !== "string" || !QRL_ADDRESS_RE.test(value)) return false;
239
+ const body = value.slice(1);
240
+ const lower = body.toLowerCase();
241
+ if (body === lower || body === body.toUpperCase()) return true;
242
+ return body === checksummedHex(lower);
243
+ }
244
+ function formatQrlAddressFingerprint(address) {
245
+ if (!/^Q(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{128})$/.test(address)) return address;
246
+ const body = address.slice(1);
247
+ const segmentLength = 8;
248
+ if (body.length < segmentLength * 3) return address;
249
+ const middleStart = Math.floor((body.length - segmentLength) / 2);
250
+ return [
251
+ `Q${body.slice(0, segmentLength)}`,
252
+ body.slice(middleStart, middleStart + segmentLength),
253
+ body.slice(-segmentLength)
254
+ ].join("...");
255
+ }
256
+ function qip55AddressFromBytes(address) {
257
+ if (!(address instanceof Uint8Array) || address.length !== QRL_ADDRESS_BYTES) {
258
+ throw new Error(`address must be exactly ${QRL_ADDRESS_BYTES} bytes`);
259
+ }
260
+ let lower = "";
261
+ for (const byte of address) lower += byte.toString(16).padStart(2, "0");
262
+ return `Q${checksummedHex(lower)}`;
263
+ }
264
+
212
265
  // src/config.ts
213
266
  var DEFAULT_RELAY_URL = "https://qrlwallet.com";
214
267
  var RELAY_PATH = "/relay";
@@ -222,10 +275,6 @@ var MAX_JSON_RPC_ID_LENGTH = 128;
222
275
  var MAX_JSON_RPC_METHOD_LENGTH = 128;
223
276
  var MAX_RELAY_URL_LENGTH = 2048;
224
277
  var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
225
- var CURRENT_QRL_ADDRESS_RE = /^Q[0-9a-fA-F]{40}$/;
226
- function isCurrentQrlAddress(value) {
227
- return typeof value === "string" && CURRENT_QRL_ADDRESS_RE.test(value);
228
- }
229
278
  function isExplicitLoopbackHostname(value) {
230
279
  const hostname = value.toLowerCase();
231
280
  return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
@@ -301,9 +350,9 @@ function classifyRpcMethod(method) {
301
350
  }
302
351
 
303
352
  // src/PQCrypto.ts
304
- var textEncoder = new TextEncoder();
305
- var LABEL = textEncoder.encode("pq-pair/v3");
306
- var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
353
+ var textEncoder2 = new TextEncoder();
354
+ var LABEL = textEncoder2.encode("pq-pair/v3");
355
+ var LABEL_AEAD_SUFFIX = textEncoder2.encode(" aead");
307
356
  var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
308
357
  var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
309
358
  var ML_KEM_768_PK_LEN = 1184;
@@ -502,7 +551,7 @@ function isSigningDescriptor(value) {
502
551
  return typeof value === "string" && /^0x01[0-9a-fA-F]{4}$/.test(value);
503
552
  }
504
553
  function hasValidSigningFields(record) {
505
- return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && /^Q[0-9a-fA-F]{40}$/.test(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
554
+ return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && isCurrentQrlAddress(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
506
555
  }
507
556
  function isQrlSignedMessageResult(value) {
508
557
  try {
@@ -551,10 +600,10 @@ function error(tag, ...args) {
551
600
  }
552
601
 
553
602
  // src/KeyExchange.ts
554
- var textEncoder2 = new TextEncoder();
603
+ var textEncoder3 = new TextEncoder();
555
604
  var textDecoder = new TextDecoder();
556
- var HELLO_WALLET = textEncoder2.encode("hello/wallet/v1");
557
- var HELLO_DAPP = textEncoder2.encode("hello/dapp/v1");
605
+ var HELLO_WALLET = textEncoder3.encode("hello/wallet/v1");
606
+ var HELLO_DAPP = textEncoder3.encode("hello/dapp/v1");
558
607
  var AES_GCM_TAG_LEN = 16;
559
608
  var SYNACK_C0_LEN = HELLO_WALLET.length + AES_GCM_TAG_LEN;
560
609
  var ACK_C1_LEN = HELLO_DAPP.length + AES_GCM_TAG_LEN;
@@ -828,7 +877,7 @@ var KeyExchange = class extends import_eventemitter3.default {
828
877
  throw new Error("KeyExchange: send counter exhausted");
829
878
  }
830
879
  const seq = session.sendSeq++;
831
- const pt = textEncoder2.encode(data);
880
+ const pt = textEncoder3.encode(data);
832
881
  try {
833
882
  const ct = await seal(session.key, session.sendDir, seq, session.htx, pt);
834
883
  if (this.stateGeneration !== generation || this.session !== session) {
@@ -3272,10 +3321,14 @@ function typeHash(primary, types) {
3272
3321
  return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
3273
3322
  }
3274
3323
  function parseQAddress(addr) {
3275
- if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
3276
- throw new Error(`invalid Q-address: ${addr}`);
3324
+ if (!isCurrentQrlAddress(addr)) {
3325
+ throw new Error(`invalid Q-address: ${String(addr)}`);
3277
3326
  }
3278
- return hexToBytes("0x" + addr.slice(1).toLowerCase());
3327
+ const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
3328
+ if (bytes.length > SLOT) {
3329
+ throw new Error("qrl_signTypedData v1 does not support QIP-55 address fields");
3330
+ }
3331
+ return bytes;
3279
3332
  }
3280
3333
  function padLeft32(bytes) {
3281
3334
  if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
@@ -3687,8 +3740,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
3687
3740
  if (params[0] !== authorizedAccount) {
3688
3741
  throw new Error("qrl_signTypedData signer is not the authorized account");
3689
3742
  }
3690
- computeTypedDataDigest(params[1]);
3691
- return void 0;
3743
+ throw new Error(
3744
+ "qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
3745
+ );
3692
3746
  }
3693
3747
  function requiresAuthorizedAccount(method) {
3694
3748
  return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
@@ -3896,7 +3950,12 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
3896
3950
  }
3897
3951
  this.pendingRequests.delete(response.id);
3898
3952
  if (response.error) {
3899
- pending.reject(new Error(response.error.message || "Request failed"));
3953
+ pending.reject(
3954
+ Object.assign(new Error(response.error.message || "Request failed"), {
3955
+ code: response.error.code,
3956
+ ...response.error.data === void 0 ? {} : { data: response.error.data }
3957
+ })
3958
+ );
3900
3959
  return;
3901
3960
  }
3902
3961
  try {
@@ -4143,9 +4202,10 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
4143
4202
  }
4144
4203
  // ── In-flight persistence (survive the return-redirect reload) ──────
4145
4204
  readInflight() {
4146
- if (typeof localStorage === "undefined") return [];
4205
+ const storage = getBrowserStorage();
4206
+ if (!storage) return [];
4147
4207
  try {
4148
- const raw = localStorage.getItem(this.inflightKey);
4208
+ const raw = storage.getItem(this.inflightKey);
4149
4209
  if (!raw) return [];
4150
4210
  const parsed = JSON.parse(raw);
4151
4211
  if (!Array.isArray(parsed)) return [];
@@ -4161,12 +4221,13 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
4161
4221
  }
4162
4222
  }
4163
4223
  writeInflight(records) {
4164
- if (typeof localStorage === "undefined") return;
4224
+ const storage = getBrowserStorage();
4225
+ if (!storage) return;
4165
4226
  try {
4166
4227
  if (records.length === 0) {
4167
- localStorage.removeItem(this.inflightKey);
4228
+ storage.removeItem(this.inflightKey);
4168
4229
  } else {
4169
- localStorage.setItem(this.inflightKey, JSON.stringify(records));
4230
+ storage.setItem(this.inflightKey, JSON.stringify(records));
4170
4231
  }
4171
4232
  } catch {
4172
4233
  }
@@ -4300,7 +4361,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
4300
4361
  var ML_DSA_DESCRIPTOR_BYTES = 3;
4301
4362
  var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
4302
4363
  var ML_DSA_87_SIGNATURE_BYTES = 4627;
4303
- var CURRENT_QRL_ADDRESS_BYTES = 20;
4304
4364
  function bytesOrHex(v) {
4305
4365
  if (v instanceof Uint8Array) return new Uint8Array(v);
4306
4366
  if (typeof v === "string") return hexToBytes(v);
@@ -4357,11 +4417,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
4357
4417
  if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
4358
4418
  return false;
4359
4419
  }
4360
- const addressBytes = shake256Digest(
4361
- concatBytes(descriptor, publicKey),
4362
- CURRENT_QRL_ADDRESS_BYTES
4363
- );
4364
- const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
4420
+ const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
4421
+ const derived = qip55AddressFromBytes(addressBytes);
4365
4422
  return derived.toLowerCase() === expectedSigner.toLowerCase();
4366
4423
  }
4367
4424
  function verifyMessageForSigner({
@@ -4432,6 +4489,8 @@ function verifyTypedData(params) {
4432
4489
  PROTOCOL_VERSION,
4433
4490
  QRLConnect,
4434
4491
  QRLConnectProvider,
4492
+ QRL_ADDRESS_BYTES,
4493
+ QRL_ADDRESS_HEX_LENGTH,
4435
4494
  QRL_CONNECT_PROVIDER_INFO,
4436
4495
  RESTRICTED_METHODS,
4437
4496
  SCHEME_TAG_MSG,
@@ -4454,11 +4513,13 @@ function verifyTypedData(params) {
4454
4513
  encodeField,
4455
4514
  encodeType,
4456
4515
  fingerprintEquals,
4516
+ formatQrlAddressFingerprint,
4457
4517
  generateConnectionURI,
4458
4518
  getAppStoreUrl,
4459
4519
  hasSigningDescriptor,
4460
4520
  hashStruct,
4461
4521
  hexToBytes,
4522
+ isCurrentQrlAddress,
4462
4523
  isMobileBrowser,
4463
4524
  isQrlSignedMessageResult,
4464
4525
  isQrlSignedResult,
@@ -4467,6 +4528,7 @@ function verifyTypedData(params) {
4467
4528
  isValidJsonRpcMethod,
4468
4529
  normalizeRelayUrl,
4469
4530
  parseConnectionURI,
4531
+ qip55AddressFromBytes,
4470
4532
  typeHash,
4471
4533
  verifyMessage,
4472
4534
  verifyMessageForSigner,
package/dist/index.mjs CHANGED
@@ -109,6 +109,54 @@ function constantTimeEquals(a, b) {
109
109
  return d === 0;
110
110
  }
111
111
 
112
+ // src/utils/qrlAddress.ts
113
+ var QRL_ADDRESS_BYTES = 64;
114
+ var QRL_ADDRESS_HEX_LENGTH = QRL_ADDRESS_BYTES * 2;
115
+ var QRL_ADDRESS_RE = new RegExp(`^Q[0-9a-fA-F]{${QRL_ADDRESS_HEX_LENGTH}}$`);
116
+ var textEncoder = new TextEncoder();
117
+ function checksummedHex(lowerHex) {
118
+ const hash = shake256Digest(textEncoder.encode(lowerHex), QRL_ADDRESS_BYTES);
119
+ let result = "";
120
+ for (let index = 0; index < lowerHex.length; index++) {
121
+ const char = lowerHex[index] ?? "";
122
+ if (char >= "a" && char <= "f") {
123
+ const byte = hash[index >> 1] ?? 0;
124
+ const nibble = (index & 1) === 0 ? byte >> 4 : byte & 15;
125
+ result += nibble >= 8 ? char.toUpperCase() : char;
126
+ } else {
127
+ result += char;
128
+ }
129
+ }
130
+ return result;
131
+ }
132
+ function isCurrentQrlAddress(value) {
133
+ if (typeof value !== "string" || !QRL_ADDRESS_RE.test(value)) return false;
134
+ const body = value.slice(1);
135
+ const lower = body.toLowerCase();
136
+ if (body === lower || body === body.toUpperCase()) return true;
137
+ return body === checksummedHex(lower);
138
+ }
139
+ function formatQrlAddressFingerprint(address) {
140
+ if (!/^Q(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{128})$/.test(address)) return address;
141
+ const body = address.slice(1);
142
+ const segmentLength = 8;
143
+ if (body.length < segmentLength * 3) return address;
144
+ const middleStart = Math.floor((body.length - segmentLength) / 2);
145
+ return [
146
+ `Q${body.slice(0, segmentLength)}`,
147
+ body.slice(middleStart, middleStart + segmentLength),
148
+ body.slice(-segmentLength)
149
+ ].join("...");
150
+ }
151
+ function qip55AddressFromBytes(address) {
152
+ if (!(address instanceof Uint8Array) || address.length !== QRL_ADDRESS_BYTES) {
153
+ throw new Error(`address must be exactly ${QRL_ADDRESS_BYTES} bytes`);
154
+ }
155
+ let lower = "";
156
+ for (const byte of address) lower += byte.toString(16).padStart(2, "0");
157
+ return `Q${checksummedHex(lower)}`;
158
+ }
159
+
112
160
  // src/config.ts
113
161
  var DEFAULT_RELAY_URL = "https://qrlwallet.com";
114
162
  var RELAY_PATH = "/relay";
@@ -122,10 +170,6 @@ var MAX_JSON_RPC_ID_LENGTH = 128;
122
170
  var MAX_JSON_RPC_METHOD_LENGTH = 128;
123
171
  var MAX_RELAY_URL_LENGTH = 2048;
124
172
  var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
125
- var CURRENT_QRL_ADDRESS_RE = /^Q[0-9a-fA-F]{40}$/;
126
- function isCurrentQrlAddress(value) {
127
- return typeof value === "string" && CURRENT_QRL_ADDRESS_RE.test(value);
128
- }
129
173
  function isExplicitLoopbackHostname(value) {
130
174
  const hostname = value.toLowerCase();
131
175
  return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
@@ -201,9 +245,9 @@ function classifyRpcMethod(method) {
201
245
  }
202
246
 
203
247
  // src/PQCrypto.ts
204
- var textEncoder = new TextEncoder();
205
- var LABEL = textEncoder.encode("pq-pair/v3");
206
- var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
248
+ var textEncoder2 = new TextEncoder();
249
+ var LABEL = textEncoder2.encode("pq-pair/v3");
250
+ var LABEL_AEAD_SUFFIX = textEncoder2.encode(" aead");
207
251
  var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
208
252
  var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
209
253
  var ML_KEM_768_PK_LEN = 1184;
@@ -402,7 +446,7 @@ function isSigningDescriptor(value) {
402
446
  return typeof value === "string" && /^0x01[0-9a-fA-F]{4}$/.test(value);
403
447
  }
404
448
  function hasValidSigningFields(record) {
405
- return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && /^Q[0-9a-fA-F]{40}$/.test(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
449
+ return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && isCurrentQrlAddress(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
406
450
  }
407
451
  function isQrlSignedMessageResult(value) {
408
452
  try {
@@ -451,10 +495,10 @@ function error(tag, ...args) {
451
495
  }
452
496
 
453
497
  // src/KeyExchange.ts
454
- var textEncoder2 = new TextEncoder();
498
+ var textEncoder3 = new TextEncoder();
455
499
  var textDecoder = new TextDecoder();
456
- var HELLO_WALLET = textEncoder2.encode("hello/wallet/v1");
457
- var HELLO_DAPP = textEncoder2.encode("hello/dapp/v1");
500
+ var HELLO_WALLET = textEncoder3.encode("hello/wallet/v1");
501
+ var HELLO_DAPP = textEncoder3.encode("hello/dapp/v1");
458
502
  var AES_GCM_TAG_LEN = 16;
459
503
  var SYNACK_C0_LEN = HELLO_WALLET.length + AES_GCM_TAG_LEN;
460
504
  var ACK_C1_LEN = HELLO_DAPP.length + AES_GCM_TAG_LEN;
@@ -728,7 +772,7 @@ var KeyExchange = class extends EventEmitter {
728
772
  throw new Error("KeyExchange: send counter exhausted");
729
773
  }
730
774
  const seq = session.sendSeq++;
731
- const pt = textEncoder2.encode(data);
775
+ const pt = textEncoder3.encode(data);
732
776
  try {
733
777
  const ct = await seal(session.key, session.sendDir, seq, session.htx, pt);
734
778
  if (this.stateGeneration !== generation || this.session !== session) {
@@ -3172,10 +3216,14 @@ function typeHash(primary, types) {
3172
3216
  return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
3173
3217
  }
3174
3218
  function parseQAddress(addr) {
3175
- if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
3176
- throw new Error(`invalid Q-address: ${addr}`);
3219
+ if (!isCurrentQrlAddress(addr)) {
3220
+ throw new Error(`invalid Q-address: ${String(addr)}`);
3177
3221
  }
3178
- return hexToBytes("0x" + addr.slice(1).toLowerCase());
3222
+ const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
3223
+ if (bytes.length > SLOT) {
3224
+ throw new Error("qrl_signTypedData v1 does not support QIP-55 address fields");
3225
+ }
3226
+ return bytes;
3179
3227
  }
3180
3228
  function padLeft32(bytes) {
3181
3229
  if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
@@ -3587,8 +3635,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
3587
3635
  if (params[0] !== authorizedAccount) {
3588
3636
  throw new Error("qrl_signTypedData signer is not the authorized account");
3589
3637
  }
3590
- computeTypedDataDigest(params[1]);
3591
- return void 0;
3638
+ throw new Error(
3639
+ "qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
3640
+ );
3592
3641
  }
3593
3642
  function requiresAuthorizedAccount(method) {
3594
3643
  return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
@@ -3796,7 +3845,12 @@ var QRLConnectProvider = class extends EventEmitter4 {
3796
3845
  }
3797
3846
  this.pendingRequests.delete(response.id);
3798
3847
  if (response.error) {
3799
- pending.reject(new Error(response.error.message || "Request failed"));
3848
+ pending.reject(
3849
+ Object.assign(new Error(response.error.message || "Request failed"), {
3850
+ code: response.error.code,
3851
+ ...response.error.data === void 0 ? {} : { data: response.error.data }
3852
+ })
3853
+ );
3800
3854
  return;
3801
3855
  }
3802
3856
  try {
@@ -4043,9 +4097,10 @@ var QRLConnectProvider = class extends EventEmitter4 {
4043
4097
  }
4044
4098
  // ── In-flight persistence (survive the return-redirect reload) ──────
4045
4099
  readInflight() {
4046
- if (typeof localStorage === "undefined") return [];
4100
+ const storage = getBrowserStorage();
4101
+ if (!storage) return [];
4047
4102
  try {
4048
- const raw = localStorage.getItem(this.inflightKey);
4103
+ const raw = storage.getItem(this.inflightKey);
4049
4104
  if (!raw) return [];
4050
4105
  const parsed = JSON.parse(raw);
4051
4106
  if (!Array.isArray(parsed)) return [];
@@ -4061,12 +4116,13 @@ var QRLConnectProvider = class extends EventEmitter4 {
4061
4116
  }
4062
4117
  }
4063
4118
  writeInflight(records) {
4064
- if (typeof localStorage === "undefined") return;
4119
+ const storage = getBrowserStorage();
4120
+ if (!storage) return;
4065
4121
  try {
4066
4122
  if (records.length === 0) {
4067
- localStorage.removeItem(this.inflightKey);
4123
+ storage.removeItem(this.inflightKey);
4068
4124
  } else {
4069
- localStorage.setItem(this.inflightKey, JSON.stringify(records));
4125
+ storage.setItem(this.inflightKey, JSON.stringify(records));
4070
4126
  }
4071
4127
  } catch {
4072
4128
  }
@@ -4200,7 +4256,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
4200
4256
  var ML_DSA_DESCRIPTOR_BYTES = 3;
4201
4257
  var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
4202
4258
  var ML_DSA_87_SIGNATURE_BYTES = 4627;
4203
- var CURRENT_QRL_ADDRESS_BYTES = 20;
4204
4259
  function bytesOrHex(v) {
4205
4260
  if (v instanceof Uint8Array) return new Uint8Array(v);
4206
4261
  if (typeof v === "string") return hexToBytes(v);
@@ -4257,11 +4312,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
4257
4312
  if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
4258
4313
  return false;
4259
4314
  }
4260
- const addressBytes = shake256Digest(
4261
- concatBytes(descriptor, publicKey),
4262
- CURRENT_QRL_ADDRESS_BYTES
4263
- );
4264
- const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
4315
+ const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
4316
+ const derived = qip55AddressFromBytes(addressBytes);
4265
4317
  return derived.toLowerCase() === expectedSigner.toLowerCase();
4266
4318
  }
4267
4319
  function verifyMessageForSigner({
@@ -4331,6 +4383,8 @@ export {
4331
4383
  PROTOCOL_VERSION,
4332
4384
  QRLConnectProvider as QRLConnect,
4333
4385
  QRLConnectProvider,
4386
+ QRL_ADDRESS_BYTES,
4387
+ QRL_ADDRESS_HEX_LENGTH,
4334
4388
  QRL_CONNECT_PROVIDER_INFO,
4335
4389
  RESTRICTED_METHODS,
4336
4390
  SCHEME_TAG_MSG,
@@ -4353,11 +4407,13 @@ export {
4353
4407
  encodeField,
4354
4408
  encodeType,
4355
4409
  fingerprintEquals,
4410
+ formatQrlAddressFingerprint,
4356
4411
  generateConnectionURI,
4357
4412
  getAppStoreUrl,
4358
4413
  hasSigningDescriptor,
4359
4414
  hashStruct,
4360
4415
  hexToBytes,
4416
+ isCurrentQrlAddress,
4361
4417
  isMobileBrowser,
4362
4418
  isQrlSignedMessageResult,
4363
4419
  isQrlSignedResult,
@@ -4366,6 +4422,7 @@ export {
4366
4422
  isValidJsonRpcMethod,
4367
4423
  normalizeRelayUrl,
4368
4424
  parseConnectionURI,
4425
+ qip55AddressFromBytes,
4369
4426
  typeHash,
4370
4427
  verifyMessage,
4371
4428
  verifyMessageForSigner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrlwallet/connect",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
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",
@@ -44,13 +44,13 @@
44
44
  "dependencies": {
45
45
  "@noble/hashes": "^2.2.0",
46
46
  "@noble/post-quantum": "^0.5.4",
47
- "@theqrl/mldsa87": "^2.1.1",
47
+ "@theqrl/mldsa87": "^2.1.4",
48
48
  "eventemitter3": "^5.0.1",
49
49
  "socket.io-client": "^4.8.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@size-limit/file": "^11.0.0",
53
- "@theqrl/wallet.js": "^3.0.1",
53
+ "@theqrl/wallet.js": "^6.2.4",
54
54
  "@types/node": "^25.3.3",
55
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
56
56
  "@typescript-eslint/parser": "^8.0.0",
@@ -60,10 +60,13 @@
60
60
  "size-limit": "^11.0.0",
61
61
  "tsup": "^8.4.0",
62
62
  "typescript": "^5.7.0",
63
- "vitest": "^3.0.0"
63
+ "vitest": "4.1.11"
64
64
  },
65
65
  "overrides": {
66
- "esbuild": "0.28.1"
66
+ "esbuild": "0.28.1",
67
+ "vitest": {
68
+ "vite": "7.3.5"
69
+ }
67
70
  },
68
71
  "size-limit": [
69
72
  {