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
@@ -6,14 +6,14 @@
6
6
  * conditions correctly per the specification.
7
7
  */
8
8
 
9
- import { describe, expect, it } from 'vitest';
9
+ import { describe, expect, it } from 'vitest'
10
10
 
11
11
  // ---------------------------------------------------------------------------
12
12
  // State machine model (pure functions, no SDK dependency)
13
13
  // ---------------------------------------------------------------------------
14
14
 
15
- type DAppState = 'idle' | 'waiting' | 'pending_accept' | 'connected' | 'disconnected' | 'closed';
16
- type WalletState = 'idle' | 'waiting_accept' | 'connected' | 'disconnected' | 'closed';
15
+ type DAppState = 'idle' | 'waiting' | 'pending_accept' | 'connected' | 'disconnected' | 'closed'
16
+ type WalletState = 'idle' | 'waiting_accept' | 'connected' | 'disconnected' | 'closed'
17
17
 
18
18
  type DAppEvent =
19
19
  | 'send_create'
@@ -28,7 +28,7 @@ type DAppEvent =
28
28
  | 'send_create_reconnect'
29
29
  | 'receive_channel_exists'
30
30
  | 'session_expired'
31
- | 'give_up';
31
+ | 'give_up'
32
32
 
33
33
  type WalletEvent =
34
34
  | 'send_join'
@@ -40,38 +40,38 @@ type WalletEvent =
40
40
  | 'send_join_reconnect'
41
41
  | 'receive_channel_not_found'
42
42
  | 'session_expired'
43
- | 'give_up';
43
+ | 'give_up'
44
44
 
45
45
  /** DApp state machine per Section 14. */
46
46
  function dappTransition(state: DAppState, event: DAppEvent): DAppState | null {
47
47
  switch (state) {
48
48
  case 'idle':
49
- if (event === 'send_create') return 'waiting';
50
- return null;
49
+ if (event === 'send_create') return 'waiting'
50
+ return null
51
51
  case 'waiting':
52
- if (event === 'receive_join') return 'pending_accept';
53
- if (event === 'receive_close' || event === 'receive_terminate') return 'closed';
54
- if (event === 'timeout') return 'closed';
55
- return null;
52
+ if (event === 'receive_join') return 'pending_accept'
53
+ if (event === 'receive_close' || event === 'receive_terminate') return 'closed'
54
+ if (event === 'timeout') return 'closed'
55
+ return null
56
56
  case 'pending_accept':
57
- if (event === 'sealed_join_verified_send_accept') return 'connected';
58
- if (event === 'user_rejects_send_close') return 'closed';
59
- if (event === 'receive_terminate') return 'closed';
60
- if (event === 'timeout') return 'closed';
61
- return null;
57
+ if (event === 'sealed_join_verified_send_accept') return 'connected'
58
+ if (event === 'user_rejects_send_close') return 'closed'
59
+ if (event === 'receive_terminate') return 'closed'
60
+ if (event === 'timeout') return 'closed'
61
+ return null
62
62
  case 'connected':
63
- if (event === 'receive_close' || event === 'receive_terminate') return 'closed';
64
- if (event === 'transport_disconnected') return 'disconnected';
65
- if (event === 'session_expired') return 'closed';
66
- return null;
63
+ if (event === 'receive_close' || event === 'receive_terminate') return 'closed'
64
+ if (event === 'transport_disconnected') return 'disconnected'
65
+ if (event === 'session_expired') return 'closed'
66
+ return null
67
67
  case 'disconnected':
68
- if (event === 'send_create_reconnect') return 'waiting';
69
- if (event === 'receive_channel_exists') return 'disconnected';
70
- if (event === 'session_expired') return 'closed';
71
- if (event === 'give_up') return 'closed';
72
- return null;
68
+ if (event === 'send_create_reconnect') return 'waiting'
69
+ if (event === 'receive_channel_exists') return 'disconnected'
70
+ if (event === 'session_expired') return 'closed'
71
+ if (event === 'give_up') return 'closed'
72
+ return null
73
73
  case 'closed':
74
- return null; // terminal state
74
+ return null // terminal state
75
75
  }
76
76
  }
77
77
 
