Skip to content

Commit eb33d48

Browse files
committed
feat(Runner): support multiple addresses for event container
1 parent fb1f92b commit eb33d48

3 files changed

Lines changed: 42 additions & 7 deletions

File tree

bots/example.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ async def handle_mints(log):
9393

9494
# You can use generic `ContractContainer.EventType`s, to get matching logs from any contract
9595
# NOTE: This will match based on `event_id := keccak(event.selector)`, so any matching will work
96-
@bot.on_(Token.Approval, spender=ROUTER)
96+
# NOTE: You can filter on logs from multiple addresses using `from_addresses=`
97+
@bot.on_(Token.Approval, from_addresses=["YFI", "WBTC", "USDT"])
9798
# Any handler function can be async too
9899
async def exec_event2(log: ContractLog):
99100
token = Token.at(log.contract_address)

silverback/main.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import atexit
2+
from collections.abc import Sequence
23
import inspect
34
from collections import defaultdict
45
from datetime import datetime, timedelta
@@ -377,6 +378,7 @@ def broker_task_decorator(
377378
self,
378379
task_type: TaskType,
379380
container: BlockContainer | ContractEvent | ContractEventWrapper | None = None,
381+
from_addresses: Sequence[AddressType | str] | None = None,
380382
filter_args: dict[str, Any] | None = None,
381383
cron_schedule: str | None = None,
382384
metric_name: str | None = None,
@@ -393,6 +395,9 @@ def broker_task_decorator(
393395
Args:
394396
task_type: :class:`~silverback.types.TaskType`: The type of task to create.
395397
container: (BlockContainer | ContractEvent): The event source to watch.
398+
from_addresses: (Sequence[AddressType | str] | None):
399+
The set of addresses to filter an anonymous event by.
400+
Defaults to none (matches all), ignored if `container` is not anonymous event.
396401
397402
Returns:
398403
Callable[[Callable], :class:`~taskiq.AsyncTaskiqDecoratedTask`]:
@@ -456,6 +461,11 @@ def add_taskiq_task(
456461
):
457462
labels["address"] = contract.address
458463

464+
elif from_addresses is not None:
465+
labels["address"] = ",".join(
466+
self.conversion_manager.convert(a, AddressType) for a in from_addresses
467+
)
468+
459469
labels["event"] = container.abi.signature
460470

461471
topics: list[list[HexStr] | HexStr | None] = [
@@ -602,6 +612,7 @@ def do_something_on_shutdown(state):
602612
def on_(
603613
self,
604614
container: BlockContainer | ContractEvent,
615+
from_addresses: Sequence[AddressType | str] | None = None,
605616
filter_args: dict[str, Any] | None = None,
606617
**filter_kwargs: dict[str, Any],
607618
) -> Callable[[Callable], AsyncTaskiqDecoratedTask]:
@@ -610,6 +621,9 @@ def on_(
610621
611622
Args:
612623
container: (BlockContainer | ContractEvent): The event source to watch.
624+
from_addresses: (Sequence[AddressType | str] | None):
625+
The set of addresses to filter an anonymous event by.
626+
Defaults to none (matches all), ignored if `container` is not anonymous event.
613627
filter_args: (dict[str, Any] | None):
614628
Arguments to use for event log filter. Gets combined with ``filter_kwargs``.
615629
Is useful for when an event argument name is a Python keyword.
@@ -634,6 +648,7 @@ def on_(
634648
return self.broker_task_decorator(
635649
TaskType.EVENT_LOG,
636650
container=container,
651+
from_addresses=from_addresses,
637652
filter_args=filter_kwargs,
638653
)
639654

silverback/runner.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -457,19 +457,28 @@ async def log_handler(ctx: LogsSubscriptionContext):
457457
)
458458
)
459459

460-
contract_address = task_data.labels.get("address")
460+
if contract_addresses_str := task_data.labels.get("address"):
461+
contract_addresses = list(map(to_checksum_address, contract_addresses_str.split(",")))
462+
463+
else:
464+
contract_addresses = None
465+
461466
topics = decode_topics_from_string(task_data.labels.get("topics", "")) or None
462467
sub_id = await self._web3.subscription_manager.subscribe(
463468
LogsSubscription(
464469
label=task_data.name,
465-
address=to_checksum_address(contract_address) if contract_address else None,
470+
address=contract_addresses,
466471
topics=topics, # type: ignore[arg-type]
467472
handler=log_handler,
468473
)
469474
)
470-
logger.debug(
471-
f"Handling '{contract_address or ''}:{topics[0] if topics else ''}' logs via {sub_id}"
472-
)
475+
if contract_addresses:
476+
for address in contract_addresses:
477+
logger.debug(
478+
f"Handling '{address}:{topics[0] if topics else ''}' logs via {sub_id}"
479+
)
480+
else:
481+
logger.debug(f"Handling '*:{topics[0] if topics else ''}' logs via {sub_id}")
473482

474483
def _daemon_tasks(self) -> list[Coroutine]:
475484
# NOTE: Handle this as a daemon task (after startup)
@@ -503,7 +512,17 @@ async def _block_task(self, task_data: TaskData):
503512
self._runtime_task_group.create_task(self.run_task(task_data, block))
504513

505514
async def _event_task(self, task_data: TaskData):
506-
contract_address = task_data.labels.get("address")
515+
if contract_addresses_str := task_data.labels.get("address"):
516+
contract_addresses = list(map(to_checksum_address, contract_addresses_str.split(",")))
517+
518+
if len(contract_addresses) != 1:
519+
raise ValueError("Only 1 contract address supported for Polling runner.")
520+
521+
contract_address = contract_addresses[0]
522+
523+
else:
524+
contract_address = None
525+
507526
event = EventABI.from_signature(task_data.labels["event"])
508527
topics = decode_topics_from_string(task_data.labels.get("topics", "")) or None
509528
async for log in async_wrap_iter(

0 commit comments

Comments
 (0)