@0xio/sdk 2.7.0 → 2.8.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
@@ -1,356 +1,211 @@
1
1
  # 0xio Wallet SDK
2
2
 
3
- **Version:** 2.7.0
3
+ **Version 2.8.0**
4
4
 
5
- Official TypeScript SDK for integrating DApps with 0xio Wallet on Octra Network.
5
+ The official TypeScript SDK for building dapps on the 0xio Wallet and the Octra Network. It works with the browser extension, inside the 0xio Desktop browser and inside the 0xio App, and picks the right transport on its own.
6
6
 
7
- ## What's New in v2.7.0
7
+ What changed in each release is in [CHANGELOG.md](CHANGELOG.md). The full API is in [DOCUMENTATION.md](DOCUMENTATION.md).
8
8
 
9
- Security hardening release (post-audit remediation):
10
- - **MessageChannel transport** (H-2 fix): Extension now uses a private `MessageChannel` port instead of `window.postMessage`, preventing page scripts from intercepting or injecting wallet messages. SDK validates a per-session nonce on every response.
11
- - **Pluggable wallet adapters**: New `src/supports/` system — implement `WalletTransportAdapter` and drop a file in `supports/` to add any wallet without touching core SDK code. See `src/supports/template.ts` for the starting point.
12
- - Fixed wildcard postMessage target leaking request payloads to any origin (HIGH)
13
- - Removed `Math.random()` fallback for request IDs — throws if `crypto` unavailable (MEDIUM)
14
- - Fixed `requestTimestamps` unbounded memory growth in idle tabs (LOW)
15
- - Removed all `octraWalletReady` legacy event listeners and `createOctraWallet` alias (LOW)
16
- - Extension fixes: balance/network events now delivered end-to-end; `accountChanged` on wallet switch; exact hostname tab matching; approval listener spoofing from content scripts blocked
17
- - All 17 post-audit findings resolved. See `AUDIT_REMEDIATION.md` for full details.
18
-
19
- ## What's New in v2.6.0
20
-
21
- - **`sendPrivateTransfer(to, amount)`**: Send encrypted (stealth) transfers — amount is hidden from everyone except sender and recipient. Uses PVAC-HFHE for ciphertext subtraction + zero-knowledge proofs. The node re-encrypts under the recipient's key.
22
- - **`getPendingPrivateTransfers()`**: List incoming private transfers claimable by your wallet.
23
- - **`claimPrivateTransfer(transferId)`**: Claim a pending private transfer into your encrypted balance.
24
- - **Privacy-first DApps**: Build prediction markets, private payments, stealth bets — all amounts stay encrypted on-chain.
25
- - Requires 0xio Wallet Extension v2.4.0+
26
-
27
- ## v2.5.0
28
-
29
- - **`switchNetwork(networkId)`**: Switch the extension's network silently — no popup, no user action. DApps can detect network mismatch and offer one-click switch (like Rabby).
30
- - **`getNetworkId()`**: Get the extension's current active network.
31
- - **Network-aware DApps**: Detect if user is on mainnet vs devnet, show banner, switch with one click.
32
-
33
- ## v2.4.5
34
-
35
- - **Correct network detection**: SDK reads active network from extension instead of hardcoding mainnet.
36
- - **Full networkInfo in connect response**: Extension returns complete network config.
37
- - **Live balance on reconnect**: Fresh balance from chain instead of stale cached zeros.
38
- - **Transaction history**: Real on-chain transactions with pagination.
39
- - **Event alignment**: `transactionConfirmed` event fires correctly.
40
-
41
- ## v2.4.4
42
-
43
- - **Fix double popup on timeout**: Transaction/signing methods no longer retry, preventing duplicate approval popups.
44
- - **120s timeout for interactive methods**: Users have 2 minutes to review and approve transactions.
45
-
46
- ## v2.4.2
47
-
48
- - **Cross-origin iframe bridge**: Localhost origins now accepted as trusted parents for dev/testing DApp browser scenarios.
49
- - **Parent origin capture**: `walletReady` signal carries `parentOrigin` for reliable cross-origin reply targeting.
50
- - **postMessage fix**: Replies to parent frame use captured origin instead of `window.location.origin` (which fails cross-port).
51
-
52
- ## v2.4.1
53
-
54
- - **No retry on user rejection**: Transactions, contract calls, and sign requests rejected by the user no longer trigger automatic retry. Prevents double confirmation popups.
55
- - **Security hardening**: postMessage origin validation, removed iframe auto-trust, response ID binding.
56
- - **Type fixes**: Replaced `NodeJS.Timeout` with `ReturnType<typeof setTimeout>` for browser compatibility.
57
-
58
- ## v2.4.0
59
-
60
- - **Desktop/Mobile DApp Bridge**: SDK now supports running inside iframes (0xio Desktop) and WebViews (0xio App). Requests are relayed to the parent frame automatically.
61
- - **Auto Frame Detection**: When `window.parent !== window`, the SDK assumes a wallet bridge is available and marks the wallet as detected.
62
- - **Frame-Aware Messaging**: `postMessageToExtension()` posts to both `window` and `window.parent` when running inside a frame; `setupMessageListener()` accepts messages from `window.parent`.
63
- - **walletReady via postMessage**: Extension detection now recognizes `walletReady` events from parent frames.
64
- - **Fully backward compatible** — extension-based DApps work unchanged with no code changes needed.
65
-
66
- ## Installation
9
+ ## Install
67
10
 
