Smart Money Wallet Data Scraping
"Smart money" is wallets whose movements have information value: early investors, whale traders, funds with proven track record, addresses linked to known protocols. Tracking their activity is the foundation of on-chain analytics and several trading strategies.
Technically, the task consists of two parts: wallet classification (who are they) and activity collection (what are they doing). Both are non-trivial.
Wallet classification
No centralized smart money registry exists. Sources for building a list:
On-chain attribution: Etherscan labels (available in API), Arkham Intelligence (partially public), Nansen (paid, but labels exportable), Dune Analytics dashboards with community labels.
Heuristic classification: wallets that regularly buy tokens before 10x growth, high ROI over 12 months via on-chain data, early participants in successful IDO/ICO.
interface WalletProfile {
address: string
labels: string[] // ['vc', 'early-investor', 'dex-whale']
chain: string
firstTx: Date
totalTxCount: number
watchPriority: 'high' | 'medium' | 'low'
source: string // where list came from
}
Initial list can be compiled via:
- Top-N holders of major tokens (Uniswap, AAVE, Compound governance tokens)
- Early round participants (parsing Transfer events from VC wallets)
- Professional services: Nansen Smart Money feed, Arkham entity tracking
Collecting on-chain activity
Token transfers
Primary signal — ERC-20 transfers. For each wallet in the list, need: what bought, what sold, in what volume.
class WalletActivityCollector {
constructor(
private provider: ethers.JsonRpcProvider,
private db: Database
) {}
async collectTransferHistory(
wallet: string,
fromBlock: number,
toBlock: number
): Promise<void> {
const transferTopic = ethers.id('Transfer(address,address,uint256)')
const walletTopic = ethers.zeroPadValue(wallet, 32)
// Outgoing transfers (sales/sends)
const outgoing = await this.provider.getLogs({
topics: [transferTopic, walletTopic],
fromBlock,
toBlock,
})
// Incoming transfers (buys/receives)
const incoming = await this.provider.getLogs({
topics: [transferTopic, null, walletTopic],
fromBlock,
toBlock,
})
const transfers = [...outgoing, ...incoming].map(log => ({
txHash: log.transactionHash,
blockNumber: log.blockNumber,
contract: log.address.toLowerCase(),
direction: log.topics[1].includes(wallet.toLowerCase().slice(2)) ? 'out' : 'in',
from: `0x${log.topics[1].slice(26)}`,
to: `0x${log.topics[2].slice(26)}`,
amount: BigInt(log.data),
}))
await this.db.batchInsertTransfers(wallet, transfers)
}
}
DEX trades
Transfers alone don't provide context: is this a DEX swap, OTC, or self-wallet transfer? To distinguish — enrich with DEX activity data.
Uniswap V3 Swap event: Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick).
async function classifyTransaction(
txHash: string,
walletAddress: string
): Promise<TxClassification> {
const receipt = await provider.getTransactionReceipt(txHash)
const UNISWAP_V3_SWAP_TOPIC = ethers.id(
'Swap(address,address,int256,int256,uint160,uint128,int24)'
)
const UNISWAP_V2_SWAP_TOPIC = ethers.id(
'Swap(address,uint256,uint256,uint256,uint256,address)'
)
const hasSwap = receipt.logs.some(log =>
log.topics[0] === UNISWAP_V3_SWAP_TOPIC ||
log.topics[0] === UNISWAP_V2_SWAP_TOPIC
)
if (hasSwap) return { type: 'dex_trade', protocol: detectDexProtocol(receipt) }
const hasTransfer = receipt.logs.some(log =>
log.topics[0] === ethers.id('Transfer(address,address,uint256)')
)
if (hasTransfer) return { type: 'token_transfer' }
return { type: 'contract_interaction' }
}
Native aggregation via Alchemy / Moralis
For large wallet lists (100+), direct getLogs queries are slow. Alternative — Alchemy Asset Transfers API:
async function getWalletAssetTransfers(
address: string,
fromBlock: string,
direction: 'from' | 'to'
): Promise<AssetTransfer[]> {
const body = {
id: 1,
jsonrpc: '2.0',
method: 'alchemy_getAssetTransfers',
params: [{
fromBlock,
toBlock: 'latest',
[direction === 'from' ? 'fromAddress' : 'toAddress']: address,
category: ['erc20', 'erc721', 'erc1155', 'external'],
withMetadata: true,
excludeZeroValue: true,
maxCount: '0x3e8', // 1000
}]
}
const res = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const data = await res.json()
return data.result.transfers
}
Real-time monitoring: webhooks
For real-time signals (wallet just bought token X) — Alchemy Notify or Moralis Streams:
// Subscribe via Alchemy Notify API
async function subscribeToWalletActivity(wallets: string[]): Promise<void> {
const payload = {
network: 'ETH_MAINNET',
webhook_type: 'ADDRESS_ACTIVITY',
webhook_url: `${process.env.APP_URL}/webhooks/alchemy`,
addresses: wallets,
}
const res = await fetch('https://notify.alchemyapi.io/dashboard/webhook-subscriptions', {
method: 'POST',
headers: {
'X-Alchemy-Token': process.env.ALCHEMY_NOTIFY_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
console.log('Webhook registered:', await res.json())
}
// Incoming webhook handler
app.post('/webhooks/alchemy', async (req, res) => {
const { event } = req.body
// Verify signature
const signature = req.headers['x-alchemy-signature']
if (!verifyAlchemySignature(req.rawBody, signature)) {
return res.status(401).send()
}
await processWalletActivity(event)
res.status(200).send()
})
Pattern analysis and signals
Raw transfers aren't signals. Need interpretation:
-- New token positions in last 24 hours
-- (tokens that weren't in wallet 24h ago but are now)
WITH yesterday_holdings AS (
SELECT DISTINCT wallet, token_contract
FROM wallet_transfers
WHERE direction = 'in'
AND block_time < NOW() - INTERVAL '24 hours'
),
new_buys AS (
SELECT t.wallet, t.token_contract, SUM(t.amount) as total_in
FROM wallet_transfers t
LEFT JOIN yesterday_holdings y
ON t.wallet = y.wallet AND t.token_contract = y.token_contract
WHERE t.direction = 'in'
AND t.block_time >= NOW() - INTERVAL '24 hours'
AND y.token_contract IS NULL -- not present before
GROUP BY t.wallet, t.token_contract
)
SELECT
nb.token_contract,
COUNT(DISTINCT nb.wallet) AS smart_money_buyers,
STRING_AGG(nb.wallet, ',') AS buyer_list
FROM new_buys nb
GROUP BY nb.token_contract
HAVING COUNT(DISTINCT nb.wallet) >= 3 -- minimum 3 smart money wallets
ORDER BY smart_money_buyers DESC
Additional signals: accumulation (repeat buys without sells), large moves to exchange (likely sale), activity in first hours after new token listing.
Full system: wallet list from Nansen/Arkham + own historical collector (archive node or Alchemy) + TimescaleDB + real-time webhook processor + Telegram/Slack notifications — 2-3 weeks development.







