Skip to content

Add event-log decoding to receipt (--event <cast-style signature>) #11

@erikzrekz

Description

@erikzrekz

Summary

Add event-log decoding to radius-cli receipt so a receipt's logs can be decoded against a cast-style event signature. Today receipt returns raw logs (topics + data); extracting a value from an emitted event means hand-parsing topics[n] and converting hex. A generic decoder — not tied to any specific contract — would make event-driven flows a clean composition.

This is a protocol-agnostic primitive, deliberately not an application command. (Context: a discussion about ERC-8004 agent registration concluded that registration semantics belong in a skill, but the one non-trivial step — reading an event value out of a receipt — is a generic gap worth closing in the CLI.)

Proposed interface

# Decode logs in a receipt that match the given event signature
radius-cli receipt 0x<hash> --event "Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"

# Repeatable for multiple events; --json emits structured decoded logs
radius-cli receipt 0x<hash> \
  --event "Transfer(address indexed,address indexed,uint256 indexed)" \
  --event "Registered(uint256 indexed,string,address indexed)" \
  --json
  • --event <sig> (repeatable): a cast-style event signature. The indexed keyword matters — it's how the decoder knows which params are in topics vs data, so accept and honor it (mirrors cast).
  • Matching is by topics[0] == the event's keccak256 selector. Non-matching logs are passed through (raw) so nothing is silently dropped.
  • Human output: event name + named args per matching log. --json: an array of { address, eventName, args: { name: value }, logIndex } (bigints as decimal strings, consistent with the existing --json convention).

Why this fits radius-cli

  • The CLI already parses cast-style signatures (parseCastSignature / coerceArg in src/lib/signature.ts) and uses viem (encodeFunctionData / decodeFunctionResult). Event decoding is the symmetric primitive: viem already provides parseAbiItem("event Transfer(...)") + decodeEventLog.
  • Keeps the tool cast-shaped — a generic primitive over receipts, no per-protocol or per-contract knowledge, no hardcoded addresses.

Motivating example (one of many)

Extracting the minted agentId from an ERC-8004 register(string) transaction is currently:

cast to-dec $(radius-cli --json receipt $TX | jq -r '.logs[0].topics[3]')   # fragile: assumes log/topic position

With this feature it becomes self-describing and position-independent:

radius-cli --json receipt $TX --event "Transfer(address indexed,address indexed,uint256 indexed tokenId)" \
  | jq -r '.[0].args.tokenId'

The same primitive serves any event (ERC-20 Transfer/Approval, escrow JobCreated, payment PaymentIntentSettled, etc.).

Acceptance criteria

  • radius-cli receipt <hash> --event <sig> decodes logs whose topics[0] matches the event selector and prints event name + named args.
  • --event is repeatable; all provided signatures are tried against every log.
  • indexed is honored so indexed vs non-indexed params decode correctly; a signature missing indexed either errors clearly or documents the limitation.
  • --json emits a structured array (address, eventName, args, logIndex); bigints serialized as decimal strings.
  • Logs that match no provided signature are passed through raw, not dropped.
  • Malformed event signatures fail with a clear message.
  • Tests cover: a single indexed-arg event, a mixed indexed/non-indexed event, multiple --event filters, and a no-match passthrough.

Out of scope

  • A 4-byte/event-signature lookup database (caller supplies the signature, same as call/tx).
  • Full-ABI receipt decoding from an --abi <file> (could be a follow-up; this issue is the signature-based path).
  • Any ERC-8004 / application-specific command.

References

  • Existing signature parsing: src/lib/signature.ts (parseCastSignature, coerceArg)
  • Current receipt command (raw logs only): src/commands/receipt.ts
  • viem helpers: parseAbiItem, decodeEventLog

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions