walletpair-sdk 1.0.2 → 1.0.5

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 (74) hide show
  1. package/README.md +13 -0
  2. package/dist/ble/framing.d.ts.map +1 -1
  3. package/dist/ble/framing.js +2 -2
  4. package/dist/ble/framing.js.map +1 -1
  5. package/dist/ble/index.d.ts +2 -2
  6. package/dist/ble/index.d.ts.map +1 -1
  7. package/dist/ble/index.js +2 -2
  8. package/dist/ble/index.js.map +1 -1
  9. package/dist/ble/web-ble-transport.d.ts +1 -1
  10. package/dist/ble/web-ble-transport.d.ts.map +1 -1
  11. package/dist/ble/web-ble-transport.js +23 -12
  12. package/dist/ble/web-ble-transport.js.map +1 -1
  13. package/dist/crypto.d.ts.map +1 -1
  14. package/dist/crypto.js +29 -12
  15. package/dist/crypto.js.map +1 -1
  16. package/dist/dapp-session.d.ts.map +1 -1
  17. package/dist/dapp-session.js +15 -5
  18. package/dist/dapp-session.js.map +1 -1
  19. package/dist/emitter.d.ts +1 -3
  20. package/dist/emitter.d.ts.map +1 -1
  21. package/dist/emitter.js +4 -2
  22. package/dist/emitter.js.map +1 -1
  23. package/dist/evm/eip1193.d.ts +2 -2
  24. package/dist/evm/eip1193.d.ts.map +1 -1
  25. package/dist/evm/eip1193.js +32 -18
  26. package/dist/evm/eip1193.js.map +1 -1
  27. package/dist/evm/index.d.ts +2 -2
  28. package/dist/evm/index.d.ts.map +1 -1
  29. package/dist/evm/index.js.map +1 -1
  30. package/dist/wallet-session.d.ts.map +1 -1
  31. package/dist/wallet-session.js +4 -3
  32. package/dist/wallet-session.js.map +1 -1
  33. package/dist/ws-transport.d.ts +3 -2
  34. package/dist/ws-transport.d.ts.map +1 -1
  35. package/dist/ws-transport.js +13 -4
  36. package/dist/ws-transport.js.map +1 -1
  37. package/package.json +20 -1
  38. package/src/__tests__/adversarial/crypto-attacks.test.ts +240 -233
  39. package/src/__tests__/adversarial/malicious-dapp.test.ts +228 -194
  40. package/src/__tests__/adversarial/malicious-relay.test.ts +292 -220
  41. package/src/__tests__/adversarial/malicious-wallet.test.ts +246 -180
  42. package/src/__tests__/spec-compliance/canonical-json.test.ts +105 -105
  43. package/src/__tests__/spec-compliance/crypto-vectors.test.ts +149 -154
  44. package/src/__tests__/spec-compliance/message-format.test.ts +180 -151
  45. package/src/__tests__/spec-compliance/sequence-numbers.test.ts +142 -149
  46. package/src/__tests__/spec-compliance/state-machine.test.ts +203 -180
  47. package/src/ble/framing.test.ts +122 -114
  48. package/src/ble/framing.ts +48 -51
  49. package/src/ble/index.ts +7 -7
  50. package/src/ble/web-ble-transport.test.ts +93 -84
  51. package/src/ble/web-ble-transport.ts +70 -57
  52. package/src/ble/web-bluetooth.d.ts +19 -19
  53. package/src/canonical-json.test.ts +301 -285
  54. package/src/crypto-directional.test.ts +155 -129
  55. package/src/crypto-hardening.test.ts +292 -283
  56. package/src/crypto.test.ts +364 -346
  57. package/src/crypto.ts +185 -175
  58. package/src/dapp-session.test.ts +522 -385
  59. package/src/dapp-session.ts +17 -11
  60. package/src/emitter.test.ts +122 -122
  61. package/src/emitter.ts +20 -18
  62. package/src/evm/eip1193.test.ts +283 -205
  63. package/src/evm/eip1193.ts +162 -138
  64. package/src/evm/index.ts +5 -5
  65. package/src/evm/wagmi.test.ts +1 -1
  66. package/src/integration.test.ts +329 -201
  67. package/src/security.test.ts +331 -238
  68. package/src/sequence-validation.test.ts +6 -9
  69. package/src/test-helpers.ts +102 -78
  70. package/src/types.test.ts +45 -50
  71. package/src/wallet-session.test.ts +611 -383
  72. package/src/wallet-session.ts +7 -9
  73. package/src/ws-transport.test.ts +141 -139
  74. package/src/ws-transport.ts +52 -41
