Skip to content

fix: read uptimePercentage/uptimeSeconds from /v1/validators/uptime - #43

Merged
owenwahlgren merged 3 commits into
mainfrom
fix/l1-uptime-field-names
May 7, 2026
Merged

owenwahlgren merged 3 commits into
mainfrom
fix/l1-uptime-field-names

Conversation

@owenwahlgren

Copy link
Copy Markdown
Collaborator

Summary

The L1UptimeEntry type was declaring uptimePercent and uptime, but firn's /v1/validators/uptime is a verbatim passthrough of subnet-evm's validators.getCurrentValidators which emits uptimePercentage and uptimeSeconds. Every read against the wrong field names resolved to undefined, so every validator detail page rendered the "no uptime reported · subnet-evm tracks per-L1 uptime locally..." fallback even when the API was returning a healthy 100%.

Verified live against grotto

curl /v1/validators/uptime → {
  validators: [
    {
      nodeID: "NodeID-Bp1kqhp7JDQnyWywhkekSBh9kEiC4M9s1",
      validationID: "WQDRLab3oz9rTugScEHK9mtkDZvau5dFe3TKiydKYButDvHe7",
      weight: 100,
      isActive: true,
      isConnected: true,
      isL1Validator: true,
      uptimePercentage: 100,
      uptimeSeconds: 10996549
    },
    ...
  ]
}

Changes

  • L1UptimeEntry uses uptimePercentage / uptimeSeconds.
  • Three consumers (home ValidatorsCard, /validators index, /validators/[nodeID]) read the correct fields.
  • Comment on the type explains why the names match the upstream wire verbatim — firn doesn't rewrite this surface, and the previous naming silently hid the entire feature.

Test plan

  • pnpm exec tsc --noEmit clean
  • Once Vercel ships: validator detail pages on grotto/echo render 100% · via subnet-evm validators.getCurrentValidators instead of the "no uptime reported" fallback

When the appMessage on a /icm/<messageId> view decodes as a recognized
ICTT shape, replace the generic source↔destination card with three
new sections:

1. Summary card — natural-language one-liner: "Account 0x… transferred
   N TOKEN from <source> to <dest> via Teleporter ICTT", with the
   amount formatted against the token's decimals (resolved via
   useToken on the destination-side TokenRemote contract). Falls back
   to the raw uint256 with a "(raw)" tag when token metadata isn't in
   firn's catalog yet — better than silently rendering 12400000000 as
   a human number.

2. Transfer flow visualization — three nodes (source → relayer →
   destination) with dashed-line connectors and a per-edge label
   (LOCK / DELIVER). Source-side timing is N/A because firn here only
   indexes the destination chain; DELIVER renders the gap between the
   Receive and MessageExecuted events when both fired in the same
   destination tx (always near-zero on canonical Teleporter delivery).

3. Overview field list — flattens the per-event cards' useful data
   into a single labeled list (Message ID, Source/Destination Chain,
   Token, Amount, Sender/Recipient, Source TX [N/A], Destination TX,
   Relayer, Reward redeemer, End-to-End). Marks source-only fields
   N/A with a muted color rather than hiding them.

Falls through to the existing SidesCard + timeline for non-ICTT
messages (raw Teleporter, custom dapps, Warp-only flows). The
timeline below the new sections is unchanged — it still shows every
event with full struct fields for users who want the raw data.

Headline tag toggles "ICTT · INTERCHAIN TOKEN TRANSFER" vs "ICM ·
MESSAGE" based on whether the appMessage was recognized.

Backend dependency: requires firn >= the icm topic0 + ictt enum +
appMessage envelope-unwrap fixes (ava-labs/firn#50). Without those
the appMessage on every Teleporter event renders as bare bytesHex
and this page falls back to the generic view.
The ICTT preview was rendering the raw uint256 amount in scientific
notation (e.g. "1.4e14") with no token symbol — that's worse than
nothing because it suggests scale the user can't actually read.

Switch to a bulk-resolved token catalog: one useTokens({limit:50})
call at the home page level, address-keyed map passed down to every
row. Format precedence:

  1. Catalog hit with decimals → "0.3 HERESY → 0x3c7a…fec"
  2. Catalog hit, no decimals  → "HERESY → 0x3c7a…fec" (drop amount)
  3. Catalog miss              → "SINGLE_HOP_SEND → 0x3c7a…fec"

Catalog hit rows render in full ink color so they pop visually; misses
fall back to muted ink-3 so the user can scan for material transfers.

Token lookup uses teleporterMessage.destinationAddress (the on-chain
TokenRemote that received the transfer — and on most ICTT deploys
that contract IS the ERC-20). Falls through cleanly when we're
looking at a Send-side or settlement-only event.
The L1UptimeEntry type was declaring `uptimePercent` and `uptime`,
but firn's /v1/validators/uptime is a verbatim passthrough of
subnet-evm's validators.getCurrentValidators which emits
`uptimePercentage` and `uptimeSeconds`. Every read against those
fields resolved to undefined, so every validator detail page rendered
"no uptime reported · subnet-evm tracks per-L1 uptime locally..." even
when the API was returning a healthy 100% uptime.

Verified live against grotto:

  curl /v1/validators/uptime → {validators: [
    {nodeID, validationID, weight, isActive: true, isConnected: true,
     isL1Validator: true, uptimePercentage: 100, uptimeSeconds: ...},
     ...
  ]}

After the rename:

- L1UptimeEntry uses uptimePercentage / uptimeSeconds.
- Three consumers (home ValidatorsCard, /validators index, /validators/
  [nodeID]) read the correct fields.
- Comment explains why the names match the upstream wire instead of
  some normalized firn shape — firn doesn't rewrite this surface.
@owenwahlgren
owenwahlgren merged commit 1fab54e into main May 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant