0

I know it is easy to lookup the token balance of a particular Ethereum address (e.g. etherscan or via code such as this stackexchange question).

If I want to find a wallet with a particular ERC-20 token balance at a particular point in time is there a way to do this? In other words, is it possible to find a wallet that either currently has (a more simple case perhaps) or at a point in time that can be specified had a certain ERC-20 balance.

For example can I identify an address (or list of addresses) that has/have had:

KNC 100.01

Either currently or at a particular point in time?

And just to be clear, the unknown would be the address. The balance would be known for a particular token.

7
  • You can get a snapshot of all addresses holding a specific token at a specific point in time, using the Transfer events emitted on that token's contract, starting from the moment it was deployed and up until your specific point in time. You can do it either via your own node if you have one, or a public node (for example, Infura), or Etherscan's developer API. Commented Oct 16, 2020 at 13:20
  • I so excited by your answer, it will take me a bit of time to work through, but it looks like this is going to be beyond my wildest expectations!!! Thank you so much. And I will add further comments and upvotes etc. once can get my head around it and confirm it works.
    – Joey
    Commented Oct 17, 2020 at 4:48
  • NP, note that once you input a large (more recent) value for DESIRED_BLOCK, the script in my answer (below) may take a very long time to complete, since there have been many Transfer events on this token. Commented Oct 17, 2020 at 7:16
  • Thank you very much indeed, I tried to upvote your answer and comments, but I don't have the "points" to be allowed that yet, but will as soon as I am. I am trying to work through this, I have registered for an API key with etherscan and am trying to see where I would enter the javascript(?) code you have created, they have html links that can be used. I am not sure where I would run this. If you could give me a hint it would be awesome.
    – Joey
    Commented Oct 17, 2020 at 14:20
  • You don't need to do anything on etherscan to run my code. Using etherscan API was just one of the options that I suggested in my first comment, but my actual answer below doesn't make any use of it. You simply need to get an access to an Ethereum node (for example, Infura), put in in my suggested script and run it via NodeJS. Commented Oct 17, 2020 at 14:30

1 Answer 1

1

You can construct a snapshot of all addresses holding a specific token at a specific point in time, using the Transfer events emitted on that token's contract, starting from the moment it was deployed and up until the desired point in time.

For example, in order to construct a snapshot of the KNC token at block #4270000, you can do:

const Web3 = require("web3");
const Decimal = require("decimal.js");
Decimal.set({precision: 80, rounding: Decimal.ROUND_DOWN});

const NODE_ADDRESS  = "YourNodeAddress";
const TOKEN_ADDRESS = "0xdd974d5c2e2928dea5f71b9825b8b646686bd200";
const GENESIS_BLOCK = 4264898;
const DESIRED_BLOCK = 4270000;
const TOKEN_ABI = [
    {"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},
    {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}
];

async function getPastEvents(contract, eventName, fromBlock, toBlock) {
    if (fromBlock <= toBlock) {
        try {
            return await contract.getPastEvents(eventName, {fromBlock: fromBlock, toBlock: toBlock});
        }
        catch (error) {
            const midBlock = (fromBlock + toBlock) >> 1;
            const arr1 = await getPastEvents(contract, eventName, fromBlock, midBlock);
            const arr2 = await getPastEvents(contract, eventName, midBlock + 1, toBlock);
            return [...arr1, ...arr2];
        }
    }
    return [];
}

async function run() {
    const web3 = new Web3(NODE_ADDRESS);
    const contract = new web3.eth.Contract(TOKEN_ABI, TOKEN_ADDRESS);
    const events = await getPastEvents(contract, "Transfer", GENESIS_BLOCK, DESIRED_BLOCK);
    const decimals = await contract.methods.decimals().call();

    const balances = {};

    for (const event of events) {
        const src = Web3.utils.toChecksumAddress(event.returnValues._from);
        const dst = Web3.utils.toChecksumAddress(event.returnValues._to);
        const val = Web3.utils.toBN(event.returnValues._value);
        balances[src] = src in balances ? balances[src].sub(val) : val.neg();
        balances[dst] = dst in balances ? balances[dst].add(val) : val;
    }

    for (const [address, balance] of Object.entries(balances)) {
        if (balance.gtn(0)) {
            console.log(address + " owns " + Decimal(balance.toString()).div(`1e${decimals}`).toFixed() + " tokens");
        }
    }
}

run();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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