Token holders are not directly available through the RPC protocol and RPC wrappers such as Web3.
Information about token holders is stored on the blockchain in the token contract (or some of its dependencies), usually in the form of a mapping. Which means that you can't just loop through all of the holders, but you need to know the address and ask for their balance.
// the key is the holder address, the value is their token amount
mapping (address => uint256) public balanceOf;
But - the ERC-20 standard defines the Transfer()
event that the token contract should emit when a transfer occurs.
mapping (address => uint256) public balanceOf;
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
function transfer(address _to, uint256 _amount) external returns (bool) {
balanceOf[msg.sender] -= _amount;
balanceOf[_to] += _amount;
emit Transfer(msg.sender, _to, _amount);
return true;
}
So you'll need to build and maintain a database of holders from all Transfer()
event logs emitted by this token contract. Collect past event logs to build the historic data, and subscribe to newly emitted logs to keep it up-to-date. Then you can aggregate all of this raw transfer data to the form of "address => current balance" and filter only addresses that have non-zero balance in your searchable DB.
Docs:
- Get past event logs in Web3 - link
- Subscribe to new event logs in Web3 - link
The same way is actually used by blockchain explorers. They scan each transaction for Transfer()
events and if the emitter is a token contract, they update the token balances in their separate DB. The list of all holders (from this separate DB) is then displayed on the token detail page.