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