Skip to content

Conversation

@sundayonah
Copy link
Collaborator

@sundayonah sundayonah commented Dec 20, 2025

  • Added decimal parsing for amounts, protocol fees, rates, settle percentages, rebate percentages, and fees in handleOrderCreated, handleOrderSettled, and handleOrderRefunded methods.
  • Replaced custom byte array parsing with a utility function in utils.go.
  • Normalized Starknet addresses in event data for better consistency.
  • Improved error handling for parsing operations.
  • Refactored Voyager event transformation to include detailed error handling and support for new data formats.
  • Updated Voyager API calls to remove unnecessary logging for cleaner output.

Checklist

  • I have added documentation and tests for new/changed functionality in this PR
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not main

By submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.

Summary by CodeRabbit

  • New Features

    • Network-aware path parsing with explicit Starknet handling.
    • Batch-oriented receipt/event processing and RPC fallback for bulk indexing.
    • Public utilities to parse Cairo byte arrays and flexible hex/decimal numeric parsing.
    • Voyager transfer window extended (defaults moved from ~4 to ~7 days) and enriched RPC event payloads.
  • Bug Fixes

    • Improved validation, consistent decimal parsing, address normalization, and clearer error messages.
    • More robust handling and logging for missing/invalid event fields.
  • Chores

    • Reduced noisy logs and streamlined RPC event shaping.

✏️ Tip: You can customize this high-level summary in your review settings.

- Added decimal parsing for amounts, protocol fees, rates, settle percentages, rebate percentages, and fees in handleOrderCreated, handleOrderSettled, and handleOrderRefunded methods.
- Replaced custom byte array parsing with a utility function in utils.go.
- Normalized Starknet addresses in event data for better consistency.
- Improved error handling for parsing operations.
- Refactored Voyager event transformation to include detailed error handling and support for new data formats.
- Updated Voyager API calls to remove unnecessary logging for cleaner output.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Walkthrough

Adds Starknet-aware pathParam parsing in the controller, converts Starknet indexer flows from per-transaction to batch RPC-receipt processing, normalizes/parses event fields (addresses, decimals, Cairo ByteArray), exposes a Voyager RPC fallback, and adds ByteArray/decimal parsing utilities.

Changes

Cohort / File(s) Summary
Controller: network-aware routing
controllers/index.go
Defers tx_hash/address inference until network is resolved; applies Starknet-specific pathParam length rules; reorders validation and ensures at least one indexing method (txHash/address or from_block/to_block).
Starknet indexer — batch processing
services/indexer/starknet.go
Converts per-transaction flows to batch-oriented RPC receipt processing; updates signatures to accept slices of event maps; removes extractUint64AsString; adapts logging, error handling, and aggregation for batched processing.
Starknet client: event decoding & normalization
services/starknet/client.go
Normalizes Starknet addresses via cryptoUtils.NormalizeStarknetAddress, parses numeric fields as decimals, replaces legacy bytearray parsing with ParseByteArray helpers, and aligns indexed/non-indexed param shapes.
Voyager service: RPC & event transform
services/voyager.go
Exposes GetEventsByTransactionHashRPC; TransformVoyagerEventToRPCFormat now returns (map[string]interface{}, error) with selector-specific parsing (OrderCreated/Settled/Refunded), adds topic/subunit value handling, and removes noisy per-request logging.
Parsing utilities
utils/utils.go
Adds ParseByteArray, ParseByteArrayFromJSON, and ParseStringAsDecimals for Cairo ByteArray and hex/decimal parsing used by Voyager and Starknet client code.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant VoyagerService
    participant IndexerStarknet
    participant DB

    Client->>Controller: HTTP /index/:pathParam or from_block/to_block
    Controller->>Controller: resolve network (Starknet vs other)
    Controller->>VoyagerService: GetEventsByTransactionHashRPC(txHash)
    VoyagerService-->>Controller: []rpcEvents (batch receipts)
    Controller->>IndexerStarknet: index...ByTransaction(events batch)
    IndexerStarknet->>IndexerStarknet: normalize addresses, parse decimals, ParseByteArray
    IndexerStarknet->>DB: persist aggregated event results
    DB-->>IndexerStarknet: ack
    IndexerStarknet-->>Controller: EventCounts
    Controller-->>Client: HTTP response (counts/status)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • Updated function signatures and all call sites in services/indexer/starknet.go.
    • Selector-specific parsing and new error paths in services/voyager.go.
    • Consistent use of cryptoUtils.NormalizeStarknetAddress and decimal conversions across handlers.
    • Controller network resolution and reordered validation logic.

Possibly related PRs

Suggested reviewers

  • chibie
  • 5ran6

Poem

🐰 I hopped through bytes and batches bright,