68
11
  ```bash
69
12
  npm install @0xio/sdk
70
13
  ```
71
14
 
72
- ## Quick Start
15
+ ## Quick start
73
16
 
74
17
  ```typescript
75
18
  import { ZeroXIOWallet } from '@0xio/sdk';
76
19
 
77
- // 1. Initialize
78
20
  const wallet = new ZeroXIOWallet({
79
21
  appName: 'My DApp',
80
- requiredPermissions: ['read_balance', 'sign_messages']
22
+ requiredPermissions: ['accounts', 'public_transactions'],
81
23
  });
82
24
 
83
25
  await wallet.initialize();
84
26
 
85
- // 2. Connect
27
+ // Connect. The wallet shows an approval the first time.
86
28
  const connection = await wallet.connect();
87
- console.log('Connected:', connection.address);
88
- console.log('Public Key:', connection.publicKey); // Base64 Ed25519 key
29
+ console.log(connection.address);
30
+ console.log(connection.publicKey); // base64 Ed25519 key
89
31
 
90
- // 3. Get Balance
32
+ // Balance, in OCT.
91
33
  const balance = await wallet.getBalance();
92
- console.log('Total:', balance.total, 'OCT');
34
+ console.log(balance.public, balance.private, balance.total);
93
35
 
94
- // 4. Sign a Message
36
+ // Sign a message. The wallet frames it (see Message signing below).
95
37
  const signature = await wallet.signMessage('Hello, 0xio!');
96
- console.log('Signature:', signature);
97
38
 
98
- // 5. Send Transaction
39
+ // Send 10.5 OCT.
99
40
  const result = await wallet.sendTransaction({
100
41
  to: 'oct1recipient...',
101
- amount: 10.5,
102
- message: 'Payment'
42
+ amountOct: '10.5',
43
+ message: 'Payment',
103
44
  });
104
- console.log('TX Hash:', result.txHash);
45
+ console.log(result.hash);
105
46
  ```
106
47
 
107
- ## API Reference
48
+ ## Amounts and units
108
49
 
109
- ### Connection
50
+ The wallet counts in raw micro-OCT: one OCT is 1000000 units. Every raw field is passed to the wallet exactly as you give it, so existing integrations keep working. Where you would rather write OCT, use the OCT field and the SDK converts it with exact decimal arithmetic.
110
51
 
111
- #### `wallet.initialize(): Promise<boolean>`
112
- Initialize the SDK. Must be called first.
52
+ | Method | Raw field (sent as is) | OCT field (converted by the SDK) |
53
+ |--------|------------------------|----------------------------------|
54
+ | `sendTransaction`, `signTransaction` | `amount` | `amountOct` |
55
+ | `callContract` | `amount` | `amountOct` |
56
+ | `sendPrivateTransfer` | `amountRaw` | `amount` |
57
+ | `getBalance` | | returns OCT |
113
58
 
114
- #### `wallet.connect(options?): Promise<ConnectEvent>`
115
- Connect to the user's wallet. Shows approval popup if first time.
59
+ Pass one field or the other, not both. A number that cannot be written in six decimals (such as `0.1 + 0.2`) is rejected; pass a string.
116
60
 
117
- #### `wallet.disconnect(): Promise<void>`
118
- Disconnect from the wallet.
61
+ ## Permissions
119
62
 
