Skip to content

Commit 06b8084

Browse files
feat(soroban, sdk): add per-contract event tracking and event type query (SC-17) (#1169)
Implements SC-17 feature adding: - Soroban contract: per-contract event counts and event type registry with query methods contract_event_count() and contract_event_types() - Python SDK: get_contract_event_types() method for sync/async clients, ContractEventTypeInfo model, CLI event-types subcommand - TypeScript SDK: getContractEventTypes() method and ContractEventTypeInfo interface Co-authored-by: TheCreatorNode <basseyu01@gmail.com>
1 parent 496180a commit 06b8084

8 files changed

Lines changed: 368 additions & 1 deletion

File tree

sdk/python/soroscan/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from soroscan.webhook_verification import verify_webhook_signature
2424
from soroscan.models import (
2525
ContractEvent,
26+
ContractEventTypeInfo,
2627
ContractStats,
2728
EventEntry,
2829
PaginatedResponse,
@@ -43,6 +44,7 @@
4344
"Paginator",
4445
"AsyncPaginator",
4546
"ContractEvent",
47+
"ContractEventTypeInfo",
4648
"TrackedContract",
4749
"WebhookSubscription",
4850
"ContractStats",

sdk/python/soroscan/cli.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ def _handle_contracts(args: argparse.Namespace) -> int:
104104
)
105105
return 0
106106

107+
if args.contract_command == "event-types":
108+
types = client.get_contract_event_types(args.contract_id)
109+
if args.output == "json":
110+
_print_json(types)
111+
else:
112+
_print_table(
113+
types,
114+
["event_type", "count", "first_seen", "last_seen"],
115+
)
116+
return 0
117+
107118
response = client.get_contracts(
108119
is_active=args.active,
109120
search=args.search,
@@ -190,6 +201,14 @@ def build_parser() -> argparse.ArgumentParser:
190201
contracts_get.add_argument("contract_id")
191202
contracts_get.add_argument("--output", choices=["table", "json"], default="table")
192203
contracts_get.set_defaults(func=_handle_contracts)
204+
contracts_event_types = contract_subcommands.add_parser(
205+
"event-types", help="Get event types for a contract (SC-17)"
206+
)
207+
contracts_event_types.add_argument("contract_id", help="Contract address (C...)")
208+
contracts_event_types.add_argument(
209+
"--output", choices=["table", "json"], default="table"
210+
)
211+
contracts_event_types.set_defaults(func=_handle_contracts)
193212

194213
return parser
195214

sdk/python/soroscan/client.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from soroscan.models import (
2121
ContractEvent,
22+
ContractEventTypeInfo,
2223
ContractStats,
2324
EventEntry,
2425
PaginatedResponse,
@@ -302,6 +303,21 @@ def get_contract_stats(self, contract_id: str) -> ContractStats:
302303
data = self._handle_response(response)
303304
return ContractStats.model_validate(data)
304305

306+
def get_contract_event_types(self, contract_id: str) -> list[ContractEventTypeInfo]:
307+
"""
308+
Get event types and their counts for a specific contract (SC-17).
309+
310+
Args:
311+
contract_id: Contract address (C...)
312+
313+
Returns:
314+
List of event type info with counts and first/last seen timestamps
315+
"""
316+
url = urljoin(self.base_url, f"/api/contracts/{contract_id}/event-types/")
317+
response = self._client.get(url, headers=self._get_headers())
318+
data = self._handle_response(response)
319+
return [ContractEventTypeInfo.model_validate(item) for item in data]
320+
305321
def get_events(
306322
self,
307323
contract_id: str | None = None,
@@ -809,6 +825,21 @@ async def get_contract_stats(self, contract_id: str) -> ContractStats:
809825
data = self._handle_response(response)
810826
return ContractStats.model_validate(data)
811827

828+
async def get_contract_event_types(self, contract_id: str) -> list[ContractEventTypeInfo]:
829+
"""
830+
Get event types and their counts for a specific contract (SC-17).
831+
832+
Args:
833+
contract_id: Contract address (C...)
834+
835+
Returns:
836+
List of event type info with counts and first/last seen timestamps
837+
"""
838+
url = urljoin(self.base_url, f"/api/contracts/{contract_id}/event-types/")
839+
response = await self._client.get(url, headers=self._get_headers())
840+
data = self._handle_response(response)
841+
return [ContractEventTypeInfo.model_validate(item) for item in data]
842+
812843
async def get_events(
813844
self,
814845
contract_id: str | None = None,

sdk/python/soroscan/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ class RecordEventResponse(BaseModel):
9191
error: str | None = Field(None, description="Error message if failed")
9292

9393

94+
# ── SC-17: Contract event type info ───────────────────────────────────────────
95+
96+
class ContractEventTypeInfo(BaseModel):
97+
"""Event type summary for a contract (SC-17)."""
98+
99+
event_type: str = Field(..., description="Event type name")
100+
count: int = Field(..., description="Number of events of this type")
101+
first_seen: str = Field(..., description="ISO timestamp of first occurrence")
102+
last_seen: str = Field(..., description="ISO timestamp of last occurrence")
103+
104+
94105
# ── SC-29: Batch event recording ──────────────────────────────────────────────
95106

96107
class EventEntry(BaseModel):

sdk/typescript/src/client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
SoroScanClientConfig,
33
SoroScanApiError,
4+
ContractEventTypeInfo,
45
GetEventsParams,
56
GetEventsResponse,
67
GetContractsParams,
@@ -179,6 +180,24 @@ export class SoroScanClient {
179180
);
180181
}
181182

183+
/**
184+
* Get event types and their counts for a specific contract (SC-17).
185+
*
186+
* @example
187+
* const types = await client.getContractEventTypes('CCAAA...');
188+
* for (const t of types) {
189+
* console.log(t.eventType, t.count);
190+
* }
191+
*/
192+
async getContractEventTypes(
193+
contractId: string
194+
): Promise<ContractEventTypeInfo[]> {
195+
return this.#request<ContractEventTypeInfo[]>(
196+
"GET",
197+
`/v1/contracts/${encodeURIComponent(contractId)}/event-types`
198+
);
199+
}
200+
182201
// ─── Transactions ──────────────────────────────────────────────────────────
183202

184203
/**

sdk/typescript/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export type {
4747
SubscribeWebhookParams,
4848
UpdateWebhookParams,
4949
WebhookListResponse,
50+
// SC-17: Contract event type info
51+
ContractEventTypeInfo,
5052
// SC-29: Batch event recording
5153
EventEntry,
5254
RecordEventsBatchParams,

sdk/typescript/src/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ export interface WebhookListResponse {
302302
totalCount: number;
303303
}
304304

305+
// ─────────────────────────────────────────────────────────────────────────────
306+
// SC-17: Contract event type info
307+
// ─────────────────────────────────────────────────────────────────────────────
308+
309+
export interface ContractEventTypeInfo {
310+
/** Event type name */
311+
eventType: string;
312+
/** Number of events of this type */
313+
count: number;
314+
/** ISO timestamp of first occurrence */
315+
firstSeen: string;
316+
/** ISO timestamp of last occurrence */
317+
lastSeen: string;
318+
}
319+
305320
// ─────────────────────────────────────────────────────────────────────────────
306321
// SC-29: Batch event recording
307322
// ─────────────────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)