Technical Details
This page covers technical specifications for working with ERC token data on Hedera, including account identifiers, event signatures, and data formats.
Data Pipeline Overview
The Hedera ERC Indexer processes token data through a six-stage pipeline:
Key Pipeline Stages
- Discovery: Scans Hedera mirror node for contracts emitting Transfer events
- Extraction: Retrieves token metadata (name, symbol, decimals) via JSON-RPC
- Balance Fetching: Fetches current balances via RPC
balanceOf()calls - Transfer History: Extracts and indexes Transfer events to tables
- NFT Tracking: For ERC-721 tokens, tracks individual NFT ownership and metadata
- Storage: Persists data to
ercschema tables exposed via Hasura GraphQL
Account Identifiers and Aliases
Understanding Hedera Account Systems
Every entity on Hedera has a unique account ID (e.g., 0.0.12345), but accounts can be referenced through multiple identifiers:
1. Account Number (Native Hedera Format)
- Format:
<shard>.<realm>.<account_number>(e.g., 0.0.924713) - Usage: Native Hedera transactions and queries
- In Topics: Stored as 1-8 bytes depending on size
2. Account-num Alias (Long-zero Address)
- Format: 20-byte EVM address with 12 leading zeros
- Example:
0x00000000000000000000000000000e1c29(Account 924713) - Purpose: Makes every Hedera account EVM-compatible
- Resolution: Direct calculation from bytes (no lookup needed)
3. ECDSA EVM Alias
- Format: Standard 20-byte Ethereum address
- Example:
0x6d9f1a927cbcb5e2c28d13ca735bc6d6131406da - Purpose: Allows MetaMask and EVM wallets to interact with Hedera
- Resolution: Requires lookup to map to Hedera account ID
Hollow Accounts and Auto-Creation
When an EVM address receives tokens or HBAR before having a Hedera account:
- Hollow Account Creation: System automatically creates an incomplete account
- Properties: Has account ID and alias, but no key (can receive but not send)
- Completion: Becomes full account when used as fee payer with proper signature
- Important: Every hollow account still has a unique Hedera ID
Event Detection & Signatures
Transfer Event
All ERC-20 and ERC-721 tokens emit a standardized Transfer event:
event Transfer(address indexed from, address indexed to, uint256 value_or_tokenId)
Event Signature Hash:
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
This signature is the keccak256 hash of the event declaration and is always the same for all ERC Transfer events.
Approval Event
event Approval(address indexed owner, address indexed spender, uint256 value)
Event Signature Hash:
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
ERC-1400 Events
Security tokens use partition-based events for transfers:
| Event | Signature Hash |
|---|---|
| IssuedByPartition | 0x5af1c8f424b104b6ba4e3c0885f2ed9fef04a9b1ea39cd9ed362432105c0791a |
| TransferByPartition | 0xff4e9a26af4eb73b8bacfaa4abd4fea03d9448e7b912dc5ff4019048875aa2d4 |
| RedeemedByPartition | 0xa4f62471c9bdf88115b97203943c74c59b655913ee5ee592706d84ef53fb6be2 |
Token Type Detection
The indexer distinguishes token types by analyzing event structure:
ERC-20 Detection
- Characteristic:
topic3 IS NULL - Reason: Transfer value is stored in the event's data section
- Structure: Transfer(from, to, amount) - amount goes in data section
ERC-721 Detection
- Characteristic:
topic3 IS NOT NULL - Reason: Unique tokenId is indexed and gets its own topic slot
- Structure: Transfer(from, to, tokenId) - tokenId becomes topic3
ERC-1400 Detection
- Method: RPC probing via
isIssuable()andisControllable() - Selectors:
0x2f1cae85(isIssuable),0x4c783bf5(isControllable) - Characteristic: Both methods must return
truefor positive detection - Note: ERC-165
supportsInterfaceis NOT used (returns false for valid ERC-1400 tokens)
Standard ERC Methods
ERC-20, ERC-721, and ERC-1400 tokens implement standard methods that the indexer queries:
Common Methods (All Standards)
function name() external view returns (string memory);
function symbol() external view returns (string memory);
ERC-20 Specific Methods
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
ERC-721 Specific Methods
function totalSupply() external view returns (uint256); // Optional
function ownerOf(uint256 tokenId) external view returns (address);
function tokenURI(uint256 tokenId) external view returns (string memory);
Reliability Scoring
The indexer calculates quality scores based on metadata completeness to help identify well-formed contracts.
Scoring Methodology
The metadata_reliability_score field indicates how many standard methods returned valid data:
ERC-20 Tokens (4 fields)
Fields checked: name, symbol, decimals, totalSupply
| Score | Fields Extracted | Meaning |
|---|---|---|
| 1.00 | 4 of 4 | Fully compliant ERC-20 |
| 0.75 | 3 of 4 | Mostly compliant |
| 0.50 | 2 of 4 | Partial implementation |
| 0.25 | 1 of 4 | Minimal implementation |
| 0.00 | 0 of 4 | No metadata available |
ERC-721 NFTs (3 fields)
Fields checked: name, symbol, totalSupply (decimals not applicable)
| Score | Fields Extracted | Meaning |
|---|---|---|
| 1.00 | 3 of 3 | Fully compliant ERC-721 |
| 0.67 | 2 of 3 | Mostly compliant |
| 0.33 | 1 of 3 | Minimal implementation |
| 0.00 | 0 of 3 | No metadata available |
ERC-1400 Security Tokens (4 fields)
Fields checked: name, symbol, decimals, totalSupply (same as ERC-20)
| Score | Fields Extracted | Meaning |
|---|---|---|
| 1.00 | 4 of 4 | Fully compliant ERC-1400 |
| 0.75 | 3 of 4 | Mostly compliant |
| 0.50 | 2 of 4 | Partial implementation |
| 0.25 | 1 of 4 | Minimal implementation |
| 0.00 | 0 of 4 | No metadata available |
Unknown/Failed Deployments
For contracts that couldn't be identified as ERC-20 or ERC-721:
| Score | Fields Extracted | Meaning |
|---|---|---|
| 1.00 | name AND symbol | Basic token interface |
| 0.50 | name OR symbol | Partial interface |
| 0.00 | Neither | No standard interface |
Using Reliability Scores
Filter for high-quality tokens in your queries:
query ReliableTokens {
erc_token(
where: {
metadata_reliability_score: { _gte: 0.75 }
contract_type: { _in: ["ERC_20", "ERC_721"] }
}
) {
name
symbol
metadata_reliability_score
}
}
Data Encoding Formats
Address Encoding
Addresses in the GraphQL API are always returned as strings in hex format with 0x prefix:
- EVM Addresses:
0x+ 40 hex characters - Example:
0x00000000000000000000000000000000002cc823
Numeric Values
Large numbers (balances, token supplies) are returned as strings to preserve precision:
- Format: Decimal string representation
- Example:
"1000000000000000000"(1 token with 18 decimals) - Max Value: Full uint256 range (2^256 - 1)
Timestamps
All timestamps use nanoseconds since epoch (Hedera format):
| Field Type | Example | Description |
|---|---|---|
| created_timestamp | 1698765432100000000 | Entity creation time |
| balance_timestamp | 1698765432100000000 | Last balance update |
| processing_timestamp | 1698765432100000000 | Record processing time |
| transfers_indexed_timestamp | 1698765432100000000 | Last transfer indexed |
| consensus_timestamp | 1698765432100000000 | Transaction time |
Working with Token Balances
ERC-20 Balances
Balances are stored in the smallest unit (wei):
// Convert balance to human-readable format
function formatBalance(balance, decimals) {
return (BigInt(balance) / BigInt(10 ** decimals)).toString();
}
// Example: 1000000000000000000 with 18 decimals = 1 token
ERC-721 Balances
For NFTs, the balance represents the count of NFTs owned:
query NFTCount($accountId: bigint!, $tokenId: bigint!) {
erc_token_account(
where: {
account_id: { _eq: $accountId }
token_id: { _eq: $tokenId }
}
) {
balance # This is the NFT count, not wei
}
}
Balance Fetching
Balances are fetched via RPC balanceOf() calls rather than calculated from Transfer events. This ensures accuracy for:
- Standard ERC-20: Exact match with on-chain state
- Rebasing tokens (aTokens): Accurate despite continuous interest accrual without Transfer events
- Debt tokens: Accurate despite interest changes
- ERC-1400: Standard
balanceOf()works for most implementations
Some ERC-1400 security tokens use partition-only balance storage, where balanceOf() returns 0 and balances are tracked per-partition via balanceOfByPartition(). These tokens will show zero balances in the erc_token_account table even when holders have positions. This is a limitation of the standard balanceOf() approach.
Process
- Account Discovery: Extract unique addresses from Transfer event topics
- Address Resolution: Resolve EVM addresses to Hedera account IDs (two-pass strategy)
- RPC Batch Calls: Fetch current balances via
balanceOf(address)method - Storage: Persist balances to
erc_token_accounttable
Why RPC Instead of Event Aggregation?
Event-based balance calculation (summing Transfer amounts) fails for:
- Rebasing tokens: Balance changes without Transfer events (e.g., Aave aTokens accrue interest continuously)
- Debt tokens: Interest accrues without events
- Fee-on-transfer tokens: Actual received amount differs from event amount
RPC balanceOf() queries the contract's current state directly, providing exact balances for all token types.