120
- #### `wallet.isConnected(): boolean`
121
- Check if currently connected.
63
+ Ask for what the dapp uses. The wallet enforces these names:
122
64
 
123
- ### Balance
65
+ | Scope | Grants |
66
+ |-------|--------|
67
+ | `accounts` | address, balance, public key |
68
+ | `public_transactions` | sends and message signing |
69
+ | `contract_calls` | state-changing contract calls |
70
+ | `contract_views` | read-only contract calls |
71
+ | `private_balance_read` | private balance, decrypting values, pending transfers |
72
+ | `private_proofs` | encrypting values, zero and range proofs |
73
+ | `private_transfers` | sending private transfers |
74
+ | `private_claims` | claiming private transfers |
124
75
 
125
- #### `wallet.getBalance(forceRefresh?: boolean): Promise<Balance>`
126
- Get wallet balance (public + private).
76
+ The private scopes show a warning in the connection dialog. Older names such as `read_balance` or `stealth_claim` still work: the SDK translates them, and they come back in the granted list as aliases.
127
77
 
128
- ```typescript
129
- interface Balance {
130
- public: number; // Visible on-chain balance
131
- private: number; // Encrypted (FHE) balance
132
- total: number; // public + private
133
- currency: 'OCT';
134
- }
135
- ```
78
+ ## API map
136
79
 
137
- ### Transactions
80
+ Connection: `initialize()`, `connect(options?)`, `disconnect()`, `isConnected()`, `getConnectionStatus()`, `getAddress()`, `getPublicKey()`.
138
81
 
139
- #### `wallet.sendTransaction(txData): Promise<TransactionResult>`
140
- Send a transaction. Returns result with transaction finality status.
82
+ Balance and network: `getBalance(forceRefresh?)`, `getNetworkInfo()`, `getNetworkId()`, `switchNetwork(id)` (connected dapps only; no popup).
141
83
 
142
- ```typescript
143
- interface TransactionData {
144
- to: string; // Recipient address (oct1...)
145
- amount: number; // Amount in OCT
146
- message?: string; // Optional memo
147
- }
84
+ Transactions: `sendTransaction(data)`, `signTransaction(data)` then `submitTransaction(signedTx)`, `getTransactionHistory(page?, limit?)`.
148
85
 
149
- interface TransactionResult {
150
- txHash: string;
151
- success: boolean;
152
- finality?: 'pending' | 'confirmed' | 'rejected';
153
- message?: string;
154
- explorerUrl?: string;
155
- }
156
- ```
86
+ Contracts: `callContract(data)` (signed, approval shown), `contractCallView(data)` (read-only, no approval), `getContractStorage(contract, key)`, `sendContractTransactionSequence(data)` (several calls under one approval; every step is listed in it).
157
87
 
158
- ### Smart Contracts
88
+ Messages: `signMessage(message)`, `signAuthMessage(service, nonce)`, and the verifiers `verifyMessage`, `getSignedMessageBytes`, `buildAuthMessage`.
159
89
 
160
- #### `wallet.callContract(data: ContractCallData): Promise<TransactionResult>`
161
- Execute a state-changing contract call. The extension signs and submits via `octra_submit`.
90
+ Private: `sendPrivateTransfer(data)`, `getPendingPrivateTransfers()`, `claimPrivateTransfer(id)` (approval shown), `getPrivateBalanceInfo()`, `registerPrivateViewKey({ address })`.
162
91
 
163
- ```typescript
164
- const result = await wallet.callContract({
165
- contract: 'oct26Lia...', // Contract address
166
- method: 'swap', // AML method name
167
- params: [100, true, 90], // Method arguments (flat, not array-wrapped)
168
- amount: '0', // Native OCT to send (optional, default '0')
169
- ou: '10000', // Operational units (optional, default '10000')
170
- });
171
- console.log('TX Hash:', result.txHash);
172
- ```
92
+ Private primitives (2.8.0): `getPrivateCapabilities()`, `encryptValue()`, `decryptValue()`, `makeZeroProof()`, `makeRangeProof()`, `getPrivateBalance()`. Keys never leave the wallet; the dapp receives ciphertexts, proofs and hashes.
173
93
 
174
- #### `wallet.contractCallView(data: ContractViewCallData): Promise<any>`
175
- Read-only contract query. No signing, no approval popup, no wallet unlock required.
94
+ Passthrough: `request(method, params)` sends any wallet method; `rpcCall(method, params)` reaches the wallet's read-only node RPC allow-list, such as `octra_balance`.
176
95
 
