spice-mcp 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,7 +80,7 @@ class ExecuteQueryTool(MCPTool):
80
80
  "additionalProperties": False,
81
81
  }
82
82
 
83
- async def execute(
83
+ def execute(
84
84
  self,
85
85
  *,
86
86
  query: str,
@@ -149,74 +149,41 @@ class ExecuteQueryTool(MCPTool):
149
149
  return {"type": "execution", "execution_id": execution_id, "status": "submitted"}
150
150
 
151
151
  if format == "metadata":
152
- try:
153
- meta = self.query_service.fetch_metadata(
154
- query=q_use,
155
- parameters=parameters,
156
- max_age=max_age,
157
- limit=limit,
158
- offset=offset,
159
- sample_count=sample_count,
160
- sort_by=sort_by,
161
- columns=columns,
162
- extras=extras,
163
- performance=performance,
164
- )
165
- except TypeError:
166
- # Back-compat: older services without 'extras'
167
- meta = self.query_service.fetch_metadata(
168
- query=q_use,
169
- parameters=parameters,
170
- max_age=max_age,
171
- limit=limit,
172
- offset=offset,
173
- sample_count=sample_count,
174
- sort_by=sort_by,
175
- columns=columns,
176
- performance=performance,
177
- )
178
- return {
179
- "type": "metadata",
180
- "metadata": meta.get("metadata"),
181
- "next_uri": meta.get("next_uri"),
182
- "next_offset": meta.get("next_offset"),
183
- }
184
- try:
185
- result = self.query_service.execute(
152
+ meta = self.query_service.fetch_metadata(
186
153
  query=q_use,
187
154
  parameters=parameters,
188
- refresh=refresh,
189
155
  max_age=max_age,
190
- poll=True,
191
- timeout_seconds=timeout_seconds,
192
156
  limit=limit,
193
157
  offset=offset,
194
158
  sample_count=sample_count,
195
159
  sort_by=sort_by,
196
160
  columns=columns,
197
161
  extras=extras,
198
- include_execution=True,
199
162
  performance=performance,
200
- return_raw=format == "raw",
201
- )
202
- except TypeError:
203
- # Back-compat: older services without 'extras'
204
- result = self.query_service.execute(
205
- query=q_use,
206
- parameters=parameters,
207
- refresh=refresh,
208
- max_age=max_age,
209
- poll=True,
210
- timeout_seconds=timeout_seconds,
211
- limit=limit,
212
- offset=offset,
213
- sample_count=sample_count,
214
- sort_by=sort_by,
215
- columns=columns,
216
- include_execution=True,
217
- performance=performance,
218
- return_raw=format == "raw",
219
163
  )
164
+ return {
165
+ "type": "metadata",
166
+ "metadata": meta.get("metadata"),
167
+ "next_uri": meta.get("next_uri"),
168
+ "next_offset": meta.get("next_offset"),
169
+ }
170
+ result = self.query_service.execute(
171
+ query=q_use,
172
+ parameters=parameters,
173
+ refresh=refresh,
174
+ max_age=max_age,
175
+ poll=True,
176
+ timeout_seconds=timeout_seconds,
177
+ limit=limit,
178
+ offset=offset,
179
+ sample_count=sample_count,
180
+ sort_by=sort_by,
181
+ columns=columns,
182
+ extras=extras,
183
+ include_execution=True,
184
+ performance=performance,
185
+ return_raw=format == "raw",
186
+ )
220
187
 
221
188
  duration_ms = int((time.time() - t0) * 1000)
222
189
  execution = result.get("execution", {})
@@ -340,7 +307,7 @@ class ExecuteQueryTool(MCPTool):
340
307
  # Add debugging information for raw SQL failures
341
308
  if q_type == "raw_sql" and "could not determine execution" in str(exc):
342
309
  context.update({
343
- "debug_info": "Raw SQL execution failed - this may be related to FastMCP async/concurrency handling",
310
+ "debug_info": "Raw SQL execution failed - check template query configuration and API key",
344
311
  "template_query_id": template_id_value,
345
312
  "environment_vars": {
346
313
  "SPICE_RAW_SQL_QUERY_ID": os.getenv("SPICE_RAW_SQL_QUERY_ID"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spice-mcp
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: MCP server for Dune Analytics data access
5
5
  Author-email: Evan-Kim2028 <ekcopersonal@gmail.com>
6
6
  License-File: LICENSE
@@ -21,7 +21,12 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # spice-mcp
23
23
 
24
- spice-mcp is an MCP server for [Dune](https://dune.com/) Analytics. It wraps a curated subset of the original Spice client inside a clean architecture (`core` models/ports → `adapters.dune` → service layer → FastMCP tools) and adds agent-friendly workflows for discovery and Sui package exploration. Results are Polars-first in Python and compact, token-efficient in MCP responses.
24
+ spice-mcp is an MCP server for [Dune](https://dune.com/) Analytics. It wraps a curated subset of the original Spice client inside a clean architecture (`core` models/ports → `adapters.dune` → service layer → FastMCP tools) and adds agent-friendly workflows for discovery. Results are Polars-first in Python and compact, token-efficient in MCP responses.
25
+
26
+ [![PyPI version](https://img.shields.io/pypi/v/spice-mcp.svg)](https://pypi.org/project/spice-mcp/)
27
+ <a href="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp">
28
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp/badge" alt="Spice MCP server" />
29
+ </a>
25
30
 
26
31
  Requirements: Python 3.13+
27
32
 
@@ -31,16 +36,17 @@ This project uses FastMCP for typed, decorator-registered tools and resources.
31
36
  - Polars LazyFrame-first pipeline: results stay lazy until explicitly materialized
32
37
  - Ports/adapters layering for maintainable integrations ([docs/architecture.md](docs/architecture.md))
33
38
  - Discovery utilities (find schemas/tables, describe columns)
34
- - Sui package workflows (events/transactions/objects) with safe defaults
35
39
  - JSONL query history + SQL artifacts (SHA-256) for reproducibility
36
- - Rich MCP surface: query info/run, discovery, health, Sui, and Dune admin (create/update/fork)
40
+ - Rich MCP surface: query info/run, discovery, health, and Dune admin (create/update/fork)
37
41
 
38
42
  ## What is Dune?
39
43
  [Dune](https://dune.com/) is a crypto data platform providing curated blockchain datasets and a public API to run and fetch query results. See the [Dune Docs](https://dune.com/docs) and [Dune API](https://dune.com/docs/api/) for full details.
40
44
 
41
45
  ## Quick Start
42
46
  - Export `DUNE_API_KEY` in your shell (the server can also load a local `.env`; set `SPICE_MCP_SKIP_DOTENV=1` to skip during tests).
43
- - Install dependencies (`uv sync` or `pip install -e .`).
47
+ - Install from PyPI: `uv pip install spice-mcp`
48
+ - Or install from source:
49
+ - `uv sync` then `uv pip install -e .`
44
50
  - Start the FastMCP stdio server:
45
51
  - `python -m spice_mcp.mcp.server --env PYTHONPATH=$(pwd)/src`
46
52
  - or install the console script via `uv tool install .` and run `spice-mcp`.
@@ -51,9 +57,12 @@ To use spice-mcp with Cursor IDE:
51
57
 
52
58
  1. **Install the MCP Server**:
53
59
  ```bash
54
- # Install dependencies and package
60
+ # Install from PyPI (recommended)
61
+ uv pip install spice-mcp
62
+
63
+ # Or install from source
55
64
  uv sync
56
- pip install -e .
65
+ uv pip install -e .
57
66
 
58
67
  # Or install via uv tool (creates console script)
59
68
  uv tool install .
@@ -97,7 +106,6 @@ To use spice-mcp with Cursor IDE:
97
106
  - `dune_query`: Run Dune queries by ID, URL, or raw SQL
98
107
  - `dune_find_tables`: Search schemas and list tables
99
108
  - `dune_describe_table`: Get column metadata
100
- - `sui_package_overview`: Analyze Sui packages
101
109
  - `dune_health_check`: Verify API connection
102
110
 
103
111
  **Tip**: Create a `.env` file in your project root with `DUNE_API_KEY=your-key-here` for easier configuration.
@@ -121,9 +129,6 @@ All tools expose typed parameters, titles, and tags; failures return a consisten
121
129
  - `dune_describe_table` (Describe Table, tags: dune, schema)
122
130
  - Column metadata for `schema.table` (Dune types + Polars inferred dtypes when available).
123
131
 
124
- - `sui_package_overview` (Sui Package Overview, tag: sui)
125
- - Compact Sui activity overview for `packages[]` with `hours` (default 72) and `timeout_seconds`.
126
-
127
132
  - Dune Admin tools (tags: dune, admin)
128
133
  - `dune_query_create(name, query_sql, description?, tags?, parameters?)`
129
134
  - `dune_query_update(query_id, name?, query_sql?, description?, tags?, parameters?)`
@@ -178,7 +183,7 @@ Core Tools (with parameters)
178
183
  - See [docs/index.md](docs/index.md) for full documentation:
179
184
  - Dune API structure and capabilities: [docs/dune_api.md](docs/dune_api.md)
180
185
  - Discovery patterns and examples: [docs/discovery.md](docs/discovery.md)
181
- - Sui package workflows: [docs/sui_packages.md](docs/sui_packages.md)
186
+
182
187
  - Tool reference and schemas: [docs/tools.md](docs/tools.md)
183
188
  - Codex CLI + tooling integration: [docs/codex_cli.md](docs/codex_cli.md), [docs/codex_cli_tools.md](docs/codex_cli_tools.md)
184
189
  - Architecture overview: [docs/architecture.md](docs/architecture.md)
@@ -190,4 +195,4 @@ Notes
190
195
  - Ports and models live in `src/spice_mcp/core`; services consume ports and are exercised by FastMCP tools.
191
196
  - Query history and SQL artefacts are always-on (see `src/spice_mcp/logging/query_history.py`).
192
197
  - To bypass dot-env loading during tests/CI, export `SPICE_MCP_SKIP_DOTENV=1`.
193
- - LazyFrames everywhere: eager `.collect()` or `pl.DataFrame` usage outside dedicated helpers is blocked by `tests/style/test_polars_lazy.py`; materialization helpers live in `src/spice_mcp/polars_utils.py`.
198
+ - LazyFrames everywhere: eager `.collect()` or `pl.DataFrame` usage outside dedicated helpers is blocked by `tests/style/test_polars_lazy.py`; materialization helpers live in `src/spice_mcp/polars_utils.py`.
@@ -1,39 +1,39 @@
1
1
  spice_mcp/__init__.py,sha256=SpDcj8yU1fpT2YZCqal1R3KLZpBty3w--6bkmrmWU6o,55
2
- spice_mcp/config.py,sha256=edQvaksAPhOcOSvT-_KUYKvnWyGAwxbSFMO7dvfsGU0,2712
2
+ spice_mcp/config.py,sha256=QX-c3veCFEoKjXIHsEX1_Kt4kjrd7tmrdTiOLFS5pSw,2766
3
3
  spice_mcp/polars_utils.py,sha256=hCw3M_iZhTFdBnfmcx2SN8J8jpoucfJnpX_ZrXz6JwY,442
4
4
  spice_mcp/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
5
  spice_mcp/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  spice_mcp/adapters/http_client.py,sha256=CYgSKAsx-5c-uxaNIBCBTgQdaoBe5J3dJvnw8iqda34,5033
7
- spice_mcp/adapters/dune/__init__.py,sha256=8HRHPxkz0y2cyHSs4oCkHq_LQaUW5MBevbeNxM9RZvo,372
7
+ spice_mcp/adapters/dune/__init__.py,sha256=nspEuDpVOktAxm8B066s-d0LwouCYGpvNEexi0mRMN8,386
8
8
  spice_mcp/adapters/dune/admin.py,sha256=yxOueVz-rmgC-ZFbT06k59G24yRgYjiEkZlall5hXNQ,3157
9
- spice_mcp/adapters/dune/cache.py,sha256=7UmmxpBNeSGPH3KYBBSPHlVXCpiZUi4-X300U2FsSBs,5252
9
+ spice_mcp/adapters/dune/cache.py,sha256=7ykT58WN1yHGIN2uV3t7fWOqGb1VJdCvf3I-xZwsv74,4304
10
10
  spice_mcp/adapters/dune/client.py,sha256=JPrQZ_dtaPcGf6lYUguDxGweOtxG-8qMqOiXuhWL9QA,9122
11
- spice_mcp/adapters/dune/extract.py,sha256=frIFRUNJ99UlP26N9F-p1psX-e_TVq9vJTQxh0MBCr8,48510
11
+ spice_mcp/adapters/dune/extract.py,sha256=3jrXqlak4Kpl11y7C3L1AhFv09lQ-2pBqmrh-0Mwlm0,28836
12
12
  spice_mcp/adapters/dune/helpers.py,sha256=BgDKr_g-UqmU2hoMb0ejQZHta_NbKwR1eDJp33sJYNk,227
13
13
  spice_mcp/adapters/dune/transport.py,sha256=eRP-jPY2ZXxvTX9HSjIFqFUlbIzXspgH95jBFoTlpaQ,1436
14
14
  spice_mcp/adapters/dune/types.py,sha256=57TMX07u-Gq4BYwRAuZV0xI81nVXgtpp7KBID9YbKyQ,1195
15
15
  spice_mcp/adapters/dune/typing_utils.py,sha256=EpWneGDn-eQdo6lkLuESR09KXkDj9OqGz8bEF3JaFkM,574
16
16
  spice_mcp/adapters/dune/urls.py,sha256=bcuPERkFQduRTT2BrgzVhoFrMn-Lkvw9NmktcBZYEig,3902
17
+ spice_mcp/adapters/spellbook/__init__.py,sha256=D2cdVtSUbmAJdbPRvAyKxYS4-wUQ3unXyX4ZFYxenuk,150
18
+ spice_mcp/adapters/spellbook/explorer.py,sha256=Q3UfEGlALizCDeeW_ZZVRRedzwMXiknmxwSkeOnxxgc,11515
17
19
  spice_mcp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
20
  spice_mcp/core/errors.py,sha256=jlfTuyRaAaA_oU07KUk-1pDAAa43KG0BbZc5CINXtoE,3256
19
- spice_mcp/core/models.py,sha256=2AgAPcaDPEXLdka1E6-0cXN_QNsN2rnDoBnDTDDsO6I,2121
20
- spice_mcp/core/ports.py,sha256=-wtWjpYSjL-qugUtsm0MTngtJZWzr7QNDbAUCj4BTS0,2028
21
+ spice_mcp/core/models.py,sha256=i0C_-UE16OWyyZo_liooEJeYvbChE5lpK80aN2OF4lk,1795
22
+ spice_mcp/core/ports.py,sha256=nEdeA3UH7v0kB_hbguMrpDljb9EhSxUAO0SdhjpoijQ,1618
21
23
  spice_mcp/logging/query_history.py,sha256=doE9lod64uzJxlA2XzHH2-VAmC6WstYAkQ0taEAxiIM,4315
22
24
  spice_mcp/mcp/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
23
- spice_mcp/mcp/server.py,sha256=RtvK0u9Q3dp3JMALrVFL43Li1lKdaQwLvtZuoo0pkbY,18082
25
+ spice_mcp/mcp/server.py,sha256=NNTjn5f_OLNd8zSnNsx73WhHzNGW_SBJoCrdzkgOGP4,26275
24
26
  spice_mcp/mcp/tools/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
25
- spice_mcp/mcp/tools/base.py,sha256=Xw8k5WAXotCdZRd-cVJIFPH87JH8DH0sh1zKu-KmW3w,1047
26
- spice_mcp/mcp/tools/execute_query.py,sha256=y2HU3Cv6T7QBXBDMea0TlXcf8_ERkq2sJeaexR6kBXE,17556
27
- spice_mcp/mcp/tools/sui_package_overview.py,sha256=a1CcpfBzEFzScjXgxLu9urxdtSbXjF98ugWPOX167rc,1736
27
+ spice_mcp/mcp/tools/base.py,sha256=zJkVxLgXR48iZcJeng8cZ2rXvbyicagoGlMN7BK7Img,1041
28
+ spice_mcp/mcp/tools/execute_query.py,sha256=CucjxoBT22VUS-QJV1JrDjoywu2nd13fL3Lfs1qytUg,16093
28
29
  spice_mcp/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
30
  spice_mcp/observability/logging.py,sha256=ceJUEpKGpf5PAgPBmpB49zjqhdGCAESfLemFUhDSmI8,529
30
31
  spice_mcp/service_layer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
32
  spice_mcp/service_layer/discovery_service.py,sha256=202O0SzCZGQukd9kb2JYfarLygZHgiXlHqp_nTAdrWA,730
32
33
  spice_mcp/service_layer/query_admin_service.py,sha256=4q1NAAuTui7cm83Aq2rFDLIzKTHX17yzbSoSJyCmLbI,1356
33
34
  spice_mcp/service_layer/query_service.py,sha256=q0eAVW5I3sUxm29DgzPN_cH3rZEzmKwmdE3Xj4qP9lI,3878
34
- spice_mcp/service_layer/sui_service.py,sha256=G-LOsl-z-ldMxAYAetnG17IQlpVtLchGUjN6opU-PF0,4712
35
- spice_mcp-0.1.2.dist-info/METADATA,sha256=TB63rlJQOinwNKj3XvevK0OLLF2Ic0xYmeLU_kaiuas,9316
36
- spice_mcp-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- spice_mcp-0.1.2.dist-info/entry_points.txt,sha256=4XiXX13Vy-oiUJwlcO_82OltBaxFnEnkJ-76sZGm5os,56
38
- spice_mcp-0.1.2.dist-info/licenses/LICENSE,sha256=r0GNDnDY1RSkVQp7kEEf6MQU21OrNGJkxUHIsv6eyLk,1079
39
- spice_mcp-0.1.2.dist-info/RECORD,,
35
+ spice_mcp-0.1.3.dist-info/METADATA,sha256=a44b2EfhkPbQSS_PEoU1LF4Kkr2RfSD1gS6gGlEMmnQ,9343
36
+ spice_mcp-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ spice_mcp-0.1.3.dist-info/entry_points.txt,sha256=4XiXX13Vy-oiUJwlcO_82OltBaxFnEnkJ-76sZGm5os,56
38
+ spice_mcp-0.1.3.dist-info/licenses/LICENSE,sha256=r0GNDnDY1RSkVQp7kEEf6MQU21OrNGJkxUHIsv6eyLk,1079
39
+ spice_mcp-0.1.3.dist-info/RECORD,,
@@ -1,56 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from ...core.errors import error_response
6
- from ...service_layer.sui_service import SuiService
7
- from .base import MCPTool
8
-
9
-
10
- class SuiPackageOverviewTool(MCPTool):
11
- """Overview of Sui packages: events, transactions, objects (small preview)."""
12
-
13
- def __init__(self, sui_service: SuiService):
14
- self.sui_service = sui_service
15
-
16
- @property
17
- def name(self) -> str:
18
- return "sui_package_overview"
19
-
20
- @property
21
- def description(self) -> str:
22
- return (
23
- "Return a compact overview (counts + previews) of Sui package activity "
24
- "over a time window, with polling timeouts."
25
- )
26
-
27
- def get_parameter_schema(self) -> dict[str, Any]:
28
- return {
29
- "type": "object",
30
- "properties": {
31
- "packages": {"type": "array", "items": {"type": "string"}},
32
- "hours": {"type": "integer", "default": 72},
33
- "timeout_seconds": {"type": "number", "default": 30},
34
- },
35
- "required": ["packages"],
36
- "additionalProperties": False,
37
- }
38
-
39
- async def execute(
40
- self, *, packages: list[str], hours: int = 72, timeout_seconds: float | None = 30
41
- ) -> dict[str, Any]:
42
- try:
43
- return self.sui_service.package_overview(
44
- packages,
45
- hours=hours,
46
- timeout_seconds=timeout_seconds,
47
- )
48
- except Exception as exc:
49
- return error_response(
50
- exc,
51
- context={
52
- "tool": "sui_package_overview",
53
- "packages": packages,
54
- "hours": hours,
55
- },
56
- )
@@ -1,131 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import time
4
-
5
- from .query_service import QueryService
6
-
7
-
8
- class SuiService:
9
- """Opinionated helpers for Sui package exploration."""
10
-
11
- def __init__(self, query_service: QueryService):
12
- self.query_service = query_service
13
-
14
- def events_preview(
15
- self, packages: list[str], *, hours: int = 72, limit: int = 50, performance: str = "large"
16
- ) -> dict[str, object]:
17
- now_ms = int(time.time() * 1000)
18
- start_ms = now_ms - hours * 3600 * 1000
19
-
20
- norms = _normalize_packages(packages)
21
- if norms:
22
- preds = [f"lower(event_type) LIKE '{pkg}::%'" for pkg in norms]
23
- where = " OR ".join(preds)
24
- sql = (
25
- "SELECT timestamp_ms, package, module, event_type FROM sui.events "
26
- f"WHERE ({where}) AND timestamp_ms BETWEEN {start_ms} AND {now_ms} "
27
- "ORDER BY timestamp_ms DESC "
28
- f"LIMIT {limit}"
29
- )
30
- else:
31
- sql = (
32
- "SELECT timestamp_ms, package, module, event_type FROM sui.events "
33
- f"WHERE timestamp_ms BETWEEN {start_ms} AND {now_ms} "
34
- "ORDER BY timestamp_ms DESC LIMIT {limit}"
35
- ).format(limit=limit)
36
-
37
- result = self.query_service.execute(
38
- sql,
39
- performance=performance,
40
- timeout_seconds=60,
41
- limit=limit,
42
- )
43
- return {
44
- "rowcount": result["rowcount"],
45
- "columns": result["columns"],
46
- "data_preview": result["data_preview"],
47
- }
48
-
49
- def package_overview(
50
- self,
51
- packages: list[str],
52
- *,
53
- hours: int = 72,
54
- timeout_seconds: float | None = 30,
55
- performance: str = "large",
56
- ) -> dict[str, object]:
57
- now_ms = int(time.time() * 1000)
58
- start_ms = now_ms - hours * 3600 * 1000
59
- norms = _normalize_packages(packages)
60
- in_clause = ",".join(f"'{pkg}'" for pkg in norms)
61
-
62
- out: dict[str, object] = {"ok": True}
63
-
64
- def _run(sql: str, limit: int) -> dict[str, object]:
65
- return self.query_service.execute(
66
- sql,
67
- performance=performance,
68
- timeout_seconds=timeout_seconds,
69
- limit=limit,
70
- )
71
-
72
- # Events
73
- try:
74
- sql_ev = (
75
- "SELECT timestamp_ms, package, module, event_type, event_json FROM sui.events "
76
- f"WHERE lower(package) IN ({in_clause}) AND timestamp_ms BETWEEN {start_ms} AND {now_ms} "
77
- "ORDER BY timestamp_ms DESC LIMIT 200"
78
- )
79
- ev = _run(sql_ev, 200)
80
- out["events_preview"] = ev.get("data_preview")
81
- out["events_count"] = ev.get("rowcount")
82
- except TimeoutError:
83
- out["events_timeout"] = True
84
- except Exception as exc:
85
- out["events_error"] = str(exc)
86
-
87
- # Transactions
88
- try:
89
- sql_tx = (
90
- "WITH txs AS (SELECT DISTINCT transaction_digest FROM sui.events "
91
- f"WHERE lower(package) IN ({in_clause}) AND timestamp_ms BETWEEN {start_ms} AND {now_ms}) "
92
- "SELECT t.* FROM sui.transactions t "
93
- "JOIN txs ON t.transaction_digest = txs.transaction_digest "
94
- "ORDER BY t.timestamp_ms DESC LIMIT 200"
95
- )
96
- tx = _run(sql_tx, 200)
97
- out["transactions_preview"] = tx.get("data_preview")
98
- out["transactions_count"] = tx.get("rowcount")
99
- except TimeoutError:
100
- out["transactions_timeout"] = True
101
- except Exception as exc:
102
- out["transactions_error"] = str(exc)
103
-
104
- # Objects
105
- try:
106
- sql_ob = (
107
- "WITH txs AS (SELECT DISTINCT transaction_digest FROM sui.events "
108
- f"WHERE lower(package) IN ({in_clause}) AND timestamp_ms BETWEEN {start_ms} AND {now_ms}) "
109
- "SELECT o.* FROM sui.objects o "
110
- "WHERE o.previous_transaction IN (SELECT transaction_digest FROM txs) "
111
- "ORDER BY o.timestamp_ms DESC LIMIT 200"
112
- )
113
- ob = _run(sql_ob, 200)
114
- out["objects_preview"] = ob.get("data_preview")
115
- out["objects_count"] = ob.get("rowcount")
116
- except TimeoutError:
117
- out["objects_timeout"] = True
118
- except Exception as exc:
119
- out["objects_error"] = str(exc)
120
-
121
- return out
122
-
123
-
124
- def _normalize_packages(packages: list[str]) -> list[str]:
125
- norms: list[str] = []
126
- for p in packages:
127
- value = p.lower()
128
- if not value.startswith("0x"):
129
- value = "0x" + value
130
- norms.append(value)
131
- return norms