Skip to main content

Overview

This guide covers gas price estimation, gas limit calculation, and optimization strategies for EVM transactions using the web3dart package.

Prerequisites

Get Current Gas Price

Fetch the current gas price from the network:
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_web3dart/dynamic_sdk_web3dart.dart';
import 'package:web3dart/web3dart.dart';

final sdk = DynamicSDK.instance;

Future<BigInt?> getGasPrice(int chainId) async {
  try {
    final client = sdk.web3dart.createPublicClient(chainId: chainId);
    final gasPrice = await client.getGasPrice();
    return gasPrice.getValueInUnitBI(EtherUnit.wei);
  } catch (e) {
    print('Failed to get gas price: $e');
    return null;
  }
}

Standard Gas Limits

Use these standard gas limits for common transaction types:
/// Common gas limits for different transaction types
class GasLimits {
  /// Standard ETH transfer: 21,000 gas
  static const int ethTransfer = 21000;

  /// ERC-20 token transfer: 65,000 gas
  static const int erc20Transfer = 65000;

  /// ERC-20 approve: 50,000 gas
  static const int erc20Approve = 50000;

  /// Contract deployment: 500,000+ gas (varies by contract)
  static const int contractDeploy = 500000;

  /// Contract function call: 100,000 gas (estimate first)
  static const int contractCall = 100000;

  /// NFT minting: 150,000 gas (varies by contract)
  static const int nftMint = 150000;
}

// Usage
final transaction = Transaction(
  from: EthereumAddress.fromHex(wallet.address),
  to: EthereumAddress.fromHex(recipientAddress),
  value: EtherAmount.inWei(BigInt.from(amountInWei)),
  gasPrice: EtherAmount.inWei(gasPrice),
  maxGas: GasLimits.ethTransfer,
);

EIP-1559 Gas Pricing

EIP-1559 introduced a new gas pricing mechanism with two components:

Understanding EIP-1559 Parameters

class EIP1559Gas {
  /// Base fee: Algorithmically determined by the network
  /// This is burned and adjusts based on network congestion
  final BigInt baseFeePerGas;

  /// Priority fee (tip): Goes to the miner/validator
  /// Higher tips get faster inclusion
  final BigInt maxPriorityFeePerGas;

  /// Max fee: Maximum you're willing to pay per gas
  /// Should be: baseFee + maxPriorityFee + buffer
  final BigInt maxFeePerGas;

  EIP1559Gas({
    required this.baseFeePerGas,
    required this.maxPriorityFeePerGas,
    required this.maxFeePerGas,
  });
}

Setting Gas Prices

Future<EIP1559Gas> calculateGasPrices(int chainId) async {
  final client = sdk.web3dart.createPublicClient(chainId: chainId);

  // Get current base fee
  final gasPrice = await client.getGasPrice();
  final baseFee = gasPrice.getValueInUnitBI(EtherUnit.wei);

  // Set priority fee (tip)
  final priorityFee = baseFee ~/ BigInt.from(2); // Typically 50% of base fee

  // Set max fee (with 2x buffer for volatility)
  final maxFee = baseFee * BigInt.from(2) + priorityFee;

  return EIP1559Gas(
    baseFeePerGas: baseFee,
    maxPriorityFeePerGas: priorityFee,
    maxFeePerGas: maxFee,
  );
}

// Usage
final gasPrices = await calculateGasPrices(1);

final transaction = Transaction(
  from: EthereumAddress.fromHex(wallet.address),
  to: EthereumAddress.fromHex(recipient),
  value: EtherAmount.inWei(BigInt.from(amount)),
  maxGas: 21000,
  maxFeePerGas: EtherAmount.inWei(gasPrices.maxFeePerGas),
  maxPriorityFeePerGas: EtherAmount.inWei(gasPrices.maxPriorityFeePerGas),
);

Gas Optimization Strategies

Choose Optimal Gas Prices

class GasStrategy {
  /// Low priority: Base fee + minimal tip
  static EIP1559Gas slow(BigInt baseFee) {
    return EIP1559Gas(
      baseFeePerGas: baseFee,
      maxPriorityFeePerGas: BigInt.from(1000000000), // 1 Gwei
      maxFeePerGas: baseFee + BigInt.from(1000000000),
    );
  }

