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)
- User authenticated (see Authentication Guide)
- EVM wallet available (see Wallet Creation)
- Contract ABI and address
Write to Contract
Execute state-changing functions on a smart contract:Copy
Ask AI
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:Copy
Ask AI
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
Copy
Ask AI
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:Copy
Ask AI
// 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:Copy
Ask AI
// 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
Copy
Ask AI
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:Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
// 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
Copy
Ask AI
// 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
Copy
Ask AI
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 - Basic ETH transfers
- ERC-20 Token Transfers - Send tokens
- Gas Management - Optimize gas costs
- Message Signing - Sign messages for verification