> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Smart Contract Interactions

> Learn how to interact with smart contracts using the Dynamic Swift SDK.

## Overview

This guide covers how to interact with smart contracts on EVM chains, including reading contract state and executing contract functions.

## Prerequisites

* Dynamic SDK initialized (see [Installation Guide](/swift/quickstart))
* User authenticated (see [Authentication Guide](/swift/authentication))
* EVM wallet available (see [Wallet Creation](/swift/wallet-creation))
* Contract ABI and address

## Write to Contract

Execute state-changing functions on a smart contract:

```swift theme={"system"}
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 {
        // Parse ABI JSON string to array
        guard let abiData = customAbi.data(using: .utf8),
              let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
            throw DynamicSDKError.custom("Invalid ABI format")
        }

        let input = WriteContractInput(
            address: "0xYourContractAddress",
            abi: abiArray,
            functionName: "setValue",
            args: ["12345"]
        )

        let txHash = try await sdk.evm.writeContract(
            wallet: wallet,
            input: input
        )

        print("Contract interaction successful!")
        print("Hash: \(txHash)")
    } catch {
        print("Contract call failed: \(error)")
    }
}
```

## Read Contract Data

Query contract state without sending a transaction:

```swift theme={"system"}
import DynamicSDKSwift

let sdk = DynamicSDK.instance()

func readContract(chainId: Int) async {
    let contractAbi = """
    [
        {
            "inputs": [],
            "name": "getValue",
            "outputs": [
                {"name": "", "type": "uint256"}
            ],
            "stateMutability": "view",
            "type": "function"
        }
    ]
    """

    do {
        let client = sdk.evm.createPublicClient(chainId: chainId)

        let result = try await client.readContract(
            address: "0xYourContractAddress",
            functionName: "getValue",
            abi: contractAbi
        )

        print("Contract value: \(result)")
    } catch {
        print("Failed to read contract: \(error)")
    }
}
```

## Complete Contract Interaction Example

```swift theme={"system"}
import SwiftUI
import DynamicSDKSwift

struct ContractInteractionView: View {
    let wallet: BaseWallet
    let contractAddress: String
    let chainId: Int

    @State private var inputValue = ""
    @State private var currentValue: String?
    @State private var txHash: String?
    @State private var isLoading = false
    @State private var error: String?

    private let sdk = DynamicSDK.instance()

    // Example ABI for a simple storage contract
    private let contractAbi = """
    [
        {
            "inputs": [{"name": "value", "type": "uint256"}],
            "name": "setValue",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "inputs": [],
            "name": "getValue",
            "outputs": [{"name": "", "type": "uint256"}],
            "stateMutability": "view",
            "type": "function"
        }
    ]
    """

    var body: some View {
        VStack(spacing: 20) {
            // Read section
            VStack(alignment: .leading, spacing: 8) {
                Text("Read Contract")
                    .font(.headline)

                if let value = currentValue {
                    HStack {
                        Text("Current Value:")
                        Text(value)
                            .fontWeight(.bold)
                    }
                }

                Button("Refresh Value") {
                    Task { await readValue() }
                }
                .buttonStyle(.bordered)
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(8)

            Divider()

            // Write section
            VStack(alignment: .leading, spacing: 8) {
                Text("Write to Contract")
                    .font(.headline)

                TextField("New value", text: $inputValue)
                    .textFieldStyle(.roundedBorder)
                    .keyboardType(.numberPad)

                Button("Set Value") {
                    Task { await setValue() }
                }
                .buttonStyle(.borderedProminent)
                .disabled(inputValue.isEmpty || isLoading)

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

                if let hash = txHash {
                    VStack(alignment: .leading, spacing: 4) {
                        Text("Transaction sent!")
                            .foregroundColor(.green)
                        Text("Hash: \(hash)")
                            .font(.caption)
                            .lineLimit(1)
                    }
                }

                if let error = error {
                    Text(error)
                        .foregroundColor(.red)
                        .font(.caption)
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(8)
        }
        .padding()
        .onAppear {
            Task { await readValue() }
        }
    }

    private func readValue() async {
        do {
            let client = sdk.evm.createPublicClient(chainId: chainId)
            let result = try await client.readContract(
                address: contractAddress,
                functionName: "getValue",
                abi: contractAbi
            )
            await MainActor.run {
                currentValue = String(describing: result)
            }
        } catch {
            await MainActor.run {
                self.error = "Failed to read: \(error.localizedDescription)"
            }
        }
    }

    private func setValue() async {
        await MainActor.run {
            isLoading = true
            error = nil
            txHash = nil
        }

        do {
            // Parse ABI JSON string to array
            guard let abiData = contractAbi.data(using: .utf8),
                  let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
                throw NSError(domain: "ContractError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid ABI format"])
            }

            let input = WriteContractInput(
                address: contractAddress,
                abi: abiArray,
                functionName: "setValue",
                args: [inputValue]
            )

            let hash = try await sdk.evm.writeContract(
                wallet: wallet,
                input: input
            )

            await MainActor.run {
                txHash = hash
                // Refresh the value after transaction
                Task { await readValue() }
            }
        } catch {
            await MainActor.run {
                self.error = "Failed to set value: \(error.localizedDescription)"
            }
        }

        await MainActor.run {
            isLoading = false
        }
    }
}
```

