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
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
Summary
Add event-log decoding to
radius-cli receiptso a receipt's logs can be decoded against a cast-style event signature. Todayreceiptreturns raw logs (topics + data); extracting a value from an emitted event means hand-parsingtopics[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
--event <sig>(repeatable): a cast-style event signature. Theindexedkeyword matters — it's how the decoder knows which params are intopicsvsdata, so accept and honor it (mirrorscast).topics[0]== the event'skeccak256selector. Non-matching logs are passed through (raw) so nothing is silently dropped.--json: an array of{ address, eventName, args: { name: value }, logIndex }(bigints as decimal strings, consistent with the existing--jsonconvention).Why this fits radius-cli
parseCastSignature/coerceArginsrc/lib/signature.ts) and uses viem (encodeFunctionData/decodeFunctionResult). Event decoding is the symmetric primitive: viem already providesparseAbiItem("event Transfer(...)")+decodeEventLog.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
agentIdfrom an ERC-8004register(string)transaction is currently:With this feature it becomes self-describing and position-independent:
The same primitive serves any event (ERC-20
Transfer/Approval, escrowJobCreated, paymentPaymentIntentSettled, etc.).Acceptance criteria
radius-cli receipt <hash> --event <sig>decodes logs whosetopics[0]matches the event selector and prints event name + named args.--eventis repeatable; all provided signatures are tried against every log.indexedis honored so indexed vs non-indexed params decode correctly; a signature missingindexedeither errors clearly or documents the limitation.--jsonemits a structured array (address,eventName,args,logIndex); bigints serialized as decimal strings.--eventfilters, and a no-match passthrough.Out of scope
call/tx).--abi <file>(could be a follow-up; this issue is the signature-based path).References
src/lib/signature.ts(parseCastSignature,coerceArg)receiptcommand (raw logs only):src/commands/receipt.tsparseAbiItem,decodeEventLog