Skip to main content

Overview

This guide covers transaction operations including sending ETH/SOL, ERC20 tokens, signing transactions, and smart contract interactions with the Dynamic Swift SDK.

Prerequisites

EVM Transactions

Send ETH Transaction

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func sendTransaction(wallet: BaseWallet, to: String, amountInWei: Int) async {
    do {
        // Get current gas price
        let chainId = 1 // Ethereum mainnet, or use your target chain
        let client = try await sdk.evm.createPublicClient(chainId: chainId)
        let gasPrice = try await client.getGasPrice()
        
        // Create transaction
        let transaction = EthereumTransaction(
            to: to,
            value: amountInWei,
            gasLimit: 21000,  // Standard ETH transfer
            maxFeePerGas: Int(gasPrice.value * 2),
            maxPriorityFeePerGas: Int(gasPrice.value)
        )
        
        // 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

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 amountInWei = Int(amountDouble * pow(10, 18))
                
                // Get gas price
                let chainId = 84532 // Base Sepolia - adjust as needed
                let client = try await sdk.evm.createPublicClient(chainId: chainId)
                let gasPrice = try await client.getGasPrice()
                
                // Create and send transaction
                let transaction = EthereumTransaction(
                    to: recipient,
                    value: amountInWei,
                    gasLimit: 21000,
                    maxFeePerGas: Int(gasPrice.value * 2),
                    maxPriorityFeePerGas: Int(gasPrice.value)
                )
                
                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

let sdk = DynamicSDK.instance()

func signTransaction(wallet: BaseWallet) async {
    do {
        let transaction = EthereumTransaction(
            to: "0x...",
            value: 1000000000000000, // 0.001 ETH in Wei
            gasLimit: 21000,
            maxFeePerGas: 20000000000,
            maxPriorityFeePerGas: 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)")
    }
}

ERC20 Token Transfers

Send ERC20 Tokens

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func sendERC20(
    wallet: BaseWallet,
    tokenAddress: String,
    recipient: String,
    amount: String  // Amount in token's smallest unit
) async {
    do {
        let input = WriteContractInput(
            contractAddress: tokenAddress,
            functionName: "transfer",
            args: [recipient, amount],
            abi: Erc20.abi
        )
        
        let txHash = try await sdk.evm.writeContract(
            wallet: wallet,
            input: input
        )
        
        print("ERC20 transfer sent!")
        print("Hash: \(txHash)")
    } catch {
        print("Failed to send ERC20: \(error)")
    }
}

Complete ERC20 Transfer Example

import SwiftUI
import DynamicSDKSwift

struct SendERC20View: View {
    let wallet: BaseWallet
    
    @State private var tokenAddress = ""
    @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("Token contract address", text: $tokenAddress)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)
            
            TextField("Recipient address", text: $recipient)
                .textFieldStyle(.roundedBorder)
                .autocapitalization(.none)
            
            TextField("Amount (in smallest unit)", text: $amount)
                .textFieldStyle(.roundedBorder)
                .keyboardType(.numberPad)
            
            Button("Send Tokens") {
                sendTokens()
            }
            .disabled(tokenAddress.isEmpty || recipient.isEmpty || amount.isEmpty || isLoading)
            .buttonStyle(.borderedProminent)
            
            if isLoading {
                ProgressView("Sending...")
            }
            
            if let hash = txHash {
                Text("Transaction: \(hash)")
                    .font(.caption)
                    .foregroundColor(.green)
            }
            
            if let error = errorMessage {
                Text(error)
                    .foregroundColor(.red)
                    .font(.caption)
            }
        }
        .padding()
    }
    
    private func sendTokens() {
        isLoading = true
        errorMessage = nil
        txHash = nil
        
        Task { @MainActor in
            do {
                let input = WriteContractInput(
                    contractAddress: tokenAddress,
                    functionName: "transfer",
                    args: [recipient, amount],
                    abi: Erc20.abi
                )
                
                txHash = try await sdk.evm.writeContract(
                    wallet: wallet,
                    input: input
                )
            } catch {
                errorMessage = "Failed: \(error.localizedDescription)"
            }
            isLoading = false
        }
    }
}

Smart Contract Interactions

Write to Contract

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func writeContract(wallet: BaseWallet) async {
    // Example: Calling a custom contract function
    let customAbi = """
    [
        {
            "inputs": [
                {"name": "value", "type": "uint256"}
            ],
            "name": "setValue",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        }
    ]
    """
    
    do {
        let input = WriteContractInput(
            contractAddress: "0xYourContractAddress",
            functionName: "setValue",
            args: ["12345"],
            abi: customAbi
        )
        
        let txHash = try await sdk.evm.writeContract(
            wallet: wallet,
            input: input
        )
        
        print("Contract interaction successful!")
        print("Hash: \(txHash)")
    } catch {
        print("Contract call failed: \(error)")
    }
}

Solana Transactions

Sign and Send Solana Transaction

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func sendSolanaTransaction(wallet: BaseWallet, base64Transaction: String) async {
    do {
        // Create connection and signer
        let connection = try await sdk.solana.createConnection()
        let signer = try await sdk.solana.createSigner(wallet: wallet)
        
        // Get latest blockhash
        let blockhash = try await connection.getLatestBlockhash()
        print("Latest blockhash: \(blockhash)")
        
        // Sign and send transaction
        let signature = try await signer.signAndSendEncodedTransaction(
            base64Transaction: base64Transaction
        )
        
        print("Solana transaction sent!")
        print("Signature: \(signature)")
    } catch {
        print("Solana transaction failed: \(error)")
    }
}

Sign Solana Message

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func signSolanaMessage(wallet: BaseWallet, message: String) async {
    do {
        let signer = try await sdk.solana.createSigner(wallet: wallet)
        let signature = try await signer.signMessage(message: message)
        
        print("Message signed!")
        print("Signature: \(signature)")
    } catch {
        print("Failed to sign message: \(error)")
    }
}

Sign Solana Transaction (Without Sending)

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func signSolanaTransaction(wallet: BaseWallet, base64Transaction: String) async {
    do {
        let signer = try await sdk.solana.createSigner(wallet: wallet)
        let signedTx = try await signer.signEncodedTransaction(
            base64Transaction: base64Transaction
        )
        
        print("Transaction signed!")
        print("Signed transaction: \(signedTx)")
    } catch {
        print("Failed to sign transaction: \(error)")
    }
}

Transaction Helpers

Get Gas Price

import DynamicSDKSwift

let sdk = DynamicSDK.instance()

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

Estimate Gas

For contract interactions, you may want to estimate gas:
// Gas limits for common operations:
// - ETH transfer: 21000
// - ERC20 transfer: ~65000
// - Complex contract calls: varies (estimate or use higher limit)

Best Practices

1. Always Get Current Gas Price

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

// Add buffer for price fluctuations
let maxFeePerGas = Int(gasPrice.value * 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

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

After mastering transactions, you can: