Skip to content

Implement apibara indexer#193

Merged
codebestia merged 9 commits intomainfrom
feat/implement-indexer
Nov 10, 2025
Merged

Implement apibara indexer#193
codebestia merged 9 commits intomainfrom
feat/implement-indexer

Conversation

@codebestia
Copy link
Member

@codebestia codebestia commented Nov 8, 2025

Description

Related Issue

Fixes #161

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation update
  • Other (please describe)

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual tests
  • Other (please describe)

Checklist:

  • I have read the contributing guidelines.
  • I have updated the documentation (if applicable).
  • My changes do not break existing functionality.
  • I have added tests that cover my changes (if applicable).
  • All new and existing tests pass.
  • I have included screenshots or GIFs to demonstrate the changes (if applicable).

Summary by CodeRabbit

  • New Features
    • CLI to run a Spherre indexer targeting StarkNet Sepolia.
    • Indexer service to monitor and process Spherre contract activity and manage account indexing.
    • Event models for account creation and token transfers with parsing and validation.
    • Event transformers and handlers to convert on-chain events into domain models and trigger indexing.
    • Runtime configuration via environment-backed token and configurable service/mongo endpoints.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a new Spherre indexer package: configuration, CLI entrypoint, event types and transformers, event handlers, a SpherreMainIndexer implementation, and runner utilities for Apibara-based event streaming and processing.

Changes

Cohort / File(s) Summary
Configuration
backend/spherre/indexer/config.py
New config module with constants: NETWORK = "starknet-sepolia", empty SPHERRE_CONTRACT_ADDRESS, SERVER_URL, MONGO_URL, and DNA_TOKEN read from environment (os.environ.get("DNA_TOKEN")).
CLI Entrypoint
backend/spherre/indexer/main.py
New Click CLI: cli group, async_command decorator to run async commands via asyncio.run, and start command that resolves server_url, mongo_url, restart (defaults from config) and calls run_main_indexer with dna_token.
Event Types & Models
backend/spherre/indexer/service/types.py
New EventEnum enum, EVENT_SELECTORS bidict, BaseEventModel (pydantic) with custom positional-arg handling and a before-validator to convert felt values to hex/int and handle generics, plus AccountCreationEvent and TokenTransferEvent models.
Event Transformers / Utils
backend/spherre/indexer/service/utils.py
New DataTransformer with transform_account_creation_event converting raw Event into AccountCreationEvent (uses felt helpers, validates with pydantic); DATA_TRANSFORMERS registry maps ACCOUNT_CREATION to the transformer.
Event Handlers
backend/spherre/indexer/service/event_handlers.py
New EventHandlers class with classmethods handle_account_creation and handle_token_transafer (stubs) and DATA_HANDLERS mapping EventEnum to handlers.
Indexers & Runners
backend/spherre/indexer/service/indexers.py
New SpherreMainIndexer (inherits StarkNetIndexer) implementing indexer_id, initial_configuration, start_account_indexer, async handle_data routing events through DATA_TRANSFORMERS/DATA_HANDLERS, handle_invalidate (raises for non-final data); plus run_indexer and run_main_indexer runner helpers that instantiate and run the indexer with IndexerRunnerConfiguration.

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant Main as main.py
    participant Config as config.py
    participant Runner as run_main_indexer
    participant Indexer as SpherreMainIndexer
    participant Apibara as Apibara Stream
    participant Transformer as DataTransformer
    participant Handlers as EventHandlers

    CLI->>Main: start(server_url, mongo_url, restart)
    Main->>Config: read defaults (server_url, mongo_url, DNA_TOKEN)
    Main->>Runner: run_main_indexer(resolved_urls, dna_token, restart)
    Runner->>Indexer: instantiate SpherreMainIndexer()
    Runner->>Apibara: start stream with IndexerRunnerConfiguration
    Apibara-->>Indexer: emit Block (with events)

    rect rgb(230,245,230)
      Indexer->>Indexer: handle_data(block)
      Indexer->>Transformer: select transformer by event selector
      Transformer-->>Indexer: AccountCreationEvent or None
      Indexer->>Handlers: dispatch to appropriate handler (e.g., handle_account_creation)
      Handlers-->>Indexer: handling result
    end

    Apibara-->>Runner: continue streaming
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus review on:
    • service/types.py — BaseEventModel before-validator (type detection, generic/list handling).
    • service/utils.py — felt conversions and ValidationError handling in transformer.
    • service/indexers.py — event routing, selector lookup, and error/logging paths.
    • main.py — CLI defaults and async wrapper behavior.

Poem

🐰
I hop through logs with nimble feet,
Parsing events both swift and neat,
From config burrow to streaming light,
Transforming bytes by moonlit night,
Indexers hum — the chain takes flight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement apibara indexer' clearly and concisely describes the main change: adding a new Apibara-based indexing infrastructure as specified in the linked issue.
Description check ✅ Passed The PR description follows the template structure with all key sections completed: description, related issue (#161), type of change (new feature marked), testing section, and checklist items marked as complete.
Linked Issues check ✅ Passed The PR implements core infrastructure for Apibara indexing (config, CLI entry, event handlers, main indexer, type definitions, and data transformers) that supports the foundational requirements for the account creation and token transfer indexers specified in issue #161.
Out of Scope Changes check ✅ Passed All changes are directly aligned with building the indexer infrastructure: configuration module, CLI setup, event handling framework, indexer orchestration, event typing, and data transformation utilities are all core components specified in issue #161.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e29541c and 3ae49f8.

📒 Files selected for processing (1)
  • backend/spherre/indexer/service/indexers.py (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d53d1e7 and 5b39e73.

📒 Files selected for processing (5)
  • backend/spherre/indexer/config.py (1 hunks)
  • backend/spherre/indexer/main.py (1 hunks)
  • backend/spherre/indexer/service/event_handlers.py (1 hunks)
  • backend/spherre/indexer/service/indexers.py (1 hunks)
  • backend/spherre/indexer/service/types.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
backend/spherre/indexer/main.py (1)
backend/spherre/indexer/service/indexers.py (1)
  • run_main_indexer (135-147)
backend/spherre/indexer/service/event_handlers.py (1)
backend/spherre/indexer/service/types.py (1)
  • AccountCreationEvent (70-78)
backend/spherre/indexer/service/indexers.py (2)
backend/spherre/indexer/service/event_handlers.py (2)
  • EventHandlers (4-11)
  • handle_account_creation (6-7)
backend/spherre/indexer/service/types.py (2)
  • AccountCreationEvent (70-78)
  • EventEnum (9-14)

Comment on lines +5 to +7
SPHERRE_CONTRACT_ADDRESS = ""
SERVER_URL = ""
MONGO_URL = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fail fast when required config is missing.

Leaving SPHERRE_CONTRACT_ADDRESS, SERVER_URL, and MONGO_URL as empty strings makes felt.from_hex("") crash at runtime and the runner attempt to dial an empty URL. Please plumb these values from env or config and raise a clear error if they aren’t provided before starting the indexer.

Comment on lines +4 to +11
class EventHandlers:
@classmethod
def handle_account_creation(cls, data: AccountCreationEvent):
pass

@classmethod
def handle_token_transafer(cls):
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Implement the handlers (and fix the typo).

Both handlers are pass, so every processed event is dropped. On top of that, handle_token_transafer is misspelled, so any future call using the correct name will fail. Please implement the expected persistence/side effects now (or at least raise NotImplementedError) and correct the method name to avoid silent breakage.

🤖 Prompt for AI Agents
In backend/spherre/indexer/service/event_handlers.py around lines 4-11, the two
handlers are unimplemented and the token handler name is misspelled; change
handle_token_transafer to handle_token_transfer, give it the correct signature
(accepting the token event data), and either implement the expected
persistence/side-effects (write event data to the index/store and emit any
required downstream events) or explicitly raise NotImplementedError in both
handle_account_creation(data: AccountCreationEvent) and
handle_token_transfer(data: TokenTransferEvent) so events are not silently
dropped; also update any callers/tests to use the corrected method name.

Comment on lines +45 to +78
account_address = felt.to_hex(event.data[0])
owner = felt.to_hex(event.data[1])
name = felt.to_hex(event.data[2])
description = felt.to_hex(event.data[3])
members_array_length = felt.to_int(event.data[4])
members = []
for i in range(5, members_array_length + 1 + 4):
members.append(felt.to_hex(event.data[i]))
threshold = felt.to_int(event.data[i + 1])
deployer = felt.to_hex(event.data[i + 2])
date_deployed = felt.to_int(event.data[i + 3])

print("Account Deployed")
print("Transaction Hash:", tx_hash)
print("Account Address:", account_address)
print("Owner:", owner)
print("Name:", name)
print("Description:", description)
print("Members:", members)
print("Threshold:", threshold)
print("Deployer:", deployer)
print("Date Deployed:", date_deployed)

try:
account_data = AccountCreationEvent(
account_address=account_address,
owner=owner,
name=name,
description=description,
members=members,
threshold=threshold,
deployer=deployer,
date_deployed=date_deployed,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix event parsing for empty member lists and type mismatches.

If a DAO boots with zero members, the members loop never runs and i is undefined when you read event.data[i + 1:], causing an UnboundLocalError. Even when members exist, you convert deployer to hex but the model expects an int, so Pydantic raises ValidationError and the event is dropped. Please restructure the offsets explicitly (e.g., track a cursor) and feed the correct types into AccountCreationEvent so validation succeeds.

🤖 Prompt for AI Agents
In backend/spherre/indexer/service/indexers.py around lines 45 to 78, the event
parsing uses a loop index `i` that can be undefined when members_array_length is
zero and also converts `deployer` to hex while the Pydantic model expects ints;
fix by using an explicit cursor variable initialized to the first member index,
advance the cursor exactly members_array_length times appending felt.to_hex
values (so an empty list is handled without relying on `i`), then read
threshold, deployer, and date_deployed using felt.to_int from the cursor
positions (not felt.to_hex for deployer) and assign the correct int/str types
into AccountCreationEvent so validation passes.

Comment on lines +80 to +82
EventHandlers.handle_account_creation(account_data)
# start a new account indexer in another thread
except ValidationError as err:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Starting account indexers is still unimplemented.

We log that we “start a new account indexer” but call nothing—real-time account monitoring never kicks off. Either wire this up to run_indexer(AccountIndexer, ...) in a background task or remove the comment and add a TODO that clearly blocks release.

🤖 Prompt for AI Agents
In backend/spherre/indexer/service/indexers.py around lines 80 to 82, the code
logs "start a new account indexer" but never actually starts it; wire this up by
invoking run_indexer with the AccountIndexer class in a background task (or
equivalent thread/task runner used in this project) passing the appropriate
account identifier and any required context, ensuring exceptions are caught and
logged; if you cannot implement it now, remove the misleading comment and add a
clear TODO comment that this missing behavior blocks release and references an
issue/PR number.

Comment on lines +17 to +33
EVENT_SELECTORS = bidict(
{
EventEnum.ACCOUNT_CREATION: felt.from_hex(
"0x347f3a34b919109f055acc8440a003ecda76b4c63c101bbc99b9d00296db557"
),
EventEnum.TOKEN_TRANSFER: felt.from_hex(
"0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9"
),
EventEnum.TRANSACTION_PROPOSAL: felt.from_hex(""),
EventEnum.TRANSACTION_APPROVAL: felt.from_hex(
"0x8fac5268bfb16142e4e1fce5f985e337db83f8b307525a0960480cd8473436"
),
EventEnum.TRANSACTION_EXECUTION: felt.from_hex(
"0x1dcde06aabdbca2f80aa51392b345d7549d7757aa855f7e37f5d335ac8243b1"
),
}
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Selector initialization currently raises immediately.

felt.from_hex("") throws, so importing this module blows up before the indexer can start. Please either provide the real selector or guard optional selectors instead of hard‑coding an empty string.

🤖 Prompt for AI Agents
In backend/spherre/indexer/service/types.py around lines 17 to 33, the
EventEnum.TRANSACTION_PROPOSAL entry calls felt.from_hex("") which raises at
import time; replace the empty string with the correct selector hex if
available, or remove/omit the TRANSACTION_PROPOSAL key from EVENT_SELECTORS and
handle missing selectors elsewhere, or wrap the conversion in a guard that only
calls felt.from_hex when the string is non-empty (or uses None/Optional for
absent selectors) so importing this module no longer throws.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (2)
backend/spherre/indexer/service/event_handlers.py (1)

12-17: Critical: Implement handlers and fix the typo and signature.

The issues flagged in the previous review persist:

  1. Typo: handle_token_transafer should be handle_token_transfer
  2. Missing parameter: handle_token_transafer lacks a data parameter (compare line 12 vs line 16)
  3. Unimplemented: Both handlers use pass, so all processed events are silently dropped

This blocks the core indexing functionality described in the PR objectives.

Apply this diff to fix the typo, signature, and add explicit not-implemented markers:

     @classmethod
     def handle_account_creation(cls, data: AccountCreationEvent):
-        pass
+        raise NotImplementedError("Account creation persistence not yet implemented")

     @classmethod
-    def handle_token_transafer(cls):
-        pass
+    def handle_token_transfer(cls, data: BaseEventModel):
+        raise NotImplementedError("Token transfer persistence not yet implemented")

Then update the mapping:

 DATA_HANDLERS: Dict[EventEnum, Callable[[BaseEventModel], None]] = {
     EventEnum.ACCOUNT_CREATION: EventHandlers.handle_account_creation,
-    EventEnum.TOKEN_TRANSFER: EventHandlers.handle_token_transafer,
+    EventEnum.TOKEN_TRANSFER: EventHandlers.handle_token_transfer,
 }
backend/spherre/indexer/service/types.py (1)

25-25: Critical: Empty selector string causes import failure.

The issue from the previous review persists: felt.from_hex("") raises ValueError at import time, preventing the module from loading.

Apply one of these fixes:

Option 1 (if selector is unknown): Remove the entry and handle missing selectors dynamically:

         EventEnum.TOKEN_TRANSFER: felt.from_hex(
             "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9"
         ),
-        EventEnum.TRANSACTION_PROPOSAL: felt.from_hex(""),
         EventEnum.TRANSACTION_APPROVAL: felt.from_hex(
             "0x8fac5268bfb16142e4e1fce5f985e337db83f8b307525a0960480cd8473436"
         ),

Option 2 (if selector is known): Replace with the correct hex value:

-        EventEnum.TRANSACTION_PROPOSAL: felt.from_hex(""),
+        EventEnum.TRANSACTION_PROPOSAL: felt.from_hex("0x<actual_selector_here>"),
🧹 Nitpick comments (1)
backend/spherre/indexer/service/utils.py (1)

29-37: Replace print statements with structured logging.

Use the imported logger for production-grade observability instead of print statements.

Apply this diff:

-        print("Account Deployed")
-        print("Account Address:", account_address)
-        print("Owner:", owner)
-        print("Name:", name)
-        print("Description:", description)
-        print("Members:", members)
-        print("Threshold:", threshold)
-        print("Deployer:", deployer)
-        print("Date Deployed:", date_deployed)
+        logger.debug(
+            f"Account Deployed - Address: {account_address}, Owner: {owner}, "
+            f"Name: {name}, Description: {description}, Members: {members}, "
+            f"Threshold: {threshold}, Deployer: {deployer}, Date: {date_deployed}"
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b39e73 and e29541c.

📒 Files selected for processing (4)
  • backend/spherre/indexer/service/event_handlers.py (1 hunks)
  • backend/spherre/indexer/service/indexers.py (1 hunks)
  • backend/spherre/indexer/service/types.py (1 hunks)
  • backend/spherre/indexer/service/utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
backend/spherre/indexer/service/event_handlers.py (1)
backend/spherre/indexer/service/types.py (3)
  • AccountCreationEvent (70-78)
  • BaseEventModel (36-67)
  • EventEnum (9-14)
backend/spherre/indexer/service/utils.py (1)
backend/spherre/indexer/service/types.py (3)
  • AccountCreationEvent (70-78)
  • BaseEventModel (36-67)
  • EventEnum (9-14)
backend/spherre/indexer/service/indexers.py (1)
backend/spherre/indexer/service/types.py (2)
  • AccountCreationEvent (70-78)
  • EventEnum (9-14)
🪛 GitHub Actions: Backend Lint and Code Formatting Check
backend/spherre/indexer/service/indexers.py

[error] 8-8: F401: 'pydantic.ValidationError' imported but unused. Remove unused import.


[error] 14-14: F401: 'AccountCreationEvent' imported but unused. Remove unused import.


[error] 63-63: E501: Line too long (96 > 88).


[error] 67-67: E501: Line too long (103 > 88).


[error] 79-79: E501: Line too long (95 > 88).


[error] 84-84: E501: Line too long (101 > 88).

🔇 Additional comments (7)
backend/spherre/indexer/service/event_handlers.py (1)

20-23: LGTM with dependency on handler fixes.

The mapping structure and type annotations are correct. However, this depends on fixing the handler implementations and typo flagged above.

backend/spherre/indexer/service/utils.py (1)

56-58: LGTM!

The mapping structure is correct. Note that TOKEN_TRANSFER transformer is not yet implemented, which aligns with the current scope focusing on account creation events.

backend/spherre/indexer/service/types.py (2)

70-78: LGTM!

The AccountCreationEvent model is well-defined with appropriate field types matching the smart contract event structure.


81-85: LGTM!

The TokenTransferEvent model is well-defined and ready for when the handler and transformer are implemented.

backend/spherre/indexer/service/indexers.py (3)

32-46: LGTM!

The indexer ID and initial configuration are correctly implemented. The hardcoded starting cursor at block 10,000 is appropriate for bootstrapping.


87-88: LGTM!

Rejecting invalidations is appropriate given the DATA_STATUS_ACCEPTED finality requirement in initial_configuration.


95-127: LGTM!

The runner functions are well-structured. run_indexer provides a generic runner that can be used for starting account-specific indexers, while run_main_indexer is a convenient wrapper for the main indexer.

Comment on lines +21 to +30
def start_account_indexer(self, address: str):
# create a new filter for the account address
account_address = felt.from_hex(address)
filter = (
EventFilter()
.with_from_address(account_address)
.with_keys([EVENT_SELECTORS.values()])
)
# add the filter to the indexer
self.update_filter(filter)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Convert dict values view to list.

Line 27: EVENT_SELECTORS.values() returns a dict_values view object, which may not be accepted by with_keys(). Convert to a list explicitly.

Apply this diff:

         filter = (
             EventFilter()
             .with_from_address(account_address)
-            .with_keys([EVENT_SELECTORS.values()])
+            .with_keys(list(EVENT_SELECTORS.values()))
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def start_account_indexer(self, address: str):
# create a new filter for the account address
account_address = felt.from_hex(address)
filter = (
EventFilter()
.with_from_address(account_address)
.with_keys([EVENT_SELECTORS.values()])
)
# add the filter to the indexer
self.update_filter(filter)
def start_account_indexer(self, address: str):
# create a new filter for the account address
account_address = felt.from_hex(address)
filter = (
EventFilter()
.with_from_address(account_address)
.with_keys(list(EVENT_SELECTORS.values()))
)
# add the filter to the indexer
self.update_filter(filter)
🤖 Prompt for AI Agents
In backend/spherre/indexer/service/indexers.py around lines 21 to 30, the call
to with_keys currently passes EVENT_SELECTORS.values() which yields a
dict_values view that may be rejected; change it to pass a concrete list by
wrapping it with list(EVENT_SELECTORS.values()), so the filter is constructed
with a list of keys and then call update_filter as before.

Comment on lines +48 to +85
async def handle_data(self, info: Info, data: Block):
# Handle one block of data
for event_with_tx in data.events:
tx_hash = felt.to_hex(event_with_tx.transaction.meta.hash)
event = event_with_tx.event
event_address = felt.to_hex(event.from_address)
logger.info("Transaction Hash:", tx_hash)

if event_address != SPHERRE_CONTRACT_ADDRESS:
event_type = event.keys
event_enum = EVENT_SELECTORS.inverse[event_type]
transformed_data = DATA_TRANSFORMERS[event_enum](event)
if transformed_data:
DATA_HANDLERS[event_enum](transformed_data)
logger.info(
f"Event with type '{event_type}' from address '{event_address}' handled"
)
else:
logger.error(
f"Failed to handle event of type '{event_type}' from address '{event_address}'"
)
else:
event_type = event.keys
if event_type == EVENT_SELECTORS[EventEnum.ACCOUNT_CREATION]:
transformed_data = DATA_TRANSFORMERS[EventEnum.ACCOUNT_CREATION](
event
)
if transformed_data:
DATA_HANDLERS[EventEnum.ACCOUNT_CREATION](transformed_data)
self.start_account_indexer(transformed_data.account_address)
logger.info(
f"Account created for address '{transformed_data.account_address}'"
)
else:
account_address = felt.to_hex(event.data[0])
logger.error(
f"Failed to handle account creation event of address '{account_address}'"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix event key extraction, logger syntax, and line lengths.

Multiple issues in the event handling logic:

  1. Line 54: logger.info() doesn't accept multiple positional arguments like print(). Use f-string formatting
  2. Lines 58, 70: event.keys is a list; extract the first element for comparison with selectors
  3. Lines 63, 67, 79, 84: Lines exceed 88 characters (pipeline failures)

Apply this diff:

         for event_with_tx in data.events:
             tx_hash = felt.to_hex(event_with_tx.transaction.meta.hash)
             event = event_with_tx.event
             event_address = felt.to_hex(event.from_address)
-            logger.info("Transaction Hash:", tx_hash)
+            logger.info(f"Transaction Hash: {tx_hash}")

             if event_address != SPHERRE_CONTRACT_ADDRESS:
-                event_type = event.keys
+                event_key = event.keys[0] if event.keys else None
+                if not event_key:
+                    logger.warning(f"Event with no keys from {event_address}")
+                    continue
-                event_enum = EVENT_SELECTORS.inverse[event_type]
+                event_enum = EVENT_SELECTORS.inverse.get(event_key)
+                if not event_enum:
+                    logger.warning(f"Unknown event key {event_key}")
+                    continue
                 transformed_data = DATA_TRANSFORMERS[event_enum](event)
                 if transformed_data:
                     DATA_HANDLERS[event_enum](transformed_data)
                     logger.info(
-                        f"Event with type '{event_type}' from address '{event_address}' handled"
+                        f"Event '{event_enum.name}' from "
+                        f"'{event_address}' handled"
                     )
                 else:
                     logger.error(
-                        f"Failed to handle event of type '{event_type}' from address '{event_address}'"
+                        f"Failed to handle event '{event_enum.name}' "
+                        f"from '{event_address}'"
                     )
             else:
-                event_type = event.keys
-                if event_type == EVENT_SELECTORS[EventEnum.ACCOUNT_CREATION]:
+                event_key = event.keys[0] if event.keys else None
+                if event_key == EVENT_SELECTORS[EventEnum.ACCOUNT_CREATION]:
                     transformed_data = DATA_TRANSFORMERS[EventEnum.ACCOUNT_CREATION](
                         event
                     )
                     if transformed_data:
                         DATA_HANDLERS[EventEnum.ACCOUNT_CREATION](transformed_data)
                         self.start_account_indexer(transformed_data.account_address)
                         logger.info(
-                            f"Account created for address '{transformed_data.account_address}'"
+                            f"Account created for "
+                            f"'{transformed_data.account_address}'"
                         )
                     else:
                         account_address = felt.to_hex(event.data[0])
                         logger.error(
-                            f"Failed to handle account creation event of address '{account_address}'"
+                            f"Failed to handle account creation "
+                            f"for '{account_address}'"
                         )
🧰 Tools
🪛 GitHub Actions: Backend Lint and Code Formatting Check

[error] 63-63: E501: Line too long (96 > 88).


[error] 67-67: E501: Line too long (103 > 88).


[error] 79-79: E501: Line too long (95 > 88).


[error] 84-84: E501: Line too long (101 > 88).

🤖 Prompt for AI Agents
In backend/spherre/indexer/service/indexers.py around lines 48 to 85, fix three
issues: replace logger.info("Transaction Hash:", tx_hash) with a single
formatted message (e.g. logger.info(f"Transaction Hash: {tx_hash}")), extract
the event key from event.keys by using event_type = event.keys[0] before
comparing to selectors or using it as a dict key, and reflow any long
log/handler lines to be under 88 characters (assign long strings or values to
short local variables and use them in f-strings) so each statement stays within
the line-length limit.

Comment on lines +91 to +92
def run_in_thread(self):
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused function.

This function is not called anywhere and has no implementation. Either implement it if it's needed for the threading strategy mentioned in previous review comments, or remove it.

-def run_in_thread(self):
-    pass
-
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def run_in_thread(self):
pass
🤖 Prompt for AI Agents
In backend/spherre/indexer/service/indexers.py around lines 91 to 92, remove the
unused empty method run_in_thread (or implement it if your threading strategy
requires it). If no callers exist and threading behavior is not needed, delete
the def run_in_thread(self): pass definition to avoid dead code; if threading is
required, implement the method to start the indexer in a background thread
(e.g., accept necessary args, create and start a Thread/Executor, and ensure
proper lifecycle/error handling) and update any callers accordingly.

Comment on lines +44 to +67
@field_validator("*", mode="before") # Apply to all fields
@classmethod
def restructure_data_before_parsing(cls, v: Any, info: ValidationInfo):
field_info = cls.model_fields[info.field_name]
field_type = field_info.annotation

print(f"\n{info.field_name}:")
print(f" Annotation: {field_type}")

new_value = v

if isinstance(field_type, str):
new_value = felt.to_hex(v)
return new_value
elif isinstance(field_type, int):
new_value = felt.to_int(v)
elif get_origin(field_type):
args = get_args(field_type)
if isinstance(args, str):
new_value = [felt.to_hex(val) for val in v]
elif isinstance(args, int):
new_value = [felt.to_int(val) for val in v]

return new_value
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Fix type checking logic and remove debug prints.

The validator has two issues:

  1. Lines 50-51: Debug print statements should be removed or use logger
  2. Lines 55, 62: isinstance(field_type, str) checks if field_type is an instance of str, but field_type is a type annotation (the class str itself), not a string. This always returns False

Apply this diff to fix the type checks:

     @field_validator("*", mode="before")
     @classmethod
     def restructure_data_before_parsing(cls, v: Any, info: ValidationInfo):
         field_info = cls.model_fields[info.field_name]
         field_type = field_info.annotation

-        print(f"\n{info.field_name}:")
-        print(f"  Annotation: {field_type}")

         new_value = v

-        if isinstance(field_type, str):
+        if field_type is str:
             new_value = felt.to_hex(v)
             return new_value
-        elif isinstance(field_type, int):
+        elif field_type is int:
             new_value = felt.to_int(v)
         elif get_origin(field_type):
             args = get_args(field_type)
-            if isinstance(args, str):
+            if args and args[0] is str:
                 new_value = [felt.to_hex(val) for val in v]
-            elif isinstance(args, int):
+            elif args and args[0] is int:
                 new_value = [felt.to_int(val) for val in v]

         return new_value
🤖 Prompt for AI Agents
In backend/spherre/indexer/service/types.py around lines 44 to 67, remove the
two debug print statements and fix the type-checking logic: use "if field_type
is str" and "elif field_type is int" (or "==") to check annotations rather than
isinstance, and when handling generic types use get_origin(field_type) and
inspect get_args(field_type) for element types (compare args[0] to str or int)
before mapping felt.to_hex / felt.to_int over v; ensure you return new_value in
each branch and handle None/empty inputs safely.

Comment on lines +17 to +28
def transform_account_creation_event(cls, event: Event) -> AccountCreationEvent:
account_address = felt.to_hex(event.data[0])
owner = felt.to_hex(event.data[1])
name = felt.to_hex(event.data[2])
description = felt.to_hex(event.data[3])
members_array_length = felt.to_int(event.data[4])
members = []
for i in range(5, members_array_length + 1 + 4):
members.append(felt.to_hex(event.data[i]))
threshold = felt.to_int(event.data[i + 1])
deployer = felt.to_int(event.data[i + 2])
date_deployed = felt.to_int(event.data[i + 3])
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Fix array parsing logic and return type.

The array parsing has critical errors:

  1. Line 24: The range range(5, members_array_length + 1 + 4) is incorrect. To read members_array_length items starting at index 5, use range(5, 5 + members_array_length)
  2. Lines 26-28: If members_array_length is 0, the loop never executes and i is undefined, causing UnboundLocalError when accessing event.data[i + 1]
  3. Line 17: Return type is AccountCreationEvent but line 52 returns None, should be Optional[AccountCreationEvent]

Apply this diff to fix the logic:

     @classmethod
-    def transform_account_creation_event(cls, event: Event) -> AccountCreationEvent:
+    def transform_account_creation_event(cls, event: Event) -> Optional[AccountCreationEvent]:
         account_address = felt.to_hex(event.data[0])
         owner = felt.to_hex(event.data[1])
         name = felt.to_hex(event.data[2])
         description = felt.to_hex(event.data[3])
         members_array_length = felt.to_int(event.data[4])
         members = []
-        for i in range(5, members_array_length + 1 + 4):
+        cursor = 5
+        for _ in range(members_array_length):
-            members.append(felt.to_hex(event.data[i]))
+            members.append(felt.to_hex(event.data[cursor]))
+            cursor += 1
-        threshold = felt.to_int(event.data[i + 1])
-        deployer = felt.to_int(event.data[i + 2])
-        date_deployed = felt.to_int(event.data[i + 3])
+        threshold = felt.to_int(event.data[cursor])
+        deployer = felt.to_int(event.data[cursor + 1])
+        date_deployed = felt.to_int(event.data[cursor + 2])

Add the import:

-from typing import Callable, Dict
+from typing import Callable, Dict, Optional
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def transform_account_creation_event(cls, event: Event) -> AccountCreationEvent:
account_address = felt.to_hex(event.data[0])
owner = felt.to_hex(event.data[1])
name = felt.to_hex(event.data[2])
description = felt.to_hex(event.data[3])
members_array_length = felt.to_int(event.data[4])
members = []
for i in range(5, members_array_length + 1 + 4):
members.append(felt.to_hex(event.data[i]))
threshold = felt.to_int(event.data[i + 1])
deployer = felt.to_int(event.data[i + 2])
date_deployed = felt.to_int(event.data[i + 3])
def transform_account_creation_event(cls, event: Event) -> Optional[AccountCreationEvent]:
account_address = felt.to_hex(event.data[0])
owner = felt.to_hex(event.data[1])
name = felt.to_hex(event.data[2])
description = felt.to_hex(event.data[3])
members_array_length = felt.to_int(event.data[4])
members = []
cursor = 5
for _ in range(members_array_length):
members.append(felt.to_hex(event.data[cursor]))
cursor += 1
threshold = felt.to_int(event.data[cursor])
deployer = felt.to_int(event.data[cursor + 1])
date_deployed = felt.to_int(event.data[cursor + 2])
🤖 Prompt for AI Agents
In backend/spherre/indexer/service/utils.py around lines 17 to 28, fix the
member-array parsing and return type: import Optional from typing; change the
function return annotation to Optional[AccountCreationEvent]; compute start=5
and end=start+members_array_length, iterate for i in range(start, end) to build
members, then set next_index = end and read threshold =
felt.to_int(event.data[next_index]), deployer =
felt.to_int(event.data[next_index + 1]), date_deployed =
felt.to_int(event.data[next_index + 2]) so no reliance on an undefined i when
members_array_length == 0.

@codebestia codebestia merged commit b777dd5 into main Nov 10, 2025
3 of 4 checks passed
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.

[BACKEND] Build Blockchain Indexer Infrastructure for Smart Contract Events

1 participant