@@ -79,26 +79,26 @@ function dappTransition(state: DAppState, event: DAppEvent): DAppState | null {
79
79
  function walletTransition(state: WalletState, event: WalletEvent): WalletState | null {
80
80
  switch (state) {
81
81
  case 'idle':
82
- if (event === 'send_join') return 'waiting_accept';
83
- return null;
82
+ if (event === 'send_join') return 'waiting_accept'
83
+ return null
84
84
  case 'waiting_accept':
85
- if (event === 'receive_ready_connected') return 'connected';
86
- if (event === 'receive_close' || event === 'receive_terminate') return 'closed';
87
- if (event === 'timeout') return 'closed';
88
- return null;
85
+ if (event === 'receive_ready_connected') return 'connected'
86
+ if (event === 'receive_close' || event === 'receive_terminate') return 'closed'
87
+ if (event === 'timeout') return 'closed'
88
+ return null
89
89
  case 'connected':
90
- if (event === 'receive_close' || event === 'receive_terminate') return 'closed';
91
- if (event === 'transport_disconnected') return 'disconnected';
92
- if (event === 'session_expired') return 'closed';
93
- return null;
90
+ if (event === 'receive_close' || event === 'receive_terminate') return 'closed'
91
+ if (event === 'transport_disconnected') return 'disconnected'
92
+ if (event === 'session_expired') return 'closed'
93
+ return null
94
94
  case 'disconnected':
95
- if (event === 'send_join_reconnect') return 'waiting_accept';
96
- if (event === 'receive_channel_not_found') return 'disconnected';
97
- if (event === 'session_expired') return 'closed';
98
- if (event === 'give_up') return 'closed';
99
- return null;
95
+ if (event === 'send_join_reconnect') return 'waiting_accept'
96
+ if (event === 'receive_channel_not_found') return 'disconnected'
97
+ if (event === 'session_expired') return 'closed'
98
+ if (event === 'give_up') return 'closed'
99
+ return null
100
100
  case 'closed':
101
- return null; // terminal state
101
+ return null // terminal state
102
102
  }
103
103
  }
104
104
 
@@ -109,103 +109,108 @@ function walletTransition(state: WalletState, event: WalletEvent): WalletState |
109
109
  describe('Section 14 — DApp state machine', () => {
110
110
  describe('valid transitions: idle -> waiting -> pending_accept -> connected -> closed', () => {
111
111
  it('full happy path', () => {
112
- let state: DAppState = 'idle';
113
- state = dappTransition(state, 'send_create')!;
114
- expect(state).toBe('waiting');
115
- state = dappTransition(state, 'receive_join')!;
116
- expect(state).toBe('pending_accept');
117
- state = dappTransition(state, 'sealed_join_verified_send_accept')!;
118
- expect(state).toBe('connected');
119
- });
112
+ let state: DAppState = 'idle'
113
+ state = dappTransition(state, 'send_create') ?? 'closed'
114
+ expect(state).toBe('waiting')
115
+ state = dappTransition(state, 'receive_join') ?? 'closed'
116
+ expect(state).toBe('pending_accept')
117
+ state = dappTransition(state, 'sealed_join_verified_send_accept') ?? 'closed'
118
+ expect(state).toBe('connected')
119
+ })
120
120
 
121
121
  it('close from connected', () => {
122
- expect(dappTransition('connected', 'receive_close')).toBe('closed');
123
- });
122
+ expect(dappTransition('connected', 'receive_close')).toBe('closed')
123
+ })
124
124
 
125
125
  it('terminate from connected', () => {
126
- expect(dappTransition('connected', 'receive_terminate')).toBe('closed');
127
- });
126
+ expect(dappTransition('connected', 'receive_terminate')).toBe('closed')
127
+ })
128
128
 
129
129
  it('session expiry from connected', () => {
130
- expect(dappTransition('connected', 'session_expired')).toBe('closed');
131
- });
132
- });
130
+ expect(dappTransition('connected', 'session_expired')).toBe('closed')
131
+ })
132
+ })
133
133
 
134
134
  describe('invalid transitions are rejected', () => {
135
135
  it('cannot receive join in idle', () => {
136
- expect(dappTransition('idle', 'receive_join')).toBeNull();
137
- });
136
+ expect(dappTransition('idle', 'receive_join')).toBeNull()
137
+ })
138
138
 
139
139
  it('cannot send create in waiting', () => {
140
- expect(dappTransition('waiting', 'send_create')).toBeNull();
141
- });
140
+ expect(dappTransition('waiting', 'send_create')).toBeNull()
141
+ })
142
142
 
143
143
  it('cannot send create in pending_accept', () => {
144
- expect(dappTransition('pending_accept', 'send_create')).toBeNull();
145
- });
144
+ expect(dappTransition('pending_accept', 'send_create')).toBeNull()
145
+ })
146
146
 
147
147
  it('cannot receive join in pending_accept', () => {
148
- expect(dappTransition('pending_accept', 'receive_join')).toBeNull();
149
- });
148
+ expect(dappTransition('pending_accept', 'receive_join')).toBeNull()
149
+ })
150
150
 
151
151
  it('cannot send create in connected', () => {
152
- expect(dappTransition('connected', 'send_create')).toBeNull();
153
- });
152
+ expect(dappTransition('connected', 'send_create')).toBeNull()
153
+ })
154
154
 
155
155
  it('closed is terminal — all events rejected', () => {
156
156
  const events: DAppEvent[] = [
157
- 'send_create', 'receive_join', 'sealed_join_verified_send_accept',
158
- 'receive_close', 'receive_terminate', 'timeout',
159
- 'transport_disconnected', 'send_create_reconnect',
160
- ];
157
+ 'send_create',
158
+ 'receive_join',
159
+ 'sealed_join_verified_send_accept',
160
+ 'receive_close',
161
+ 'receive_terminate',
162
+ 'timeout',
163
+ 'transport_disconnected',
164
+ 'send_create_reconnect',
165
+ ]
161
166
  for (const event of events) {
162
- expect(dappTransition('closed', event)).toBeNull();
167
+ expect(dappTransition('closed', event)).toBeNull()
163
168
  }
164
- });
165
- });
169
+ })
170
+ })
166
171
 
167
172
  describe('close and timeout paths', () => {
168
173
  it('waiting -> close on receive_close', () => {
169
- expect(dappTransition('waiting', 'receive_close')).toBe('closed');
170
- });
174
+ expect(dappTransition('waiting', 'receive_close')).toBe('closed')
175
+ })
171
176
 
172
177
  it('waiting -> closed on timeout', () => {
173
- expect(dappTransition('waiting', 'timeout')).toBe('closed');
174
- });
178
+ expect(dappTransition('waiting', 'timeout')).toBe('closed')
179
+ })
175
180
 
176
181
  it('pending_accept -> closed on user_rejects_send_close', () => {
177
- expect(dappTransition('pending_accept', 'user_rejects_send_close')).toBe('closed');
178
- });
182
+ expect(dappTransition('pending_accept', 'user_rejects_send_close')).toBe('closed')
183
+ })
179
184
 
180
185
  it('pending_accept -> closed on timeout', () => {
181
- expect(dappTransition('pending_accept', 'timeout')).toBe('closed');
182
- });
183
- });
186
+ expect(dappTransition('pending_accept', 'timeout')).toBe('closed')
187
+ })
188
+ })
184
189
 
185
190
  describe('reconnect: disconnected -> waiting with same ch', () => {
186
191
  it('transport disconnect moves to disconnected', () => {
187
- expect(dappTransition('connected', 'transport_disconnected')).toBe('disconnected');
188
- });
192
+ expect(dappTransition('connected', 'transport_disconnected')).toBe('disconnected')
193
+ })
189
194
 
190
195
  it('send_create_reconnect from disconnected goes to waiting', () => {
191
- expect(dappTransition('disconnected', 'send_create_reconnect')).toBe('waiting');
192
- });
196
+ expect(dappTransition('disconnected', 'send_create_reconnect')).toBe('waiting')
197
+ })
193
198
 
194
199
  it('session expiry from disconnected goes to closed', () => {
195
- expect(dappTransition('disconnected', 'session_expired')).toBe('closed');
196
- });
200
+ expect(dappTransition('disconnected', 'session_expired')).toBe('closed')
201
+ })
197
202
 