177
- ```typescript
178
- const price = await wallet.contractCallView({
179
- contract: 'oct26Lia...',
180
- method: 'get_active_price',
181
- params: [],
182
- });
183
- console.log('Price:', price);
184
- ```
96
+ `encryptBalance` and `decryptBalance` remain for other wallets; the 0xio extension answers `NOT_AVAILABLE`, and users encrypt from its Privacy screen.
185
97
 
186
- #### `wallet.getContractStorage(contract: string, key: string): Promise<string | null>`
187
- Read contract storage by key.
98
+ ## Message signing
188
99
 
189
- ```typescript
190
- const value = await wallet.getContractStorage('oct26Lia...', 'total_supply');
191
- console.log('Total supply:', value);
192
- ```
100
+ The wallet never signs a raw message. It signs a framed payload, so a signed message can never be a transaction (a transaction is JSON and starts with `{`):
193
101
 
194
- ### Message Signing
102
+ ```
103
+ "Octra Signed Message:\n" + utf8ByteLength(message) + "\n" + message
104
+ ```
195
105
 
196
- #### `wallet.signMessage(message: string): Promise<string>`
197
- Sign an arbitrary message with the wallet's private key. User will be prompted to approve.
106
+ The signature is an Ed25519 detached signature over the UTF-8 bytes of that string. Verify it with the SDK:
198
107
 
199
108
  ```typescript
200
- // Sign a message for authentication
201
- const message = `Login to MyDApp\nTimestamp: ${Date.now()}`;
202
- const signature = await wallet.signMessage(message);
109
+ import { verifyMessage } from '@0xio/sdk';
203
110
 
204
- // Signature is base64-encoded Ed25519
205
- console.log('Signature:', signature);
111
+ const publicKey = await wallet.getPublicKey();
112
+ const ok = await verifyMessage(message, signature, publicKey);
206
113
  ```
207
114
 
208
- **Use cases:**
209
- - Prove wallet ownership for API authentication
210
- - Sign login challenges
211
- - Authorize off-chain actions
212
- - Create verifiable attestations
115
+ Or with any Ed25519 library, over the exact bytes from `getSignedMessageBytes(message)`. For `signAuthMessage(service, nonce)`, rebuild the signed string with `buildAuthMessage(service, nonce, origin)` first.
213
116
 
214
- ### Events
117
+ ## Events
215
118
 
216
119
  ```typescript
217
120
  wallet.on('connect', (event) => console.log('Connected:', event.data.address));
218
- wallet.on('disconnect', (event) => console.log('Disconnected'));
219
- wallet.on('balanceChanged', (event) => console.log('New balance:', event.data.newBalance.total));
220
- wallet.on('accountChanged', (event) => console.log('Account changed:', event.data.newAddress));
121
+ wallet.on('disconnect', (event) => console.log('Disconnected:', event.data.reason));
122
+ wallet.on('accountChanged', (event) => console.log('Account:', event.data.newAddress));
123
+ wallet.on('balanceChanged', (event) => console.log('Balance:', event.data.newBalance.total));
221
124
  wallet.on('networkChanged', (event) => console.log('Network:', event.data.newNetwork.name));
125
+ wallet.on('transactionConfirmed', (event) => console.log('Confirmed:', event.data.txHash));
126
+ wallet.on('transactionFailed', (event) => console.log('Failed:', event.data.error));
127
+ wallet.on('extensionLocked', () => console.log('Locked'));
222
128
  ```
223
129
 
224
- ## Error Handling
130
+ ## Errors
131
+
132
+ Every failure is a `ZeroXIOWalletError` with a `code`.
225
133
 
