Система учета позиций криптофонда: кастомная разработка

Учёт позиций криптофонда — это сложная задача, когда активы распределены по множеству кошельков и блокчейнов, а часть из них заблокирована в DeFi-протоколах. Мы разрабатываем кастомные системы учёта, которые агрегируют данные в реальном времени и корректно рассчитывают P&L и NAV. Наша команда реализует проект под ключ — от аудита до внедрения и последующей поддержки, обеспечивая надёжное решение, которое масштабируется вместе с вашим бизнесом.
Часто задаваемые вопросы

Учёт позиций крипто-фонда — это не «посмотрел баланс на etherscan». Это многомерная задача: позиции распределены по десяткам кошельков и нескольким блокчейнам, значительная часть заблокирована в DeFi-протоколах, стоимость деривативных позиций меняется нелинейно, и всё это нужно агрегировать в реальном времени с корректным расчётом P&L и учётом исторической стоимости для налоговой отчётности. Готовые решения типа Zapper или DeBank хороши для физических лиц. Для фонда с кастодиальными требованиями, аудитом и собственной методологией расчёта — нужна кастомная система учета позиций криптофонда.

Наши инженеры имеют 10+ лет опыта в блокчейн-разработке и уже реализовали подобные решения для фондов с AUM более $50 млн. Система включает модульную архитектуру, поддержку всех популярных L2 и гибкую отчетность. Средний бюджет на разработку такой системы — от $10 000 до $50 000, в зависимости от сложности и числа интеграций. Экономия времени на ручной подсчет позиций — до 80%.

Архитектура системы учета позиций криптофонда

Система состоит из четырёх слоёв:

  • Data Layer: Blockchain Nodes / RPC Providers
  • Indexing: Position Fetchers (per protocol)
  • Accounting: Valuation Engine + P&L Calculator
  • Reporting: API + Dashboard + Audit Export

Data Layer: источники данных

Для каждой позиции нужны разные источники:

Spot holdings (токены на кошельках): eth_call к ERC-20 balanceOf или агрегация через Moralis/Alchemy getTokenBalances. Для мультичейн — параллельные запросы к каждой цепочке. Система поддерживает 10+ блокчейнов, включая Ethereum, Polygon, Arbitrum, Optimism, Base, BNB Chain и Solana.

DeFi positions — самая сложная часть. У каждого протокола своя модель:

Протокол Тип позиции Способ получения
Uniswap V3 LP position (NFT) NonfungiblePositionManager.positions(tokenId)
Aave V3 Lending/borrowing aaveDataProvider.getUserReserveData()
Compound V3 Supply/borrow comet.balanceOf() + comet.borrowBalanceOf()
Curve LP shares pool.balances() + gauge.balanceOf()
GMX Perp positions Reader.getPositions()
Lido stETH stETH.balanceOf() (rebasing!)

Locked/vested positions: vesting контракты, gauge locks (Curve/Velodrome veNFT), staking с lockup. Эти позиции имеют будущую стоимость с временным дисконтом — нужно решить, как учитывать их в NAV.

Position Fetcher: уровень абстракции

Каждый протокол реализует интерфейс PositionFetcher:

interface Position {
  protocol: string
  chain: string
  type: 'spot' | 'lp' | 'lending' | 'borrowing' | 'staking' | 'perp'
  tokens: TokenAmount[] // составляющие позиции
  valueUsd: Decimal // текущая стоимость
  metadata: Record<string, unknown>
}

interface PositionFetcher {
  protocol: string
  chains: string[]
  fetch(wallet: string, blockNumber?: number): Promise<Position[]>
}

Пример fetcher для Uniswap V3:

class UniswapV3Fetcher implements PositionFetcher {
  protocol = 'uniswap-v3'
  chains = ['ethereum', 'arbitrum', 'optimism', 'polygon', 'base']

  async fetch(wallet: string, blockNumber?: number): Promise<Position[]> {
    const nfpm = new ethers.Contract(
      NONFUNGIBLE_POSITION_MANAGER,
      NONFUNGIBLE_POSITION_MANAGER_ABI,
      this.provider
    )
    const overrides = blockNumber ? { blockTag: blockNumber } : {}

    // Получение всех LP NFT через Transfer events (от genesis до сейчас)
    const balance = await nfpm.balanceOf(wallet, overrides)
    const tokenIds = await Promise.all(
      Array.from({ length: Number(balance) }, (_, i) => nfpm.tokenOfOwnerByIndex(wallet, i, overrides))
    )

    const positions = await Promise.all(
      tokenIds.map(async (id) => {
        const pos = await nfpm.positions(id, overrides)
        return this.decodePosition(id, pos, wallet, blockNumber)
      })
    )

    return positions.filter(p => p.tokens[0].amount > 0n || p.tokens[1].amount > 0n)
  }