@@ -231,6 +231,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
231
231
  }
232
232
 
233
233
  const id = `req-${++this.reqCounter}`
234
+ const sendKey = this.sendKey
234
235
 
235
236
  // Always seal: even parameterless requests must be authenticated via AEAD
236
237
  // to prevent method injection by a malicious relay.
@@ -243,7 +244,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
243
244
  ? (params as Record<string, unknown>)
244
245
  : { _params: params ?? {} }),
245
246
  }
246
- const sealed = sealPayload(this.sendKey!, this.channelId, seq, sealedParams, hdr)
247
+ const sealed = sealPayload(sendKey, this.channelId, seq, sealedParams, hdr)
247
248
 
248
249
  const msg: ProtocolMessage = {
249
250
  v: 1,
@@ -260,7 +261,13 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
260
261
  reject(new Error(`Request ${method} timed out`))
261
262
  }, this.requestTimeout)
262
263
 
263
- this.pendingRequests.set(id, { id, method, resolve: resolve as any, reject, timer })
264
+ this.pendingRequests.set(id, {
265
+ id,
266
+ method,
267
+ resolve: resolve as (v: unknown) => void,
268
+ reject,
269
+ timer,
270
+ })
264
271
  this.sendRaw(msg)
265
272
  })
266
273
  }
@@ -400,6 +407,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
400
407
  d.dappMeta ??
401
408
  (d.dappName ? { name: d.dappName, description: '', url: '', icon: '' } : this.meta)
402
409
  this.sessionStartTime = d.sessionStartTime ?? null
410
+ this.setPhase('connected')
403
411
  return true
404
412
  } catch {
405
413
  return false
@@ -560,9 +568,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
560
568
  const declared = new Set(joinCapabilities.methods)
561
569
  const absent = requiredMethods.filter((m) => !declared.has(m))
562
570
  if (absent.length > 0) {
563
- console.warn(
564
- `[WalletPair] Wallet missing MUST-support methods: ${absent.join(', ')}`,
565
- )
571
+ console.warn(`[WalletPair] Wallet missing MUST-support methods: ${absent.join(', ')}`)
566
572
  }
567
573
  }
568
574
 
@@ -634,6 +640,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
634
640
  }
635
641
 
636
642
  case 'res': {
643
+ if (this.phase !== 'connected') break
637
644
  const resBody = msg.body as { id?: string; sealed?: string }
638
645
  if (this.remotePubKey && msg.from !== b64urlEncode(this.remotePubKey)) break
639
646
  if (!resBody.id) break
@@ -700,6 +707,7 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
700
707
  }
701
708
 
702
709
  case 'evt': {
710
+ if (this.phase !== 'connected') break
703
711
  const evtBody = msg.body as { id?: string; sealed?: string }
704
712
  if (this.remotePubKey && msg.from !== b64urlEncode(this.remotePubKey)) break
705
713
  // Events MUST be sealed — drop unsealed events to prevent forgery.
@@ -729,12 +737,10 @@ export class DAppSession extends Emitter<DAppSessionEvents> {
729
737
  }
730
738
  const persisted = this.persistSnapshot()
731
739
  if (isPromiseLike(persisted)) {
732
- void persisted
733
- .then(afterPersist)
734
- .catch((e) => {
735
- this.recvSeq = prevRecvSeqEvt // rollback on persist failure
736
- this.emit('error', this.persistenceError(e))
737
- })
740
+ void persisted.then(afterPersist).catch((e) => {
741
+ this.recvSeq = prevRecvSeqEvt // rollback on persist failure
742
+ this.emit('error', this.persistenceError(e))
743
+ })
738
744
  } else {
739
745
  afterPersist()
740
746
  }
@@ -1,169 +1,169 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { Emitter } from './emitter.js';
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { Emitter } from './emitter.js'
3
3
 
4
4
  interface TestEvents {
5
- [key: string]: unknown;
6
- message: string;
7
- count: number;
8
- complex: { name: string; value: number };
5
+ [key: string]: unknown
6
+ message: string
7
+ count: number
8
+ complex: { name: string; value: number }
9
9
  }
10
10
 
11
11
  describe('Emitter', () => {
12
12
  it('emits events to registered handlers', () => {
13
- const emitter = new Emitter<TestEvents>();
14
- const handler = vi.fn();
15
- emitter.on('message', handler);
16
- emitter.emit('message', 'hello');
17
- expect(handler).toHaveBeenCalledWith('hello');
18
- expect(handler).toHaveBeenCalledTimes(1);
19
- });
13
+ const emitter = new Emitter<TestEvents>()
14
+ const handler = vi.fn()
15
+ emitter.on('message', handler)
16
+ emitter.emit('message', 'hello')
17
+ expect(handler).toHaveBeenCalledWith('hello')
18
+ expect(handler).toHaveBeenCalledTimes(1)
19
+ })
20
20
 
21
21
  it('supports multiple handlers for the same event', () => {
22
- const emitter = new Emitter<TestEvents>();
23
- const h1 = vi.fn();
24
- const h2 = vi.fn();
25
- emitter.on('message', h1);
26
- emitter.on('message', h2);
27
- emitter.emit('message', 'test');
28
- expect(h1).toHaveBeenCalledWith('test');
29
- expect(h2).toHaveBeenCalledWith('test');
30
- });
22
+ const emitter = new Emitter<TestEvents>()
23
+ const h1 = vi.fn()
24
+ const h2 = vi.fn()
25
+ emitter.on('message', h1)
26
+ emitter.on('message', h2)
27
+ emitter.emit('message', 'test')
28
+ expect(h1).toHaveBeenCalledWith('test')
29
+ expect(h2).toHaveBeenCalledWith('test')
30
+ })
31
31
 
