Skip to main content

Overview

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

Prerequisites

Get Current Gas Price

Fetch the current gas price from the network:
import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func getGasPrice(chainId: Int) async -> Int? {
    do {
        let client = sdk.evm.createPublicClient(chainId: chainId)
        let gasPrice = try await client.getGasPrice()
        return Int(gasPrice)
    } catch {
        print("Failed to get gas price: \(error)")
        return nil
    }
}

Standard Gas Limits

Use these standard gas limits for common transaction types:
import SwiftBigInt

/// Common gas limits for different transaction types
struct GasLimits {
    /// Standard ETH transfer: 21,000 gas
    static let ethTransfer = 21_000

    /// ERC-20 token transfer: 65,000 gas
    static let erc20Transfer = 65_000

    /// ERC-20 approve: 50,000 gas
    static let erc20Approve = 50_000

    /// Contract deployment: 500,000+ gas (varies by contract)
    static let contractDeploy = 500_000

    /// Contract function call: 100,000 gas (estimate first)
    static let contractCall = 100_000

    /// NFT minting: 150,000 gas (varies by contract)
    static let nftMint = 150_000
}

// Usage
let transaction = EthereumTransaction(
    from: wallet.address,
    to: recipientAddress,
    value: BigUInt(amountInWei),
    gas: BigUInt(GasLimits.ethTransfer),
    maxFeePerGas: gasPrice,
    maxPriorityFeePerGas: priorityFee
)

Estimate Gas for Transactions

For complex contract interactions, estimate gas dynamically:
import DynamicSDKSwift

let sdk = DynamicSDK.instance()

/// Estimate gas for a transaction
func estimateGas(
    for transaction: EthereumTransaction,
    chainId: Int
) async throws -> Int {
    let client = sdk.evm.createPublicClient(chainId: chainId)
    let estimate = try await client.estimateGas(transaction)
    return Int(estimate.value)
}

/// Estimate gas with safety buffer
func estimateGasWithBuffer(
    for transaction: EthereumTransaction,
    chainId: Int,
    bufferPercentage: Double = 20.0
) async throws -> Int {
    let baseEstimate = try await estimateGas(for: transaction, chainId: chainId)
    let buffer = 1.0 + (bufferPercentage / 100.0)
    return Int(Double(baseEstimate) * buffer)
}

// Usage
let transaction = EthereumTransaction(
    to: contractAddress,
    data: calldata,
    value: 0
)

let gasLimit = try await estimateGasWithBuffer(
    for: transaction,
    chainId: 1,
    bufferPercentage: 20
)

EIP-1559 Gas Pricing

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

Understanding EIP-1559 Parameters

struct EIP1559Gas {
    /// Base fee: Algorithmically determined by the network
    /// This is burned and adjusts based on network congestion
    let baseFeePerGas: Int

    /// Priority fee (tip): Goes to the miner/validator
    /// Higher tips get faster inclusion
    let maxPriorityFeePerGas: Int

    /// Max fee: Maximum you're willing to pay per gas
    /// Should be: baseFee + maxPriorityFee + buffer
    let maxFeePerGas: Int
}

Setting Gas Prices