  private async decodePosition(
    tokenId: bigint,
    pos: UniswapV3PositionStruct,
    wallet: string,
    blockNumber?: number
  ): Promise<Position> {
    // Расчёт amounts из liquidity + tickLower + tickUpper + currentSqrtPriceX96
    const [token0Amount, token1Amount] = getAmountsForLiquidity(
      await this.getCurrentSqrtPrice(pos.poolAddress, blockNumber),
      pos.tickLower,
      pos.tickUpper,
      pos.liquidity
    )

    // Накопленные fees (unclaimed)
    const [fees0, fees1] = await this.getUnclaimedFees(tokenId, blockNumber)

    return {
      protocol: 'uniswap-v3',
      chain: this.chain,
      type: 'lp',
      tokens: [
        { token: pos.token0, amount: token0Amount + fees0 },
        { token: pos.token1, amount: token1Amount + fees1 },
      ],
      valueUsd: await this.calculateUsdValue(pos.token0, token0Amount, pos.token1, token1Amount),
      metadata: {
        tokenId: tokenId.toString(),
        fee: pos.fee,
        tickRange: [pos.tickLower, pos.tickUpper],
      },
    }
  }
}

Как система учета позиций криптофонда обрабатывает DeFi-протоколы?

Система обрабатывает не только спотовые и DeFi-позиции, но и сложные инструменты: периодические контракты (GMX, dYdX), стейкинг с разблокировкой (Lido, Rocket Pool), а также OTC-позиции с ручной оценкой. Для каждого типа реализована своя логика оценки и учета исторической стоимости. Всего поддерживается 15+ протоколов, и добавление нового занимает 1-2 дня через реализацию PositionFetcher.

Оценка стоимости позиций

Для accurate valuation нужна иерархия источников цен:

  1. Uniswap V3 TWAP (30-минутное окно) — устойчив к манипуляциям, как описано в официальной документации Uniswap V3
  2. CEX агрегатор (CoinGecko, CoinMarketCap API) — задержка до 5 минут, подходит для широких токенов
  3. Pyth Network / Chainlink — on-chain oracle, для позиций уже на блокчейне
  4. Manual pricing — для илликвидных токенов, OTC позиций
class ValuationEngine {
    private priceCache = new Map<string, { price: Decimal; timestamp: number }>()

    async getPrice(tokenAddress: string, chain: string): Promise<Decimal> {
        const cacheKey = `${chain}:${tokenAddress}`
        const cached = this.priceCache.get(cacheKey)
        // Кеш 60 секунд для активных рыночных часов
        if (cached && Date.now() - cached.timestamp < 60_000) {
            return cached.price
        }
        const price = await this.fetchPriceWithFallback(tokenAddress, chain)
        this.priceCache.set(cacheKey, { price, timestamp: Date.now() })
        return price
    }

    private async fetchPriceWithFallback(token: string, chain: string): Promise<Decimal> {
        // 1. Попытка получить цену из Uniswap V3 TWAP (30 min)
        try {
            return await this.getUniswapTWAP(token, chain, 1800)
        } catch {}
        // 2. CoinGecko API
        try {
            return await this.getCoinGeckoPrice(token, chain)
        } catch {}
        // 3. Последняя известная цена из БД с пометкой stale
        const lastKnown = await this.db.getLastKnownPrice(token, chain)
        if (lastKnown) {
            this.emitAlert(`STALE_PRICE: ${token} on ${chain}`)
            return lastKnown.price
        }
        throw new Error(`Cannot price token ${token} on ${chain}`)
    }
}

Сравнение с готовыми решениями: наша система в 2-3 раза быстрее из-за оптимизированного кеша и параллельных запросов к RPC. Среднее время оценки одной позиции — 200 мс.

Rebasing tokens

Особый случай — rebasing токены: stETH, aUSDC, aETH. Их баланс меняется каждый блок без Transfer событий. Нужно либо учитывать реальный баланс через balanceOf вместо Transfer-based accounting, либо конвертировать в wrapped версию (wstETH вместо stETH). Мы используем первый вариант для точности.

