Overview
The Dynamic SDK provides multiple ways to send ERC-20 tokens. You can use thewriteContract method with the built-in ABI, or encode the transfer calldata manually for more control.
Prerequisites
- Dynamic SDK initialized (see Installation Guide)
- User authenticated (see Authentication Guide)
- EVM wallet available (see Wallet Creation)
Using writeContract
The SDK provides awriteContract method with the built-in Erc20.abi that handles all the encoding for you:
Copy
Ask AI
import DynamicSDKSwift
import SwiftBigInt
let sdk = DynamicSDK.instance()
func sendERC20(
wallet: BaseWallet,
tokenAddress: String,
recipient: String,
amount: String, // Human-readable amount (e.g., "1.5")
decimals: Int = 18
) async {
do {
// Convert human-readable amount to base units
let baseUnits = try parseDecimalToBaseUnits(amount, decimals: decimals)
// 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: "transfer",
args: [recipient, baseUnits]
)
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)")
}
}
// Helper function to convert decimal string to base units
func parseDecimalToBaseUnits(_ value: String, decimals: Int) throws -> BigUInt {
guard decimals >= 0 && decimals <= 77 else {
throw TokenError.invalidDecimals
}
let parts = value.split(separator: ".", omittingEmptySubsequences: false)
let wholePart = String(parts[0].isEmpty ? "0" : parts[0])
let fracPartRaw = parts.count == 2 ? String(parts[1]) : ""
guard let whole = BigUInt(wholePart) else {
throw TokenError.invalidAmount
}
// Trim fractional part to token's decimal places
let trimmedFrac = String(fracPartRaw.prefix(decimals))
let fracPadded = trimmedFrac.padding(toLength: decimals, withPad: "0", startingAt: 0)
let frac = fracPadded.isEmpty ? BigUInt(0) : (BigUInt(fracPadded) ?? BigUInt(0))
return whole * BigUInt(10).power(decimals) + frac
}
enum TokenError: LocalizedError {
case invalidDecimals
case invalidAmount
var errorDescription: String? {
switch self {
case .invalidDecimals: return "Token decimals must be between 0 and 77"
case .invalidAmount: return "Invalid token amount"
}
}
}
The SDK includes a built-in
Erc20.abi constant that contains the standard ERC-20 ABI, including the transfer function. Using writeContract handles all the encoding automatically.Complete ERC20 Transfer View
Copy
Ask AI
import SwiftUI
import DynamicSDKSwift
import SwiftBigInt
struct SendERC20View: View {
let wallet: BaseWallet
@State private var tokenAddress = ""
@State private var recipient = ""
@State private var amount = ""
@State private var decimals = "18"
@State private var txHash: String?
@State private var isLoading = false
@State private var error: String?
var body: some View {
VStack(spacing: 16) {
TextField("Token Contract (0x...)", text: $tokenAddress)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
TextField("Recipient (0x...)", text: $recipient)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
HStack {
TextField("Amount", text: $amount)
.textFieldStyle(.roundedBorder)
.keyboardType(.decimalPad)
TextField("Decimals", text: $decimals)
.textFieldStyle(.roundedBorder)
.keyboardType(.numberPad)
.frame(width: 80)
}
Button("Send Tokens") { sendTokens() }
.buttonStyle(.borderedProminent)
.disabled(isLoading)
if let hash = txHash {
Text("Success: \(hash)")
.font(.caption)
.foregroundColor(.green)
}
if let error = error {
Text(error).foregroundColor(.red).font(.caption)
}
}
.padding()
}
private func sendTokens() {
isLoading = true
error = nil
Task { @MainActor in
do {
let tokenDecimals = Int(decimals) ?? 18
let baseUnits = try parseDecimalToBaseUnits(amount, decimals: tokenDecimals)
// 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: "transfer",
args: [recipient, baseUnits]
)
txHash = try await DynamicSDK.instance().evm.writeContract(
wallet: wallet,
input: input
)
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
}
private func parseDecimalToBaseUnits(_ value: String, decimals: Int) throws -> BigUInt {
let parts = value.split(separator: ".", omittingEmptySubsequences: false)
let whole = BigUInt(String(parts[0])) ?? 0
let fracRaw = parts.count == 2 ? String(parts[1]) : ""
let fracPadded = String(fracRaw.prefix(decimals)).padding(toLength: decimals, withPad: "0", startingAt: 0)
let frac = BigUInt(fracPadded) ?? 0
return whole * BigUInt(10).power(decimals) + frac
}
}
Common Token Decimals
| Token | Decimals | Notes |
|---|---|---|
| ETH, WETH | 18 | Most ERC-20 tokens use 18 |
| USDC | 6 | Circle’s USD Coin |
| USDT | 6 | Tether USD |
| WBTC | 8 | Wrapped Bitcoin |
| DAI | 18 | MakerDAO stablecoin |
Best Practices
1. Validate Token Address
Always validate the token contract address before sending:Copy
Ask AI
func isValidEthereumAddress(_ address: String) -> Bool {
let pattern = "^0x[a-fA-F0-9]{40}$"
guard let regex = try? NSRegularExpression(pattern: pattern) else {
return false
}
let range = NSRange(location: 0, length: address.utf16.count)
return regex.firstMatch(in: address, range: range) != nil
}
2. Check Token Balance Before Transfer
Copy
Ask AI
// Query token balance using balanceOf(address)
func getTokenBalance(
wallet: BaseWallet,
tokenAddress: String,
chainId: Int
) async throws -> BigUInt {
let client = sdk.evm.createPublicClient(chainId: chainId)
// Use readContract to call balanceOf
let balance = try await client.readContract(
address: tokenAddress,
functionName: "balanceOf",
args: [wallet.address]
)
return BigUInt(balance) ?? 0
}
3. Use Appropriate Gas Limits
Copy
Ask AI
struct GasLimits {
static let erc20Transfer = 65_000 // ERC-20 token transfer
static let erc20Approve = 50_000 // ERC-20 approve
}
Next Steps
- Send ETH Transactions - Basic ETH transfers
- Smart Contract Interactions - Advanced contract calls
- Gas Management - Optimize gas usage
- Token Balances - Query token balances