@mictonode/widget 0.3.22 → 0.3.23

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 (89) hide show
  1. package/README.md +2 -2
  2. package/dist/{ConstantsUtil-df193f67.js → ConstantsUtil-f9dc60b6.js} +3 -3
  3. package/dist/{ConstantsUtil-1b0c0436.js → ConstantsUtil-fb0ff2e5.js} +1 -1
  4. package/dist/{ExchangeController-953f14bc.js → ExchangeController-173fd1dc.js} +2 -2
  5. package/dist/{HelpersUtil-3810bb58.js → HelpersUtil-4891ff5f.js} +4 -4
  6. package/dist/{ReownAuthentication-7c5b6e79.js → ReownAuthentication-bba89dc5.js} +2 -2
  7. package/dist/{SwapController-e1a7b5f8.js → SwapController-e8b097ca.js} +3 -3
  8. package/dist/{abis-61cccfbf.js → abis-96fed285.js} +1 -1
  9. package/dist/{assertCurrentChain-0c3d6c93.js → assertCurrentChain-b60aa59f.js} +1 -1
  10. package/dist/{ccip-80fe88fb.js → ccip-8f82ae2a.js} +6 -6
  11. package/dist/{data-capture-7edeb077.js → data-capture-7009ffa0.js} +8 -8
  12. package/dist/{email-40537ec5.js → email-d6e2da58.js} +12 -12
  13. package/dist/{embedded-wallet-f71f6169.js → embedded-wallet-48284179.js} +14 -14
  14. package/dist/{encodeFunctionData-91e7c0f6.js → encodeFunctionData-b24a0b2b.js} +2 -2
  15. package/dist/{fallback-1ab5a270.js → fallback-ae0315d4.js} +2 -2
  16. package/dist/features-82db0d52.js +8 -0
  17. package/dist/{hashTypedData-1e059dc4.js → hashTypedData-c13ba550.js} +2 -2
  18. package/dist/{http-d9e0a50a.js → http-90ef23f3.js} +2 -2
  19. package/dist/{index-d3f36c31.js → index-0ac134af.js} +34 -34
  20. package/dist/{index-2cd6a2b2.js → index-19f302c4.js} +3 -3
  21. package/dist/{index-78c5b9e2.js → index-22727038.js} +3 -3
  22. package/dist/{index-144edd3f.js → index-270ff8a2.js} +11 -11
  23. package/dist/{index-abb0ff46.js → index-29eb95bd.js} +3 -3
  24. package/dist/{index-9cc1116b.js → index-2a90711e.js} +2 -2
  25. package/dist/{index-289a865f.js → index-2de6c5bf.js} +4 -4
  26. package/dist/{index-fbced89b.js → index-3de7fa96.js} +3 -3
  27. package/dist/{index-26b53c8a.js → index-42a8a5ec.js} +15 -15
  28. package/dist/{index-58ec291c.js → index-4bd03aed.js} +7 -7
  29. package/dist/{index-85bc86d7.js → index-52edc95a.js} +2 -2
  30. package/dist/{index-04dd1ab3.js → index-577d2709.js} +4 -4
  31. package/dist/{index-4a4ee1e7.js → index-582e4199.js} +7 -7
  32. package/dist/{index-72706598.js → index-617b2147.js} +6 -6
  33. package/dist/{index-0b6166c5.js → index-6a0902fc.js} +2 -2
  34. package/dist/{index-a37d4c03.js → index-6d51f24f.js} +2 -2
  35. package/dist/{index-759b09b0.js → index-80954332.js} +3 -3
  36. package/dist/{index-0e2c49ad.js → index-95777860.js} +7 -7
  37. package/dist/{index-d9effd37.js → index-96729706.js} +13 -13
  38. package/dist/{index-39f31546.js → index-997bc756.js} +2 -2
  39. package/dist/{index-cb4d3345.js → index-a41841e6.js} +3 -3
  40. package/dist/{index-c10f5e72.js → index-adaa2ea4.js} +3 -3
  41. package/dist/{index-97b58f89.js → index-b37b670f.js} +11 -11
  42. package/dist/{index-0763de43.js → index-b8e2c282.js} +3 -3
  43. package/dist/{index-1f02e7d0.js → index-bb5e4071.js} +1 -1
  44. package/dist/{index-3678e7d8.js → index-bf40b971.js} +2 -2
  45. package/dist/{index-a4f054e9.js → index-cb69565e.js} +2 -2
  46. package/dist/{index-197dfd69.js → index-cbb6096a.js} +3 -3
  47. package/dist/{index-119c358f.js → index-ce6854ef.js} +14 -14
  48. package/dist/{index-4fdc7a25.js → index-d7e2a4a3.js} +2 -2
  49. package/dist/{index-0176180b.js → index-e7fbb597.js} +4 -4
  50. package/dist/{index-d5e3e4ba.js → index-f4cd8dd1.js} +3 -3
  51. package/dist/{index-a0c590f7.js → index-f886cf92.js} +2 -2
  52. package/dist/{index-95e1a01b.js → index-faade258.js} +3 -3
  53. package/dist/{localBatchGatewayRequest-8fd1b198.js → localBatchGatewayRequest-ade4aa2f.js} +3 -3
  54. package/dist/{main-7ddb238e.js → main-3b573cfc.js} +38694 -38472
  55. package/dist/{networks-d2ae1da7.js → networks-446d3512.js} +10 -10
  56. package/dist/{onramp-2966f1b1.js → onramp-57d19c5d.js} +19 -19
  57. package/dist/{parseSignature-fca372a1.js → parseSignature-783d626a.js} +8 -8
  58. package/dist/{pay-with-exchange-b0198b3f.js → pay-with-exchange-c59a5773.js} +22 -22
  59. package/dist/ping-widget.js +1 -1
  60. package/dist/ping-widget.umd.cjs +680 -680
  61. package/dist/{query.lcd-873499ca.js → query.lcd-447fb91a.js} +1 -1
  62. package/dist/{query.rpc.Query-d2bcf632.js → query.rpc.Query-97503d87.js} +1 -1
  63. package/dist/{receive-6fbb6662.js → receive-3073a49d.js} +6 -6
  64. package/dist/{ref-2c86b029.js → ref-6c3aabc7.js} +2 -2
  65. package/dist/{send-630ed203.js → send-42403797.js} +22 -22
  66. package/dist/{serializeAuthorizationList-e95a85d8.js → serializeAuthorizationList-70ec977c.js} +4 -4
  67. package/dist/{sha2-c04ffd62.js → sha2-249ae7eb.js} +2 -2
  68. package/dist/{socials-73a5efb0.js → socials-69b0cf18.js} +18 -18
  69. package/dist/{swaps-7b57265f.js → swaps-4e99bb0f.js} +18 -18
  70. package/dist/{transactions-876c066c.js → transactions-1d0c2669.js} +9 -9
  71. package/dist/{tx.rpc.msg-4d8bd1a4.js → tx.rpc.msg-8aa727ea.js} +1 -1
  72. package/dist/{tx.rpc.msg-d08b8ebe.js → tx.rpc.msg-a26b3557.js} +1 -1
  73. package/dist/{verifyHash-ee28e3d7.js → verifyHash-c8962067.js} +5 -5
  74. package/dist/{vue-bea1a08b.js → vue-9dc93a14.js} +10 -10
  75. package/dist/{w3m-modal-7d61c210.js → w3m-modal-291afec4.js} +25 -25
  76. package/dist/{zora-1091e7bb.js → zora-061565b0.js} +4 -4
  77. package/lib/components/ConnectWallet/index.vue +113 -31
  78. package/lib/wallet/KeplrSessionManager.ts +142 -0
  79. package/lib/wallet/WalletSessionManager.ts +111 -25
  80. package/lib/wallet/reown/EvmNetworkProvisioner.ts +97 -0
  81. package/lib/wallet/reown/ReownGateway.ts +22 -3
  82. package/package.json +1 -1
  83. package/test/components/ConnectWallet.test.ts +640 -2
  84. package/test/components/ConnectWalletStorage.test.ts +113 -3
  85. package/test/wallet/KeplrSessionManager.test.ts +269 -0
  86. package/test/wallet/WalletSessionManager.test.ts +901 -2
  87. package/test/wallet/reown/EvmNetworkProvisioner.test.ts +264 -0
  88. package/test/wallet/reown/ReownGateway.test.ts +66 -1
  89. package/dist/features-a2f2ff4f.js +0 -8
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import {
3
3
  WalletName,
4
4
  type AbstractWallet,
5
+ type ConnectedWallet,
5
6
  type WalletArgument,
6
7
  } from '../../lib/wallet/Wallet';
