@mictonode/widget 0.3.23 → 0.3.24

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 (104) hide show
  1. package/dist/{ConstantsUtil-f9dc60b6.js → ConstantsUtil-69f4aadb.js} +3 -3
  2. package/dist/{ConstantsUtil-fb0ff2e5.js → ConstantsUtil-ffeefb33.js} +1 -1
  3. package/dist/{ExchangeController-173fd1dc.js → ExchangeController-920db609.js} +2 -2
  4. package/dist/{HelpersUtil-4891ff5f.js → HelpersUtil-e60d77c2.js} +4 -4
  5. package/dist/{ReownAuthentication-bba89dc5.js → ReownAuthentication-a05128a9.js} +2 -2
  6. package/dist/{SwapController-e8b097ca.js → SwapController-3307b7d5.js} +3 -3
  7. package/dist/{abis-96fed285.js → abis-5e7391e2.js} +1 -1
  8. package/dist/{assertCurrentChain-b60aa59f.js → assertCurrentChain-42cdea0e.js} +1 -1
  9. package/dist/{ccip-8f82ae2a.js → ccip-b3df1d28.js} +6 -6
  10. package/dist/{data-capture-7009ffa0.js → data-capture-6b7e5c19.js} +8 -8
  11. package/dist/{email-d6e2da58.js → email-5c6fce3f.js} +12 -12
  12. package/dist/{embedded-wallet-48284179.js → embedded-wallet-87192e5a.js} +14 -14
  13. package/dist/{encodeFunctionData-b24a0b2b.js → encodeFunctionData-929ae2a4.js} +2 -2
  14. package/dist/{fallback-ae0315d4.js → fallback-366fc17d.js} +2 -2
  15. package/dist/features-cf9f5cda.js +8 -0
  16. package/dist/{hashTypedData-c13ba550.js → hashTypedData-f86861cf.js} +2 -2
  17. package/dist/{http-90ef23f3.js → http-b4045adf.js} +2 -2
  18. package/dist/{index-3de7fa96.js → index-0313c561.js} +3 -3
  19. package/dist/{index-4bd03aed.js → index-0d05a79e.js} +7 -7
  20. package/dist/{index-b8e2c282.js → index-1450179f.js} +3 -3
  21. package/dist/{index-bb5e4071.js → index-20c073be.js} +1 -1
  22. package/dist/{index-b37b670f.js → index-23d992c9.js} +11 -11
  23. package/dist/{index-ce6854ef.js → index-2b5ce2e7.js} +14 -14
  24. package/dist/{index-cb69565e.js → index-2b935b7c.js} +2 -2
  25. package/dist/{index-bf40b971.js → index-311166f4.js} +2 -2
  26. package/dist/{index-cbb6096a.js → index-3d20f99e.js} +3 -3
  27. package/dist/{index-0ac134af.js → index-4552387b.js} +34 -34
  28. package/dist/{index-2a90711e.js → index-48fbe63e.js} +2 -2
  29. package/dist/{index-617b2147.js → index-4d538f05.js} +6 -6
  30. package/dist/{index-577d2709.js → index-4ecd4250.js} +4 -4
  31. package/dist/{index-95777860.js → index-50a00b25.js} +7 -7
  32. package/dist/{index-582e4199.js → index-566c48ac.js} +7 -7
  33. package/dist/{index-270ff8a2.js → index-58f616f1.js} +11 -11
  34. package/dist/{index-d7e2a4a3.js → index-5ae39b48.js} +2 -2
  35. package/dist/{index-6d51f24f.js → index-5f3d0c2f.js} +2 -2
  36. package/dist/{index-6a0902fc.js → index-6f8bfdd4.js} +2 -2
  37. package/dist/{index-997bc756.js → index-806b59ae.js} +2 -2
  38. package/dist/{index-96729706.js → index-8327440a.js} +13 -13
  39. package/dist/{index-80954332.js → index-986ea4f3.js} +3 -3
  40. package/dist/{index-2de6c5bf.js → index-9ee4129a.js} +4 -4
  41. package/dist/{index-f886cf92.js → index-a16b0b1a.js} +2 -2
  42. package/dist/{index-29eb95bd.js → index-a84fb2d4.js} +3 -3
  43. package/dist/{index-a41841e6.js → index-a8cfb216.js} +3 -3
  44. package/dist/{index-19f302c4.js → index-acb4d6e1.js} +3 -3
  45. package/dist/{index-f4cd8dd1.js → index-bc8dc6e6.js} +3 -3
  46. package/dist/{index-22727038.js → index-be60697a.js} +3 -3
  47. package/dist/{index-e7fbb597.js → index-cf5dc9e0.js} +4 -4
  48. package/dist/{index-faade258.js → index-d29e5bfc.js} +3 -3
  49. package/dist/{index-52edc95a.js → index-d333ef20.js} +2 -2
  50. package/dist/{index-42a8a5ec.js → index-d5d48d61.js} +15 -15
  51. package/dist/{index-adaa2ea4.js → index-f706ea18.js} +3 -3
  52. package/dist/{localBatchGatewayRequest-ade4aa2f.js → localBatchGatewayRequest-bd902016.js} +3 -3
  53. package/dist/{main-3b573cfc.js → main-cce20875.js} +42707 -42613
  54. package/dist/{networks-446d3512.js → networks-fcbf0c74.js} +10 -10
  55. package/dist/{onramp-57d19c5d.js → onramp-eb02e46e.js} +19 -19
  56. package/dist/{parseSignature-783d626a.js → parseSignature-0f866446.js} +8 -8
  57. package/dist/{pay-with-exchange-c59a5773.js → pay-with-exchange-5073a468.js} +22 -22
  58. package/dist/ping-widget.js +1 -1
  59. package/dist/ping-widget.umd.cjs +727 -727
  60. package/dist/{query.lcd-447fb91a.js → query.lcd-cefcdaf4.js} +1 -1
  61. package/dist/{query.rpc.Query-97503d87.js → query.rpc.Query-f2981fc7.js} +1 -1
  62. package/dist/{receive-3073a49d.js → receive-11a25c5f.js} +6 -6
  63. package/dist/{ref-6c3aabc7.js → ref-7866a390.js} +2 -2
  64. package/dist/{send-42403797.js → send-a8333bb0.js} +22 -22
  65. package/dist/{serializeAuthorizationList-70ec977c.js → serializeAuthorizationList-b5a1a7f6.js} +4 -4
  66. package/dist/{sha2-249ae7eb.js → sha2-fb4e167b.js} +2 -2
  67. package/dist/{socials-69b0cf18.js → socials-3184225d.js} +18 -18
  68. package/dist/{swaps-4e99bb0f.js → swaps-196b9a1a.js} +18 -18
  69. package/dist/{transactions-1d0c2669.js → transactions-cb14f528.js} +9 -9
  70. package/dist/{tx.rpc.msg-a26b3557.js → tx.rpc.msg-c784af37.js} +1 -1
  71. package/dist/{tx.rpc.msg-8aa727ea.js → tx.rpc.msg-ca20b10a.js} +1 -1
  72. package/dist/{verifyHash-c8962067.js → verifyHash-fa9728b0.js} +5 -5
  73. package/dist/{vue-9dc93a14.js → vue-634e8253.js} +10 -10
  74. package/dist/{w3m-modal-291afec4.js → w3m-modal-7b0a1b01.js} +25 -25
  75. package/dist/{zora-061565b0.js → zora-e0295798.js} +4 -4
  76. package/lib/components/ConnectWallet/index.vue +38 -18
  77. package/lib/components/TokenConvert/index.vue +0 -2
  78. package/lib/components/TxDialog/index.vue +40 -20
  79. package/lib/components/TxDialog/messages/Delegate.vue +8 -2
  80. package/lib/components/TxDialog/messages/Deposit.vue +9 -3
  81. package/lib/components/TxDialog/messages/Redelegate.vue +10 -3
  82. package/lib/components/TxDialog/messages/Send.vue +7 -2
  83. package/lib/components/TxDialog/messages/Transfer.vue +8 -2
  84. package/lib/components/TxDialog/messages/Unbond.vue +9 -3
  85. package/lib/components/TxDialog/messages/Vote.vue +9 -3
  86. package/lib/components/TxDialog/messages/Withdraw.vue +7 -2
  87. package/lib/components/TxDialog/messages/WithdrawCommission.vue +10 -3
  88. package/lib/components/TxDialog/txParams.ts +30 -0
  89. package/lib/components/TxDialog/wasm/ClearAdmin.vue +10 -3
  90. package/lib/components/TxDialog/wasm/ExecuteContract.vue +11 -5
  91. package/lib/components/TxDialog/wasm/InstantiateContract.vue +9 -3
  92. package/lib/components/TxDialog/wasm/MigrateContract.vue +10 -3
  93. package/lib/components/TxDialog/wasm/MigrateContract2.vue +10 -3
  94. package/lib/components/TxDialog/wasm/StoreCode.vue +6 -2
  95. package/lib/components/TxDialog/wasm/UpdateAdmin.vue +10 -3
  96. package/lib/wallet/UniClient.ts +0 -1
  97. package/lib/wallet/WalletError.ts +110 -29
  98. package/package.json +1 -1
  99. package/test/components/ConnectWallet.test.ts +68 -9
  100. package/test/components/TxDialogParams.test.ts +23 -0
  101. package/test/components/TxDialogSecurity.test.ts +154 -0
  102. package/test/wallet/WalletError.test.ts +38 -14
  103. package/test/wallet/WalletSecurity.test.ts +19 -14
  104. package/dist/features-82db0d52.js +0 -8