32
32
  it('supports multiple event types', () => {
33
- const emitter = new Emitter<TestEvents>();
34
- const msgHandler = vi.fn();
35
- const countHandler = vi.fn();
36
- emitter.on('message', msgHandler);
37
- emitter.on('count', countHandler);
33
+ const emitter = new Emitter<TestEvents>()
34
+ const msgHandler = vi.fn()
35
+ const countHandler = vi.fn()
36
+ emitter.on('message', msgHandler)
37
+ emitter.on('count', countHandler)
38
38
 
39
- emitter.emit('message', 'hello');
40
- emitter.emit('count', 42);
39
+ emitter.emit('message', 'hello')
40
+ emitter.emit('count', 42)
41
41
 
42
- expect(msgHandler).toHaveBeenCalledWith('hello');
43
- expect(countHandler).toHaveBeenCalledWith(42);
44
- expect(msgHandler).toHaveBeenCalledTimes(1);
45
- expect(countHandler).toHaveBeenCalledTimes(1);
46
- });
42
+ expect(msgHandler).toHaveBeenCalledWith('hello')
43
+ expect(countHandler).toHaveBeenCalledWith(42)
44
+ expect(msgHandler).toHaveBeenCalledTimes(1)
45
+ expect(countHandler).toHaveBeenCalledTimes(1)
46
+ })
47
47
 
48
48
  it('on() returns an unsubscribe function', () => {
49
- const emitter = new Emitter<TestEvents>();
50
- const handler = vi.fn();
51
- const off = emitter.on('message', handler);
49
+ const emitter = new Emitter<TestEvents>()
50
+ const handler = vi.fn()
51
+ const off = emitter.on('message', handler)
52
52
 
53
- emitter.emit('message', 'first');
54
- expect(handler).toHaveBeenCalledTimes(1);
53
+ emitter.emit('message', 'first')
54
+ expect(handler).toHaveBeenCalledTimes(1)
55
55
 
56
- off();
57
- emitter.emit('message', 'second');
58
- expect(handler).toHaveBeenCalledTimes(1); // not called again
59
- });
56
+ off()
57
+ emitter.emit('message', 'second')
58
+ expect(handler).toHaveBeenCalledTimes(1) // not called again
59
+ })
60
60
 
61
61
  it('off() removes a specific handler', () => {
62
- const emitter = new Emitter<TestEvents>();
63
- const h1 = vi.fn();
64
- const h2 = vi.fn();
65
- emitter.on('message', h1);
66
- emitter.on('message', h2);
62
+ const emitter = new Emitter<TestEvents>()
63
+ const h1 = vi.fn()
64
+ const h2 = vi.fn()
65
+ emitter.on('message', h1)
66
+ emitter.on('message', h2)
67
67
 
68
- emitter.off('message', h1);
69
- emitter.emit('message', 'test');
68
+ emitter.off('message', h1)
69
+ emitter.emit('message', 'test')
70
70
 
71
- expect(h1).not.toHaveBeenCalled();
72
- expect(h2).toHaveBeenCalledWith('test');
73
- });
71
+ expect(h1).not.toHaveBeenCalled()
72
+ expect(h2).toHaveBeenCalledWith('test')
73
+ })
74
74
 
