Verify a treasury yourself

You never have to trust this interface. Everything BALLAST shows is computed from public on-chain data you can read yourself. Here is how to reproduce the backing figure from scratch.

1. Find the treasury

From a project page, take the project token address and read its immutable treasury() value on the explorer, or resolve it from the BallastFactory creation event. Confirm the treasury's projectToken() points back to the same token — this two-way check defeats a spoofed treasury.

2. List what it holds

On the ProjectTreasury contract, read:

  • assets() — every asset the treasury has held
  • lockedBalance(asset) — permanently locked amount
  • creatorWithdrawable(asset) — amount the creator could withdraw

The amount that counts toward backing is lockedBalance + creatorWithdrawable. Pending (proposed-but-not-accepted) deposits sit in escrow and are not counted.

3. Price each asset

For each asset, read its Chainlink feed:

(, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
uint8 feedDecimals = feed.decimals();     // read it — do not assume 8
  • Reject a zero or negative answer.
  • Check updatedAt. Off-hours the price rests with no new heartbeat — that is expected, not an error. Note the age; do not discard the price for being rested.
  • On an L2, check the Chainlink sequencer uptime feed first. During a sequencer outage feeds go stale while contracts still respond.
  • Do not multiply by the token's uiMultiplier(). The feed price already includes it; applying it again double-counts.

4. Do the arithmetic

value(asset)      = balance × price ÷ 10^(assetDecimals + feedDecimals)
total value       = Σ value(asset)
backing per token = total value ÷ (totalSupply ÷ 10^tokenDecimals)

Compare it to what the project page shows. They should match. If the chain and this interface ever disagree, the chain is the source of truth.

5. Watch for withdrawals

Read pendingWithdrawal() on the treasury to see any announced withdrawal — its asset, amount, and unlock time — without asking anyone's permission. Because only one withdrawal can be active at a time and the notice period is immutable, the countdown you see is the real one.

Reproduce it in code

The BackingLens contract does all of the above in a single batched call, and its source is verified on the explorer. You can call it directly, or copy its logic — it is the same math described here.

Verify the launch itself

Every token page also shows a live verification panel — one row per check, no narrative. Here is what each row actually means and how to reproduce it.

Source verified — is the contract's source code published and matching its bytecode on the block explorer, for both the token and its treasury? Read via Blockscout's public API (/api/v2/smart-contracts/{address}, is_verified).

Mint authority — BallastToken.sol has no mint() function anywhere in its source. Every Ballast launch shares the same creation bytecode, so this is a structural fact, not a per-token guess — confirmed by checking the token is a genuine BallastFactory launch (launchIdOf(token) > 0) rather than trusting it in isolation.

Mutable params — the only value a creator can change after launch is the project metadata (name, logo, links) via setMetadataURI, and every change is logged in a MetadataUpdated event forever. noticePeriod, the treasury pointer, and the creator address are all immutable.

Liquidity locked — once graduated, BallastSeeder holds the pool position permanently; it has no function that could remove it. Checked live by reading the pool's liquidity via StateView.getLiquidity(poolId) — zero would mean something is wrong, not that the lock failed.

Creator allocation — BallastToken's full supply mints to the factory at launch, never the creator, so the launch itself grants 0%. A nonzero live balance just means the creator later bought on the open market like anyone else — worth showing, not worth treating as a red flag.

Backing assets — each asset the treasury actually holds, checked against the same on-chain AssetRegistry the create flow itself reads, by address — never by ticker. An address that isn't registered renders as unrecognized; an address that isn't registered but claims a ticker that collides with a real asset's renders as hostile. See why ticker matching alone isn't safe — a real impostor pool with a fabricated multi-billion-dollar reserve exists on this chain today under a different address than the real asset it impersonates.

Sell simulation — a real V4Quoter.quoteExactInputSingle call through the token's actual pool and hook, for a small test amount, run at request time. A nonzero quote is live proof a sell path exists right now — not a cached assumption from whenever the pool graduated.