Lesson 26 · Oracle / NAV Discovery · Discovery Internals

How a token learns its price feed

The upstream L20 left open: where value_defining_oracle comes from. ~13 min.

Builds on: L20 · L25 · L24 Anchor: Chainlink feeds, RWA treasuries New: registry symbol-binding New: cross-source safety net

L20 hinged on a node attribute: T.attrs.value_defining_oracle. "If this marker is set," we said, "a bad NAV drains the whole supply." But we never asked the obvious question: how does the system know USTBL's price is set by a particular Chainlink feed in the first place? Nothing on-chain says "this token trusts that oracle." This lesson is the subsystem that figures it out — binds the token to its feed, prices it, and stamps the marker L20 depends on.

Your anchor: tokenized treasuries & Chainlink NAV feeds
RWA tokens — BUIDL, USTBL, WTGXX, ACRED, mTBILL — aren't priced by a market. Their price is a net asset value published by an off-chain administrator to a Chainlink feed (a "NAVLink" feed). The token contract reads that feed to value itself. You already know the Chainlink shape: a stable proxy in front of a swappable aggregator, exposing latestRoundData(). Discovery's job is to connect "this token" ↔ "that feed" with zero on-chain link to follow.

1 · The binding problem

There's no oracle() getter on these tokens pointing at the feed. So the NAVLinkRefresher (navlink_refresher.go) bridges the gap the same way a human would — by symbol:

Chainlink registryCDN feed list, symbol-keyed
focus tokensby symbol (Memgraph)
candidate binding(token, feed)
read + validatelatestRoundData()
stampprice + oracle + edge

It fetches Chainlink's published feed registry, loads focus tokens keyed by symbol from the graph, and intersects them (matchBindings — a pure function). A feed whose Symbol matches a focus token's symbol becomes a candidate binding. If a symbol matches several feeds (or vice versa), all combinations are produced — the binding isn't trusted yet.

2 · From whitelist to cross-source — a design evolution worth studying

Symbol-matching is fuzzy: a registry update adding a wrong-symbol feed could auto-bind and stamp a wrong price. How do you trust a binding? The answer changed, and the why is instructive:

Old: Redis whitelist gateNew: cross-source check (validateCrossSource)
HowAn operator manually confirms each (token, feed) binding in Redis before it stamps.If an independent price (defillama / known_peg) exists and diverges past tolerance, reject the stamp. Else accept.
CostManual curation per binding — onboarding an RWA needs a human.Zero curation — onboarding is just registering the focus token.
FailureFail-open on read error or no independent signal (the steady state for most RWAs) — trust NAVLink.

3 · Reading the feed (the EVM details that matter)

For each confirmed binding, three RPCs read the feed (navlink_chain.go): aggregator(), latestRoundData(), decimals(). Three decisions are worth your attention:

The admin angle — closing the loop with L25

// ReadNavlinkProxyOwner — the on-chain admin who can publish arbitrary NAV
owner := callOwner(proxy)   // owner(), NOT accessController()

4 · The write-back — and what it sets in motion

writeStamps emits one idempotent graphwrite per cycle:

MATCH (t:Entity {id:$tok, graph_id:$g})        // MATCH not MERGE — never stub a delisted token
MERGE (o:Entity {id:$proxy, graph_id:$g})       // the oracle node = the feed PROXY
SET t.usd_price=$p, t.usd_price_source='navlink', t.price_updated_at=$ts
MERGE (t)-[:ORACLE_DEP]->(o)                    // the dependency edge L20's eligibility reads

Two things follow, and both connect to lessons you've done:

L20's one unexplained input is now explained
The chain is whole: registry symbol-match → cross-source-validated binding → read the NAV off the proxy → stamp usd_price + ORACLE_DEP + discover the proxy owner() as admin → that's the value_defining_oracle L20 attacks, priced by a feed whose owner L25 attributes, refreshing USD values per L24. Idempotent MERGE+SET write-back, replay-safe — the L9 discipline again.

Check yourself

1. How does the NAVLinkRefresher connect a focus token to its Chainlink NAV feed, given no on-chain link exists?
2. Why was the Redis whitelist gate replaced by the cross-source check?
3. validateCrossSource finds NO independent price (defillama/known_peg) for a token. It…
4. Why does the oracle node reference the feed PROXY rather than the underlying aggregator?
5. A feed's updatedAt is older than the staleness threshold. The refresher…
6. The on-chain admin of a NAVLink feed is read via owner(), not accessController(). Why?
7. The discovered proxy owner() matters downstream because…
8. After stamping the price, the refresher SADDs the token to price-dirty:{chain}. What does that trigger?
↳ Ask your teacher
Try: "Show me NavlinkPriceUSD and how int256 + 8 decimals becomes USD." · "What exactly does the NavlinkFreshnessVerifier alarm on?" · "How does the Oracle Bridger turn this ORACLE_DEP into per-market eligibility (L20)?" · "Walk the source-priority race between navlink and defillama stamps." · "What's the registry CDN, and what happens when it's down?"

What you can now do

Discovery subsystem — three lessons deep, and it now wraps around the risk engine
L24 (flywheel) → L25 (who controls a token) → L26 (how a token is priced & bound to its oracle). Between them, you can trace every input the at_risk engine consumes — addresses, admins, severities, prices, oracle edges — back to the on-chain probe that produced it.

Grounded in: pkg/enrichment/navlink_refresher.go (NAVLinkRefresher registry symbol-binding, matchBindings, validateCrossSource + the whitelist→cross-source pivot, readAllFeeds staleness gate, writeStamps MATCH-token/MERGE-oracle/ORACLE_DEP/price + price-dirty SADD, recordDiscoveredBindings audit trail), pkg/enrichment/navlink_chain.go (ReadNavlinkLatestRound/ReadNavlinkDecimals/ReadNavlinkProxyOwner = owner() not accessController, proxy-not-aggregator). Verify against source — the code is the truth.