Decimals twinkled, addresses made right,
Receipts in bundles, parsing in tow,
The indexer hums, events all aglow —
Hooray, the rabbit digs the show! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR changes focus on Starknet event parsing, decimal conversion, and logging improvements, but do not address the linked issue #407 requirements about saving previous provider rates in Redis priority queues. Verify that the PR is intended for issue #407 or re-link to appropriate issues. If intended for #407, implement required Redis queue management, rate comparison logic, and priority queue refresh handling.
Out of Scope Changes check ⚠️ Warning The PR changes (event handling, decimal parsing, address normalization, Voyager API updates) appear unrelated to the stated objective of managing Redis priority queues for provider rates. Clarify the PR's intended scope and linked issues. The current changes suggest this PR addresses Starknet event processing rather than the Redis priority queue functionality described in issue #407.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description provides a clear summary of changes but largely omits required template sections: no References section identifying issue #407, incomplete Testing section with unchecked boxes, and incomplete Checklist items. Add References section linking to issue #407, complete Testing section with specific test details and environment info, and update Checklist to indicate completion status of documentation/tests.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes made to the codebase, focusing on enhanced event handling and parsing improvements in the Starknet client.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clean/starknet-logs

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/voyager.go (1)

1105-1247: TransformVoyagerEventToRPCFormat emits topic but indexer reads topics

The new TransformVoyagerEventToRPCFormat correctly:

  • Builds indexed_params / non_indexed_params maps for OrderCreated, OrderSettled, and OrderRefunded.
  • Parses numeric fields (amount, protocol_fee, rate, settle_percent, rebate_percent, fee) into decimal.Decimal.
  • Decodes message_hash ByteArray via u.ParseByteArrayFromJSON.
  • Normalizes address via NormalizeStarknetAddress.

However, it writes the selector under the key:

"topic": keys[0],

while indexer/starknet.go’s indexGatewayByTransaction reads:

eventSignature, ok := eventMap["topics"].(string)

so any gateway events coming from Voyager+this transformer will have no "topics" field and will be skipped by the indexer. Only RPC‑originated events (which still use "topics") will be processed.

To fix this, either:

  • Change the transformer to emit "topics" (string) to mirror the RPC client shape:
-    "topic":            keys[0],
+    "topics":           fmt.Sprintf("%v", keys[0]),

or

  • Update the indexer to accept both, e.g.:
sig, _ := eventMap["topics"].(string)
if sig == "" {
    if v, ok := eventMap["topic"].(string); ok {
        sig = v
    }
}

Using the same key name as the RPC path is simpler and less error‑prone.

services/indexer/starknet.go (1)

274-303: Gateway indexing ignores Voyager-transformed events due to topics vs topic mismatch

indexGatewayByTransaction currently does:

eventSignature, ok := eventMap["topics"].(string)
if !ok {
    logger.Errorf("Missing or invalid 'topics' field in gateway event")
    continue
}

and then switches on eventSignature against the Starknet selectors.

When IndexGateway uses Voyager as the data source (GetContractEvents + TransformVoyagerEventToRPCFormat), the transformed events carry the selector under "topic", not "topics". As a result, all Voyager‑sourced gateway events are skipped here, and only pure RPC‑sourced events are processed.

Once you align TransformVoyagerEventToRPCFormat to emit "topics" (or update this code to fall back to "topic"), this will work for both sources. Until then, gateway indexing in Voyager‑success cases will silently do nothing.

This is coupled with the topic/topics issue described in services/voyager.go.

Also applies to: 315-363, 377-399, 431-462, 465-521

🧹 Nitpick comments (3)
utils/utils.go (2)

903-965: Redis bucket scanning mixes current and _prev queues without explicit precedence

The updated getProviderRateFromRedis, validateBucketRate, parseBucketKey, and findSuitableProviderRate look structurally sound and add useful validation and logging. However:

  • Scan uses pattern bucket_<currency>_*_* and parseBucketKey accepts 4‑ or 5‑part keys, so both current (bucket_NGN_1_500000) and _prev (bucket_NGN_1_500000_prev) buckets will be treated identically.
  • Because validateBucketRate iterates keys in arbitrary SCAN order and stops on the first ProviderRateResult.Found == true, a _prev bucket can win over the “current” bucket instead of being used strictly as a fallback when current has no suitable provider.

If the intent from the linked issue is “_prev only as fallback when current queue has no match”, consider:

  • Splitting keys into “current” and _prev groups via the 5th segment and trying all currents first, only then _prev buckets.
  • Or scanning twice (once with a pattern that excludes _prev, then with one that only includes _prev).

Also note that findSuitableProviderRate performs multiple Ent queries per provider; if this path becomes hot, caching ProviderOrderToken / ProviderCurrencies per provider+token+currency (as hinted by the TODO) would help.

Also applies to: 968-1047