75
75
  it('off() without handler removes all handlers for that event', () => {
76
- const emitter = new Emitter<TestEvents>();
77
- const h1 = vi.fn();
78
- const h2 = vi.fn();
79
- emitter.on('message', h1);
80
- emitter.on('message', h2);
76
+ const emitter = new Emitter<TestEvents>()
77
+ const h1 = vi.fn()
78
+ const h2 = vi.fn()
79
+ emitter.on('message', h1)
80
+ emitter.on('message', h2)
81
81
 
82
- emitter.off('message');
83
- emitter.emit('message', 'test');
82
+ emitter.off('message')
83
+ emitter.emit('message', 'test')
84
84
 
85
- expect(h1).not.toHaveBeenCalled();
86
- expect(h2).not.toHaveBeenCalled();
87
- });
85
+ expect(h1).not.toHaveBeenCalled()
86
+ expect(h2).not.toHaveBeenCalled()
87
+ })
88
88
 
89
89
  it('once() fires handler only once', () => {
90
- const emitter = new Emitter<TestEvents>();
91
- const handler = vi.fn();
92
- emitter.once('message', handler);
90
+ const emitter = new Emitter<TestEvents>()
91
+ const handler = vi.fn()
92
+ emitter.once('message', handler)
93
93
 
94
- emitter.emit('message', 'first');
95
- emitter.emit('message', 'second');
94
+ emitter.emit('message', 'first')
95
+ emitter.emit('message', 'second')
96
96
 
97
- expect(handler).toHaveBeenCalledTimes(1);
98
- expect(handler).toHaveBeenCalledWith('first');
99
- });
97
+ expect(handler).toHaveBeenCalledTimes(1)
98
+ expect(handler).toHaveBeenCalledWith('first')
99
+ })
100
100
 
101
101
  it('once() returns an unsubscribe function that works before emit', () => {
102
- const emitter = new Emitter<TestEvents>();
103
- const handler = vi.fn();
104
- const off = emitter.once('message', handler);
102
+ const emitter = new Emitter<TestEvents>()
103
+ const handler = vi.fn()
104
+ const off = emitter.once('message', handler)
105
105
 
106
- off(); // cancel before any emission
107
- emitter.emit('message', 'test');
108
- expect(handler).not.toHaveBeenCalled();
109
- });
106
+ off() // cancel before any emission
107
+ emitter.emit('message', 'test')
108
+ expect(handler).not.toHaveBeenCalled()
109
+ })
110
110
 
111
111
  it('removeAll() clears all events', () => {
112
- const emitter = new Emitter<TestEvents>();
113
- const h1 = vi.fn();
114
- const h2 = vi.fn();
115
- emitter.on('message', h1);
116
- emitter.on('count', h2);
112
+ const emitter = new Emitter<TestEvents>()
113
+ const h1 = vi.fn()
114
+ const h2 = vi.fn()
115
+ emitter.on('message', h1)
116
+ emitter.on('count', h2)
117
117
 
118
- emitter.removeAll();
119
- emitter.emit('message', 'test');
120
- emitter.emit('count', 1);
118
+ emitter.removeAll()
119
+ emitter.emit('message', 'test')
120
+ emitter.emit('count', 1)
121
121
 
122
- expect(h1).not.toHaveBeenCalled();
123
- expect(h2).not.toHaveBeenCalled();
124
- });
122
+ expect(h1).not.toHaveBeenCalled()
123
+ expect(h2).not.toHaveBeenCalled()
124
+ })
125
125
 
126
126
  it('emitting with no handlers does not throw', () => {
127
- const emitter = new Emitter<TestEvents>();
128
- expect(() => emitter.emit('message', 'test')).not.toThrow();
129
- });
127
+ const emitter = new Emitter<TestEvents>()
128
+ expect(() => emitter.emit('message', 'test')).not.toThrow()
129
+ })
130
130
 
131
131
  it('handles complex event data', () => {
132
- const emitter = new Emitter<TestEvents>();
133
- const handler = vi.fn();
134
- emitter.on('complex', handler);
132
+ const emitter = new Emitter<TestEvents>()
133
+ const handler = vi.fn()
134
+ emitter.on('complex', handler)
135
135
 
136
- const data = { name: 'test', value: 99 };
137
- emitter.emit('complex', data);
138
- expect(handler).toHaveBeenCalledWith(data);
139
- });
136
+ const data = { name: 'test', value: 99 }
137
+ emitter.emit('complex', data)
138
+ expect(handler).toHaveBeenCalledWith(data)
139
+ })
140
140
 
