feat: add IKA coordinator event listening to SUI indexer. #322
Merged
feat: add IKA coordinator event listening to SUI indexer. #322
Conversation
… only logged for now, processing will be added in a follow up PR Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Contributor
Reviewer's GuideAdds IKA coordinator event polling to the Sui indexer, including new DB state for coordinator cursors, storage helpers, event handling for IKA sign completion/rejection, and wiring into the existing polling loop with module-aware GraphQL event fetching. Sequence diagram for IKA coordinator polling in Sui indexersequenceDiagram
participant IndexerLoop
participant D1Storage
participant Processor
participant SuiGraphQLClient
participant SuiEventHandler
IndexerLoop->>D1Storage: getActiveCoordinatorPkgs(netCfg.name)
D1Storage-->>IndexerLoop: ikaPkgs
alt ikaPkgs not empty
IndexerLoop->>Processor: pollIkaEvents(ikaPkgs)
loop while hasAnyNextPage
Processor->>D1Storage: getIkaCursors(coordinatorPkgIds)
D1Storage-->>Processor: cursors
Processor->>SuiGraphQLClient: fetchEvents(packages with module coordinator_inner)
SuiGraphQLClient-->>Processor: results
loop for each coordinatorPkgId
alt events for pkgId exist
Processor->>SuiEventHandler: new SuiEventHandler(storage)
Processor->>SuiEventHandler: handleEvents(result.events)
SuiEventHandler-->>Processor: done
alt endCursor advanced
Processor->>D1Storage: saveIkaCursors(cursorsToSave)
D1Storage-->>Processor: done
end
end
end
end
else ikaPkgs empty
IndexerLoop-->>IndexerLoop: skip IKA polling
end
ER diagram for new indexer_ika_state tableerDiagram
indexer_ika_state {
TEXT sui_network
TEXT coordinator_pkg_id PK
TEXT ika_cursor
INTEGER updated_at
}
Updated class diagram for Sui indexer IKA supportclassDiagram
class Processor {
+NetworkConfig netCfg
+D1Storage storage
+EventFetcher eventFetcher
+pollAllNbtcEvents(packageIds string[]) void
+pollIkaEvents(coordinatorPkgIds string[]) void
}
class D1Storage {
+getActiveNbtcPkgs(suiNetwork SuiNet) string[]
+getActiveCoordinatorPkgs(suiNetwork SuiNet) string[]
+getIkaCursors(coordinatorPkgIds string[]) Record_string_string_or_null_
+saveIkaCursors(cursors CursorRecord[]) void
}
class CursorRecord {
+coordinatorPkgId string
+suiNetwork SuiNet
+cursor string
}
class EventFetcher {
<<interface>>
+fetchEvents(packages EventFetchPackage[]) Record_string_EventsBatch_
}
class EventFetchPackage {
+id string
+cursor string_or_null
+module string_or_undefined
}
class SuiGraphQLClient {
+url string
+fetchEvents(packages EventFetchPackage[]) Record_string_EventsBatch_
}
class SuiEventHandler {
-D1Storage storage
-number_or_undefined setupId
+constructor(storage D1Storage, setupId number_or_undefined)
+handleEvents(events SuiEventNode[]) Promise_void
-getSetupId() number
-handleMint(txDigest string, e MintEventRaw) Promise_void
-handleRedeemRequest(txDigest string, e RedeemRequestEventRaw) Promise_void
-handleSolved(e SolvedEventRaw) Promise_void
-handleIkaSignatureRecorded(e SignatureRecordedEventRaw) Promise_void
-handleCompletedSign(e CompletedSignEventRaw) Promise_void
-handleRejectedSign(e RejectedSignEventRaw) Promise_void
}
class CompletedSignEventRaw {
+sign_id string
+signature number[]
+is_future_sign boolean
}
class RejectedSignEventRaw {
+sign_id string
+is_future_sign boolean
}
Processor --> D1Storage
Processor --> EventFetcher
SuiGraphQLClient ..|> EventFetcher
Processor --> SuiEventHandler
SuiEventHandler --> D1Storage
D1Storage --> CursorRecord
EventFetcher --> EventFetchPackage
SuiEventHandler --> CompletedSignEventRaw
SuiEventHandler --> RejectedSignEventRaw
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The new
createInterfaceimport inmodels.tsis never used and can be safely removed to avoid confusion. - In
storage.ts, theeslint-disable-next-line @typescript-eslint/no-non-null-assertioncomment was removed but the non-null assertiondwalletIds[i]!remains; either restore the disable or refactor that code path to satisfy the linter without a non-null assertion. - In
Processor.pollIkaEventserror handling, you lognetwork: this.netCfgwhereas other logs usethis.netCfg.name; consider logging the network name for consistency and to avoid dumping the full config object.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `createInterface` import in `models.ts` is never used and can be safely removed to avoid confusion.
- In `storage.ts`, the `eslint-disable-next-line @typescript-eslint/no-non-null-assertion` comment was removed but the non-null assertion `dwalletIds[i]!` remains; either restore the disable or refactor that code path to satisfy the linter without a non-null assertion.
- In `Processor.pollIkaEvents` error handling, you log `network: this.netCfg` whereas other logs use `this.netCfg.name`; consider logging the network name for consistency and to avoid dumping the full config object.
## Individual Comments
### Comment 1
<location> `packages/sui-indexer/src/storage.ts:139-143` </location>
<code_context>
+ ): Promise<void> {
+ if (cursors.length === 0) return;
+
+ const stmt = this.db.prepare(
+ `INSERT INTO indexer_ika_state (coordinator_pkg_id, sui_network, ika_cursor, updated_at)
+ VALUES (?, ?, ?, ?)
+ ON CONFLICT(coordinator_pkg_id) DO UPDATE SET ika_cursor = excluded.ika_cursor, updated_at = excluded.updated_at`,
+ );
+
+ const now = Date.now();
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Conflict handling for `indexer_ika_state` only keys on `coordinator_pkg_id`, ignoring `sui_network`.
Because `coordinator_pkg_id` is the primary key and conflict target:
- You can't persist distinct cursors per `sui_network` for the same `coordinator_pkg_id`.
- If a `coordinator_pkg_id` is reused on another network, the row’s `sui_network` will never change and becomes misleading.
If you need per-network cursors, use a composite key `(sui_network, coordinator_pkg_id)` (and the same pair in `ON CONFLICT`). If you truly want a single global row per coordinator, enforce that assumption in code and consider dropping `sui_network` from the key path to avoid confusion.
Suggested implementation:
```typescript
const stmt = this.db.prepare(
`INSERT INTO indexer_ika_state (coordinator_pkg_id, sui_network, ika_cursor, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(sui_network, coordinator_pkg_id) DO UPDATE SET ika_cursor = excluded.ika_cursor, updated_at = excluded.updated_at`,
);
```
To fully support per-network cursors as implied by this change, the database schema for `indexer_ika_state` must be updated to:
1. Define a composite primary key or unique constraint on `(sui_network, coordinator_pkg_id)`.
2. Remove any conflicting single-column primary key on `coordinator_pkg_id` if it exists.
These changes should be implemented in the relevant migration or schema definition files for the indexer database.
</issue_to_address>
### Comment 2
<location> `packages/sui-indexer/src/handler.ts:39-42` </location>
<code_context>
await this.handleSolved(json as SolvedEventRaw);
} else if (e.type.includes("::nbtc::redeem_request::SignatureRecordedEvent")) {
await this.handleIkaSignatureRecorded(json as SignatureRecordedEventRaw);
+ } else if (e.type.includes("CompletedSignEvent")) {
+ await this.handleCompletedSign(e.json as CompletedSignEventRaw);
+ } else if (e.type.includes("RejectedSignEvent")) {
+ await this.handleRejectedSign(e.json as RejectedSignEventRaw);
}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The `includes("CompletedSignEvent")` / `includes("RejectedSignEvent")` checks are quite broad and may match unintended event types.
These branches rely on substring matches:
```ts
} else if (e.type.includes("CompletedSignEvent")) {
await this.handleCompletedSign(e.json as CompletedSignEventRaw);
} else if (e.type.includes("RejectedSignEvent")) {
await this.handleRejectedSign(e.json as RejectedSignEventRaw);
}
```
If Sui adds other events whose names contain these substrings, they could be routed here unintentionally. To avoid that, prefer an exact or fully-qualified match, e.g.:
```ts
if (e.type === "<pkg>::coordinator_inner::CompletedSignEvent")
// or
if (e.type.endsWith("::coordinator_inner::CompletedSignEvent"))
```
```suggestion
} else if (e.type.endsWith("::CompletedSignEvent")) {
await this.handleCompletedSign(e.json as CompletedSignEventRaw);
} else if (e.type.endsWith("::RejectedSignEvent")) {
await this.handleRejectedSign(e.json as RejectedSignEventRaw);
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
This was referenced Jan 26, 2026
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
* Removed optimistic polling from recordIkaSig into event handler so that when we listen to IKA events, we process a redeem request based on the 2 events we listen Signed-off-by: Rayane Charif <rayane.charif@gonative.cc> * Resolved comments Signed-off-by: Rayane Charif <rayane.charif@gonative.cc> * Resolved comments Signed-off-by: Rayane Charif <rayane.charif@gonative.cc> --------- Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds IKA (decentralized MPC coordinator) event listening functionality to the Sui indexer to track signature completion and rejection events for nBTC redeem operations.
Changes:
- Adds IkaEventHandler to process CompletedSignEvent and RejectedSignEvent from IKA coordinator
- Refactors signature verification from polling-based to event-driven architecture
- Introduces ika_state table and supporting storage methods for tracking IKA event cursors per network and coordinator package
- Extends GraphQL client and processor to support querying events from multiple modules
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sui-indexer/src/handler.ts | Renames SuiEventHandler to NbtcEventHandler and adds new IkaEventHandler class for processing IKA coordinator events |
| packages/sui-indexer/src/processor.ts | Adds pollIkaEvents method and suiClient parameter to support IKA event polling |
| packages/sui-indexer/src/storage.ts | Adds methods for IKA cursor management, redeem sign info retrieval, and updates getSigningRedeems query |
| packages/sui-indexer/src/models.ts | Adds IkaCursorUpdate and RedeemSignInfo interfaces and IKA event type definitions |
| packages/sui-indexer/src/index.ts | Integrates IKA event polling into main loop and refactors client initialization |
| packages/sui-indexer/src/redeem-service.ts | Removes polling-based signature verification logic, now handled by IKA event handlers |
| packages/sui-indexer/src/graphql-client.ts | Adds optional module parameter to support querying events from different Move modules |
| packages/lib/src/secrets.ts | Adds reusable getMnemonic helper function for secret retrieval with error handling |
| packages/btcindexer/src/btcindexer.ts | Updates to use new getMnemonic helper function |
| packages/btcindexer/db/migrations/0001_initial_schema.sql | Adds ika_state table and ika_coordinator_pkg column to setups table |
| packages/lib/src/test-helpers/init_db.ts | Updates test table list to include ika_state |
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
…lanning Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
robert-zaremba
approved these changes
Feb 11, 2026
Signed-off-by: Rayane Charif <rayane.charif@gonative.cc>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Events are only logged for now, processing will be added in a follow up PR
Closes: #297
Summary by Sourcery
Add support for polling and handling IKA coordinator events in the Sui indexer alongside existing nBTC event processing.
New Features:
Enhancements: