mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Enhance EVM Fees Error Handling (#2610)
* fix: Add more patterns to the EVM fees error handler * refactor: Enhance EVM transaction error handling with multiple parsing patterns --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
c457a7fea6
commit
226addcc3d
@@ -32,100 +32,174 @@ class EVMTransactionErrorFeesHandler {
|
||||
String errorMessage,
|
||||
double assetPriceUsd,
|
||||
) {
|
||||
// Pattern: "insufficient funds for gas * price + value: have 4728796358953246 want 4728796842182575"
|
||||
RegExp insufficientFundsRegExp = RegExp(r'have (\d+) want (\d+)');
|
||||
Match? insufficientFundsMatch = insufficientFundsRegExp.firstMatch(errorMessage);
|
||||
// Allows us define multiple patterns to parse the error message
|
||||
// Order matters: more specific patterns first, generic patterns last
|
||||
final patterns = [
|
||||
// Pattern 1: "have X want Y" format
|
||||
ErrorPattern(
|
||||
name: 'have_want',
|
||||
regex: RegExp(r'have\s+(\d+)\s+want\s+(\d+)', caseSensitive: false),
|
||||
extractor: (match) => {
|
||||
'balance': match.group(1)!,
|
||||
'required': match.group(2)!,
|
||||
},
|
||||
calculator: (values) => {
|
||||
'balanceWei': values['balance']!,
|
||||
'txCostWei': values['required']!,
|
||||
'overshotWei':
|
||||
(BigInt.parse(values['required']!) - BigInt.parse(values['balance']!)).toString(),
|
||||
},
|
||||
),
|
||||
|
||||
if (insufficientFundsMatch != null) {
|
||||
try {
|
||||
// Extract the numerical strings from the new format
|
||||
String balanceStr = insufficientFundsMatch.group(1)!;
|
||||
String requiredStr = insufficientFundsMatch.group(2)!;
|
||||
// Pattern 2: "balance X, queued cost Y, tx cost Z, overshot W" format (most specific)
|
||||
ErrorPattern(
|
||||
name: 'balance_queued_txcost_overshot',
|
||||
regex: RegExp(r'balance\s+(\d+),\s*queued\s+cost\s+(\d+),\s*tx\s+cost\s+(\d+),\s*overshot\s+(\d+)', caseSensitive: false),
|
||||
extractor: (match) => {
|
||||
'balance': match.group(1)!,
|
||||
'txCost': match.group(3)!, // Skip queued cost, use tx cost
|
||||
'overshot': match.group(4)!,
|
||||
},
|
||||
calculator: (values) => {
|
||||
'balanceWei': values['balance']!,
|
||||
'txCostWei': values['txCost']!,
|
||||
'overshotWei': values['overshot']!,
|
||||
},
|
||||
),
|
||||
|
||||
// Parse the numerical strings to BigInt
|
||||
BigInt balanceWei = BigInt.parse(balanceStr);
|
||||
BigInt requiredWei = BigInt.parse(requiredStr);
|
||||
// Pattern 3: "balance X, tx cost Y, overshot Z" format
|
||||
ErrorPattern(
|
||||
name: 'balance_txcost_overshot',
|
||||
regex: RegExp(r'balance\s+(\d+),\s*tx\s+cost\s+(\d+),\s*overshot\s+(\d+)', caseSensitive: false),
|
||||
extractor: (match) => {
|
||||
'balance': match.group(1)!,
|
||||
'txCost': match.group(2)!,
|
||||
'overshot': match.group(3)!,
|
||||
},
|
||||
calculator: (values) => {
|
||||
'balanceWei': values['balance']!,
|
||||
'txCostWei': values['txCost']!,
|
||||
'overshotWei': values['overshot']!,
|
||||
},
|
||||
),
|
||||
|
||||
// Calculate overshot (how much more is needed)
|
||||
BigInt overshotWei = requiredWei - balanceWei;
|
||||
// Pattern 4: Individual field matching (legacy fallback)
|
||||
ErrorPattern(
|
||||
name: 'individual_fields',
|
||||
regex: RegExp(r'balance\s+(\d+).*tx\s+cost\s+(\d+).*overshot\s+(\d+)', caseSensitive: false),
|
||||
extractor: (match) => {
|
||||
'balance': match.group(1)!,
|
||||
'txCost': match.group(2)!,
|
||||
'overshot': match.group(3)!,
|
||||
},
|
||||
calculator: (values) => {
|
||||
'balanceWei': values['balance']!,
|
||||
'txCostWei': values['txCost']!,
|
||||
'overshotWei': values['overshot']!,
|
||||
},
|
||||
),
|
||||
|
||||
// The transaction cost is the required amount
|
||||
BigInt txCostWei = requiredWei;
|
||||
// Pattern 5: Generic "insufficient funds for gas * price + value" (least specific - must be last)
|
||||
ErrorPattern(
|
||||
name: 'generic_insufficient_funds',
|
||||
regex: RegExp(r'insufficient\s+funds\s+for\s+gas\s*\*\s*price\s*\+\s*value', caseSensitive: false),
|
||||
extractor: (match) => {
|
||||
'balance': '0', // We don't have specific values, so use 0
|
||||
'txCost': '0',
|
||||
'overshot': '0',
|
||||
},
|
||||
calculator: (values) => {
|
||||
'balanceWei': values['balance']!,
|
||||
'txCostWei': values['txCost']!,
|
||||
'overshotWei': values['overshot']!,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
// Convert wei to ETH (1 ETH = 1e18 wei)
|
||||
double balanceEth = balanceWei.toDouble() / 1e18;
|
||||
double txCostEth = txCostWei.toDouble() / 1e18;
|
||||
double overshotEth = overshotWei.toDouble() / 1e18;
|
||||
for (final pattern in patterns) {
|
||||
final match = pattern.regex.firstMatch(errorMessage);
|
||||
if (match != null) {
|
||||
try {
|
||||
final extractedValues = pattern.extractor(match);
|
||||
final calculatedValues = pattern.calculator(extractedValues);
|
||||
|
||||
// Calculate the USD values
|
||||
double balanceUsd = balanceEth * assetPriceUsd;
|
||||
double txCostUsd = txCostEth * assetPriceUsd;
|
||||
double overshotUsd = overshotEth * assetPriceUsd;
|
||||
|
||||
return EVMTransactionErrorFeesHandler(
|
||||
balanceWei: balanceWei.toString(),
|
||||
balanceEth: balanceEth.toString().safeSubString(0, 12),
|
||||
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
|
||||
txCostWei: txCostWei.toString(),
|
||||
txCostEth: txCostEth.toString().safeSubString(0, 12),
|
||||
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
|
||||
overshotWei: overshotWei.toString(),
|
||||
overshotEth: overshotEth.toString().safeSubString(0, 12),
|
||||
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Define Regular Expressions to extract the numerical values
|
||||
RegExp balanceRegExp = RegExp(r'balance (\d+)');
|
||||
RegExp txCostRegExp = RegExp(r'tx cost (\d+)');
|
||||
RegExp overshotRegExp = RegExp(r'overshot (\d+)');
|
||||
|
||||
// Match the patterns in the error message
|
||||
Match? balanceMatch = balanceRegExp.firstMatch(errorMessage);
|
||||
Match? txCostMatch = txCostRegExp.firstMatch(errorMessage);
|
||||
Match? overshotMatch = overshotRegExp.firstMatch(errorMessage);
|
||||
|
||||
// Check if all required values are found
|
||||
if (balanceMatch != null && txCostMatch != null && overshotMatch != null) {
|
||||
try {
|
||||
// Extract the numerical strings
|
||||
String balanceStr = balanceMatch.group(1)!;
|
||||
String txCostStr = txCostMatch.group(1)!;
|
||||
String overshotStr = overshotMatch.group(1)!;
|
||||
|
||||
// Parse the numerical strings to BigInt
|
||||
BigInt balanceWei = BigInt.parse(balanceStr);
|
||||
BigInt txCostWei = BigInt.parse(txCostStr);
|
||||
BigInt overshotWei = BigInt.parse(overshotStr);
|
||||
|
||||
// Convert wei to ETH (1 ETH = 1e18 wei)
|
||||
double balanceEth = balanceWei.toDouble() / 1e18;
|
||||
double txCostEth = txCostWei.toDouble() / 1e18;
|
||||
double overshotEth = overshotWei.toDouble() / 1e18;
|
||||
|
||||
// Calculate the USD values
|
||||
double balanceUsd = balanceEth * assetPriceUsd;
|
||||
double txCostUsd = txCostEth * assetPriceUsd;
|
||||
double overshotUsd = overshotEth * assetPriceUsd;
|
||||
|
||||
return EVMTransactionErrorFeesHandler(
|
||||
balanceWei: balanceWei.toString(),
|
||||
balanceEth: balanceEth.toString().safeSubString(0, 12),
|
||||
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
|
||||
txCostWei: txCostWei.toString(),
|
||||
txCostEth: txCostEth.toString().safeSubString(0, 12),
|
||||
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
|
||||
overshotWei: overshotWei.toString(),
|
||||
overshotEth: overshotEth.toString().safeSubString(0, 12),
|
||||
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
|
||||
);
|
||||
} catch (e) {
|
||||
// If parsing fails, continue to error case
|
||||
return _createHandlerFromValues(calculatedValues, assetPriceUsd);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If both parsing attempts fail, return an error message
|
||||
return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.');
|
||||
}
|
||||
|
||||
/// Creates a handler instance from parsed values
|
||||
static EVMTransactionErrorFeesHandler _createHandlerFromValues(
|
||||
Map<String, String> values,
|
||||
double assetPriceUsd,
|
||||
) {
|
||||
final balanceWei = BigInt.parse(values['balanceWei']!);
|
||||
final txCostWei = BigInt.parse(values['txCostWei']!);
|
||||
final overshotWei = BigInt.parse(values['overshotWei']!);
|
||||
|
||||
final isGenericError = balanceWei == BigInt.zero &&
|
||||
txCostWei == BigInt.zero &&
|
||||
overshotWei == BigInt.zero;
|
||||
|
||||
if (isGenericError) {
|
||||
return genericInsufficientFunds();
|
||||
}
|
||||
|
||||
// Convert wei to ETH (1 ETH = 1e18 wei)
|
||||
final balanceEth = balanceWei.toDouble() / 1e18;
|
||||
final txCostEth = txCostWei.toDouble() / 1e18;
|
||||
final overshotEth = overshotWei.toDouble() / 1e18;
|
||||
|
||||
// Calculate USD values
|
||||
final balanceUsd = balanceEth * assetPriceUsd;
|
||||
final txCostUsd = txCostEth * assetPriceUsd;
|
||||
final overshotUsd = overshotEth * assetPriceUsd;
|
||||
|
||||
return EVMTransactionErrorFeesHandler(
|
||||
balanceWei: balanceWei.toString(),
|
||||
balanceEth: balanceEth.toString().safeSubString(0, 12),
|
||||
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
|
||||
txCostWei: txCostWei.toString(),
|
||||
txCostEth: txCostEth.toString().safeSubString(0, 12),
|
||||
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
|
||||
overshotWei: overshotWei.toString(),
|
||||
overshotEth: overshotEth.toString().safeSubString(0, 12),
|
||||
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
|
||||
);
|
||||
}
|
||||
|
||||
static EVMTransactionErrorFeesHandler genericInsufficientFunds() {
|
||||
return EVMTransactionErrorFeesHandler(
|
||||
balanceWei: '0',
|
||||
balanceEth: '0',
|
||||
balanceUsd: '0',
|
||||
txCostWei: '0',
|
||||
txCostEth: '0',
|
||||
txCostUsd: '0',
|
||||
overshotWei: '0',
|
||||
overshotEth: '0',
|
||||
overshotUsd: '0',
|
||||
error: 'generic_insufficient_funds',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error parsing pattern
|
||||
class ErrorPattern {
|
||||
final String name;
|
||||
final RegExp regex;
|
||||
final Map<String, String> Function(Match) extractor;
|
||||
final Map<String, String> Function(Map<String, String>) calculator;
|
||||
|
||||
ErrorPattern({
|
||||
required this.name,
|
||||
required this.regex,
|
||||
required this.extractor,
|
||||
required this.calculator,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1031,10 +1031,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
_fiatConversationStore.prices[currency] ?? 0.0,
|
||||
);
|
||||
|
||||
// Handle generic insufficient funds error (no specific values available)
|
||||
if (parsedErrorMessageResult.error == 'generic_insufficient_funds') {
|
||||
return S.current.insufficient_funds_for_tx;
|
||||
}
|
||||
|
||||
// Handle parsing errors (couldn't parse the error message)
|
||||
if (parsedErrorMessageResult.error != null) {
|
||||
return S.current.insufficient_funds_for_tx;
|
||||
}
|
||||
|
||||
// Handle successfully parsed errors with specific values
|
||||
return '''${S.current.insufficient_funds_for_tx} \n\n'''
|
||||
'''${S.current.balance}: ${parsedErrorMessageResult.balanceEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.balanceUsd} ${fiatFromSettings.name})\n\n'''
|
||||
'''${S.current.transaction_cost}: ${parsedErrorMessageResult.txCostEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.txCostUsd} ${fiatFromSettings.name})\n\n'''
|
||||
|
||||
Reference in New Issue
Block a user