## Working with Contract ABIs

### Defining ABIs

ABIs (Application Binary Interfaces) define how to interact with smart contracts:

```swift theme={"system"}
// Simple storage contract ABI
let storageAbi = """
[
    {
        "inputs": [{"name": "value", "type": "uint256"}],
        "name": "store",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "retrieve",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function"
    }
]
"""

// ERC-721 NFT contract functions
let nftAbi = """
[
    {
        "inputs": [{"name": "to", "type": "address"}, {"name": "tokenId", "type": "uint256"}],
        "name": "mint",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [{"name": "owner", "type": "address"}],
        "name": "balanceOf",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function"
    }
]
"""
```

### Built-in ABIs

The SDK includes common contract ABIs. Note that you need to parse the ABI string to a JSON array:

```swift theme={"system"}
// Parse the built-in ERC-20 ABI
guard let abiData = Erc20.abi.data(using: .utf8),
      let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
    throw DynamicSDKError.custom("Invalid Erc20 ABI")
}

// ERC-20 token transfers
let input = WriteContractInput(
    address: tokenAddress,
    abi: abiArray,
    functionName: "transfer",
    args: [recipient, amount]
)
```

## Advanced Contract Interactions

### NFT Minting

```swift theme={"system"}
func mintNFT(
    wallet: BaseWallet,
    nftContractAddress: String,
    recipient: String,
    tokenId: String
) async throws -> String {
    let nftAbi = """
    [
        {
            "inputs": [
                {"name": "to", "type": "address"},
                {"name": "tokenId", "type": "uint256"}
            ],
            "name": "mint",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        }
    ]
    """

    // Parse ABI JSON string to array
    guard let abiData = nftAbi.data(using: .utf8),
          let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
        throw DynamicSDKError.custom("Invalid ABI format")
    }

    let input = WriteContractInput(
        address: nftContractAddress,
        abi: abiArray,
        functionName: "mint",
        args: [recipient, tokenId]
    )

    return try await sdk.evm.writeContract(
        wallet: wallet,
        input: input
    )
}
```

### Approve Token Spending

Before a contract can spend your tokens, you need to approve it:

```swift theme={"system"}
func approveTokenSpending(
    wallet: BaseWallet,
    tokenAddress: String,
    spenderAddress: String,
    amount: String
) async throws -> String {
    // Parse the built-in ERC-20 ABI
    guard let abiData = Erc20.abi.data(using: .utf8),
          let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
        throw DynamicSDKError.custom("Invalid Erc20 ABI")
    }

    let input = WriteContractInput(
        address: tokenAddress,
        abi: abiArray,
        functionName: "approve",
        args: [spenderAddress, amount]
    )

    return try await sdk.evm.writeContract(
        wallet: wallet,
        input: input
    )
}
```

