Problem Statement
Contract logs are retrieved as raw cryptographic hex strings from the Soroban RPC getTransaction response. The current implementation displays these hex strings directly in the dashboard notification panel, forcing operators to manually decode each event by cross-referencing the Soroban contract specification. This adds 30-60 seconds of friction per alert and makes the real-time monitoring dashboard nearly unusable during high-attestation periods.
Technical Bounds & Invariants
- Soroban event topics: 4 topics per event (ContractId, Signature, Key, Value), each 32-byte hex
- Event body: SCVal encoded as XDR base64 (~200-500 bytes per event)
- Supported event types:
approve_attestation, reject_attestation, slash_node, reward_distributed, node_registered, node_deregistered, parameter_changed
- Decoding latency budget: < 10ms per event client-side
- Must handle unknown event types gracefully (display raw hex with a "not recognized" label)
Codebase Navigation Guide
- Primary target:
/src/hooks/useLedgerEvents.ts
- Hex decoder utilities:
/src/utils/hexDecoder.ts (to be created)
- Alert notification component:
/src/components/alerts/AlertBanner.tsx
- Event type definitions:
/src/types/ledgerEvents.ts
Step-by-Step Resolution Blueprint
- Define a TypeScript discriminated union
LedgerEvent = ApproveAttestationEvent | RejectAttestationEvent | SlashNodeEvent | RewardDistributedEvent | NodeRegisteredEvent | NodeDeregisteredEvent | ParameterChangedEvent | UnknownEvent in /src/types/ledgerEvents.ts
- Build
decodeLedgerEvent(rawHexTopics: string[], rawBase64Body: string): LedgerEvent in /src/utils/hexDecoder.ts that: (a) parses topic[0] as the event signature via scval_decode(topic[0]) using the @stellar/stellar-sdk xdr.ScVal.fromXDR method, (b) matches the signature against known event types using a lookup table, (c) decodes topic[1..3] and the body according to the matched type's schema, (d) returns a typed event object
- Create a React hook
useDecodedLedgerEvents() that wraps useLedgerEvents and applies decodeLedgerEvent to each incoming event via useMemo; memoize the decoded array with a Map<eventId, LedgerEvent> to avoid re-decoding events that have already been processed
- Build an
AlertBanner component that renders each LedgerEvent with: (a) human-readable title derived from the event type (e.g., "Node Slashed" for slash_node), (b) severity color coding (red for slash/error, yellow for warning, green for approval/reward), (c) actionable details link, (d) timestamp relative to now
- For
UnknownEvent types, render a fallback display: "Unknown Event — Raw: 0x{hexTopics[0].slice(0, 16)}..." with a "Copy Raw" button and a "Report Unknown Event" feedback action that logs the event shape to Sentry
- Add a notification sound (configurable, off by default) for high-severity events (
slash_node, reject_attestation) using the Web Audio API with a preloaded short buffer to avoid network requests
- Write unit tests using
vitest that encode known event structures with @stellar/stellar-sdk, decode them, and assert that: (a) each known type produces the correct human-readable output, (b) an unknown hex string produces an UnknownEvent without throwing, (c) decoding 1,000 events completes in under 100ms
Problem Statement
Contract logs are retrieved as raw cryptographic hex strings from the Soroban RPC
getTransactionresponse. The current implementation displays these hex strings directly in the dashboard notification panel, forcing operators to manually decode each event by cross-referencing the Soroban contract specification. This adds 30-60 seconds of friction per alert and makes the real-time monitoring dashboard nearly unusable during high-attestation periods.Technical Bounds & Invariants
approve_attestation,reject_attestation,slash_node,reward_distributed,node_registered,node_deregistered,parameter_changedCodebase Navigation Guide
/src/hooks/useLedgerEvents.ts/src/utils/hexDecoder.ts(to be created)/src/components/alerts/AlertBanner.tsx/src/types/ledgerEvents.tsStep-by-Step Resolution Blueprint
LedgerEvent = ApproveAttestationEvent | RejectAttestationEvent | SlashNodeEvent | RewardDistributedEvent | NodeRegisteredEvent | NodeDeregisteredEvent | ParameterChangedEvent | UnknownEventin/src/types/ledgerEvents.tsdecodeLedgerEvent(rawHexTopics: string[], rawBase64Body: string): LedgerEventin/src/utils/hexDecoder.tsthat: (a) parses topic[0] as the event signature viascval_decode(topic[0])using the@stellar/stellar-sdkxdr.ScVal.fromXDRmethod, (b) matches the signature against known event types using a lookup table, (c) decodes topic[1..3] and the body according to the matched type's schema, (d) returns a typed event objectuseDecodedLedgerEvents()that wrapsuseLedgerEventsand appliesdecodeLedgerEventto each incoming event viauseMemo; memoize the decoded array with aMap<eventId, LedgerEvent>to avoid re-decoding events that have already been processedAlertBannercomponent that renders eachLedgerEventwith: (a) human-readable title derived from the event type (e.g., "Node Slashed" forslash_node), (b) severity color coding (red for slash/error, yellow for warning, green for approval/reward), (c) actionable details link, (d) timestamp relative to nowUnknownEventtypes, render a fallback display: "Unknown Event — Raw: 0x{hexTopics[0].slice(0, 16)}..." with a "Copy Raw" button and a "Report Unknown Event" feedback action that logs the event shape to Sentryslash_node,reject_attestation) using the Web Audio API with a preloaded short buffer to avoid network requestsvitestthat encode known event structures with@stellar/stellar-sdk, decode them, and assert that: (a) each known type produces the correct human-readable output, (b) an unknown hex string produces anUnknownEventwithout throwing, (c) decoding 1,000 events completes in under 100ms