Hartii developer docs

HartiiLabs

Pricing a token

If you only read one page, read this one. Every number below was read from Quai mainnet on 2026-09-23 (block 10,250,105) and checked against a simulated trade.

The short answer

Don't compute it — ask the API. GET https://hartiilabs.com/api/token/<address> returns the indexer's price and market cap, already correct:

bash
curl -s https://hartiilabs.com/api/token/0x0035187a7660f595d93cd53a4d16c635d6cffc8f
FieldMeaning
token.lastPriceWeiSpot price in wei of QUAI per 1 whole token (divide by 1e18 for QUAI).
token.totalSupplyWeiLive total supply: 1,000,000,000 minted − everything burned.
token.marketCapWeilastPriceWei × totalSupplyWei / 1e18, in wei of QUAI.
token.burnedWeiTokens burned all time (null = no burn indexed).

All wei values are decimal strings — parse them with BigInt, never Number or parseInt.

If you want to compute it yourself from the chain (to avoid trusting us, or for a bot that must react inside a block), follow the rest of this page.

1. Spot price

A token is in one of two phases. Read curve.graduated() first (find the curve with factory.curveOf(token)).

Bonding phase (graduated() == false) — the curve prices off virtual reserves:

text
quaiReserve  = virtualQuaiReserve + realQuaiReserve
tokenReserve = virtualTokenReserve − tokensSold
price        = quaiReserve / tokenReserve        (QUAI per token)

Graduated (graduated() == true) — the curve's own internal pool:

text
price = poolQuaiReserve / poolTokenReserve

2. What a trade actually returns

quoteBuy and quoteSell do not apply the trading fee (feeBps, 100 = 1%). The real buy() takes the fee off the QUAI you send before swapping; the real sell() takes it off the QUAI it pays out.

text
buy:   tokensOut = quoteBuy(value − value × feeBps / 10000)
sell:  quaiOut   = quoteSell(tokensIn) × (10000 − feeBps) / 10000

Verified against a simulated buy() of exactly 1 QUAI:

BFDQ (bonding phase)QAXE (graduated)
feeBps100100
Spot price (QUAI per token)0.00002025710.0030421426
quoteBuy(1 QUAI)wrong49,362.72328.71
quoteBuy(0.99 QUAI) — correct48,869.12325.43
Simulated buy() with 1 QUAI48,869.12325.43

The correct formula matches the real trade to the wei; the naive quote overstates it by about 1%.

3. Market cap

text
marketCap = price × token.totalSupply()

Read totalSupply() live from the token (or use the API's totalSupplyWei). Every Hartii token is minted with exactly 1,000,000,000 and burns are the only thing that changes supply — verified on all 33 mainnet tokens: totalSupply() == 1,000,000,000 − burned, to the wei.

Live supplyMarket cap (QUAI)
BFDQ1,000,000,00020,257
QAXE877,074,085 (122.9M burned)2,668,184

Complete example (quais)

js
import { quais } from 'quais';

const provider = new quais.JsonRpcProvider('https://rpc.quai.network/cyprus1', undefined, { usePathing: false });
const addr = (a) => quais.getAddress(a.toLowerCase()); // the Quai RPC rejects lowercase addresses

const factory = new quais.Contract(addr('0x001AF1BbB40807fcb99C9Eeaa49dF5E91e7Efd42'),
  ['function curveOf(address) view returns (address)'], provider);
const CURVE_ABI = [
  'function graduated() view returns (bool)', 'function feeBps() view returns (uint256)',
  'function virtualQuaiReserve() view returns (uint256)', 'function realQuaiReserve() view returns (uint256)',
  'function virtualTokenReserve() view returns (uint256)', 'function tokensSold() view returns (uint256)',
  'function poolQuaiReserve() view returns (uint256)', 'function poolTokenReserve() view returns (uint256)',
  'function quoteBuy(uint256) view returns (uint256)', 'function quoteSell(uint256) view returns (uint256)',
];

export async function priceToken(tokenAddress) {
  const token = new quais.Contract(addr(tokenAddress), ['function totalSupply() view returns (uint256)'], provider);
  const curve = new quais.Contract(await factory.curveOf(addr(tokenAddress)), CURVE_ABI, provider);
  const graduated = await curve.graduated();
  const [qr, tr] = graduated
    ? await Promise.all([curve.poolQuaiReserve(), curve.poolTokenReserve()])
    : await Promise.all([
        Promise.all([curve.virtualQuaiReserve(), curve.realQuaiReserve()]).then(([v, r]) => v + r),
        Promise.all([curve.virtualTokenReserve(), curve.tokensSold()]).then(([v, s]) => v - s),
      ]);
  const priceWei = (qr * 10n ** 18n) / tr;            // wei of QUAI per whole token
  const supply = await token.totalSupply();
  const marketCapWei = (priceWei * supply) / 10n ** 18n;

  const feeBps = await curve.feeBps();
  const quoteBuy = (quaiWei) => curve.quoteBuy(quaiWei - (quaiWei * feeBps) / 10000n);
  const quoteSell = async (tokensWei) => ((await curve.quoteSell(tokensWei)) * (10000n - feeBps)) / 10000n;

  return { graduated, priceWei, supply, marketCapWei, quoteBuy, quoteSell };
}

When you actually trade, pass a slippage floor built from the fee-correct quote — e.g. minTokensOut = quote × 99 / 100 — to buy(minTokensOut).

The three mistakes everyone makes

  1. Quoting with quoteBuy(value) — overstates by the 1% fee. Take the fee off first.
  2. Estimating supply from curveSupply (e.g. curveSupply × 10 / 8) — wrong whenever a token has a creator allocation or any burns. For QAXE it overstated market cap by about 12%; for POEM (344M burned) by about 53%. Use totalSupply() / totalSupplyWei.
  3. Pricing from the curve's QUAI balance — includes unwithdrawn fees. Use the reserve getters.

Around graduation

At the graduating trade the pool is seeded from the curve's real reserves only (virtual liquidity does not carry over), so the price after graduation can differ from the price just before. This is deliberate — see Bonding curve › Graduation mechanics.

QAXE's second market

QAXE also trades on a standard Quainance V2 pair. Its price is reserveWQUAI / reserveQAXE and its fee is 0.3%; arbitrage keeps it aligned with the curve pool (0.001% apart when checked). See QAXE markets.

Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.