Skip to main content

Overview

This guide covers sending ETH transactions including transaction creation, signing, and sending with the Dynamic Swift SDK.

Prerequisites

Send ETH Transaction

import DynamicSDKSwift
import SwiftBigInt

let sdk = DynamicSDK.instance()

func sendTransaction(wallet: BaseWallet, to: String, amountInEth: Double) async {
    do {
        // Get current gas price
        let chainId = 1 // Ethereum mainnet, or use your target chain
        let client = sdk.evm.createPublicClient(chainId: chainId)
        let gasPrice = try await client.getGasPrice()

        // Convert ETH to Wei (1 ETH = 10^18 Wei)
        let weiAmount = BigUInt(amountInEth * pow(10.0, 18.0))

        // Create transaction
        let transaction = EthereumTransaction(
            from: wallet.address,
            to: to,
            value: weiAmount,
            gas: BigUInt(21000),  // Standard ETH transfer
            maxFeePerGas: gasPrice * 2,
            maxPriorityFeePerGas: gasPrice
        )

        // Send transaction
        let txHash = try await sdk.evm.sendTransaction(
            transaction: transaction,
            wallet: wallet
        )

        print("Transaction sent!")
        print("Hash: \(txHash)")
    } catch {
        print("Transaction failed: \(error)")
    }
}

Complete Send Transaction Example

import SwiftUI
import DynamicSDKSwift
import SwiftBigInt

struct SendTransactionView: View {
    let wallet: BaseWallet

    @State private var recipient = ""
    @State private var amount = ""
    @State private var txHash: String?
    @State private var isLoading = false
    @State private var errorMessage: String?

    private let sdk = DynamicSDK.instance()

    var body: some View {
        VStack(spacing: 16) {
            TextField("Recipient address", text: $recipient)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)

            TextField("Amount (ETH)", text: $amount)
                .textFieldStyle(.roundedBorder)
                .keyboardType(.decimalPad)

            Button("Send Transaction") {
                sendTransaction()
            }
            .disabled(recipient.isEmpty || amount.isEmpty || isLoading)
            .buttonStyle(.borderedProminent)

            if isLoading {
                ProgressView("Sending...")
            }

            if let hash = txHash {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Transaction Sent!")
                        .font(.headline)
                        .foregroundColor(.green)

                    Text("Hash: \(hash)")
                        .font(.system(.caption, design: .monospaced))
                        .lineLimit(2)

                    Button("Copy Hash") {
                        UIPasteboard.general.string = hash
                    }
                    .font(.caption)
                }
                .padding()
                .background(Color.green.opacity(0.1))
                .cornerRadius(8)
            }

            if let error = errorMessage {
                Text(error)
                    .foregroundColor(.red)
                    .font(.caption)
            }
        }
        .padding()
    }

    private func sendTransaction() {
        guard let amountDouble = Double(amount) else {
            errorMessage = "Invalid amount"
            return
        }

        isLoading = true
        errorMessage = nil
        txHash = nil

        Task { @MainActor in
            do {
                // Convert ETH to Wei (1 ETH = 10^18 Wei)
                let weiAmount = BigUInt(amountDouble * pow(10, 18))

                // Get gas price
                let chainId = 84532 // Base Sepolia - adjust as needed
                let client = sdk.evm.createPublicClient(chainId: chainId)
                let gasPrice = try await client.getGasPrice()

                // Create and send transaction
                let transaction = EthereumTransaction(
                    from: wallet.address,
                    to: recipient,
                    value: weiAmount,
                    gas: BigUInt(21000),
                    maxFeePerGas: gasPrice * 2,
                    maxPriorityFeePerGas: gasPrice
                )

                txHash = try await sdk.evm.sendTransaction(
                    transaction: transaction,
                    wallet: wallet
                )
            } catch {
                errorMessage = "Failed: \(error.localizedDescription)"
            }
            isLoading = false
        }
    }
}

Sign Transaction (Without Sending)

To sign a transaction without broadcasting it:
import DynamicSDKSwift
import SwiftBigInt

let sdk = DynamicSDK.instance()

func signTransaction(wallet: BaseWallet) async {
    do {
        let transaction = EthereumTransaction(
            from: wallet.address,
            to: "0x...",
            value: BigUInt(1000000000000000), // 0.001 ETH in Wei
            gas: BigUInt(21000),
            maxFeePerGas: BigUInt(20000000000),
            maxPriorityFeePerGas: BigUInt(1000000000)
        )

        // Sign without sending
        let signedTx = try await sdk.evm.signTransaction(
            transaction: transaction,
            wallet: wallet
        )

        print("Signed transaction: \(signedTx)")
    } catch {
        print("Failed to sign: \(error)")
    }
}

Best Practices

1. Always Get Current Gas Price

let client = sdk.evm.createPublicClient(chainId: chainId)
let gasPrice = try await client.getGasPrice()

// Add buffer for price fluctuations
let maxFeePerGas = gasPrice * 2

2. Handle Transaction Errors

do {
    let txHash = try await sdk.evm.sendTransaction(
        transaction: transaction,
        wallet: wallet
    )
} catch {
    // Common errors:
    // - Insufficient funds
    // - Gas price too low
    // - Invalid recipient address
    // - Network issues

    if error.localizedDescription.contains("insufficient") {
        showError("Insufficient funds for transaction")
    } else {
        showError("Transaction failed: \(error.localizedDescription)")
    }
}

3. Show Transaction Status

struct TransactionStatusView: View {
    let txHash: String
    let chainId: Int

    var explorerUrl: String {
        // Map chain ID to block explorer
        switch chainId {
        case 1: return "https://etherscan.io/tx/\(txHash)"
        case 84532: return "https://sepolia.basescan.org/tx/\(txHash)"
        case 137: return "https://polygonscan.com/tx/\(txHash)"
        default: return ""
        }
    }

    var body: some View {
        VStack {
            Text("Transaction Submitted")
                .font(.headline)

            Text(txHash)
                .font(.caption)
                .lineLimit(1)
                .truncationMode(.middle)

            if !explorerUrl.isEmpty {
                Link("View on Explorer", destination: URL(string: explorerUrl)!)
                    .font(.caption)
            }
        }
    }
}

4. Confirm Before Sending

struct ConfirmTransactionView: View {
    let recipient: String
    let amount: String
    let onConfirm: () -> Void
    let onCancel: () -> Void

    var body: some View {
        VStack(spacing: 16) {
            Text("Confirm Transaction")
                .font(.headline)

            VStack(alignment: .leading) {
                Text("To: \(recipient)")
                Text("Amount: \(amount) ETH")
            }
            .font(.caption)

            HStack {
                Button("Cancel", action: onCancel)
                    .buttonStyle(.bordered)

                Button("Confirm", action: onConfirm)
                    .buttonStyle(.borderedProminent)
            }
        }
        .padding()
    }
}

Error Handling

Common Transaction Errors

do {
    let txHash = try await sdk.evm.sendTransaction(
        transaction: transaction,
        wallet: wallet
    )
} catch {
    let errorMessage: String
    let errorDesc = error.localizedDescription.lowercased()

    if errorDesc.contains("insufficient") {
        errorMessage = "Insufficient balance for this transaction"
    } else if errorDesc.contains("gas") {
        errorMessage = "Gas estimation failed. Try increasing gas limit."
    } else if errorDesc.contains("rejected") || errorDesc.contains("denied") {
        errorMessage = "Transaction was rejected"
    } else if errorDesc.contains("network") {
        errorMessage = "Network error. Please check your connection."
    } else {
        errorMessage = "Transaction failed. Please try again."
    }

    showAlert(errorMessage)
}

Next Steps