Search code examples
javascriptweb3jstronetherscan

Fetching hashes and amounts of USDT transactions


I'm new to coding. Trying to fetch hashes and amounts of USDT transactions on TRC20 and ERC20 systems, without binding to single wallet.

Have been trying to use TRX and ETH native API's and third party API as well, such as QuickNode - which gave me better experience, but still can't get a proper result:(

Using QuickNode API managed to fetch some blocks, and TRX transactions, but have no idea how to get their amounts and properly filter them by contract - in another words, by token, such as USDT.

Expecting to be able to fetch hashes and amounts of latest USDT transactions on TRC20 or ERC20 ecosystems without binding fetch just to a single wallet


Solution

  • 👋🏼 Head of DevRel at QuickNode here

    Go-forward basis (aka forwardfill)

    Instead of having to repeatedly query the blockchain or an index (polling), you can use a service to feed you the data you're looking for. Our QuickAlerts product is one such service.

    In our Expression Library, we have an example expression for ERC-20/TRC-20 transfers. The Expression Library explains exactly how the expression works. All you need to do is:

    1. Select the network you want to watch for new USDT transfers.
    2. Click the deploy button.
    3. Set up your webhook URL where you want to be notified.

    To make it easy, we have a video guide on how to use the QuickAlerts interface.

    Getting historical data (AKA backfill)

    Finding a company that provides an indexed API will always be easiest here, though there aren't many that offer the ability to query by ERC-20 token or a large set of chains. If you're set on this, find a company that supports the chains you're looking for, and reach out asking if they can expose this new method.

    The manual way may be where you land here since with someone like QuickNode, you can get access to all the chains you need. Just keep in mind it's going to take quite a few RPC calls and processing as you'll be iterating over many blocks.

    I've written some code for you below showing how it can be done on Ethereum Mainnet. You'll want to make sure that you:

    1. Insert your RPC URL.
    2. Experiment with small block ranges to start, then expand to the full block range by adjusting the startingBlock definition in fetchAllTransfers. Adjust this value based on the block the USDT contract was deployed on each chain you're working with.
    3. Adjust the script to write to a file or database. Logging to the console, as I have in my script, will probably run out of memory pretty quick.

    const { Web3 } = require('web3');
    
    // QuickNode API URL (replace with your own)
    const quickNodeApiUrl = 'YOUR_QUICKNODE_API_URL';
    
    // Initialize Web3
    const web3 = new Web3(quickNodeApiUrl);
    
    // Ethereum MainNet USDT Token Contract Address, adjust for other chains
    const usdtContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
    
    // Define the ABI for the USDT token transfer event
    const usdtAbi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_upgradedAddress","type":"address"}],"name":"deprecate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"deprecated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgradedAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maximumFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newBasisPoints","type":"uint256"},{"name":"newMaxFee","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"basisPointsRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UINT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAddress","type":"address"}],"name":"Deprecate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint256"},{"indexed":false,"name":"maxFee","type":"uint256"}],"name":"Params","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"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"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}];
    
    // Initialize the USDT Token Contract
    const usdtToken = new web3.eth.Contract(usdtAbi, usdtContractAddress);
    
    // Function to fetch transfers with pagination
    const fetchTransfersPaginated = async (fromBlock, toBlock, accumulatedEvents = []) => {
        try {
            // Maximum block range to fetch in a single call, QuickNode limit for getlogs is 10,000
            const MAX_BLOCK_RANGE = 10000;
    
            const remainingBlocks = toBlock - fromBlock;
            const batchSize = Math.min(remainingBlocks, MAX_BLOCK_RANGE);
            const batchToBlock = fromBlock + batchSize - 1; // Inclusive range
    
            console.log(`Fetching transfers batch (block ${fromBlock} to ${batchToBlock})`);
    
            const pageEvents = await usdtToken.getPastEvents('Transfer', {
                fromBlock,
                toBlock: batchToBlock,
            });
    
            accumulatedEvents = accumulatedEvents.concat(pageEvents);
    
            if (remainingBlocks > batchSize) {
                // Fetch next batch
                return await fetchTransfersPaginated(fromBlock + batchSize, toBlock, accumulatedEvents);
            }
    
            return accumulatedEvents;
        } catch (error) {
            console.error('Error fetching transfers:', error);
            return accumulatedEvents; // Return accumulated events so far
        }
    };
    
    const fetchAllTransfers = async () => {
        try {
            const latestBlock = await web3.eth.getBlockNumber();
    
            // Switch to first definition for full run, second for testing with smaller ranges
            //const startingBlock = BigInt(4634748); // block where USDT contract was deployed on Ethereum Mainnet
            const startingBlock = latestBlock - BigInt(100); 
    
            console.log(`Fetching transfers form block ${startingBlock} to ${latestBlock}`);
    
            // Fetch transfers in batches of 10,000
            const events = await fetchTransfersPaginated(Number(startingBlock), Number(latestBlock));
    
            for (const event of events) {
                // Extract transaction hash and transferred amount
                const transactionHash = event.transactionHash;
                let amount = event.returnValues.value;
    
                if (typeof amount === 'bigint') {
                  // Convert to number for division
                  amount = Number(amount);
                }
                
                // Convert based on decimals (USDT has 6 decimals)
                amount = amount / 10 ** 6;
    
                // Log information, write this to a database or file if needed
                console.log(`Transaction Hash: ${transactionHash}`);
                console.log(`Amount Transferred: ${amount}`);
                console.log('---');
            }
    
            console.log('Fetched', events.length, 'transfers');
    
        } catch (error) {
            console.error('Error fetching transfers:', error);
        }
    };
    
    
    fetchAllTransfers().then(() => {
        console.log('All transfers fetched');
    }).catch((error) => {
        console.error('Error:', error);
    });