walletpair-sdk 1.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +415 -0
  3. package/dist/ble/framing.d.ts +23 -0
  4. package/dist/ble/framing.d.ts.map +1 -0
  5. package/dist/ble/framing.js +83 -0
  6. package/dist/ble/framing.js.map +1 -0
  7. package/dist/ble/index.d.ts +9 -0
  8. package/dist/ble/index.d.ts.map +1 -0
  9. package/dist/ble/index.js +9 -0
  10. package/dist/ble/index.js.map +1 -0
  11. package/dist/ble/web-ble-transport.d.ts +29 -0
  12. package/dist/ble/web-ble-transport.d.ts.map +1 -0
  13. package/dist/ble/web-ble-transport.js +93 -0
  14. package/dist/ble/web-ble-transport.js.map +1 -0
  15. package/dist/crypto.d.ts +102 -0
  16. package/dist/crypto.d.ts.map +1 -0
  17. package/dist/crypto.js +279 -0
  18. package/dist/crypto.js.map +1 -0
  19. package/dist/dapp-session.d.ts +106 -0
  20. package/dist/dapp-session.d.ts.map +1 -0
  21. package/dist/dapp-session.js +918 -0
  22. package/dist/dapp-session.js.map +1 -0
  23. package/dist/emitter.d.ts +16 -0
  24. package/dist/emitter.d.ts.map +1 -0
  25. package/dist/emitter.js +41 -0
  26. package/dist/emitter.js.map +1 -0
  27. package/dist/evm/eip1193.d.ts +83 -0
  28. package/dist/evm/eip1193.d.ts.map +1 -0
  29. package/dist/evm/eip1193.js +270 -0
  30. package/dist/evm/eip1193.js.map +1 -0
  31. package/dist/evm/index.d.ts +8 -0
  32. package/dist/evm/index.d.ts.map +1 -0
  33. package/dist/evm/index.js +8 -0
  34. package/dist/evm/index.js.map +1 -0
  35. package/dist/evm/wagmi.d.ts +118 -0
  36. package/dist/evm/wagmi.d.ts.map +1 -0
  37. package/dist/evm/wagmi.js +205 -0
  38. package/dist/evm/wagmi.js.map +1 -0
  39. package/dist/index.d.ts +22 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +24 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/types.d.ts +225 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +31 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/wallet-session.d.ts +107 -0
  48. package/dist/wallet-session.d.ts.map +1 -0
  49. package/dist/wallet-session.js +794 -0
  50. package/dist/wallet-session.js.map +1 -0
  51. package/dist/ws-transport.d.ts +29 -0
  52. package/dist/ws-transport.d.ts.map +1 -0
  53. package/dist/ws-transport.js +79 -0
  54. package/dist/ws-transport.js.map +1 -0
  55. package/package.json +55 -0
  56. package/src/__tests__/adversarial/crypto-attacks.test.ts +557 -0
  57. package/src/__tests__/adversarial/malicious-dapp.test.ts +505 -0
  58. package/src/__tests__/adversarial/malicious-relay.test.ts +528 -0
  59. package/src/__tests__/adversarial/malicious-wallet.test.ts +467 -0
  60. package/src/__tests__/spec-compliance/canonical-json.test.ts +227 -0
  61. package/src/__tests__/spec-compliance/crypto-vectors.test.ts +321 -0
  62. package/src/__tests__/spec-compliance/message-format.test.ts +356 -0
  63. package/src/__tests__/spec-compliance/sequence-numbers.test.ts +300 -0
  64. package/src/__tests__/spec-compliance/state-machine.test.ts +364 -0
  65. package/src/ble/framing.test.ts +196 -0
  66. package/src/ble/framing.ts +100 -0
  67. package/src/ble/index.ts +18 -0
  68. package/src/ble/web-ble-transport.test.ts +192 -0
  69. package/src/ble/web-ble-transport.ts +116 -0
  70. package/src/ble/web-bluetooth.d.ts +47 -0
  71. package/src/canonical-json.test.ts +612 -0
  72. package/src/crypto-directional.test.ts +263 -0
  73. package/src/crypto-hardening.test.ts +529 -0
  74. package/src/crypto.test.ts +635 -0
  75. package/src/crypto.ts +405 -0
  76. package/src/dapp-session.test.ts +647 -0
  77. package/src/dapp-session.ts +1004 -0
  78. package/src/emitter.test.ts +169 -0
  79. package/src/emitter.ts +45 -0
  80. package/src/evm/eip1193.test.ts +365 -0
  81. package/src/evm/eip1193.ts +346 -0
  82. package/src/evm/index.ts +19 -0
  83. package/src/evm/wagmi.test.ts +396 -0
  84. package/src/evm/wagmi.ts +321 -0
  85. package/src/index.ts +86 -0
  86. package/src/integration.test.ts +385 -0
  87. package/src/security.test.ts +430 -0
  88. package/src/sequence-validation.test.ts +1185 -0
  89. package/src/test-helpers.ts +216 -0
  90. package/src/types.test.ts +82 -0
  91. package/src/types.ts +305 -0
  92. package/src/wallet-session.test.ts +683 -0
  93. package/src/wallet-session.ts +922 -0
  94. package/src/ws-transport.test.ts +231 -0
  95. package/src/ws-transport.ts +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,415 @@
