@qrlwallet/connect 4.0.0 → 5.0.1
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 +29 -4
- package/dist/index.d.mts +32 -15
- package/dist/index.d.ts +32 -15
- package/dist/index.js +114 -34
- package/dist/index.mjs +109 -34
- package/package.json +8 -5
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: '
|
|
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`
|
|
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`
|
|
172
|
-
challenge or payload and the expected
|
|
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
|
|
273
|
-
* `qrl_signTypedData
|
|
274
|
-
* ML-DSA-87 public keys cannot be recovered from a signature,
|
|
275
|
-
* wallet
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
307
|
-
* verifier
|
|
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]`.
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
273
|
-
* `qrl_signTypedData
|
|
274
|
-
* ML-DSA-87 public keys cannot be recovered from a signature,
|
|
275
|
-
* wallet
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
307
|
-
* verifier
|
|
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]`.
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
305
|
-
var LABEL =
|
|
306
|
-
var LABEL_AEAD_SUFFIX =
|
|
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" &&
|
|
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
|
|
603
|
+
var textEncoder3 = new TextEncoder();
|
|
555
604
|
var textDecoder = new TextDecoder();
|
|
556
|
-
var HELLO_WALLET =
|
|
557
|
-
var HELLO_DAPP =
|
|
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 =
|
|
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 (
|
|
3276
|
-
throw new Error(`invalid Q-address: ${addr}`);
|
|
3324
|
+
if (!isCurrentQrlAddress(addr)) {
|
|
3325
|
+
throw new Error(`invalid Q-address: ${String(addr)}`);
|
|
3326
|
+
}
|
|
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");
|
|
3277
3330
|
}
|
|
3278
|
-
return
|
|
3331
|
+
return bytes;
|
|
3279
3332
|
}
|
|
3280
3333
|
function padLeft32(bytes) {
|
|
3281
3334
|
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
@@ -3592,7 +3645,7 @@ function requestedSwitchChainId(params) {
|
|
|
3592
3645
|
}
|
|
3593
3646
|
return canonicalChainId2(request.chainId);
|
|
3594
3647
|
}
|
|
3595
|
-
var TRANSACTION_FIELDS = /* @__PURE__ */ new Set(["from", "to", "value", "gas", "data"]);
|
|
3648
|
+
var TRANSACTION_FIELDS = /* @__PURE__ */ new Set(["from", "to", "value", "gas", "data", "chainId"]);
|
|
3596
3649
|
var QRL_TRANSACTION_MAX_DATA_BYTES = 128 * 1024;
|
|
3597
3650
|
var QRL_TRANSACTION_MAX_QUANTITY_HEX_DIGITS = 64;
|
|
3598
3651
|
var RPC_QUANTITY_RE = /^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/;
|
|
@@ -3607,8 +3660,11 @@ function validateRpcQuantity(value, field) {
|
|
|
3607
3660
|
if (field === "gas" && BigInt(value) > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3608
3661
|
throw new Error("transaction gas exceeds the wallet safe-integer limit");
|
|
3609
3662
|
}
|
|
3663
|
+
if (field === "chainId" && BigInt(value) === 0n) {
|
|
3664
|
+
throw new Error("transaction chainId must be positive");
|
|
3665
|
+
}
|
|
3610
3666
|
}
|
|
3611
|
-
function validateTransactionRequest(method, params, authorizedAccount) {
|
|
3667
|
+
function validateTransactionRequest(method, params, authorizedAccount, currentChainId) {
|
|
3612
3668
|
if (params?.length !== 1 || !isRecordObj(params[0])) {
|
|
3613
3669
|
throw new Error(`${method} requires exactly one transaction object`);
|
|
3614
3670
|
}
|
|
@@ -3626,6 +3682,12 @@ function validateTransactionRequest(method, params, authorizedAccount) {
|
|
|
3626
3682
|
}
|
|
3627
3683
|
if ("value" in tx) validateRpcQuantity(tx.value, "value");
|
|
3628
3684
|
if ("gas" in tx) validateRpcQuantity(tx.gas, "gas");
|
|
3685
|
+
if ("chainId" in tx) {
|
|
3686
|
+
validateRpcQuantity(tx.chainId, "chainId");
|
|
3687
|
+
if (canonicalChainId2(tx.chainId) !== canonicalChainId2(currentChainId)) {
|
|
3688
|
+
throw new Error("transaction chainId does not match the connected wallet");
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3629
3691
|
if ("data" in tx && (typeof tx.data !== "string" || tx.data.length > QRL_TRANSACTION_MAX_DATA_BYTES * 2 + 2 || !/^0x(?:[0-9a-fA-F]{2})*$/.test(tx.data))) {
|
|
3630
3692
|
throw new Error("transaction data must be bounded 0x-prefixed bytes");
|
|
3631
3693
|
}
|
|
@@ -3643,7 +3705,7 @@ function snapshotRequestParams(params) {
|
|
|
3643
3705
|
throw new Error(`Request params must be JSON-serializable: ${detail}`);
|
|
3644
3706
|
}
|
|
3645
3707
|
}
|
|
3646
|
-
function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
3708
|
+
function validateRestrictedRequest(method, params, authorizedAccounts, currentChainId) {
|
|
3647
3709
|
if (method === "qrl_requestAccounts") {
|
|
3648
3710
|
if (params !== void 0 && params.length !== 0) {
|
|
3649
3711
|
throw new Error("qrl_requestAccounts does not accept parameters");
|
|
@@ -3658,7 +3720,7 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3658
3720
|
throw new Error("No authorized account: call qrl_requestAccounts first");
|
|
3659
3721
|
}
|
|
3660
3722
|
if (method === "qrl_sendTransaction" || method === "qrl_signTransaction") {
|
|
3661
|
-
validateTransactionRequest(method, params, authorizedAccount);
|
|
3723
|
+
validateTransactionRequest(method, params, authorizedAccount, currentChainId);
|
|
3662
3724
|
return void 0;
|
|
3663
3725
|
}
|
|
3664
3726
|
if (method === "qrl_signMessage") {
|
|
@@ -3687,8 +3749,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3687
3749
|
if (params[0] !== authorizedAccount) {
|
|
3688
3750
|
throw new Error("qrl_signTypedData signer is not the authorized account");
|
|
3689
3751
|
}
|
|
3690
|
-
|
|
3691
|
-
|
|
3752
|
+
throw new Error(
|
|
3753
|
+
"qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
|
|
3754
|
+
);
|
|
3692
3755
|
}
|
|
3693
3756
|
function requiresAuthorizedAccount(method) {
|
|
3694
3757
|
return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
|
|
@@ -3896,7 +3959,12 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
3896
3959
|
}
|
|
3897
3960
|
this.pendingRequests.delete(response.id);
|
|
3898
3961
|
if (response.error) {
|
|
3899
|
-
pending.reject(
|
|
3962
|
+
pending.reject(
|
|
3963
|
+
Object.assign(new Error(response.error.message || "Request failed"), {
|
|
3964
|
+
code: response.error.code,
|
|
3965
|
+
...response.error.data === void 0 ? {} : { data: response.error.data }
|
|
3966
|
+
})
|
|
3967
|
+
);
|
|
3900
3968
|
return;
|
|
3901
3969
|
}
|
|
3902
3970
|
try {
|
|
@@ -4000,7 +4068,8 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4000
4068
|
const expectedChainId = validateRestrictedRequest(
|
|
4001
4069
|
method,
|
|
4002
4070
|
stableParams,
|
|
4003
|
-
this.connectionManager.getAccounts()
|
|
4071
|
+
this.connectionManager.getAccounts(),
|
|
4072
|
+
this.connectionManager.getChainId()
|
|
4004
4073
|
);
|
|
4005
4074
|
return this.enqueueRestrictedRequest(method, stableParams, expectedChainId, generation);
|
|
4006
4075
|
}
|
|
@@ -4065,6 +4134,14 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4065
4134
|
throw new Error("Not connected to QRL Wallet");
|
|
4066
4135
|
}
|
|
4067
4136
|
}
|
|
4137
|
+
if (restricted) {
|
|
4138
|
+
validateRestrictedRequest(
|
|
4139
|
+
method,
|
|
4140
|
+
params,
|
|
4141
|
+
this.connectionManager.getAccounts(),
|
|
4142
|
+
this.connectionManager.getChainId()
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4068
4145
|
if (this.requestCounter >= Number.MAX_SAFE_INTEGER) {
|
|
4069
4146
|
throw new Error("JSON-RPC request id counter exhausted");
|
|
4070
4147
|
}
|
|
@@ -4143,9 +4220,10 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4143
4220
|
}
|
|
4144
4221
|
// ── In-flight persistence (survive the return-redirect reload) ──────
|
|
4145
4222
|
readInflight() {
|
|
4146
|
-
|
|
4223
|
+
const storage = getBrowserStorage();
|
|
4224
|
+
if (!storage) return [];
|
|
4147
4225
|
try {
|
|
4148
|
-
const raw =
|
|
4226
|
+
const raw = storage.getItem(this.inflightKey);
|
|
4149
4227
|
if (!raw) return [];
|
|
4150
4228
|
const parsed = JSON.parse(raw);
|
|
4151
4229
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -4161,12 +4239,13 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4161
4239
|
}
|
|
4162
4240
|
}
|
|
4163
4241
|
writeInflight(records) {
|
|
4164
|
-
|
|
4242
|
+
const storage = getBrowserStorage();
|
|
4243
|
+
if (!storage) return;
|
|
4165
4244
|
try {
|
|
4166
4245
|
if (records.length === 0) {
|
|
4167
|
-
|
|
4246
|
+
storage.removeItem(this.inflightKey);
|
|
4168
4247
|
} else {
|
|
4169
|
-
|
|
4248
|
+
storage.setItem(this.inflightKey, JSON.stringify(records));
|
|
4170
4249
|
}
|
|
4171
4250
|
} catch {
|
|
4172
4251
|
}
|
|
@@ -4300,7 +4379,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
|
|
|
4300
4379
|
var ML_DSA_DESCRIPTOR_BYTES = 3;
|
|
4301
4380
|
var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
|
|
4302
4381
|
var ML_DSA_87_SIGNATURE_BYTES = 4627;
|
|
4303
|
-
var CURRENT_QRL_ADDRESS_BYTES = 20;
|
|
4304
4382
|
function bytesOrHex(v) {
|
|
4305
4383
|
if (v instanceof Uint8Array) return new Uint8Array(v);
|
|
4306
4384
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -4357,11 +4435,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
|
|
|
4357
4435
|
if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
|
|
4358
4436
|
return false;
|
|
4359
4437
|
}
|
|
4360
|
-
const addressBytes = shake256Digest(
|
|
4361
|
-
|
|
4362
|
-
CURRENT_QRL_ADDRESS_BYTES
|
|
4363
|
-
);
|
|
4364
|
-
const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
|
|
4438
|
+
const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
|
|
4439
|
+
const derived = qip55AddressFromBytes(addressBytes);
|
|
4365
4440
|
return derived.toLowerCase() === expectedSigner.toLowerCase();
|
|
4366
4441
|
}
|
|
4367
4442
|
function verifyMessageForSigner({
|
|
@@ -4432,6 +4507,8 @@ function verifyTypedData(params) {
|
|
|
4432
4507
|
PROTOCOL_VERSION,
|
|
4433
4508
|
QRLConnect,
|
|
4434
4509
|
QRLConnectProvider,
|
|
4510
|
+
QRL_ADDRESS_BYTES,
|
|
4511
|
+
QRL_ADDRESS_HEX_LENGTH,
|
|
4435
4512
|
QRL_CONNECT_PROVIDER_INFO,
|
|
4436
4513
|
RESTRICTED_METHODS,
|
|
4437
4514
|
SCHEME_TAG_MSG,
|
|
@@ -4454,11 +4531,13 @@ function verifyTypedData(params) {
|
|
|
4454
4531
|
encodeField,
|
|
4455
4532
|
encodeType,
|
|
4456
4533
|
fingerprintEquals,
|
|
4534
|
+
formatQrlAddressFingerprint,
|
|
4457
4535
|
generateConnectionURI,
|
|
4458
4536
|
getAppStoreUrl,
|
|
4459
4537
|
hasSigningDescriptor,
|
|
4460
4538
|
hashStruct,
|
|
4461
4539
|
hexToBytes,
|
|
4540
|
+
isCurrentQrlAddress,
|
|
4462
4541
|
isMobileBrowser,
|
|
4463
4542
|
isQrlSignedMessageResult,
|
|
4464
4543
|
isQrlSignedResult,
|
|
@@ -4467,6 +4546,7 @@ function verifyTypedData(params) {
|
|
|
4467
4546
|
isValidJsonRpcMethod,
|
|
4468
4547
|
normalizeRelayUrl,
|
|
4469
4548
|
parseConnectionURI,
|
|
4549
|
+
qip55AddressFromBytes,
|
|
4470
4550
|
typeHash,
|
|
4471
4551
|
verifyMessage,
|
|
4472
4552
|
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
|
|
205
|
-
var LABEL =
|
|
206
|
-
var LABEL_AEAD_SUFFIX =
|
|
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" &&
|
|
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
|
|
498
|
+
var textEncoder3 = new TextEncoder();
|
|
455
499
|
var textDecoder = new TextDecoder();
|
|
456
|
-
var HELLO_WALLET =
|
|
457
|
-
var HELLO_DAPP =
|
|
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 =
|
|
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 (
|
|
3176
|
-
throw new Error(`invalid Q-address: ${addr}`);
|
|
3219
|
+
if (!isCurrentQrlAddress(addr)) {
|
|
3220
|
+
throw new Error(`invalid Q-address: ${String(addr)}`);
|
|
3221
|
+
}
|
|
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");
|
|
3177
3225
|
}
|
|
3178
|
-
return
|
|
3226
|
+
return bytes;
|
|
3179
3227
|
}
|
|
3180
3228
|
function padLeft32(bytes) {
|
|
3181
3229
|
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
@@ -3492,7 +3540,7 @@ function requestedSwitchChainId(params) {
|
|
|
3492
3540
|
}
|
|
3493
3541
|
return canonicalChainId2(request.chainId);
|
|
3494
3542
|
}
|
|
3495
|
-
var TRANSACTION_FIELDS = /* @__PURE__ */ new Set(["from", "to", "value", "gas", "data"]);
|
|
3543
|
+
var TRANSACTION_FIELDS = /* @__PURE__ */ new Set(["from", "to", "value", "gas", "data", "chainId"]);
|
|
3496
3544
|
var QRL_TRANSACTION_MAX_DATA_BYTES = 128 * 1024;
|
|
3497
3545
|
var QRL_TRANSACTION_MAX_QUANTITY_HEX_DIGITS = 64;
|
|
3498
3546
|
var RPC_QUANTITY_RE = /^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/;
|
|
@@ -3507,8 +3555,11 @@ function validateRpcQuantity(value, field) {
|
|
|
3507
3555
|
if (field === "gas" && BigInt(value) > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3508
3556
|
throw new Error("transaction gas exceeds the wallet safe-integer limit");
|
|
3509
3557
|
}
|
|
3558
|
+
if (field === "chainId" && BigInt(value) === 0n) {
|
|
3559
|
+
throw new Error("transaction chainId must be positive");
|
|
3560
|
+
}
|
|
3510
3561
|
}
|
|
3511
|
-
function validateTransactionRequest(method, params, authorizedAccount) {
|
|
3562
|
+
function validateTransactionRequest(method, params, authorizedAccount, currentChainId) {
|
|
3512
3563
|
if (params?.length !== 1 || !isRecordObj(params[0])) {
|
|
3513
3564
|
throw new Error(`${method} requires exactly one transaction object`);
|
|
3514
3565
|
}
|
|
@@ -3526,6 +3577,12 @@ function validateTransactionRequest(method, params, authorizedAccount) {
|
|
|
3526
3577
|
}
|
|
3527
3578
|
if ("value" in tx) validateRpcQuantity(tx.value, "value");
|
|
3528
3579
|
if ("gas" in tx) validateRpcQuantity(tx.gas, "gas");
|
|
3580
|
+
if ("chainId" in tx) {
|
|
3581
|
+
validateRpcQuantity(tx.chainId, "chainId");
|
|
3582
|
+
if (canonicalChainId2(tx.chainId) !== canonicalChainId2(currentChainId)) {
|
|
3583
|
+
throw new Error("transaction chainId does not match the connected wallet");
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3529
3586
|
if ("data" in tx && (typeof tx.data !== "string" || tx.data.length > QRL_TRANSACTION_MAX_DATA_BYTES * 2 + 2 || !/^0x(?:[0-9a-fA-F]{2})*$/.test(tx.data))) {
|
|
3530
3587
|
throw new Error("transaction data must be bounded 0x-prefixed bytes");
|
|
3531
3588
|
}
|
|
@@ -3543,7 +3600,7 @@ function snapshotRequestParams(params) {
|
|
|
3543
3600
|
throw new Error(`Request params must be JSON-serializable: ${detail}`);
|
|
3544
3601
|
}
|
|
3545
3602
|
}
|
|
3546
|
-
function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
3603
|
+
function validateRestrictedRequest(method, params, authorizedAccounts, currentChainId) {
|
|
3547
3604
|
if (method === "qrl_requestAccounts") {
|
|
3548
3605
|
if (params !== void 0 && params.length !== 0) {
|
|
3549
3606
|
throw new Error("qrl_requestAccounts does not accept parameters");
|
|
@@ -3558,7 +3615,7 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3558
3615
|
throw new Error("No authorized account: call qrl_requestAccounts first");
|
|
3559
3616
|
}
|
|
3560
3617
|
if (method === "qrl_sendTransaction" || method === "qrl_signTransaction") {
|
|
3561
|
-
validateTransactionRequest(method, params, authorizedAccount);
|
|
3618
|
+
validateTransactionRequest(method, params, authorizedAccount, currentChainId);
|
|
3562
3619
|
return void 0;
|
|
3563
3620
|
}
|
|
3564
3621
|
if (method === "qrl_signMessage") {
|
|
@@ -3587,8 +3644,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3587
3644
|
if (params[0] !== authorizedAccount) {
|
|
3588
3645
|
throw new Error("qrl_signTypedData signer is not the authorized account");
|
|
3589
3646
|
}
|
|
3590
|
-
|
|
3591
|
-
|
|
3647
|
+
throw new Error(
|
|
3648
|
+
"qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
|
|
3649
|
+
);
|
|
3592
3650
|
}
|
|
3593
3651
|
function requiresAuthorizedAccount(method) {
|
|
3594
3652
|
return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
|
|
@@ -3796,7 +3854,12 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
3796
3854
|
}
|
|
3797
3855
|
this.pendingRequests.delete(response.id);
|
|
3798
3856
|
if (response.error) {
|
|
3799
|
-
pending.reject(
|
|
3857
|
+
pending.reject(
|
|
3858
|
+
Object.assign(new Error(response.error.message || "Request failed"), {
|
|
3859
|
+
code: response.error.code,
|
|
3860
|
+
...response.error.data === void 0 ? {} : { data: response.error.data }
|
|
3861
|
+
})
|
|
3862
|
+
);
|
|
3800
3863
|
return;
|
|
3801
3864
|
}
|
|
3802
3865
|
try {
|
|
@@ -3900,7 +3963,8 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
3900
3963
|
const expectedChainId = validateRestrictedRequest(
|
|
3901
3964
|
method,
|
|
3902
3965
|
stableParams,
|
|
3903
|
-
this.connectionManager.getAccounts()
|
|
3966
|
+
this.connectionManager.getAccounts(),
|
|
3967
|
+
this.connectionManager.getChainId()
|
|
3904
3968
|
);
|
|
3905
3969
|
return this.enqueueRestrictedRequest(method, stableParams, expectedChainId, generation);
|
|
3906
3970
|
}
|
|
@@ -3965,6 +4029,14 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
3965
4029
|
throw new Error("Not connected to QRL Wallet");
|
|
3966
4030
|
}
|
|
3967
4031
|
}
|
|
4032
|
+
if (restricted) {
|
|
4033
|
+
validateRestrictedRequest(
|
|
4034
|
+
method,
|
|
4035
|
+
params,
|
|
4036
|
+
this.connectionManager.getAccounts(),
|
|
4037
|
+
this.connectionManager.getChainId()
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
3968
4040
|
if (this.requestCounter >= Number.MAX_SAFE_INTEGER) {
|
|
3969
4041
|
throw new Error("JSON-RPC request id counter exhausted");
|
|
3970
4042
|
}
|
|
@@ -4043,9 +4115,10 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
4043
4115
|
}
|
|
4044
4116
|
// ── In-flight persistence (survive the return-redirect reload) ──────
|
|
4045
4117
|
readInflight() {
|
|
4046
|
-
|
|
4118
|
+
const storage = getBrowserStorage();
|
|
4119
|
+
if (!storage) return [];
|
|
4047
4120
|
try {
|
|
4048
|
-
const raw =
|
|
4121
|
+
const raw = storage.getItem(this.inflightKey);
|
|
4049
4122
|
if (!raw) return [];
|
|
4050
4123
|
const parsed = JSON.parse(raw);
|
|
4051
4124
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -4061,12 +4134,13 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
4061
4134
|
}
|
|
4062
4135
|
}
|
|
4063
4136
|
writeInflight(records) {
|
|
4064
|
-
|
|
4137
|
+
const storage = getBrowserStorage();
|
|
4138
|
+
if (!storage) return;
|
|
4065
4139
|
try {
|
|
4066
4140
|
if (records.length === 0) {
|
|
4067
|
-
|
|
4141
|
+
storage.removeItem(this.inflightKey);
|
|
4068
4142
|
} else {
|
|
4069
|
-
|
|
4143
|
+
storage.setItem(this.inflightKey, JSON.stringify(records));
|
|
4070
4144
|
}
|
|
4071
4145
|
} catch {
|
|
4072
4146
|
}
|
|
@@ -4200,7 +4274,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
|
|
|
4200
4274
|
var ML_DSA_DESCRIPTOR_BYTES = 3;
|
|
4201
4275
|
var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
|
|
4202
4276
|
var ML_DSA_87_SIGNATURE_BYTES = 4627;
|
|
4203
|
-
var CURRENT_QRL_ADDRESS_BYTES = 20;
|
|
4204
4277
|
function bytesOrHex(v) {
|
|
4205
4278
|
if (v instanceof Uint8Array) return new Uint8Array(v);
|
|
4206
4279
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -4257,11 +4330,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
|
|
|
4257
4330
|
if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
|
|
4258
4331
|
return false;
|
|
4259
4332
|
}
|
|
4260
|
-
const addressBytes = shake256Digest(
|
|
4261
|
-
|
|
4262
|
-
CURRENT_QRL_ADDRESS_BYTES
|
|
4263
|
-
);
|
|
4264
|
-
const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
|
|
4333
|
+
const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
|
|
4334
|
+
const derived = qip55AddressFromBytes(addressBytes);
|
|
4265
4335
|
return derived.toLowerCase() === expectedSigner.toLowerCase();
|
|
4266
4336
|
}
|
|
4267
4337
|
function verifyMessageForSigner({
|
|
@@ -4331,6 +4401,8 @@ export {
|
|
|
4331
4401
|
PROTOCOL_VERSION,
|
|
4332
4402
|
QRLConnectProvider as QRLConnect,
|
|
4333
4403
|
QRLConnectProvider,
|
|
4404
|
+
QRL_ADDRESS_BYTES,
|
|
4405
|
+
QRL_ADDRESS_HEX_LENGTH,
|
|
4334
4406
|
QRL_CONNECT_PROVIDER_INFO,
|
|
4335
4407
|
RESTRICTED_METHODS,
|
|
4336
4408
|
SCHEME_TAG_MSG,
|
|
@@ -4353,11 +4425,13 @@ export {
|
|
|
4353
4425
|
encodeField,
|
|
4354
4426
|
encodeType,
|
|
4355
4427
|
fingerprintEquals,
|
|
4428
|
+
formatQrlAddressFingerprint,
|
|
4356
4429
|
generateConnectionURI,
|
|
4357
4430
|
getAppStoreUrl,
|
|
4358
4431
|
hasSigningDescriptor,
|
|
4359
4432
|
hashStruct,
|
|
4360
4433
|
hexToBytes,
|
|
4434
|
+
isCurrentQrlAddress,
|
|
4361
4435
|
isMobileBrowser,
|
|
4362
4436
|
isQrlSignedMessageResult,
|
|
4363
4437
|
isQrlSignedResult,
|
|
@@ -4366,6 +4440,7 @@ export {
|
|
|
4366
4440
|
isValidJsonRpcMethod,
|
|
4367
4441
|
normalizeRelayUrl,
|
|
4368
4442
|
parseConnectionURI,
|
|
4443
|
+
qip55AddressFromBytes,
|
|
4369
4444
|
typeHash,
|
|
4370
4445
|
verifyMessage,
|
|
4371
4446
|
verifyMessageForSigner,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qrlwallet/connect",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
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.
|
|
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": "^
|
|
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": "
|
|
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
|
{
|