1411-1458: Cairo ByteArray and string→decimal helpers look correct; consider stricter validation

  • ParseByteArray matches Cairo’s ByteArray layout (num full 31‑byte chunks, then pending word + length) and safely guards against out‑of‑bounds access when len(data) < numFullChunks+3.
  • ParseByteArrayFromJSON correctly reconstructs the felt slice from {data.value, pending_word.value, pending_word_len.value} and delegates to ParseByteArray. Right now, invalid full‑chunk entries (HexToFelt error) are silently skipped, which can cause num_full_chunks to overcount and return an empty string. If malformed Voyager payloads are a concern, failing fast (return "", err) instead of continue would be safer and easier to debug.
  • ParseStringAsDecimals and parseHexString handle both 0x/0X hex and plain decimal strings and use decimal.NewFromBigInt for hex, which is appropriate for large on‑chain values.

Overall the utilities look good and align with how Starknet events are consumed elsewhere; the only suggestion is to treat inconsistent ByteArray JSON as an error instead of degrading to "".

Also applies to: 1460-1529, 1531-1568

services/starknet/client.go (1)

676-709: OrderCreated event shaping aligns with indexer expectations

The refactor to:

  • Convert amount, protocolFee, and rate into decimal.Decimal via u.ParseStringAsDecimals.
  • Decode message_hash using u.ParseByteArray.
  • Normalize address, sender, and token via cryptoUtils.NormalizeStarknetAddress.

is consistent with how indexer/starknet.go reads decoded.indexed_params.amount (as decimal.Decimal) and non‑indexed params, and it keeps event shape compatible between RPC and Voyager paths. The extra ParseStringAsDecimals(bigInt.String()) hop is slightly redundant but harmless.

Also applies to: 714-728

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b59e56 and 677c957.

📒 Files selected for processing (5)
  • controllers/index.go (1 hunks)
  • services/indexer/starknet.go (18 hunks)
  • services/starknet/client.go (9 hunks)
  • services/voyager.go (7 hunks)
  • utils/utils.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
controllers/index.go (2)
ent/network/where.go (1)
  • Identifier (75-77)
utils/http.go (1)
  • APIResponse (23-29)
services/starknet/client.go (2)
utils/utils.go (2)
  • ParseStringAsDecimals (1531-1548)
  • ParseByteArray (1413-1458)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
services/indexer/starknet.go (7)
ent/token/where.go (1)
  • ContractAddress (74-76)
services/voyager.go (2)
  • TransformVoyagerTransferToRPCFormat (1044-1102)
  • TransformVoyagerEventToRPCFormat (1105-1247)
ent/network/where.go (1)
  • GatewayContractAddress (85-87)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
types/types.go (2)
  • EventCounts (789-794)
  • OrderSettledEvent (84-92)
utils/rpc_events.go (1)
  • OrderSettledStarknetSelector (18-18)
services/common/indexer.go (1)
  • ProcessSettledOrders (119-174)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-test
🔇 Additional comments (4)
services/starknet/client.go (1)

848-866: Transfer event address normalization is a nice consistency improvement

Normalizing:

  • address (FromAddress),
  • from, and
  • to

via cryptoUtils.NormalizeStarknetAddress brings RPC transfer events in line with the normalized Starknet address format you use elsewhere (Voyager transforms, indexer). This should reduce downstream comparison issues without changing semantics.

Also applies to: 855-867

services/voyager.go (2)

1045-1077: Voyager transfer → RPC transform is sound and defensively coded

The updated TransformVoyagerTransferToRPCFormat:

  • Validates tokenDecimals > 0 and non‑empty transferValue.
  • Parses transferValue as decimal.Decimal and converts it to subunits via u.ToSubunit(decimalValue, int8(tokenDecimals)).
  • Emits the subunit value as a string in decoded.non_indexed_params.value and sets topic to u.TransferStarknetSelector.

This matches how the Starknet indexer later expects a string subunit amount and keeps the RPC‑shaped transfer events consistent with the native Starknet client path. Returning nil on invalid inputs and letting the caller skip those transfers is a reasonable degradation strategy.

Also applies to: 1083-1096


1627-1665: Exported GetEventsByTransactionHashRPC is wired correctly into fallbacks

The refactor to:

  • Introduce GetEventsByTransactionHashRPC(ctx, txHash) and
  • Use it as the RPC fallback in both GetEventsByTransactionHashImmediate and the queued GetEventsByTransactionHash

is consistent with how other RPC fallbacks in this service are structured. It centralizes the Starknet client call and keeps the call sites cleaner without changing behavior.

Also applies to: 1723-1726, 1763-1771

services/indexer/starknet.go (1)

52-63: Receive-address indexing flow is coherent with new RPC/Voyager event shapes

  • For a specific txHash, you now pull events via GetEventsByTransactionHashRPC and accumulate them into transactionHashReceipt.
  • For range/last‑N mode, you fetch transfers via GetAddressTokenTransfers, convert each with TransformVoyagerTransferToRPCFormat, and feed the RPC‑shaped events into processReceiveAddressByTransactionEvents.
  • processReceiveAddressByTransactionEvents then:
    • Validates decoded.non_indexed_params.{from,to,value} are strings,
    • Normalizes addresses when comparing with userAccountAddress and the gateway contract,
    • Converts value from subunits using token decimals before passing to common.ProcessTransfers.

