Парсинг данных smart money кошельков
«Smart money» — это кошельки, движения которых имеют информационную ценность: ранние инвесторы, whale трейдеры, фонды с доказанным треком, адреса связанные с известными протоколами. Отслеживание их активности — основа on-chain аналитики и ряда торговых стратегий.
Технически задача состоит из двух частей: классификация кошельков (кто это) и сбор их активности (что они делают). Обе нетривиальны.
Классификация кошельков
Нет единого реестра smart money. Источники для построения списка:
On-chain атрибуция: Etherscan labels (есть в API), Arkham Intelligence (частично публичный), Nansen (платный, но labels экспортируются), Dune Analytics дашборды с community labels.
Heuristic классификация: кошельки которые регулярно покупают токены до x10 роста, высокая ROI за 12 месяцев по on-chain данным, ранние участники успешных 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 // откуда пришёл в список
}
Первичный список можно собрать через:
- Топ-N holders крупных токенов (Uniswap, AAVE, Compound governance tokens)
- Участники early rounds (парсинг Transfer событий от VC-кошельков)
- Профессиональные сервисы: Nansen Smart Money feed, Arkham entity tracking
Сбор on-chain активности
Token transfers
Основной сигнал — ERC-20 трансферы. Для каждого кошелька из списка нужно знать: что купил, что продал, в каком объёме.
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)
// Исходящие трансферы (продажи/отправки)
const outgoing = await this.provider.getLogs({
topics: [transferTopic, walletTopic],
fromBlock,
toBlock,
})
// Входящие трансферы (покупки/получения)
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
Трансферы токенов не дают контекста: это своп на DEX, OTC, или перевод между своими кошельками? Для разграничения — обогащение данными о DEX активности.
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' }
}
Нативная агрегация через Alchemy / Moralis
Для крупных списков кошельков (100+) прямые getLogs запросы медленны. Альтернатива — 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 мониторинг: webhooks
Для real-time сигналов (кошелёк только что купил токен X) — Alchemy Notify или Moralis Streams:
// Подписка через 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())
}
// Обработчик входящего webhook
app.post('/webhooks/alchemy', async (req, res) => {
const { event } = req.body
// Верификация подписи
const signature = req.headers['x-alchemy-signature']
if (!verifyAlchemySignature(req.rawBody, signature)) {
return res.status(401).send()
}
await processWalletActivity(event)
res.status(200).send()
})
Анализ паттернов и сигналы
Сырые трансферы — не сигнал. Нужна интерпретация:
-- Новые токен-позиции за последние 24 часа
-- (токены, которых не было в кошельке 24ч назад, но есть сейчас)
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 -- не было раньше
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 -- минимум 3 smart money кошелька
ORDER BY smart_money_buyers DESC
Дополнительные сигналы: аккумуляция (повторные покупки без продаж), крупные перемещения на биржу (вероятная продажа), активность в первые часы после листинга нового токена.
Полная система: список кошельков из Nansen/Arkham + собственный исторический сборщик (архивная нода или Alchemy) + TimescaleDB + real-time webhook обработчик + Telegram/Slack нотификации — 2-3 недели разработки.







