Building an Ethereum Android Wallet: Fetching ETH and Token Balances

·

This article continues our series on developing an Ethereum Android wallet, focusing on retrieving account balance information for both Ether and ERC-20 tokens. We'll explore key architectural components and implementation details.

Prerequisites and Architecture Overview

In the previous installment, we introduced a TokensViewModel to decouple the UI from data operations. Let's revisit its structure:

public class TokensViewModel extends ViewModel {
    private final MutableLiveData<ETHWallet> defaultWallet;
    private final MutableLiveData<NetworkInfo> defaultNetwork;
    private final MutableLiveData<Token[]> tokens;
    private final MutableLiveData<Ticker> prices;
}

The remaining three variables include:

Understanding Ethereum Networks

Supported Networks

Ethereum operates multiple networks, each serving different purposes:

Additionally, most wallets support local development networks.

Network Information Structure

The application represents networks using a NetworkInfo class:

public class NetworkInfo {
    public final String name;          // Network identifier (e.g., "mainnet")
    public final String symbol;        // Native currency symbol
    public final String rpcServerUrl;  // Blockchain node endpoint
    public final String backendUrl;    // Transaction history service
    public final String etherscanUrl;  // Block explorer URL
    public final int chainId;          // Network chain ID
    public final boolean isMainNetwork;
}

Network configurations are typically stored in a repository class, containing RPC endpoints and related services.

Network Selection Implementation

The wallet persists the user's network preference in SharedPreferences. The application retrieves this preference and matches it against supported networks to determine the active configuration.

Differentiating Between Native Currency and Tokens

Ethereum Account Model

Ethereum's native currency (ETH) exists within the base account structure:

class Account {
    nonce: '0x01',
    balance: '0x03e7',    // Wei balance
    stateRoot: '0x56abc...',
    codeHash: '0x56abc...',
}

Retrieving ETH balance requires calling the eth_getBalance JSON-RPC method.

ERC-20 tokens, however, store balances within smart contract state. Each token contract maintains a balanceOf mapping that tracks individual address holdings. Retrieving token balances requires calling the contract's balanceOf method with the target address as parameter.

Unified Token Representation

The wallet represents both native currency and tokens using a unified Token class:

public class Token {
    public final TokenInfo tokenInfo;
    public final String balance;    // Token balance
    public String value;            // Fiat value equivalent
}

public class TokenInfo {
    public final String address;    // Contract address (empty for ETH)
    public final String name;
    public final String symbol;
    public final int decimals;
}

This approach simplifies handling throughout the application while maintaining necessary distinctions.

Retrieving Account Assets

Managing Token Associations

The wallet maintains a database of token associations for each account-network combination using Realm mobile database. Users add new tokens through the UI, which persists them to the local database.

The token retrieval process follows this sequence:

  1. ViewModel initiates fetch request
  2. Interactor coordinates the operation
  3. Repository manages data sources
  4. Local source returns persisted tokens
  5. Results propagate back to ViewModel

External API Integration

For mainnet operations, the wallet integrates with Ethplorer API to discover tokens associated with an address. The getAddressInfo endpoint provides comprehensive token information:

/getAddressInfo/0xaccount?apiKey=freekey

👉 Explore advanced API integration techniques

Note that free API tiers typically enforce rate limits (e.g., one request per two seconds) that must be respected in production applications.

Balance Retrieval Implementation

Fetching Native ETH Balance

Retrieving Ethereum balance involves two primary steps:

  1. Web3j Client Initialization:

    web3j = Web3j.build(new HttpService(networkInfo.rpcServerUrl, httpClient, false));
  2. Balance Query Execution:

    private BigDecimal getEthBalance(String walletAddress) throws Exception {
     return new BigDecimal(web3j
         .ethGetBalance(walletAddress, DefaultBlockParameterName.LATEST)
         .send()
         .getBalance());
    }

Retrieving ERC-20 Token Balances

For token contracts, balance retrieval requires calling the balanceOf function:

  1. Function Encoding:

    private static org.web3j.abi.datatypes.Function balanceOf(String owner) {
     return new org.web3j.abi.datatypes.Function(
         "balanceOf",
         Collections.singletonList(new Address(owner)),
         Collections.singletonList(new TypeReference<Uint256>() {}));
    }
  2. Contract Call Execution:

    private BigDecimal getBalance(String walletAddress, TokenInfo tokenInfo) throws Exception {
     org.web3j.abi.datatypes.Function function = balanceOf(walletAddress);
     String responseValue = callSmartContractFunction(function, tokenInfo.address, walletAddress);
     
     List<Type> response = FunctionReturnDecoder.decode(
         responseValue, function.getOutputParameters());
     
     if (response.size() == 1) {
         return new BigDecimal(((Uint256) response.get(0)).getValue());
     } else {
         return null;
     }
    }

The callSmartContractFunction method constructs an ethCall transaction with encoded function data and executes it against the blockchain node.

Balance Formatting and Display

Raw balance values are returned in smallest units (wei for ETH, base units for tokens). Conversion to display units requires division by 10^decimals:

BigDecimal decimalDivisor = new BigDecimal(Math.pow(10, decimals));
BigDecimal ethbalance = balance.divide(decimalDivisor);

The formatted balance typically displays with four decimal places:

ethBalance.setScale(4, RoundingMode.CEILING).toPlainString()

The UI observes LiveData objects in the ViewModel to automatically update when balance information changes.

Frequently Asked Questions

What's the difference between ETH balance and token balance retrieval?
ETH balances are stored directly in account objects and retrieved via JSON-RPC calls. Token balances are stored in smart contract state and require calling contract methods with encoded function data.

How often should I update balance information?
For active accounts, consider updating every 15-30 seconds. For less active accounts, reduce frequency to conserve resources. Always implement appropriate caching strategies.

Why does my balance query return zero for known tokens?
This typically indicates either network connectivity issues, incorrect contract addresses, or the token using a different standard than ERC-20. Verify contract addresses and network compatibility.

How can I handle custom token decimals?
Always retrieve and use the decimals value from the token contract rather than assuming standard values. This ensures proper formatting across diverse tokens.

What's the best practice for error handling in balance queries?
Implement retry mechanisms with exponential backoff, provide user feedback for failed queries, and consider caching previous successful responses for fallback display.

How do I test balance functionality on test networks?
Use faucets to obtain test ETH, deploy test token contracts, and verify functionality across multiple networks before deploying to production.

👉 Discover comprehensive blockchain development resources

Conclusion

Retrieving Ethereum and token balances requires understanding different data sources and appropriate interaction methods. Native currency balances are directly available through JSON-RPC, while token balances require smart contract interactions. Implementing proper error handling, rate limiting, and user feedback creates a robust balance management system for Ethereum wallets.

The architecture presented demonstrates separation of concerns through ViewModel pattern, proper data source abstraction, and efficient blockchain interaction methods. These principles apply equally to Ethereum and other EVM-compatible blockchains.