Skip to main content

Overview

Transfer ERC-20 tokens by interacting with token contracts and handling decimal conversions.

Prerequisites

Send ERC-20 Token

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

val sdk = DynamicSDK.getInstance()

suspend fun sendErc20Token(
    wallet: BaseWallet,
    tokenAddress: String,
    recipientAddress: String,
    amount: String,
    decimals: Int = 18
) {
    try {
        val abiList = parseAbiJson(Erc20.abi)
        val amountValue = BigDecimal(amount)
        val multiplier = BigDecimal.TEN.pow(decimals)
        val rawAmount = amountValue.multiply(multiplier).toBigInteger()

        val input = WriteContractInput(
            address = tokenAddress,
            abi = abiList,
            functionName = "transfer",
            args = listOf(recipientAddress, rawAmount.toString())
        )

        val txHash = sdk.evm.writeContract(wallet, input)
        println("ERC-20 transfer successful!")
        println("Transaction hash: $txHash")
    } catch (e: Exception) {
        println("Failed to send ERC-20: ${e.message}")
    }
}

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
}

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
}

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
    }
}

Common ERC-20 Tokens

USDC (6 decimals)

suspend fun sendUSDC(wallet: BaseWallet, recipientAddress: String, amount: String) {
    val usdcAddress = when (chainId) {
        1 -> "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
        137 -> "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
        else -> throw Exception("USDC not available on this network")
    }
    sendErc20Token(wallet, usdcAddress, recipientAddress, amount, decimals = 6)
}

USDT (6 decimals)

suspend fun sendUSDT(wallet: BaseWallet, recipientAddress: String, amount: String) {
    val usdtAddress = when (chainId) {
        1 -> "0xdAC17F958D2ee523a2206206994597C13D831ec7"
        137 -> "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
        else -> throw Exception("USDT not available on this network")
    }
    sendErc20Token(wallet, usdtAddress, recipientAddress, amount, decimals = 6)
}

DAI (18 decimals)

suspend fun sendDAI(wallet: BaseWallet, recipientAddress: String, amount: String) {
    val daiAddress = when (chainId) {
        1 -> "0x6B175474E89094C44Da98b954EedeAC495271d0F"
        137 -> "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063"
        else -> throw Exception("DAI not available on this network")
    }
    sendErc20Token(wallet, daiAddress, recipientAddress, amount, decimals = 18)
}

Read Token Information

Get Token Balance

import com.dynamic.sdk.android.Chains.EVM.ReadContractInput
import java.math.BigInteger

suspend fun getTokenBalance(
    wallet: BaseWallet,
    tokenAddress: String,
    decimals: Int = 18
): String {
    val abiList = parseAbiJson(Erc20.abi)

    val input = ReadContractInput(
        address = tokenAddress,
        abi = abiList,
        functionName = "balanceOf",
        args = listOf(wallet.address)
    )

    val result = sdk.evm.readContract(wallet, input)
    val balance = BigInteger(result.toString())
    val divisor = BigDecimal.TEN.pow(decimals)
    return BigDecimal(balance).divide(divisor).toPlainString()
}

Get Token Metadata

data class TokenMetadata(
    val name: String,
    val symbol: String,
    val decimals: Int,
    val totalSupply: String
)

suspend fun getTokenMetadata(
    wallet: BaseWallet,
    tokenAddress: String
): TokenMetadata {
    val abiList = parseAbiJson(Erc20.abi)
    val name = sdk.evm.readContract(wallet, ReadContractInput(tokenAddress, abiList, "name", emptyList())).toString()
    val symbol = sdk.evm.readContract(wallet, ReadContractInput(tokenAddress, abiList, "symbol", emptyList())).toString()
    val decimals = sdk.evm.readContract(wallet, ReadContractInput(tokenAddress, abiList, "decimals", emptyList())).toString().toInt()
    val totalSupply = sdk.evm.readContract(wallet, ReadContractInput(tokenAddress, abiList, "totalSupply", emptyList())).toString()
    return TokenMetadata(name, symbol, decimals, totalSupply)
}

Best Practices

  • Verify token address format before sending
  • Handle decimal conversion carefully using BigDecimal
  • Check token allowance before transfers that require approval
  • Validate recipient addresses
  • Check token balance before sending

Error Handling

sealed class Erc20Error {
    data class InsufficientBalance(val message: String) : Erc20Error()
    data class InvalidToken(val message: String) : Erc20Error()
    data class TransferFailed(val message: String) : Erc20Error()
    data class Unknown(val message: String) : Erc20Error()
}

fun parseErc20Error(e: Exception): Erc20Error {
    val errorMessage = e.message?.lowercase() ?: ""

    return when {
        errorMessage.contains("insufficient") || errorMessage.contains("balance") ->
            Erc20Error.InsufficientBalance("Insufficient token balance")
        errorMessage.contains("invalid") && errorMessage.contains("address") ->
            Erc20Error.InvalidToken("Invalid token contract address")
        errorMessage.contains("transfer") ->
            Erc20Error.TransferFailed("Token transfer failed")
        else ->
            Erc20Error.Unknown("Operation failed: ${e.message}")
    }
}

Troubleshooting

Transfer fails: Check token balance, verify token address, ensure recipient is valid, confirm network matches Decimal precision: Always use BigDecimal for calculations, never use Double

What’s Next