Skip to content

Support solidity events and add high-level filter RPC wrappers - #86

Merged
kubo39 merged 3 commits into
mainfrom
implements-high-level-filiter-apis
Feb 14, 2026
Merged

Support solidity events and add high-level filter RPC wrappers#86
kubo39 merged 3 commits into
mainfrom
implements-high-level-filiter-apis

Conversation

@kubo39

@kubo39 kubo39 commented Feb 11, 2026

Copy link
Copy Markdown
Owner
  • Add FilterID type for type-safe filter ID handling
  • Add newFilter, getFilterChanges, getFilterLogs, uninstallFilter, newBlockFilter, newPendingTransactionFilter wrappers to RPCConnector
  • Fix convTo!LogsResponse: Nullable!(Log[]) doesn't support ~=
  • Add unit tests for all filter RPC wrappers

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds higher-level, type-oriented wrappers around the Ethereum JSON-RPC filter APIs to RPCConnector, introduces a FilterID type for safer filter identifier handling, and fixes convTo!LogsResponse to work with Nullable!(Log[]).

Changes:

  • Add FilterID struct to represent filter IDs returned by eth_newFilter / eth_newBlockFilter / eth_newPendingTransactionFilter.
  • Add RPCConnector wrapper methods for creating filters and fetching/uninstalling them.
  • Fix convTo!LogsResponse accumulation logic and add mock-based unit tests for several of the new wrappers.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
source/deth/util/types.d Fixes convTo!LogsResponse and adds the FilterID type.
source/deth/rpcconnector.d Adds filter RPC wrapper methods and associated mock-based unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread source/deth/util/types.d
Comment on lines 268 to 304
@@ -296,9 +296,11 @@ To convTo(To, _From)(const _From f) @safe pure
{
log.blockTimestamp = elem[`blockTimestamp`].str[2 .. $].to!ulong(16);
}
logs ~= log;
logArray ~= log;
}
}();
LogsResponse logs;
logs.logs = logArray;
return logs;

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

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

convTo!LogsResponse builds logArray via repeated ~= appends inside a loop, which causes repeated reallocations for large log sets. Since f.array.length is known, consider pre-sizing the array or using an appender/indexed assignment to avoid quadratic growth behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +379 to +385
Log[] getFilterChanges(FilterID filterID) @trusted
{
JSONValue rawResponse = eth_getFilterChanges(filterID.id);
if (rawResponse.isNull)
return [];
return rawResponse.convTo!LogsResponse.get;
}

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

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

getFilterChanges always attempts to decode the response as log objects (LogsResponse). Per the execution-api spec, eth_getFilterChanges returns different shapes depending on filter type (log filter => log objects, block filter => block hashes, pending tx filter => tx hashes). Since this PR also adds newBlockFilter/newPendingTransactionFilter, calling getFilterChanges with those filter IDs will fail to parse. Consider splitting this into typed wrappers (e.g., one returning Log[] for eth_newFilter IDs and another returning Hash[]/bytes[] for block/pending filters), or returning a SumType/raw JSONValue so callers can handle the variant response safely.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

We'll implement the more high-level approach later, such as:

| API | based on | retuning |
|-----|----------------|------------------|
| `watchLogs(filter)` | `eth_newFilter` | `Log[]` |
| `watchBlocks()` | `eth_newBlockFilter` | `Hash[]` |
| `watchPendingTransactions()` | `eth_newPendingTransactionFilter` | `Hash[]` |

jtx["address"] = filter.address.get.convTo!string.ox;
}
if (!filter.topics.isNull)
jtx["topics"] = filter.topics.get;

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

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

topics is being assigned as Hash[] (ubyte[32][]) directly into a JSONValue. The JSON-RPC schema expects topics as 0x-prefixed 32-byte hex strings (or arrays thereof), so serializing raw bytes here will not match what execution clients accept. Convert each topic hash to a hex string before assigning to jtx["topics"].

Suggested change
jtx["topics"] = filter.topics.get;
{
auto topicStrings = filter.topics.get
.map!(t => "0x" ~ toHexString(t[]))
.array;
jtx["topics"] = topicStrings;
}

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +366
JSONValue jtx;
if (!filter.from.isNull)
{
const block = filter.from.get;
mixin BlockNumberToJSON!block;
jtx["fromBlock"] = _block;
}
if (!filter.to.isNull)
{
const block = filter.to.get;
mixin BlockNumberToJSON!block;
jtx["toBlock"] = _block;
}

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

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

newFilter encodes block bounds using fromBlock/toBlock, but the existing getLogs wrapper in this same file builds the JSON with keys from/to (see getLogs around lines ~255-275). Since both use LogFilter, this inconsistency is likely to break one of the calls and is hard to maintain. Consider extracting a shared helper that builds the filter JSON and uses the execution-api field names consistently (typically fromBlock/toBlock).

Copilot uses AI. Check for mistakes.
Comment on lines +387 to +396
/// Wrapper for eth_getFilterLogs.
///
/// https://ethereum.github.io/execution-apis/api/methods/eth_getFilterLogs/
Log[] getFilterLogs(FilterID filterID) @trusted
{
JSONValue rawResponse = eth_getFilterLogs(filterID.id);
if (rawResponse.isNull)
return [];
return rawResponse.convTo!LogsResponse.get;
}

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

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

A wrapper for eth_getFilterLogs was added, but there is no corresponding mock-based unit test verifying that it correctly decodes log objects and calls eth_getFilterLogs (similar to the getFilterChanges tests below). Adding a unit test here would prevent regressions in JSON decoding and method wiring.

Copilot uses AI. Check for mistakes.
Comment thread source/deth/rpcconnector.d
- Add FilterID type for type-safe filter ID handling
- Add newFilter, getFilterChanges, getFilterLogs, uninstallFilter,
  newBlockFilter, newPendingTransactionFilter wrappers to RPCConnector
- Fix convTo!LogsResponse: Nullable!(Log[]) doesn't support ~=
- Add unit tests for all filter RPC wrappers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kubo39
kubo39 force-pushed the implements-high-level-filiter-apis branch from 64ae646 to 9cd7a20 Compare February 11, 2026 00:56
@kubo39 kubo39 mentioned this pull request Feb 11, 2026
5 tasks
kubo39 and others added 2 commits February 12, 2026 09:22
- Add LogFilterWatcher, BlockFilterWatcher, PendingTxFilterWatcher
  for HTTP polling-based event/block/tx monitoring via getChanges()
- Add watchLogs/watchBlocks/watchPendingTransactions factory methods
  to RPCConnector
- Add getFilterChangesHashes for block/pending tx filters returning Hash[]
- Add compile-time event code generation (allEvents) that produces
  typed event structs and decode methods from contract ABI
- Add dataInputTypes to ContractEvent, fix eventFromJson to parse
  indexed/non-indexed types with explicit loop (CTFE compatible)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add NumberChanged event to Counter contract and demonstrate
event watching with watchLogs/getChanges/decodeNumberChangedEvent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kubo39 kubo39 changed the title Add high-level filter RPC wrappers Support solidity events and add high-level filter RPC wrappers Feb 14, 2026
@kubo39
kubo39 merged commit dcb7a8c into main Feb 14, 2026
1 check passed
@kubo39
kubo39 deleted the implements-high-level-filiter-apis branch February 14, 2026 09:02
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.

2 participants