-
Notifications
You must be signed in to change notification settings - Fork 415
/
prettify.ts
141 lines (124 loc) · 5.37 KB
/
prettify.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { AppCurrency } from "@keplr-wallet/types";
import { CoinPretty, Int } from "@keplr-wallet/unit";
import { MultiLanguageT } from "~/hooks";
const regexLegacySignatureVerificationFailed =
/^signature verification failed; please verify account number \(\d*\), sequence \((\d*)\) and chain-id \(.*\): unauthorized/;
const regexSignatureVerificationFailed =
/^account sequence mismatch, expected (\d*), got (\d*): incorrect account sequence/;
const regexFailedToExecuteMessageAt =
/^failed to execute message; message index: (\d+): (.+)/;
const regexCoinsOrDenoms = /(\d*)([a-zA-Z][a-zA-Z0-9/]{2,127})(,*)/g;
const regexSplitAmountAndDenomOfCoin = /(\d+)([a-zA-Z][a-zA-Z0-9/]{2,127})/;
const regexInvalidClPositionAmounts =
/failed to execute message; message index: (\d+): slippage bound: insufficient amount of token (\d+) created. Actual: \((\d+)\). Minimum estimated: \((\d+)\)/;
const regexFailedSwapSlippage =
/failed to execute message; message index: \d+: (.*?) token is lesser than min amount: calculated amount is lesser than min amount: invalid request/;
/** Uses regex matching to map less readable chain errors to a less technical user-friendly string.
* @param message Error message from chain.
* @param currencies Currencies used to map to human-readable coin denoms (e.g. ATOM)
* @returns Human readable error message if possible. */
export function prettifyTxError(
message: string,
currencies: AppCurrency[]
): Parameters<MultiLanguageT> | string | undefined {
try {
const matchLegacySignatureVerificationFailed = message.match(
regexLegacySignatureVerificationFailed
);
if (matchLegacySignatureVerificationFailed) {
if (matchLegacySignatureVerificationFailed.length >= 2) {
const sequence = matchLegacySignatureVerificationFailed[1];
if (!Number.isNaN(parseInt(sequence))) {
return ["errors.sequenceNumber", { sequence }];
}
}
}
const matchSignatureVerificationFailed = message.match(
regexSignatureVerificationFailed
);
if (matchSignatureVerificationFailed) {
if (matchSignatureVerificationFailed.length >= 3) {
const sequence = matchSignatureVerificationFailed[2];
if (!Number.isNaN(parseInt(sequence))) {
return ["errors.sequenceNumber", { sequence }];
}
}
}
// Failed swap due to slippage
const matchFailedSwapSlippage = message.match(regexFailedSwapSlippage);
if (matchFailedSwapSlippage) {
if (matchFailedSwapSlippage.length >= 2) {
const denom = matchFailedSwapSlippage[1];
const coinDenom = currencies.find(
(cur) => cur.coinMinimalDenom === denom
)?.coinDenom;
return ["errors.swapSlippage", { coinDenom: coinDenom ?? denom }];
}
}
// Invalid CL position amounts
const matchInvalidClPositionAmounts = message.match(
regexInvalidClPositionAmounts
);
if (matchInvalidClPositionAmounts) {
return ["errors.invalidAmounts"];
}
// It is not important to let the usual users to know that in which order the transaction failed.
const matchFailedToExecuteMessageAt = message.match(
regexFailedToExecuteMessageAt
);
if (matchFailedToExecuteMessageAt) {
if (matchFailedToExecuteMessageAt.length >= 3) {
const failedAt = matchFailedToExecuteMessageAt[1];
const actualMessage = matchFailedToExecuteMessageAt[2];
if (!Number.isNaN(parseInt(failedAt))) {
message = `${actualMessage.trim()} (at msg ${failedAt})`;
}
}
}
const currencyMap: Record<string, AppCurrency> = {};
for (const currency of currencies) {
currencyMap[currency.coinMinimalDenom] = currency;
}
// Remove the stack trace to avoid excessively long error messages.
const stackIndex = message.indexOf("stack:");
if (stackIndex !== -1) {
message = message.slice(0, stackIndex).trim();
}
const split = message.split(" ");
for (let i = 0; i < split.length; i++) {
const frag = split[i];
const matchCoinsOrDenoms = frag.match(regexCoinsOrDenoms);
if (matchCoinsOrDenoms) {
// Actually, many cases can pass the regexCoinsOrDenoms because that regex tests onlt that the string has only alphabet or number or "/", ",".
const mayCoinOrDenom = frag.split(",");
for (let i = 0; i < mayCoinOrDenom.length; i++) {
const value = mayCoinOrDenom[i];
const valueHasLastColon =
value.length > 0 && value.indexOf(":") === value.length - 1;
const splitAmountAndDenom = value.match(
regexSplitAmountAndDenomOfCoin
);
if (splitAmountAndDenom && splitAmountAndDenom.length === 3) {
const amount = splitAmountAndDenom[1];
const denom = splitAmountAndDenom[2];
const currency = currencyMap[denom];
if (currency) {
const coinPretty = new CoinPretty(currency, new Int(amount))
.separator("")
.trim(true);
mayCoinOrDenom[i] =
coinPretty.toString() + (valueHasLastColon ? ":" : "");
}
} else if (currencyMap[value]) {
mayCoinOrDenom[i] =
currencyMap[value].coinDenom + (valueHasLastColon ? ":" : "");
}
}
split[i] = mayCoinOrDenom.join(",");
}
}
return split.join(" ");
} catch (e) {
console.error(e);
}
}