Парсинг даних smart money кошельків
"Smart money" — це кошельки, рухи яких мають інформаційну цінність: ранні інвестори, whale трейдери, фонди з доказаним треком, адреси пов'язані з відомими протоколами. Відслідковування їхної активності — основа on-chain аналітики та кількох торгових стратегій.
Технічно завдання складається з двох частин: класифікація кошельків (хто це) та збір їхної активності (що вони роблять). Обидва нетривіальні.
Класифікація кошельків
Немає єдиного реєстру smart money. Джерела для побудови списку:
On-chain атрибуція: Etherscan labels (доступні у API), Arkham Intelligence (частково публічний), Nansen (платний, але labels експортуються), Dune Analytics дашборди з community labels.
Heuristic класифікація: кошельки які регулярно купують токени до 10x росту, висока 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 холдерів крупних токенів (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 подія: 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 тижні розробки.