  /// Medium priority: Base fee + standard tip
  static EIP1559Gas standard(BigInt baseFee) {
    final priorityFee = baseFee ~/ BigInt.from(2);
    return EIP1559Gas(
      baseFeePerGas: baseFee,
      maxPriorityFeePerGas: priorityFee,
      maxFeePerGas: baseFee * BigInt.from(2) + priorityFee,
    );
  }

  /// High priority: Base fee + high tip
  static EIP1559Gas fast(BigInt baseFee) {
    final priorityFee = baseFee;
    return EIP1559Gas(
      baseFeePerGas: baseFee,
      maxPriorityFeePerGas: priorityFee,
      maxFeePerGas: baseFee * BigInt.from(3) + priorityFee,
    );
  }
}

// Usage
final client = sdk.web3dart.createPublicClient(chainId: 1);
final gasPrice = await client.getGasPrice();
final baseFee = gasPrice.getValueInUnitBI(EtherUnit.wei);

// Choose strategy based on urgency
final gas = GasStrategy.fast(baseFee);

Gas Cost Calculator

Calculate the total cost of a transaction:
class GasCostCalculator {
  /// Calculate transaction cost in Wei
  static BigInt calculateCost({
    required int gasLimit,
    required BigInt maxFeePerGas,
  }) {
    return BigInt.from(gasLimit) * maxFeePerGas;
  }

  /// Calculate transaction cost in ETH
  static double calculateCostInETH({
    required int gasLimit,
    required BigInt maxFeePerGas,
  }) {
    final costWei = calculateCost(
      gasLimit: gasLimit,
      maxFeePerGas: maxFeePerGas,
    );
    return costWei.toDouble() / 1e18;
  }

  /// Calculate transaction cost in USD (given ETH price)
  static double calculateCostInUSD({
    required int gasLimit,
    required BigInt maxFeePerGas,
    required double ethPriceUSD,
  }) {
    final costETH = calculateCostInETH(
      gasLimit: gasLimit,
      maxFeePerGas: maxFeePerGas,
    );
    return costETH * ethPriceUSD;
  }
}

// Usage
final gasLimit = 21000;
final maxFeePerGas = BigInt.from(50000000000); // 50 Gwei

final costETH = GasCostCalculator.calculateCostInETH(
  gasLimit: gasLimit,
  maxFeePerGas: maxFeePerGas,
);
print('Transaction cost: $costETH ETH');

final costUSD = GasCostCalculator.calculateCostInUSD(
  gasLimit: gasLimit,
  maxFeePerGas: maxFeePerGas,
  ethPriceUSD: 2000,
);
print('Transaction cost: \$$costUSD USD');

Gas Price Display Widget

import 'package:flutter/material.dart';
import 'package:dynamic_sdk/dynamic_sdk.dart';
import 'package:dynamic_sdk_web3dart/dynamic_sdk_web3dart.dart';
import 'package:web3dart/web3dart.dart';

class GasPriceWidget extends StatefulWidget {
  final int chainId;

  const GasPriceWidget({Key? key, required this.chainId}) : super(key: key);

  @override
  State<GasPriceWidget> createState() => _GasPriceWidgetState();
}

class _GasPriceWidgetState extends State<GasPriceWidget> {
  final sdk = DynamicSDK.instance;
  BigInt? currentGasPrice;
  String? recommendation;

  @override
  void initState() {
    super.initState();
    _updateGasPrice();
  }

