Intro
I will first explain the steps that you need to do to get the list of token holders. This applies to any language so you can recreate this on your own if you don't use Python. Then I will present my opinionated solution in Python that connects to Infura and explain how it works.
My approach gets you the exact same result as Etherescan's token holders page.
Steps
To get the list of token holders, you will do:
1. Get all the Transfer
events from your contract.
For that you need your contract address. Go to Etherscan Tokens page and search for your token, mine was this one so my address is 0x83e2be8d114f9661221384b3a50d24b96a5653f5
.
A short lesson in Ethereum: every ERC20 compatible token should emit a Transmit
event (also called a log) whenever this token is sent from one address to another (example).
You can search for all the Transmit
events through the JSON RPC interface of any Ethereum node that you have access to. Since I don't currently run an Ethereum node, I connected to a node from Infura. You search for logs by using the eth_getlogs
RPC function.
When you search for logs, you don't want all the logs, you only want the Transmit
logs. The magic string here is 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
so you filter all the logs by this topic. Why exactly this string is a bit out of scope for this, but you can read more about events and topics here in the Solidity Docs and in this tutorial that shows how this magic string is done.
To speed up searching it's best if you limit your search to blocks that happened when the contract was deployed or later. For me that was block 5857657
, which translates to hex 0x596179
.
Finally, I can call the RPC method eth_getLogs
on Infura using the parameters contract address
, fromBlock
and topics
.
2. Parse the events
Ethereum's logs are not straightforward so we need to parse them. If you check one log, there is information about the from address, to address and the token amount, but it's all encoded so you need to know where to look. Check this log entry for example:
{'address': '0x83e2be8d114f9661221384b3a50d24b96a5653f5',
'blockHash': '0x2b25a69098c037c0a49b43697f8dbf2eede8ce4dabdf8585530cad952a521cb3',
'blockNumber': '0x5a193b',
'data': '0x00000000000000000000000000000000000000000000023934c5a09da1900000',
'logIndex': '0x52',
'removed': False,
'topics': ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
'0x000000000000000000000000c1678c7d5f4d01d30ad72bc5848bb7dfb248b5b9',
'0x000000000000000000000000b8ca929e2bd96548cabcabc56cfc9a5147cef0ff'],
'transactionHash': '0x9c85f209b321369f15da6ffef9d0bedeffaab18d7063901197b8f89ffdbe3813',
'transactionIndex': '0x33'}
The from address is the second topic and the to address is the third topic. The amount of tokens transferred is in data
. Again, you'll have to refer to how Ethereum handles topics and data to understand why this is and how it relates to the Transmit
event.
You parse it like this:
* Hex decode the data
to get the amount of tokens transferred. Note that you'll have to move the decimal point by the decimals
of the token, in my case 18.
* To get the from and to addresses just take the last 40 characters of a topic and prepend it with 0x
.
3. Compute balances
Make a list of all the addresses found in these events and process all the events by adding the token amount to the to
address and subtracting it from the from
address. At last just throw out all the addresses with a zero balance.
Python solution
My Python solution is here. A few comments:
- I'm using the
requests
library to call the JSON RPC on Infura.
- I'm using
Decimal
because it's better than float
for financial computation.
- I call it like this:
from ethereum import *
contract = "0x83e2be8d114f9661221384b3a50d24b96a5653f5"
transfers = get_contract_transfers(contract, from_block="0x596179")
balances = get_balances_list(transfers)