@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.
- package/dist/{ConstantsUtil-f9dc60b6.js → ConstantsUtil-69f4aadb.js} +3 -3
- package/dist/{ConstantsUtil-fb0ff2e5.js → ConstantsUtil-ffeefb33.js} +1 -1
- package/dist/{ExchangeController-173fd1dc.js → ExchangeController-920db609.js} +2 -2
- package/dist/{HelpersUtil-4891ff5f.js → HelpersUtil-e60d77c2.js} +4 -4
- package/dist/{ReownAuthentication-bba89dc5.js → ReownAuthentication-a05128a9.js} +2 -2
- package/dist/{SwapController-e8b097ca.js → SwapController-3307b7d5.js} +3 -3
- package/dist/{abis-96fed285.js → abis-5e7391e2.js} +1 -1
- package/dist/{assertCurrentChain-b60aa59f.js → assertCurrentChain-42cdea0e.js} +1 -1
- package/dist/{ccip-8f82ae2a.js → ccip-b3df1d28.js} +6 -6
- package/dist/{data-capture-7009ffa0.js → data-capture-6b7e5c19.js} +8 -8
- package/dist/{email-d6e2da58.js → email-5c6fce3f.js} +12 -12
- package/dist/{embedded-wallet-48284179.js → embedded-wallet-87192e5a.js} +14 -14
- package/dist/{encodeFunctionData-b24a0b2b.js → encodeFunctionData-929ae2a4.js} +2 -2
- package/dist/{fallback-ae0315d4.js → fallback-366fc17d.js} +2 -2
- package/dist/features-cf9f5cda.js +8 -0
- package/dist/{hashTypedData-c13ba550.js → hashTypedData-f86861cf.js} +2 -2
- package/dist/{http-90ef23f3.js → http-b4045adf.js} +2 -2
- package/dist/{index-3de7fa96.js → index-0313c561.js} +3 -3
- package/dist/{index-4bd03aed.js → index-0d05a79e.js} +7 -7
- package/dist/{index-b8e2c282.js → index-1450179f.js} +3 -3
- package/dist/{index-bb5e4071.js → index-20c073be.js} +1 -1
- package/dist/{index-b37b670f.js → index-23d992c9.js} +11 -11
- package/dist/{index-ce6854ef.js → index-2b5ce2e7.js} +14 -14
- package/dist/{index-cb69565e.js → index-2b935b7c.js} +2 -2
- package/dist/{index-bf40b971.js → index-311166f4.js} +2 -2
- package/dist/{index-cbb6096a.js → index-3d20f99e.js} +3 -3
- package/dist/{index-0ac134af.js → index-4552387b.js} +34 -34
- package/dist/{index-2a90711e.js → index-48fbe63e.js} +2 -2
- package/dist/{index-617b2147.js → index-4d538f05.js} +6 -6
- package/dist/{index-577d2709.js → index-4ecd4250.js} +4 -4
- package/dist/{index-95777860.js → index-50a00b25.js} +7 -7
- package/dist/{index-582e4199.js → index-566c48ac.js} +7 -7
- package/dist/{index-270ff8a2.js → index-58f616f1.js} +11 -11
- package/dist/{index-d7e2a4a3.js → index-5ae39b48.js} +2 -2
- package/dist/{index-6d51f24f.js → index-5f3d0c2f.js} +2 -2
- package/dist/{index-6a0902fc.js → index-6f8bfdd4.js} +2 -2
- package/dist/{index-997bc756.js → index-806b59ae.js} +2 -2
- package/dist/{index-96729706.js → index-8327440a.js} +13 -13
- package/dist/{index-80954332.js → index-986ea4f3.js} +3 -3
- package/dist/{index-2de6c5bf.js → index-9ee4129a.js} +4 -4
- package/dist/{index-f886cf92.js → index-a16b0b1a.js} +2 -2
- package/dist/{index-29eb95bd.js → index-a84fb2d4.js} +3 -3
- package/dist/{index-a41841e6.js → index-a8cfb216.js} +3 -3
- package/dist/{index-19f302c4.js → index-acb4d6e1.js} +3 -3
- package/dist/{index-f4cd8dd1.js → index-bc8dc6e6.js} +3 -3
- package/dist/{index-22727038.js → index-be60697a.js} +3 -3
- package/dist/{index-e7fbb597.js → index-cf5dc9e0.js} +4 -4
- package/dist/{index-faade258.js → index-d29e5bfc.js} +3 -3
- package/dist/{index-52edc95a.js → index-d333ef20.js} +2 -2
- package/dist/{index-42a8a5ec.js → index-d5d48d61.js} +15 -15
- package/dist/{index-adaa2ea4.js → index-f706ea18.js} +3 -3
- package/dist/{localBatchGatewayRequest-ade4aa2f.js → localBatchGatewayRequest-bd902016.js} +3 -3
- package/dist/{main-3b573cfc.js → main-cce20875.js} +42707 -42613
- package/dist/{networks-446d3512.js → networks-fcbf0c74.js} +10 -10
- package/dist/{onramp-57d19c5d.js → onramp-eb02e46e.js} +19 -19
- package/dist/{parseSignature-783d626a.js → parseSignature-0f866446.js} +8 -8
- package/dist/{pay-with-exchange-c59a5773.js → pay-with-exchange-5073a468.js} +22 -22
- package/dist/ping-widget.js +1 -1
- package/dist/ping-widget.umd.cjs +727 -727
- package/dist/{query.lcd-447fb91a.js → query.lcd-cefcdaf4.js} +1 -1
- package/dist/{query.rpc.Query-97503d87.js → query.rpc.Query-f2981fc7.js} +1 -1
- package/dist/{receive-3073a49d.js → receive-11a25c5f.js} +6 -6
- package/dist/{ref-6c3aabc7.js → ref-7866a390.js} +2 -2
- package/dist/{send-42403797.js → send-a8333bb0.js} +22 -22
- package/dist/{serializeAuthorizationList-70ec977c.js → serializeAuthorizationList-b5a1a7f6.js} +4 -4
- package/dist/{sha2-249ae7eb.js → sha2-fb4e167b.js} +2 -2
- package/dist/{socials-69b0cf18.js → socials-3184225d.js} +18 -18
- package/dist/{swaps-4e99bb0f.js → swaps-196b9a1a.js} +18 -18
- package/dist/{transactions-1d0c2669.js → transactions-cb14f528.js} +9 -9
- package/dist/{tx.rpc.msg-a26b3557.js → tx.rpc.msg-c784af37.js} +1 -1
- package/dist/{tx.rpc.msg-8aa727ea.js → tx.rpc.msg-ca20b10a.js} +1 -1
- package/dist/{verifyHash-c8962067.js → verifyHash-fa9728b0.js} +5 -5
- package/dist/{vue-9dc93a14.js → vue-634e8253.js} +10 -10
- package/dist/{w3m-modal-291afec4.js → w3m-modal-7b0a1b01.js} +25 -25
- package/dist/{zora-061565b0.js → zora-e0295798.js} +4 -4
- package/lib/components/ConnectWallet/index.vue +38 -18
- package/lib/components/TokenConvert/index.vue +0 -2
- package/lib/components/TxDialog/index.vue +40 -20
- package/lib/components/TxDialog/messages/Delegate.vue +8 -2
- package/lib/components/TxDialog/messages/Deposit.vue +9 -3
- package/lib/components/TxDialog/messages/Redelegate.vue +10 -3
- package/lib/components/TxDialog/messages/Send.vue +7 -2
- package/lib/components/TxDialog/messages/Transfer.vue +8 -2
- package/lib/components/TxDialog/messages/Unbond.vue +9 -3
- package/lib/components/TxDialog/messages/Vote.vue +9 -3
- package/lib/components/TxDialog/messages/Withdraw.vue +7 -2
- package/lib/components/TxDialog/messages/WithdrawCommission.vue +10 -3
- package/lib/components/TxDialog/txParams.ts +30 -0
- package/lib/components/TxDialog/wasm/ClearAdmin.vue +10 -3
- package/lib/components/TxDialog/wasm/ExecuteContract.vue +11 -5
- package/lib/components/TxDialog/wasm/InstantiateContract.vue +9 -3
- package/lib/components/TxDialog/wasm/MigrateContract.vue +10 -3
- package/lib/components/TxDialog/wasm/MigrateContract2.vue +10 -3
- package/lib/components/TxDialog/wasm/StoreCode.vue +6 -2
- package/lib/components/TxDialog/wasm/UpdateAdmin.vue +10 -3
- package/lib/wallet/UniClient.ts +0 -1
- package/lib/wallet/WalletError.ts +110 -29
- package/package.json +1 -1
- package/test/components/ConnectWallet.test.ts +68 -9
- package/test/components/TxDialogParams.test.ts +23 -0
- package/test/components/TxDialogSecurity.test.ts +154 -0
- package/test/wallet/WalletError.test.ts +38 -14
- package/test/wallet/WalletSecurity.test.ts +19 -14
- 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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
_wallet?: WalletName
|
|
53
135
|
): string {
|
|
54
|
-
|
|
55
|
-
return error instanceof Error ? error.message : String(error);
|
|
136
|
+
return normalizeWalletError(error).message;
|
|
56
137
|
}
|
package/package.json
CHANGED
|
@@ -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(
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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:
|
|
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).
|
|
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:
|
|
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).
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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('
|
|
19
|
-
|
|
20
|
-
'
|
|
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('
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
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(
|
|
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 () => {
|