7
8
  import {
@@ -43,17 +44,24 @@ const walletArgument: WalletArgument = {
43
44
  prefix: 'warden',
44
45
  };
45
46
 
46
- function fakeGateway() {
47
+ function fakeGateway(
48
+ initialAccount?: ReownAccountState,
49
+ initialNetwork?: ReownNetworkState,
50
+ ) {
47
51
  let accountListener: ((state: ReownAccountState) => void) | undefined;
48
52
  let networkListener: ((state: ReownNetworkState) => void) | undefined;
53
+ let currentAccount = initialAccount;
54
+ let currentNetwork = initialNetwork;
49
55
  const gateway: ReownGateway & {
50
56
  emitAccount: (state: ReownAccountState) => void;
51
57
  emitNetwork: (state: ReownNetworkState) => void;
52
58
  } = {
53
59
  open: vi.fn(async () => undefined),
54
60
  disconnect: vi.fn(async () => undefined),
61
+ ensureNetwork: vi.fn(async () => undefined),
55
62
  getProvider: vi.fn(() => ({ request: vi.fn(async () => undefined) })),
56
- getAccount: vi.fn(() => undefined),
63
+ getAccount: vi.fn(() => currentAccount),
64
+ getNetwork: vi.fn(() => currentNetwork),
57
65
  getWalletInfo: vi.fn(() => ({
58
66
  name: 'Rabby Wallet',
59
67
  type: 'injected',
@@ -67,21 +75,391 @@ function fakeGateway() {
67
75
  return vi.fn();
68
76
  }),
69
77
  emitAccount(state) {
78
+ currentAccount = state;
70
79
  accountListener?.(state);
71
80
  },
72
81
  emitNetwork(state) {
82
+ currentNetwork = state;
73
83
  networkListener?.(state);
74
84
  },
75
85
  };
76
86
  return gateway;
77
87
  }
78
88
 
89
+ function walletForAddress(
90
+ evmAddress: string,
91
+ marker: number,
92
+ ): AbstractWallet {
93
+ return {
94
+ name: WalletName.Evm,
95
+ getAccounts: vi.fn(async () => [
96
+ {
97
+ address: ethToEthermint(evmAddress, 'warden'),
98
+ algo: 'secp256k1',
99
+ pubkey: new Uint8Array([marker]),
100
+ },
101
+ ]),
102
+ supportCoinType: vi.fn(async () => true),
103
+ sign: vi.fn(async () => new Uint8Array()),
104
+ };
105
+ }
106
+
79
107
  describe('WalletSessionManager', () => {
80
108
  beforeEach(() => {
81
109
  localStorage.clear();
82
110
  clearRuntimeWallet(hdPath);
83
111
  });
84
112
 
113
+ it('does not provision a network while restoring a session', async () => {
114
+ const address = '0x1111111111111111111111111111111111111111';
115
+ const gateway = fakeGateway(
116
+ {
117
+ address,
118
+ isConnected: true,
119
+ status: 'connected',
120
+ },
121
+ { caipNetworkId: config.network.caipNetworkId },
122
+ );
123
+ const wallet = walletForAddress(address, 1);
124
+ const manager = new WalletSessionManager(
125
+ hdPath,
126
+ async () => gateway,
127
+ () => wallet,
128
+ );
129
+
130
+ await manager.start(
131
+ config,
132
+ walletArgument,
133
+ vi.fn(),
134
+ vi.fn(),
135
+ );
136
+
137
+ expect(gateway.ensureNetwork).not.toHaveBeenCalled();
138
+ expect(wallet.getAccounts).toHaveBeenCalledOnce();
139
+ });
140
+
141
+ it('provisions the network before verifying a manual account', async () => {
142
+ const order: string[] = [];
143
+ const address = '0x1111111111111111111111111111111111111111';
144
+ const gateway = fakeGateway(
145
+ {
146
+ address,
147
+ isConnected: true,
148
+ status: 'connected',
149
+ },
150
+ { caipNetworkId: config.network.caipNetworkId },
151
+ );
152
+ gateway.ensureNetwork = vi.fn(async () => {
153
+ order.push('network');
154
+ });
155
+ const wallet = walletForAddress(address, 1);
156
+ wallet.getAccounts = vi.fn(async () => {
157
+ order.push('accounts');
158
+ return [{
159
+ address: ethToEthermint(address, 'warden'),
160
+ algo: 'secp256k1',
161
+ pubkey: new Uint8Array([1]),
162
+ }];
163
+ });
164
+ const onChange = vi.fn();
165
+ const manager = new WalletSessionManager(
166
+ hdPath,
167
+ async () => gateway,
168
+ () => wallet,
169
+ );
170
+
171
+ await manager.connect(
172
+ config,
173
+ walletArgument,
174
+ onChange,
175
+ vi.fn(),
176
+ );
177
+
178
+ expect(order).toEqual(['network', 'accounts']);
179
+ expect(onChange).toHaveBeenLastCalledWith(
180
+ expect.objectContaining({ wallet: WalletName.Evm }),
181
+ );
182
+ expect(gateway.open).not.toHaveBeenCalled();
183
+ });
184
+
185
+ it('does not report the bootstrap network while waiting for a manual account', async () => {
186
+ const address = '0x1111111111111111111111111111111111111111';
187
+ const gateway = fakeGateway(
188
+ undefined,
189
+ { caipNetworkId: 'eip155:1' },
190
+ );
191
+ const wallet = walletForAddress(address, 1);
192
+ const onError = vi.fn();
193
+ const manager = new WalletSessionManager(
194
+ hdPath,
195
+ async () => gateway,
196
+ () => wallet,
197
+ );
198
+
199
+ await manager.connect(
200
+ config,
201
+ walletArgument,
202
+ vi.fn(),
203
+ onError,
204
+ );
205
+
206
+ expect(gateway.open).toHaveBeenCalledOnce();
207
+ expect(gateway.ensureNetwork).not.toHaveBeenCalled();
208
+ expect(onError).not.toHaveBeenCalled();
209
+
210
+ gateway.emitAccount({
211
+ address,
212
+ isConnected: true,
213
+ status: 'connected',
214
+ });
215
+ await vi.waitFor(() =>
216
+ expect(getRuntimeWallet(hdPath)).toBe(wallet),
217
+ );
218
+
219
+ expect(gateway.ensureNetwork).toHaveBeenCalledOnce();
220
+ expect(wallet.getAccounts).toHaveBeenCalledOnce();
221
+ expect(onError).not.toHaveBeenCalled();
222
+ });
223
+
224
+ it('fails closed before account verification when provisioning is rejected', async () => {
225
+ const address = '0x1111111111111111111111111111111111111111';
226
+ const gateway = fakeGateway(
227
+ {
228
+ address,
229
+ isConnected: true,
230
+ status: 'connected',
231
+ },
232
+ { caipNetworkId: config.network.caipNetworkId },
233
+ );
234
+ gateway.ensureNetwork = vi.fn(async () => {
235
+ throw { code: 4001 };
236
+ });
237
+ const wallet = walletForAddress(address, 1);
238
+ const onChange = vi.fn();
239
+ const onError = vi.fn();
240
+ const manager = new WalletSessionManager(
241
+ hdPath,
242
+ async () => gateway,
243
+ () => wallet,
244
+ );
245
+
246
+ await manager.connect(
247
+ config,
248
+ walletArgument,
249
+ onChange,
250
+ onError,
251
+ );
252
+
253
+ expect(wallet.getAccounts).not.toHaveBeenCalled();
254
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
255
+ expect(localStorage.length).toBe(0);
256
+ expect(onChange).toHaveBeenLastCalledWith(undefined);
257
+ expect(onError).toHaveBeenCalledOnce();
258
+ expect(gateway.open).not.toHaveBeenCalled();
259
+ });
260
+
261
+ it('can retry a manual connection after provisioning was rejected', async () => {
262
+ const address = '0x1111111111111111111111111111111111111111';
263
+ const gateway = fakeGateway(
264
+ {
265
+ address,
266
+ isConnected: true,
267
+ status: 'connected',
268
+ },
269
+ { caipNetworkId: config.network.caipNetworkId },
270
+ );
271
+ gateway.ensureNetwork = vi.fn()
272
+ .mockRejectedValueOnce({ code: 4001 })
273
+ .mockResolvedValueOnce(undefined);
274
+ const wallet = walletForAddress(address, 1);
275
+ const onChange = vi.fn();
276
+ const manager = new WalletSessionManager(
277
+ hdPath,
278
+ async () => gateway,
279
+ () => wallet,
280
+ );
281
+
282
+ await manager.connect(
283
+ config,
284
+ walletArgument,
285
+ onChange,
286
+ vi.fn(),
287
+ );
288
+
289
+ gateway.emitAccount({
290
+ address,
291
+ isConnected: true,
292
+ status: 'connected',
293
+ });
294
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
295
+
296
+ expect(gateway.ensureNetwork).toHaveBeenCalledOnce();
297
+ expect(wallet.getAccounts).not.toHaveBeenCalled();
298
+
299
+ await manager.connect(
300
+ config,
301
+ walletArgument,
302
+ onChange,
303
+ vi.fn(),
304
+ );
305
+
306
+ expect(gateway.ensureNetwork).toHaveBeenCalledTimes(2);
307
+ expect(wallet.getAccounts).toHaveBeenCalledOnce();
308
+ expect(getRuntimeWallet(hdPath)).toBe(wallet);
309
+ expect(onChange).toHaveBeenLastCalledWith(
310
+ expect.objectContaining({ wallet: WalletName.Evm }),
311
+ );
312
+ });
313
+
314
+ it('does not provision again after a successful manual attempt', async () => {
315
+ const address = '0x1111111111111111111111111111111111111111';
316
+ const gateway = fakeGateway(
317
+ {
318
+ address,
319
+ isConnected: true,
320
+ status: 'connected',
321
+ },
322
+ { caipNetworkId: config.network.caipNetworkId },
323
+ );
324
+ const wallet = walletForAddress(address, 1);
325
+ const manager = new WalletSessionManager(
326
+ hdPath,
327
+ async () => gateway,
328
+ () => wallet,
329
+ );
330
+
331
+ await manager.connect(
332
+ config,
333
+ walletArgument,
334
+ vi.fn(),
335
+ vi.fn(),
336
+ );
337
+
338
+ gateway.emitNetwork({ caipNetworkId: 'eip155:1' });
339
+ gateway.emitAccount({
340
+ address,
341
+ isConnected: true,
342
+ status: 'connected',
343
+ });
344
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
345
+
346
+ expect(gateway.ensureNetwork).toHaveBeenCalledOnce();
347
+ expect(wallet.getAccounts).toHaveBeenCalledOnce();
348
+ });
349
+
350
+ it('keeps provisioning owned through its transient wrong-network event', async () => {
351
+ const address = '0x1111111111111111111111111111111111111111';
352
+ const gateway = fakeGateway(
353
+ {
354
+ address,
355
+ isConnected: true,
356
+ status: 'connected',
357
+ },
358
+ { caipNetworkId: config.network.caipNetworkId },
359
+ );
360
+ let releaseProvisioning!: () => void;
361
+ let isCurrent: (() => boolean) | undefined;
362
+ gateway.ensureNetwork = vi.fn(async (checkCurrent?: () => boolean) => {
363
+ isCurrent = checkCurrent;
364
+ await new Promise<void>((resolve) => {
365
+ releaseProvisioning = resolve;
366
+ });
367
+ if (!checkCurrent?.()) {
368
+ throw new Error('EVM network request is no longer active');
369
+ }
370
+ });
371
+ const wallet = walletForAddress(address, 1);
372
+ const onError = vi.fn();
373
+ const manager = new WalletSessionManager(
374
+ hdPath,
375
+ async () => gateway,
376
+ () => wallet,
377
+ );
378
+
379
+ const connecting = manager.connect(
380
+ config,
381
+ walletArgument,
382
+ vi.fn(),
383
+ onError,
384
+ );
385
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
386
+
387
+ gateway.emitNetwork({ caipNetworkId: 'eip155:1' });
388
+
389
+ expect(isCurrent).toBeTypeOf('function');
390
+ expect(isCurrent?.()).toBe(true);
391
+
392
+ releaseProvisioning();
393
+ await connecting;
394
+
395
+ expect(wallet.getAccounts).toHaveBeenCalledOnce();
396
+ expect(getRuntimeWallet(hdPath)).toBe(wallet);
397
+ expect(onError).not.toHaveBeenCalled();
398
+ });
399
+
400
+ it('invalidates pending provisioning when the account changes', async () => {
401
+ const firstAddress =
402
+ '0x1111111111111111111111111111111111111111';
403
+ const secondAddress =
404
+ '0x2222222222222222222222222222222222222222';
405
+ const gateway = fakeGateway(
406
+ {
407
+ address: firstAddress,
408
+ isConnected: true,
409
+ status: 'connected',
410
+ },
411
+ { caipNetworkId: config.network.caipNetworkId },
412
+ );
413
+ let releaseProvisioning!: () => void;
414
+ let isCurrent: (() => boolean) | undefined;
415
+ gateway.ensureNetwork = vi.fn(async (checkCurrent?: () => boolean) => {
416
+ isCurrent = checkCurrent;
417
+ await new Promise<void>((resolve) => {
418
+ releaseProvisioning = resolve;
419
+ });
420
+ if (!checkCurrent?.()) {
421
+ throw new Error('EVM network request is no longer active');
422
+ }
423
+ });
424
+ const wallet = walletForAddress(firstAddress, 1);
425
+ const manager = new WalletSessionManager(
426
+ hdPath,
427
+ async () => gateway,
428
+ () => wallet,
429
+ );
430
+
431
+ const connecting = manager.connect(
432
+ config,
433
+ walletArgument,
434
+ vi.fn(),
435
+ vi.fn(),
436
+ );
437
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
438
+
439
+ gateway.emitAccount({
440
+ address: secondAddress,
441
+ isConnected: true,
442
+ status: 'connected',
443
+ });
444
+
445
+ expect(isCurrent).toBeTypeOf('function');
446
+ expect(isCurrent?.()).toBe(false);
447
+
448
+ releaseProvisioning();
449
+ await connecting;
450
+
451
+ gateway.emitAccount({
452
+ address: secondAddress,
453
+ isConnected: true,
454
+ status: 'connected',
455
+ });
456
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
457
+
458
+ expect(gateway.ensureNetwork).toHaveBeenCalledOnce();
459
+ expect(wallet.getAccounts).not.toHaveBeenCalled();
460
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
461
+ });
462
+
85
463
  it('activates and persists a connected Reown wallet', async () => {
86
464
  const gateway = fakeGateway();
87
465
  const cosmosAddress = ethToEthermint(
@@ -234,6 +612,114 @@ describe('WalletSessionManager', () => {
234
612
  );
235
613
  });
236
614
 
615
+ it('keeps the latest account authoritative when selection returns during validation', async () => {
616
+ const gateway = fakeGateway();
617
+ const firstAddress = '0x1111111111111111111111111111111111111111';
618
+ const secondAddress = '0x2222222222222222222222222222222222222222';
619
+ const firstCosmosAddress = ethToEthermint(firstAddress, 'warden');
620
+ const secondCosmosAddress = ethToEthermint(secondAddress, 'warden');
621
+ let resolveSecondAccounts!: (
622
+ accounts: Awaited<ReturnType<AbstractWallet['getAccounts']>>,
623
+ ) => void;
624
+ const firstWallet = {
625
+ name: WalletName.Evm,
626
+ getAccounts: vi.fn(async () => [
627
+ {
628
+ address: firstCosmosAddress,
629
+ algo: 'secp256k1' as const,
630
+ pubkey: new Uint8Array([1]),
631
+ },
632
+ ]),
633
+ supportCoinType: vi.fn(async () => true),
634
+ sign: vi.fn(async () => new Uint8Array()),
635
+ } as AbstractWallet;
636
+ const pendingSecondWallet = {
637
+ name: WalletName.Evm,
638
+ getAccounts: vi.fn(
639
+ () =>
640
+ new Promise<
641
+ Awaited<ReturnType<AbstractWallet['getAccounts']>>
642
+ >((resolve) => {
643
+ resolveSecondAccounts = resolve;
644
+ }),
645
+ ),
646
+ supportCoinType: vi.fn(async () => true),
647
+ sign: vi.fn(async () => new Uint8Array()),
648
+ } as AbstractWallet;
649
+ const revalidatedFirstWallet = {
650
+ ...firstWallet,
651
+ getAccounts: vi.fn(async () => [
652
+ {
653
+ address: firstCosmosAddress,
654
+ algo: 'secp256k1' as const,
655
+ pubkey: new Uint8Array([2]),
656
+ },
657
+ ]),
658
+ } as AbstractWallet;
659
+ const firstWallets = [firstWallet, revalidatedFirstWallet];
660
+ const walletFactory = vi.fn((argument: WalletArgument) =>
661
+ argument.evmAddress?.toLowerCase() === secondAddress
662
+ ? pendingSecondWallet
663
+ : (firstWallets.shift() as AbstractWallet),
664
+ );
665
+ const changed = vi.fn();
666
+ const manager = new WalletSessionManager(
667
+ hdPath,
668
+ async () => gateway,
669
+ walletFactory,
670
+ );
671
+ await manager.start(config, walletArgument, changed);
672
+
673
+ gateway.emitAccount({
674
+ isConnected: true,
675
+ status: 'connected',
676
+ address: firstAddress,
677
+ });
678
+ await vi.waitFor(() =>
679
+ expect(getRuntimeWallet(hdPath)).toBe(firstWallet),
680
+ );
681
+ changed.mockClear();
682
+
683
+ gateway.emitAccount({
684
+ isConnected: true,
685
+ status: 'connected',
686
+ address: secondAddress,
687
+ });
688
+
689
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
690
+ expect(localStorage.getItem(hdPath)).toBeNull();
691
+ expect(changed).toHaveBeenLastCalledWith(undefined);
692
+
693
+ gateway.emitAccount({
694
+ isConnected: true,
695
+ status: 'connected',
696
+ address: firstAddress,
697
+ });
698
+ await vi.waitFor(() =>
699
+ expect(getRuntimeWallet(hdPath)).toBe(revalidatedFirstWallet),
700
+ );
701
+
702
+ resolveSecondAccounts([
703
+ {
704
+ address: secondCosmosAddress,
705
+ algo: 'secp256k1',
706
+ pubkey: new Uint8Array([3]),
707
+ },
708
+ ]);
709
+ await Promise.resolve();
710
+ await Promise.resolve();
711
+
712
+ expect(getRuntimeWallet(hdPath)).toBe(revalidatedFirstWallet);
713
+ expect(JSON.parse(localStorage.getItem(hdPath) || '{}')).toEqual(
714
+ expect.objectContaining({ cosmosAddress: firstCosmosAddress }),
715
+ );
716
+ expect(
717
+ changed.mock.calls.some(
718
+ ([value]) => value?.cosmosAddress === secondCosmosAddress,
719
+ ),
720
+ ).toBe(false);
721
+ });
722
+
237
723
  it('clears the session after disconnect', async () => {
238
724
  const gateway = fakeGateway();
239
725
  localStorage.setItem(hdPath, '{"stale":true}');
@@ -273,6 +759,90 @@ describe('WalletSessionManager', () => {
273
759
  );
274
760
  });
275
761
 
762
+ it('blocks account synchronization while the current network is invalid', async () => {
763
+ const gateway = fakeGateway(undefined, {
764
+ caipNetworkId: 'eip155:8765',
765
+ });
766
+ const walletFactory = vi.fn();
767
+ const changed = vi.fn();
768
+ const failed = vi.fn();
769
+ const manager = new WalletSessionManager(
770
+ hdPath,
771
+ async () => gateway,
772
+ walletFactory,
773
+ );
774
+ await manager.start(config, walletArgument, changed, failed);
775
+
776
+ gateway.emitAccount({
777
+ isConnected: true,
778
+ status: 'connected',
779
+ address: '0x1111111111111111111111111111111111111111',
780
+ });
781
+ await Promise.resolve();
782
+
783
+ expect(walletFactory).not.toHaveBeenCalled();
784
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
785
+ expect(localStorage.getItem(hdPath)).toBeNull();
786
+ expect(changed).toHaveBeenLastCalledWith(undefined);
787
+ expect(failed).toHaveBeenCalledWith(
788
+ expect.objectContaining({ category: 'wrong-network' }),
789
+ );
790
+ });
791
+
792
+ it('revalidates the current account after returning to the configured network', async () => {
793
+ const evmAddress = '0x1111111111111111111111111111111111111111';
794
+ const cosmosAddress = ethToEthermint(evmAddress, 'warden');
795
+ const currentAccount = {
796
+ isConnected: true,
797
+ status: 'connected',
798
+ address: evmAddress,
799
+ };
800
+ const gateway = fakeGateway(currentAccount, {
801
+ caipNetworkId: config.network.caipNetworkId,
802
+ });
803
+ const [initialWallet, revalidatedWallet] = [1, 2].map(
804
+ (marker) =>
805
+ ({
806
+ name: WalletName.Evm,
807
+ getAccounts: vi.fn(async () => [
808
+ {
809
+ address: cosmosAddress,
810
+ algo: 'secp256k1',
811
+ pubkey: new Uint8Array([marker]),
812
+ },
813
+ ]),
814
+ supportCoinType: vi.fn(async () => true),
815
+ sign: vi.fn(async () => new Uint8Array()),
816
+ }) as AbstractWallet,
817
+ );
818
+ const wallets = [initialWallet, revalidatedWallet];
819
+ const walletFactory = vi.fn(() => wallets.shift() as AbstractWallet);
820
+ const manager = new WalletSessionManager(
821
+ hdPath,
822
+ async () => gateway,
823
+ walletFactory,
824
+ );
825
+ await manager.start(config, walletArgument, vi.fn());
826
+ await vi.waitFor(() =>
827
+ expect(getRuntimeWallet(hdPath)).toBeDefined(),
828
+ );
829
+
830
+ gateway.emitNetwork({ caipNetworkId: 'eip155:8765' });
831
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
832
+
833
+ gateway.emitNetwork({
834
+ caipNetworkId: config.network.caipNetworkId,
835
+ });
836
+
837
+ await vi.waitFor(() =>
838
+ expect(getRuntimeWallet(hdPath)).toBe(revalidatedWallet),
839
+ );
840
+ expect(walletFactory).toHaveBeenCalledTimes(2);
841
+ expect(JSON.parse(localStorage.getItem(hdPath) || '{}')).toEqual(
842
+ expect.objectContaining({ cosmosAddress }),
843
+ );
844
+ });
845
+
276
846
  it('does not restore a pending account after a later disconnect', async () => {
277
847
  const gateway = fakeGateway();
278
848
  let resolveAccounts!: (
@@ -382,6 +952,241 @@ describe('WalletSessionManager', () => {
382
952
  expect(localStorage.getItem(hdPath)).toBeNull();
383
953
  });
384
954
 
955
+ it('does not clear a successor session when an older provider disconnect completes', async () => {
956
+ const oldGateway = fakeGateway();
957
+ let resolveOldDisconnect!: () => void;
958
+ vi.mocked(oldGateway.disconnect).mockImplementationOnce(
959
+ () =>
960
+ new Promise<void>((resolve) => {
961
+ resolveOldDisconnect = resolve;
962
+ }),
963
+ );
964
+ const oldManager = new WalletSessionManager(
965
+ hdPath,
966
+ async () => oldGateway,
967
+ vi.fn(),
968
+ );
969
+ setRuntimeWalletForTests(
970
+ hdPath,
971
+ walletForAddress(
972
+ '0x1111111111111111111111111111111111111111',
973
+ 1,
974
+ ),
975
+ );
976
+ localStorage.setItem(hdPath, '{"old":true}');
977
+
978
+ const disconnecting = oldManager.disconnect(config);
979
+ await vi.waitFor(() =>
980
+ expect(oldGateway.disconnect).toHaveBeenCalledOnce(),
981
+ );
982
+
983
+ const successorAddress =
984
+ '0x2222222222222222222222222222222222222222';
985
+ const successorCosmosAddress = ethToEthermint(
986
+ successorAddress,
987
+ 'warden',
988
+ );
989
+ const successorGateway = fakeGateway();
990
+ const successorWallet = walletForAddress(successorAddress, 2);
991
+ let visible: ConnectedWallet | undefined;
992
+ const changed = vi.fn((value: ConnectedWallet | undefined) => {
993
+ visible = value;
994
+ });
995
+ const successorManager = new WalletSessionManager(
996
+ hdPath,
997
+ async () => successorGateway,
998
+ () => successorWallet,
999
+ );
1000
+ await successorManager.start(config, walletArgument, changed);
1001
+ successorGateway.emitAccount({
1002
+ isConnected: true,
1003
+ status: 'connected',
1004
+ address: successorAddress,
1005
+ });
1006
+ await vi.waitFor(() =>
1007
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet),
1008
+ );
1009
+
1010
+ resolveOldDisconnect();
1011
+ await disconnecting;
1012
+
1013
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet);
1014
+ expect(JSON.parse(localStorage.getItem(hdPath) || '{}')).toEqual(
1015
+ expect.objectContaining({
1016
+ cosmosAddress: successorCosmosAddress,
1017
+ }),
1018
+ );
1019
+ expect(visible).toEqual(
1020
+ expect.objectContaining({
1021
+ cosmosAddress: successorCosmosAddress,
1022
+ }),
1023
+ );
1024
+ });
1025
+
1026
+ it('clears local state even when gateway acquisition fails during disconnect', async () => {
1027
+ const wallet = {
1028
+ name: WalletName.Evm,
1029
+ getAccounts: vi.fn(),
1030
+ supportCoinType: vi.fn(),
1031
+ sign: vi.fn(),
1032
+ } as unknown as AbstractWallet;
1033
+ setRuntimeWalletForTests(hdPath, wallet);
1034
+ localStorage.setItem(hdPath, '{"stale":true}');
1035
+ const manager = new WalletSessionManager(
1036
+ hdPath,
1037
+ async () => {
1038
+ throw new Error('factory failure');
1039
+ },
1040
+ vi.fn(),
1041
+ );
1042
+
1043
+ await expect(manager.disconnect(config)).rejects.toThrow(
1044
+ 'factory failure',
1045
+ );
1046
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
1047
+ expect(localStorage.getItem(hdPath)).toBeNull();
1048
+ });
1049
+
1050
+ it('clears the runtime signer as soon as the session is disposed', () => {
1051
+ const gateway = fakeGateway();
1052
+ const wallet = {
1053
+ name: WalletName.Evm,
1054
+ getAccounts: vi.fn(),
1055
+ supportCoinType: vi.fn(),
1056
+ sign: vi.fn(),
1057
+ } as unknown as AbstractWallet;
1058
+ setRuntimeWalletForTests(hdPath, wallet);
1059
+ const manager = new WalletSessionManager(
1060
+ hdPath,
1061
+ async () => gateway,
1062
+ vi.fn(),
1063
+ );
1064
+
1065
+ manager.dispose();
1066
+
1067
+ expect(getRuntimeWallet(hdPath)).toBeUndefined();
1068
+ });
1069
+
1070
+ it('ignores a queued account callback after disposal and successor connection', async () => {
1071
+ const oldGateway = fakeGateway();
1072
+ let visible: ConnectedWallet | undefined;
1073
+ const changed = vi.fn((value: ConnectedWallet | undefined) => {
1074
+ visible = value;
1075
+ });
1076
+ const oldManager = new WalletSessionManager(
1077
+ hdPath,
1078
+ async () => oldGateway,
1079
+ vi.fn(),
1080
+ );
1081
+ await oldManager.start(config, walletArgument, changed);
1082
+ const staleAccountCallback = vi.mocked(
1083
+ oldGateway.subscribeAccount,
1084
+ ).mock.calls[0][0];
1085
+ oldManager.dispose();
1086
+
1087
+ const successorAddress =
1088
+ '0x2222222222222222222222222222222222222222';
1089
+ const successorCosmosAddress = ethToEthermint(
1090
+ successorAddress,
1091
+ 'warden',
1092
+ );
1093
+ const successorGateway = fakeGateway();
1094
+ const successorWallet = walletForAddress(successorAddress, 2);
1095
+ const successorManager = new WalletSessionManager(
1096
+ hdPath,
1097
+ async () => successorGateway,
1098
+ () => successorWallet,
1099
+ );
1100
+ await successorManager.start(config, walletArgument, changed);
1101
+ successorGateway.emitAccount({
1102
+ isConnected: true,
1103
+ status: 'connected',
1104
+ address: successorAddress,
1105
+ });
1106
+ await vi.waitFor(() =>
1107
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet),
1108
+ );
1109
+ const changeCount = changed.mock.calls.length;
1110
+
1111
+ staleAccountCallback({
1112
+ isConnected: false,
1113
+ status: 'disconnected',
1114
+ });
1115
+ await Promise.resolve();
1116
+
1117
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet);
1118
+ expect(JSON.parse(localStorage.getItem(hdPath) || '{}')).toEqual(
1119
+ expect.objectContaining({
1120
+ cosmosAddress: successorCosmosAddress,
1121
+ }),
1122
+ );
1123
+ expect(visible).toEqual(
1124
+ expect.objectContaining({
1125
+ cosmosAddress: successorCosmosAddress,
1126
+ }),
1127
+ );
1128
+ expect(changed).toHaveBeenCalledTimes(changeCount);
1129
+ });
1130
+
1131
+ it('ignores a queued network callback after disposal and successor connection', async () => {
1132
+ const oldGateway = fakeGateway();
1133
+ let visible: ConnectedWallet | undefined;
1134
+ const changed = vi.fn((value: ConnectedWallet | undefined) => {
1135
+ visible = value;
1136
+ });
1137
+ const failed = vi.fn();
1138
+ const oldManager = new WalletSessionManager(
1139
+ hdPath,
1140
+ async () => oldGateway,
1141
+ vi.fn(),
1142
+ );
1143
+ await oldManager.start(config, walletArgument, changed, failed);
1144
+ const staleNetworkCallback = vi.mocked(
1145
+ oldGateway.subscribeNetwork,
1146
+ ).mock.calls[0][0];
1147
+ oldManager.dispose();
1148
+
1149
+ const successorAddress =
1150
+ '0x2222222222222222222222222222222222222222';
1151
+ const successorCosmosAddress = ethToEthermint(
1152
+ successorAddress,
1153
+ 'warden',
1154
+ );
1155
+ const successorGateway = fakeGateway();
1156
+ const successorWallet = walletForAddress(successorAddress, 3);
1157
+ const successorManager = new WalletSessionManager(
1158
+ hdPath,
1159
+ async () => successorGateway,
1160
+ () => successorWallet,
1161
+ );
1162
+ await successorManager.start(config, walletArgument, changed);
1163
+ successorGateway.emitAccount({
1164
+ isConnected: true,
1165
+ status: 'connected',
1166
+ address: successorAddress,
1167
+ });
1168
+ await vi.waitFor(() =>
1169
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet),
1170
+ );
1171
+ const changeCount = changed.mock.calls.length;
1172
+
1173
+ staleNetworkCallback({ caipNetworkId: 'eip155:8765' });
1174
+
1175
+ expect(getRuntimeWallet(hdPath)).toBe(successorWallet);
1176
+ expect(JSON.parse(localStorage.getItem(hdPath) || '{}')).toEqual(
1177
+ expect.objectContaining({
1178
+ cosmosAddress: successorCosmosAddress,
1179
+ }),
1180
+ );
1181
+ expect(visible).toEqual(
1182
+ expect.objectContaining({
1183
+ cosmosAddress: successorCosmosAddress,
1184
+ }),
1185
+ );
1186
+ expect(changed).toHaveBeenCalledTimes(changeCount);
1187
+ expect(failed).not.toHaveBeenCalled();
1188
+ });
1189
+
385
1190
  it('cancels a start that completes after the manager is disposed', async () => {
386
1191
  const gateway = fakeGateway();
387
1192
  let resolveGateway!: (gateway: ReownGateway) => void;
@@ -402,4 +1207,98 @@ describe('WalletSessionManager', () => {
402
1207
  expect(gateway.subscribeAccount).not.toHaveBeenCalled();
403
1208
  expect(gateway.subscribeNetwork).not.toHaveBeenCalled();
404
1209
  });
1210
+
1211
+ it('does not open a stale gateway after disposal during network provisioning', async () => {
1212
+ const address = '0x1111111111111111111111111111111111111111';
1213
+ const gateway = fakeGateway(
1214
+ {
1215
+ address,
1216
+ isConnected: true,
1217
+ status: 'connected',
1218
+ },
1219
+ { caipNetworkId: config.network.caipNetworkId },
1220
+ );
1221
+ let finishProvisioning!: () => void;
1222
+ gateway.ensureNetwork = vi.fn(
1223
+ () =>
1224
+ new Promise<void>((resolve) => {
1225
+ finishProvisioning = resolve;
1226
+ }),
1227
+ );
1228
+ const wallet = walletForAddress(address, 1);
1229
+ const manager = new WalletSessionManager(
1230
+ hdPath,
1231
+ async () => gateway,
1232
+ () => wallet,
1233
+ );
1234
+
1235
+ const connecting = manager.connect(
1236
+ config,
1237
+ walletArgument,
1238
+ vi.fn(),
1239
+ vi.fn(),
1240
+ );
1241
+ const rejected = expect(connecting).rejects.toThrow(
1242
+ 'Wallet session changed',
1243
+ );
1244
+ await vi.waitFor(() =>
1245
+ expect(gateway.ensureNetwork).toHaveBeenCalledOnce(),
1246
+ );
1247
+
1248
+ manager.dispose();
1249
+ finishProvisioning();
1250
+
1251
+ await rejected;
1252
+ expect(gateway.open).not.toHaveBeenCalled();
1253
+ expect(wallet.getAccounts).not.toHaveBeenCalled();
1254
+ });
1255
+
1256
+ it('does not open a superseded gateway after network provisioning completes', async () => {
1257
+ const address = '0x1111111111111111111111111111111111111111';
1258
+ const staleGateway = fakeGateway(
1259
+ {
1260
+ address,
1261
+ isConnected: true,
1262
+ status: 'connected',
1263
+ },
1264
+ { caipNetworkId: config.network.caipNetworkId },
1265
+ );
1266
+ const successorGateway = fakeGateway();
1267
+ let finishProvisioning!: () => void;
1268
+ staleGateway.ensureNetwork = vi.fn(
1269
+ () =>
1270
+ new Promise<void>((resolve) => {
1271
+ finishProvisioning = resolve;
1272
+ }),
1273
+ );
1274
+ const gatewayFactory = vi.fn()
1275
+ .mockResolvedValueOnce(staleGateway)
1276
+ .mockResolvedValueOnce(successorGateway);
1277
+ const manager = new WalletSessionManager(
1278
+ hdPath,
1279
+ gatewayFactory,
1280
+ vi.fn(),
1281
+ );
1282
+
1283
+ const connecting = manager.connect(
1284
+ config,
1285
+ walletArgument,
1286
+ vi.fn(),
1287
+ vi.fn(),
1288
+ );
1289
+ const rejected = expect(connecting).rejects.toThrow(
1290
+ 'Wallet session changed',
1291
+ );
1292
+ await vi.waitFor(() =>
1293
+ expect(staleGateway.ensureNetwork).toHaveBeenCalledOnce(),
1294
+ );
1295
+
1296
+ await manager.start(config, walletArgument, vi.fn(), vi.fn());
1297
+ finishProvisioning();
1298
+
1299
+ await rejected;
1300
+ expect(staleGateway.open).not.toHaveBeenCalled();
1301
+ expect(successorGateway.subscribeAccount).toHaveBeenCalledOnce();
1302
+ expect(successorGateway.subscribeNetwork).toHaveBeenCalledOnce();
1303
+ });
405
1304
  });