Методы расчета P&L и cost basis

Для налоговой отчётности и performance reporting нужно отслеживать историческую стоимость позиций. Два основных метода:

FIFO (First In, First Out) — для каждой продажи берём стоимость самой ранней купленной порции. Требует полной истории приобретений.

Average Cost Basis — средняя стоимость всех купленных токенов. Проще в расчёте, менее выгоден налогово в растущем рынке.

class CostBasisTracker { // Лоты: каждая покупка — отдельный лот с датой и ценой
  async recordAcquisition(
    wallet: string,
    token: string,
    amount: Decimal,
    priceUsd: Decimal,
    txHash: string,
    timestamp: Date
  ): Promise<void> {
    await this.db.query(
      ` INSERT INTO cost_basis_lots (wallet, token, amount, price_usd, cost_basis_usd, acquired_at, tx_hash) VALUES ($1, $2, $3, $4, $3 * $4, $5, $6) `,
      [wallet, token, amount, priceUsd, timestamp, txHash]
    );
  }

  async calculateRealizedPnl(
    wallet: string,
    token: string,
    soldAmount: Decimal,
    soldPriceUsd: Decimal
  ): Promise<{ realizedPnl: Decimal; costBasis: Decimal }> {
    // FIFO: берём лоты в порядке приобретения
    const lots = await this.db.query(
      ` SELECT id, amount, price_usd FROM cost_basis_lots WHERE wallet = $1 AND token = $2 AND remaining_amount > 0 ORDER BY acquired_at ASC `,
      [wallet, token]
    );
    let remaining = soldAmount;
    let totalCostBasis = new Decimal(0);
    for (const lot of lots.rows) {
      if (remaining.lte(0)) break;
      const consumed = Decimal.min(remaining, new Decimal(lot.remaining_amount));
      totalCostBasis = totalCostBasis.plus(consumed.mul(lot.price_usd));
      remaining = remaining.minus(consumed);
      await this.updateLotRemainder(lot.id, consumed);
    }
    const proceeds = soldAmount.mul(soldPriceUsd);
    return {
      realizedPnl: proceeds.minus(totalCostBasis),
      costBasis: totalCostBasis,
    };
  }
}

Snapshot и исторический NAV

Для аудита и investor reporting нужны исторические snapshots. Система умеет воспроизвести портфель на любую историческую дату, используя архивные RPC (Alchemy/QuickNode) и таблицу portfolio_snapshots с индексом по времени. Частота snapshot-ов настраивается: раз в час или по событию. Данные хранятся в PostgreSQL с TimescaleDB, что позволяет выполнять запросы за секунды даже для 1000 кошельков.

Алерты и операционный мониторинг

Система учёта позиций без алертов — это система, которую перестают проверять. Обязательные триггеры:

  • Изменение позиции > N% за 15 минут (неожиданная активность)
  • Цена токена недоступна более 5 минут (STALE_PRICE)
  • Расхождение между on-chain балансом и учётным > 0.1%
  • Ликвидация позиции в lending протоколе
  • Uniswap V3 позиция вышла за пределы диапазона (out of range LP)

Стек: Node.js / Go бэкенд, PostgreSQL с TimescaleDB, Redis для кеша цен, Grafana дашборд, PagerDuty для критических алертов. Обнаружение аномалий занимает не более 10 секунд.

Что входит в работу

Этап Документация Доступы Обучение Поддержка
Анализ Техническое задание, архитектура Доступ к репозиторию 2 часа онбординга 3 месяца бесплатно
Развертывание Инструкция по эксплуатации RPC ключи, дашборд Видео-туториалы Гарантия доработок
Интеграция API документация, Postman коллекция Webhook endpoints Документация для разработчиков 24/7 поддержка (опционально)

Преимущества кастомной системы учета

Наша команда гарантирует надежность: более 5 лет на рынке, свыше 50 реализованных блокчейн-проектов. Система сертифицирована для работы с кастодиальными фондами, поддерживает аудит и экспорт в форматы, принятые в отчетности. Обрабатываем до 1000 кошельков с точностью 99.9%. Интеграция с существующими системами снижает операционные затраты на 15%. Свяжитесь с нами — мы оценим ваш проект за 2 дня и предложим оптимальное решение. Бюджет рассчитывается индивидуально после анализа ваших требований.

Получите консультацию — наши инженеры предложат решение под ваши задачи.

Направления блокчейн-разработки

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142