feat(contracts): tnt-core 0.18 compatibility behind a per-chain revision flag#3315
Conversation
…ion flag
0.18 chains are greenfield redeploys with three consumer-visible breaks
the dapp would hit silently:
- getBlueprint dropped operatorCount from the struct (7->6 fields): the
fixed-ABI decode throws and the catalog chain-fallback renders empty.
- MultiAssetDelegation LockInfo's expiryBlock slot now carries a unix
TIMESTAMP (same position/type, decode silently succeeds): comparing it
against a block number reads 'locked for decades' and locked balances
never become withdrawable in the UI.
- Display prose is event-sourced (indexer path; storage zeroed).
Add getTntCoreRevisionByChainId ('legacy' | 'v018') to dapp-config and
key the three divergent reads on it: readBlueprintCore() picks the
right getBlueprint tuple + blueprintOperatorCount view (both fetch
sites + the binary-upgrade owner gate), and the withdraw lock check
compares against now-seconds on v018. Every wired chain is 'legacy'
today (Base Sepolia probed live: blueprintDefinitionHash reverts =
pre-#193), so this is zero behavior change until a chain's addresses
are flipped to a 0.18 redeploy — flip the revision in that same change.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 644b3449
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-07-03T04:01:30Z
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 2 (1 low, 1 weak-concern) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 135.1s (2 bridge agents) |
| Total | 135.1s |
💰 Value — sound
Adds a per-chain tnt-core revision flag that keys two divergent reads (getBlueprint ABI shape, LockInfo timestamp-vs-blocknumber) for the upcoming 0.18 cutover, consolidating three duplicated getBlueprint decodes into one revision-aware helper — zero behavior change today, correct grain, no better a
- What it does: Introduces
getTntCoreRevisionByChainId(chainId): 'legacy'|'v018'in dapp-config (contracts.ts:154) that currently returns 'legacy' for every chain. It keys two reads that diverge across the tnt-core 0.18 contract revision: (1)readBlueprintCore(new file) selects either the existing 7-fieldgetBlueprinttuple (legacy, operatorCount in-struct) or a new 6-field tuple + the already-synced `blue - Goals it achieves: (1) Prevent silent breakage when a chain flips to tnt-core 0.18 greenfield redeploys: a dropped struct field throws inside per-id try/catch and empties the blueprint catalog, and a redefined LockInfo slot makes locked balances read as locked-for-decades. (2) De-risk the cutover by making it a single config flip tied to the address bump, not an ABI resync that would break live chains. (3) Collatera
- Assessment: Good change on its merits. The revision flag follows the exact
get*ByChainIdswitch grain that contracts.ts already uses for addresses — no new mechanism introduced. ThereadBlueprintCoreconsolidation is a real DRY improvement independent of the revision work: verified via grep that everyfunctionName: 'getBlueprint'call site (libs + apps) now routes through it, leaving zero orphaned direc - Better / existing approach: none — this is the right approach. Searched: (a) dapp-config for any existing contract-version/revision/ABI-variant concept — found none, so
getTntCoreRevisionByChainIdis not duplicative; (b) the full repo for otherfunctionName: 'getBlueprint'decodes — all three former sites are migrated to the helper, none missed; (c) runtime auto-detection as an alternative — viable for the blueprint stru - Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 2
- Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content
🎯 Usefulness — sound
A well-targeted, consolidation-positive fix that routes three duplicated getBlueprint decode sites through one revision-aware helper and branches the withdraw lock comparison by chain revision; every new surface is reachable today via the active chain-read fallback path.
- Integration: Fully wired and reachable. readBlueprintCore is consumed by all three places that previously inlined the getBlueprint decode: fetchBlueprintsOnChain (fetchBlueprintsOnChain.ts:65), fetchBlueprintByIdOnChain (:143), and useBlueprintOwner (BlueprintVersionsPanel.tsx:99). Those are reached by useBlueprints.ts:420/478 and :587/730 — the indexer-down fallback that is the ACTIVELY-USED path on testnet t
- Fit with existing patterns: Fits the codebase grain and improves on what it replaces. getTntCoreRevisionByChainId sits directly beside the established getContractsByChainId switch in the same file (contracts.ts:167), using the identical chain-keyed-dispatch pattern. readBlueprintCore consolidates what was triplicated getBlueprint decode logic (two copies in fetchBlueprintsOnChain + one in BlueprintVersionsPanel) into one typ
- Real-world viability: Holds up on the paths that fire today and the ones that will fire post-cutover. The legacy branch is byte-identical to prior behavior (same TANGLE_ABI, same fields). The v018 two-read Promise.all is fully covered by the existing per-id try/catch in all three consumers (fetchBlueprintsOnChain.ts:107, fetchBlueprintByIdOnChain.ts:179, BlueprintVersionsPanel.tsx:106), so a revert on either sub-read d
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
🔎 Heuristic Signals
🟡 Cruft: magic number added apps/tangle-dapp/src/pages/staking/withdraw/index.tsx
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
💰 Value Audit
🟡 Inlined v018 ABI fragment will need reconciliation with synced ABI at cutover [maintenance] ``
GET_BLUEPRINT_V018_ABI is defined locally in readBlueprintCore.ts:15 rather than in the synced libs/tangle-shared-ui/src/abi/tangle.ts. This is deliberate and correctly documented (the legacy 7-field entry at tangle.ts:1336 must keep decoding live chains, so a full resync now would break them), and the PR body explicitly scopes the resync+flip+address-bump as one future change. Reviewer note only: when that cutover lands, the local fragment should be removed in favor of the resynced entry so two
What this audit checks
It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.
| Pass | What it asks |
|---|---|
| Heuristic | Vague title? Whitespace-only or cruft-bearing diff? (content signals only) |
| Duplication | Do added function/class names already exist elsewhere in the repo? |
| Value Audit | What does it do? What goal does it achieve? Is it good? Better architecture or already-exists? |
| Usefulness Audit | Does it integrate and fit? Will it hold up in real use and actually get used? |
Findings are concerns, not blocks — the human reviewer decides what to do with them.
Problem
tnt-core 0.18 chains (greenfield redeploys) break three dapp reads silently:
getBlueprintdroppedoperatorCountfrom the struct (7→6 fields) — the fixed-ABI decode throws inside per-id try/catch, so the blueprint catalog's chain-fallback renders empty and the binary-upgrade owner gate disables itself.LockInfo.expiryBlocknow carries a unix timestamp in the same slot/type — decode succeeds, andtimestamp > blockNumberreads 'locked for decades': locked balances never become withdrawable in the UI.BlueprintDefinitionRecorded→ indexer; storage zeroed) — indexer side handled in feat(indexer): decode 0.18 event-sourced blueprint definitions + binary versions tnt-core#196.Solution
getTntCoreRevisionByChainId(chainId): 'legacy' | 'v018'in dapp-config, keying exactly the divergent reads:readBlueprintCore()— picks the rightgetBlueprinttuple ABI and sources the count fromblueprintOperatorCounton v018 (already in the synced ABI). Used by bothfetchBlueprintsOnChainsites +useBlueprintOwner.Zero behavior change today: every wired chain returns 'legacy' (Base Sepolia probed live —
blueprintDefinitionHash(0)reverts FunctionNotFound = pre-#193 deployment; local anvil is post-#193/pre-#194 which behaves 'legacy' on every branched surface). A 0.18 cutover flips the revision in the same change that updates the chain's addresses.Verification
Not included (deliberate): full ABI resync — the legacy
getBlueprintentry must keep decoding live chains; resync + revision flip + address bump land together at cutover.