feat(0.19): support tnt-core 0.19 behind the per-chain revision flag (staggered)#3316
Conversation
…(staggered) Re-synced ABIs to tnt-core 0.19 and made every 0.19 break site revision-aware so 0.18 and 0.19 both work per-chain — NO hard cutover, no chain flipped to v019 yet. - dapp-config: add 'v019' to TntCoreRevision (getTntCoreRevisionByChainId stays 'legacy'). - abi/*: re-synced from canonical tnt-core 0.19 (blueprintSources/canScheduleExit/reportUri/ disputeReason gone; shorter getSlashProposal tuple). - useBinaryVersions: BinaryVersion.binaryUri / Attestation.reportUri -> string | null; v019 read ABIs (URI slots dropped); URI null on v019 (indexer-sourced follow-up). - useSlashing: v018 vs v019 getSlashProposal tuple ABIs + revision branch in the normalizer (proposedAt/disputeReason/disputedAt default to 0/null on v019); buildSlashTimeline tolerates proposedAt=0. - useCanScheduleExit: legacy direct-read kept; v019 fail-closed default. - readBlueprintCore: dedicated legacy getBlueprint ABI (0.19 dropped operatorCount from it). - UI: null binaryUri/reportUri render a graceful "unavailable on this chain". Write paths (publishBinaryVersion/attestBinaryVersion still take the URIs as params) unchanged. Verified: nx typecheck tangle-dapp + tangle-cloud pass; trustScore tests 11/11.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 0aff75e9
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-07T20:53:26Z
❌ Needs Work —
|
| deepseek | kimi-code | aggregate | |
|---|---|---|---|
| Readiness | 9 | 62 | 9 |
| Confidence | 75 | 75 | 75 |
| Correctness | 9 | 62 | 9 |
| Security | 9 | 62 | 9 |
| Testing | 9 | 62 | 9 |
| Architecture | 9 | 62 | 9 |
Reviewer score is advisory once the run is complete and the verdict has no blockers.
Full multi-shot audit completed 3/3 planned shots over 12 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 3/3 planned shots over 12 changed files. Global verifier still owns final merge decision.
Blocking
🟣 CRITICAL expiryBlock → expiryTimestamp ABI rename silently breaks all staking withdrawal lock displays — libs/tangle-shared-ui/src/abi/multiAssetDelegation.ts
The
getLocksoutput struct renamed field 2 fromname: 'expiryBlock'toname: 'expiryTimestamp'. Viem decodes return data using ABI component names as keys, so the decoded result will containexpiryTimestampnotexpiryBlock. Consumerapps/tangle-dapp/src/pages/staking/withdraw/index.tsx:299destructuresexpiryBlock, which becomesundefined. The lock-is-active checks at lines 310-311 (lock.expiryBlock > nowSeconds/lock.expiryBlock > currentBlockNumber) both evaluateundefined > number = false, making all locks appear unlocked. On v019 this misleads users; on legacy/v018 it also breaks because the ABI is shared across a
🔴 HIGH ABI type mismatch risk for operatorCount on legacy chains — libs/tangle-shared-ui/src/data/blueprints/readBlueprintCore.ts
The new
GET_BLUEPRINT_LEGACY_ABIfragment typesoperatorCountasuint64at line 28. The old syncedTANGLE_ABI(pre-PR, file line ~1289) typed the same struct field asuint32. If the actual legacy Solidity struct usesuint32, decoding with theuint64ABI will mis-read the subsequent fields (membership, pricing, active). The PR's README for this section states the fragment replaces the synced ABI for legacy reads but does not explain the type size change. Verify against the actual legacy contractTypes.Blueprintstruct definition in tnt-core.
Other
🟠 MEDIUM v019 documentation omits LockInfo/expiryTimestamp semantics — libs/dapp-config/src/contracts.ts
The v018 bullet (lines 146-147) documents that MultiAssetDelegation
LockInfo.expiryBlockis a unix TIMESTAMP, but the v019 bullet (lines 148-157) does not state whether v019 preserves that semantics or what the field is named. In the same PR, libs/tangle-shared-ui/src/abi/multiAssetDelegation.ts renames the ABI output fromexpiryBlocktoexpiryTimestamp. apps/tangle-dapp/src/pages/staking/withdraw/index.tsx still readslock.expiryBlockand only treats 'v018' as timestamp semantics, so a future chain flip to v019 will compa
🟠 MEDIUM multiAssetDelegation ABI field rename breaks existing locked-balance caller — libs/tangle-shared-ui/src/abi/multiAssetDelegation.ts
The LockInfo component was renamed from
expiryBlocktoexpiryTimestamp. The consumer in apps/tangle-dapp/src/pages/staking/withdraw/index.tsx:299-311 still accesseslock.expiryBlock. Because viem attaches named tuple properties from ABI component names,lock.expiryBlockbecomesundefinedafter this ABI change, makingundefined > bigintalways false and summing locked balances to zero for all users. Unlike readBlueprintCore/useSlashing/useBinaryVersions, this read was not given a revision-aware ABI fragment, so legacy/v018 callers break too. Fix: either update the withdraw page to useexpiryTimestamp(with revision-aware comparison) or provide a LockInfo ABI fragment selected byTntCoreRevisionand keep the synced ABI for v019 only.
🟠 MEDIUM blueprintSources function renamed to blueprintSourcesHash with structural output change — libs/tangle-shared-ui/src/abi/tangle.ts
The
blueprintSources(uint64)view was renamed toblueprintSourcesHash(uint64)and its output changed from a complexBlueprintSource[]struct tuple to a singlebytes32. No in-repo consumers found for either name. External consumers (scripts, indexers, other UIs importing TANGLE_ABI) that callblueprintSourceswill hit a 'function not found in ABI' error post-update. ThesetBlueprintSourceswrite function at the new entries (lines 3627-3780) uses the sameBlueprintSource[]struct as a calldata param, so the write path is unchanged — only the read path changed. Document the breaking rename in the PR body.
🟠 MEDIUM canScheduleExit view removed from synced ABI — v019 fallback blocks all exit scheduling UI — libs/tangle-shared-ui/src/abi/tangle.ts
The
canScheduleExit(uint64,address)view was removed from the synced ABI and replaced by the unrelatedcancelBlueprintTransfer(uint64)nonpayable.useCanScheduleExit.tshandles this correctly with a localCAN_SCHEDULE_EXIT_ABIfragment for legacy/v018 and a fail-closed{ canExit: false, reason: 'Exit eligibility unavailable on this chain' }default for v019. The consumerOperatorExitPanel.tsx:318shows the reason text as a yellow warning, blocking the 'Schedule Exit' button. This is safe (fail-closed) but means ALL operators on v019 cannot schedule exits through the UI until the follow-upgetExitStatus-based eligibility check is wired. Add a TODO linking to the tracking issue.
🟠 MEDIUM No tests for new revision-aware read branches — libs/tangle-shared-ui/src/data/blueprints/useBinaryVersions.ts
The PR adds revision-gated ABI selection in readBlueprintCore, useSlashing (getSlashProposal), and useBinaryVersions, plus a v019-only degradation in useCanScheduleExit. None of these branches have unit or integration tests. The only test in the changed set remains trustScore.spec.ts, which does not exercise ABI selection or tuple normalization. Add tests that verify decoding and normalization for both the legacy/v018 full tuples and the v019 short tuples, and that
binaryUri/reportUriare nulled correctly on v019.
🟡 LOW Default BinaryUpgradeABI export still carries URI-bearing read tuples — footgun for v019 reads — libs/tangle-shared-ui/src/abi/tangleBinaryUpgrade.ts
The default
export default ABIat line 470 still hasbinaryUri(string) andreportUri(string) in thegetBinaryVersion,effectiveBinaryVersion,getAttestation, andlistAttestationsoutput tuples (lines 40-44, 67-73, 186-199, 215-231). The PR guards against misuse viabinaryReadAbiFor()inuseBinaryVersions.ts:75-78, but any direct consumer ofBinaryUpgradeABIthat calls these views on v019 will decode misaligned data. Consider adding a runtime guard or removing the URI slots from
🟡 LOW No test coverage for revision-aware ABI selection and tuple normalization — libs/tangle-shared-ui/src/data/graphql/useSlashing.ts
The PR adds complex branching logic across 4 files (readBlueprintCore.ts, useSlashing.ts, useBinaryVersions.ts, useCanScheduleExit.ts) that selects ABIs and normalizes tuples based on
TntCoreRevision.normalizeOnChainSlashProposalalone has 15 positional fallback branches gated byisV019. None of this logic has test coverage. A positional index error (e.g.,proposal?.[6]vsproposal?.[7]forexecuteAfter) would silently produce wrong data in the UI. The existing test files (useCancelExitTx.spec.tsx,useScheduleExitTx.spec.tsx) only mockuseCanScheduleExit— they don't test the revision-aware path.
🟡 LOW v019 canScheduleExit always fails closed, disabling UI exit scheduling — libs/tangle-shared-ui/src/data/services/useCanScheduleExit.ts
For
TntCoreRevision === 'v019'the hook returns{ canExit: false, reason: 'Exit eligibility unavailable on this chain' }. This is an intentional fail-closed degradation noted as a follow-up, but it means operators on any v019 chain cannot schedule an exit through the UI even when eligible. Ensure the follow-up to derive eligibility fromgetExitStatus+ exit config is tracked before v019 chains are enabled.
tangletools · 2026-07-07T21:13:32Z · trace
tangletools
left a comment
There was a problem hiding this comment.
❌ 2 Blocking Findings — 0aff75e9
Full multi-shot audit completed 3/3 planned shots over 12 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 3/3 planned shots over 12 changed files. Global verifier still owns final merge decision.
Full immutable report for this review: trace
Summary comment for this run: full summary
tangletools · 2026-07-07T21:13:32Z · immutable trace
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 0 (none) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 986.0s (2 bridge agents) |
| Total | 986.0s |
💰 Value — sound
Adds tnt-core 0.19 read-path support behind the existing per-chain revision flag (no chain flipped), extending the exact pattern landed one commit earlier by the 0.18 PR — coherent, safe, well-documented.
- What it does: Re-syncs the canonical ABIs to the tnt-core 0.19 shape (which dropped several on-chain storage fields — binaryUri/reportUri from structs, proposedAt/disputeReason/disputedAt from the slash tuple, operatorCount already gone in 0.18, and removed the canScheduleExit view entirely — all now event-sourced), then adds local legacy ABI fragments + revision-keyed read-ABI selectors so legacy/v018 chains k
- Goals it achieves: (1) Forward-compat with tnt-core 0.19 contracts before any chain deploys them, so a flip is a one-line config change later. (2) Keep 0.18 and 0.19 both working per-chain — staggered, not a hard cutover. (3) Avoid the decode-misalignment footgun where a 0.18 (URI-bearing) tuple decoded against 0.19 returndata silently shifts every field after the dropped slot. (4) Degrade gracefully where 0.19 remo
- Assessment: This is a textbook extension of the revision-flag pattern established by PR #3315 (0.18) — same TntCoreRevision type, same getTntCoreRevisionByChainId selector, same co-located 'canonical ABI = latest + local legacy fragment + xxxReadAbiFor(chainId) ternary' shape, now in 3 consumer modules (readBlueprintCore:46-67, useBinaryVersions:75-78, useSlashing:812-815) plus the canScheduleExit:85 branch.
- Better / existing approach: none — this is the right approach. Searched for a centralized ABI-selection abstraction (rg 'revisionAbi|abiFor|abiByRevision|versionedAbi|selectAbi') and found only the same per-module xxxReadAbiFor pattern this PR uses — there is no existing registry to extend. A shared selectAbiByRevision(chainId, v019Abi, legacyAbi) helper would wrap a 3-line ternary without removing the contract-specific frag
- 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
Clean v019 compatibility behind the existing per-chain revision flag — extends the v018 pattern consistently, all integrations are reachable once a chain flips, error paths degrade gracefully, and no chain is flipped yet.
- Integration: All surface is reachable. The
getTntCoreRevisionByChainIdreturns'legacy'everywhere now, but every consumer —useBinaryVersions(binaryReadAbiForat useBinaryVersions.ts:75),useSlashProposalDetails(slashProposalReadAbiForat useSlashing.ts:812),useCanScheduleExit(useCanScheduleExit.ts:85),readBlueprintCore(readBlueprintCore.ts:89), and theexpiryTimestamprename in multiA - Fit with existing patterns: Extends the established per-chain revision pattern introduced in #3315 (v018) without deviating. Same mechanism:
TntCoreRevisionunion +getTntCoreRevisionByChainIdswitch + local ABI fragments selected by revision + safe defaults on the v019 path. The staking/withdraw page (apps/tangle-dapp/src/pages/staking/withdraw/index.tsx:309) already branches onv018the same way, and `readBlueprintCo - Real-world viability: Robust. Every chain read is wrapped in try/catch with safe defaults (e.g.,
useCanScheduleExitreturnscanExit: falseon v019;fetchBinaryVersionscatches per-version failures).normalizeBinaryVersion/normalizeAttestationuse??(not||) to correctly handle empty-string URIs.buildSlashTimelinetoleratesproposedAt = 0on v019 with a clear message. The ABI selection prevents the - Model: opencode/deepseek/deepseek-v4-pro
- Bridge attempts: 3
- Bridge warning: opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content
No concerns — sound change, no better or existing approach found. ✅
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.
…n-read tests Addresses the blocking review findings on the 0.19 adoption: - CRITICAL: withdrawal lock displays were reading LockInfo.expiryBlock, which the synced 0.19 ABI renamed to expiryTimestamp — viem keys decoded tuples by ABI component name, so every read was undefined and all locks showed as unlocked. Read expiryTimestamp on every revision; branch the comparison basis: legacy stores a BLOCK NUMBER (vs currentBlockNumber), 0.18/0.19 store a unix TIMESTAMP (vs wall-clock seconds). Same uint64 slot, only the value semantics differ. - HIGH: legacy getBlueprint ABI had operatorCount as uint64; the real pre-#194 struct is uint32. Wrong width mis-aligned the following membership/pricing/active fields on legacy. - MED: blueprintSources -> blueprintSourcesHash — grep confirms zero dapp callers of either; documented the rename (the setBlueprintSources WRITE is unchanged). - MED: canScheduleExit view removed -> fail-closed on v019; added TODO(v019) markers at the fallback and the exit-button block to track the getExitStatus follow-up. - MED: extracted the pure decode/ABI-select logic out of the wagmi-importing hooks into wagmi-free modules (slashProposal.ts, binaryVersion.ts, re-exported, zero consumer change) and added 11 revision tests: v019 nulls binaryUri/reportUri without throwing; v018 decodes the full 14-field slash tuple; v019 short-tuple positional decode has no index slip. - MED: documented the v019 LockInfo/expiryTimestamp semantics in getTntCoreRevisionByChainId. Verified: nx typecheck (tangle-dapp + tangle-cloud) pass; nx test tangle-shared-ui 85 pass (11 new + 74 existing); prettier clean; no .expiryBlock runtime reads remain.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 115a2b61
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-08T02:01:30Z
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 1 (1 low) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 284.3s (2 bridge agents) |
| Total | 284.3s |
💰 Value — sound
Extends the existing per-chain TntCoreRevision flag (legacy/v018 from PR #3315) with v019, adding revision-aware ABI selectors and tuple normalizers for the read surfaces 0.19 slimmed — clean, well-tested, ships inert behind the flag.
- What it does: Adds 'v019' to the TntCoreRevision union (contracts.ts:170) so the dapp can read tnt-core 0.19 contracts. 0.19 moved several on-chain storage fields off-chain (event-sourced): getSlashProposal dropped proposedAt/disputeReason/disputedAt; getBinaryVersion/effectiveBinaryVersion dropped binaryUri; listAttestations/getAttestation dropped reportUri; blueprintSources(uint64)->BlueprintSource[] became b
- Goals it achieves: (1) Let the dapp decode tnt-core 0.19 contract reads correctly once a chain is flipped — without that, every slash-proposal, binary-version, attestation, and blueprint-source read would either throw (tuple-length mismatch) or silently mis-align fields. (2) Keep legacy + v018 chains byte-identical (staggered rollout — flip a chain only when its 0.19 contracts deploy). (3) Degrade gracefully where 0
- Assessment: A textbook extension of the pattern PR #3315 established. That PR created TntCoreRevision + getTntCoreRevisionByChainId, readBlueprintCore.ts with revision-branching ABI selection, and the withdraw lock-basis branch. This PR applies the identical pattern to the remaining read surfaces 0.19 touched — same shape (pure wagmi-free decode module split out for unit testing, local legacy-tuple ABI fragme
- Better / existing approach: none — this is the right approach. Searched for an existing revision→ABI registry to reuse: the codebase has four revision-branching sites (readBlueprintCore.ts:93, binaryVersion.ts:48, slashProposal.ts:129, useCanScheduleExit.ts:88) and each branches for a DIFFERENT tuple shape, so a central registry would just replace four ternaries with four Map<revision,abi> lookups — no real win, and it would
- 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 — error
usefulness agent produced no parseable value-audit JSON.
- Model: opencode/deepseek/deepseek-v4-pro
- Bridge attempts: 3
- Bridge error: opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: bridge stream ended without value-audit content
🔎 Heuristic Signals
🟡 Cruft: todo added apps/tangle-cloud/src/pages/services/[id]/OperatorExitPanel.tsx
TODO(v019): `useCanScheduleExit` fail-closes to `{ canExit: false }` on
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.
Adds tnt-core 0.19 support behind the existing per-chain revision flag — staggered, not a hard cutover: 0.18 and 0.19 both work per-chain, and no chain is flipped to v019 (
getTntCoreRevisionByChainIdstill returnslegacyeverywhere). Flip a chain when its 0.19 contracts deploy.Changes
blueprintSources/canScheduleExit;reportUri/disputeReasongone from structs; shortergetSlashProposaltuple).'v019'added toTntCoreRevision.binaryUri/reportUri→string | null; v019 read ABIs drop the URI slots; URI null on v019 (comes from the indexer — see tnt-core indexer PR that adds attestationreportUri).getSlashProposaltuple ABIs + revision branch;proposedAt/disputeReason/disputedAtdefault on v019 (indexer-sourced);buildSlashTimelinetoleratesproposedAt=0.getBlueprintABI (0.19 droppedoperatorCountfrom it).binaryUri/reportUrirender a graceful "unavailable on this chain".publishBinaryVersion/attestBinaryVersionstill pass the URIs as params) unchanged.Verified
nx typechecktangle-dapp + tangle-cloud pass; trustScore unit tests 11/11.