198
203
  it('give up from disconnected goes to closed', () => {
199
- expect(dappTransition('disconnected', 'give_up')).toBe('closed');
200
- });
201
- });
204
+ expect(dappTransition('disconnected', 'give_up')).toBe('closed')
205
+ })
206
+ })
202
207
 
203
208
  describe('race condition: channel_exists handling', () => {
204
209
  it('receive_channel_exists stays in disconnected (transient failure)', () => {
205
- expect(dappTransition('disconnected', 'receive_channel_exists')).toBe('disconnected');
206
- });
207
- });
208
- });
210
+ expect(dappTransition('disconnected', 'receive_channel_exists')).toBe('disconnected')
211
+ })
212
+ })
213
+ })
209
214
 
210
215
  // ---------------------------------------------------------------------------
211
216
  // Wallet state machine tests
@@ -214,81 +219,85 @@ describe('Section 14 — DApp state machine', () => {
214
219
  describe('Section 14 — Wallet state machine', () => {
215
220
  describe('valid transitions: idle -> waiting_accept -> connected -> closed', () => {
216
221
  it('full happy path', () => {
217
- let state: WalletState = 'idle';
218
- state = walletTransition(state, 'send_join')!;
219
- expect(state).toBe('waiting_accept');
220
- state = walletTransition(state, 'receive_ready_connected')!;
221
- expect(state).toBe('connected');
222
- });
222
+ let state: WalletState = 'idle'
223
+ state = walletTransition(state, 'send_join') ?? 'closed'
224
+ expect(state).toBe('waiting_accept')
225
+ state = walletTransition(state, 'receive_ready_connected') ?? 'closed'
226
+ expect(state).toBe('connected')
227
+ })
223
228
 
224
229
  it('close from connected', () => {
225
- expect(walletTransition('connected', 'receive_close')).toBe('closed');
226
- });
230
+ expect(walletTransition('connected', 'receive_close')).toBe('closed')
231
+ })
227
232
 
228
233
  it('terminate from connected', () => {
229
- expect(walletTransition('connected', 'receive_terminate')).toBe('closed');
230
- });
231
- });
234
+ expect(walletTransition('connected', 'receive_terminate')).toBe('closed')
235
+ })
236
+ })
232
237
 
233
238
  describe('invalid transitions are rejected', () => {
234
239
  it('cannot receive_ready_connected in idle', () => {
235
- expect(walletTransition('idle', 'receive_ready_connected')).toBeNull();
236
- });
240
+ expect(walletTransition('idle', 'receive_ready_connected')).toBeNull()
241
+ })
237
242
 
238
243
  it('cannot send_join in waiting_accept', () => {
239
- expect(walletTransition('waiting_accept', 'send_join')).toBeNull();
240
- });
244
+ expect(walletTransition('waiting_accept', 'send_join')).toBeNull()
245
+ })
241
246
 
242
247
  it('cannot send_join in connected', () => {
243
- expect(walletTransition('connected', 'send_join')).toBeNull();
244
- });
248
+ expect(walletTransition('connected', 'send_join')).toBeNull()
249
+ })
245
250
 
246
251
  it('closed is terminal — all events rejected', () => {
247
252
  const events: WalletEvent[] = [
248
- 'send_join', 'receive_ready_connected', 'receive_close',
249
- 'receive_terminate', 'timeout', 'transport_disconnected',
253
+ 'send_join',
254
+ 'receive_ready_connected',
255
+ 'receive_close',
256
+ 'receive_terminate',
257
+ 'timeout',
258
+ 'transport_disconnected',
250
259
  'send_join_reconnect',
251
- ];
260
+ ]
252
261
  for (const event of events) {
253
- expect(walletTransition('closed', event)).toBeNull();
262
+ expect(walletTransition('closed', event)).toBeNull()
254
263
  }
255
- });
256
- });
264
+ })
265
+ })
257
266
 
258
267
  describe('close and timeout paths', () => {
259
268
  it('waiting_accept -> closed on receive_close', () => {
260
- expect(walletTransition('waiting_accept', 'receive_close')).toBe('closed');
261
- });
269
+ expect(walletTransition('waiting_accept', 'receive_close')).toBe('closed')
270
+ })
262
271
 