@@ -1,56 +1,137 @@
1
- import { WalletName } from './Wallet';
1
+ import type { WalletName } from './Wallet';
2
2
 
3
3
  export type WalletErrorCategory =
4
4
  | 'rejected'
5
+ | 'wallet-unavailable'
6
+ | 'wrong-network'
7
+ | 'insufficient-funds'
5
8
  | 'unsupported-method'
6
9
  | 'disconnected'
7
- | 'wrong-network'
8
10
  | 'unknown';
9
11
 
12
+ export const WALLET_ERROR_MESSAGES: Record<WalletErrorCategory, string> = {
13
+ rejected: 'Wallet request was rejected',
14
+ 'wallet-unavailable':
15
+ 'Wallet extension is not installed or is unavailable',
16
+ 'wrong-network': 'Wallet is not connected to the required network',
17
+ 'insufficient-funds':
18
+ 'Insufficient balance to cover the amount and network fee',
19
+ 'unsupported-method':
20
+ 'This wallet does not support the required signing method',
21
+ disconnected: 'Wallet is disconnected',
22
+ unknown: 'Wallet request failed',
23
+ };
24
+
25
+ const MAX_ERROR_DEPTH = 5;
26
+ const MAX_ERROR_NODES = 24;
27
+ const NESTED_ERROR_KEYS = ['cause', 'error', 'data', 'details'] as const;
28
+
29
+ interface ProviderSignals {
30
+ codes: number[];
31
+ messages: string[];
32
+ }
33
+
34
+ function numericCode(value: unknown): number | undefined {
35
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
36
+ if (typeof value === 'string' && /^-?\d+$/.test(value.trim())) {
37
+ return Number(value);
38
+ }
39
+ return undefined;
40
+ }
41
+
42
+ function collectProviderSignals(root: unknown): ProviderSignals {
43
+ const signals: ProviderSignals = { codes: [], messages: [] };
44
+ const seen = new Set<object>();
45
+ const queue: Array<{ value: unknown; depth: number }> = [
46
+ { value: root, depth: 0 },
47
+ ];
48
+ let inspected = 0;
49
+
50
+ while (queue.length && inspected < MAX_ERROR_NODES) {
51
+ const item = queue.shift()!;
52
+ if (
53
+ typeof item.value !== 'object' ||
54
+ item.value === null ||
55
+ seen.has(item.value)
56
+ ) {
57
+ continue;
58
+ }
59
+
60
+ seen.add(item.value);
61
+ inspected += 1;
62
+ const record = item.value as Record<string, unknown>;
63
+ const code = numericCode(record.code);
64
+ if (code !== undefined) signals.codes.push(code);
65
+ if (typeof record.message === 'string') {
66
+ signals.messages.push(record.message);
67
+ }
68
+
69
+ if (item.depth >= MAX_ERROR_DEPTH) continue;
70
+ for (const key of NESTED_ERROR_KEYS) {
71
+ queue.push({ value: record[key], depth: item.depth + 1 });
72
+ }
73
+ }
74
+
75
+ if (typeof root === 'string') signals.messages.push(root);
76
+ return signals;
77
+ }
78
+
10
79
  export class WalletUserError extends Error {
11
80
  constructor(
12
81
  public readonly category: WalletErrorCategory,
13
- message: string,
82
+ message: string
14
83
  ) {
15
84
  super(message);
16
85
  this.name = 'WalletUserError';
17
86
  }
18
87
  }
19
88
 
20
- function providerCode(error: unknown): number | undefined {
21
- if (typeof error !== 'object' || error === null || !('code' in error)) {
22
- return undefined;
89
+ function classifyWalletError(signals: ProviderSignals): WalletErrorCategory {
90
+ const text = signals.messages.join(' ');
91
+ const hasCode = (code: number) => signals.codes.includes(code);
92
+
93
+ if (hasCode(4001)) return 'rejected';
94
+ if (
95
+ /please install|not installed|provider is unavailable|wallet connection is unavailable|no wallet connected/i.test(
96
+ text
97
+ )
98
+ ) {
99
+ return 'wallet-unavailable';
23
100
  }
24
- return typeof error.code === 'number' ? error.code : undefined;
101
+ if (
102
+ hasCode(4901) ||
103
+ /no chain info|wrong network|required network|unknown chain/i.test(text)
104
+ ) {
105
+ return 'wrong-network';
106
+ }
107
+ if (
108
+ /insufficient (?:funds|fee)|not enough (?:funds|balance)|insufficient balance/i.test(
109
+ text
110
+ )
111
+ ) {
112
+ return 'insufficient-funds';
113
+ }
114
+ if (
115
+ hasCode(-32601) ||
116
+ /method (?:is )?not supported|unsupported (?:method|signing)/i.test(text)
117
+ ) {
118
+ return 'unsupported-method';
119
+ }
120
+ if (hasCode(4900) || /wallet (?:is )?disconnected/i.test(text)) {
121
+ return 'disconnected';
122
+ }
123
+ return 'unknown';
25
124
  }
26
125
 
27
126
  export function normalizeWalletError(error: unknown): WalletUserError {
28
127
  if (error instanceof WalletUserError) return error;
29
-
30
- switch (providerCode(error)) {
31
- case 4001:
32
- return new WalletUserError('rejected', 'Wallet request was rejected');
33
- case -32601:
34
- return new WalletUserError(
35
- 'unsupported-method',
36
- 'This wallet does not support the required signing method',
37
- );
38
- case 4900:
39
- return new WalletUserError('disconnected', 'Wallet is disconnected');
40
- case 4901:
41
- return new WalletUserError(
42
- 'wrong-network',
43
- 'Wallet is connected to the wrong network',
44
- );
45
- default:
46
- return new WalletUserError('unknown', 'Wallet request failed');
47
- }
128
+ const category = classifyWalletError(collectProviderSignals(error));
129
+ return new WalletUserError(category, WALLET_ERROR_MESSAGES[category]);
48
130
  }
49
131
 
50
132
  export function walletErrorMessage(
51
133
  error: unknown,
52
- wallet?: WalletName,
134
+ _wallet?: WalletName
53
135
  ): string {
54
- if (wallet === WalletName.Evm) return normalizeWalletError(error).message;
55
- return error instanceof Error ? error.message : String(error);
136
+ return normalizeWalletError(error).message;
56
137
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mictonode/widget",
3
3
  "private": false,
4
- "version": "0.3.23",
4
+ "version": "0.3.24",
5
5
  "packageManager": "yarn@1.22.22",
6
6
  "type": "module",
7
7
  "main": "dist/ping-widget.js",
@@ -8,6 +8,10 @@ import {
8
8
  readWallet,
9
9
  removeWallet,
10
10
  } from '../../lib/wallet/Wallet';
11
+ import {
12
+ WALLET_ERROR_MESSAGES,
13
+ WalletUserError,
14
+ } from '../../lib/wallet/WalletError';
11
15
 
12
16
  const sessionMocks = vi.hoisted(() => ({
13
17
  paths: [] as string[],
@@ -251,10 +255,61 @@ describe('ConnectWallet', () => {
251
255
  );
252
256
  });
253
257
 
258
+ it('shows the Keplr suggestion only for a Keplr wrong-network category', async () => {
259
+ keplrSessionMocks.start.mockImplementationOnce(
260
+ async (_argument, _changed, failed) => {
261
+ failed(
262
+ new WalletUserError(
263
+ 'wrong-network',
264
+ WALLET_ERROR_MESSAGES['wrong-network'],
265
+ ),
266
+ );
267
+ },
268
+ );
269
+
270
+ const wrapper = mountWallet();
271
+ await wrapper.get('.ping-connect-confirm').trigger('click');
272
+ await flushPromises();
273
+
274
+ expect(wrapper.text()).toContain(
275
+ WALLET_ERROR_MESSAGES['wrong-network'],
276
+ );
277
+ expect(wrapper.find('a[href*="/wallet/keplr"]').exists()).toBe(true);
278
+ });
279
+
280
+ it('does not show the Keplr suggestion for an EVM wrong-network error', async () => {
281
+ sessionMocks.connect.mockImplementationOnce(
282
+ async (_config, _argument, _changed, failed) => {
283
+ failed(
284
+ new WalletUserError(
285
+ 'wrong-network',
286
+ WALLET_ERROR_MESSAGES['wrong-network'],
287
+ ),
288
+ );
289
+ },
290
+ );
291
+
292
+ const wrapper = mountWallet({
293
+ hdPath: "m/44'/60/0'/0/0",
294
+ evmConfig,
295
+ });
296
+ const option = wrapper
297
+ .findAll('li')
298
+ .find((item) => item.text().includes(WalletName.Evm));
299
+ await option?.trigger('click');
300
+ await wrapper.get('.ping-connect-confirm').trigger('click');
301
+ await flushPromises();
302
+
303
+ expect(wrapper.text()).toContain(
304
+ WALLET_ERROR_MESSAGES['wrong-network'],
305
+ );
306
+ expect(wrapper.find('a[href*="/wallet/keplr"]').exists()).toBe(false);
307
+ });
308
+
254
309
  it('keeps the Keplr dialog open when manual validation fails', async () => {
255
310
  keplrSessionMocks.start.mockImplementationOnce(
256
311
  async (_argument, _onChange, onError) => {
257
- onError({ message: 'Keplr unavailable' });
312
+ onError(new Error('Please install Keplr extension'));
258
313
  },
259
314
  );
260
315
  const wrapper = mountWallet();
@@ -265,11 +320,13 @@ describe('ConnectWallet', () => {
265
320
  expect(
266
321
  (wrapper.get('#PingConnectWallet').element as HTMLInputElement).checked,
267
322
  ).toBe(true);
268
- expect(wrapper.text()).toContain('Keplr unavailable');
323
+ expect(wrapper.text()).toContain(
324
+ WALLET_ERROR_MESSAGES['wallet-unavailable'],
325
+ );
269
326
  });
270
327
 
271
328
  it('does not reopen the Keplr dialog for a later refresh failure', async () => {
272
- let reportRefreshFailure!: (error: { message: string }) => void;
329
+ let reportRefreshFailure!: (error: unknown) => void;
273
330
  keplrSessionMocks.start.mockImplementationOnce(
274
331
  async (_argument, _onChange, onError) => {
275
332
  reportRefreshFailure = onError;
@@ -283,13 +340,15 @@ describe('ConnectWallet', () => {
283
340
  (wrapper.get('#PingConnectWallet').element as HTMLInputElement).checked,
284
341
  ).toBe(false);
285
342
 
286
- reportRefreshFailure({ message: 'Keplr refresh unavailable' });
343
+ reportRefreshFailure(new Error('Please install Keplr extension'));
287
344
  await flushPromises();
288
345
 
289
346
  expect(
290
347
  (wrapper.get('#PingConnectWallet').element as HTMLInputElement).checked,
291
348
  ).toBe(false);
292
- expect(wrapper.text()).toContain('Keplr refresh unavailable');
349
+ expect(wrapper.text()).toContain(
350
+ WALLET_ERROR_MESSAGES['wallet-unavailable'],
351
+ );
293
352
  });
294
353
 
295
354
  it('isolates a replacement wallet from stale Keplr callbacks', async () => {
@@ -561,7 +620,7 @@ describe('ConnectWallet', () => {
561
620
  $: {
562
621
  setupState: {
563
622
  connected: ConnectedWallet;
564
- error: string;
623
+ error: WalletUserError | undefined;
565
624
  };
566
625
  };
567
626
  }).$.setupState;
@@ -572,7 +631,7 @@ describe('ConnectWallet', () => {
572
631
  await flushPromises();
573
632
 
574
633
  expect(onDisconnect).toHaveBeenCalledTimes(disconnectCount);
575
- expect(setupState.error).toBe('');
634
+ expect(setupState.error).toBeUndefined();
576
635
  expect(setupState.connected.cosmosAddress).toBe('warden1connected');
577
636
  });
578
637
 
@@ -747,7 +806,7 @@ describe('ConnectWallet', () => {
747
806
  $: {
748
807
  setupState: {
749
808
  connected: ConnectedWallet;
750
- error: string;
809
+ error: WalletUserError | undefined;
751
810
  };
752
811
  };
753
812
  }).$.setupState;
@@ -758,7 +817,7 @@ describe('ConnectWallet', () => {
758
817
  await flushPromises();
759
818
 
760
819
  expect(onDisconnect).toHaveBeenCalledTimes(disconnectCount);
761
- expect(setupState.error).toBe('');
820
+ expect(setupState.error).toBeUndefined();
762
821
  expect(setupState.connected.cosmosAddress).toBe('warden1connected');
763
822
  });
764
823
 
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ parseTxParams,
4
+ type TxParamsResult,
5
+ } from '../../lib/components/TxDialog/txParams';
6
+
7
+ describe('parseTxParams', () => {
8
+ it.each<[string | undefined, TxParamsResult]>([
9
+ [undefined, { value: {} }],
10
+ ['', { value: {} }],
11
+ [' ', { value: {} }],
12
+ ['{"fees":[{"denom":"uatom","amount":"10"}]}', {
13
+ value: { fees: [{ denom: 'uatom', amount: '10' }] },
14
+ }],
15
+ ['{', { value: {}, issue: 'invalid-json' }],
16
+ ['null', { value: {}, issue: 'invalid-shape' }],
17
+ ['[]', { value: {}, issue: 'invalid-shape' }],
18
+ ['"memo"', { value: {}, issue: 'invalid-shape' }],
19
+ ['1', { value: {}, issue: 'invalid-shape' }],
20
+ ])('normalizes %p', (raw, expected) => {
21
+ expect(parseTxParams(raw)).toEqual(expected);
22
+ });
23
+ });
@@ -0,0 +1,154 @@
1
+ import { flushPromises, mount } from '@vue/test-utils';
2
+ import { defineComponent } from 'vue';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import TxDialog from '../../lib/components/TxDialog/index.vue';
5
+ import {
6
+ getAccount,
7
+ getBalance,
8
+ getLatestBlock,
9
+ getStakingParam,
10
+ } from '../../lib/utils/http';
11
+ import { createSessionClient } from '../../lib/wallet/SessionClient';
12
+
13
+ const sessionClient = vi.hoisted(() => ({
14
+ simulate: vi.fn(),
15
+ sign: vi.fn(),
16
+ broadcastTx: vi.fn(),
17
+ }));
18
+
19
+ vi.mock('../../lib/utils/http', () => ({
20
+ getAccount: vi.fn(),
21
+ getBalance: vi.fn(),
22
+ getBalanceMetadata: vi.fn(),
23
+ getIBCDenomMetadata: vi.fn(),
24
+ getLatestBlock: vi.fn(),
25
+ getStakingParam: vi.fn(),
26
+ getTxByHash: vi.fn(),
27
+ }));
28
+
29
+ vi.mock('../../lib/wallet/SessionClient', () => ({
30
+ createSessionClient: vi.fn(() => sessionClient),
31
+ }));
32
+
33
+ vi.mock('../../lib/wallet/Wallet', () => ({
34
+ isEvmChain: vi.fn(() => false),
35
+ readWallet: vi.fn(() => ({ wallet: 'Keplr' })),
36
+ }));
37
+
38
+ vi.mock('@ping-pub/chain-registry-client', () => ({
39
+ default: class ChainRegistryClient {},
40
+ }));
41
+
42
+ const SendStub = defineComponent({
43
+ name: 'Send',
44
+ props: { params: { type: Object, default: () => ({}) } },
45
+ setup(_props, { expose }) {
46
+ expose({
47
+ initial: vi.fn(),
48
+ isValid: { ok: true, error: '' },
49
+ msgs: [{ typeUrl: '/cosmos.bank.v1beta1.MsgSend', value: {} }],
50
+ });
51
+ },
52
+ template: '<div data-test="send-params">{{ params }}</div>',
53
+ });
54
+
55
+ describe('TxDialog parameter boundary', () => {
56
+ beforeEach(() => vi.clearAllMocks());
57
+
58
+ it.each(['{', 'null', '[]', '"memo"'])(
59
+ 'renders but blocks transaction work for invalid params %p',
60
+ async (params) => {
61
+ const wrapper = mount(TxDialog, {
62
+ props: {
63
+ endpoint: 'https://rest.example',
64
+ params,
65
+ sender: 'cosmos1sender',
66
+ type: 'send',
67
+ },
68
+ global: {
69
+ stubs: { Send: SendStub },
70
+ },
71
+ });
72
+
73
+ await wrapper.get('input.modal-toggle').setValue(true);
74
+ await flushPromises();
75
+ await wrapper.get('button.btn-primary').trigger('click');
76
+
77
+ expect(wrapper.text()).toContain('Transaction parameters are invalid');
78
+ expect(getAccount).not.toHaveBeenCalled();
79
+ expect(getBalance).not.toHaveBeenCalled();
80
+ expect(createSessionClient).not.toHaveBeenCalled();
81
+ expect(sessionClient.simulate).not.toHaveBeenCalled();
82
+ expect(sessionClient.sign).not.toHaveBeenCalled();
83
+ expect(sessionClient.broadcastTx).not.toHaveBeenCalled();
84
+ },
85
+ );
86
+
87
+ it('normalizes nested provider rejection details during signing', async () => {
88
+ vi.mocked(getBalance).mockResolvedValueOnce({
89
+ balances: [{ amount: '1000000', denom: 'factory/chain/token' }],
90
+ });
91
+ vi.mocked(getAccount).mockResolvedValueOnce({
92
+ account: { account_number: '1', sequence: '1' },
93
+ });
94
+ vi.mocked(getLatestBlock).mockResolvedValueOnce({
95
+ block: { header: { chain_id: 'cosmoshub-4' } },
96
+ });
97
+ vi.mocked(getStakingParam).mockResolvedValueOnce({
98
+ params: { bond_denom: 'factory/chain/token' },
99
+ });
100
+ sessionClient.simulate.mockResolvedValueOnce(200000);
101
+ sessionClient.sign.mockRejectedValueOnce({
102
+ cause: {
103
+ code: '4001',
104
+ message: 'provider internal trace',
105
+ },
106
+ });
107
+
108
+ const wrapper = mount(TxDialog, {
109
+ props: {
110
+ endpoint: 'https://rest.example',
111
+ params: '{}',
112
+ sender: 'cosmos1sender',
113
+ type: 'send',
114
+ },
115
+ global: { stubs: { Send: SendStub } },
116
+ });
117
+
118
+ await wrapper.get('input.modal-toggle').setValue(true);
119
+ await flushPromises();
120
+ await wrapper.get('button.btn-primary').trigger('click');
121
+ await flushPromises();
122
+
123
+ expect(wrapper.text()).toContain('Wallet request was rejected');
124
+ expect(wrapper.text()).not.toContain('provider internal trace');
125
+ });
126
+
127
+ it('preserves local fee-denom validation without creating a wallet client', async () => {
128
+ vi.mocked(getBalance).mockResolvedValueOnce({ balances: [] });
129
+ vi.mocked(getLatestBlock).mockResolvedValueOnce({
130
+ block: { header: { chain_id: 'cosmoshub-4' } },
131
+ });
132
+ vi.mocked(getStakingParam).mockResolvedValueOnce({
133
+ params: { bond_denom: '' },
134
+ });
135
+
136
+ const wrapper = mount(TxDialog, {
137
+ props: {
138
+ endpoint: 'https://rest.example',
139
+ params: '{}',
140
+ sender: 'cosmos1sender',
141
+ type: 'send',
142
+ },
143
+ global: { stubs: { Send: SendStub } },
144
+ });
145
+
146
+ await wrapper.get('input.modal-toggle').setValue(true);
147
+ await flushPromises();
148
+ await wrapper.get('button.btn-primary').trigger('click');
149
+
150
+ expect(wrapper.text()).toContain('Fee Denom is empty');
151
+ expect(wrapper.text()).not.toContain('Wallet request failed');
152
+ expect(createSessionClient).not.toHaveBeenCalled();
153
+ });
154
+ });
@@ -1,32 +1,56 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { WalletName } from '../../lib/wallet/Wallet';
3
2
  import {
4
3
  normalizeWalletError,
5
- walletErrorMessage,
4
+ WALLET_ERROR_MESSAGES,
5
+ WalletUserError,
6
6
  } from '../../lib/wallet/WalletError';
7
7
 
8
8
  describe('normalizeWalletError', () => {
9
9
  it.each([
10
10
  [{ code: 4001 }, 'rejected'],
11
+ [{ code: '4001' }, 'rejected'],
11
12
  [{ code: -32601 }, 'unsupported-method'],
12
13
  [{ code: 4900 }, 'disconnected'],
13
14
  [{ code: 4901 }, 'wrong-network'],
14
- ])('maps provider error %#', (input, category) => {
15
- expect(normalizeWalletError(input).category).toBe(category);
15
+ [{ message: 'Please install Keplr extension' }, 'wallet-unavailable'],
16
+ [{ message: 'No chain info for cosmoshub-4' }, 'wrong-network'],
17
+ [{ message: 'insufficient funds for fee' }, 'insufficient-funds'],
18
+ [{ message: 'provider is unavailable' }, 'wallet-unavailable'],
19
+ [{ message: 'method is not supported' }, 'unsupported-method'],
20
+ [{ message: 'wallet disconnected' }, 'disconnected'],
21
+ [{ message: 'RPC stack trace: secret' }, 'unknown'],
22
+ ] as const)('maps %o to %s', (error, category) => {
23
+ expect(normalizeWalletError(error)).toEqual(
24
+ new WalletUserError(category, WALLET_ERROR_MESSAGES[category])
25
+ );
16
26
  });
17
27
 
18
- it('does not expose unknown provider payloads', () => {
19
- expect(normalizeWalletError({ secret: 'typed data' }).message).toBe(
20
- 'Wallet request failed',
28
+ it('preserves an existing WalletUserError', () => {
29
+ const error = new WalletUserError(
30
+ 'wrong-network',
31
+ WALLET_ERROR_MESSAGES['wrong-network']
21
32
  );
33
+ expect(normalizeWalletError(error)).toBe(error);
22
34
  });
23
35
 
24
- it('uses safe EVM copy while preserving legacy errors', () => {
25
- expect(walletErrorMessage({ code: -32601 }, WalletName.Evm)).toBe(
26
- 'This wallet does not support the required signing method',
27
- );
28
- expect(
29
- walletErrorMessage(new Error('Insufficient fee'), WalletName.Keplr),
30
- ).toBe('Insufficient fee');
36
+ it('gives a nested rejection code precedence and survives cycles', () => {
37
+ const error: {
38
+ message: string;
39
+ cause?: unknown;
40
+ } = { message: 'insufficient funds' };
41
+ const cause: { code: string; details?: unknown } = { code: '4001' };
42
+ error.cause = cause;
43
+ cause.details = error;
44
+
45
+ expect(normalizeWalletError(error).category).toBe('rejected');
46
+ });
47
+
48
+ it('does not traverse beyond the fixed node budget', () => {
49
+ let error: Record<string, unknown> = { code: 4900 };
50
+ for (let index = 0; index < 30; index += 1) {
51
+ error = { cause: error };
52
+ }
53
+
54
+ expect(normalizeWalletError(error).category).toBe('unknown');
31
55
  });
32
56
  });
@@ -1,30 +1,35 @@
1
- import { readFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
1
+ import { readFileSync, readdirSync } from 'node:fs';
2
+ import { extname, join } from 'node:path';
3
3
  import { Registry } from '@cosmjs/proto-signing';
4
4
  import { beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { createWallet, WalletName } from '../../lib/wallet/Wallet';
6
6
  import { MetamaskWallet } from '../../lib/wallet/wallets/MetamaskWallet';
7
7
  import { MetamaskSnapWallet } from '../../lib/wallet/wallets/MetamaskSnapWallet';
8
8
 
9
- const walletSources = [
10
- 'LeapWallet.ts',
11
- 'MetamaskWallet.ts',
12
- 'OKXWallet.ts',
13
- 'UnisatWallet.ts',
14
- ];
9
+ function sourceFiles(directory: string): string[] {
10
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
11
+ const path = join(directory, entry.name);
12
+ if (entry.isDirectory()) return sourceFiles(path);
13
+ return ['.ts', '.tsx', '.vue', '.js'].includes(extname(entry.name)) ? [path] : [];
14
+ });
15
+ }
15
16
 
16
17
  describe('wallet security hygiene', () => {
17
18
  beforeEach(() => {
18
19
  localStorage.clear();
19
20
  });
20
21
 
21
- it.each(walletSources)('does not log wallet or signing data in %s', (file) => {
22
- const source = readFileSync(
23
- resolve(process.cwd(), 'lib', 'wallet', 'wallets', file),
24
- 'utf8'
25
- );
22
+ it('contains no console.log or console.debug calls in production source', () => {
23
+ const findings = sourceFiles(join(process.cwd(), 'lib')).flatMap((path) => {
24
+ const lines = readFileSync(path, 'utf8').split(/\r?\n/);
25
+ return lines.flatMap((line, index) =>
26
+ /\bconsole\.(?:log|debug)\s*\(/.test(line)
27
+ ? [`${path}:${index + 1}: ${line.trim()}`]
28
+ : []
29
+ );
30
+ });
26
31
 
27
- expect(source).not.toMatch(/\bconsole\.log\s*\(/);
32
+ expect(findings).toEqual([]);
28
33
  });
29
34
 
30
35
  it('falls back to a fresh MetaMask request when its account cache is malformed', async () => {
@@ -1,8 +0,0 @@
1
- import { R as r, a } from "./ReownAuthentication-bba89dc5.js";
2
- import "./ConstantsUtil-f9dc60b6.js";
3
- import "./main-3b573cfc.js";
4
- import "./abis-96fed285.js";
5
- export {
6
- r as ReownAuthentication,
7
- a as ReownAuthenticationMessenger
8
- };