kricket-client 0.1.0__tar.gz

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.
@@ -0,0 +1,52 @@
1
+ # Rust
2
+ /core/target/
3
+ **/*.rs.bk
4
+
5
+ # Go
6
+ /services/*/vendor/
7
+ # Build artifacts — each service builds a binary matching its directory name
8
+ /services/gateway/gateway
9
+ /services/pulse-api/pulse-api
10
+ /services/mantis-api/mantis-api
11
+ /services/firefly-api/firefly-api
12
+ /services/chirps/chirps
13
+ /services/status/status
14
+ /services/hub-sidecar/cricket-hub-sidecar
15
+ # One-off CLI binaries — rebuild via `go build -o services/mint-backfill ./services/gateway/cmd/mint-backfill`
16
+ /services/mint-backfill
17
+
18
+ # TypeScript / Node
19
+ node_modules/
20
+ dist/
21
+ *.tsbuildinfo
22
+
23
+ # Environment
24
+ .env
25
+ .env.local
26
+ .env.production
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+
34
+ # Playwright MCP artifacts
35
+ .playwright-mcp/
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Docker
42
+ docker-compose.override.yml
43
+
44
+ # Python
45
+ __pycache__/
46
+ *.pyc
47
+ *.egg-info/
48
+
49
+ # Secrets
50
+ *.pem
51
+ *.key
52
+ cricket-admin/.env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VYLTH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: kricket-client
3
+ Version: 0.1.0
4
+ Summary: Python client for Kricket Protocol: DeFi intelligence and execution suite
5
+ Project-URL: Homepage, https://github.com/VYLTH/kricket
6
+ Project-URL: Repository, https://github.com/VYLTH/kricket
7
+ Author: VYLTH
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: api-client,defi,kricket,solana,web3
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # kricket-client
25
+
26
+ Async Python client for the [Kricket Protocol](https://cricket.vylth.com) — DeFi intelligence APIs for rug detection, wallet profiling, price feeds, smart contract analysis, and notifications.
27
+
28
+ Requires Python 3.10+. All methods are async.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install kricket-client
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ```python
39
+ import asyncio
40
+ from kricket_client import KricketClient
41
+
42
+ async def main():
43
+ async with KricketClient(api_key="your-api-key") as kricket:
44
+ # Scan a token for rug-pull risk
45
+ result = await kricket.mantis.scan("TOKEN_ADDRESS")
46
+ print(result.risk_score.rating) # 'low' | 'moderate' | 'high' | 'critical'
47
+ print(result.risk_score.score) # 0–100
48
+
49
+ # Get a wallet intelligence profile
50
+ wallet = await kricket.firefly.get_wallet("WALLET_ADDRESS")
51
+ print(wallet.score, wallet.style, wallet.win_rate)
52
+
53
+ asyncio.run(main())
54
+ ```
55
+
56
+ ## Clients
57
+
58
+ | Client | Description | Key Methods |
59
+ |--------|-------------|-------------|
60
+ | `kricket.mantis` | Rug-pull detection & token risk scoring | `scan(token)`, `get_score(token)`, `add_to_watchlist(tokens)`, `get_watchlist()` |
61
+ | `kricket.firefly` | Wallet intelligence & smart money signals | `get_wallet(address)`, `get_signals()`, `get_leaderboard()`, `track(address)` |
62
+ | `kricket.pulse` | Unified price feeds (CEX + DEX) | `get_price(symbol)`, `get_spread(symbol)` |
63
+ | `kricket.chirps` | Notification channel management | `list_channels()`, `create_channel(config)`, `delete_channel(id)` |
64
+ | `kricket.debugger` | Smart contract vulnerability analysis | `analyze(source, language)` |
65
+
66
+ ## Configuration
67
+
68
+ ```python
69
+ from kricket_client import KricketClient, KricketConfig
70
+
71
+ config = KricketConfig(
72
+ api_key="your-api-key",
73
+ base_url="https://api-cricket.vylth.com/api/kricket", # optional override
74
+ timeout=30.0, # seconds, default 30
75
+ )
76
+ kricket = KricketClient(config)
77
+ ```
78
+
79
+ ## Error Handling
80
+
81
+ ```python
82
+ from kricket_client import KricketError
83
+
84
+ try:
85
+ result = await kricket.mantis.scan(token)
86
+ except KricketError as e:
87
+ print(e.code, e.message, e.status)
88
+ ```
89
+
90
+ ## Full Docs
91
+
92
+ [cricket.vylth.com/docs](https://cricket.vylth.com/docs)
@@ -0,0 +1,69 @@
1
+ # kricket-client
2
+
3
+ Async Python client for the [Kricket Protocol](https://cricket.vylth.com) — DeFi intelligence APIs for rug detection, wallet profiling, price feeds, smart contract analysis, and notifications.
4
+
5
+ Requires Python 3.10+. All methods are async.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install kricket-client
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ import asyncio
17
+ from kricket_client import KricketClient
18
+
19
+ async def main():
20
+ async with KricketClient(api_key="your-api-key") as kricket:
21
+ # Scan a token for rug-pull risk
22
+ result = await kricket.mantis.scan("TOKEN_ADDRESS")
23
+ print(result.risk_score.rating) # 'low' | 'moderate' | 'high' | 'critical'
24
+ print(result.risk_score.score) # 0–100
25
+
26
+ # Get a wallet intelligence profile
27
+ wallet = await kricket.firefly.get_wallet("WALLET_ADDRESS")
28
+ print(wallet.score, wallet.style, wallet.win_rate)
29
+
30
+ asyncio.run(main())
31
+ ```
32
+
33
+ ## Clients
34
+
35
+ | Client | Description | Key Methods |
36
+ |--------|-------------|-------------|
37
+ | `kricket.mantis` | Rug-pull detection & token risk scoring | `scan(token)`, `get_score(token)`, `add_to_watchlist(tokens)`, `get_watchlist()` |
38
+ | `kricket.firefly` | Wallet intelligence & smart money signals | `get_wallet(address)`, `get_signals()`, `get_leaderboard()`, `track(address)` |
39
+ | `kricket.pulse` | Unified price feeds (CEX + DEX) | `get_price(symbol)`, `get_spread(symbol)` |
40
+ | `kricket.chirps` | Notification channel management | `list_channels()`, `create_channel(config)`, `delete_channel(id)` |
41
+ | `kricket.debugger` | Smart contract vulnerability analysis | `analyze(source, language)` |
42
+
43
+ ## Configuration
44
+
45
+ ```python
46
+ from kricket_client import KricketClient, KricketConfig
47
+
48
+ config = KricketConfig(
49
+ api_key="your-api-key",
50
+ base_url="https://api-cricket.vylth.com/api/kricket", # optional override
51
+ timeout=30.0, # seconds, default 30
52
+ )
53
+ kricket = KricketClient(config)
54
+ ```
55
+
56
+ ## Error Handling
57
+
58
+ ```python
59
+ from kricket_client import KricketError
60
+
61
+ try:
62
+ result = await kricket.mantis.scan(token)
63
+ except KricketError as e:
64
+ print(e.code, e.message, e.status)
65
+ ```
66
+
67
+ ## Full Docs
68
+
69
+ [cricket.vylth.com/docs](https://cricket.vylth.com/docs)
@@ -0,0 +1,8 @@
1
+ """Kricket Protocol Python Client: DeFi intelligence and execution suite."""
2
+
3
+ from kricket_client.client import KricketClient, KricketConfig
4
+ from kricket_client.types import * # noqa: F401,F403
5
+ from kricket_client.exceptions import KricketError
6
+
7
+ __all__ = ["KricketClient", "KricketConfig", "KricketError"]
8
+ __version__ = "0.1.0"
@@ -0,0 +1,36 @@
1
+ """Chirps API client: notification channel management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from kricket_client.http import HttpClient
8
+ from kricket_client.types import ChirpChannel, ChirpRecord
9
+
10
+
11
+ class ChirpsClient:
12
+ def __init__(self, http: HttpClient):
13
+ self._http = http
14
+
15
+ async def create_channel(self, channel: dict) -> ChirpChannel:
16
+ """Create a notification channel."""
17
+ data = await self._http.post("/chirps/channels", json=channel)
18
+ return ChirpChannel(**data)
19
+
20
+ async def list_channels(self) -> list[ChirpChannel]:
21
+ """List all notification channels."""
22
+ data = await self._http.get("/chirps/channels")
23
+ return [ChirpChannel(**c) for c in data]
24
+
25
+ async def delete_channel(self, channel_id: str) -> None:
26
+ """Delete a notification channel."""
27
+ await self._http.delete(f"/chirps/channels/{quote(channel_id)}")
28
+
29
+ async def test_channel(self, channel_id: str) -> dict:
30
+ """Send a test chirp to verify channel configuration."""
31
+ return await self._http.post(f"/chirps/test/{quote(channel_id)}")
32
+
33
+ async def get_history(self) -> list[ChirpRecord]:
34
+ """Get notification delivery history."""
35
+ data = await self._http.get("/chirps/history")
36
+ return [ChirpRecord(**r) for r in data]
@@ -0,0 +1,164 @@
1
+ """Main Kricket client: unified entry point for all API modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import AsyncIterator, Optional
8
+ from urllib.parse import quote
9
+
10
+ import httpx
11
+
12
+ from kricket_client.exceptions import KricketError
13
+ from kricket_client.http import HttpClient
14
+ from kricket_client.types import CheckResponse, WatchEvent
15
+ from kricket_client.pulse import PulseClient
16
+ from kricket_client.mantis import MantisClient
17
+ from kricket_client.firefly import FireflyClient
18
+ from kricket_client.chirps import ChirpsClient
19
+ from kricket_client.debugger import DebuggerClient
20
+
21
+
22
+ @dataclass
23
+ class KricketConfig:
24
+ """Configuration for the Kricket client."""
25
+
26
+ base_url: str = "https://api-cricket.vylth.com/api/cricket"
27
+ api_key: str = ""
28
+ timeout: float = 30.0
29
+ # Intel REST base for the one-call check()/guard()/watch() surface.
30
+ intel_url: str = "https://api-cricket.vylth.com"
31
+
32
+
33
+ class KricketClient:
34
+ """Unified client for Kricket Protocol APIs."""
35
+
36
+ def __init__(self, config: KricketConfig | None = None, **kwargs):
37
+ cfg = config or KricketConfig(**kwargs)
38
+ self._http = HttpClient(cfg.base_url, cfg.api_key, cfg.timeout)
39
+ self._intel_url = cfg.intel_url.rstrip("/")
40
+ self._api_key = cfg.api_key
41
+ self._timeout = cfg.timeout
42
+ self._intel: httpx.AsyncClient | None = None
43
+ self._pulse: PulseClient | None = None
44
+ self._mantis: MantisClient | None = None
45
+ self._firefly: FireflyClient | None = None
46
+ self._chirps: ChirpsClient | None = None
47
+ self._debugger: DebuggerClient | None = None
48
+
49
+ def _intel_client(self) -> httpx.AsyncClient:
50
+ if self._intel is None:
51
+ self._intel = httpx.AsyncClient(
52
+ base_url=self._intel_url,
53
+ headers={"X-API-Key": self._api_key},
54
+ timeout=self._timeout,
55
+ )
56
+ return self._intel
57
+
58
+ async def check(self, chain: str, address: str) -> CheckResponse:
59
+ """One-call safety check for an address. The drop-in: is it safe to touch?
60
+
61
+ Returns the raw verdict; raises :class:`KricketError` with the server
62
+ reason on a non-ok response.
63
+ """
64
+ resp = await self._intel_client().get(
65
+ f"/v1/check/{quote(chain, safe='')}/{quote(address, safe='')}"
66
+ )
67
+ if resp.status_code != 200:
68
+ reason = f"HTTP {resp.status_code}"
69
+ try:
70
+ body = resp.json()
71
+ if isinstance(body, dict) and body.get("reason"):
72
+ reason = body["reason"]
73
+ except Exception:
74
+ pass # non-JSON error body
75
+ raise KricketError("CHECK_FAILED", reason, resp.status_code)
76
+ return CheckResponse(**resp.json())
77
+
78
+ async def guard(
79
+ self, chain: str, address: str, fail_open: bool = False
80
+ ) -> Optional[CheckResponse]:
81
+ """Pre-sign guard — one line in a signing hook.
82
+
83
+ Returns the verdict when cleared; **raises** ``KricketError('BLOCKED')``
84
+ when not, so the transaction is never signed. Fail-closed by default: if
85
+ the engine is unreachable the error propagates (caller does not sign).
86
+ Pass ``fail_open=True`` to instead return ``None`` on an engine error —
87
+ an explicit danger verdict still raises.
88
+ """
89
+ try:
90
+ verdict = await self.check(chain, address)
91
+ except KricketError:
92
+ if fail_open:
93
+ return None # engine unreachable — caller chose to allow
94
+ raise # fail closed: cannot verify -> do not sign
95
+ if not verdict.cleared:
96
+ raise KricketError("BLOCKED", verdict.reason, 403) # explicit danger always blocks
97
+ return verdict
98
+
99
+ async def watch(
100
+ self, addresses: Optional[list[str]] = None
101
+ ) -> AsyncIterator[WatchEvent]:
102
+ """Subscribe to live revocations — yields a :class:`WatchEvent` the instant
103
+ a watched address turns dangerous. Pass ``None`` to watch everything; break
104
+ out of the loop to unsubscribe.
105
+ """
106
+ params = {"addresses": ",".join(addresses)} if addresses else None
107
+ async with self._intel_client().stream(
108
+ "GET", "/v1/watch", params=params, headers={"Accept": "text/event-stream"}
109
+ ) as resp:
110
+ if resp.status_code != 200:
111
+ raise KricketError("WATCH_FAILED", f"HTTP {resp.status_code}", resp.status_code)
112
+ buf = ""
113
+ async for chunk in resp.aiter_text():
114
+ buf += chunk
115
+ while "\n\n" in buf:
116
+ block, buf = buf.split("\n\n", 1)
117
+ for line in block.split("\n"):
118
+ if line.startswith("data:"):
119
+ try:
120
+ yield WatchEvent(**json.loads(line[5:].strip()))
121
+ except Exception:
122
+ pass # skip malformed event
123
+
124
+ @property
125
+ def pulse(self) -> PulseClient:
126
+ if self._pulse is None:
127
+ self._pulse = PulseClient(self._http)
128
+ return self._pulse
129
+
130
+ @property
131
+ def mantis(self) -> MantisClient:
132
+ if self._mantis is None:
133
+ self._mantis = MantisClient(self._http)
134
+ return self._mantis
135
+
136
+ @property
137
+ def firefly(self) -> FireflyClient:
138
+ if self._firefly is None:
139
+ self._firefly = FireflyClient(self._http)
140
+ return self._firefly
141
+
142
+ @property
143
+ def chirps(self) -> ChirpsClient:
144
+ if self._chirps is None:
145
+ self._chirps = ChirpsClient(self._http)
146
+ return self._chirps
147
+
148
+ @property
149
+ def debugger(self) -> DebuggerClient:
150
+ if self._debugger is None:
151
+ self._debugger = DebuggerClient(self._http)
152
+ return self._debugger
153
+
154
+ async def close(self) -> None:
155
+ """Close the underlying HTTP clients."""
156
+ await self._http.close()
157
+ if self._intel is not None:
158
+ await self._intel.aclose()
159
+
160
+ async def __aenter__(self) -> KricketClient:
161
+ return self
162
+
163
+ async def __aexit__(self, *args) -> None:
164
+ await self.close()
@@ -0,0 +1,19 @@
1
+ """Debugger API client: smart contract vulnerability scanning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kricket_client.http import HttpClient
6
+ from kricket_client.types import AnalysisResult, Language
7
+
8
+
9
+ class DebuggerClient:
10
+ def __init__(self, http: HttpClient):
11
+ self._http = http
12
+
13
+ async def analyze(self, source: str, language: Language | str) -> AnalysisResult:
14
+ """Analyze source code for vulnerabilities and gas optimizations."""
15
+ data = await self._http.post(
16
+ "/debugger/analyze",
17
+ json={"source": source, "language": language.value if hasattr(language, "value") else str(language)},
18
+ )
19
+ return AnalysisResult(**data)
@@ -0,0 +1,11 @@
1
+ """Kricket API exceptions."""
2
+
3
+
4
+ class KricketError(Exception):
5
+ """Raised when the Kricket API returns an error."""
6
+
7
+ def __init__(self, code: str, message: str, status: int):
8
+ self.code = code
9
+ self.message = message
10
+ self.status = status
11
+ super().__init__(f"[{code}] {message} (HTTP {status})")
@@ -0,0 +1,32 @@
1
+ """Firefly API client: wallet intelligence and smart money signals."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from kricket_client.http import HttpClient
8
+ from kricket_client.types import WalletProfile, Signal
9
+
10
+
11
+ class FireflyClient:
12
+ def __init__(self, http: HttpClient):
13
+ self._http = http
14
+
15
+ async def get_wallet(self, address: str) -> WalletProfile:
16
+ """Get a wallet's intelligence profile."""
17
+ data = await self._http.get(f"/firefly/wallet/{quote(address)}")
18
+ return WalletProfile(**data)
19
+
20
+ async def get_signals(self) -> list[Signal]:
21
+ """Get active smart money signals."""
22
+ data = await self._http.get("/firefly/signals")
23
+ return [Signal(**s) for s in data]
24
+
25
+ async def get_leaderboard(self) -> list[WalletProfile]:
26
+ """Get the top wallets leaderboard."""
27
+ data = await self._http.get("/firefly/leaderboard")
28
+ return [WalletProfile(**w) for w in data]
29
+
30
+ async def track(self, address: str) -> dict:
31
+ """Start tracking a wallet."""
32
+ return await self._http.post("/firefly/track", json={"address": address})
@@ -0,0 +1,53 @@
1
+ """Base HTTP client for Kricket API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, TypeVar
6
+
7
+ import httpx
8
+
9
+ from kricket_client.exceptions import KricketError
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ class HttpClient:
15
+ """Async HTTP client with API key auth and error handling."""
16
+
17
+ def __init__(self, base_url: str, api_key: str, timeout: float = 30.0):
18
+ self._client = httpx.AsyncClient(
19
+ base_url=base_url.rstrip("/"),
20
+ headers={
21
+ "Content-Type": "application/json",
22
+ "X-API-Key": api_key,
23
+ },
24
+ timeout=timeout,
25
+ )
26
+
27
+ async def get(self, path: str) -> Any:
28
+ return await self._request("GET", path)
29
+
30
+ async def post(self, path: str, json: Any = None) -> Any:
31
+ return await self._request("POST", path, json=json)
32
+
33
+ async def delete(self, path: str) -> None:
34
+ resp = await self._client.delete(path)
35
+ if resp.status_code not in (200, 204):
36
+ raise KricketError("REQUEST_FAILED", f"HTTP {resp.status_code}", resp.status_code)
37
+
38
+ async def _request(self, method: str, path: str, json: Any = None) -> Any:
39
+ resp = await self._client.request(method, path, json=json)
40
+ data = resp.json()
41
+
42
+ if not data.get("success") or data.get("error"):
43
+ err = data.get("error", {})
44
+ raise KricketError(
45
+ code=err.get("code", "UNKNOWN_ERROR"),
46
+ message=err.get("message", "Unknown error"),
47
+ status=resp.status_code,
48
+ )
49
+
50
+ return data.get("data")
51
+
52
+ async def close(self) -> None:
53
+ await self._client.aclose()
@@ -0,0 +1,32 @@
1
+ """Mantis API client: rug-pull detection and token risk scoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from kricket_client.http import HttpClient
8
+ from kricket_client.types import ScanResponse, RiskScore, WatchlistEntry
9
+
10
+
11
+ class MantisClient:
12
+ def __init__(self, http: HttpClient):
13
+ self._http = http
14
+
15
+ async def scan(self, token: str) -> ScanResponse:
16
+ """Run a full security scan on a token."""
17
+ data = await self._http.get(f"/mantis/scan/{quote(token)}")
18
+ return ScanResponse(**data)
19
+
20
+ async def get_score(self, token: str) -> RiskScore:
21
+ """Get only the risk score for a token."""
22
+ data = await self._http.get(f"/mantis/score/{quote(token)}")
23
+ return RiskScore(**data)
24
+
25
+ async def add_to_watchlist(self, tokens: list[str]) -> dict:
26
+ """Add tokens to the watchlist."""
27
+ return await self._http.post("/mantis/watchlist", json={"tokens": tokens})
28
+
29
+ async def get_watchlist(self) -> list[WatchlistEntry]:
30
+ """Get all watched tokens."""
31
+ data = await self._http.get("/mantis/watchlist")
32
+ return [WatchlistEntry(**e) for e in data]
@@ -0,0 +1,28 @@
1
+ """Pulse API client: unified price feeds across CEX and DEX venues."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from urllib.parse import quote
6
+
7
+ from kricket_client.http import HttpClient
8
+ from kricket_client.types import PriceQuote, SpreadAnalysis, Venue
9
+
10
+
11
+ class PulseClient:
12
+ def __init__(self, http: HttpClient):
13
+ self._http = http
14
+
15
+ async def get_price(self, token: str) -> list[PriceQuote]:
16
+ """Get price quotes for a token across all venues."""
17
+ data = await self._http.get(f"/pulse/price/{quote(token)}")
18
+ return [PriceQuote(**q) for q in data]
19
+
20
+ async def get_spread(self, token: str) -> SpreadAnalysis:
21
+ """Get cross-venue spread analysis for a token."""
22
+ data = await self._http.get(f"/pulse/spread/{quote(token)}")
23
+ return SpreadAnalysis(**data)
24
+
25
+ async def get_venues(self) -> list[Venue]:
26
+ """List all supported venues."""
27
+ data = await self._http.get("/pulse/venues")
28
+ return [Venue(**v) for v in data]