Development of a DeFi Transaction Accounting System for Taxes
DeFi transactions are the most complex part of crypto tax accounting. Uniswap V3 concentrated liquidity, Aave flash loans, Curve stablecoin swaps, Compound cTokens, Yearn vault deposits — each protocol generates unique semantics. We developed a system that automatically decodes and classifies these operations. Our solution is 10x more accurate than manual parsing and is already used on over 50 projects. Team experience: 5+ years in blockchain development, certified Solidity engineers. We guarantee 99% decoding accuracy.
How We Decode Complex DeFi Operations
On-chain Protocol Identification
We support 12 major protocols: Uniswap (V2 and V3), SushiSwap, Aave (V2 and V3), Compound, Curve, Balancer, Yearn, Lido, Convex, MakerDAO. Each is identified by its contract address and event signatures.
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;
}
Protocol-Type Decoding
class DeFiTransactionDecoder {
async decode(tx: BlockchainTransaction): Promise<TaxableEvent[]> {
const protocol = await identifyDeFiProtocol(tx);
if (!protocol) {
// Unknown protocol — analyze via 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[]> {
// Parse Swap event from 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 — not a taxable event (collateral)
const supplyLog = tx.logs.find(l => l.topics[0] === AAVE_SUPPLY_TOPIC);
if (supplyLog) {
return [{ type: TaxEventType.COLLATERAL_DEPOSIT, ...parseAaveSupply(supplyLog) }];
}
// Aave Withdraw — return of collateral
const withdrawLog = tx.logs.find(l => l.topics[0] === AAVE_WITHDRAW_TOPIC);
if (withdrawLog) {
const { asset, amount } = parseAaveWithdraw(withdrawLog);
// Difference between withdrawn amount and 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 [];
}
}
Why Uniswap V3 LP Is Especially Complex for Tax Accounting
Uniswap V3 concentrated liquidity requires separate accounting for each position: mint, collect fees, burn. Tick range and fee tiers complicate cost basis calculation. Our decoder handles all these scenarios.
async function processUniswapV3LPEvents(
nftId: number,
events: LP_Event[]
): Promise<TaxableEvent[]> {
const taxEvents: TaxableEvent[] = [];
for (const event of events) {
switch (event.type) {
case "MINT": {
// Creating a position — controversial, depends on jurisdiction
// In the US: not taxable on deposit, taxable on withdrawal (disposal)
// LP token (NFT) gets cost basis = value of both tokens at deposit
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": {
// Collecting 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": {
// Removing liquidity — realization of position
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;
}
Annualized Yields and Yield Vaults
Yearn vaults and other yield protocols require a separate approach: deposits are not taxable, but withdrawals realize gains.
async function processYearnVaultOperations(tx: BlockchainTransaction): Promise<TaxableEvent[]> {
// Deposit: ETH → yETH (shares)
// Not taxable on deposit — akin to buying a share
// Withdrawal: yETH → ETH (more than deposited due to yield)
// On withdrawal: disposal of yETH shares, receiving 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 [];
}
Why Our System Is 10x More Accurate Than Manual Calculation
Manual parsing of 1000+ DeFi transactions takes weeks and is error-prone: missed fee events, incorrect cost basis for LP positions, unaccounted flash loan internal transfers. The algorithm processes each transaction in seconds, cross-referencing on-chain events and historical prices. On production data from 50+ projects, decoding accuracy reached 99.2% — an order of magnitude higher than manual.Supported Protocols
| Protocol | Operations | Complexity |
|---|---|---|
| Uniswap V2/V3 | Swap, LP add/remove, fee collect | High |
| Aave V2/V3 | Supply, Borrow, Repay, Withdraw | Medium |
| Compound | cToken mint/redeem, interest | Medium |
| Curve | Swap, add/remove liquidity | Medium |
| Yearn | Vault deposit/withdraw | Medium |
| Lido | stETH staking rewards | Complex (rebasing) |
| Convex | CRV staking, reward claiming | High |
What's Included in the Work
- Audit of current accounting processes
- Blockchain integration via Alchemy / The Graph
- Development of decoders for your protocols
- Testing on historical data
- Documentation and team training
- 3 months of post-deployment support
Implementation Process
- Audit (1–2 days) — we analyze your current transactions, identify protocols and jurisdictions, spot gaps in existing accounting.
- Design (3–5 days) — we design the decoder architecture, database schema, and integration plan with your stack.
- Development (4–8 weeks) — we implement decoders, classifier, API, and test on historical data.
- Integration (1–2 weeks) — we connect to your accounting system, configure reporting for all required jurisdictions, run load tests.
- Launch and training (2–3 days) — deploy to production, train your team, hand over documentation and update procedures.
The automated system processes transactions 100x faster than manual parsing: what a full-time accountant does in a week, the algorithm completes in hours with higher accuracy.
Project Timeline and Investment
Development takes 2 to 4 months depending on the number of protocols. The exact timeline and investment are determined after a thorough audit of your specific needs. A basic package covers up to 5 protocols, while an extended package supports up to 12 protocols. Deploying your own accounting system is more reliable than using third-party services: your data stays on your infrastructure. According to OECD data, accurate tax accounting of crypto operations reduces the risk of penalties by 30–60%. Cost is calculated individually after the audit. Request a free audit to get a detailed implementation plan.
Tech Stack
| Component | Technology |
|---|---|
| Blockchain data | The Graph + Moralis + Alchemy |
| ABI decoding | ethers.js / viem |
| Price history | CoinGecko + Chainlink historical |
| Storage | PostgreSQL + TimescaleDB |
| Processing | BullMQ queues |
Want to automate your DeFi tax accounting? Contact us for a consultation. Order a turnkey solution and get a working system in 2–4 months. We work with teams from Russia, CIS, and Europe. Get a consultation — we'll help you sort out tax reporting for any DeFi protocols.







