@qrlwallet/connect 3.3.0 → 4.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
@@ -10,7 +10,10 @@ Connect your dApp to QRL Wallet. Users scan a QR code (desktop) or tap a button
10
10
  4. Your dApp sends JSON-RPC requests, the wallet prompts for approval
11
11
  5. Signed results come back to your dApp
12
12
 
13
- All communication is end-to-end encrypted with ML-KEM-768 (FIPS 203) key encapsulation and AES-256-GCM. The relay server never sees your data.
13
+ All application traffic is end-to-end encrypted with ML-KEM-768 (FIPS 203)
14
+ key encapsulation and AES-256-GCM. PQP3 adds a fresh 32-byte pairing
15
+ capability to every QR/deep link. The relay serves the dApp public key, but it
16
+ never receives that capability or application plaintext.
14
17
 
15
18
  ## Install
16
19
 
@@ -18,6 +21,10 @@ All communication is end-to-end encrypted with ML-KEM-768 (FIPS 203) key encapsu
18
21
  npm install @qrlwallet/connect
19
22
  ```
20
23
 
24
+ Package installation and source builds require Node.js 20.19.0 or newer because
25
+ the direct cryptography dependencies use that engine floor. Browser execution
26
+ requires Web Crypto.
27
+
21
28
  Want a ready-made pairing dialog (QR code, deep link, copy-code fallback) instead of building your own? Add the framework-free [`@qrlwallet/connect-ui`](ui/) web component.
22
29
 
23
30
  ## Quick start
@@ -68,11 +75,13 @@ const accounts = await qrl.request({ method: 'qrl_requestAccounts' });
68
75
 
69
76
  const txHash = await qrl.request({
70
77
  method: 'qrl_sendTransaction',
71
- params: [{
72
- from: accounts[0],
73
- to: '0x...',
74
- value: '0x2386F26FC10000', // 0.01 QRL
75
- }],
78
+ params: [
79
+ {
80
+ from: accounts[0],
81
+ to: 'Q20E7Bde67f00EA38ABb2aC57e1B0DD93f518446c',
82
+ value: '0x2386F26FC10000', // 0.01 QRL
83
+ },
84
+ ],
76
85
  });
77
86
  ```
78
87
 
@@ -85,75 +94,120 @@ const qrl = new QRLConnect({
85
94
  name: 'QuantaPool',
86
95
  url: 'https://quantapool.com',
87
96
  icon: 'https://quantapool.com/icon.png', // optional
97
+ redirectUrl: 'https://quantapool.com/return', // optional
88
98
  },
89
99
 
90
100
  // Optional
91
- relayUrl: 'https://qrlwallet.com', // default relay
92
- chainId: '0x0', // QRL chain ID
93
- autoReconnect: true, // reconnect on page load (default: true)
94
- walletRedirectOnRequest: true, // mobile: deep-link the wallet app awake
95
- // for approval requests (default: true)
96
- debug: false, // console logging
101
+ relayUrl: 'https://qrlwallet.com', // default relay
102
+ chainId: '0x0', // QRL chain ID
103
+ autoReconnect: true, // reconnect on page load (default: true)
104
+ walletRedirectOnRequest: true, // mobile: deep-link the wallet app awake
105
+ // for approval requests (default: true)
106
+ debug: false, // console logging
97
107
  });
98
108
  ```
99
109
 
110
+ `dappMetadata.url` and optional `redirectUrl` are canonicalized
111
+ credential-free HTTP(S) URLs. HTTPS is required except on an explicit
112
+ `localhost`, `.localhost`, `127.0.0.1`, or `[::1]` development hostname. A
113
+ custom app scheme is not accepted as a return target.
114
+
100
115
  ## API
101
116
 
102
117
  ### `QRLConnect`
103
118
 
104
119
  The main class. Creates a connection manager and EIP-1193 provider.
105
120
 
106
- | Method | Description |
107
- |--------|-------------|
108
- | `getConnectionURI()` | Returns the `qrlconnect://` URI for QR codes or deep links |
109
- | `request({ method, params })` | Send a JSON-RPC request to the wallet |
110
- | `isMobile()` | Check if the user is on a mobile browser |
111
- | `getAppStoreUrl()` | Get the app store link for QRL Wallet |
112
- | `isConnected()` | Whether the wallet is connected |
113
- | `isPaired()` | Whether a pairing exists, even while the wallet app is backgrounded |
114
- | `isWalletPresent()` | Whether the wallet is currently live in the relay channel |
115
- | `getAccounts()` | Get connected accounts |
116
- | `getStatus()` | Current connection status |
117
- | `hasStoredSession()` | Check if a reconnectable session exists in local storage |
118
- | `newConnection()` | Reset current pairing and generate a new channel/URI |
119
- | `disconnect()` | End the session |
120
- | `getChannelId()` | Get the current relay channel ID (useful for debugging) |
121
+ | Method | Description |
122
+ | ----------------------------- | ------------------------------------------------------------------- |
123
+ | `getConnectionURI()` | Retires any prior pairing and returns a fresh `qrlconnect://` URI |
124
+ | `request({ method, params })` | Send a JSON-RPC request to the wallet |
125
+ | `isMobile()` | Check if the user is on a mobile browser |
126
+ | `getAppStoreUrl()` | Get the app store link for QRL Wallet |
127
+ | `isConnected()` | Whether the wallet is connected |
128
+ | `isPaired()` | Whether a pairing exists, even while the wallet app is backgrounded |
129
+ | `isWalletPresent()` | Whether the wallet is currently live in the relay channel |
130
+ | `getAccounts()` | Get connected accounts |
131
+ | `getStatus()` | Current connection status |
132
+ | `hasStoredSession()` | Check if a reconnectable session exists in local storage |
133
+ | `newConnection()` | Reset current pairing and generate a new channel/URI |
134
+ | `disconnect()` | End the session |
135
+ | `getChannelId()` | Get the current relay channel ID (useful for debugging) |
121
136
 
