Skip to main content

Overview

Send ETH by creating transactions, estimating gas, and handling responses.

Prerequisites

Send ETH Transaction

import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import com.dynamic.sdk.android.Chains.EVM.EthereumTransaction
import com.dynamic.sdk.android.Chains.EVM.convertEthToWei
import kotlinx.coroutines.launch
import java.math.BigInteger

val sdk = DynamicSDK.getInstance()

suspend fun sendTransaction(wallet: BaseWallet, to: String, amountInEth: String) {
    try {
        val chainId = 1
        val client = sdk.evm.createPublicClient(chainId)
        val gasPrice = client.getGasPrice()
        val maxFeePerGas = gasPrice * BigInteger.valueOf(2)
        val maxPriorityFeePerGas = gasPrice
        val weiAmount = convertEthToWei(amountInEth)

        val transaction = EthereumTransaction(
            from = wallet.address,
            to = to,
            value = weiAmount,
            gas = BigInteger.valueOf(21000),
            maxFeePerGas = maxFeePerGas,
            maxPriorityFeePerGas = maxPriorityFeePerGas
        )

        val txHash = sdk.evm.sendTransaction(transaction, wallet)
        println("Transaction sent!")
        println("Hash: $txHash")
    } catch (e: Exception) {
        println("Transaction failed: ${e.message}")
    }
}

Sign Transaction (Without Sending)

import com.dynamic.sdk.android.Chains.EVM.signEthereumTransaction

suspend fun signTransactionOnly(wallet: BaseWallet) {
    try {
        val signedTx = sdk.wallets.signEthereumTransaction(
            wallet = wallet,
            to = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bDd7",
            value = "0.001",
            gasLimit = "21000",
            maxPriorityFeePerGas = "2",
            maxFeePerGas = "50"
        )
        println("Signed transaction: $signedTx")
    } catch (e: Exception) {
        println("Failed to sign: ${e.message}")
    }
}

Best Practices

  • Always get current gas price before sending
  • Add buffer for price fluctuations (2x multiplier)
  • Handle common errors: insufficient funds, gas too low, invalid address
  • Validate address format before sending
  • Show transaction confirmations to users
  • Display transaction status with explorer links

Handle Transaction Errors

try {
    val txHash = sdk.evm.sendTransaction(transaction, wallet)
} catch (e: Exception) {
    when {
        e.message?.contains("insufficient", ignoreCase = true) == true -> {
            showError("Insufficient funds for transaction")
        }
        e.message?.contains("gas", ignoreCase = true) == true -> {
            showError("Gas estimation failed. Try increasing gas limit.")
        }
        else -> {
            showError("Transaction failed: ${e.message}")
        }
    }
}

Show Transaction Status

@Composable
fun TransactionStatusView(txHash: String, chainId: Int) {
    val explorerUrl = when (chainId) {
        1 -> "https://etherscan.io/tx/$txHash"
        11155111 -> "https://sepolia.etherscan.io/tx/$txHash"
        84532 -> "https://sepolia.basescan.org/tx/$txHash"
        137 -> "https://polygonscan.com/tx/$txHash"
        else -> null
    }

    Column {
        Text("Transaction Submitted", style = MaterialTheme.typography.headlineSmall)
        Text(
            text = txHash,
            style = MaterialTheme.typography.bodySmall,
            maxLines = 1
        )

        explorerUrl?.let { url ->
            Button(onClick = { /* Open URL */ }) {
                Text("View on Explorer")
            }
        }
    }
}

Validate Address Format

fun isValidEthereumAddress(address: String): Boolean {
    return address.matches(Regex("^0x[a-fA-F0-9]{40}$"))
}

Error Handling

Common Transaction Errors

sealed class TransactionError {
    data class InsufficientFunds(val message: String) : TransactionError()
    data class GasEstimationFailed(val message: String) : TransactionError()
    data class UserRejected(val message: String) : TransactionError()
    data class NetworkError(val message: String) : TransactionError()
    data class InvalidAddress(val message: String) : TransactionError()
    data class Unknown(val message: String) : TransactionError()
}

fun parseTransactionError(e: Exception): TransactionError {
    val errorMessage = e.message?.lowercase() ?: ""

    return when {
        errorMessage.contains("insufficient") ->
            TransactionError.InsufficientFunds("Insufficient balance for this transaction")
        errorMessage.contains("gas") ->
            TransactionError.GasEstimationFailed("Gas estimation failed. Try increasing gas limit.")
        errorMessage.contains("rejected") || errorMessage.contains("denied") ->
            TransactionError.UserRejected("Transaction was rejected")
        errorMessage.contains("network") ->
            TransactionError.NetworkError("Network error. Please check your connection.")
        errorMessage.contains("invalid") && errorMessage.contains("address") ->
            TransactionError.InvalidAddress("Invalid recipient address")
        else ->
            TransactionError.Unknown("Transaction failed. Please try again.")
    }
}

// Usage in ViewModel
try {
    val txHash = sdk.evm.sendTransaction(transaction, wallet)
    _transactionHash.value = txHash
} catch (e: Exception) {
    val error = parseTransactionError(e)
    _errorMessage.value = when (error) {
        is TransactionError.InsufficientFunds -> error.message
        is TransactionError.GasEstimationFailed -> error.message
        is TransactionError.UserRejected -> error.message
        is TransactionError.NetworkError -> error.message
        is TransactionError.InvalidAddress -> error.message
        is TransactionError.Unknown -> error.message
    }
}

Troubleshooting

Transaction not confirming: Increase gas price multiplier to 3x Insufficient funds: Check balance includes gas fees before sending Nonce issues: SDK handles nonces automatically, manually specify only if needed

What’s Next