263
272
  it('waiting_accept -> closed on timeout', () => {
264
- expect(walletTransition('waiting_accept', 'timeout')).toBe('closed');
265
- });
266
- });
273
+ expect(walletTransition('waiting_accept', 'timeout')).toBe('closed')
274
+ })
275
+ })
267
276
 
268
277
  describe('reconnect: disconnected -> waiting_accept with same ch', () => {
269
278
  it('transport disconnect moves to disconnected', () => {
270
- expect(walletTransition('connected', 'transport_disconnected')).toBe('disconnected');
271
- });
279
+ expect(walletTransition('connected', 'transport_disconnected')).toBe('disconnected')
280
+ })
272
281
 
273
282
  it('send_join_reconnect from disconnected goes to waiting_accept', () => {
274
- expect(walletTransition('disconnected', 'send_join_reconnect')).toBe('waiting_accept');
275
- });
283
+ expect(walletTransition('disconnected', 'send_join_reconnect')).toBe('waiting_accept')
284
+ })
276
285
 
277
286
  it('session expiry from disconnected goes to closed', () => {
278
- expect(walletTransition('disconnected', 'session_expired')).toBe('closed');
279
- });
287
+ expect(walletTransition('disconnected', 'session_expired')).toBe('closed')
288
+ })
280
289
 
281
290
  it('give up from disconnected goes to closed', () => {
282
- expect(walletTransition('disconnected', 'give_up')).toBe('closed');
283
- });
284
- });
291
+ expect(walletTransition('disconnected', 'give_up')).toBe('closed')
292
+ })
293
+ })
285
294
 
286
295
  describe('race condition: channel_not_found handling', () => {
287
296
  it('receive_channel_not_found stays in disconnected (transient failure)', () => {
288
- expect(walletTransition('disconnected', 'receive_channel_not_found')).toBe('disconnected');
289
- });
290
- });
291
- });
297
+ expect(walletTransition('disconnected', 'receive_channel_not_found')).toBe('disconnected')
298
+ })
299
+ })
300
+ })
292
301
 
293
302
  // ---------------------------------------------------------------------------
294
303
  // Cross-cutting state machine properties
@@ -297,68 +306,82 @@ describe('Section 14 — Wallet state machine', () => {
297
306
  describe('Section 14 — Cross-cutting properties', () => {
298
307
  it('closed is terminal for dApp — no outgoing transitions', () => {
299
308
  const allDappEvents: DAppEvent[] = [
300
- 'send_create', 'receive_join', 'sealed_join_verified_send_accept',
301
- 'user_rejects_send_close', 'receive_ready_connected', 'receive_close',
302
- 'receive_terminate', 'timeout', 'transport_disconnected',
303
- 'send_create_reconnect', 'receive_channel_exists', 'session_expired',
309
+ 'send_create',
310
+ 'receive_join',
311
+ 'sealed_join_verified_send_accept',
312
+ 'user_rejects_send_close',
313
+ 'receive_ready_connected',
314
+ 'receive_close',
315
+ 'receive_terminate',
316
+ 'timeout',
317
+ 'transport_disconnected',
318
+ 'send_create_reconnect',
319
+ 'receive_channel_exists',
320
+ 'session_expired',
304
321
  'give_up',
305
- ];
322
+ ]
306
323
  for (const event of allDappEvents) {
307
- expect(dappTransition('closed', event)).toBeNull();
324
+ expect(dappTransition('closed', event)).toBeNull()
308
325
  }
309
- });
326
+ })
310
327
 
311
328
  it('closed is terminal for wallet — no outgoing transitions', () => {
312
329
  const allWalletEvents: WalletEvent[] = [
313
- 'send_join', 'receive_ready_connected', 'receive_close',
314
- 'receive_terminate', 'timeout', 'transport_disconnected',
315
- 'send_join_reconnect', 'receive_channel_not_found', 'session_expired',
330
+ 'send_join',
331
+ 'receive_ready_connected',
332
+ 'receive_close',
333
+ 'receive_terminate',
334
+ 'timeout',
335
+ 'transport_disconnected',
336
+ 'send_join_reconnect',
337
+ 'receive_channel_not_found',
338
+ 'session_expired',
316
339
  'give_up',
317
- ];
340
+ ]
318
341
  for (const event of allWalletEvents) {
319
- expect(walletTransition('closed', event)).toBeNull();
342
+ expect(walletTransition('closed', event)).toBeNull()
320
343
  }
321
- });
344
+ })
322
345
 