122
137
  ### Events
123
138
 
124
- | Event | Payload | Description |
125
- |-------|---------|-------------|
126
- | `connect` | `{ chainId }` | Wallet connected |
127
- | `disconnect` | `{ code, message }` | Wallet disconnected |
128
- | `accountsChanged` | `string[]` | Account list changed |
129
- | `chainChanged` | `string` | Chain switched |
130
- | `statusChanged` | `ConnectionStatus` | Intermediate lifecycle states (see values below) |
131
- | `connection_lost` | (none) | Emitted after 5 failed reconnection attempts |
139
+ | Event | Payload | Description |
140
+ | ----------------- | ------------------- | ------------------------------------------------ |
141
+ | `connect` | `{ chainId }` | Wallet connected |
142
+ | `disconnect` | `{ code, message }` | Wallet disconnected |
143
+ | `accountsChanged` | `string[]` | Account list changed |
144
+ | `chainChanged` | `string` | Chain switched |
145
+ | `statusChanged` | `ConnectionStatus` | Intermediate lifecycle states (see values below) |
146
+ | `connection_lost` | (none) | Emitted after 5 failed reconnection attempts |
132
147
 
133
148
  #### `ConnectionStatus` values
134
149
 
135
- | Value | Meaning |
136
- |-------|---------|
137
- | `disconnected` | No active connection |
138
- | `connecting` | Connecting to relay server |
139
- | `waiting` | Waiting for the wallet: a first scan, or (when `isPaired()`) a backgrounded wallet app to return |
140
- | `key_exchange` | Post-quantum key exchange in progress |
141
- | `connected` | Wallet connected, ready for requests |
142
- | `reconnecting` | Attempting to restore a previous session |
150
+ | Value | Meaning |
151
+ | -------------- | ------------------------------------------------------------------------------------------------ |
152
+ | `disconnected` | No active connection |
153
+ | `connecting` | Connecting to relay server |
154
+ | `waiting` | Waiting for the wallet: a first scan, or (when `isPaired()`) a backgrounded wallet app to return |
155
+ | `key_exchange` | Post-quantum key exchange in progress |
156
+ | `connected` | Wallet connected, ready for requests |
157
+ | `reconnecting` | Attempting to restore a previous session |
143
158
 
144
159
  ### Supported RPC methods
145
160
 
146
161
  **Require user approval:**
147
- `qrl_requestAccounts`, `qrl_sendTransaction`, `qrl_signTransaction`, `qrl_signMessage`, `qrl_signTypedData`, `wallet_addQrlChain`, `wallet_switchQrlChain`
148
-
149
- `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, signer, digest, schemeVersion }` object; verify locally with `verifyMessage` / `verifyTypedData` exported from this package.
162
+ `qrl_requestAccounts`, `qrl_sendTransaction`, `qrl_signTransaction`, `qrl_signMessage`, `qrl_signTypedData`, `wallet_switchQrlChain`
163
+
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.
165
+
166
+ `request()` returns `unknown`. First validate the exact response shape with
167
+ `isQrlSignedMessageResult` or `isQrlSignedTypedDataResult`, then require
168
+ `hasSigningDescriptor`. PQP3 authenticates the paired transport session. It
169
+ does not prove that the returned ML-DSA signature is valid or that its public
170
+ 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.
173
+
174
+ The lower-level `verifyMessageSignature` / `verifyTypedDataSignature` helpers
175
+ verify only the supplied key and signature; they do not prove that the key
176
+ belongs to a claimed signer. The old `verifyMessage` / `verifyTypedData` names
177
+ remain as deprecated aliases. `descriptor` remains optional in the response
178
+ type for older-wallet compatibility. Strict shape guards accept its absence,
179
+ while `hasSigningDescriptor` and both bound verifiers fail closed.
180
+
181
+ Approval-bound calls are serialized. Unknown methods and direct node mutation
182
+ surfaces such as `qrl_sendRawTransaction` and `wallet_addQrlChain` are rejected
183
+ locally. Signing and transaction calls also fail locally until exactly one
184
+ account has been approved by `qrl_requestAccounts`. Their signer or `from`
185
+ field must exactly match that approved address.
150
186
 
