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:
tokens: Array of tokens owned by the current accountdefaultNetwork: Currently selected blockchain networkprices: Token pricing data (covered in future articles)
Understanding Ethereum Networks
Supported Networks
Ethereum operates multiple networks, each serving different purposes:
- Mainnet: Production network with real economic value
- Ropsten: Proof-of-Work test network
- Kovan: Proof-of-Authority test network (Parity clients)
- Rinkeby: Proof-of-Authority test network (Geth clients)
- Goerli: Cross-client test network for Ethereum 2.0 development
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:
- ViewModel initiates fetch request
- Interactor coordinates the operation
- Repository manages data sources
- Local source returns persisted tokens
- 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:
Web3j Client Initialization:
web3j = Web3j.build(new HttpService(networkInfo.rpcServerUrl, httpClient, false));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:
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>() {})); }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.