> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# ERC-20 Token Transfers

> Send ERC-20 tokens with the Dynamic Android SDK.

## Overview

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

## Prerequisites

* Dynamic SDK initialized (see [Installation Guide](/kotlin/quickstart))
* User authenticated (see [Authentication Guide](/kotlin/authentication))
* Wallets available (see [Wallet Creation](/kotlin/wallet-creation))
* Understanding of smart contracts (see [Smart Contracts](/kotlin/wallets/evm/smart-contracts))

## Send ERC-20 Token

```kotlin theme={"system"}
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)

```kotlin theme={"system"}
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)

```kotlin theme={"system"}
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)

```kotlin theme={"system"}
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

```kotlin theme={"system"}
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

```kotlin theme={"system"}
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

```kotlin theme={"system"}
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

* [Smart Contract Interactions](/kotlin/wallets/evm/smart-contracts) - Advanced contract interactions
* [Gas Management](/kotlin/wallets/evm/gas-management) - Optimize gas usage
* [Send ETH Transactions](/kotlin/wallets/evm/send-eth) - Send native ETH
* [Message Signing](/kotlin/wallets/evm/message-signing) - Sign messages with wallets