func calculateGasPrices(chainId: Int) async throws -> EIP1559Gas {
    let client = sdk.evm.createPublicClient(chainId: chainId)

    // Get current base fee
    let gasPrice = try await client.getGasPrice()
    let baseFee = Int(gasPrice.value)

    // Set priority fee (tip)
    let priorityFee = baseFee / 2  // Typically 50% of base fee

    // Set max fee (with 2x buffer for volatility)
    let maxFee = baseFee * 2 + priorityFee

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

// Usage
let gasPrices = try await calculateGasPrices(chainId: 1)

let transaction = EthereumTransaction(
    from: wallet.address,
    to: recipient,
    value: BigUInt(amount),
    gas: BigUInt(21000),
    maxFeePerGas: BigUInt(gasPrices.maxFeePerGas),
    maxPriorityFeePerGas: BigUInt(gasPrices.maxPriorityFeePerGas)
)

Gas Optimization Strategies

1. Batch Transactions

Instead of sending multiple transactions, batch them when possible:
/// Example: Batch multiple token approvals
func batchApprovals(
    wallet: BaseWallet,
    tokenAddress: String,
    spenders: [(address: String, amount: String)]
) async throws {
    // Use a multicall contract to batch approvals
    // This saves gas compared to individual transactions
}

2. Choose Optimal Gas Prices

struct GasStrategy {
    /// Low priority: Base fee + minimal tip
    static func slow(baseFee: Int) -> EIP1559Gas {
        EIP1559Gas(
            baseFeePerGas: baseFee,
            maxPriorityFeePerGas: 1_000_000_000, // 1 Gwei
            maxFeePerGas: baseFee + 1_000_000_000
        )
    }

    /// Medium priority: Base fee + standard tip
    static func standard(baseFee: Int) -> EIP1559Gas {
        let priorityFee = baseFee / 2
        EIP1559Gas(
            baseFeePerGas: baseFee,
            maxPriorityFeePerGas: priorityFee,
            maxFeePerGas: baseFee * 2 + priorityFee
        )
    }

    /// High priority: Base fee + high tip
    static func fast(baseFee: Int) -> EIP1559Gas {
        let priorityFee = baseFee
        EIP1559Gas(
            baseFeePerGas: baseFee,
            maxPriorityFeePerGas: priorityFee,
            maxFeePerGas: baseFee * 3 + priorityFee
        )
    }
}

// Usage
let client = sdk.evm.createPublicClient(chainId: 1)
let gasPrice = try await client.getGasPrice()
let baseFee = Int(gasPrice.value)

// Choose strategy based on urgency
let gas = GasStrategy.fast(baseFee: baseFee)

3. Monitor and Adjust Gas

@MainActor
class GasMonitor: ObservableObject {
    @Published var currentGasPrice: Int?
    @Published var recommendation: String?

    private let sdk = DynamicSDK.instance()
    private let chainId: Int

    init(chainId: Int) {
        self.chainId = chainId
    }

    func updateGasPrice() async {
        do {
            let client = sdk.evm.createPublicClient(chainId: chainId)
            let gasPrice = try await client.getGasPrice()
            let priceGwei = Double(gasPrice.value) / 1_000_000_000

            currentGasPrice = Int(gasPrice.value)

            // 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 {
            print("Failed to update gas price: \(error)")
        }
    }
}

Gas Cost Calculator

Calculate the total cost of a transaction:
struct GasCostCalculator {
    /// Calculate transaction cost in Wei
    static func calculateCost(
        gasLimit: Int,
        maxFeePerGas: Int
    ) -> Int {
        return gasLimit * maxFeePerGas
    }

    /// Calculate transaction cost in ETH
    static func calculateCostInETH(
        gasLimit: Int,
        maxFeePerGas: Int
    ) -> Double {
        let costWei = calculateCost(
            gasLimit: gasLimit,
            maxFeePerGas: maxFeePerGas
        )
        return Double(costWei) / 1_000_000_000_000_000_000
    }

    /// Calculate transaction cost in USD (given ETH price)
    static func calculateCostInUSD(
        gasLimit: Int,
        maxFeePerGas: Int,
        ethPriceUSD: Double
    ) -> Double {
        let costETH = calculateCostInETH(
            gasLimit: gasLimit,
            maxFeePerGas: maxFeePerGas
        )
        return costETH * ethPriceUSD
    }
}

// Usage
let gasLimit = 21_000
let maxFeePerGas = 50_000_000_000 // 50 Gwei

let costETH = GasCostCalculator.calculateCostInETH(
    gasLimit: gasLimit,
    maxFeePerGas: maxFeePerGas
)
print("Transaction cost: \(costETH) ETH")

let costUSD = GasCostCalculator.calculateCostInUSD(
    gasLimit: gasLimit,
    maxFeePerGas: maxFeePerGas,
    ethPriceUSD: 2000
)
print("Transaction cost: $\(costUSD) USD")

Gas Price Display Component

import SwiftUI

struct GasPriceView: View {
    @StateObject private var monitor: GasMonitor

    init(chainId: Int) {
        _monitor = StateObject(wrappedValue: GasMonitor(chainId: chainId))
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Text("Gas Price")
                    .font(.headline)

                Spacer()

                Button {
                    Task { await monitor.updateGasPrice() }
                } label: {
                    Image(systemName: "arrow.clockwise")
                }
            }

            if let gasPrice = monitor.currentGasPrice {
                let priceGwei = Double(gasPrice) / 1_000_000_000

                HStack {
                    Text("\(String(format: "%.2f", priceGwei)) Gwei")
                        .font(.title2)
                        .fontWeight(.bold)

                    if priceGwei < 20 {
                        Image(systemName: "checkmark.circle.fill")
                            .foregroundColor(.green)
                    } else if priceGwei < 50 {
                        Image(systemName: "exclamationmark.circle.fill")
                            .foregroundColor(.orange)
                    } else {
                        Image(systemName: "xmark.circle.fill")
                            .foregroundColor(.red)
                    }
                }

                if let recommendation = monitor.recommendation {
                    Text(recommendation)
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            } else {
                ProgressView()
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .onAppear {
            Task { await monitor.updateGasPrice() }
        }
    }
}

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
struct ChainGasDefaults {
    static func getDefaults(for chainId: Int) -> (gasLimit: Int, priorityFee: Int) {
        switch chainId {
        case 1: // Ethereum
            return (21_000, 2_000_000_000) // 2 Gwei priority

        case 137: // Polygon
            return (21_000, 30_000_000_000) // 30 Gwei priority

        case 10, 8453: // Optimism, Base
            return (21_000, 1_000_000) // 0.001 Gwei priority

        case 42161: // Arbitrum
            return (21_000, 1_000_000) // 0.001 Gwei priority

        default:
            return (21_000, 1_000_000_000) // 1 Gwei default
        }
    }
}

Best Practices

1. Always Add a Buffer

// Add 10-20% buffer to gas estimates
let estimatedGas = try await estimateGas(for: transaction, chainId: chainId)
let gasWithBuffer = Int(Double(estimatedGas) * 1.2)

2. Set Reasonable Max Fees

// Don't set maxFeePerGas too low, or transaction may fail
let baseFee = try await client.getGasPrice()
let maxFee = Int(baseFee.value) * 2 // At least 2x base fee

3. Monitor Gas Prices

// Check gas prices before important transactions
let gasPrice = try await client.getGasPrice()
let priceGwei = Double(gasPrice.value) / 1_000_000_000

if priceGwei > 100 {
    showAlert("Gas prices are high. Consider waiting.")
}

4. Handle Gas Errors

do {
    let txHash = try await sdk.evm.sendTransaction(
        transaction: transaction,
        wallet: wallet
    )
} catch {
    if error.localizedDescription.contains("gas") {
        // Gas estimation failed - try increasing gas limit
        print("Consider increasing gas limit")
    }
}

Next Steps