Track ERC-20 token transfers
In this tutorial, you'll track ERC-20 token transfers from a specific address using the Web3 JavaScript library.
Create a new directory for your project. This can be done from the command line:
mkdir trackERC20
Change into the new directory:
cd trackERC20
Install the
web3
package in the project directory:npm install web3
Create a file called
trackERC20.js
. At the top of file, add the following lines to import the web3.js library and connect to the Infura WebSocket endpoint:const Web3 = require('web3');
const web3 = new Web3('wss://mainnet.infura.io/ws/v3/<YOUR_API_KEY>');
Make sure to replace
<YOUR_API_KEY>
with your Infura API key.Define the ERC-20 ABI by adding the following to the script:
const abi = [
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
];
You can subscribe to the events that token contracts emit, allowing you to track every new token transfer as it occurs.
Add the following filter to the script, which tells the
web3.eth.subscribe
function in web3.js which events to track:let options = {
topics: [
web3.utils.sha3('Transfer(address,address,uint256)')
]
};
Then, initiate the subscription by passing along the filter:
let subscription = web3.eth.subscribe('logs', options);
You can also add the following lines to the script to see whether the subscription started successfully or if any errors occurred:
subscription.on('error', err => { throw err });
subscription.on('connected', nr => console.log('Subscription on ERC-20 started with ID %s', nr));
You can set the listener for the
subscription
created in step 5 by adding the following lines to the script:subscription.on('data', event => {
if (event.topics.length == 3) {
...
}
});
To verify that the
Transfer
event you catch is an ERC-20 transfer, these lines check to see whether the length of the topics
array equals 3. This is because ERC-721 events also emit a Transfer
event but contain four items instead.Because you can't read the event topics on their own, you must decode them using the ERC-20 ABI. Edit the listener as follows:
subscription.on('data', event => {
if (event.topics.length == 3) {
let transaction = web3.eth.abi.decodeLog([{
type: 'address',
name: 'from',
indexed: true
}, {
type: 'address',
name: 'to',
indexed: true
}, {
type: 'uint256',
name: 'value',
indexed: false
}],
event.data,
[event.topics[1], event.topics[2], event.topics[3]]);
}
});
You can now retrieve the sender address (
from
), receiving address (to
), and the number of tokens transferred (value
, though yet to be converted, see step 7) from the transaction
object.Even though you retrieve a
value
from the contract, this isn't the actual number of tokens transferred. ERC-20 tokens contain a decimal
value, which indicates the number of decimals a token should have. You can directly call the decimals
method of the smart contract to retrieve the decimal value, after which you can calculate the correct number of tokens sent.Outside the
subscription.on()
listener created in step 6, define a new method that allows you to collect more information from the smart contract:async function collectData(contract) {
const [decimals, symbol] = await Promise.all([
contract.methods.decimals().call(),
contract.methods.symbol().call()
]);
return { decimals, symbol };
}
Since you’re already requesting the
decimals
value from the contract, you can also request the symbol
value to display the ticker of the token.Inside the listener, call the
collectData
function every time a new ERC-20 transaction is found. You can also calculate the correct decimal value:subscription.on('data', event => {
if (event.topics.length == 3) {
let transaction = web3.eth.abi.decodeLog(
...
);
const contract = new web3.eth.Contract(abi, event.address)
collectData(contract).then(contractData => {
const unit = Object.keys(web3.utils.unitMap).find(key => web3.utils.unitMap[key] === web3.utils.toBN(10).pow(web3.utils.toBN(contractData.decimals)).toString());
console.log(`Transfer of ${web3.utils.fromWei(transaction.value, unit)} ${contractData.symbol} from ${transaction.from} to ${transaction.to}`)
})
}
});
You can track a specific sender address by reading the
from
value of the decoded transaction
object. Add the following line to the listener created in step 6, replacing <SENDER_ADDRESS>
with the Ethereum address to track:if (transaction.from == '<SENDER_ADDRESS>') { console.log('Specified address sent an ERC-20 token!') };
You can also track a specific recipient address receiving any tokens by tracking the
transaction.to
value:if (transaction.to == '<RECIPIENT_ADDRESS>') { console.log('Specified address received an ERC-20 token!') };
You can track a specific address sending a specific ERC-20 token, by checking for both
transaction.from
(the token sender) and event.address
(the ERC-20 smart contract). Add the following line to the listener created in step 6, replacing <SENDER_ADDRESS>
with the Ethereum address to track, and <CONTRACT_ADDRESS>
with the smart contract address to track:if (transaction.from == '<SENDER_ADDRESS>' && event.address == '<CONTRACT_ADDRESS>') { console.log('Specified address transferred specified token!') };
You can also track any transactions for a specific ERC-20 token, regardless of the sender or recipient:
if (event.address == '<CONTRACT_ADDRESS>') { console.log('Specified ERC-20 transfer!') };
Run the script using the following command:
Command
Example output
node trackERC20.js
Transfer of 100.010001 USDC from 0x048917c72734B97dB03a92b9e37649BB6a9C89a6 to 0x157DA967D621cF7A086ed2A90eD6C4F42e8d551a
Transfer of 184.583283 USDT from 0x651B28f41A70742eF74Adc8BB24Ce450c0D3Ef21 to 0xF9977FCe2A0CE0eaBd51B0251b9d67E304A3991c
Transfer of 1.5 MILADY from 0x33d5CC43deBE407d20dD360F4853385135f97E9d to 0x15A8E38942F9e353BEc8812763fb3C104c89eCf4
Transfer of 1.255882219500739994 WETH from 0x15A8E38942F9e353BEc8812763fb3C104c89eCf4 to 0x33d5CC43deBE407d20dD360F4853385135f97E9d
Transfer of 1435 USDT from 0x651B28f41A70742eF74Adc8BB24Ce450c0D3Ef21 to 0x5336dEC72db2662F8bB4f3f2905cAA76aa1D3f15
Transfer of 69.41745 USDT from 0x16147b424423b6fae48161a27962CAFED51fD5B8 to 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852
const Web3 = require('web3');
const web3 = new Web3('wss://mainnet.infura.io/ws/v3/<YOUR_API_KEY>');
let options = {
topics: [
web3.utils.sha3('Transfer(address,address,uint256)')
]
};
const abi = [
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
];
let subscription = web3.eth.subscribe('logs', options);
async function collectData(contract) {
const [decimals, symbol] = await Promise.all([
contract.methods.decimals().call(),
contract.methods.symbol().call()
]);
return { decimals, symbol };
}
subscription.on('data', event => {
if (event.topics.length == 3) {
let transaction = web3.eth.abi.decodeLog([{
type: 'address',
name: 'from',
indexed: true
}, {
type: 'address',
name: 'to',
indexed: true
}, {
type: 'uint256',
name: 'value',
indexed: false
}],
event.data,
[event.topics[1], event.topics[2], event.topics[3]]);
const contract = new web3.eth.Contract(abi, event.address)
collectData(contract).then(contractData => {
const unit = Object.keys(web3.utils.unitMap).find(key => web3.utils.unitMap[key] === web3.utils.toBN(10).pow(web3.utils.toBN(contractData.decimals)).toString());
console.log(`Transfer of ${web3.utils.fromWei(transaction.value, unit)} ${contractData.symbol} from ${transaction.from} to ${transaction.to}`)
if (transaction.from == '0x495f947276749ce646f68ac8c248420045cb7b5e') { console.log('Specified address sent an ERC-20 token!') };
if (transaction.to == '0x495f947276749ce646f68ac8c248420045cb7b5e') { console.log('Specified address received an ERC-20 token!') };
if (transaction.from == '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' && event.address == '0x6b175474e89094c44da98b954eedeac495271d0f') { console.log('Specified address transferred specified token!') }; // event.address contains the contract address
if (event.address == '0x6b175474e89094c44da98b954eedeac495271d0f') { console.log('Specified ERC-20 transfer!') };
})
}
});
subscription.on('error', err => { throw err });
subscription.on('connected', nr => console.log('Subscription on ERC-20 started with ID %s', nr));