Skip to main content

Overview

Read from and write to smart contracts on EVM chains using the WriteContractInput API.

Prerequisites

Write to a Contract

import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import com.dynamic.sdk.android.Chains.EVM.WriteContractInput
import org.json.JSONArray
import org.json.JSONObject

val sdk = DynamicSDK.getInstance()

suspend fun writeContract(
    wallet: BaseWallet,
    contractAddress: String,
    abi: List<Map<String, Any>>,
    functionName: String,
    args: List<Any>
) {
    try {
        val input = WriteContractInput(
            address = contractAddress,
            abi = abi,
            functionName = functionName,
            args = args
        )

        val txHash = sdk.evm.writeContract(wallet, input)
        println("Transaction hash: $txHash")
    } catch (e: Exception) {
        println("Contract write failed: ${e.message}")
    }
}

Common Contract Patterns

Minting NFTs

suspend fun mintNFT(
    wallet: BaseWallet,
    nftContractAddress: String,
    recipientAddress: String
) {
    val abi = parseAbiJson("""
    [
      {
        "inputs": [
          { "internalType": "address", "name": "to", "type": "address" }
        ],
        "name": "mint",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
      }
    ]
    """)

    val input = WriteContractInput(
        address = nftContractAddress,
        abi = abi,
        functionName = "mint",
        args = listOf(recipientAddress)
    )

    val txHash = sdk.evm.writeContract(wallet, input)
    println("NFT minted! Transaction: $txHash")
}

Staking Tokens

suspend fun stakeTokens(
    wallet: BaseWallet,
    stakingContractAddress: String,
    amount: BigInteger
) {
    val abi = parseAbiJson("""
    [
      {
        "inputs": [
          { "internalType": "uint256", "name": "amount", "type": "uint256" }
        ],
        "name": "stake",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
      }
    ]
    """)

    val input = WriteContractInput(
        address = stakingContractAddress,
        abi = abi,
        functionName = "stake",
        args = listOf(amount.toString())
    )

    val txHash = sdk.evm.writeContract(wallet, input)
    println("Tokens staked! Transaction: $txHash")
}

Swapping Tokens (DEX)

suspend fun swapTokens(
    wallet: BaseWallet,
    dexContractAddress: String,
    tokenIn: String,
    tokenOut: String,
    amountIn: BigInteger,
    minAmountOut: BigInteger,
    deadline: Long
) {
    val abi = parseAbiJson("""
    [
      {
        "inputs": [
          { "internalType": "address", "name": "tokenIn", "type": "address" },
          { "internalType": "address", "name": "tokenOut", "type": "address" },
          { "internalType": "uint256", "name": "amountIn", "type": "uint256" },
          { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" },
          { "internalType": "uint256", "name": "deadline", "type": "uint256" }
        ],
        "name": "swap",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
      }
    ]
    """)

    val input = WriteContractInput(
        address = dexContractAddress,
        abi = abi,
        functionName = "swap",
        args = listOf(
            tokenIn,
            tokenOut,
            amountIn.toString(),
            minAmountOut.toString(),
            deadline.toString()
        )
    )

    val txHash = sdk.evm.writeContract(wallet, input)
    println("Tokens swapped! Transaction: $txHash")
}

Read from a Contract

import com.dynamic.sdk.android.Chains.EVM.ReadContractInput

suspend fun readContract(
    wallet: BaseWallet,
    contractAddress: String,
    abi: List<Map<String, Any>>,
    functionName: String,
    args: List<Any> = emptyList()
): Any {
    val input = ReadContractInput(
        address = contractAddress,
        abi = abi,
        functionName = functionName,
        args = args
    )

    return sdk.evm.readContract(wallet, input)
}

// Example: Get token balance
suspend fun getBalance(
    wallet: BaseWallet,
    tokenAddress: String,
    ownerAddress: String
): String {
    val abi = parseAbiJson("""
    [
      {
        "inputs": [
          { "internalType": "address", "name": "account", "type": "address" }
        ],
        "name": "balanceOf",
        "outputs": [
          { "internalType": "uint256", "name": "", "type": "uint256" }
        ],
        "stateMutability": "view",
        "type": "function"
      }
    ]
    """)

    val result = readContract(
        wallet = wallet,
        contractAddress = tokenAddress,
        abi = abi,
        functionName = "balanceOf",
        args = listOf(ownerAddress)
    )

    return result.toString()
}

ABI Parsing Helper

object AbiParser {
    fun parseAbiJson(abiString: String): List<Map<String, Any>> {
        val jsonArray = JSONArray(abiString)
        val result = mutableListOf<Map<String, Any>>()

        for (i in 0 until jsonArray.length()) {
            val jsonObj = jsonArray.getJSONObject(i)
            result.add(jsonObjectToMap(jsonObj))
        }

        return result
    }

    private fun jsonObjectToMap(jsonObj: JSONObject): Map<String, Any> {
        val map = mutableMapOf<String, Any>()
        val keys = jsonObj.keys()

        while (keys.hasNext()) {
            val key = keys.next()
            val value = jsonObj.get(key)
            map[key] = jsonValueToAny(value)
        }

        return map
    }

    private fun jsonValueToAny(value: Any): Any {
        return when (value) {
            is JSONObject -> jsonObjectToMap(value)
            is JSONArray -> {
                val list = mutableListOf<Any>()
                for (i in 0 until value.length()) {
                    list.add(jsonValueToAny(value.get(i)))
                }
                list
            }
            JSONObject.NULL -> "null"
            else -> value
        }
    }
}

// Usage
val abi = AbiParser.parseAbiJson(abiJsonString)

Best Practices

  • Validate contract addresses before calling
  • Handle ABI parsing errors gracefully
  • Cache ABIs to avoid repeated parsing
  • Verify argument types match ABI
  • Test contracts on testnet first

Error Handling

sealed class ContractError {
    data class InvalidAbi(val message: String) : ContractError()
    data class InvalidAddress(val message: String) : ContractError()
    data class ExecutionFailed(val message: String) : ContractError()
    data class InsufficientGas(val message: String) : ContractError()
    data class Unknown(val message: String) : ContractError()
}

fun parseContractError(e: Exception): ContractError {
    val errorMessage = e.message?.lowercase() ?: ""

    return when {
        errorMessage.contains("abi") || errorMessage.contains("parse") ->
            ContractError.InvalidAbi("Invalid or malformed ABI")
        errorMessage.contains("address") ->
            ContractError.InvalidAddress("Invalid contract address")
        errorMessage.contains("gas") ->
            ContractError.InsufficientGas("Insufficient gas for execution")
        errorMessage.contains("execution") || errorMessage.contains("revert") ->
            ContractError.ExecutionFailed("Contract execution failed: ${e.message}")
        else ->
            ContractError.Unknown("Operation failed: ${e.message}")
    }
}

// Usage in ViewModel
try {
    val txHash = sdk.evm.writeContract(wallet, input)
    _transactionHash.value = txHash
} catch (e: Exception) {
    val error = parseContractError(e)
    _errorMessage.value = when (error) {
        is ContractError.InvalidAbi -> error.message
        is ContractError.InvalidAddress -> error.message
        is ContractError.ExecutionFailed -> error.message
        is ContractError.InsufficientGas -> error.message
        is ContractError.Unknown -> error.message
    }
}

Troubleshooting

Invalid ABI: Ensure JSON is properly formatted with quotes and commas Type mismatch: Pass address/uint256 as String, bool as Boolean, bytes as hex String Contract reverts: Check requirements, permissions, input ranges, and contract state

What’s Next