323
346
  it('reconnect path reuses the full handshake flow (create/join/accept)', () => {
324
347
  // DApp: disconnected -> waiting (via create) -> pending_accept (via join) -> connected
325
- let dapp: DAppState = 'disconnected';
326
- dapp = dappTransition(dapp, 'send_create_reconnect')!;
327
- expect(dapp).toBe('waiting');
328
- dapp = dappTransition(dapp, 'receive_join')!;
329
- expect(dapp).toBe('pending_accept');
330
- dapp = dappTransition(dapp, 'sealed_join_verified_send_accept')!;
331
- expect(dapp).toBe('connected');
348
+ let dapp: DAppState = 'disconnected'
349
+ dapp = dappTransition(dapp, 'send_create_reconnect') ?? 'closed'
350
+ expect(dapp).toBe('waiting')
351
+ dapp = dappTransition(dapp, 'receive_join') ?? 'closed'
352
+ expect(dapp).toBe('pending_accept')
353
+ dapp = dappTransition(dapp, 'sealed_join_verified_send_accept') ?? 'closed'
354
+ expect(dapp).toBe('connected')
332
355
 
333
356
  // Wallet: disconnected -> waiting_accept (via join) -> connected
334
- let wallet: WalletState = 'disconnected';
335
- wallet = walletTransition(wallet, 'send_join_reconnect')!;
336
- expect(wallet).toBe('waiting_accept');
337
- wallet = walletTransition(wallet, 'receive_ready_connected')!;
338
- expect(wallet).toBe('connected');
339
- });
357
+ let wallet: WalletState = 'disconnected'
358
+ wallet = walletTransition(wallet, 'send_join_reconnect') ?? 'closed'
359
+ expect(wallet).toBe('waiting_accept')
360
+ wallet = walletTransition(wallet, 'receive_ready_connected') ?? 'closed'
361
+ expect(wallet).toBe('connected')
362
+ })
340
363
 
341
364
  it('race condition recovery: multiple channel_exists before successful reconnect', () => {
342
- let state: DAppState = 'disconnected';
365
+ let state: DAppState = 'disconnected'
343
366
  // Multiple transient failures
344
- state = dappTransition(state, 'receive_channel_exists')!;
345
- expect(state).toBe('disconnected');
346
- state = dappTransition(state, 'receive_channel_exists')!;
347
- expect(state).toBe('disconnected');
367
+ state = dappTransition(state, 'receive_channel_exists') ?? 'closed'
368
+ expect(state).toBe('disconnected')
369
+ state = dappTransition(state, 'receive_channel_exists') ?? 'closed'
370
+ expect(state).toBe('disconnected')
348
371
  // Eventually succeeds
349
- state = dappTransition(state, 'send_create_reconnect')!;
350
- expect(state).toBe('waiting');
351
- });
372
+ state = dappTransition(state, 'send_create_reconnect') ?? 'closed'
373
+ expect(state).toBe('waiting')
374
+ })
352
375
 
353
376
  it('race condition recovery: multiple channel_not_found before wallet reconnects', () => {
354
- let state: WalletState = 'disconnected';
377
+ let state: WalletState = 'disconnected'
355
378
  // Multiple transient failures
356
- state = walletTransition(state, 'receive_channel_not_found')!;
357
- expect(state).toBe('disconnected');
358
- state = walletTransition(state, 'receive_channel_not_found')!;
359
- expect(state).toBe('disconnected');
379
+ state = walletTransition(state, 'receive_channel_not_found') ?? 'closed'
380
+ expect(state).toBe('disconnected')
381
+ state = walletTransition(state, 'receive_channel_not_found') ?? 'closed'
382
+ expect(state).toBe('disconnected')
360
383
  // Eventually succeeds
361
- state = walletTransition(state, 'send_join_reconnect')!;
362
- expect(state).toBe('waiting_accept');
363
- });
364
- });
384
+ state = walletTransition(state, 'send_join_reconnect') ?? 'closed'
385
+ expect(state).toBe('waiting_accept')
386
+ })
387
+ })