151
187
  **Auto-proxied (no approval needed):**
152
- `qrl_getBalance`, `qrl_call`, `qrl_estimateGas`, `qrl_blockNumber`, `qrl_chainId`, `qrl_getTransactionReceipt`, and 30+ more read-only methods.
188
+ `qrl_chainId`, `qrl_blockNumber`, `qrl_getBalance`,
189
+ `qrl_getTransactionCount`, `qrl_getBlockByNumber`,
190
+ `qrl_getTransactionReceipt`, `qrl_call`, `qrl_estimateGas`, `qrl_gasPrice`,
191
+ `qrl_getCode`, `qrl_getLogs`, `net_version`, and `net_listening`.
192
+
193
+ `qrl_accounts` is a wallet-local authorization read. It returns `[]` until
194
+ `qrl_requestAccounts` succeeds and is never forwarded to hosted node RPC.
195
+ An account string is not proof of control. For authentication, request a fresh
196
+ challenge with `qrl_signMessage` and verify it with `verifyMessageForSigner`.
153
197
 
154
198
  ## Sessions
155
199
 
156
- Sessions persist in `localStorage` for 7 days. When a user returns to your dApp, the SDK can automatically reconnect without requiring a new QR scan.
200
+ Established v5 sessions persist in `localStorage` for 7 days. When a user
201
+ returns to your dApp, the SDK can automatically reconnect without requiring a
202
+ new QR scan. Pairing capabilities are never persisted. An exclusive Web Lock
203
+ gives one browser tab ownership of the persisted key and AEAD counters. A
204
+ second tab cannot restore or use that stream. Browsers without Web Locks use a
205
+ memory-only session and require a fresh pairing after reload.
206
+
207
+ Malformed handshake frames and ambiguous relay acknowledgements retire the
208
+ affected pairing. A fresh QR starts a new key, channel, and request generation;
209
+ pending work from the prior wallet is rejected locally, and the old relay
210
+ channel must be tombstoned before the new bearer capability is issued.
157
211
 
158
212
  A paired session also survives the wallet app being backgrounded or closed. On the same device at most one of the two apps is ever foregrounded, so the wallet's socket being absent is the normal steady state of a mobile flow, not an error:
159
213
 
@@ -189,6 +243,11 @@ const qrl = new QRLConnect({
189
243
  });
190
244
  ```
191
245
 
246
+ Relay URLs must use HTTPS. Plain HTTP is accepted only for an explicit
247
+ `localhost`, `.localhost`, `127.0.0.1`, or `[::1]` development endpoint. Relay
248
+ URLs identify an origin only: credentials, paths, query strings, and fragments
249
+ are rejected before the URL can be logged, connected, or embedded in a QR.
250
+
192
251
  ## Development
193
252
 
194
253
  ```bash
@@ -234,14 +293,18 @@ The example connects to the production relay at `wss://qrlwallet.com/relay` by d
234
293
 
235
294
  - Private keys and seeds never leave the wallet
236
295
  - All relay traffic is end-to-end encrypted with **AES-256-GCM** bound to a
237
- **transcript hash** derived from the full handshake (`LABEL || cid || pk || ct`)
296
+ **transcript hash** derived from the full handshake
297
+ (`"pq-pair/v3" || cid || pk || ct || capability`)
238
298
  - Session keys are established with **ML-KEM-768** (FIPS 203, NIST Level 3),
239
- carried in the QR code so the relay never sees an uncommitted public key
299
+ with the public key uploaded to the relay and pinned by a full 32-byte
300
+ capability-bound fingerprint in the QR
301
+ - The PQP3 capability is a bearer secret. Do not log, persist, cache, or send
302
+ a pairing URI to analytics. Retire the channel when a pairing UI is cancelled
240
303
  - Ciphertext tampering is detected exclusively at the AES-GCM tag;
241
304
  ML-KEM's implicit rejection is NOT used for authentication
242
305
  - PIN or biometric authentication required for every transaction
243
- - dApp URL is displayed to the user before connecting
244
- - Unknown RPC methods are rejected with `-32601`
306
+ - dApp name and URL are displayed as unverified, dApp-supplied metadata
307
+ - Unknown RPC methods are rejected locally before a relay round trip
245
308
 
246
309
  ## License
247
310