226
134
  ```typescript
227
135
  import { ZeroXIOWalletError, ErrorCode } from '@0xio/sdk';
228
136
 
229
137
  try {
230
- const result = await wallet.sendTransaction({ to: 'oct1...', amount: 10 });
138
+ await wallet.sendTransaction({ to: 'oct1...', amountOct: '10' });
231
139
  } catch (error) {
232
140
  if (error instanceof ZeroXIOWalletError) {
233
141
  switch (error.code) {
234
- case ErrorCode.USER_REJECTED:
235
- console.log('User rejected the request');
236
- break;
237
- case ErrorCode.INSUFFICIENT_BALANCE:
238
- console.log('Not enough balance');
239
- break;
240
- case ErrorCode.INVALID_SIGNATURE:
241
- console.log('Invalid transaction signature');
242
- break;
243
- case ErrorCode.DUPLICATE_TRANSACTION:
244
- console.log('Transaction already submitted');
245
- break;
246
- case ErrorCode.SELF_TRANSFER:
247
- console.log('Cannot send to yourself');
248
- break;
249
- case ErrorCode.NONCE_TOO_FAR:
250
- console.log('Transaction nonce too far ahead');
251
- break;
252
- case ErrorCode.WALLET_LOCKED:
253
- console.log('Please unlock your wallet');
254
- break;
142
+ case ErrorCode.NOT_CONNECTED: // connect() first
143
+ case ErrorCode.WALLET_LOCKED: // the user has to unlock the wallet
144
+ case ErrorCode.USER_REJECTED: // the user declined the approval
145
+ case ErrorCode.INVALID_AMOUNT: // bad unit or precision
146
+ case ErrorCode.NONCE_TOO_FAR: // node rejected the transaction
147
+ console.log(error.code, error.message);
255
148
  }
256
149
  }
257
150
  }
258
151
  ```
259
152
 
260
- ## Networks
153
+ The wallet refuses anything that signs, submits or changes state until the page has connected, and opens its unlock screen at most once a minute for a page that is not connected.
261
154
 
262
- The SDK ships with built-in configurations for Octra networks:
155
+ ## Networks
263
156
 
