115
116
late final EVMChainClient _client;
117
118
- int gasPrice = 0;
119
- int? gasBaseFee = 0;
120
- int estimatedGasUnits = 0;
118
+ bool hasPriorityFee = true;
119
122
- Timer? _updateFeesTimer;
120
+ @observable
121
+ String? nativeTxEstimatedFee;
122
+
123
+ @observable
124
+ String? erc20TxEstimatedFee;
125
126
bool _isTransactionUpdating;
127
170
EncryptionFileUtils encryptionFileUtils,
171
);
172
173
+ String _getUSDCContractAddress() {
174
+ return switch (_client.chainId) {
175
+ 1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
176
+ 137 => "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
177
+ 8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
178
+ 42161 => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
179
+ _ => throw Exception("Unsupported chain ID: ${_client.chainId}"),
180
+ };
181
+ }
182
+
183
@override
184
Future<bool> checkNodeHealth() async {
185
try {
187
await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
188
189
// Check USDC token balance
178
- String usdcContractAddress;
179
-
180
- switch (_client.chainId) {
181
- case 1:
182
- usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
183
- break;
184
- case 137:
185
- usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
186
- break;
187
- case 8453:
188
- usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
189
- break;
190
- default:
191
- return true;
192
- }
190
+ String usdcContractAddress = _getUSDCContractAddress();
191
194
- await _client.fetchERC20Balances(
195
- _evmChainPrivateKey.address,
196
- usdcContractAddress,
197
- );
192
+ await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress);
193
194
return true;
195
} catch (e) {
274
}
275
276
@override
282
- int calculateEstimatedFee(TransactionPriority priority, int? amount) {
283
- {
284
- try {
285
- if (priority is EVMChainTransactionPriority) {
286
- final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
287
-
288
- int maxFeePerGas;
289
- if (gasBaseFee != null) {
290
- // MaxFeePerGas with EIP1559;
291
- maxFeePerGas = gasBaseFee! + priorityFee;
292
- } else {
293
- // MaxFeePerGas with gasPrice;
294
- maxFeePerGas = gasPrice;
295
- printV('MaxFeePerGas with gasPrice: $maxFeePerGas');
296
- }
277
+ int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
278
+
279
+ @override
280
+ Future<void> updateEstimatedFeesParams(TransactionPriority priority) async =>
281
+ await _getEstimatedFees(priority);
282
+
283
+ Future<void> _getEstimatedFees(TransactionPriority? priority) async {
284
+ final nativeFee = await _getNativeTxFee(priority);
285
+ nativeTxEstimatedFee = nativeFee.toString();
286
298
- final totalGasFee = estimatedGasUnits * maxFeePerGas;
299
- return totalGasFee;
287
+ final erc20Fee = await _getErc20TxFee(priority);
288
+ erc20TxEstimatedFee = erc20Fee.toString();
289
+
290
+ printV('Native Estimated Fee: $nativeTxEstimatedFee');
291
+ printV('ERC20 Estimated Fee: $erc20TxEstimatedFee');
292
+ }
293
+
294
+ Future<int> _getNativeTxFee(TransactionPriority? priority) async {
295
+ try {
296
+ int priorityFee = 0;
297
+ if (hasPriorityFee) {
298
+ if (priority is EVMChainTransactionPriority) {
299
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
300
}
301
+ }
302
302
- return 0;
303
- } catch (e) {
304
- return 0;
303
+ final gasPrice = await _client.getGasUnitPrice();
304
+ final gasBaseFee = await _client.getGasBaseFee();
305
+
306
+ final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
307
+ senderAddress: evmChainPrivateKey.address,
308
+ toAddress: evmChainPrivateKey.address,
309
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
310
+ value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
311
+ );
312
+
313
+ int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
314
+ final totalGasFee = gasUnits * maxFeePerGas;
315
+ return totalGasFee;
316
+ } catch (e) {
317
+ printV(e.toString());
318
+ return 0;
319
+ }
320
+ }
321
+
322
+ Future<int> _getErc20TxFee(TransactionPriority? priority) async {
323
+ try {
324
+ int priorityFee = 0;
325
+ if (hasPriorityFee) {
326
+ if (priority is EVMChainTransactionPriority) {
327
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
328
+ }
329
}
330
+
331
+ final gasPrice = await _client.getGasUnitPrice();
332
+ final gasBaseFee = await _client.getGasBaseFee();
333
+
334
+ final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
335
+ senderAddress: evmChainPrivateKey.address,
336
+ toAddress: evmChainPrivateKey.address,
337
+ contractAddress: _getUSDCContractAddress(), // Using USDC for default estimation
338
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
339
+ value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
340
+ );
341
+
342
+ int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
343
+ final totalGasFee = gasUnits * maxFeePerGas;
344
+ return totalGasFee;
345
+ } catch (e) {
346
+ printV(e.toString());
347
+ return 0;
348
}
349
}
350
358
required amount,
359
required String? contractAddress,
360
required String receivingAddressHex,
319
- required TransactionPriority priority,
361
+ required TransactionPriority? priority,
362
Uint8List? data,
363
}) async {
364
try {
323
- if (priority is EVMChainTransactionPriority) {
324
- final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
365
+ int priorityFee = 0;
366
+ if (hasPriorityFee && priority != null) {
367
+ if (priority is EVMChainTransactionPriority) {
368
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
369
+ }
370
+ }
371
326
- int maxFeePerGas;
327
- int adjustedGasPrice;
372
+ final gasBaseFee = await _client.getGasBaseFee();
373
+ final gasPrice = await _client.getGasUnitPrice();
374
329
- bool isPolygon = _client.chainId == 137;
375
+ int maxFeePerGas;
376
+ int adjustedGasPrice;
377
331
- if (gasBaseFee != null) {
332
- // MaxFeePerGas with EIP1559;
333
- maxFeePerGas = gasBaseFee! + priorityFee;
334
- } else {
335
- // MaxFeePerGas with gasPrice
336
- maxFeePerGas = gasPrice + priorityFee;
337
- }
378
+ bool isPolygon = _client.chainId == 137;
379
339
- adjustedGasPrice = maxFeePerGas;
340
-
341
- // Polygon has a minimum priority fee of 25 gwei
342
- if (isPolygon) {
343
- int minPriorityFee = 25;
344
- int minPriorityFeeWei =
345
- EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
346
-
347
- // Calculate user selected priority-based additional fee on top of minimum
348
- int additionalPriorityFee = 0;
349
- switch (priority) {
350
- case EVMChainTransactionPriority.slow:
351
- // We use minimum priority fee only
352
- additionalPriorityFee = 0;
353
- break;
354
- case EVMChainTransactionPriority.medium:
355
- // We add 15 gwei on top of minimum
356
- additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
357
- break;
358
- case EVMChainTransactionPriority.fast:
359
- // We add 35 gwei on top of minimum
360
- additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
361
- break;
362
- }
380
+ if (gasBaseFee != null) {
381
+ // MaxFeePerGas with EIP1559;
382
+ maxFeePerGas = gasBaseFee + priorityFee;
383
+ } else {
384
+ // MaxFeePerGas with gasPrice
385
+ maxFeePerGas = gasPrice + priorityFee;
386
+ }
387
364
- int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
365
- adjustedGasPrice = gasPrice + totalPriorityFee;
366
- maxFeePerGas = gasPrice + totalPriorityFee;
388
+ adjustedGasPrice = maxFeePerGas;
389
+
390
+ // Polygon has a minimum priority fee of 25 gwei
391
+ if (isPolygon) {
392
+ int minPriorityFee = 25;
393
+ int minPriorityFeeWei =
394
+ EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
395
+
396
+ // Calculate user selected priority-based additional fee on top of minimum
397
+ int additionalPriorityFee = 0;
398
+ switch (priority) {
399
+ case EVMChainTransactionPriority.slow:
400
+ // We use minimum priority fee only
401
+ additionalPriorityFee = 0;
402
+ break;
403
+ case EVMChainTransactionPriority.medium:
404
+ // We add 15 gwei on top of minimum
405
+ additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
406
+ break;
407
+ case EVMChainTransactionPriority.fast:
408
+ // We add 35 gwei on top of minimum
409
+ additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
410
+ break;
411
}
412
369
- final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
370
- contractAddress: contractAddress,
371
- senderAddress: _evmChainPrivateKey.address,
372
- value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
373
- gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
374
- toAddress: EthereumAddress.fromHex(receivingAddressHex),
375
- maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
376
- data: data,
377
- );
413
+ int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
414
+ adjustedGasPrice = gasPrice + totalPriorityFee;
415
+ maxFeePerGas = gasPrice + totalPriorityFee;
416
+ }
417
379
- final totalGasFee = estimatedGas * adjustedGasPrice;
418
+ final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
419
+ contractAddress: contractAddress,
420
+ senderAddress: _evmChainPrivateKey.address,
421
+ value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
422
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
423
+ toAddress: EthereumAddress.fromHex(receivingAddressHex),
424
+ maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
425
+ data: data,
426
+ );
427
381
- return GasParamsHandler(
382
- estimatedGasUnits: estimatedGas,
383
- estimatedGasFee: totalGasFee,
384
- maxFeePerGas: maxFeePerGas,
385
- gasPrice: adjustedGasPrice,
386
- );
387
- }
388
- return GasParamsHandler.zero();
428
+ final totalGasFee = estimatedGas * adjustedGasPrice;
429
+
430
+ return GasParamsHandler(
431
+ estimatedGasUnits: estimatedGas,
432
+ estimatedGasFee: totalGasFee,
433
+ maxFeePerGas: maxFeePerGas,
434
+ gasPrice: adjustedGasPrice,
435
+ );
436
} catch (e) {
437
return GasParamsHandler.zero();
438
}
447
Future<void> close({bool shouldCleanup = false}) async {
448
_client.stop();
449
_transactionsUpdateTimer?.cancel();
403
- _updateFeesTimer?.cancel();
450
}
451
452
@action
486
487
await _updateBalance();
488
await _updateTransactions();
443
- await _updateEstimatedGasFeeParams();
489
+ await _getEstimatedFees(
490
+ hasPriorityFee ? EVMChainTransactionPriority.medium : null,
491
+ ); // We're using medium priority for default estimation
492
445
- _updateFeesTimer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
446
- await _updateEstimatedGasFeeParams();
447
- });
493
syncStatus = SyncedSyncStatus();
494
} catch (e) {
495
syncStatus = FailedSyncStatus();
496
}
497
}
498
454
- Future<void> _updateEstimatedGasFeeParams() async {
455
- gasBaseFee = await _client.getGasBaseFee();
456
-
457
- gasPrice = await _client.getGasUnitPrice();
458
-
459
- estimatedGasUnits = await _client.getEstimatedGasUnitsForTransaction(
460
- senderAddress: _evmChainPrivateKey.address,
461
- toAddress: _evmChainPrivateKey.address,
462
- gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
463
- value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.one),
464
- );
465
- }
466
-
499
@override
500
Future<PendingTransaction> createTransaction(Object credentials) async {
501
final _credentials = credentials as EVMChainTransactionCredentials;
543
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
544
amount: totalAmount,
545
receivingAddressHex: toAddress,
514
- priority: _credentials.priority!,
546
+ priority: _credentials.priority,
547
contractAddress: contractAddress,
548
);
549
571
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
572
amount: totalAmount,
573
receivingAddressHex: toAddress,
542
- priority: _credentials.priority!,
574
+ priority: _credentials.priority,
575
contractAddress: contractAddress,
576
);
577
594
final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
595
amount: totalAmount,
596
receivingAddressHex: toAddress,
565
- priority: _credentials.priority!,
597
+ priority: _credentials.priority,
598
contractAddress: contractAddress,
599
);
600
609
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
610
}
611
580
- // check the fees on the base currency (Eth/Polygon)
612
+ // check the fees on the base currency
613
if (estimatedFeesForTransaction > balance[currency]!.balance) {
614
throw EVMChainTransactionFeesException(currency.title);
615
}
631
toAddress: toAddress,
632
amount: totalAmount,
633
gasFee: estimatedFeesForTransaction,
602
- priority: _credentials.priority!,
634
+ priority: _credentials.priority,
635
currency: transactionCurrency,
604
- feeCurrency: switch (_client.chainId) { 1 => "ETH", 137 => "POL", 8453 => "ETH", _ => "ETH" },
636
+ feeCurrency: switch (_client.chainId) { 137 => "POL", _ => "ETH" },
637
maxFeePerGas: maxFeePerGasForTransaction,
638
exponent: exponent,
639
contractAddress:
646
}
647
648
Future<PendingTransaction> createCallDataTransaction(
617
- String to,
618
- String dataHex,
619
- BigInt valueWei,
620
- EVMChainTransactionPriority priority,
621
- ) async {
622
-
649
+ String to,
650
+ String dataHex,
651
+ BigInt valueWei,
652
+ EVMChainTransactionPriority? priority,
653
+ ) async {
654
// Estimate gas with the SAME call (sender, to, value, data)
655
final gas = await calculateActualEstimatedFeeForCreateTransaction(
656
amount: valueWei, // native value (usually 0 for ERC20 transfer)
663
final nativeCurrency = switch (_client.chainId) {
664
137 => CryptoCurrency.maticpoly,
665
8453 => CryptoCurrency.baseEth,
635
- _ => CryptoCurrency.eth,
666
+ 42161 => CryptoCurrency.arbEth,
667
+ _ => CryptoCurrency.eth,
668
};
669
670
// Fallback for nodes that fail estimate (non-zero)
688
);
689
}
690
659
-
660
- Future<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
661
- CryptoCurrency token, EVMChainTransactionPriority priority, String feeCurrency) async {
691
+ Future<PendingTransaction> createApprovalTransaction(
692
+ BigInt amount,
693
+ String spender,
694
+ CryptoCurrency token,
695
+ EVMChainTransactionPriority? priority,
696
+ String feeCurrency,
697
+ ) async {
698
final CryptoCurrency transactionCurrency =
699
balance.keys.firstWhere((element) => element.title == token.title);
700
assert(transactionCurrency is Erc20Token);
876
}
877
878
Future<bool> isApprovalRequired(
843
- String tokenContract,
844
- String spender,
845
- BigInt requiredAmount) async {
846
-
879
+ String tokenContract, String spender, BigInt requiredAmount) async {
880
const zero = '0x0000000000000000000000000000000000000000';
881
const evmNative = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
882
892
chainId: _client.chainId,
893
);
894
862
- final allowance = await erc20.allowance(
863
- owner,
864
- EthereumAddress.fromHex(spender));
895
+ final allowance = await erc20.allowance(owner, EthereumAddress.fromHex(spender));
896
897
return allowance < requiredAmount;
898
} catch (e) {
929
@override
930
Future<void> updateTransactionsHistory() async => await _updateTransactions();
931
901
-
932
List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
933
934
Future<void> addErc20Token(Erc20Token token) async {
1028
/// PolygonScan for Polygon.
1029
///
1030
/// BaseScan for Base.
1031
+ ///
1032
+ /// ArbiScan for Arbitrum.
1033
void updateScanProviderUsageState(bool isEnabled) {
1034
if (isEnabled) {
1035
_updateTransactions();