This end‑to‑end path looks consistent and robust; logging will also be much easier to reason about now.

Also applies to: 75-86, 100-122, 133-142, 186-203, 250-259

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: 2

🧹 Nitpick comments (1)
services/voyager.go (1)

1126-1231: Consider adding a default case for unknown event types.

The switch statement handles three specific Starknet event selectors but lacks a default case. If an unrecognized topic is encountered, the function will silently return an event with empty indexed_params and non_indexed_params. Consider whether this is the intended behavior or if unknown event types should be logged or return an error.

Optional: Add default case with logging
 		case u.OrderRefundedStarknetSelector:
 			// ... existing code ...
+		default:
+			logger.WithFields(logger.Fields{
+				"Topic":           topics,
+				"TransactionHash": transactionHash,
+			}).Warnf("Unrecognized event topic, returning empty params")
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 677c957 and 440fbec.

📒 Files selected for processing (1)
  • services/voyager.go (8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-test
🔇 Additional comments (5)
services/voyager.go (5)

709-710: LGTM: Expanded default lookback window.

The default timestamp window has been expanded from 4 to 7 days, which provides a wider range for transfer queries when block numbers are not specified.


1054-1076: LGTM: Robust validation for transfer value parsing.

The added validation ensures that tokenDecimals is positive and transferValue is non-empty before attempting decimal parsing and conversion to subunits. This prevents downstream errors and improves data quality.


1083-1095: LGTM: Consistent use of converted subunit values.

The changes correctly use the parsed and converted rawValueDecimals value instead of the raw string, and add the topic field for event type identification. This aligns with the standardized RPC format.


1667-1668: LGTM: Exported RPC fallback function.

Making GetEventsByTransactionHashRPC a public method allows external callers to use the RPC fallback path directly. All internal call sites have been updated consistently.


1105-1246: Note: PR objectives mismatch.

The linked issue #407 discusses saving previous provider rates in priority queues to reduce order failures, but the changes in this file focus on Voyager event transformation and parsing. The code changes appear to be well-implemented for their purpose, but there's a disconnect between the PR's linked issue and the actual changes being made.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/indexer/starknet.go (1)

223-258: Starknet indexer assumes block_number is int64 but emitters provide other types

Throughout the Starknet indexer you assert:

blockNumber, ok := eventMap["block_number"].(int64)
...
if !ok { /* skip */ }

in:

  • processReceiveAddressByTransactionEvents (Line 223),
  • indexGatewayByTransaction (Line 301),
  • indexProviderAddressByTransaction (Line 684),

but:

  • The Starknet RPC client currently stores block_number as a uint64 (emittedEvent.BlockNumber).
  • Voyager transforms for transfers/events presently don’t normalize to int64 and may leave block_number as 0.

Net effect: every Starknet event coming from the RPC path, and many from Voyager, will be skipped here because the type assertion fails.

Once client.go and Voyager transforms are updated to emit int64 (or at least a consistent numeric type), this code will work. Until then, either:

  • Accept multiple numeric forms and normalize locally:

    var blockNumber int64
    switch v := eventMap["block_number"].(type) {
    case int64:
        blockNumber = v
    case uint64:
        blockNumber = int64(v)
    case float64:
        blockNumber = int64(v)
    default:
        logger.Errorf("Unexpected block_number type %T", v)
        continue
    }
  • Or, better, fix all emitters (RPC client and Voyager transformers) to set block_number as int64 and keep the assertions strict here.

Right now this mismatch is a functional blocker for Starknet indexing.

Also applies to: 301-307, 684-688

controllers/index.go (1)

2233-2247: Avoid EVM-only type assertion in IndexTransaction when network is Starknet

You’ve correctly introduced Starknet support:

} else if strings.HasPrefix(network.Identifier, "starknet") {
    indexerInstance, indexerErr = indexer.NewIndexerStarknet()

but later, when handling an address that’s found as a receive address, you still do:

counts, err := indexerInstance.(*indexer.IndexerEVM).
    IndexReceiveAddressWithBypass(ctx, token, address, fromBlock, toBlock, txHash, true)

This downcast assumes indexerInstance is always *IndexerEVM. For Starknet networks, it will actually be *IndexerStarknet, so this cast will panic at runtime as soon as a Starknet receive address is indexed.

You need to branch by network type here as well. For example:

Sketch of network-aware receive-address indexing
- counts, err := indexerInstance.(*indexer.IndexerEVM).IndexReceiveAddressWithBypass(
-     ctx, token, address, fromBlock, toBlock, txHash, true)
+ switch idx := indexerInstance.(type) {
+ case *indexer.IndexerEVM:
+     counts, err := idx.IndexReceiveAddressWithBypass(
+         ctx, token, address, fromBlock, toBlock, txHash, true)
+ case *indexer.IndexerStarknet:
+     counts, err := idx.IndexReceiveAddress(
+         ctx, token, address, fromBlock, toBlock, txHash)
+ default:
+     err = fmt.Errorf("unsupported indexer type %T for receive address", idx)
+ }

(adjust signatures to match your Starknet indexer API.)

Without this change, Starknet address-based indexing via IndexTransaction is unsafe.

Also applies to: 2407-2476

♻️ Duplicate comments (2)
services/starknet/client.go (1)

712-722: Unify block_number type across emitters and indexer (currently uint64 vs int64)

In all handlers you emit:

"block_number": emittedEvent.BlockNumber, // uint64

but the Starknet indexer now does:

blockNumber, ok := eventMap["block_number"].(int64)

in both processReceiveAddressByTransactionEvents, indexGatewayByTransaction, and indexProviderAddressByTransaction. That type mismatch means the assertions will always fail for RPC‑shaped events, so Starknet transfers and gateway/provider events from the RPC path will be silently skipped.

To fix this, standardize block_number on a single type (int64 is already assumed by the indexer). For example:

Proposed fix: cast to int64 in all handlers
-    event := map[string]interface{}{
-        "block_number":     emittedEvent.BlockNumber,
+    event := map[string]interface{}{
+        "block_number":     int64(emittedEvent.BlockNumber),

Apply the same change in handleOrderCreated, handleOrderSettled, handleOrderRefunded, and handleTransfer.

Also applies to: 775-789, 822-832, 855-867

services/voyager.go (1)

1105-1174: Align Voyager event transform with RPC/indexer schema and avoid unsafe assertions

TransformVoyagerEventToRPCFormat has several schema and safety problems that will break Starknet gateway indexing:

  1. Block number typing from Voyager

You currently do:

blockNumber, _ := event["blockNumber"].(int64)

but elsewhere in this file you treat Voyager blockNumber values as float64 (block["blockNumber"].(float64) in makeVoyagerBlocksAPICall). This assertion will almost always fail, leaving blockNumber == 0 in the output rpcEvent, while the indexer expects a meaningful int64 block number. Use the same pattern as for transfers: accept float64 and convert to int64.

  1. topic vs topics field mismatch with indexer

The output map sets:

"topic": keys[0],

but indexGatewayByTransaction reads:

eventSignature, ok := eventMap["topics"].(string)

So Voyager‑transformed gateway events will never be recognized by the indexer. You should either:

  • Emit "topics" as a string (using the already‑derived topics variable), or
  • Update the indexer to consume "topic" consistently.

Example fix on the transform side:

-    "topic":            keys[0],
+    "topics":           topics,
  1. Inconsistent amount typing vs RPC path

For OrderCreated you set:

case "amount":
    orderAmount, err := u.ParseStringAsDecimals(keyValue.(string))
    ...
    indexedParams[keyName] = orderAmount.String()

Whereas the RPC Starknet client emits amount as decimal.Decimal (and the indexer asserts indexedParams["amount"].(decimal.Decimal)). This means Voyager‑originated OrderCreated events will be silently skipped by indexGatewayByTransaction.

Either:

  • Change this to store the decimal.Decimal directly (no .String()), or
  • Teach the indexer to accept both decimal.Decimal and string (and parse the latter).
  1. Unchecked type assertions can still panic (similar to a prior review)

Patterns like:

orderAmount, err := u.ParseStringAsDecimals(keyValue.(string))
...
valueDecimals, err := u.ParseStringAsDecimals(dataValue.(string))
...
dataValue, _ := dataMap["value"].(string)

will panic if Voyager returns a non‑string (e.g., nil, numeric, or nested map). The previous review already flagged this class of issues.

Consider safe assertions with descriptive errors, e.g.:

valStr, ok := dataValue.(string)
if !ok {
    return nil, fmt.Errorf("expected string for %s, got %T", dataName, dataValue)
}

and likewise for all ...(string) assertions in this function.

Together, these issues will cause Voyager‑based Starknet event indexing either to skip events or panic. Fixing them will make Voyager and RPC paths interoperable with the updated Starknet indexer.

Also applies to: 1185-1232, 1237-1247

🧹 Nitpick comments (2)
services/starknet/client.go (1)

751-773: Simplify settle_percent / rebate_percent parsing and drop extractUint64AsString

handleOrderSettled currently converts:

settlePercent := emittedEvent.Data[1].BigInt(...).Uint64()
settlePercentStr, ok := extractUint64AsString(settlePercent)
...
settledPercentDecimals, err := u.ParseStringAsDecimals(settlePercentStr)

and extractUint64AsString takes interface{} even though all callers pass uint64.

You can simplify and remove the helper entirely:

Proposed simplification
-    settlePercent := emittedEvent.Data[1].BigInt(big.NewInt(0)).Uint64()
-    rebatePercent := emittedEvent.Data[2].BigInt(big.NewInt(0)).Uint64()
-
-    settlePercentStr, ok := extractUint64AsString(settlePercent)
-    if !ok {
-        return nil, fmt.Errorf("failed to extract settle_percent as uint64")
-    }
-    rebatePercentStr, ok := extractUint64AsString(rebatePercent)
-    if !ok {
-        return nil, fmt.Errorf("failed to extract rebate_percent as uint64")
-    }
-
-    settledPercentDecimals, err := u.ParseStringAsDecimals(settlePercentStr)
+    settlePercent := emittedEvent.Data[1].BigInt(big.NewInt(0)).Uint64()
+    rebatePercent := emittedEvent.Data[2].BigInt(big.NewInt(0)).Uint64()
+
+    settledPercentDecimals, err := u.ParseStringAsDecimals(fmt.Sprintf("%d", settlePercent))
     if err != nil {
         return nil, fmt.Errorf("failed to parse settle_percent as decimals: %w", err)
     }
-    rebatePercentDecimals, err := u.ParseStringAsDecimals(rebatePercentStr)
+    rebatePercentDecimals, err := u.ParseStringAsDecimals(fmt.Sprintf("%d", rebatePercent))
     if err != nil {
         return nil, fmt.Errorf("failed to parse rebate_percent as decimals: %w", err)
     }

and then delete extractUint64AsString entirely.

This keeps the code straightforward and avoids an unnecessary interface{} helper.

Also applies to: 875-880

controllers/index.go (1)

2127-2134: Clarify whether range-only indexing (without pathParam) is intentionally disallowed

The new validation:

pathParam := ctx.Param("tx_hash_or_address")

if pathParam == "" || !strings.HasPrefix(pathParam, "0x") {
    u.APIResponse(..., "Invalid path parameter. Must be a valid transaction hash or address")
    return
}

means callers can no longer index purely by from_block/to_block range; they must always provide a non-empty, 0x-prefixed path parameter even if they’re only interested in block ranges. This tightens behavior compared to the earlier version and to the subsequent check:

if txHash == "" && address == "" && (fromBlockStr == "" || toBlockStr == "") {
    // ...
}

If that stricter behavior is intentional, consider updating API docs to make it explicit. If you still want to support range-only indexing, change the validation to only enforce the 0x prefix when pathParam != "" and rely on the later “at least one indexing method” check.

Also applies to: 2226-2230

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 440fbec and a176060.

📒 Files selected for processing (4)
  • controllers/index.go (3 hunks)
  • services/indexer/starknet.go (18 hunks)
  • services/starknet/client.go (9 hunks)
  • services/voyager.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
services/starknet/client.go (2)
utils/utils.go (2)
  • ParseStringAsDecimals (1531-1548)
  • ParseByteArray (1413-1458)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
services/voyager.go (4)
utils/logger/logger.go (3)
  • WithFields (76-109)
  • Fields (73-73)
  • Errorf (141-146)
utils/utils.go (3)
  • ToSubunit (44-56)
  • ParseStringAsDecimals (1531-1548)
  • ParseByteArrayFromJSON (1460-1529)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
utils/rpc_events.go (3)
  • OrderCreatedStarknetSelector (17-17)
  • OrderSettledStarknetSelector (18-18)
  • OrderRefundedStarknetSelector (19-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-test
🔇 Additional comments (3)
services/indexer/starknet.go (3)

132-143: Receive-address transfer processing looks good but relies on consistent event schema

The refactor to batch process transferEvents via processReceiveAddressByTransactionEvents with:

  • Normalized Starknet addresses (NormalizeStarknetAddress on from/to and user address),
  • Robust checks for missing/empty decoded and non_indexed_params,
  • Decimal parsing of valueStr and rescaling by token decimals,

is solid and aligns well with the new Voyager→RPC transfer transform.

Once block_number typing is unified (per other comments), this path should correctly handle both RPC receipts (via GetEventsByTransactionHashRPC) and Voyager transfer events transformed through TransformVoyagerTransferToRPCFormat.

Also applies to: 186-268


273-347: Gateway event parsing assumes decimal+schema alignment with both RPC and Voyager transforms

The new indexGatewayByTransaction logic is directionally correct: it batches all gateway events and constructs typed OrderCreatedEvent, OrderSettledEvent, and OrderRefundedEvent from decoded params.

However, it assumes:

  • indexed_params["amount"], ["sender"], and ["token"] exist and:
    • amount is decimal.Decimal,
    • sender/token are strings.
  • non_indexed_params["protocol_fee"], ["rate"], ["settle_percent"], ["rebate_percent"], and ["fee"] are all decimal.Decimal.

Given:

  • The RPC Starknet client now emits these values as decimal.Decimal (good).
  • TransformVoyagerEventToRPCFormat currently stores amount as a string (not decimal.Decimal) and emits "topic" instead of "topics",

Voyager‑originated events will currently be dropped here (type assertions fail or topics missing). Once the transformer is updated as suggested in services/voyager.go (emit topics as string and use decimal.Decimal for amount), this batching logic should work for both sources.

No change needed here, but it’s tightly coupled to those upstream fixes; keep that dependency in mind when testing.

Also applies to: 375-399, 429-459


641-751: Provider settlement indexing is consistent with new decimal schema but blocked by block_number type

indexProviderAddressByTransaction correctly:

  • Filters only OrderSettled events by selector/topic.
  • Narrows to events whose liquidity_provider matches the normalized provider address.
  • Expects settle_percent and rebate_percent as decimal.Decimal, matching the updated RPC and Voyager transforms.
  • Builds OrderSettledEvent values and delegates to common.ProcessSettledOrders.

The only hard blocker here is again the block_number assertion as int64. Once emitters are fixed to use int64 (or you relax the assertion as suggested earlier), this method should behave as intended.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/starknet/client.go (1)

840-853: Bounds check missing for emittedEvent.Data access; potential panic.

The comment at lines 841-842 states data is in keys for Transfer events, but lines 848-853 access emittedEvent.Data[0] through Data[3] without verifying length. If Data has fewer than 4 elements, this will panic.

🔎 Proposed fix with bounds check
 func (c *Client) handleTransfer(emittedEvent rpc.EmittedEvent) (map[string]interface{}, error) {
 	// Keys: [event_selector, from, to, value_low, value_high]
-	// Data: [] (empty - all data is in keys for Transfer event on Starknet)
+	// Data: [from, to, value_low, value_high] for some token contracts

 	if len(emittedEvent.Keys) == 0 {
 		return nil, fmt.Errorf("invalid Transfer event: insufficient keys")
 	}

+	if len(emittedEvent.Data) < 4 {
+		return nil, fmt.Errorf("invalid Transfer event: insufficient data (expected 4, got %d)", len(emittedEvent.Data))
+	}
+
 	from := emittedEvent.Data[0]
 	to := emittedEvent.Data[1]
♻️ Duplicate comments (4)
services/voyager.go (3)

1053-1060: Validate tokenDecimals type before comparison.

Line 1052 asserts tokenDecimals as int, but JSON unmarshaling produces float64 for numbers. This assertion will silently yield 0, triggering the warning at lines 1054-1060 for valid transfers.

This duplicates the past review comment about numeric type handling. The fix should handle multiple numeric types:

-	tokenDecimals, _ := transfer["tokenDecimals"].(int)
+	var tokenDecimals int
+	switch v := transfer["tokenDecimals"].(type) {
+	case float64:
+		tokenDecimals = int(v)
+	case int:
+		tokenDecimals = v
+	case int64:
+		tokenDecimals = int(v)
+	}

1130-1183: Unchecked type assertions may panic on unexpected Voyager data.

Multiple unchecked type assertions remain in the OrderCreatedStarknetSelector case:

  • Line 1139: keyValue.(string)
  • Line 1158: dataValue.(string)

If Voyager returns unexpected types, these will panic.

This was flagged in a past review. Apply safe assertions consistently:

 case "amount":
-	orderAmount, err := u.ParseStringAsDecimals(keyValue.(string))
+	keyValueStr, ok := keyValue.(string)
+	if !ok {
+		return nil, fmt.Errorf("expected string for amount, got %T", keyValue)
+	}
+	orderAmount, err := u.ParseStringAsDecimals(keyValueStr)

1222-1232: Unchecked type assertion in OrderRefundedStarknetSelector case.

Line 1225 asserts dataValue as string without checking. If the fee value comes as a different type (e.g., a number), this will panic.

-	dataValue, _ := dataMap["value"].(string)
-	feeValue, err := u.ParseStringAsDecimals(dataValue)
+	dataValue, ok := dataMap["value"].(string)
+	if !ok {
+		return nil, fmt.Errorf("expected string for fee value, got %T", dataMap["value"])
+	}
+	feeValue, err := u.ParseStringAsDecimals(dataValue)
services/indexer/starknet.go (1)

322-325: Type mismatch: amount is string from Voyager but expected as decimal.Decimal.

Line 322-323 expects indexedParams["amount"].(decimal.Decimal), but TransformVoyagerEventToRPCFormat in voyager.go (line 1143) sets it as orderAmount.String() — a string, not decimal.Decimal.

This will cause all Voyager-sourced OrderCreated events to be silently skipped.

🔎 Fix in voyager.go or accept both types here

Option 1: Fix voyager.go line 1143

-	indexedParams[keyName] = orderAmount.String()
+	indexedParams[keyName] = orderAmount

Option 2: Handle both types in starknet.go

-	orderAmount, ok := indexedParams["amount"].(decimal.Decimal)
-	if !ok {
-		continue
-	}
+	var orderAmount decimal.Decimal
+	switch v := indexedParams["amount"].(type) {
+	case decimal.Decimal:
+		orderAmount = v
+	case string:
+		var err error
+		orderAmount, err = decimal.NewFromString(v)
+		if err != nil {
+			continue
+		}
+	default:
+		continue
+	}
🧹 Nitpick comments (3)
services/starknet/client.go (2)

755-773: Simplify extractUint64AsString usage or inline conversion.

settlePercent and rebatePercent at lines 752-753 are already uint64 values after the BigInt().Uint64() call. The extractUint64AsString helper then type-asserts them back to uint64, which is redundant. Consider inlining the conversion:

-	settlePercentStr, ok := extractUint64AsString(settlePercent)
-	if !ok {
-		return nil, fmt.Errorf("failed to extract settle_percent as uint64")
-	}
+	settlePercentStr := fmt.Sprintf("%d", settlePercent)

The current code works correctly but adds unnecessary indirection.


775-791: Inconsistent address normalization in handleOrderSettled.

handleOrderCreated (lines 714, 719-720) and handleTransfer (lines 858, 864-865) normalize addresses using cryptoUtils.NormalizeStarknetAddress, but handleOrderSettled outputs raw addresses at lines 778 and 783 (liquidityProvider.String(), emittedEvent.FromAddress.String()). This inconsistency may cause address comparison failures downstream.

🔎 Proposed fix for consistency
 	event := map[string]interface{}{
 		"block_number":     float64(emittedEvent.BlockNumber),
 		"transaction_hash": emittedEvent.TransactionHash.String(),
-		"address":          emittedEvent.FromAddress.String(),
+		"address":          cryptoUtils.NormalizeStarknetAddress(emittedEvent.FromAddress.String()),
 		"topics":           emittedEvent.Keys[0].String(),
 		"decoded": map[string]interface{}{
 			"indexed_params": map[string]interface{}{
 				"order_id":           orderID.String(),
-				"liquidity_provider": liquidityProvider.String(),
+				"liquidity_provider": cryptoUtils.NormalizeStarknetAddress(liquidityProvider.String()),
 			},

Apply the same pattern to handleOrderRefunded at line 824.

services/indexer/starknet.go (1)

610-623: Consider deduplicating transaction hashes before fetching receipts.

The loop fetches RPC receipts for each transfer, but multiple transfers could share the same txHash. Consider collecting unique hashes first to avoid redundant RPC calls:

txHashSet := make(map[string]bool)
for _, transfer := range transfers {
    if txHash, _ := transfer["txHash"].(string); txHash != "" {
        txHashSet[txHash] = true
    }
}
for txHash := range txHashSet {
    eventsByTx, err := s.voyagerService.GetEventsByTransactionHashRPC(ctx, txHash)
    // ...
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a176060 and c46417c.

📒 Files selected for processing (3)
  • services/indexer/starknet.go (18 hunks)
  • services/starknet/client.go (9 hunks)
  • services/voyager.go (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
services/voyager.go (4)
utils/logger/logger.go (5)
  • WithFields (76-109)
  • Fields (73-73)
  • Warnf (133-138)
  • Infof (125-130)
  • Errorf (141-146)
utils/utils.go (3)
  • ToSubunit (44-56)
  • ParseStringAsDecimals (1531-1548)
  • ParseByteArrayFromJSON (1460-1529)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
utils/rpc_events.go (3)
  • OrderCreatedStarknetSelector (17-17)
  • OrderSettledStarknetSelector (18-18)
  • OrderRefundedStarknetSelector (19-19)
services/indexer/starknet.go (8)
ent/token/where.go (1)
  • ContractAddress (74-76)
services/voyager.go (2)
  • TransformVoyagerTransferToRPCFormat (1044-1102)
  • TransformVoyagerEventToRPCFormat (1105-1250)
ent/network/where.go (1)
  • GatewayContractAddress (85-87)
utils/crypto/crypto.go (1)
  • NormalizeStarknetAddress (312-324)
ent/ent.go (1)
  • Value (44-44)
types/types.go (2)
  • EventCounts (789-794)
  • OrderSettledEvent (84-92)
utils/rpc_events.go (2)
  • OrderCreatedStarknetSelector (17-17)
  • OrderSettledStarknetSelector (18-18)
services/common/indexer.go (1)
  • ProcessSettledOrders (119-174)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-test
🔇 Additional comments (4)
services/starknet/client.go (1)

676-709: LGTM on decimal parsing for OrderCreated event.

The decimal parsing for amount, protocol_fee, and rate is properly implemented with appropriate error handling. The switch to u.ParseByteArray for message_hash extraction is cleaner than the previous custom implementation.

services/voyager.go (1)

1670-1689: LGTM on exporting GetEventsByTransactionHashRPC.

Exporting this method enables external callers (like the Starknet indexer) to directly use the RPC fallback path for batch processing. The function properly handles nil address and topics parameters.

services/indexer/starknet.go (2)

54-62: LGTM on batch receipt collection pattern.

The change from per-transaction processing to collecting receipts into transactionHashReceipt slice enables efficient batch processing. The RPC fallback via GetEventsByTransactionHashRPC is correctly integrated.


704-712: LGTM on decimal type handling for settlement percentages.

The code correctly expects decimal.Decimal for settle_percent and rebate_percent, which matches the output from both handleOrderSettled in client.go and TransformVoyagerEventToRPCFormat in voyager.go.

@onahprosper onahprosper merged commit 1aedd23 into main Dec 20, 2025
2 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.

3 participants