Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions lib/src/crypto/evm/entities/abi/uniswap_v2/uniswap_v2_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1291,19 +1291,17 @@ class UniswapV2Router extends InternalContract {
final function = abi.getFunction("addLiquidity")!;
final result = await buildTransactionForFunction(
sender: sender,
function: function.addValues(values: [
tokenA,
tokenB,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline,
]),
feeInfo: EvmFeeInformation(
gasLimit: 3E5.toInt(),
gasPrice: Amount(value: 1E10.toInt().toBigInt, decimals: 18),
function: function.addValues(
values: [
tokenA,
tokenB,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
to,
deadline,
],
),
);
return result;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/crypto/evm/entities/contract/internal_contract.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ abstract class InternalContract {
sender: sender,
recipient: contractAddress,
seed: seed,
feeInfo: feeInfo ?? EvmFeeInformation.zero,
feeInfo: feeInfo,
data: functionData,
value: value ?? BigInt.zero,
accessList: accessList,
Expand Down Expand Up @@ -69,7 +69,7 @@ abstract class InternalContract {
return await rpc.buildUnsignedTransaction(
sender: sender,
recipient: contractAddress,
feeInfo: feeInfo ?? EvmFeeInformation.zero,
feeInfo: feeInfo,
data: functionData,
value: value ?? BigInt.zero,
);
Expand Down
11 changes: 11 additions & 0 deletions lib/src/crypto/evm/repositories/rpc/evm_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,17 @@ base class EvmRpcClient {
return gasPrice;
}

Future<BigInt> getPriorityFee() async {
// Direct RPC call to get suggested priority fee
final response = await _call<String>(
'eth_maxPriorityFeePerGas',
);

final priorityFee = response.toBigIntOrNull;
if (priorityFee == null) throw Exception('Could not parse priority fee');
return priorityFee;
}

///
/// Estimate Gas Fee
///
Expand Down
130 changes: 84 additions & 46 deletions lib/src/crypto/evm/repositories/rpc/evm_rpc_interface.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:walletkit_dart/src/common/logger.dart';
import 'package:walletkit_dart/src/crypto/evm/entities/block_number.dart';
import 'package:walletkit_dart/src/crypto/evm/repositories/rpc/queued_rpc_interface.dart';
import 'package:walletkit_dart/src/domain/exceptions.dart';
import 'package:walletkit_dart/src/utils/int.dart';
import 'package:walletkit_dart/walletkit_dart.dart';

const type2Multiplier = 1.5;

final class EvmRpcInterface {
final EVMNetworkType type;
final Map<int, int> blockTimestampCache = {};
Expand Down Expand Up @@ -126,6 +129,13 @@ final class EvmRpcInterface {
);
}

///
/// Get Gas Price
///
Future<Amount> getGasPriceAmount() => getGasPrice().then(
(value) => Amount(value: value, decimals: 18),
);

///
/// Get Transaction Count (Nonce)
///
Expand Down Expand Up @@ -201,6 +211,61 @@ final class EvmRpcInterface {
);
}

Future<Amount> getPriorityFee() async {
final priorityFee = await performTask(
(client) => client.getPriorityFee(),
);

return Amount(value: priorityFee, decimals: 9);
}

Future<EvmType2GasPrice> getType2GasPrice() async {
final maxFeePerGas = await getGasPriceAmount();
final maxPriorityFeePerGas = await getPriorityFee();

return EvmType2GasPrice(
maxFeePerGas:
maxFeePerGas.multiplyAndCeil(type2Multiplier) + maxPriorityFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
);
}

Future<(int gasLimit, EvmGasPrice gasPrice)> fetchNetworkFees({
EvmFeeInformation? existing,
required String recipient,
required String sender,
required Uint8List? data,
required BigInt? value,
}) async {
var gasLimit = existing?.gasLimit;
try {
gasLimit ??= await estimateGasLimit(
recipient: recipient,
sender: sender,
data: data,
value: value,
);
} catch (e) {
Logger.logError(e, hint: "Gas estimation failed");

// Only Debug
assert(true, "Gas estimation failed");

gasLimit = 1E6.toInt();
}

final EvmGasPrice gasPrice = switch (existing?.gasPrice) {
EvmLegacyGasPrice feeInfo => feeInfo,
EvmType2GasPrice feeInfo => feeInfo,
null when type.useEIP1559 => await getType2GasPrice(),
null => EvmLegacyGasPrice(
gasPrice: await getGasPriceAmount(),
),
};

return (gasLimit, gasPrice);
}
Comment on lines +233 to +267
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Review gas estimation fallback value

The fallback gas limit of 1E6 in case of estimation failure might be too high for some contract interactions.

Consider:

  1. Using different fallbacks based on transaction type
  2. Adding a safety margin to the last successful estimation
  3. Logging more details about the estimation failure
-      gasLimit = 1E6.toInt();
+      // Use transaction-type specific fallback
+      gasLimit = switch(data?.isNotEmpty) {
+        true => 150000, // Contract interaction
+        false => 21000, // Simple transfer
+      };
+      Logger.logWarning(
+        'Gas estimation failed. Using fallback: $gasLimit\n'
+        'Error: $e\n'
+        'Transaction: recipient=$recipient, data=${data?.length ?? 0} bytes'
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<(int gasLimit, EvmGasPrice gasPrice)> fetchNetworkFees({
EvmFeeInformation? existing,
required String recipient,
required String sender,
required Uint8List? data,
required BigInt? value,
}) async {
var gasLimit = existing?.gasLimit;
try {
gasLimit ??= await estimateGasLimit(
recipient: recipient,
sender: sender,
data: data,
value: value,
);
} catch (e) {
Logger.logError(e, hint: "Gas estimation failed");
// Only Debug
assert(true, "Gas estimation failed");
gasLimit = 1E6.toInt();
}
final EvmGasPrice gasPrice = switch (existing?.gasPrice) {
EvmLegacyGasPrice feeInfo => feeInfo,
EvmType2GasPrice feeInfo => feeInfo,
null when type.useEIP1559 => await getType2GasPrice(),
null => EvmLegacyGasPrice(
gasPrice: await getGasPriceAmount(),
),
};
return (gasLimit, gasPrice);
}
Future<(int gasLimit, EvmGasPrice gasPrice)> fetchNetworkFees({
EvmFeeInformation? existing,
required String recipient,
required String sender,
required Uint8List? data,
required BigInt? value,
}) async {
var gasLimit = existing?.gasLimit;
try {
gasLimit ??= await estimateGasLimit(
recipient: recipient,
sender: sender,
data: data,
value: value,
);
} catch (e) {
Logger.logError(e, hint: "Gas estimation failed");
// Only Debug
assert(true, "Gas estimation failed");
// Use transaction-type specific fallback
gasLimit = switch(data?.isNotEmpty) {
true => 150000, // Contract interaction
false => 21000, // Simple transfer
};
Logger.logWarning(
'Gas estimation failed. Using fallback: $gasLimit\n'
'Error: $e\n'
'Transaction: recipient=$recipient, data=${data?.length ?? 0} bytes'
);
}
final EvmGasPrice gasPrice = switch (existing?.gasPrice) {
EvmLegacyGasPrice feeInfo => feeInfo,
EvmType2GasPrice feeInfo => feeInfo,
null when type.useEIP1559 => await getType2GasPrice(),
null => EvmLegacyGasPrice(
gasPrice: await getGasPriceAmount(),
),
};
return (gasLimit, gasPrice);
}


///
/// Used to create a raw Transactions
/// Fetches the gasPrice and gasLimit from the network
Expand All @@ -210,66 +275,39 @@ final class EvmRpcInterface {
Future<RawEvmTransaction> buildUnsignedTransaction({
required String sender,
required String recipient,
required EvmFeeInformation feeInfo,
required EvmFeeInformation? feeInfo,
required Uint8List? data,
required BigInt? value,
List<AccessListItem>? accessList,
}) async {
final (gasPrice, gasLimit) = switch ((feeInfo.gasPrice, feeInfo.gasLimit)) {
(null, int gasLimit) => (
Amount(value: await getGasPrice(), decimals: 18),
gasLimit
),
(Amount gasPrice, null) => (
gasPrice,
await estimateGasLimit(
recipient: recipient,
sender: sender,
data: data,
value: value,
)
),
_ => await estimateNetworkFees(
recipient: recipient, sender: sender, data: data, value: value),
};
final (gasLimit, gasPrice) = await fetchNetworkFees(
recipient: recipient,
sender: sender,
data: data,
value: value,
existing: feeInfo,
);

final nonce = await performTask(
(client) => client.getTransactionCount(sender),
);

if (type is ZENIQ_SMART_NETWORK) {
return RawEVMTransactionType0.unsigned(
nonce: nonce,
gasPrice: gasPrice.value,
gasLimit: gasLimit.toBI,
to: recipient,
value: value ?? BigInt.zero,
data: data ?? Uint8List(0),
);
}

if (feeInfo is EvmType2FeeInformation &&
gasPrice.value < feeInfo.maxPriorityFeePerGas!.value) {
throw Failure("Gas price is less than max priority fee");
}

return switch (feeInfo) {
EvmType2FeeInformation() => RawEVMTransactionType2.unsigned(
return switch (gasPrice) {
EvmType2GasPrice fee => RawEVMTransactionType2.unsigned(
nonce: nonce,
maxFeePerGas: gasPrice.value,
maxPriorityFeePerGas:
feeInfo.maxPriorityFeePerGas?.value ?? Amount.zero.value,
maxFeePerGas: fee.maxFeePerGas.value,
maxPriorityFeePerGas: fee.maxPriorityFeePerGas.value,
gasLimit: gasLimit.toBI,
to: recipient,
value: value ?? BigInt.zero,
data: data ?? Uint8List(0),
accessList: accessList ?? [],
chainId: type.chainId,
),
EvmFeeInformation() => accessList != null
EvmLegacyGasPrice fee => accessList != null
? RawEVMTransactionType1.unsigned(
nonce: nonce,
gasPrice: gasPrice.value,
gasPrice: fee.gasPrice.value,
gasLimit: gasLimit.toBI,
to: recipient,
value: value ?? BigInt.zero,
Expand All @@ -279,7 +317,7 @@ final class EvmRpcInterface {
)
: RawEVMTransactionType0.unsigned(
nonce: nonce,
gasPrice: gasPrice.value,
gasPrice: fee.gasPrice.value,
gasLimit: gasLimit.toBI,
to: recipient,
value: value ?? BigInt.zero,
Expand All @@ -298,7 +336,7 @@ final class EvmRpcInterface {
required String sender,
required String recipient,
required Uint8List seed,
required EvmFeeInformation feeInfo,
required EvmFeeInformation? feeInfo,
required Uint8List? data,
required BigInt? value,
List<AccessListItem>? accessList,
Expand Down Expand Up @@ -345,7 +383,7 @@ final class EvmRpcInterface {
required String sender,
required String recipient,
required Uint8List seed,
required EvmFeeInformation feeInfo,
required EvmFeeInformation? feeInfo,
required Uint8List? data,
required BigInt? value,
List<AccessListItem>? accessList,
Expand Down Expand Up @@ -409,7 +447,7 @@ final class EvmRpcInterface {
sender: sender,
recipient: contractAddress,
seed: seed,
feeInfo: feeInfo ?? EvmFeeInformation.zero,
feeInfo: feeInfo,
data: data,
value: value ?? BigInt.zero,
);
Expand Down Expand Up @@ -477,7 +515,7 @@ final class EvmRpcInterface {
function: function,
sender: from,
seed: seed,
feeInfo: EvmFeeInformation.zero,
feeInfo: null,
);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/src/crypto/network_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ sealed class NetworkType {

sealed class EVMNetworkType extends NetworkType {
final int chainId;
final bool useEIP1559;

const EVMNetworkType({
required super.messagePrefix,
required super.coin,
required super.blockTime,
required this.chainId,
this.useEIP1559 = true,
});
}

Expand Down Expand Up @@ -382,6 +384,7 @@ class ZENIQ_SMART_NETWORK extends EVMNetworkType {
coin: zeniqSmart,
messagePrefix: "\x19Zeniq Signed Message:\n",
blockTime: 3,
useEIP1559: false,
);
}

Expand All @@ -394,6 +397,7 @@ class BNB_NETWORK extends EVMNetworkType {
coin: binanceSmart,
messagePrefix: "\x19Binance Chain Signed Message:\n",
blockTime: 3,
useEIP1559: true,
);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/crypto/utxo/utils/send.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import 'package:walletkit_dart/walletkit_dart.dart';
///

RawTransaction buildUnsignedTransaction({
required TransferIntent intent,
required TransferIntent<UtxoFeeInformation> intent,
required UTXONetworkType networkType,
required HDWalletPath walletPath,
required Iterable<UTXOTransaction> txList,
Expand Down
Loading
Loading