5

I working on a Ethereum project and I would like to gather a list of top X (20 for example) Ethereum accounts. This would behave similar to etherscan.io (https://etherscan.io/accounts)

Is there an API that does this ? If not does anyone know of a way to accomplish this. Im curious how etherscan accomplished this. I looked at their and there is no endpoint that behaves like it.

I would like to do this for other ERC20 tokens as well.

3
6

I don't know of any APIs that will achieve what you want.

If not does anyone know of a way to accomplish this?

Here's some fairly dumb Python code that scrapes that page and writes it to a .csv file:

#!/usr/bin/env python

import requests
from bs4 import BeautifulSoup
import csv

URL = "https://etherscan.io/accounts"
resp = requests.get(URL)
sess = requests.Session()
soup = BeautifulSoup(sess.get(URL).text, 'html.parser')

with open('output.csv', 'wb') as f:
    wr = csv.writer(f, quoting=csv.QUOTE_ALL)
    wr.writerow(map(str, "Rank Address Balance Percentage TxCount".split()))

    for tr in soup.find_all('tr'):
        tds = tr.find_all('td')
        rows = [0] * len(tds)
        for i in xrange(len(tds)):
            rows[i] = tds[i].get_text()

        try:
            wr.writerow(rows)
        except:
            # The page contains another table that we're
            # not worried about but which contains special 
            # characters...
            pass

Im curious how etherscan accomplished this.

Probably by parsing the state data and creating their own internal representation of it. This would then allow them to manipulate and present it in any way they like.

I would like to do this for other ERC20 tokens as well.

Here's a slightly more complicated script (that I wrote a while ago) that lists all addresses and balances - in rank order - associated with a given token contract, across multiple Etherscan pages. You can poke around with it to suit your needs. (There's an example contract address currently hard-coded into it.)

#!/usr/bin/env python

from __future__ import print_function
import os
import requests
from bs4 import BeautifulSoup
import csv
import time

RESULTS = "results.csv"
URL = "https://etherscan.io/token/generic-tokenholders2?a=0x6425c6be902d692ae2db752b3c268afadb099d3b&s=0&p="

def getData(sess, page):
    url = URL + page
    print("Retrieving page", page)
    return BeautifulSoup(sess.get(url).text, 'html.parser')

def getPage(sess, page):
    table = getData(sess, str(int(page))).find('table')
    try:
        data = [[X.text.strip() for X in row.find_all('td')] for row in table.find_all('tr')]
    except:
        data = None
    finally:
        return data

def main():
    resp = requests.get(URL)
    sess = requests.Session()

    with open(RESULTS, 'wb') as f:
        wr = csv.writer(f, quoting=csv.QUOTE_ALL)
        wr.writerow(map(str, "Rank Address Quantity Percentage".split()))
        page = 0
        while True:
            page += 1
            data = getPage(sess, page)

            if data == None:
                break
            else:
                for row in data:
                    wr.writerow(row)
                time.sleep(1)

if __name__ == "__main__":
    main()
2
  • First script works ok, but only gets data from 1st page, 2nd script seems to be able to change pages, but it doesn't work at all (empty csv). The third script (ethereum.py) doesn't write a – Andrew Voronkov May 28 '19 at 14:33
  • I am genuinely interested in how you found the url "etherscan.io/token/…" ? this is a gem – pcko1 Dec 13 '20 at 22:30
6

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)
1
  • 1
    This is really interesting. I did not know of Infura before this post. I can't wait to play with this solution this weekend! Thank you. Once I dive in I'll probably have a lot of questions. Thanks again! – Rstack Aug 9 '18 at 21:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.