A practical TypeScript guide to discovering pools, joining token metadata, displaying meaningful metrics, handling multiple AMM types, and verifying pool state on TON.
A STON.fi pool explorer is essentially a read-only DeFi application that turns raw pool contracts and indexed protocol data into something people can understand. At minimum, it should answer a few questions quickly: which assets are in a pool, how much liquidity is there, what fees apply, how active is the pool, which STON.fi contract version powers it, and where can the underlying addresses be verified?
The good news is that you do not need to index TON from scratch. STON.fi exposes a public REST API for pools, assets, routers, statistics, simulations, and other protocol data. You can use that API as the discovery layer, then optionally read the pool contract directly when you want stronger on-chain verification.
This guide builds that architecture step by step.
What a useful STON.fi pool explorer should show
A pool explorer becomes useful when it combines several kinds of information that exist separately at the protocol level.
For each pool, a practical interface should expose:
- token names, symbols, images, decimals, and contract addresses
- both pool reserves in readable token units
- LP fee and other relevant fee information
- 24-hour trading volume
- recent APY values when available
- LP token supply and its USD value
- pool contract and router addresses
- router version and pool type
- warnings for deprecated or problematic assets
- pool-specific parameters for specialized AMMs
STON.fi's API already exposes much of this data. The main engineering problem is joining it correctly.
A pool response identifies its assets through token0_address and token1_address, while token metadata lives in the asset dataset. The pool also contains router_address, which you can join with the router dataset to identify the contract version and router type.
A simple data flow therefore looks like this:
- Load pools from
/v1/pools. - Load assets from
/v1/assets. - Load routers from
/v1/routers. - Index assets and routers by address.
- Enrich every pool with token metadata and router information.
- Normalize raw blockchain amounts using token decimals.
- Render searchable pool cards and detailed pool pages.
That is enough for a surprisingly capable first version.
Model the data around pools, assets, and routers
It is tempting to treat the pool response as a complete object, but keeping the three protocol concepts separate makes the application easier to maintain.
The current STON.fi pool schema includes fields such as:
address
token0_address
token1_address
reserve0
reserve1
lp_fee
protocol_fee
ref_fee
router_address
lp_total_supply
lp_total_supply_usd
volume_24h_usd
apy_1d
apy_7d
apy_30d
deprecated
amp
rate
w0
tags
The asset API provides the presentation layer around those addresses, including display_name, symbol, decimals, image_url, pricing fields, tags, and status flags.
The router endpoint adds another important dimension. Routers expose their major and minor contract versions as well as a router_type. Current router types can include Constant Product, Stable Swap, Weighted Stable Swap, Weighted Constant Product, and Constant Sum variants.
That distinction matters because your explorer should not assume that every pool behaves like a basic x * y = k AMM.
A useful mental model is:
| Layer | What your explorer gets from it |
|---|---|
| Pool | reserves, fees, LP data, volume, APY, pool parameters |
| Asset | symbol, name, decimals, image, pricing metadata, safety flags |
| Router | DEX version, router type, contract context |
| TON contract | direct verification of current on-chain state |
Keep these layers separate internally even if the UI eventually merges them into a single pool card.
Fetch and join the STON.fi API data
You can use the official @ston-fi/api TypeScript client, but raw HTTP requests make the underlying explorer architecture especially easy to see.
Start with a small loader:
const STON_API = "https://api.ston.fi";
async function getJson<T>(path: string): Promise<T> {
const response = await fetch(`${STON_API}${path}`);
if (!response.ok) {
throw new Error(
`STON.fi API returned ${response.status} for ${path}`
);
}
return response.json() as Promise<T>;
}
type Pool = {
address: string;
token0_address: string;
token1_address: string;
reserve0: string;
reserve1: string;
router_address: string;
lp_fee: string;
protocol_fee: string;
ref_fee: string | null;
lp_total_supply: string;
lp_total_supply_usd: string | null;
volume_24h_usd: string | null;
apy_1d: string | null;
apy_7d: string | null;
apy_30d: string | null;
deprecated: boolean;
amp: string | null;
rate: string | null;
w0: string | null;
tags: string[];
};
type Asset = {
contract_address: string;
display_name: string | null;
symbol: string;
decimals: number;
image_url: string | null;
deprecated: boolean;
blacklisted: boolean;
kind: string;
tags: string[];
};
type Router = {
address: string;
major_version: number;
minor_version: number;
router_type: string;
};
Then retrieve the datasets together:
async function loadExplorerData() {
const [poolData, assetData, routerData] = await Promise.all([
getJson<{ pool_list: Pool[] }>("/v1/pools?dex_v2=true"),
getJson<{ asset_list: Asset[] }>("/v1/assets"),
getJson<{ router_list: Router[] }>("/v1/routers?dex_v2=true"),
]);
const assets = new Map(
assetData.asset_list.map((asset) => [
asset.contract_address,
asset,
])
);
const routers = new Map(
routerData.router_list.map((router) => [
router.address,
router,
])
);
return poolData.pool_list.map((pool) => ({
...pool,
asset0: assets.get(pool.token0_address),
asset1: assets.get(pool.token1_address),
router: routers.get(pool.router_address),
}));
}
You now have one object that the frontend can render without repeatedly searching arrays.
For a prototype, loading the complete datasets is convenient. Once your explorer becomes larger, investigate /v1/pools/query, which supports search terms, sorting, limits, and other query conditions. Server-side filtering can reduce unnecessary data transfer.
Turn raw blockchain values into readable metrics
The next problem is units.
STON.fi reserves are returned in basic token units. A jetton with six decimals and a raw reserve of "2500000000" represents 2,500 tokens, not 2.5 billion tokens.
You therefore need the decimals from the asset metadata before displaying reserves.
A BigInt-safe formatter can look like this:
function formatUnits(raw: string, decimals: number): string {
const value = BigInt(raw);
const base = 10n ** BigInt(decimals);
const whole = value / base;
const fraction = value % base;
if (fraction === 0n) {
return whole.toString();
}
const fractionString = fraction
.toString()
.padStart(decimals, "0")
.replace(/0+$/, "");
return `${whole}.${fractionString}`;
}
Then:
const reserve0 = pool.asset0
? formatUnits(pool.reserve0, pool.asset0.decimals)
: pool.reserve0;
const reserve1 = pool.asset1
? formatUnits(pool.reserve1, pool.asset1.decimals)
: pool.reserve1;
Fees deserve similar care. STON.fi pool contract documentation defines fee values against a divider of 10,000. A fee value of 100, for example, corresponds to 1%.
For display:
function formatFee(value: string): string {
return `${Number(value) / 100}%`;
}
For sorting and presentation, converting USD metrics to JavaScript numbers is usually acceptable:
const volume24h = pool.volume_24h_usd
? Number(pool.volume_24h_usd)
: null;
For financial calculations where precision matters, keep the original integer or decimal strings and use BigInt or a decimal arithmetic library rather than binary floating-point math.
APY should also be labeled carefully. Fields such as apy_1d, apy_7d, and apy_30d describe recent annualized conditions. They are useful historical signals, not promised future returns.
Build the pool list and detail views
With normalized data, the frontend becomes straightforward.
A compact explorer card might contain:
function PoolCard({ pool }: { pool: any }) {
const token0 = pool.asset0;
const token1 = pool.asset1;
return (
<article>
<h3>
{token0?.symbol ?? "Unknown"} /{" "}
{token1?.symbol ?? "Unknown"}
</h3>
<p>
Type: {pool.router?.router_type ?? "Unknown"}
</p>
<p>
Version:{" "}
{pool.router
? `v${pool.router.major_version}.${pool.router.minor_version}`
: "Unknown"}
</p>
<p>
24h volume:{" "}
{pool.volume_24h_usd
? `$${Number(pool.volume_24h_usd).toLocaleString()}`
: "N/A"}
</p>
<p>LP fee: {formatFee(pool.lp_fee)}</p>
<code>{pool.address}</code>
</article>
);
}
The list page should optimize for comparison. Pair, liquidity value, 24-hour volume, fee, recent APY, pool type, and version are usually enough.
The detail page can go deeper.
Useful detail fields include:
- full pool, router, and token contract addresses
- normalized and raw reserves
- LP token supply
- protocol and referral fee settings
- collected protocol fees
-
amp,rate, orw0when applicable - pool and asset tags
- deprecated status
- 1-day, 7-day, and 30-day APY
- links to a TON blockchain explorer
Always display the underlying token addresses somewhere accessible. TON's token metadata documentation explicitly warns that anyone can create a jetton with a copied name, symbol, description, or image. A professional pool explorer should never identify an asset by ticker alone.
You can also use STON.fi's asset flags, such as deprecated or blacklisted status, to add visible warnings rather than silently treating every returned token as equivalent.
Handle pricing and pool types correctly
One of the easiest mistakes when building an AMM explorer is calculating:
price = reserve1 / reserve0
and calling the result the pool's current execution price.
That shortcut can be informative for a conventional constant product pool after adjusting both reserves for token decimals. It is not a universal pricing formula.
STON.fi supports multiple pool and router types, and the API exposes specialized parameters such as amp, rate, and w0. Those exist because different pool designs do not share one invariant.
A better explorer separates three concepts:
- Reserve ratio: a description of the pool's current token composition.
- Reference or statistical price: a price derived from indexed market statistics.
- Executable quote: the expected result for a particular trade size under the current pool conditions.
If the reader wants to know what a trade would actually return, use STON.fi's swap simulation endpoint rather than extrapolating from reserves. POST /v1/swap/simulate is designed to calculate expected swap output, fees, gas information, and other execution data before a transaction is built.
Trade size matters. A quote for 1 TON and a quote for 10,000 TON can imply different effective prices because the trade itself moves through pool liquidity.
This gives your explorer a useful optional feature: a small "simulate swap" panel on each pool page. It turns a static pool viewer into a tool for understanding liquidity depth without requiring a wallet connection or transaction.
Add on-chain verification and production hardening
The STON.fi REST API is the convenient discovery and analytics layer. The pool contract remains the source of on-chain state.
For DEX v2 pools, the documented get_pool_data getter exposes values including the router address, LP supply, token reserves, token wallet addresses, and liquidity pool fee. A verification mode can query the relevant contract through a TON RPC provider and compare selected values with the indexed API response.
That creates two complementary modes:
- Fast mode: use STON.fi API data for discovery, metadata, statistics, search, and normal page rendering.
- Verification mode: read selected contract state directly from TON when the reader wants to inspect the underlying pool.
Do not treat a temporary difference as automatic evidence that something is wrong. An indexed API and the latest blockchain state can briefly differ because they are observed at different moments.
Before publishing the explorer, I would also add this checklist:
- Cache pool, asset, and router requests instead of refetching everything on every render.
- Add graceful handling for missing metadata and nullable USD values.
- Search by token symbol, token address, and pool address.
- Make contract addresses copyable.
- Show deprecated and blacklisted warnings prominently.
- Never use token symbols as unique database keys.
- Distinguish pool types before calculating derived metrics.
- Keep the raw API values available for debugging.
- Record when your application last refreshed its data.
- Treat API schemas as external dependencies and validate responses at runtime.
The official STON.fi documentation currently states that the DEX API has no usage limits. That is useful for development, but production software should still cache sensible responses and avoid unnecessary traffic because service policies can change.
Practical takeaway: start the explorer with three STON.fi datasets: pools, assets, and routers. Join them by contract address, normalize reserves using token decimals, and render only metrics whose meaning you can explain precisely. Once that works, add simulation for executable pricing and direct TON contract reads for verification. That architecture keeps the first version simple without locking you into an inaccurate data model.
Frequently Asked Questions
Do I need to run a TON node to build a STON.fi pool explorer?
No. A basic explorer can use the public STON.fi REST API for pool discovery, token metadata, router information, volume, APY, and other indexed data. A TON RPC provider becomes useful when you want to verify contract state directly or add deeper blockchain functionality, but it is not required for the first working version.
Which STON.fi endpoint should I start with?
Start with GET /v1/pools, then join its token addresses with GET /v1/assets and its router addresses with GET /v1/routers. This combination gives you the core information needed for a pool directory. You can later use /v1/pools/{address} for individual pages and /v1/pools/query for more selective discovery.
Can I calculate a token's price directly from pool reserves?
Sometimes, but you should not use one reserve-ratio formula for every STON.fi pool. Different AMM types can use different pricing mechanics. A reserve ratio is useful as a pool-composition metric. For an expected trade result, STON.fi's swap simulation endpoint is a safer source because it evaluates a specific amount against the relevant routing and pool logic.
What should I use as the pool's liquidity or TVL figure?
STON.fi pool responses expose LP supply information, including lp_total_supply_usd when available, alongside the underlying reserves. You can use the USD LP supply value as a convenient liquidity-value metric in your interface, while keeping the field's actual meaning clear. If you calculate your own value from reserves and asset prices, document exactly how that calculation works.
Why do I need the router endpoint if I already have the pool?
The router tells you important context about the contract behind the pool, including its major and minor version and router type. That information matters once your explorer supports multiple STON.fi pool designs. It prevents the frontend from assuming that every pool follows the same pricing model or exposes the same specialized parameters.
Should a STON.fi pool explorer trust token names and symbols?
No. Names and symbols are presentation metadata, not unique identities. TON documentation warns that jettons can copy another token's name, symbol, or image. Display contract addresses and use STON.fi metadata flags where appropriate. Internally, index tokens by their contract addresses rather than by symbols such as USDT, TON, or STON.
How can I verify that the STON.fi API reserves match the blockchain?
Read the pool contract's documented get_pool_data getter through a TON provider and compare its reserves and other relevant state with the indexed API values. Small temporary differences can occur because indexing and direct blockchain reads happen at different times. For an explorer, API-first rendering plus optional on-chain verification is usually a practical architecture.
Sources and Further Reading
- STON.fi DEX API Reference - official list of pool, asset, router, statistics, simulation, wallet, and market endpoints
- STON.fi REST API documentation - overview of the public DEX data API and integration options
- STON.fi Swagger UI - current REST API schemas, including PoolInfoSchema, AssetInfoSchema, RouterInfoSchema, query parameters, and supported router types
- STON.fi API TypeScript client - official
@ston-fi/apiclient with methods for retrieving pools, assets, routers, wallet positions, and other protocol data - STON.fi DEX Architecture - official explanation of routers, pools, LP contracts, liquidity flows, and the protocol contract model
- STON.fi Pool v2 smart contract reference - documents
get_pool_data, reserves, LP supply, fee fields, router address, and other on-chain pool state - STON.fi v1 to v2 SDK migration guide - explains typed pool support and architectural differences developers should account for across STON.fi versions
- TON Token Metadata - official guidance on jetton metadata fields and why applications should distinguish tokens by contract address rather than names or symbols
- TON Run Get Method API - official TON API documentation for executing read-only smart contract getter methods when adding direct on-chain verification






Top comments (1)
Hi. If you find an error in the text or code above, please be sure to write about it in the comments here – it will be a great help to others reading this article. Thank you very much for your support and assistance!