The upstream L20 left open: where value_defining_oracle comes from. ~13 min.
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.
latestRoundData(). Discovery's job is to connect "this token" ↔ "that feed" with zero on-chain link
to follow.
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:
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.
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 gate | New: cross-source check (validateCrossSource) | |
|---|---|---|
| How | An 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. |
| Cost | Manual curation per binding — onboarding an RWA needs a human. | Zero curation — onboarding is just registering the focus token. |
| Failure | — | Fail-open on read error or no independent signal (the steady state for most RWAs) — trust NAVLink. |
NavlinkFreshnessVerifier, part of the chainref quality harness from L23) that alarms when an
ORACLE_DEP edge exists but the bound token's price is missing/stale. This is the system's recurring shape:
move the check out of the hot path and into an independent monitor. Every discovered binding is also SADD'd
to navlink-bindings:{chain} as a durable operator audit trail.
For each confirmed binding, three RPCs read the feed (navlink_chain.go): aggregator(),
latestRoundData(), decimals(). Three decisions are worth your attention:
ORACLE_DEP reference it.updatedAt is older than the threshold, skip the stamp and count it (navlink.feeds.stale) — a stale NAV is worse than no NAV. Defensive decoding even handles a (hypothetical) negative answer rather than silently mangling it.decimals() is 8 for NAVLink feeds; NavlinkPriceUSD(answer, decimals) scales the raw int256 to USD.// ReadNavlinkProxyOwner — the on-chain admin who can publish arbitrary NAV owner := callOwner(proxy) // owner(), NOT accessController()
owner() and not accessController()accessController gates
reads — and NAVLink feeds are designed to be publicly readable, so that gate is wide open and useless as an admin
signal. The real power — publishing the NAV — lives at owner() on the proxy (often a Chainlink ops multisig).
That one key is the admin that L20's value-defining oracle cells attribute the whole supply to, and (per L25) if
it's a Safe, L22 fans it out to signers. Discovery here feeds admin discovery there.
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:
ORACLE_DEP edge + the stamped price are exactly what make a token a value-defining-oracle target — the trigger emitValueDefiningOracleCells fires on.SADDs the token to price-dirty:{chain}, so the PriceDirtyDrainer recomputes every HOLDS edge's USD value at the new price — the refresher pattern from L24, feeding the dollars L19–L21 consume.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.
validateCrossSource finds NO independent price (defillama/known_peg) for a token. It…updatedAt is older than the staleness threshold. The refresher…owner(), not accessController(). Why?owner() matters downstream because…SADDs the token to price-dirty:{chain}. What does that trigger?owner() (not accessController()) is the admin selector.price-dirty ripple.value_defining_oracle), L25 (the owner becomes the attributed admin), L24 (price refresh), and L23 (the verifier).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.