  Future<void> _updateGasPrice() async {
    try {
      final client = sdk.web3dart.createPublicClient(chainId: widget.chainId);
      final gasPrice = await client.getGasPrice();
      final priceWei = gasPrice.getValueInUnitBI(EtherUnit.wei);
      final priceGwei = priceWei.toDouble() / 1e9;

      setState(() {
        currentGasPrice = priceWei;

        // Provide recommendations
        if (priceGwei < 20) {
          recommendation = 'Low gas prices - good time to transact';
        } else if (priceGwei < 50) {
          recommendation = 'Moderate gas prices';
        } else {
          recommendation = 'High gas prices - consider waiting';
        }
      });
    } catch (e) {
      print('Failed to update gas price: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    final priceGwei = currentGasPrice != null
        ? (currentGasPrice!.toDouble() / 1e9)
        : null;

    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[200],
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Text(
                'Gas Price',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const Spacer(),
              IconButton(
                icon: const Icon(Icons.refresh),
                onPressed: _updateGasPrice,
              ),
            ],
          ),
          const SizedBox(height: 8),
          if (priceGwei != null) ...[
            Row(
              children: [
                Text(
                  '${priceGwei.toStringAsFixed(2)} Gwei',
                  style: const TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(width: 8),
                if (priceGwei < 20)
                  const Icon(Icons.check_circle, color: Colors.green)
                else if (priceGwei < 50)
                  const Icon(Icons.warning, color: Colors.orange)
                else
                  const Icon(Icons.error, color: Colors.red),
              ],
            ),
            if (recommendation != null) ...[
              const SizedBox(height: 4),
              Text(
                recommendation!,
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ],
          ] else
            const CircularProgressIndicator(),
        ],
      ),
    );
  }
}

Chain-Specific Gas Considerations

Ethereum Mainnet

  • Base fee: Highly variable (10-100+ Gwei)
  • Priority fee: 1-5 Gwei typically
  • Peak times: Weekdays during US/EU business hours

Layer 2 Solutions

  • Optimism/Arbitrum: ~0.001-0.01 Gwei
  • Polygon: ~30-100 Gwei
  • Base: ~0.001-0.01 Gwei
class ChainGasDefaults {
  static Map<String, int> getDefaults(int chainId) {
    switch (chainId) {
      case 1: // Ethereum
        return {'gasLimit': 21000, 'priorityFee': 2000000000}; // 2 Gwei

      case 137: // Polygon
        return {'gasLimit': 21000, 'priorityFee': 30000000000}; // 30 Gwei

      case 10: // Optimism
      case 8453: // Base
        return {'gasLimit': 21000, 'priorityFee': 1000000}; // 0.001 Gwei

      case 42161: // Arbitrum
        return {'gasLimit': 21000, 'priorityFee': 1000000}; // 0.001 Gwei

      default:
        return {'gasLimit': 21000, 'priorityFee': 1000000000}; // 1 Gwei
    }
  }
}

Best Practices

1. Always Add a Buffer

// Add 10-20% buffer to gas estimates
int addGasBuffer(int estimatedGas, {double bufferPercentage = 20}) {
  return (estimatedGas * (1 + bufferPercentage / 100)).toInt();
}

// Usage
final gasWithBuffer = addGasBuffer(21000, bufferPercentage: 20);

2. Set Reasonable Max Fees

// Don't set maxFeePerGas too low, or transaction may fail
Future<BigInt> calculateSafeMaxFee(int chainId) async {
  final client = sdk.web3dart.createPublicClient(chainId: chainId);
  final gasPrice = await client.getGasPrice();
  final baseFee = gasPrice.getValueInUnitBI(EtherUnit.wei);
  return baseFee * BigInt.from(2); // At least 2x base fee
}

3. Monitor Gas Prices

Future<bool> shouldWaitForBetterGas(int chainId, {double thresholdGwei = 100}) async {
  try {
    final client = sdk.web3dart.createPublicClient(chainId: chainId);
    final gasPrice = await client.getGasPrice();
    final priceGwei = gasPrice.getValueInUnitBI(EtherUnit.wei).toDouble() / 1e9;

    if (priceGwei > thresholdGwei) {
      print('Gas prices are high ($priceGwei Gwei). Consider waiting.');
      return true;
    }
    return false;
  } catch (e) {
    print('Failed to check gas price: $e');
    return false;
  }
}

4. Handle Gas Errors

Future<String?> sendTransactionWithGasHandling({
  required BaseWallet wallet,
  required Transaction transaction,
}) async {
  try {
    return await DynamicSDK.instance.web3dart.sendTransaction(
      transaction: transaction,
      wallet: wallet,
    );
  } catch (e) {
    if (e.toString().contains('gas')) {
      print('Gas estimation failed - try increasing gas limit');
    }
    return null;
  }
}

Next Steps