Skip to content

Commit 707cd30

Browse files
esenyidaniel-hashhunter-baddie
andauthored
feat:implement Soroban contract and sdk feature sc-10 (#1190)
Co-authored-by: hunter-baddie <sulyman.m1703634@st.futminna.edu.ng.com>
1 parent 19be985 commit 707cd30

7 files changed

Lines changed: 956 additions & 15 deletions

File tree

sdk/python/soroscan/cli.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@ def _handle_contracts(args: argparse.Namespace) -> int:
130130
return 0
131131

132132

133+
def _handle_record_event(args: argparse.Namespace) -> int:
134+
"""Submit a single event to the SoroScan contract (SC-10)."""
135+
with _build_client(args) as client:
136+
result = client.record_event(
137+
contract_id=args.contract_id,
138+
event_type=args.event_type,
139+
payload_hash=args.payload_hash,
140+
)
141+
if args.output == "json":
142+
_print_json(result)
143+
else:
144+
_print_table([result], ["status", "tx_hash", "transaction_status", "error"])
145+
return 0
146+
147+
133148
def _handle_webhooks(args: argparse.Namespace) -> int:
134149
with _build_client(args) as client:
135150
if args.webhook_command == "test":
@@ -210,6 +225,20 @@ def build_parser() -> argparse.ArgumentParser:
210225
)
211226
contracts_event_types.set_defaults(func=_handle_contracts)
212227

228+
# SC-10: record a single event from the CLI
229+
record = subcommands.add_parser(
230+
"record-event",
231+
help="Submit a single event to the SoroScan indexing contract",
232+
)
233+
record.add_argument("contract_id", help="Target contract address (C...)")
234+
record.add_argument("event_type", help="Event type name (e.g. transfer, swap)")
235+
record.add_argument(
236+
"payload_hash",
237+
help="SHA-256 hex hash of the event payload (64 hex chars)",
238+
)
239+
record.add_argument("--output", choices=["table", "json"], default="table")
240+
record.set_defaults(func=_handle_record_event)
241+
213242
return parser
214243

215244

sdk/python/tests/test_sc10_cli.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Tests for the SC-10 record-event CLI command."""
2+
3+
import json
4+
5+
import pytest
6+
from pytest_httpx import HTTPXMock
7+
8+
from soroscan.cli import main, build_parser
9+
10+
11+
BASE_CONTRACT = "CCAAA111222333444555666777888999AAABBBCCCDDDEEEFFF"
12+
EVENT_TYPE = "transfer"
13+
PAYLOAD_HASH = "a" * 64
14+
15+
RECORD_RESPONSE = {
16+
"status": "submitted",
17+
"tx_hash": "txabc123",
18+
"transaction_status": "pending",
19+
"error": None,
20+
}
21+
22+
23+
# ── Parser tests (no HTTP needed) ────────────────────────────────────────────
24+
25+
class TestRecordEventParser:
26+
def test_record_event_subcommand_exists(self):
27+
parser = build_parser()
28+
args = parser.parse_args([
29+
"record-event",
30+
BASE_CONTRACT,
31+
EVENT_TYPE,
32+
PAYLOAD_HASH,
33+
])
34+
assert args.contract_id == BASE_CONTRACT
35+
assert args.event_type == EVENT_TYPE
36+
assert args.payload_hash == PAYLOAD_HASH
37+
assert args.output == "table"
38+
39+
def test_record_event_json_flag(self):
40+
parser = build_parser()
41+
args = parser.parse_args([
42+
"record-event",
43+
BASE_CONTRACT,
44+
EVENT_TYPE,
45+
PAYLOAD_HASH,
46+
"--output", "json",
47+
])
48+
assert args.output == "json"
49+
50+
def test_record_event_missing_args_exits(self):
51+
parser = build_parser()
52+
with pytest.raises(SystemExit):
53+
parser.parse_args(["record-event"])
54+
55+
56+
# ── Integration tests (HTTP mocked) ──────────────────────────────────────────
57+
58+
class TestRecordEventCLI:
59+
def test_record_event_table_output(
60+
self,
61+
base_url: str,
62+
httpx_mock: HTTPXMock,
63+
capsys,
64+
) -> None:
65+
httpx_mock.add_response(
66+
url=f"{base_url}/api/record-event/",
67+
method="POST",
68+
json=RECORD_RESPONSE,
69+
status_code=201,
70+
)
71+
72+
exit_code = main([
73+
"--base-url", base_url,
74+
"record-event",
75+
BASE_CONTRACT,
76+
EVENT_TYPE,
77+
PAYLOAD_HASH,
78+
])
79+
80+
assert exit_code == 0
81+
output = capsys.readouterr().out
82+
assert "submitted" in output
83+
assert "txabc123" in output
84+
85+
def test_record_event_json_output(
86+
self,
87+
base_url: str,
88+
httpx_mock: HTTPXMock,
89+
capsys,
90+
) -> None:
91+
httpx_mock.add_response(
92+
url=f"{base_url}/api/record-event/",
93+
method="POST",
94+
json=RECORD_RESPONSE,
95+
status_code=201,
96+
)
97+
98+
exit_code = main([
99+
"--base-url", base_url,
100+
"record-event",
101+
BASE_CONTRACT,
102+
EVENT_TYPE,
103+
PAYLOAD_HASH,
104+
"--output", "json",
105+
])
106+
107+
assert exit_code == 0
108+
data = json.loads(capsys.readouterr().out)
109+
assert data["status"] == "submitted"
110+
assert data["tx_hash"] == "txabc123"
111+
assert data["error"] is None
112+
113+
def test_record_event_api_error_prints_to_stderr(
114+
self,
115+
base_url: str,
116+
httpx_mock: HTTPXMock,
117+
capsys,
118+
) -> None:
119+
httpx_mock.add_response(
120+
url=f"{base_url}/api/record-event/",
121+
method="POST",
122+
json={"detail": "Indexer not authorized"},
123+
status_code=403,
124+
)
125+
126+
exit_code = main([
127+
"--base-url", base_url,
128+
"record-event",
129+
BASE_CONTRACT,
130+
EVENT_TYPE,
131+
PAYLOAD_HASH,
132+
])
133+
134+
assert exit_code == 1
135+
assert "Error" in capsys.readouterr().err

0 commit comments

Comments
 (0)