Skip to content

improve(svm): Type SVM events as a name-discriminated union - #1510

Open
pxrl wants to merge 5 commits into
pxrl/svm2from
pxrl/svm3
Open

improve(svm): Type SVM events as a name-discriminated union#1510
pxrl wants to merge 5 commits into
pxrl/svm2from
pxrl/svm3

Conversation

@pxrl

@pxrl pxrl commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Map each SvmSpoke event name to its generated event data type and type
EventWithData as a name-discriminated union, so checking event.name statically
narrows event.data. queryEvents() returns EventWithData for the requested
name, and SVMEventNames members are interchangeable with their string literals.
The decoder table's type annotation is tightened so each entry's name/data
correlation is proven at compile time. queryDerivedAddressEvents() deliberately
stays untyped (eventName: string, raw events): it is the generic query for
non-SvmSpoke programs, and the relayer's CCTP flows call it with plain string
event names a regression test pins that call pattern. findDeposit()'s
depositId cast is replaced by typed access, and mock event construction is
checked against the generated types, which surfaced a missing repaymentChainId
field in mocked FilledRelay events.

Map each SvmSpoke event name to its generated event data type and type
EventWithData as a name-discriminated union, so checking event.name
statically narrows event.data. queryEvents() and
queryDerivedAddressEvents() return EventWithData<T> for the requested
name, and SVMEventNames members are interchangeable with their string
literals. The decoder table constructs name/data pairs per event type,
proving the correlation at compile time. Mock event construction is
consequently checked against the generated types, which surfaced a
missing repaymentChainId field in mocked FilledRelay events. Supersedes
the interim runtime shape-check in findDeposit(); the types now carry
that proof.
@pxrl

pxrl commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 1753058dcf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@droplet-rl

Copy link
Copy Markdown
Contributor

The <T extends EventName> constraint turns the relayer's CCTP queries into compile errors.

src/arch/svm/eventsClient.ts:113queryDerivedAddressEvents<T extends EventName>(eventName: T, ...).

EventName is keyof typeof SVMEventNames, so it only admits SvmSpoke event names. The relayer calls this method with "DepositForBurn" and "MintAndWithdraw" (CCTPUtils.ts:751, adapter/bridges/SolanaUsdcCCTPBridge.ts:141, adapter/l2Bridges/SolanaUsdcCCTPBridge.ts:156), which are events of the CCTP token messenger / message transmitter programs and are not in SVMEventNames.

The narrowing itself is a genuine improvement — the previous eventName: string plus as EventWithData[] was hiding exactly this. The point is just that the SvmSpoke-only assumption is now baked into three separate places across the stack: this constraint, the decodeEvent assert in #1508, and the removal of the method entirely in #1513. Whatever the resolution, it probably wants to be one decision rather than three.

pxrl added 4 commits August 10, 2026 20:45
Non-mechanical resolution in decodeEvent(): composed svm3's typed
SvmSpoke dispatch (correlated DecodedEvent constructor table) with
svm2's CCTP codama decoder registry and generic Anchor fallback, in
that order. The two sides' event types cannot share one shape, so
types.ts gains RawDecodedEvent/RawEventWithData (shared envelope
extracted); queryAllEvents() and processEventFromTx() now carry raw
events, and the typed queryEvents() narrows raw to EventWithData<T>
via its existing predicate, which is sound because every typed event
is structurally a raw event. decodeEvent() returns
DecodedEvent | RawDecodedEvent accordingly.
…ddress query

Typed SvmSpoke event names don't apply here: downstream consumers (the relayer's
CCTP flows) query TokenMessengerMinter and MessageTransmitter events through this
method with plain string event names, so constraining it to EventName would break
them. Return raw (untyped) events instead, and document that results are scoped
to transactions referencing the derived address, so callers must associate events
themselves. Adds a regression test in the relayer's call pattern.
): Promise<EventWithData<T>[]> {
const events = await this.queryAllEvents(fromSlot, toSlot, options);
return events.filter((event) => event.name === eventName);
return events.filter((event): event is EventWithData<T> => event.name === eventName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: queryAllEvents() returns raw events because createFor(...) can bind this client to an arbitrary IDL, but this predicate treats name equality alone as proof of the SvmSpoke payload layout. An unrelated program can emit an event named FundsDeposited with different data and TypeScript will expose it as SvmSpokeClient.FundsDeposited. I reproduced this with raw data { fromAnotherProgram: true }: TypeScript accepted data.inputAmount as bigint, while it was undefined at runtime. Could queryEvents() require the exact bundled SvmSpokeIdl before returning typed events, or live on a Spoke-specific client instead?


Sent from Reinis Martinsons's Codex CLI Agent using gpt-5.6-sol 🤖

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.

3 participants