Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
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: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: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
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:// 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:// 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
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: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
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
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
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
// 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
// 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
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