Разработка системы учета DeFi-операций для налогов
DeFi транзакции — самая сложная часть крипто-налогового учёта. Uniswap V3 concentrated liquidity, Aave flash loans, Curve стейблкоин свопы, Compound cTokens, Yearn vault deposits — каждый протокол имеет уникальную семантику, которую нужно декодировать и классифицировать.
Декодирование DeFi транзакций
On-chain идентификация протокола
const KNOWN_PROTOCOLS: Record<string, ProtocolInfo> = {
"0xE592427A0AEce92De3Edee1F18E0157C05861564": { name: "Uniswap V3 Router", type: "DEX" },
"0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45": { name: "Uniswap V3 Router 2", type: "DEX" },
"0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F": { name: "SushiSwap Router", type: "DEX" },
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D": { name: "Uniswap V2 Router", type: "DEX" },
"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2": { name: "Aave V3 Pool", type: "LENDING" },
"0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B": { name: "Compound Comptroller", type: "LENDING" },
"0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7": { name: "Curve 3pool", type: "STABLE_SWAP" },
"0xBA12222222228d8Ba445958a75a0704d566BF2C8": { name: "Balancer Vault", type: "DEX" },
};
async function identifyDeFiProtocol(tx: BlockchainTransaction): Promise<ProtocolInfo | null> {
return KNOWN_PROTOCOLS[tx.to?.toLowerCase()] ?? null;
}
Декодирование по типу протокола
class DeFiTransactionDecoder {
async decode(tx: BlockchainTransaction): Promise<TaxableEvent[]> {
const protocol = await identifyDeFiProtocol(tx);
if (!protocol) {
// Неизвестный протокол — анализируем по ERC-20 Transfer events
return this.decodeByTransferEvents(tx);
}
switch (protocol.type) {
case "DEX":
return this.decodeDEXSwap(tx, protocol);
case "LENDING":
return this.decodeLendingOperation(tx, protocol);
case "STABLE_SWAP":
return this.decodeStableSwap(tx, protocol);
case "YIELD":
return this.decodeYieldVault(tx, protocol);
}
}
private async decodeDEXSwap(tx: BlockchainTransaction, protocol: ProtocolInfo): Promise<TaxableEvent[]> {
// Парсим Swap event из logs
const swapLogs = tx.logs.filter(log =>
log.topics[0] === UNISWAP_V3_SWAP_TOPIC || log.topics[0] === UNISWAP_V2_SWAP_TOPIC
);
const events: TaxableEvent[] = [];
for (const swapLog of swapLogs) {
const [tokenIn, tokenOut, amountIn, amountOut] = await this.parseSwapLog(swapLog);
const priceIn = await this.priceService.getHistoricalPrice(tokenIn, tx.timestamp);
const priceOut = await this.priceService.getHistoricalPrice(tokenOut, tx.timestamp);
events.push({
type: TaxEventType.SWAP,
timestamp: tx.timestamp,
assetIn: tokenIn,
amountIn,
valueInUSD: amountIn * priceIn,
assetOut: tokenOut,
amountOut,
valueOutUSD: amountOut * priceOut,
protocol: protocol.name,
txHash: tx.hash,
});
}
return events;
}
private async decodeLendingOperation(tx: BlockchainTransaction, protocol: ProtocolInfo): Promise<TaxableEvent[]> {
const events: TaxableEvent[] = [];
// Aave Supply — не taxable event (залог)
const supplyLog = tx.logs.find(l => l.topics[0] === AAVE_SUPPLY_TOPIC);
if (supplyLog) {
return [{ type: TaxEventType.COLLATERAL_DEPOSIT, ...parseAaveSupply(supplyLog) }];
}
// Aave Withdraw — возврат залога
const withdrawLog = tx.logs.find(l => l.topics[0] === AAVE_WITHDRAW_TOPIC);
if (withdrawLog) {
const { asset, amount } = parseAaveWithdraw(withdrawLog);
// Разница между withdrawn amount и deposited amount = interest earned
const originalDeposit = await this.db.getAaveDeposit(tx.from, asset);
const interest = amount - originalDeposit.amount;
if (interest > 0) {
events.push({
type: TaxEventType.LENDING_INTEREST,
asset,
amount: interest,
valueUSD: interest * await this.priceService.getHistoricalPrice(asset, tx.timestamp),
});
}
events.push({ type: TaxEventType.COLLATERAL_RETURN, asset, amount: originalDeposit.amount });
return events;
}
return [];
}
}
Uniswap V3 LP Positions
Uniswap V3 concentrated liquidity создаёт особую сложность: позиция определяется tick range, комиссии накапливаются отдельно, и price range в/out-of-range влияет на composition.
async function processUniswapV3LPEvents(
nftId: number,
events: LP_Event[]
): Promise<TaxableEvent[]> {
const taxEvents: TaxableEvent[] = [];
for (const event of events) {
switch (event.type) {
case "MINT": {
// Создание позиции — спорно, зависит от юрисдикции
// В США: не taxable при deposit, taxable при withdrawal (disposal)
// LP токен (NFT) получает cost basis = value обоих токенов при деносите
taxEvents.push({
type: TaxEventType.LP_MINT,
token0: event.token0, amount0: event.amount0,
token1: event.token1, amount1: event.amount1,
totalValueUSD: await getPositionValue(event),
nftId,
});
break;
}
case "COLLECT_FEES": {
// Сбор accumulated fees — income event
const feeValueUSD = await getFeesValue(event, event.timestamp);
taxEvents.push({
type: TaxEventType.LIQUIDITY_FEES,
token0: event.token0, fee0: event.amount0Collected,
token1: event.token1, fee1: event.amount1Collected,
valueUSD: feeValueUSD,
timestamp: event.timestamp,
});
break;
}
case "BURN": {
// Вывод ликвидности — реализация позиции
const originalCostBasis = await db.getLPCostBasis(nftId);
const currentValue = await getPositionValue(event);
taxEvents.push({
type: TaxEventType.LP_BURN,
gainLossUSD: currentValue - originalCostBasis,
isLongTerm: isLongTerm(event.mintTimestamp, event.timestamp),
});
break;
}
}
}
return taxEvents;
}
Yearn и yield vault accounting
async function processYearnVaultOperations(tx: BlockchainTransaction): Promise<TaxableEvent[]> {
// Deposit: ETH → yETH (shares)
// Не taxable при deposit — это как покупка долевого участия
// Withdrawal: yETH → ETH (больше чем вложили из-за yield)
// При выводе: disposal yETH shares, получение ETH
// Gain = current ETH value - original ETH cost basis
const withdrawLog = tx.logs.find(l => l.address === YEARN_VAULT_ADDRESS && l.topics[0] === WITHDRAW_TOPIC);
if (withdrawLog) {
const { shares, assets } = parseYearnWithdraw(withdrawLog);
const costBasis = await db.getYearnSharesCostBasis(tx.from, YEARN_VAULT_ADDRESS, shares);
const currentValue = assets * await priceService.getHistoricalPrice("ETH", tx.timestamp);
return [{
type: TaxEventType.DISPOSAL,
assetSold: "yETH",
amountSold: shares,
proceeds: currentValue,
costBasis: costBasis,
gainLoss: currentValue - costBasis,
}];
}
return [];
}
Поддерживаемые протоколы
| Протокол | Операции | Сложность |
|---|---|---|
| Uniswap V2/V3 | Swap, LP add/remove, fee collect | Высокая |
| Aave V2/V3 | Supply, Borrow, Repay, Withdraw | Средняя |
| Compound | cToken mint/redeem, interest | Средняя |
| Curve | Swap, add/remove liquidity | Средняя |
| Yearn | Vault deposit/withdraw | Средняя |
| Lido | stETH staking rewards | Сложная (rebasing) |
| Convex | CRV staking, reward claiming | Высокая |
Стек
| Компонент | Технология |
|---|---|
| Blockchain data | The Graph + Moralis + Alchemy |
| ABI decoding | ethers.js / viem |
| Price history | CoinGecko + Chainlink historical |
| Storage | PostgreSQL + TimescaleDB |
| Processing | BullMQ queues |
Полная DeFi tax accounting система с поддержкой 10+ протоколов: 2-3 месяца разработки.







