I'm looking for guidance or tools to help with a project involving the recursive exploration of blockchain addresses. My goal is to trace all associated wallet addresses by following every transaction linked to a particular wallet address, up to a recursive depth of 15-20 hops.
Objective:Create list of all coins associated (or loosely associated) with a specific wallet address by following the transaction trail from the given address to any interacting addresses, continuing this process recursively up to 20 hops.
Specific Example:
- Start with the wallet address used for minting.
- Identify all wallets that have transferred to this minting address.
- Identify all wallets that have transferred to those wallets.
- Continue this process recursively for all subsequent wallets, working my way outwards up to a depth of 15-20 hops
I attempted to implement this in both Rust and Python (for some reason I could only get my rust code to return signatures but not transactions/transfer address details such as source/destination address and amount SOL transferred)
Below is a JavaScript implementation I tried using the Solana Web3.js library:
const solanaWeb3 = require('@solana/web3.js'); const fs = require('fs'); // Establish a connection to the Solana mainnet const connection = new solanaWeb3.Connection(solanaWeb3.clusterApiUrl('mainnet-beta'), 'confirmed'); const processedAddresses = new Set(); let transactions = []; const MAX_BATCH_SIZE = 100; // Maximum number of transactions to fetch per request const INITIAL_RETRY_DELAY_MS = 500; // Initial delay for retrying requests const MAX_RETRIES = 5; // Maximum number of retries for a request const DELAY_MS = 6000; // Delay between requests to avoid rate limits // Utility function to pause execution for a given amount of time async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Function to fetch transactions for a given wallet address, with recursion to explore linked addresses async function fetchTransactions(walletAddress, depth, retryCount = 0) { if (depth === 0 || processedAddresses.has(walletAddress)) return; // Stop recursion if depth is 0 or address is already processed processedAddresses.add(walletAddress); console.log(`Fetching transactions for wallet: ${walletAddress} at depth: ${depth}`); let before = undefined; let allSignatures = []; // Fetch transaction signatures in smaller batches with rate limiting while (true) { try { let transactionList = await connection.getConfirmedSignaturesForAddress2( new solanaWeb3.PublicKey(walletAddress), { limit: MAX_BATCH_SIZE, before } ); if (transactionList.length === 0) break; // No more transactions to fetch let signaturesList = transactionList.map(transaction => transaction.signature); allSignatures = allSignatures.concat(signaturesList); before = transactionList[transactionList.length - 1].signature; console.log(`Fetched ${transactionList.length} signatures, total: ${allSignatures.length}`); process.stdout.write(''); // Delay between requests to avoid rate limiting await sleep(DELAY_MS); } catch (error) { if (error.message.includes("Too Many Requests")) { console.log("Rate limit hit, retrying after delay..."); await sleep(DELAY_MS * 2); // Exponential backoff } else { throw error; } } } // Process transactions in batches to avoid rate limits for (let i = 0; i < allSignatures.length; i += MAX_BATCH_SIZE) { let batchSignatures = allSignatures.slice(i, i + MAX_BATCH_SIZE); try { let txDetails = await connection.getParsedTransactions(batchSignatures, { maxSupportedTransactionVersion: 0, }); console.log(`Processing batch ${i / MAX_BATCH_SIZE + 1}: ${batchSignatures.length} signatures`); process.stdout.write(''); for (let tx of txDetails) { if (tx && tx.transaction) { transactions.push(tx); extractAndFetchNewAddresses(tx, depth - 1); // Recursively fetch new addresses } } // Delay between requests to avoid rate limiting await sleep(DELAY_MS); } catch (error) { if (error.message.includes("Too Many Requests")) { console.log("Rate limit hit, retrying after delay..."); await sleep(DELAY_MS * 2); // Exponential backoff } else { throw error; } } } } // Extract addresses from a transaction and fetch transactions for new addresses recursively function extractAndFetchNewAddresses(transaction, depth) { if (!transaction || !transaction.transaction || !transaction.transaction.message) { console.error('Invalid transaction structure:', transaction); return; } const involvedAddresses = new Set(); // Collect all addresses involved in the transaction transaction.transaction.message.accountKeys.forEach(accountKey => { involvedAddresses.add(accountKey.toString()); }); // Recursively fetch transactions for each new address involvedAddresses.forEach(address => { if (!processedAddresses.has(address)) { fetchTransactions(address, depth); } }); } // Main function to start the transaction fetching process async function main() { const initialWalletAddress = 'PASTE_WALLET_ADDRESS_TO_ANALYZE_HERE'; const recursionDepth = 10; // Adjust this value based on how deep you want to fetch await fetchTransactions(initialWalletAddress, recursionDepth); fs.writeFileSync('transactionDetails.json', JSON.stringify(transactions, null, 2)); // Save transaction details to a file console.log('Transaction details saved to transactionDetails.json'); } // Execute the main function and handle any errors main().catch(err => { console.error('Error in main:', err); });
I'm open to switching back to rust or python instead of JS if that will make it easier. Also, I'm open to paying for an existing solutions that can do this (if there is one already available). Thanks in advance for your help!