DeFi Position Tracker

v2.0.0

Monitor and analyze DeFi positions across protocols and chains. Track LP (liquidity provider) positions, staking rewards, yield farming returns, impermanent...

0· 254· 12 versions· 1 current· 1 all-time· Updated 19h ago· MIT-0

Install

openclaw skills install defi-position-tracker

DeFi Position Tracker

Monitor LP positions, staking rewards, and yield farming across protocols. Calculate impermanent loss, track cost basis, and feed structured data to crypto-tax-agent for tax reporting.


Supported Protocols

CategoryProtocols
DEX LPUniswap v2/v3, Curve, Balancer, Velodrome, PancakeSwap
LendingAave v2/v3, Compound v2/v3, Euler, Morpho
Liquid StakingLido (stETH), Rocket Pool (rETH), Frax (sfrxETH)
Yield FarmingConvex, Yearn, Beefy, Pendle
Bridges/xChainLayerZero positions, Stargate LPs

Supported Chains

Ethereum, Arbitrum, Optimism, Base, Polygon, BSC, Avalanche, Fantom, Solana (via Birdeye/Helius).


Core Workflows

1. Full Portfolio Snapshot

Pull all active DeFi positions for a wallet address using DeBank Pro API (most comprehensive):

# DeBank Pro API — full protocol positions
curl -s "https://pro-openapi.debank.com/v1/user/all_complex_protocol_list?id=0xYOUR_WALLET&chain_ids=eth,arb,op,base,matic" \
  -H "AccessKey: YOUR_DEBANK_API_KEY" | jq '.[] | {protocol: .name, net_usd_value: .net_usd_value, positions: .portfolio_item_list}'

Free alternative — Zapper API:

curl -s "https://api.zapper.xyz/v2/balances?addresses[]=0xYOUR_WALLET&networks[]=ethereum&networks[]=arbitrum" \
  -H "Authorization: Basic $(echo -n ':YOUR_ZAPPER_KEY' | base64)"

2. Impermanent Loss Calculator

Formula:

IL% = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1

Where price_ratio = current_price / entry_price for the volatile asset vs stable.

Python implementation:

import math

def impermanent_loss(entry_price: float, current_price: float) -> float:
    """
    Calculate impermanent loss percentage for a 50/50 LP position.
    
    Args:
        entry_price: Price of volatile asset at LP entry (in terms of stable)
        current_price: Current price of volatile asset
    
    Returns:
        IL as a decimal (negative = loss). Multiply by 100 for percentage.
    
    Example:
        entry_price = 2000  # ETH at entry
        current_price = 3000  # ETH now
        il = impermanent_loss(2000, 3000)
        # Returns ~-0.0203 → -2.03% IL
    """
    price_ratio = current_price / entry_price
    il = (2 * math.sqrt(price_ratio)) / (1 + price_ratio) - 1
    return il

def lp_position_pnl(
    token0_qty: float, token1_qty: float,
    token0_entry: float, token1_entry: float,
    token0_current: float, token1_current: float,
    fees_earned_usd: float = 0.0
) -> dict:
    """
    Full P&L for an LP position including IL and fees.
    
    Returns:
        Dict with: hodl_value, lp_value, il_usd, fees_earned, net_pnl
    """
    hodl_value = (token0_qty * token0_current) + (token1_qty * token1_current)
    lp_value = _calculate_lp_value(
        token0_qty, token1_qty,
        token0_entry, token1_entry,
        token0_current, token1_current
    )
    il_usd = lp_value - hodl_value
    net_pnl = il_usd + fees_earned_usd
    
    return {
        "hodl_value_usd": hodl_value,
        "lp_value_usd": lp_value,
        "il_usd": il_usd,
        "il_pct": il_usd / hodl_value if hodl_value else 0,
        "fees_earned_usd": fees_earned_usd,
        "net_pnl_usd": net_pnl,
        "net_pnl_pct": net_pnl / hodl_value if hodl_value else 0,
    }

def _calculate_lp_value(t0_qty, t1_qty, t0_entry, t1_entry, t0_cur, t1_cur):
    """Compute constant-product AMM LP value at current prices."""
    k = t0_qty * t1_qty  # invariant
    # At current prices: t0_new = sqrt(k * t1_cur / t0_cur) — wait, recalc with entry ratio
    entry_value = (t0_qty * t0_entry) + (t1_qty * t1_entry)
    price_ratio = t0_cur / t0_entry
    # AMM rebalances: each side = sqrt(initial_product * price_ratio)
    t0_new = math.sqrt(k * t0_cur / t1_cur)
    t1_new = math.sqrt(k * t1_cur / t0_cur)
    return (t0_new * t0_cur) + (t1_new * t1_cur)

Uniswap v3 LP (concentrated liquidity): Uniswap v3 IL is range-dependent. Use the official SDK or Revert Finance API:

# Revert Finance — v3 position analytics
curl "https://api.revert.finance/v1/position?position_id=YOUR_NFT_ID&chain_id=1"

3. Cost Basis Tracking Per Position

Track entry prices and quantities for accurate P&L and tax reporting:

from dataclasses import dataclass
from datetime import datetime
from typing import List

@dataclass
class LPEntry:
    """Single LP entry event (add liquidity)."""
    timestamp: datetime
    protocol: str
    chain: str
    pool: str
    token0_symbol: str
    token0_qty: float
    token0_price_usd: float
    token1_symbol: str
    token1_qty: float
    token1_price_usd: float
    tx_hash: str
    gas_cost_usd: float = 0.0

    @property
    def cost_basis_usd(self) -> float:
        return (self.token0_qty * self.token0_price_usd +
                self.token1_qty * self.token1_price_usd +
                self.gas_cost_usd)

@dataclass
class LPExit:
    """LP exit event (remove liquidity)."""
    timestamp: datetime
    protocol: str
    chain: str
    pool: str
    token0_qty_returned: float
    token0_price_usd: float
    token1_qty_returned: float
    token1_price_usd: float
    fees_token0: float
    fees_token1: float
    tx_hash: str
    gas_cost_usd: float = 0.0

    @property
    def proceeds_usd(self) -> float:
        return (self.token0_qty_returned * self.token0_price_usd +
                self.token1_qty_returned * self.token1_price_usd +
                self.fees_token0 * self.token0_price_usd +
                self.fees_token1 * self.token1_price_usd -
                self.gas_cost_usd)

IRS treatment (current guidance):

  • Adding liquidity: typically not a taxable event, but track cost basis
  • LP fees earned: ordinary income at time of receipt (FMV)
  • Removing liquidity: capital gain/loss (proceeds - cost basis)
  • Staking rewards: ordinary income at FMV when received

4. Staking Rewards Tracker

