nirium 0.2.0__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.
- nirium/__init__.py +6 -0
- nirium/client.py +201 -0
- nirium-0.2.0.dist-info/METADATA +103 -0
- nirium-0.2.0.dist-info/RECORD +6 -0
- nirium-0.2.0.dist-info/WHEEL +5 -0
- nirium-0.2.0.dist-info/top_level.txt +1 -0
nirium/__init__.py
ADDED
nirium/client.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════════════════════════
|
|
2
|
+
# Nirium Python SDK v0.2.0 — Official Quantitative Client
|
|
3
|
+
# Synced with backend API (real Horizon data, Soroban execution)
|
|
4
|
+
# ═══════════════════════════════════════════════════════════════
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import aiohttp # type: ignore
|
|
9
|
+
import websockets # type: ignore
|
|
10
|
+
from typing import Callable, Dict, Any, List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("nirium.client")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Agent:
|
|
16
|
+
"""
|
|
17
|
+
Nirium Agent — Full API + WebSocket client for the Nirium autonomous agent.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
agent = Agent(api_url="http://localhost:3001", api_key="nrm_your_key")
|
|
21
|
+
market = await agent.get_market()
|
|
22
|
+
print(f"XLM Price: ${market['xlmPrice']:.4f}")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, api_url: str = "http://localhost:3001", api_key: Optional[str] = None, token: Optional[str] = None):
|
|
26
|
+
self.api_url = api_url.rstrip('/')
|
|
27
|
+
self.ws_url = self.api_url.replace("http", "ws") + "/ws/signals"
|
|
28
|
+
self.api_key = api_key
|
|
29
|
+
self.token = token
|
|
30
|
+
|
|
31
|
+
api_key_local = self.api_key
|
|
32
|
+
token_local = self.token
|
|
33
|
+
|
|
34
|
+
self.headers: Dict[str, str] = {"Content-Type": "application/json"}
|
|
35
|
+
if api_key_local is not None:
|
|
36
|
+
self.headers["x-api-key"] = api_key_local
|
|
37
|
+
elif token_local is not None:
|
|
38
|
+
self.headers["Authorization"] = f"Bearer {token_local}"
|
|
39
|
+
|
|
40
|
+
self.callbacks: Dict[str, List[Callable]] = {"signal": [], "log": [], "connected": []}
|
|
41
|
+
|
|
42
|
+
# ─── Decorators ────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
def on(self, event_type: str):
|
|
45
|
+
"""Decorator to register event callbacks."""
|
|
46
|
+
def decorator(func: Callable):
|
|
47
|
+
if event_type not in self.callbacks:
|
|
48
|
+
self.callbacks[event_type] = []
|
|
49
|
+
self.callbacks[event_type].append(func)
|
|
50
|
+
return func
|
|
51
|
+
return decorator
|
|
52
|
+
|
|
53
|
+
async def _emit(self, event_type: str, data: Any):
|
|
54
|
+
for callback in self.callbacks.get(event_type, []):
|
|
55
|
+
if asyncio.iscoroutinefunction(callback):
|
|
56
|
+
await callback(data)
|
|
57
|
+
else:
|
|
58
|
+
callback(data)
|
|
59
|
+
|
|
60
|
+
# ─── HTTP Helpers ─────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
async def _get(self, path: str) -> Any:
|
|
63
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
64
|
+
async with session.get(f"{self.api_url}{path}") as resp:
|
|
65
|
+
resp.raise_for_status()
|
|
66
|
+
return await resp.json()
|
|
67
|
+
|
|
68
|
+
async def _post(self, path: str, payload: Optional[Dict[str, Any]] = None) -> Any:
|
|
69
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
70
|
+
async with session.post(f"{self.api_url}{path}", json=payload or {}) as resp:
|
|
71
|
+
resp.raise_for_status()
|
|
72
|
+
return await resp.json()
|
|
73
|
+
|
|
74
|
+
async def _delete(self, path: str) -> Any:
|
|
75
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
76
|
+
async with session.delete(f"{self.api_url}{path}") as resp:
|
|
77
|
+
resp.raise_for_status()
|
|
78
|
+
return await resp.json()
|
|
79
|
+
|
|
80
|
+
# ─── Health ───────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
async def ping(self) -> bool:
|
|
83
|
+
"""Check if the agent is reachable."""
|
|
84
|
+
try:
|
|
85
|
+
data = await self._get("/health")
|
|
86
|
+
return data.get("status") == "operational"
|
|
87
|
+
except Exception:
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
async def health(self) -> Dict[str, Any]:
|
|
91
|
+
"""Get detailed health info."""
|
|
92
|
+
return await self._get("/health")
|
|
93
|
+
|
|
94
|
+
async def system_health(self) -> Dict[str, Any]:
|
|
95
|
+
"""Get full system health (Horizon, Soroban, WebSocket, IPFS, LLM)."""
|
|
96
|
+
return await self._get("/api/system/health")
|
|
97
|
+
|
|
98
|
+
# ─── Market Data ─────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
async def get_market(self) -> Dict[str, Any]:
|
|
101
|
+
"""Fetch real market state from Horizon (XLM price, SDEX spread, fees, paths)."""
|
|
102
|
+
return await self._get("/api/market")
|
|
103
|
+
|
|
104
|
+
async def get_loop_status(self) -> Dict[str, Any]:
|
|
105
|
+
"""Get autonomous loop status."""
|
|
106
|
+
return await self._get("/api/loop/status")
|
|
107
|
+
|
|
108
|
+
async def start_loop(self, config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
109
|
+
"""Start the autonomous scanning loop."""
|
|
110
|
+
return await self._post("/api/loop/start", {"config": config or {}})
|
|
111
|
+
|
|
112
|
+
async def stop_loop(self) -> Dict[str, Any]:
|
|
113
|
+
"""Stop the autonomous scanning loop."""
|
|
114
|
+
return await self._post("/api/loop/stop")
|
|
115
|
+
|
|
116
|
+
async def trigger_scan(self) -> Dict[str, Any]:
|
|
117
|
+
"""Trigger a manual market scan."""
|
|
118
|
+
return await self._post("/api/loop/scan")
|
|
119
|
+
|
|
120
|
+
# ─── Execution ───────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async def execute(self, strategy: str, asset: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
123
|
+
"""Execute a strategy via real Soroban contract call.
|
|
124
|
+
|
|
125
|
+
Strategies: flash-loan-arb, path-arbitrage, cross-dex, blend-yield, soroswap-swap
|
|
126
|
+
"""
|
|
127
|
+
return await self._post("/api/execute", {
|
|
128
|
+
"strategy": strategy,
|
|
129
|
+
"asset": asset,
|
|
130
|
+
"params": params or {},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
async def execute_demo(self, strategy: str, asset: str) -> Dict[str, Any]:
|
|
134
|
+
"""Execute a strategy in demo mode (Soroban dry-run, no TX submitted)."""
|
|
135
|
+
return await self._post("/api/execute-demo", {
|
|
136
|
+
"strategy": strategy,
|
|
137
|
+
"asset": asset,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
# ─── Signals ─────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
async def get_recent_signals(self, count: int = 20) -> Any:
|
|
143
|
+
"""Get recent market signals."""
|
|
144
|
+
return await self._get(f"/api/signals/recent?count={count}")
|
|
145
|
+
|
|
146
|
+
# ─── Skills ──────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
async def get_skills(self) -> Any:
|
|
149
|
+
"""List all loaded skills (built-in + user-installed)."""
|
|
150
|
+
return await self._get("/api/skills")
|
|
151
|
+
|
|
152
|
+
async def install_skill(self, source: str) -> Dict[str, Any]:
|
|
153
|
+
"""Install a skill by slug."""
|
|
154
|
+
return await self._post("/api/skills/install", {"source": source})
|
|
155
|
+
|
|
156
|
+
async def uninstall_skill(self, slug: str) -> Dict[str, Any]:
|
|
157
|
+
"""Uninstall a user-installed skill by slug."""
|
|
158
|
+
return await self._delete(f"/api/skills/{slug}")
|
|
159
|
+
|
|
160
|
+
# ─── Webhooks ────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
async def register_webhook(self, url: str, events: List[str], secret: Optional[str] = None) -> Dict[str, Any]:
|
|
163
|
+
"""Register a webhook endpoint with HMAC signing."""
|
|
164
|
+
return await self._post("/api/webhooks", {"url": url, "events": events, "secret": secret})
|
|
165
|
+
|
|
166
|
+
async def get_webhooks(self) -> List[Dict[str, Any]]:
|
|
167
|
+
"""List all registered webhooks."""
|
|
168
|
+
return await self._get("/api/webhooks")
|
|
169
|
+
|
|
170
|
+
async def delete_webhook(self, webhook_id: str) -> Dict[str, Any]:
|
|
171
|
+
"""Delete a webhook by ID."""
|
|
172
|
+
return await self._delete(f"/api/webhooks/{webhook_id}")
|
|
173
|
+
|
|
174
|
+
async def test_webhook(self, webhook_id: str) -> Dict[str, Any]:
|
|
175
|
+
"""Send a test event to a webhook."""
|
|
176
|
+
return await self._post(f"/api/webhooks/{webhook_id}/test")
|
|
177
|
+
|
|
178
|
+
# ─── WebSocket ───────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
async def subscribe(self, callback: Optional[Callable] = None):
|
|
181
|
+
"""Start real-time WebSocket connection for signals."""
|
|
182
|
+
if callback:
|
|
183
|
+
self.callbacks.setdefault("signal", []).append(callback)
|
|
184
|
+
|
|
185
|
+
auth_query = f"?token={self.token}" if self.token else ""
|
|
186
|
+
url = f"{self.ws_url}{auth_query}"
|
|
187
|
+
|
|
188
|
+
while True:
|
|
189
|
+
try:
|
|
190
|
+
async with websockets.connect(url) as ws:
|
|
191
|
+
logger.info("Connected to Nirium Signal Stream")
|
|
192
|
+
await self._emit("connected", None)
|
|
193
|
+
|
|
194
|
+
async for message in ws:
|
|
195
|
+
data = json.loads(message)
|
|
196
|
+
event = data.get("type")
|
|
197
|
+
if event in self.callbacks:
|
|
198
|
+
await self._emit(event, data)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"WS Disconnected: {e}. Reconnecting in 5s...")
|
|
201
|
+
await asyncio.sleep(5)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nirium
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK for the Nirium autonomous DeFi agent on Stellar
|
|
5
|
+
Author: Nirium Protocol
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://nirium.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/nirium/nirium
|
|
9
|
+
Project-URL: Documentation, https://nirium.dev/docs
|
|
10
|
+
Keywords: nirium,stellar,defi,sdk,soroban,agent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
14
|
+
Requires-Dist: websockets>=13.0
|
|
15
|
+
|
|
16
|
+
# nirium
|
|
17
|
+
|
|
18
|
+
Official Python SDK for the **Nirium Protocol** — autonomous DeFi agent on Stellar/Soroban.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install nirium
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
from nirium import Agent
|
|
31
|
+
|
|
32
|
+
agent = Agent(
|
|
33
|
+
api_url="https://api.nirium.xyz",
|
|
34
|
+
api_key="sk_inst_your_key_here",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
# Health check
|
|
39
|
+
alive = await agent.ping()
|
|
40
|
+
print(f"Agent alive: {alive}")
|
|
41
|
+
|
|
42
|
+
# Real market data from Stellar Horizon
|
|
43
|
+
market = await agent.get_market()
|
|
44
|
+
print(f"XLM Price: ${market['xlmPrice']:.4f}")
|
|
45
|
+
|
|
46
|
+
# Execute a strategy
|
|
47
|
+
result = await agent.execute("flash-loan-arb", "XLM-USDC", {"amount": 5000})
|
|
48
|
+
print(f"Profit: {result['profit']}")
|
|
49
|
+
|
|
50
|
+
asyncio.run(main())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Real-Time Signals (WebSocket)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
agent = Agent(api_url="https://api.nirium.xyz", api_key="sk_inst_...", token="eyJhbG...")
|
|
57
|
+
|
|
58
|
+
@agent.on("signal")
|
|
59
|
+
async def on_signal(data):
|
|
60
|
+
print(f"Signal: {data['signal_type']} — {data['data']['details']}")
|
|
61
|
+
|
|
62
|
+
asyncio.run(agent.subscribe())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Authentication
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# API Key for REST endpoints
|
|
69
|
+
agent = Agent(api_url="https://api.nirium.xyz", api_key="sk_inst_...")
|
|
70
|
+
|
|
71
|
+
# With JWT token for WebSocket
|
|
72
|
+
agent = Agent(api_url="https://api.nirium.xyz", api_key="sk_inst_...", token="eyJhbG...")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Endpoint Access Model
|
|
76
|
+
|
|
77
|
+
| Access | Endpoints |
|
|
78
|
+
|---|---|
|
|
79
|
+
| **Public** (no key) | `health`, `loop/status`, `execute-demo`, `signals/recent`, `skills` list |
|
|
80
|
+
| **Protected** (API key) | `execute`, `market`, `loop/start\|stop\|scan`, `subscriptions`, `skills/install`, `webhooks` |
|
|
81
|
+
| **WebSocket** (JWT) | `/ws/signals` — real-time signal stream |
|
|
82
|
+
|
|
83
|
+
## API Coverage
|
|
84
|
+
|
|
85
|
+
| Category | Methods |
|
|
86
|
+
|---|---|
|
|
87
|
+
| Health | `ping()`, `health()`, `system_health()` |
|
|
88
|
+
| Execution | `execute()`, `execute_demo()` |
|
|
89
|
+
| Market | `get_market()`, `get_loop_status()`, `start_loop()`, `stop_loop()`, `trigger_scan()` |
|
|
90
|
+
| Signals | `get_recent_signals()` |
|
|
91
|
+
| Skills | `get_skills()`, `install_skill()`, `uninstall_skill()` |
|
|
92
|
+
| Webhooks | `register_webhook()`, `get_webhooks()`, `delete_webhook()`, `test_webhook()` |
|
|
93
|
+
| WebSocket | `subscribe()`, `on()` decorator |
|
|
94
|
+
|
|
95
|
+
## Requirements
|
|
96
|
+
|
|
97
|
+
- Python >= 3.10
|
|
98
|
+
- aiohttp >= 3.9.0
|
|
99
|
+
- websockets >= 13.0
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT — Nirium Protocol
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nirium/__init__.py,sha256=J5M-koNyzVHeTn0SGSJYbnTUKQ2B0rEI6zD_NU00z3Q,161
|
|
2
|
+
nirium/client.py,sha256=VrfHjEvp9uyn376T_272enYZXx3E2t2oKX9pQZ6d4Tc,9229
|
|
3
|
+
nirium-0.2.0.dist-info/METADATA,sha256=qvQYJftJCM7KQqNGYSB1IsnR6AEyjkP6kWRw-glFncA,2715
|
|
4
|
+
nirium-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
nirium-0.2.0.dist-info/top_level.txt,sha256=zxpE7vZKWqwB4nVbBlI-mtZS6lcRLGJW4ws9zHJA4Dg,7
|
|
6
|
+
nirium-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nirium
|