Introduction
If you want to find out whether a token or contract is an ERC-20 token, either for validation on a site or in your backend system, this guide is for you. Below, I’ll break down a simple Node.js script that checks if a contract is an ERC-20 token by verifying the presence of essential functions in its bytecode.
Step-by-Step Breakdown
Here’s a detailed explanation of the code, broken down into easy steps:
1. Set Up Web3
First, we need to set up Web3, which allows us to interact with the blockchain. Replace 'YOUR_RPC_URL'
with the RPC URL of your blockchain node.
const { Web3 } = require('web3');
const web3 = new Web3('YOUR_RPC_URL');
2. Define the Implementation Slot
In the EIP-1967 proxy standard, the implementation address is stored at a specific slot. We’ll define this slot for later use.
const IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
3. Function to Get the Implementation Address
This function retrieves the implementation address from a proxy contract. If the contract is a proxy, it will return the address of the actual implementation contract.
async function getImplementationAddress(proxyAddress) {
try {
const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
const storageValue = await web3.eth.getStorageAt(proxyAddress, IMPLEMENTATION_SLOT);
const implementationAddress = `0x${storageValue.slice(26)}`; // Remove the padding and prepend '0x'
console.log(`Implementation address found at slot ${IMPLEMENTATION_SLOT}: ${implementationAddress}`);
if (implementationAddress === NULL_ADDRESS) {
console.log(
`Implementation address is null. Proxy at ${proxyAddress} is not initialized.`
);
return null;
}
return implementationAddress;
} catch (error) {
console.error(`Error fetching implementation address from proxy: ${error.message}`);
return null;
}
}
4. Function to Check ERC-20 Compliance
This function checks if a contract implements all the mandatory ERC-20 functions. It first checks if the contract is a proxy, then retrieves the bytecode of the actual contract and verifies the presence of essential ERC-20 function signatures.
async function isERC20(contractAddress) {
// ERC-20 function signatures
const erc20Functions = [
'totalSupply()',
'balanceOf(address)',
'transfer(address,uint256)',
'allowance(address,address)',
'approve(address,uint256)',
'transferFrom(address,address,uint256)'
];
try {
console.log(`Checking if contract at ${contractAddress} is an ERC-20 token.`);
// Check if the contract is a proxy
let addressToCheck = contractAddress;
const implementationAddress = await getImplementationAddress(contractAddress);
if (implementationAddress) {
console.log(`Proxy detected. Implementation address: ${implementationAddress}`);
addressToCheck = implementationAddress;
} else {
console.log(`No proxy detected for address: ${contractAddress}`);
}
// Get contract bytecode
let bytecode = await web3.eth.getCode(addressToCheck);
bytecode = bytecode.startsWith('0x') ? bytecode.slice(2) : bytecode;
console.log(`Bytecode of contract at ${addressToCheck}: ${bytecode}`);
if (!bytecode || bytecode === '0x') {
throw new Error('Contract not found or invalid address');
}
// Check mandatory functions
for (const func of erc20Functions) {
const methodSignature = web3.utils.sha3(func).substring(2, 10); // Get the method signature without '0x'
console.log(`Checking for function ${func} with method signature ${methodSignature} in contract bytecode.`);
if (!bytecode.includes(methodSignature)) {
console.log(`Function ${func} with method signature ${methodSignature} not found in contract bytecode.`);
throw new Error(`Function ${func} is not implemented.`);
}
console.log(`Function ${func} with method signature ${methodSignature} is present in contract bytecode.`);
}
console.log(`Contract at ${contractAddress} is an ERC-20 token.`);
return true; // All mandatory ERC-20 functions are present
} catch (error) {
console.error(`Error while checking ERC-20 compliance for contract at ${contractAddress}: ${error.message}`);
return false; // Missing one or more mandatory ERC-20 functions
}
}
5. Example Usage
Finally, here’s how you can use the isERC20
function to check if a contract address is an ERC-20 token.
(async () => {
const contractAddress = 'YOUR_CONTRACT_ADDRESS';
const result = await isERC20(contractAddress);
console.log(`Is the contract at ${contractAddress} an ERC-20 token? ${result}`);
})();
Conclusion
This script helps you verify if a contract follows the ERC-20 standard by checking for the presence of essential functions. This is useful for ensuring compatibility with various platforms and applications. By following these steps, you can easily identify ERC-20 tokens programmatically.