141
141
  it('handler added during emit is not called in the same emit cycle', () => {
142
- const emitter = new Emitter<TestEvents>();
143
- const late = vi.fn();
142
+ const emitter = new Emitter<TestEvents>()
143
+ const late = vi.fn()
144
144
  emitter.on('message', () => {
145
- emitter.on('message', late);
146
- });
147
- emitter.emit('message', 'trigger');
145
+ emitter.on('message', late)
146
+ })
147
+ emitter.emit('message', 'trigger')
148
148
  // The late handler was added during iteration, behavior depends on Set iteration
149
149
  // but it should not cause errors
150
- });
150
+ })
151
151
 
152
152
  it('multiple on() calls return independent unsubscribe functions', () => {
153
- const emitter = new Emitter<TestEvents>();
154
- const handler = vi.fn();
155
- const off1 = emitter.on('message', handler);
156
- const off2 = emitter.on('count', handler);
157
-
158
- off1();
159
- emitter.emit('message', 'gone');
160
- emitter.emit('count', 42);
161
-
162
- expect(handler).toHaveBeenCalledTimes(1);
163
- expect(handler).toHaveBeenCalledWith(42);
164
-
165
- off2();
166
- emitter.emit('count', 99);
167
- expect(handler).toHaveBeenCalledTimes(1);
168
- });
169
- });
153
+ const emitter = new Emitter<TestEvents>()
154
+ const handler = vi.fn()
155
+ const off1 = emitter.on('message', handler)
156
+ const off2 = emitter.on('count', handler)
157
+
158
+ off1()
159
+ emitter.emit('message', 'gone')
160
+ emitter.emit('count', 42)
161
+
162
+ expect(handler).toHaveBeenCalledTimes(1)
163
+ expect(handler).toHaveBeenCalledWith(42)
164
+
165
+ off2()
166
+ emitter.emit('count', 99)
167
+ expect(handler).toHaveBeenCalledTimes(1)
168
+ })
169
+ })
package/src/emitter.ts CHANGED
@@ -2,44 +2,46 @@
2
2
  * Minimal typed event emitter — no external dependencies.
3
3
  */
4
4
 
5
- type Handler<T> = (data: T) => void;
5
+ type Handler<T> = (data: T) => void
6
6
 
7
- // biome-ignore lint: index signature needed for generic emitter
8
- export class Emitter<Events extends { [key: string]: unknown }> {
9
- private handlers = new Map<keyof Events, Set<Handler<any>>>();
7
+ export class Emitter<Events extends Record<string, unknown>> {
8
+ // biome-ignore lint/suspicious/noExplicitAny: generic handler storage requires any
9
+ private handlers = new Map<keyof Events, Set<Handler<any>>>()
10
10
 
11
11
  on<K extends keyof Events>(event: K, handler: Handler<Events[K]>): () => void {
12
- let set = this.handlers.get(event);
12
+ let set = this.handlers.get(event)
13
13
  if (!set) {
14
- set = new Set();
15
- this.handlers.set(event, set);
14
+ set = new Set()
15
+ this.handlers.set(event, set)
16
+ }
17
+ set.add(handler)
18
+ return () => {
19
+ set?.delete(handler)
16
20
  }
17
- set.add(handler);
18
- return () => { set!.delete(handler); };
19
21
  }
20
22
 
21
23
  once<K extends keyof Events>(event: K, handler: Handler<Events[K]>): () => void {
22
24
  const off = this.on(event, (data) => {
23
- off();
24
- handler(data);
25
- });
26
- return off;
25
+ off()
26
+ handler(data)
27
+ })
28
+ return off
27
29
  }
28
30
 
29
31
  emit<K extends keyof Events>(event: K, data: Events[K]): void {
30
- const set = this.handlers.get(event);
31
- if (set) for (const h of set) h(data);
32
+ const set = this.handlers.get(event)
33
+ if (set) for (const h of set) h(data)
32
34
  }
33
35
 
34
36
  off<K extends keyof Events>(event: K, handler?: Handler<Events[K]>): void {
35
37
  if (handler) {
36
- this.handlers.get(event)?.delete(handler);
38
+ this.handlers.get(event)?.delete(handler)
37
39
  } else {
38
- this.handlers.delete(event);
40
+ this.handlers.delete(event)
39
41
  }
40
42
  }
41
43
 
42
44
  removeAll(): void {
43
- this.handlers.clear();
45
+ this.handlers.clear()
44
46
  }
45
47
  }