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();
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.DESIRED_BLOCK
, the script in my answer (below) may take a very long time to complete, since there have been manyTransfer
events on this token.