# Pull staking reward history via The Graph (Lido example)
curl -X POST "https://api.thegraph.com/subgraphs/name/lidofinance/lido" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ totalRewards(where: {account: \"0xYOUR_WALLET\"}, orderBy: block, orderDirection: desc, first: 100) { id totalRewards totalFee block blockTime } }"
  }'

Aave interest accrual:

# aToken balance change = interest earned
# Use Aave subgraph to get historical balance snapshots
curl -X POST "https://api.thegraph.com/subgraphs/name/aave/protocol-v3" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ userReserves(where: {user: \"0xYOUR_WALLET\"}) { reserve { symbol } currentATokenBalance scaledATokenBalance } }"
  }'

5. Multi-Chain Aggregation

Using Moralis Web3 Data API (no API key for public endpoints):

# ETH mainnet DeFi positions
curl "https://deep-index.moralis.io/api/v2.2/0xYOUR_WALLET/defi/positions?chain=eth" \
  -H "X-API-Key: YOUR_MORALIS_KEY"

# Arbitrum positions  
curl "https://deep-index.moralis.io/api/v2.2/0xYOUR_WALLET/defi/positions?chain=arbitrum" \
  -H "X-API-Key: YOUR_MORALIS_KEY"

Using cast (Foundry) for on-chain reads:

# Check Uniswap v3 position NFT owner (verify position still active)
cast call 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 \
  "ownerOf(uint256)(address)" YOUR_NFT_ID \
  --rpc-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY

# Get Aave v3 account health factor
cast call 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 \
  "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
  0xYOUR_WALLET --rpc-url YOUR_RPC

6. Portfolio Summary Output

Standard JSON schema for handoff to crypto-tax-agent and treasury dashboards:

{
  "snapshot_date": "2026-03-15T18:00:00Z",
  "wallet": "0x...",
  "total_value_usd": 125430.00,
  "positions": [
    {
      "id": "uniswap-v3-eth-usdc-500-12345",
      "protocol": "Uniswap v3",
      "chain": "ethereum",
      "pool": "ETH/USDC 0.05%",
      "type": "lp",
      "nft_id": 12345,
      "token0": { "symbol": "ETH", "qty": 1.5, "price_usd": 3000, "value_usd": 4500 },
      "token1": { "symbol": "USDC", "qty": 4500, "price_usd": 1.0, "value_usd": 4500 },
      "total_value_usd": 9000,
      "cost_basis_usd": 8800,
      "unrealized_pnl_usd": 200,
      "fees_earned_usd": 145.50,
      "il_usd": -42.10,
      "il_pct": -0.0047,
      "entry_date": "2026-01-10T00:00:00Z",
      "in_range": true
    },
    {
      "id": "lido-steth-deposit-20240101",
      "protocol": "Lido",
      "chain": "ethereum",
      "type": "liquid_staking",
      "token": { "symbol": "stETH", "qty": 5.0, "price_usd": 3010, "value_usd": 15050 },
      "cost_basis_usd": 14500,
      "staking_rewards_usd": 380.00,
      "apy_current": 3.8,
      "entry_date": "2025-06-01T00:00:00Z"
    }
  ],
  "summary": {
    "total_cost_basis_usd": 89000,
    "total_unrealized_pnl_usd": 36430,
    "total_fees_earned_usd_ytd": 2140,
    "total_staking_rewards_usd_ytd": 890,
    "total_il_usd": -520,
    "net_yield_usd_ytd": 3030
  }
}

Tax Handoff to crypto-tax-agent

Export in crypto-tax-agent's expected format:

def export_for_tax_agent(positions: list, year: int) -> dict:
    """
    Generate tax-ready export for crypto-tax-agent consumption.
    
    Produces:
    - Income events: staking rewards, LP fees (ordinary income)
    - Disposal events: LP exits (capital gains/losses)
    - Cost basis lots: FIFO/HIFO tracking per position
    """
    income_events = []
    disposal_events = []
    
    for pos in positions:
        # Fee income events (ordinary income when earned)
        for reward in pos.get("reward_events", []):
            income_events.append({
                "date": reward["timestamp"],
                "type": "defi_income",
                "subtype": reward["type"],  # "lp_fee" | "staking_reward" | "yield"
                "asset": reward["symbol"],
                "qty": reward["qty"],
                "fmv_usd": reward["price_usd"],
                "income_usd": reward["qty"] * reward["price_usd"],
                "protocol": pos["protocol"],
                "tx_hash": reward["tx_hash"]
            })
        
        # LP exits (capital events)
        for exit_event in pos.get("exit_events", []):
            disposal_events.append({
                "date": exit_event["timestamp"],
                "type": "lp_exit",
                "protocol": pos["protocol"],
                "cost_basis_usd": exit_event["cost_basis_usd"],
                "proceeds_usd": exit_event["proceeds_usd"],
                "gain_loss_usd": exit_event["proceeds_usd"] - exit_event["cost_basis_usd"],
                "holding_period_days": exit_event["holding_period_days"],
                "is_long_term": exit_event["holding_period_days"] >= 365,
                "tx_hash": exit_event["tx_hash"]
            })
    
    return {
        "tax_year": year,
        "generated_at": datetime.utcnow().isoformat(),
        "income_events": income_events,
        "disposal_events": disposal_events,
        "total_income_usd": sum(e["income_usd"] for e in income_events),
        "total_realized_gain_usd": sum(
            e["gain_loss_usd"] for e in disposal_events if e["gain_loss_usd"] > 0
        ),
        "total_realized_loss_usd": sum(
            e["gain_loss_usd"] for e in disposal_events if e["gain_loss_usd"] < 0
        ),
    }

Monitoring & Alerts

Aave Health Factor Monitoring

HEALTH_FACTOR_THRESHOLDS = {
    "critical": 1.1,   # Alert immediately — liquidation imminent
    "warning": 1.3,    # Alert — add collateral or reduce debt
    "caution": 1.5,    # Notify — monitor closely
}

def check_health_factor(wallet: str, rpc_url: str) -> dict:
    """
    Check Aave v3 health factor for liquidation risk.
    Returns alert level and recommended action.
    """
    # Use cast or web3.py to call getUserAccountData
    # Returns: totalCollateralBase, totalDebtBase, availableBorrowsBase,
    #          currentLiquidationThreshold, ltv, healthFactor
    pass

LP Out-of-Range Detection (Uniswap v3)

# Check if v3 position is still in range (earning fees)
# currentTick within [tickLower, tickUpper] = in range
cast call 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 \
  "positions(uint256)(uint96,address,address,address,uint24,int24,int24,uint128,uint256,uint256,uint128,uint128)" \
  YOUR_NFT_ID --rpc-url YOUR_RPC

Data Sources Reference

ToolBest ForCost
DeBank Pro APIMost comprehensive, all protocols$99/mo
Zapper APIGood free tier, Ethereum + L2Free tier available
Zerion APIClean data, portfolio-focusedFreemium
MoralisMulti-chain, developer-friendlyFreemium
The GraphProtocol-specific subgraphsFree
Revert FinanceUniswap v3 concentrated LP analyticsFree
Alchemy/InfuraRaw RPC callsFreemium

Common Workflows

Monthly portfolio review:

  1. Pull snapshot via DeBank/Zapper
  2. Run IL calculator on all LP positions
  3. Flag any Aave/Compound positions below HF 1.5
  4. Flag any v3 positions out of range
  5. Export income events (fees, staking rewards) to crypto-tax-agent
  6. Generate markdown summary for treasury dashboard

Pre-tax-season export (Q1):

  1. Pull all 2025 transactions for tracked wallets
  2. Classify: income events vs capital events
  3. Calculate cost basis (FIFO default, HIFO optional)
  4. Reconcile staking rewards (ordinary income)
  5. Hand off structured JSON to crypto-tax-agent for 8949/Schedule D

New position entry:

  1. Record entry tx_hash, block, prices at entry
  2. Calculate cost basis (token values + gas)
  3. Store in position ledger
  4. Set monitoring thresholds (IL%, HF, range status)

Not For This Skill

  • Executing trades or transactions — use a wallet/trading skill
  • on-chain-payroll — PTIN-backed Moltlaunch service, not ClawHub
  • NFT portfolio tracking — different data model and APIs
  • CEX holdings (Coinbase, Kraken) — use a CEX API skill or crypto-tax-agent directly
  • Real-time price ticker — use a price feed skill
  • Bridging or swapping assets — use a transaction execution skill

Version tags

latestvk979fmjws7xpf9s6sfaszsb0gx836ctg