TractionEye: net-worth indexer for TON wallets

An indexer that rebuilds any TON wallet's net-worth history on demand, from native coins to jettons and DEX LP positions.

Domain
Fintech, TON blockchain
Our role
Architecture, backend, data engineering, DevOps, load testing
Year
2024

TractionEye is a Telegram Mini App that shows how a TON wallet's net worth changes over time, with a chart and a list of assets, prices and USD values. We designed and built its backend: Python indexing microservices that rebuild a wallet's full balance history on the first request from public blockchain data, price every asset including DEX liquidity-pool tokens, and serve chart-ready time series from TimescaleDB on PostgreSQL 16.

TractionEye mini app: wallet net-worth chart for a one-month range and an asset list with price and USD value

Challenge

  • Public APIs return current state and raw transactions, not balance history. We needed that history for TON and for every jetton a wallet ever held.
  • Charts for one day and one month need historical USD prices for each token at a fine enough resolution.
  • DEX LP tokens have no market price. Their value has to be derived from pool reserves and total supply at each point in time.
  • Any user can open any wallet, so indexing has to start on the first request and finish in seconds to minutes, not hours.
  • All data comes from public TON providers with per-key RPS caps, shared by several workers running in parallel.

Solution

We built a pipeline of independent, single-purpose services around a shared time-series database. The wallet indexer pages through transactions by logical time and replays them backwards from the current balance, for TON and for each jetton wallet. The price indexer detects new tokens, backfills up to two years of USD prices with an adaptive step and then keeps the series current. The pool indexer discovers DeDust and STON.fi pools and prices their LP tokens from reserves and supply read from archived chain state. The query API joins balances with prices in time buckets and returns a ready net-worth series to the mini app. An OpenResty gateway handles TLS with automatic certificate renewal. The first API request registers a wallet and the workers pick it up right away; after that its history grows one month deeper per pass, and recent activity is refreshed every few minutes.

Architecture

Architecture: gateway, query API, three indexers, time-series storage and public TON data providers
Architecture: gateway, query API, three indexers, time-series storage and public TON data providers

What the system does

Net worth over time
A net-worth series for any TON wallet, at the period and resolution the client asks for.
Per-asset breakdown
Balance, current price and USD value for TON, jettons and LP tokens, plus a per-token history endpoint for drill-down charts.
On-demand onboarding
The first request for a wallet starts indexing it, with no manual setup.
Continuous refresh
Recent activity is picked up every few minutes, and history grows deeper in the background.
LP position valuation
LP token prices for DeDust and STON.fi pools, including handling of DEX farming transfers.
Docs and debug views
OpenAPI (Swagger) documentation and HTML views for checking computed series by eye.

Engineering decisions

  • Exact balances without a full node: the indexer starts from the current balance and walks back through every transaction, subtracting amounts and fees, to get the exact balance after each event. Jetton transfer direction and amount are decoded from message opcodes.
  • A work queue in the database: workers claim jobs with one atomic "select, lock and return" update, so many processes run in parallel with no external broker. Each step is a single database transaction, so a crash loses at most one in-flight unit of work.
  • Rate-limit-aware API clients: each provider key sits behind a token-bucket pool that hands out at most N permits per second, and failed calls retry with bounded attempts.
  • Time-series queries on TimescaleDB: time_bucket aggregation plus carry-forward of the last known balance turn sparse transactions into evenly spaced chart points.
  • Load testing: distributed Locust workers on Docker Swarm and a separate time-to-first-chart benchmark. In those tests, the median first sync ranged from about 13 seconds for the smallest wallets to about 2 minutes for the most active ones.

Diagrams

Data flow: first request for a wallet, on-demand indexing and the chart query
Data flow: first request for a wallet, on-demand indexing and the chart query
Deployment: shared base image, one config branch per environment, containerized host
Deployment: shared base image, one config branch per environment, containerized host

Stack

  • Python 3.12
  • Flask
  • uWSGI
  • SQLAlchemy 2
  • PostgreSQL 16
  • TimescaleDB
  • OpenResty
  • Lua
  • Docker Compose
  • Ansible Vault
  • Locust
  • pytoniq
  • Telegram Mini Apps

What this shows

  • Blockchain indexers that turn raw on-chain transactions into analytics-ready time series
  • DeFi valuation: pricing tokens and liquidity-pool positions from on-chain state
  • Microservices that coordinate through the database with no message broker and stay within third-party rate limits
  • Production delivery: containers, automated TLS, encrypted configuration and repeatable multi-environment deploys

Questions about this case

Do you need your own TON node to get balance history?

No. The indexer reads from public TON data providers and rebuilds history by replaying transactions backwards from the current balance. For LP token prices, pool state at a given block height is read from an archive endpoint.

How long until a new wallet's chart appears?

Indexing starts on the first request. In load tests, the median first sync ranged from about 13 seconds for small wallets to about 2 minutes for the most active ones. Deeper history keeps filling in the background.

Can this approach be reused for other products?

Yes. The same design fits portfolio trackers, PnL and tax reporting, wallet analytics and treasury dashboards, on TON or other account-based chains.