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