1
+ # walletpair-sdk
2
+
3
+ TypeScript SDK for the [WalletPair Protocol](../walletpair-protocol-v1.md) -- connect dApps and wallets with end-to-end encrypted, relay-based or BLE communication.
4
+
5
+ ## Features
6
+
7
+ - **Chain-agnostic core** -- uses [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) chain IDs (`eip155:1`, `solana:mainnet`), ready for multi-chain
8
+ - **EVM support** -- EIP-1193 provider + wagmi connector (under `walletpair-sdk/evm`)
9
+ - **Transport-agnostic** -- WebSocket relay and Web Bluetooth (BLE) transports included, pluggable `Transport` interface for custom transports
10
+ - **End-to-end encrypted** -- X25519 key exchange + ChaCha20-Poly1305 AEAD, relay never sees payload content
11
+ - **Session snapshots** -- `serialize()` / `restore()` for controlled reconnect flows; production crash recovery requires write-ahead counter persistence
12
+ - **Zero native dependencies** -- pure JS crypto via [noble](https://github.com/paulmillr/noble-curves) libraries
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ walletpair-sdk
18
+ ├── Core (chain-agnostic)
19
+ │ ├── crypto.ts X25519, HKDF, ChaCha20-Poly1305, seal/unseal
20
+ │ ├── types.ts Transport interface, CAIP-2 helpers, protocol messages
21
+ │ ├── emitter.ts Typed event emitter
22
+ │ ├── ws-transport.ts WebSocket transport (browser/Node/Deno/Bun)
23
+ │ ├── dapp-session.ts DApp-side session state machine
24
+ │ └── wallet-session.ts Wallet-side session state machine
25
+ ├── BLE (walletpair-sdk/ble)
26
+ │ ├── framing.ts BLE message fragmentation/reassembly (Section 19.5)
27
+ │ └── web-ble-transport.ts Web Bluetooth Central transport (runtime detection)
28
+ └── EVM (walletpair-sdk/evm)
29
+ ├── eip1193.ts EIP-1193 provider (maps eth_ methods to WalletPair)
30
+ └── wagmi.ts wagmi connector factory
31
+ ```
32
+
33
+ ### Data Flow
34
+
35
+ ```
36
+ ┌──────────┐ ┌──────────┐
37
+ │ dApp │ │ Wallet │
38
+ │ │ │ │
39
+ │ DAppSession WalletSession │
40
+ │ │ │ │ │ │
41
+ │ ▼ │ ┌──────────────┐ │ ▼ │
42
+ │ Transport├────►│ Relay / BLE │◄────────┤Transport │
43
+ │ │ └──────────────┘ │ │
44
+ └──────────┘ (sees only routing └──────────┘
45
+ metadata -- payloads
46
+ are E2E encrypted)
47
+ ```
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ npm install walletpair-sdk
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ### DApp Side (Vanilla JS/TS)
58
+
59
+ ```ts
60
+ import { DAppSession, WebSocketTransport } from 'walletpair-sdk'
61
+
62
+ const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
63
+ const session = new DAppSession({
64
+ transport,
65
+ meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
66
+ })
67
+
68
+ // 1. Create pairing -- display the URI as a QR code
69
+ const uri = await session.createPairing()
70
+ console.log('Scan this:', uri)
71
+
72
+ // 2. When wallet joins, show session fingerprint for visual verification
73
+ // (DApp auto-accepts after sealed_join verification)
74
+ session.on('sessionFingerprint', (fingerprint) => {
75
+ console.log('Session fingerprint:', fingerprint)
76
+ // Display to user so they can verify it matches wallet display
77
+ })
78
+
79
+ // 3. Once connected, send requests
80
+ session.on('phase', async (phase) => {
81
+ if (phase === 'connected') {
82
+ const accounts = await session.request('wallet_getAccounts')
83
+ console.log('Accounts:', accounts)
84
+ }
85
+ })
86
+
87
+ // 4. Listen for wallet events
88
+ session.on('event', ({ event, data }) => {
89
+ console.log(`Event: ${event}`, data)
90
+ })
91
+ ```
92
+
93
+ ### Wallet Side (JS/TS / React Native)
94
+
95
+ ```ts
96
+ import { WalletSession, WebSocketTransport } from 'walletpair-sdk'
97
+
98
+ const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
99
+ const session = new WalletSession({
100
+ transport,
101
+ capabilities: {
102
+ methods: ['wallet_getAccounts', 'wallet_signMessage'],
103
+ events: ['accountsChanged', 'chainChanged'],
104
+ chains: ['eip155:1', 'eip155:137'],
105
+ },
106
+ meta: { name: 'My Wallet', description: 'Example Wallet', url: 'https://mywallet.app', icon: 'https://mywallet.app/icon.png' },
107
+ })
108
+
109
+ // 1. Join from pairing URI (scanned from QR code)
110
+ const fingerprint = await session.joinFromUri(uri)
111
+ console.log('Session fingerprint:', fingerprint) // show to user for visual verification
112
+
113
+ // 2. Handle incoming requests
114
+ session.on('request', ({ id, method, params }) => {
115
+ switch (method) {
116
+ case 'wallet_getAccounts':
117
+ session.approve(id, ['0xYourAddress'])
118
+ break
119
+ case 'wallet_signMessage':
120
+ // Sign and return, or reject
121
+ session.approve(id, { signature: '0x...' })
122
+ // session.reject(id, 'user_rejected', 'User declined')
123
+ break
124
+ }
125
+ })
126
+
127
+ // 3. Push events to dApp
128
+ session.pushEvent('accountsChanged', { accounts: ['0xNewAddress'] })
129
+ ```
130
+
131
+ ### EVM dApp with EIP-1193 Provider
132
+
133
+ ```ts
134
+ import { DAppSession, WebSocketTransport } from 'walletpair-sdk'
135
+ import { WalletPairProvider } from 'walletpair-sdk/evm'
136
+
137
+ const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
138
+ const session = new DAppSession({
139
+ transport,
140
+ meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
141
+ })
142
+ const provider = new WalletPairProvider({ session, chainId: 1 })
143
+
144
+ // Use like any EIP-1193 provider
145
+ const accounts = await provider.request({ method: 'eth_requestAccounts' })
146
+ const chainId = await provider.request({ method: 'eth_chainId' })
147
+
148
+ // Standard EIP-1193 events
149
+ provider.on('accountsChanged', (accounts) => { /* ... */ })
150
+ provider.on('chainChanged', (chainId) => { /* ... */ })
151
+ provider.on('disconnect', (error) => { /* ... */ })
152
+ ```
153
+
154
+ ### EVM dApp with wagmi
155
+
156
+ ```ts
157
+ import { walletPair } from 'walletpair-sdk/evm/wagmi'
158
+ import { createConfig, http } from 'wagmi'
159
+ import { mainnet, polygon } from 'wagmi/chains'
160
+
161
+ const config = createConfig({
162
+ chains: [mainnet, polygon],
163
+ connectors: [
164
+ walletPair({
165
+ relayUrl: 'wss://relay.walletpair.org/v1',
166
+ meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
167
+ onPairingUri: (uri) => {
168
+ // Display QR code with this URI
169
+ showQrCode(uri)
170
+ },
171
+ onSessionFingerprint: (fingerprint) => {
172
+ // Display session fingerprint for user visual verification
173
+ showSessionFingerprint(fingerprint)
174
+ },
175
+ }),
176
+ ],
177
+ transports: {
178
+ [mainnet.id]: http(),
179
+ [polygon.id]: http(),
180
+ },
181
+ })
182
+ ```
183
+
184
+ ### BLE Transport (Web Bluetooth)
185
+
186
+ ```ts
187
+ import { DAppSession } from 'walletpair-sdk'
188
+ import { WebBleCentralTransport, isWebBleSupported } from 'walletpair-sdk/ble'
189
+
190
+ if (isWebBleSupported()) {
191
+ const transport = new WebBleCentralTransport()
192
+ const session = new DAppSession({
193
+ transport,
194
+ meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
195
+ })
196
+
197
+ // BLE pairing URI has no relay parameter
198
+ const uri = await session.createPairing()
199
+ // uri = "walletpair:?ch=...&pubkey=..."
200
+ }
201
+ ```
202
+
203
+ ## Session Snapshots
204
+
205
+ `serialize()` and `restore()` can be used in controlled reconnect flows,
206
+ but they are not enough for production crash recovery by themselves. The
207
+ protocol requires sequence counters to be persisted before every encrypted
208
+ send. A crash after sending but before saving a new snapshot can roll back a
209
+ counter and cause nonce reuse with the same traffic key.
210
+
211
+ For production, persist `{ traffic_keys, sendSeq, recvSeq }` with a
212
+ write-ahead store before each send, or disable reconnect after process/page
213
+ termination and require fresh pairing.
214
+
215
+ Demo-only page reload snapshot:
216
+
217
+ ```ts
218
+ // Save before unload
219
+ window.addEventListener('beforeunload', () => {
220
+ sessionStorage.setItem('wp', session.serialize())
221
+ })
222
+
223
+ // Restore on load
224
+ const saved = sessionStorage.getItem('wp')
225
+ if (saved && session.restore(saved)) {
226
+ await session.reconnect()
227
+ }
228
+ ```
229
+
230
+ ## API Reference
231
+
232
+ ### Core
233
+
234
+ #### `DAppSession`
235
+
236
+ ```ts
237
+ new DAppSession({ transport, meta: { name, description, url, icon }, requestTimeout?, autoAccept? })
238
+ ```
239
+
240
+ | Method | Description |
241
+ |--------|-------------|
242
+ | `createPairing(): Promise<string>` | Create channel, returns pairing URI for QR display |
243
+ | `acceptWallet()` | Accept wallet (called automatically after sealed_join verification) |
244
+ | `rejectWallet()` | Reject wallet pairing |
245
+ | `request<T>(method, params?): Promise<T>` | Send encrypted request, returns decrypted response |
246
+ | `ping()` | Send heartbeat ping |
247
+ | `close()` | Gracefully close session |
248
+ | `destroy()` | Close + remove all event listeners |
249
+ | `serialize(): string` | Serialize a session snapshot |
250
+ | `restore(json): boolean` | Restore a session snapshot |
251
+ | `reconnect(): Promise<void>` | Reconnect after restore |
252
+
253
+ **Events:**
254
+
255
+ | Event | Payload | Description |
256
+ |-------|---------|-------------|
257
+ | `phase` | `DAppPhase` | State machine transition |
258
+ | `pairingUri` | `string` | Pairing URI generated |
259
+ | `sessionFingerprint` | `string` | Session fingerprint for visual verification |
260
+ | `walletJoined` | `{ pubkey, capabilities?, meta }` | Wallet joined the channel |
261
+ | `response` | `{ id, ok, data }` | Response received |
262
+ | `event` | `{ event, data }` | Wallet pushed an event |
263
+ | `error` | `Error` | Error occurred |
264
+
265
+ **Phases:** `idle` -> `waiting` -> `pending_accept` -> `connected` -> `closed`
266
+
267
+ #### `WalletSession`
268
+
269
+ ```ts
270
+ new WalletSession({ transport, capabilities, meta: { name, description, url, icon } })
271
+ ```
272
+
273
+ | Method | Description |
274
+ |--------|-------------|
275
+ | `joinFromUri(uri): Promise<string>` | Join channel, returns session fingerprint |
276
+ | `approve(requestId, result)` | Approve request with encrypted result |
277
+ | `reject(requestId, code?, message?)` | Reject request with error |
278
+ | `pushEvent(event, data)` | Push event to dApp |
279
+ | `ping()` | Send heartbeat ping |
280
+ | `close()` | Gracefully close session |
281
+ | `destroy()` | Close + remove all event listeners |
282
+ | `serialize()` / `restore(json)` | Session snapshot/restore |
283
+
284
+ **Events:**
285
+
286
+ | Event | Payload | Description |
287
+ |-------|---------|-------------|
288
+ | `phase` | `WalletPhase` | State machine transition |
289
+ | `sessionFingerprint` | `string` | Session fingerprint for visual verification |
290
+ | `request` | `{ id, method, params }` | Incoming request from dApp |
291
+ | `error` | `Error` | Error occurred |
292
+
293
+ **Phases:** `idle` -> `waiting` -> `connected` -> `closed`
294
+
295
+ #### `WebSocketTransport`
296
+
297
+ ```ts
298
+ new WebSocketTransport(url: string)
299
+ new WebSocketTransport({ url: string, protocols?: string[] })
300
+ ```
301
+
302
+ #### Transport Interface
303
+
304
+ Implement this to create custom transports (e.g., for React Native BLE peripheral):
305
+
306
+ ```ts
307
+ interface Transport {
308
+ readonly state: 'disconnected' | 'connecting' | 'connected'
309
+ send(msg: ProtocolMessage): void
310
+ connect(): Promise<void>
311
+ disconnect(): void
312
+ onMessage(handler: (msg: ProtocolMessage) => void): void
313
+ onClose(handler: () => void): void
314
+ onOpen(handler: () => void): void
315
+ }
316
+ ```
317
+
318
+ ### CAIP-2 Chain ID Helpers
319
+
320
+ ```ts
321
+ import { parseChainId, formatChainId, evmChainId, evmNumericChainId } from 'walletpair-sdk'
322
+
323
+ parseChainId('eip155:1') // { namespace: 'eip155', reference: '1' }
324
+ formatChainId('eip155', '137') // 'eip155:137'
325
+ evmChainId(1) // 'eip155:1'
326
+ evmNumericChainId('eip155:1') // 1
327
+ evmNumericChainId('solana:mainnet') // null
328
+ ```
329
+
330
+ ### EVM
331
+
332
+ #### `WalletPairProvider` (EIP-1193)
333
+
334
+ ```ts
335
+ import { WalletPairProvider } from 'walletpair-sdk/evm'
336
+
337
+ new WalletPairProvider({ session, chainId?, mapper? })
338
+ ```
339
+
340
+ **Default method mapping:**
341
+
342
+ | EIP-1193 Method | WalletPair Method |
343
+ |----------------|-------------------|
344
+ | `eth_requestAccounts` | `wallet_getAccounts` |
345
+ | `eth_accounts` | `wallet_getAccounts` |
346
+ | `personal_sign` | `wallet_signMessage` |
347
+ | `eth_signTypedData_v4` | `wallet_signTypedData` |
348
+ | `eth_sendTransaction` | `wallet_signTransaction` |
349
+ | `wallet_switchEthereumChain` | `wallet_switchChain` |
350
+ | `wallet_addEthereumChain` | `wallet_addChain` |
351
+ | `eth_chainId` | Handled locally (returns cached chain ID) |
352
+ | `net_version` | Handled locally (returns cached chain ID) |
353
+ | Others | Passed through as-is |
354
+
355
+ Override with a custom `MethodMapper` for specialized behavior.
356
+
357
+ #### `walletPair()` (wagmi connector)
358
+
359
+ ```ts
360
+ import { walletPair } from 'walletpair-sdk/evm/wagmi'
361
+
362
+ walletPair({
363
+ relayUrl: string, // WebSocket relay URL
364
+ meta: { name, description, url, icon }, // DApp metadata
365
+ requestTimeout?: number, // Request timeout in ms
366
+ onPairingUri?: (uri) => void, // QR code display callback
367
+ onSessionFingerprint?: (fingerprint) => void, // Session fingerprint display callback
368
+ })
369
+ ```
370
+
371
+ ### BLE
372
+
373
+ ```ts
374
+ import {
375
+ WebBleCentralTransport, // Web Bluetooth Central (dApp side)
376
+ isWebBleSupported, // Runtime availability check
377
+ frameMessage, // Low-level: split JSON into BLE frames
378
+ Defragmenter, // Low-level: reassemble BLE frames
379
+ BLE_SERVICE_UUID, // WalletPair BLE service UUID
380
+ BLE_WRITE_CHAR_UUID, // DApp -> Wallet characteristic
381
+ BLE_NOTIFY_CHAR_UUID, // Wallet -> DApp characteristic
382
+ } from 'walletpair-sdk/ble'
383
+ ```
384
+
385
+ ## Extending for New Chains
386
+
387
+ To add support for a new chain (e.g., Solana):
388
+
389
+ 1. Create `src/solana/` directory
390
+ 2. Implement a provider that maps Solana RPC methods to WalletPair requests
391
+ 3. Add subpath export in `package.json`:
392
+ ```json
393
+ { "./solana": "./src/solana/index.ts" }
394
+ ```
395
+ 4. Wallet side: declare Solana chains in capabilities:
396
+ ```ts
397
+ capabilities: {
398
+ methods: ['wallet_signTransaction', 'wallet_signMessage'],
399
+ chains: ['solana:mainnet', 'solana:devnet'],
400
+ }
401
+ ```
402
+
403
+ The core protocol is chain-agnostic -- `DAppSession.request()` and `WalletSession.approve()` work with any method/params structure.
404
+
405
+ ## Security
406
+
407
+ - **E2E Encryption**: X25519 ECDH -> HKDF-SHA256 -> ChaCha20-Poly1305 AEAD
408
+ - **MITM Protection**: Session fingerprint derived from SHA256(prefix || channel_id || dapp_pubkey) for visual verification on both devices
409
+ - **Replay Protection**: Sequence-number-based nonces, monotonically increasing
410
+ - **Channel Isolation**: 256-bit random channel IDs
411
+ - **Zero Trust Relay**: Relay sees routing metadata only, never plaintext payloads
412
+
413
+ ## License
414
+
415
+ MIT
@@ -0,0 +1,23 @@
1
+ /**
2
+ * BLE message framing per WalletPair Protocol Section 19.5.
3
+ *
4
+ * Frame format: [1 byte flags] [2 bytes total_length BE] [payload fragment]
5
+ *
6
+ * flags bit 0: first fragment
7
+ * flags bit 1: last fragment
8
+ */
9
+ export declare const DEFAULT_FRAME_PAYLOAD = 509;
10
+ export declare const MIN_FRAME_PAYLOAD = 20;
11
+ export declare const BLE_SERVICE_UUID = "e3a10001-7770-4270-8000-000077700001";
12
+ export declare const BLE_WRITE_CHAR_UUID = "e3a10002-7770-4270-8000-000077700001";
13
+ export declare const BLE_NOTIFY_CHAR_UUID = "e3a10003-7770-4270-8000-000077700001";
14
+ /** Split a JSON string into BLE frames. */
15
+ export declare function frameMessage(jsonStr: string, maxPayload?: number): Uint8Array[];
16
+ /** Accumulates BLE frames and emits complete JSON strings. */
17
+ export declare class Defragmenter {
18
+ private buffer;
19
+ private offset;
20
+ push(data: Uint8Array): string | null;
21
+ reset(): void;
22
+ }
23
+ //# sourceMappingURL=framing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framing.d.ts","sourceRoot":"","sources":["../../src/ble/framing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AACvE,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAC1E,eAAO,MAAM,oBAAoB,yCAAyC,CAAC;AAE3E,2CAA2C;AAC3C,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,SAAwB,GACjC,UAAU,EAAE,CA6Bd;AAED,8DAA8D;AAC9D,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAK;IAEnB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAoCrC,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * BLE message framing per WalletPair Protocol Section 19.5.
3
+ *
4
+ * Frame format: [1 byte flags] [2 bytes total_length BE] [payload fragment]
5
+ *
6
+ * flags bit 0: first fragment
7
+ * flags bit 1: last fragment
8
+ */
9
+ const FLAG_FIRST = 0x01;
10
+ const FLAG_LAST = 0x02;
11
+ export const DEFAULT_FRAME_PAYLOAD = 509;
12
+ export const MIN_FRAME_PAYLOAD = 20;
13
+ export const BLE_SERVICE_UUID = 'e3a10001-7770-4270-8000-000077700001';
14
+ export const BLE_WRITE_CHAR_UUID = 'e3a10002-7770-4270-8000-000077700001';
15
+ export const BLE_NOTIFY_CHAR_UUID = 'e3a10003-7770-4270-8000-000077700001';
16
+ /** Split a JSON string into BLE frames. */
17
+ export function frameMessage(jsonStr, maxPayload = DEFAULT_FRAME_PAYLOAD) {
18
+ const payload = new TextEncoder().encode(jsonStr);
19
+ const frames = [];
20
+ if (payload.length === 0) {
21
+ const frame = new Uint8Array(3);
22
+ frame[0] = FLAG_FIRST | FLAG_LAST;
23
+ return [frame];
24
+ }
25
+ const chunkSize = Math.max(maxPayload, MIN_FRAME_PAYLOAD);
26
+ for (let offset = 0; offset < payload.length; offset += chunkSize) {
27
+ const isFirst = offset === 0;
28
+ const end = Math.min(offset + chunkSize, payload.length);
29
+ const isLast = end === payload.length;
30
+ const fragment = payload.subarray(offset, end);
31
+ const frame = new Uint8Array(3 + fragment.length);
32
+ frame[0] = (isFirst ? FLAG_FIRST : 0) | (isLast ? FLAG_LAST : 0);
33
+ if (isFirst) {
34
+ frame[1] = (payload.length >> 8) & 0xff;
35
+ frame[2] = payload.length & 0xff;
36
+ }
37
+ frame.set(fragment, 3);
38
+ frames.push(frame);
39
+ }
40
+ return frames;
41
+ }
42
+ /** Accumulates BLE frames and emits complete JSON strings. */
43
+ export class Defragmenter {
44
+ buffer = null;
45
+ offset = 0;
46
+ push(data) {
47
+ if (data.length < 3)
48
+ return null;
49
+ const flags = data[0];
50
+ const isFirst = !!(flags & FLAG_FIRST);
51
+ const isLast = !!(flags & FLAG_LAST);
52
+ const fragment = data.subarray(3);
53
+ if (isFirst) {
54
+ const totalLength = (data[1] << 8) | data[2];
55
+ this.buffer = new Uint8Array(totalLength || fragment.length);
56
+ this.offset = 0;
57
+ }
58
+ if (this.buffer) {
59
+ if (this.offset + fragment.length <= this.buffer.length) {
60
+ this.buffer.set(fragment, this.offset);
61
+ }
62
+ else {
63
+ const grown = new Uint8Array(this.offset + fragment.length);
64
+ grown.set(this.buffer.subarray(0, this.offset));
65
+ grown.set(fragment, this.offset);
66
+ this.buffer = grown;
67
+ }
68
+ this.offset += fragment.length;
69
+ }
70
+ if (isLast && this.buffer) {
71
+ const result = new TextDecoder().decode(this.buffer.subarray(0, this.offset));
72
+ this.buffer = null;
73
+ this.offset = 0;
74
+ return result;
75
+ }
76
+ return null;
77
+ }
78
+ reset() {
79
+ this.buffer = null;
80
+ this.offset = 0;
81
+ }
82
+ }
83
+ //# sourceMappingURL=framing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framing.js","sourceRoot":"","sources":["../../src/ble/framing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * BLE transport exports.
3
+ *
4
+ * Re-exports framing utilities and provides the Web Bluetooth Central transport.
5
+ * Safe to import on any platform — Web Bluetooth availability is checked at runtime.
6
+ */
7
+ export { frameMessage, Defragmenter, DEFAULT_FRAME_PAYLOAD, MIN_FRAME_PAYLOAD, BLE_SERVICE_UUID, BLE_WRITE_CHAR_UUID, BLE_NOTIFY_CHAR_UUID, } from './framing.js';
8
+ export { WebBleCentralTransport, isWebBleSupported } from './web-ble-transport.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ble/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * BLE transport exports.
3
+ *
4
+ * Re-exports framing utilities and provides the Web Bluetooth Central transport.
5
+ * Safe to import on any platform — Web Bluetooth availability is checked at runtime.
6
+ */
7
+ export { frameMessage, Defragmenter, DEFAULT_FRAME_PAYLOAD, MIN_FRAME_PAYLOAD, BLE_SERVICE_UUID, BLE_WRITE_CHAR_UUID, BLE_NOTIFY_CHAR_UUID, } from './framing.js';
8
+ export { WebBleCentralTransport, isWebBleSupported } from './web-ble-transport.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ble/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Web Bluetooth Central transport for dApp side.
3
+ *
4
+ * The dApp acts as BLE Central and connects to the wallet's GATT Peripheral.
5
+ * Safe to import anywhere — checks Web Bluetooth availability at runtime.
6
+ */
7
+ import type { Transport, TransportState, ProtocolMessage } from '../types.js';
8
+ export declare function isWebBleSupported(): boolean;
9
+ export declare class WebBleCentralTransport implements Transport {
10
+ state: TransportState;
11
+ private device;
12
+ private writeChar;
13
+ private notifyChar;
14
+ private defrag;
15
+ private mtuPayload;
16
+ private messageHandler;
17
+ private closeHandler;
18
+ private openHandler;
19
+ onMessage(handler: (msg: ProtocolMessage) => void): void;
20
+ onClose(handler: () => void): void;
21
+ onOpen(handler: () => void): void;
22
+ connect(): Promise<void>;
23
+ send(msg: ProtocolMessage): void;
24
+ disconnect(): void;
25
+ private onNotification;
26
+ private onDisconnect;
27
+ private cleanup;
28
+ }
29
+ //# sourceMappingURL=web-ble-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-ble-transport.d.ts","sourceRoot":"","sources":["../../src/ble/web-ble-transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS9E,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,qBAAa,sBAAuB,YAAW,SAAS;IACtD,KAAK,EAAE,cAAc,CAAkB;IAEvC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,SAAS,CAAkD;IACnE,OAAO,CAAC,UAAU,CAAkD;IACpE,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,UAAU,CAAO;IAEzB,OAAO,CAAC,cAAc,CAAiD;IACvE,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAA6B;IAEhD,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IACxD,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAClC,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAE3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B9B,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAUhC,UAAU,IAAI,IAAI;IAOlB,OAAO,CAAC,cAAc,CAQpB;IAEF,OAAO,CAAC,YAAY,CAGlB;IAEF,OAAO,CAAC,OAAO;CAahB"}