### Check Token Allowance

```swift theme={"system"}
func checkAllowance(
    tokenAddress: String,
    ownerAddress: String,
    spenderAddress: String,
    chainId: Int
) async throws -> String {
    let client = sdk.evm.createPublicClient(chainId: chainId)

    let result = try await client.readContract(
        address: tokenAddress,
        functionName: "allowance",
        abi: Erc20.abi,
        args: [ownerAddress, spenderAddress]
    )

    return String(describing: result)
}
```

## Best Practices

### 1. Validate Contract Addresses

```swift theme={"system"}
func isValidContractAddress(_ address: String, chainId: Int) async -> Bool {
    do {
        let client = sdk.evm.createPublicClient(chainId: chainId)
        let code = try await client.getCode(address: address)
        return !code.isEmpty && code != "0x"
    } catch {
        return false
    }
}
```

### 2. Handle Contract Errors

```swift theme={"system"}
do {
    let txHash = try await sdk.evm.writeContract(wallet: wallet, input: input)
} catch {
    let errorDesc = error.localizedDescription.lowercased()

    if errorDesc.contains("revert") {
        print("Contract reverted. Check contract requirements.")
    } else if errorDesc.contains("gas") {
        print("Out of gas. Increase gas limit.")
    } else {
        print("Contract call failed: \(error)")
    }
}
```

### 3. Estimate Gas for Complex Operations

```swift theme={"system"}
// For complex contract interactions, estimate gas first
let estimatedGas = try await client.estimateGas(transaction)
let gasLimitWithBuffer = Int(Double(estimatedGas) * 1.2) // Add 20% buffer
```

## Common Use Cases

### DeFi: Swap Tokens

```swift theme={"system"}
// Example: Uniswap token swap
func swapTokens(
    wallet: BaseWallet,
    routerAddress: String,
    amountIn: String,
    amountOutMin: String,
    path: [String],
    deadline: String
) async throws -> String {
    let swapAbi = """
    [
        {
            "inputs": [
                {"name": "amountIn", "type": "uint256"},
                {"name": "amountOutMin", "type": "uint256"},
                {"name": "path", "type": "address[]"},
                {"name": "to", "type": "address"},
                {"name": "deadline", "type": "uint256"}
            ],
            "name": "swapExactTokensForTokens",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        }
    ]
    """

    // Parse ABI JSON string to array
    guard let abiData = swapAbi.data(using: .utf8),
          let abiArray = try JSONSerialization.jsonObject(with: abiData) as? [[String: Any]] else {
        throw DynamicSDKError.custom("Invalid ABI format")
    }

    let input = WriteContractInput(
        address: routerAddress,
        abi: abiArray,
        functionName: "swapExactTokensForTokens",
        args: [amountIn, amountOutMin, path, wallet.address, deadline]
    )

    return try await sdk.evm.writeContract(wallet: wallet, input: input)
}
```

### NFT: Check Ownership

```swift theme={"system"}
func checkNFTOwnership(
    nftContractAddress: String,
    tokenId: String,
    chainId: Int
) async throws -> String {
    let nftAbi = """
    [
        {
            "inputs": [{"name": "tokenId", "type": "uint256"}],
            "name": "ownerOf",
            "outputs": [{"name": "", "type": "address"}],
            "stateMutability": "view",
            "type": "function"
        }
    ]
    """

    let client = sdk.evm.createPublicClient(chainId: chainId)
    let owner = try await client.readContract(
        address: nftContractAddress,
        functionName: "ownerOf",
        abi: nftAbi,
        args: [tokenId]
    )

    return String(describing: owner)
}
```

## Next Steps

* [Send ETH Transactions](/swift/wallets/evm/send-eth) - Basic ETH transfers
* [ERC-20 Token Transfers](/swift/wallets/evm/erc20-transfers) - Send tokens
* [Gas Management](/swift/wallets/evm/gas-management) - Optimize gas costs
* [Message Signing](/swift/wallets/evm/message-signing) - Sign messages for verification
