@qrlwallet/connect 3.2.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 +123 -49
- package/dist/index.d.mts +375 -70
- package/dist/index.d.ts +375 -70
- package/dist/index.js +2893 -882
- package/dist/index.mjs +2870 -882
- package/package.json +8 -3
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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,71 +94,126 @@ 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',
|
|
92
|
-
chainId: '0x0',
|
|
93
|
-
autoReconnect: true,
|
|
94
|
-
|
|
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
|
|
95
107
|
});
|
|
96
108
|
```
|
|
97
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
|
+
|
|
98
115
|
## API
|
|
99
116
|
|
|
100
117
|
### `QRLConnect`
|
|
101
118
|
|
|
102
119
|
The main class. Creates a connection manager and EIP-1193 provider.
|
|
103
120
|
|
|
104
|
-
| Method
|
|
105
|
-
|
|
106
|
-
| `getConnectionURI()`
|
|
107
|
-
| `request({ method, params })` | Send a JSON-RPC request to the wallet
|
|
108
|
-
| `isMobile()`
|
|
109
|
-
| `getAppStoreUrl()`
|
|
110
|
-
| `isConnected()`
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
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) |
|
|
117
136
|
|
|
118
137
|
### Events
|
|
119
138
|
|
|
120
|
-
| Event
|
|
121
|
-
|
|
122
|
-
| `connect`
|
|
123
|
-
| `disconnect`
|
|
124
|
-
| `accountsChanged` | `string[]`
|
|
125
|
-
| `chainChanged`
|
|
126
|
-
| `statusChanged`
|
|
127
|
-
| `connection_lost` |
|
|
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 |
|
|
128
147
|
|
|
129
148
|
#### `ConnectionStatus` values
|
|
130
149
|
|
|
131
|
-
| Value
|
|
132
|
-
|
|
133
|
-
| `disconnected` | No active connection
|
|
134
|
-
| `connecting`
|
|
135
|
-
| `waiting`
|
|
136
|
-
| `key_exchange` | Post-quantum key exchange in progress
|
|
137
|
-
| `connected`
|
|
138
|
-
| `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 |
|
|
139
158
|
|
|
140
159
|
### Supported RPC methods
|
|
141
160
|
|
|
142
161
|
**Require user approval:**
|
|
143
|
-
`qrl_requestAccounts`, `qrl_sendTransaction`, `qrl_signTransaction`, `qrl_signMessage`, `qrl_signTypedData`, `
|
|
144
|
-
|
|
145
|
-
`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
|
|
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.
|
|
146
186
|
|
|
147
187
|
**Auto-proxied (no approval needed):**
|
|
148
|
-
`
|
|
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`.
|
|
149
197
|
|
|
150
198
|
## Sessions
|
|
151
199
|
|
|
152
|
-
|
|
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.
|
|
211
|
+
|
|
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:
|
|
213
|
+
|
|
214
|
+
- `request()` still works while the wallet is away: the relay buffers the encrypted request for up to 5 minutes and the wallet drains it when it re-joins.
|
|
215
|
+
- On mobile browsers, an approval-needing request also fires a `qrlconnect://` deep link that brings the wallet app to the foreground (disable with `walletRedirectOnRequest: false`). `qrl_requestAccounts` never redirects: dApps call it on page load without a user gesture.
|
|
216
|
+
- Use `isPaired()` to tell "wallet away, session intact" apart from "never paired" when `getStatus()` is not `connected`.
|
|
153
217
|
|
|
154
218
|
Recommended lifecycle:
|
|
155
219
|
|
|
@@ -157,6 +221,7 @@ Recommended lifecycle:
|
|
|
157
221
|
- Keep a single `QRLConnect` instance for the page lifetime
|
|
158
222
|
- Use `newConnection()` when the user explicitly wants to pair a different wallet
|
|
159
223
|
- Use `statusChanged` for UI state transitions instead of relying on internals
|
|
224
|
+
- On a `disconnect` event, check `hasStoredSession()`: if a session is still stored the wallet is merely unreachable (revived by any request); only re-pair when it is gone
|
|
160
225
|
|
|
161
226
|
## How the relay works
|
|
162
227
|
|
|
@@ -178,6 +243,11 @@ const qrl = new QRLConnect({
|
|
|
178
243
|
});
|
|
179
244
|
```
|
|
180
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
|
+
|
|
181
251
|
## Development
|
|
182
252
|
|
|
183
253
|
```bash
|
|
@@ -204,7 +274,7 @@ node test-e2e.mjs
|
|
|
204
274
|
|
|
205
275
|
The `example/` directory contains a Vite test dApp with QR code generation, transaction sending, message signing, and read-only RPC calls.
|
|
206
276
|
|
|
207
|
-
> **Want to try it without any setup?** The same example is hosted live at [zondscan.com/dapp-example](https://zondscan.com/dapp-example)
|
|
277
|
+
> **Want to try it without any setup?** The same example is hosted live at [zondscan.com/dapp-example](https://zondscan.com/dapp-example): it pairs with the production relay and MyQRLWallet mobile app out of the box.
|
|
208
278
|
|
|
209
279
|
```bash
|
|
210
280
|
# 1. Build the SDK first (the example links to it locally)
|
|
@@ -223,14 +293,18 @@ The example connects to the production relay at `wss://qrlwallet.com/relay` by d
|
|
|
223
293
|
|
|
224
294
|
- Private keys and seeds never leave the wallet
|
|
225
295
|
- All relay traffic is end-to-end encrypted with **AES-256-GCM** bound to a
|
|
226
|
-
**transcript hash** derived from the full handshake
|
|
296
|
+
**transcript hash** derived from the full handshake
|
|
297
|
+
(`"pq-pair/v3" || cid || pk || ct || capability`)
|
|
227
298
|
- Session keys are established with **ML-KEM-768** (FIPS 203, NIST Level 3),
|
|
228
|
-
|
|
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
|
|
229
303
|
- Ciphertext tampering is detected exclusively at the AES-GCM tag;
|
|
230
304
|
ML-KEM's implicit rejection is NOT used for authentication
|
|
231
305
|
- PIN or biometric authentication required for every transaction
|
|
232
|
-
- dApp URL
|
|
233
|
-
- Unknown RPC methods are rejected
|
|
306
|
+
- dApp name and URL are displayed as unverified, dApp-supplied metadata
|
|
307
|
+
- Unknown RPC methods are rejected locally before a relay round trip
|
|
234
308
|
|
|
235
309
|
## License
|
|
236
310
|
|