264
157
  ```typescript
265
- import { NETWORKS, getNetworkConfig } from '@0xio/sdk';
158
+ import { getNetworkConfig } from '@0xio/sdk';
266
159
 
267
- // Get devnet config
268
160
  const devnet = getNetworkConfig('devnet');
269
- console.log(devnet.rpcUrl); // http://165.227.225.79:8080
270
- console.log(devnet.supportsPrivacy); // true
271
- console.log(devnet.isTestnet); // true
272
-
273
- // Get mainnet config
274
- const mainnet = getNetworkConfig('mainnet');
275
- console.log(mainnet.rpcUrl); // http://46.101.86.250:8080
276
- console.log(mainnet.supportsPrivacy); // true
277
- ```
278
-
279
- | Network | Privacy (FHE) | Explorer |
280
- |---------|:---:|---|
281
- | Mainnet Alpha | Yes | [octrascan.io](https://octrascan.io) |
282
- | Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
283
-
284
- ### NetworkInfo Type
285
-
286
- ```typescript
287
- interface NetworkInfo {
288
- id: string;
289
- name: string;
290
- rpcUrl: string;
291
- explorerUrl?: string; // Transaction explorer base URL
292
- explorerAddressUrl?: string; // Address explorer base URL
293
- indexerUrl?: string; // Indexer/API base URL
294
- supportsPrivacy: boolean; // FHE encrypted balance support
295
- color: string; // Brand color hex
296
- isTestnet: boolean;
297
- }
161
+ console.log(devnet.rpcUrl); // https://devnet.octrascan.io
162
+ console.log(devnet.isTestnet); // true
298
163
  ```
299
164
 
300
- ## Desktop & Mobile Support
165
+ | Network | RPC | Explorer | Privacy |
166
+ |---------|-----|----------|---------|
167
+ | Mainnet | `https://octra.network` | [octrascan.io](https://octrascan.io) | yes |
168
+ | Devnet | `https://devnet.octrascan.io` | [devnet.octrascan.io](https://devnet.octrascan.io) | yes |
301
169
 
302
- The SDK automatically detects when your DApp is running inside:
170
+ `connect()` returns the wallet's active network in `networkInfo`; the SDK never assumes one.
303
171
 
304
- - **0xio Desktop's built-in browser** — Your DApp is loaded in an iframe. The SDK detects `window.parent !== window` and relays all requests to the desktop wallet via the iframe bridge.
305
- - **0xio App's built-in browser** — Your DApp is loaded in a WebView. The SDK communicates with the mobile wallet via the existing WebView bridge.
306
- - **0xio Wallet Extension** — Standard browser extension communication (unchanged).
172
+ ## Where it runs
307
173
 
308
- No code changes are needed for DApp developers. Just use the SDK as normal and it will auto-detect the environment and choose the correct transport.
174
+ - **Browser extension.** The page talks to the extension over a private message channel that page scripts cannot read or forge.
175
+ - **0xio Desktop.** The dapp runs in an iframe and the SDK relays requests to the desktop wallet. Set `trustedParentOrigins` in production; localhost is trusted by default for development.
176
+ - **0xio App.** The dapp runs in a WebView and the SDK uses the app's bridge.
309
177
 
310
- ## Wallet Adapters
178
+ No code changes are needed; the SDK detects the environment.
311
179
 
312
- The SDK ships with a pluggable adapter system so multiple wallets can be supported without changing core code.
180
+ ## Wallet adapters
313
181
 
314
- ### Auto-detect
182
+ Transport is pluggable, so another wallet can be supported without touching the core.
315
183
 
316
184
  ```typescript
317
- import { detectWalletAdapter, ZeroXIOWallet } from '@0xio/sdk';
185
+ import { detectWalletAdapter, ZeroXIOWallet, OctraProviderAdapter } from '@0xio/sdk';
318
186
 
187
+ // Detect: the 0xio bridge first, then any RFC-O-1 provider on window.octra.
319
188
  const adapter = detectWalletAdapter();
320
- if (!adapter) throw new Error('No supported wallet found');
321
-
322
189
  const wallet = new ZeroXIOWallet({ appName: 'My DApp', adapter });
323
- ```
324
190
 
325
- ### Pass an adapter explicitly
326
-
327
- ```typescript
328
- import { ZeroXIOWallet, ZeroXIOAdapter } from '@0xio/sdk';
329
-
330
- const wallet = new ZeroXIOWallet({ appName: 'My DApp', adapter: ZeroXIOAdapter });
191
+ // Or choose one explicitly.
192
+ const rfc = new ZeroXIOWallet({ appName: 'My DApp', adapter: OctraProviderAdapter });
331
193
  ```
332
194
 
333
- ### Add support for another wallet
195
+ The 0xio bridge carries every SDK method. The RFC-O-1 provider carries what the RFC names, so `getBalance`, `getPublicKey`, `getTransactionHistory`, `getContractStorage`, `getPendingPrivateTransfers` and `rpcCall` have no equivalent there. The table in DOCUMENTATION.md lists coverage per method.
334
196
 
335
- 1. Copy `src/supports/template.ts` `src/supports/my-wallet.ts`
336
- 2. Fill in the four constants (`REQUEST_SOURCE`, `RESPONSE_SOURCE`, `WINDOW_KEY`, `READY_EVENT`) and the message-shape mapping
337
- 3. Register it in `src/supports/index.ts` `REGISTERED_ADAPTERS`
338
- 4. Export it from `src/index.ts` if you want it in the public API
197
+ To add a wallet, copy `src/supports/template.ts`, fill in its constants and message mapping, register it in `src/supports/index.ts`, and export it from `src/index.ts` if it belongs in the public API.
339
198
 
340
- See `DOCUMENTATION.md → Wallet Adapter System` for the full interface spec.
199
+ ## Timeouts
341
200
 
342
- ## Requirements
343
-
344
- - 0xio Wallet Extension v2.0.1 or higher (Mainnet Alpha)
345
- - 0xio Wallet Extension v2.2.1 or higher (Devnet — required for contract calls and privacy features)
346
- - 0xio Desktop v1.0+ (for iframe bridge support)
347
- - 0xio App v1.0+ (for WebView bridge support)
348
- - Modern browser (Chrome, Firefox, Edge, Brave)
201
+ Approvals wait up to three minutes. A private transfer waits up to ten, because the wallet builds proofs after the approval. Interactive methods are never retried, so a slow approval never produces a second popup.
349
202
 
350
- ## Documentation
203
+ ## Requirements
351
204
 
352
- See [DOCUMENTATION.md](DOCUMENTATION.md) for complete API reference.
205
+ - 0xio Wallet Extension 2.5.5 or newer for the 2.8.0 private primitives and framed message signing; 2.4.0 or newer for private transfers and claims
206
+ - 0xio Desktop 1.0 or newer for the iframe bridge, 0xio App 1.0 or newer for the WebView bridge
207
+ - A current Chromium-based browser (Chrome, Edge, Brave) or Firefox
353
208
 
354
209
  ## License
355
210
 
356
- MIT License. Copyright 2026 0xio Labs.
211
+ MIT License. Copyright 2026 0xio Labs.