Skip to content

Commit 9eca7db

Browse files
authored
[#1129] feat: soroban contract and sdk feature SC-24 (#1209)
Implements the tagged event indexing feature (SC-24) across all three layers of the SoroScan stack. ## Soroban Contract (soroban-contracts/soroscan_core) - Added TaggedEventRecord struct with a ags: Vec<Symbol> field (max 4 tags, enforced on-chain) - Added DataKey::LatestTaggedByType(Symbol) storage key so tagged events occupy a slot independent of the legacy EventRecord slot - Added MAX_TAGS = 4 constant and ContractError::TooManyTags variant - Added ecord_tagged_event() entry-point (auth-gated to registered indexers) that validates the tag count, stores the record, increments the global counter, and emits a (soroscan, sc24, event_type) event - Added latest_tagged_by_type() read-only entry-point - Added 6 unit tests covering: happy-path, empty tags, too-many-tags rejection, unauthorized indexer rejection, None-before-first-event, and legacy/tagged storage independence ## Python SDK (sdk/python) - Added TaggedEventRequest and TaggedEventResponse Pydantic models with a ≤4 tag validator - Added ecord_tagged_event() (sync) and async counterpart to both SoroScanClient and AsyncSoroScanClient - Exported new models from soroscan/__init__.py - Added ests/test_sc24_tagged_event.py with 16 unit tests covering sync, async (asyncio + trio), error propagation, and model validation ## TypeScript SDK (sdk/typescript) - Added RecordTaggedEventParams, RecordTaggedEventResponse, and MAX_TAGS constant to ypes.ts - Added GetEventsByContractsParams/Response (SC-23) and RecordStructuredEventParams/Response (SC-38) to ypes.ts so that all cross-feature imports resolve on this branch - Added ecordTaggedEvent() and getEventsByContracts() methods to SoroScanClient - Exported all new types from index.ts - Added est/sc24-tagged-event.test.ts (2 unit tests: serialization and default empty-tag behaviour) - Build ( pm run build) and all tests (46 passed) are green ## Documentation - Added Tagged Events (SC-24) example snippet to docs/sdk-python.md - Added Tagged Events (SC-24) example snippet to docs/sdk-typescript.md Closes #1129
1 parent d5bdbe2 commit 9eca7db

9 files changed

Lines changed: 795 additions & 86 deletions

File tree

docs/sdk-python.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,24 @@ async def main():
4444

4545
asyncio.run(main())
4646
```
47+
### Tagged Events (SC-24)
48+
49+
SoroScan supports recording and indexing events with up to 4 producer-defined tags. This allows off-chain indexers to categorize and filter events efficiently:
50+
51+
```python
52+
# Submit a tagged event
53+
response = client.record_tagged_event(
54+
contract_id="CCAAA...",
55+
event_type="transfer",
56+
payload_hash="a" * 64,
57+
tags=["defi", "token"]
58+
)
59+
print(f"Status: {response.status} | Echoed Tags: {response.tags}")
60+
```
4761

4862
## Features
4963

64+
5065
- **Type Safety**: Built with Pydantic v2 for robust data validation.
5166
- **Full Coverage**: 100% endpoint coverage for Contracts, Events, and Webhooks.
5267
- **Async Support**: Native support for `httpx` async clients.

docs/sdk-typescript.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,24 @@ do {
6262
after = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
6363
} while (after);
6464
```
65+
### Tagged Events (SC-24)
66+
67+
You can submit events classified by up to 4 tags to support efficient event category indexing:
68+
69+
```typescript
70+
const response = await client.recordTaggedEvent({
71+
contractId: "CCAAA...",
72+
eventType: "transfer",
73+
payloadHash: "a".repeat(64),
74+
tags: ["defi", "token"],
75+
});
76+
77+
console.log(`Status: ${response.status}, tags: ${response.tags.join(", ")}`);
78+
```
6579

6680
## Features
6781

82+
6883
- **Strict Typing**: Full TypeScript support for all request and response shapes.
6984
- **Zero Dependencies**: Uses native `fetch` API for a minimal footprint.
7085
- **Dual Build**: Supports both ESM and CJS modules.

sdk/python/soroscan/client.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,65 @@ def record_event(
463463
payload_hash=payload_hash,
464464
)
465465
response = self._client.post(url, headers=self._get_headers(), json=request.model_dump())
466-
data = self._handle_response(response)
467-
return RecordEventResponse.model_validate(data)
466+
return RecordEventResponse.model_validate(self._handle_response(response))
467+
468+
def record_structured_event(
469+
self,
470+
contract_id: str,
471+
event_type: str,
472+
payload_hash: str,
473+
schema_version: int,
474+
correlation_id: str,
475+
) -> RecordEventResponse:
476+
"""Submit an idempotent SC-38 structured event."""
477+
request = StructuredEventRequest(
478+
contract_id=contract_id,
479+
event_type=event_type,
480+
payload_hash=payload_hash,
481+
schema_version=schema_version,
482+
correlation_id=correlation_id,
483+
)
484+
response = self._client.post(
485+
urljoin(self.base_url, "/api/record/structured/"),
486+
headers=self._get_headers(),
487+
json=request.model_dump(),
488+
)
489+
return RecordEventResponse.model_validate(self._handle_response(response))
490+
491+
def record_tagged_event(
492+
self,
493+
contract_id: str,
494+
event_type: str,
495+
payload_hash: str,
496+
tags: list[str] | None = None,
497+
) -> TaggedEventResponse:
498+
"""Submit an SC-24 tagged event.
499+
500+
Tags are short producer-defined classification strings that allow
501+
off-chain indexers to filter events without decoding the full payload.
502+
At most 4 tags may be supplied per event.
503+
504+
Args:
505+
contract_id: Target contract address
506+
event_type: Event type name
507+
payload_hash: SHA-256 hash of payload (hex)
508+
tags: Up to 4 classification tag strings (default: empty list)
509+
510+
Returns:
511+
TaggedEventResponse with submission status and echoed tags
512+
"""
513+
request = TaggedEventRequest(
514+
contract_id=contract_id,
515+
event_type=event_type,
516+
payload_hash=payload_hash,
517+
tags=tags or [],
518+
)
519+
response = self._client.post(
520+
urljoin(self.base_url, "/api/record/tagged/"),
521+
headers=self._get_headers(),
522+
json=request.model_dump(),
523+
)
524+
return TaggedEventResponse.model_validate(self._handle_response(response))
468525

469526
def add_indexer(self, indexer_address: str) -> AddIndexerResponse:
470527
"""

0 commit comments

Comments
 (0)