irl-sdk 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.
- irl_sdk-0.1.0/PKG-INFO +199 -0
- irl_sdk-0.1.0/README.md +176 -0
- irl_sdk-0.1.0/examples/demo_e2e.py +61 -0
- irl_sdk-0.1.0/irl_sdk/__init__.py +6 -0
- irl_sdk-0.1.0/irl_sdk/client.py +144 -0
- irl_sdk-0.1.0/irl_sdk/models.py +61 -0
- irl_sdk-0.1.0/pyproject.toml +34 -0
irl_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: irl-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the IRL Engine — cryptographic pre-execution compliance rail
|
|
5
|
+
Author-email: MacroPulse <hello@macropulse.live>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: ai,compliance,crypto,fintech,trading
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: cryptography>=42
|
|
17
|
+
Requires-Dist: httpx>=0.27
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
21
|
+
Requires-Dist: respx; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# irl-sdk — Python SDK for the IRL Engine
|
|
25
|
+
|
|
26
|
+
Async Python client for the [IRL Engine](https://irl.macropulse.live) — the cryptographic
|
|
27
|
+
pre-execution compliance gateway for autonomous AI trading agents.
|
|
28
|
+
|
|
29
|
+
- Fetches a signed Layer 2 heartbeat from the MTA operator automatically
|
|
30
|
+
- Constructs and signs the authorize request
|
|
31
|
+
- Returns a sealed `trace_id` and `reasoning_hash` before any order reaches the exchange
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install irl-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires Python 3.10+.
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
from irl_sdk import IRLClient, AuthorizeRequest, TradeAction, OrderType
|
|
46
|
+
|
|
47
|
+
IRL_URL = "https://irl.macropulse.live"
|
|
48
|
+
MTA_URL = "https://api.macropulse.live"
|
|
49
|
+
API_TOKEN = "your-irl-api-token"
|
|
50
|
+
AGENT_ID = "your-agent-uuid" # from POST /irl/agents
|
|
51
|
+
MODEL_HASH = "your-model-sha256-hex" # 64-char hex
|
|
52
|
+
|
|
53
|
+
async def main():
|
|
54
|
+
async with IRLClient(IRL_URL, API_TOKEN, MTA_URL) as client:
|
|
55
|
+
req = AuthorizeRequest(
|
|
56
|
+
agent_id=AGENT_ID,
|
|
57
|
+
model_id="my-model-v1",
|
|
58
|
+
model_hash_hex=MODEL_HASH,
|
|
59
|
+
action=TradeAction.LONG,
|
|
60
|
+
asset="BTC-USD",
|
|
61
|
+
order_type=OrderType.MARKET,
|
|
62
|
+
venue_id="coinbase",
|
|
63
|
+
quantity=0.1,
|
|
64
|
+
notional=6500.0,
|
|
65
|
+
notional_currency="USD",
|
|
66
|
+
)
|
|
67
|
+
result = await client.authorize(req)
|
|
68
|
+
|
|
69
|
+
if result.authorized:
|
|
70
|
+
print(f"AUTHORIZED trace_id={result.trace_id}")
|
|
71
|
+
print(f"reasoning_hash={result.reasoning_hash[:24]}...")
|
|
72
|
+
# embed trace_id in your exchange order metadata, then place the order
|
|
73
|
+
else:
|
|
74
|
+
print("HALTED — IRL blocked the trade")
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## End-to-End Demo
|
|
80
|
+
|
|
81
|
+
The demo registers nothing — it uses a pre-seeded demo agent in the public sandbox:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd examples
|
|
85
|
+
pip install -e ..
|
|
86
|
+
python demo_e2e.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Expected output:
|
|
90
|
+
```
|
|
91
|
+
IRL Engine : https://irl.macropulse.live
|
|
92
|
+
MTA : https://api.macropulse.live
|
|
93
|
+
Agent ID : 00000000-0000-4000-a000-000000000001
|
|
94
|
+
|
|
95
|
+
Fetching heartbeat and submitting authorize request...
|
|
96
|
+
|
|
97
|
+
AUTHORIZED
|
|
98
|
+
trace_id : <uuid>
|
|
99
|
+
reasoning_hash: <first 24 chars>...
|
|
100
|
+
shadow_blocked: False
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### `IRLClient(irl_url, api_token, mta_url)`
|
|
106
|
+
|
|
107
|
+
Async context manager. All parameters are positional.
|
|
108
|
+
|
|
109
|
+
| Parameter | Description |
|
|
110
|
+
|-----------|-------------|
|
|
111
|
+
| `irl_url` | IRL Engine base URL |
|
|
112
|
+
| `api_token` | Bearer token (from `IRL_API_TOKENS` env on the engine) |
|
|
113
|
+
| `mta_url` | MTA operator URL for heartbeat fetch. Pass empty string `""` when `LAYER2_ENABLED=false`. |
|
|
114
|
+
|
|
115
|
+
### `client.authorize(req: AuthorizeRequest) → AuthorizeResult`
|
|
116
|
+
|
|
117
|
+
1. Fetches a fresh signed heartbeat from `{mta_url}/v1/irl/heartbeat`
|
|
118
|
+
2. POSTs to `{irl_url}/irl/authorize` with the heartbeat and request payload
|
|
119
|
+
3. Returns `AuthorizeResult`
|
|
120
|
+
|
|
121
|
+
### `AuthorizeRequest` fields
|
|
122
|
+
|
|
123
|
+
| Field | Type | Required | Notes |
|
|
124
|
+
|-------|------|----------|-------|
|
|
125
|
+
| `agent_id` | str | yes | UUID registered via `POST /irl/agents` |
|
|
126
|
+
| `model_id` | str | yes | Human-readable model name |
|
|
127
|
+
| `model_hash_hex` | str | yes | SHA-256 of model config (64 hex chars) |
|
|
128
|
+
| `action` | TradeAction | yes | `LONG` / `SHORT` / `NEUTRAL` |
|
|
129
|
+
| `asset` | str | yes | e.g. `"BTC-USD"`, `"SPY"` |
|
|
130
|
+
| `order_type` | OrderType | yes | `MARKET` / `LIMIT` / `STOP` / `TWAP` / `VWAP` |
|
|
131
|
+
| `venue_id` | str | yes | Exchange identifier |
|
|
132
|
+
| `quantity` | float | yes | Order size |
|
|
133
|
+
| `notional` | float | yes | Notional value |
|
|
134
|
+
| `notional_currency` | str | yes | e.g. `"USD"` |
|
|
135
|
+
| `client_order_id` | str | no | Your internal order reference |
|
|
136
|
+
| `agent_valid_time` | int | no | Model inference timestamp (ms). Defaults to now. |
|
|
137
|
+
|
|
138
|
+
### `AuthorizeResult` fields
|
|
139
|
+
|
|
140
|
+
| Field | Type | Notes |
|
|
141
|
+
|-------|------|-------|
|
|
142
|
+
| `trace_id` | str | UUID — embed in exchange order metadata |
|
|
143
|
+
| `reasoning_hash` | str | SHA-256 seal of the full cognitive snapshot |
|
|
144
|
+
| `authorized` | bool | `True` = proceed; `False` = halted by policy |
|
|
145
|
+
| `shadow_blocked` | bool | `True` = would have been blocked; only set when `SHADOW_MODE=true` on the engine |
|
|
146
|
+
|
|
147
|
+
### `TradeAction`
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from irl_sdk import TradeAction
|
|
151
|
+
|
|
152
|
+
TradeAction.LONG # "Long"
|
|
153
|
+
TradeAction.SHORT # "Short"
|
|
154
|
+
TradeAction.NEUTRAL # "Neutral"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `OrderType`
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from irl_sdk import OrderType
|
|
161
|
+
|
|
162
|
+
OrderType.MARKET # "MARKET"
|
|
163
|
+
OrderType.LIMIT # "LIMIT"
|
|
164
|
+
OrderType.STOP # "STOP"
|
|
165
|
+
OrderType.TWAP # "TWAP"
|
|
166
|
+
OrderType.VWAP # "VWAP"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
The SDK raises `httpx.HTTPStatusError` on non-2xx responses. The response body
|
|
172
|
+
contains `{"error": "ERROR_CODE", "message": "..."}`. Common codes:
|
|
173
|
+
|
|
174
|
+
| Code | Meaning |
|
|
175
|
+
|------|---------|
|
|
176
|
+
| `HEARTBEAT_DRIFT_EXCEEDED` | Heartbeat too old — clock skew or slow network |
|
|
177
|
+
| `HEARTBEAT_MTA_REF_MISMATCH` | MTA hash mismatch — regime changed between heartbeat and authorize |
|
|
178
|
+
| `REGIME_VIOLATION` | Action not permitted in current regime |
|
|
179
|
+
| `NOTIONAL_EXCEEDS_LIMIT` | Exceeds agent notional cap × regime scale |
|
|
180
|
+
| `MODEL_HASH_MISMATCH` | Provided hash ≠ registered hash |
|
|
181
|
+
| `AGENT_NOT_FOUND` | Register the agent first via `POST /irl/agents` |
|
|
182
|
+
|
|
183
|
+
## Layer 2 (Heartbeat) Details
|
|
184
|
+
|
|
185
|
+
When `LAYER2_ENABLED=true` on the engine (default), every authorize request must
|
|
186
|
+
carry a `SignedHeartbeat`. The SDK fetches this automatically from `{mta_url}/v1/irl/heartbeat`.
|
|
187
|
+
|
|
188
|
+
The heartbeat binds each request to a specific MTA broadcast:
|
|
189
|
+
- `sequence_id` — strictly monotone (anti-replay)
|
|
190
|
+
- `timestamp_ms` — must be within `MAX_HEARTBEAT_DRIFT_MS` of `txn_time`
|
|
191
|
+
- `mta_ref` — SHA-256 of the raw `/v1/regime/current` HTTP response body
|
|
192
|
+
- `signature` — Ed25519 signature by the MTA operator
|
|
193
|
+
|
|
194
|
+
For local dev with `LAYER2_ENABLED=false`, pass `mta_url=""`. The engine
|
|
195
|
+
substitutes a zero heartbeat internally.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
irl_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# irl-sdk — Python SDK for the IRL Engine
|
|
2
|
+
|
|
3
|
+
Async Python client for the [IRL Engine](https://irl.macropulse.live) — the cryptographic
|
|
4
|
+
pre-execution compliance gateway for autonomous AI trading agents.
|
|
5
|
+
|
|
6
|
+
- Fetches a signed Layer 2 heartbeat from the MTA operator automatically
|
|
7
|
+
- Constructs and signs the authorize request
|
|
8
|
+
- Returns a sealed `trace_id` and `reasoning_hash` before any order reaches the exchange
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install irl-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Python 3.10+.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
from irl_sdk import IRLClient, AuthorizeRequest, TradeAction, OrderType
|
|
23
|
+
|
|
24
|
+
IRL_URL = "https://irl.macropulse.live"
|
|
25
|
+
MTA_URL = "https://api.macropulse.live"
|
|
26
|
+
API_TOKEN = "your-irl-api-token"
|
|
27
|
+
AGENT_ID = "your-agent-uuid" # from POST /irl/agents
|
|
28
|
+
MODEL_HASH = "your-model-sha256-hex" # 64-char hex
|
|
29
|
+
|
|
30
|
+
async def main():
|
|
31
|
+
async with IRLClient(IRL_URL, API_TOKEN, MTA_URL) as client:
|
|
32
|
+
req = AuthorizeRequest(
|
|
33
|
+
agent_id=AGENT_ID,
|
|
34
|
+
model_id="my-model-v1",
|
|
35
|
+
model_hash_hex=MODEL_HASH,
|
|
36
|
+
action=TradeAction.LONG,
|
|
37
|
+
asset="BTC-USD",
|
|
38
|
+
order_type=OrderType.MARKET,
|
|
39
|
+
venue_id="coinbase",
|
|
40
|
+
quantity=0.1,
|
|
41
|
+
notional=6500.0,
|
|
42
|
+
notional_currency="USD",
|
|
43
|
+
)
|
|
44
|
+
result = await client.authorize(req)
|
|
45
|
+
|
|
46
|
+
if result.authorized:
|
|
47
|
+
print(f"AUTHORIZED trace_id={result.trace_id}")
|
|
48
|
+
print(f"reasoning_hash={result.reasoning_hash[:24]}...")
|
|
49
|
+
# embed trace_id in your exchange order metadata, then place the order
|
|
50
|
+
else:
|
|
51
|
+
print("HALTED — IRL blocked the trade")
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## End-to-End Demo
|
|
57
|
+
|
|
58
|
+
The demo registers nothing — it uses a pre-seeded demo agent in the public sandbox:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd examples
|
|
62
|
+
pip install -e ..
|
|
63
|
+
python demo_e2e.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Expected output:
|
|
67
|
+
```
|
|
68
|
+
IRL Engine : https://irl.macropulse.live
|
|
69
|
+
MTA : https://api.macropulse.live
|
|
70
|
+
Agent ID : 00000000-0000-4000-a000-000000000001
|
|
71
|
+
|
|
72
|
+
Fetching heartbeat and submitting authorize request...
|
|
73
|
+
|
|
74
|
+
AUTHORIZED
|
|
75
|
+
trace_id : <uuid>
|
|
76
|
+
reasoning_hash: <first 24 chars>...
|
|
77
|
+
shadow_blocked: False
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### `IRLClient(irl_url, api_token, mta_url)`
|
|
83
|
+
|
|
84
|
+
Async context manager. All parameters are positional.
|
|
85
|
+
|
|
86
|
+
| Parameter | Description |
|
|
87
|
+
|-----------|-------------|
|
|
88
|
+
| `irl_url` | IRL Engine base URL |
|
|
89
|
+
| `api_token` | Bearer token (from `IRL_API_TOKENS` env on the engine) |
|
|
90
|
+
| `mta_url` | MTA operator URL for heartbeat fetch. Pass empty string `""` when `LAYER2_ENABLED=false`. |
|
|
91
|
+
|
|
92
|
+
### `client.authorize(req: AuthorizeRequest) → AuthorizeResult`
|
|
93
|
+
|
|
94
|
+
1. Fetches a fresh signed heartbeat from `{mta_url}/v1/irl/heartbeat`
|
|
95
|
+
2. POSTs to `{irl_url}/irl/authorize` with the heartbeat and request payload
|
|
96
|
+
3. Returns `AuthorizeResult`
|
|
97
|
+
|
|
98
|
+
### `AuthorizeRequest` fields
|
|
99
|
+
|
|
100
|
+
| Field | Type | Required | Notes |
|
|
101
|
+
|-------|------|----------|-------|
|
|
102
|
+
| `agent_id` | str | yes | UUID registered via `POST /irl/agents` |
|
|
103
|
+
| `model_id` | str | yes | Human-readable model name |
|
|
104
|
+
| `model_hash_hex` | str | yes | SHA-256 of model config (64 hex chars) |
|
|
105
|
+
| `action` | TradeAction | yes | `LONG` / `SHORT` / `NEUTRAL` |
|
|
106
|
+
| `asset` | str | yes | e.g. `"BTC-USD"`, `"SPY"` |
|
|
107
|
+
| `order_type` | OrderType | yes | `MARKET` / `LIMIT` / `STOP` / `TWAP` / `VWAP` |
|
|
108
|
+
| `venue_id` | str | yes | Exchange identifier |
|
|
109
|
+
| `quantity` | float | yes | Order size |
|
|
110
|
+
| `notional` | float | yes | Notional value |
|
|
111
|
+
| `notional_currency` | str | yes | e.g. `"USD"` |
|
|
112
|
+
| `client_order_id` | str | no | Your internal order reference |
|
|
113
|
+
| `agent_valid_time` | int | no | Model inference timestamp (ms). Defaults to now. |
|
|
114
|
+
|
|
115
|
+
### `AuthorizeResult` fields
|
|
116
|
+
|
|
117
|
+
| Field | Type | Notes |
|
|
118
|
+
|-------|------|-------|
|
|
119
|
+
| `trace_id` | str | UUID — embed in exchange order metadata |
|
|
120
|
+
| `reasoning_hash` | str | SHA-256 seal of the full cognitive snapshot |
|
|
121
|
+
| `authorized` | bool | `True` = proceed; `False` = halted by policy |
|
|
122
|
+
| `shadow_blocked` | bool | `True` = would have been blocked; only set when `SHADOW_MODE=true` on the engine |
|
|
123
|
+
|
|
124
|
+
### `TradeAction`
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from irl_sdk import TradeAction
|
|
128
|
+
|
|
129
|
+
TradeAction.LONG # "Long"
|
|
130
|
+
TradeAction.SHORT # "Short"
|
|
131
|
+
TradeAction.NEUTRAL # "Neutral"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `OrderType`
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from irl_sdk import OrderType
|
|
138
|
+
|
|
139
|
+
OrderType.MARKET # "MARKET"
|
|
140
|
+
OrderType.LIMIT # "LIMIT"
|
|
141
|
+
OrderType.STOP # "STOP"
|
|
142
|
+
OrderType.TWAP # "TWAP"
|
|
143
|
+
OrderType.VWAP # "VWAP"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Error Handling
|
|
147
|
+
|
|
148
|
+
The SDK raises `httpx.HTTPStatusError` on non-2xx responses. The response body
|
|
149
|
+
contains `{"error": "ERROR_CODE", "message": "..."}`. Common codes:
|
|
150
|
+
|
|
151
|
+
| Code | Meaning |
|
|
152
|
+
|------|---------|
|
|
153
|
+
| `HEARTBEAT_DRIFT_EXCEEDED` | Heartbeat too old — clock skew or slow network |
|
|
154
|
+
| `HEARTBEAT_MTA_REF_MISMATCH` | MTA hash mismatch — regime changed between heartbeat and authorize |
|
|
155
|
+
| `REGIME_VIOLATION` | Action not permitted in current regime |
|
|
156
|
+
| `NOTIONAL_EXCEEDS_LIMIT` | Exceeds agent notional cap × regime scale |
|
|
157
|
+
| `MODEL_HASH_MISMATCH` | Provided hash ≠ registered hash |
|
|
158
|
+
| `AGENT_NOT_FOUND` | Register the agent first via `POST /irl/agents` |
|
|
159
|
+
|
|
160
|
+
## Layer 2 (Heartbeat) Details
|
|
161
|
+
|
|
162
|
+
When `LAYER2_ENABLED=true` on the engine (default), every authorize request must
|
|
163
|
+
carry a `SignedHeartbeat`. The SDK fetches this automatically from `{mta_url}/v1/irl/heartbeat`.
|
|
164
|
+
|
|
165
|
+
The heartbeat binds each request to a specific MTA broadcast:
|
|
166
|
+
- `sequence_id` — strictly monotone (anti-replay)
|
|
167
|
+
- `timestamp_ms` — must be within `MAX_HEARTBEAT_DRIFT_MS` of `txn_time`
|
|
168
|
+
- `mta_ref` — SHA-256 of the raw `/v1/regime/current` HTTP response body
|
|
169
|
+
- `signature` — Ed25519 signature by the MTA operator
|
|
170
|
+
|
|
171
|
+
For local dev with `LAYER2_ENABLED=false`, pass `mta_url=""`. The engine
|
|
172
|
+
substitutes a zero heartbeat internally.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IRL SDK — end-to-end demo.
|
|
3
|
+
|
|
4
|
+
Registers an agent in the IRL Engine MAR (if not already present) and
|
|
5
|
+
submits a test trade intent for authorization.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
pip install -e ..
|
|
9
|
+
python demo_e2e.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import hashlib
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from irl_sdk import IRLClient, AuthorizeRequest, TradeAction, OrderType
|
|
17
|
+
|
|
18
|
+
IRL_URL = os.getenv("IRL_URL", "https://irl.macropulse.live")
|
|
19
|
+
MTA_URL = os.getenv("MTA_URL", "https://api.macropulse.live")
|
|
20
|
+
API_TOKEN = os.getenv("IRL_API_TOKEN", "demo-readonly-1a1bb53fb4bcb5f1ca2c2f48808a35ba")
|
|
21
|
+
|
|
22
|
+
# Stable demo agent — must be registered in the IRL Engine MAR.
|
|
23
|
+
AGENT_ID = "00000000-0000-4000-a000-000000000001" # demo-crypto-agent in MAR
|
|
24
|
+
MODEL_HASH = "cee60d4e409bd88b5e1998ef6ac078498491616a3a321bc76399b94784c4f283" # sha256(b"demo-crypto-agent-v1")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main() -> None:
|
|
28
|
+
print(f"IRL Engine : {IRL_URL}")
|
|
29
|
+
print(f"MTA : {MTA_URL}")
|
|
30
|
+
print(f"Agent ID : {AGENT_ID}")
|
|
31
|
+
print(f"Model hash : {MODEL_HASH[:16]}...")
|
|
32
|
+
print()
|
|
33
|
+
|
|
34
|
+
async with IRLClient(IRL_URL, API_TOKEN, MTA_URL) as client:
|
|
35
|
+
req = AuthorizeRequest(
|
|
36
|
+
agent_id=AGENT_ID,
|
|
37
|
+
model_id="demo-algo-v1",
|
|
38
|
+
model_hash_hex=MODEL_HASH,
|
|
39
|
+
action=TradeAction.LONG,
|
|
40
|
+
asset="BTC-USD",
|
|
41
|
+
order_type=OrderType.MARKET,
|
|
42
|
+
venue_id="coinbase",
|
|
43
|
+
quantity=0.01,
|
|
44
|
+
notional=650.0,
|
|
45
|
+
notional_currency="USD",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
print("Fetching heartbeat and submitting authorize request...")
|
|
49
|
+
try:
|
|
50
|
+
result = await client.authorize(req)
|
|
51
|
+
print()
|
|
52
|
+
print("AUTHORIZED" if result.authorized else "HALTED")
|
|
53
|
+
print(f" trace_id : {result.trace_id}")
|
|
54
|
+
print(f" reasoning_hash: {result.reasoning_hash[:24]}...")
|
|
55
|
+
print(f" shadow_blocked: {result.shadow_blocked}")
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
print(f"ERROR: {exc}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""IRL SDK — Python client for the IRL Engine compliance rail."""
|
|
2
|
+
|
|
3
|
+
from .client import IRLClient
|
|
4
|
+
from .models import AuthorizeRequest, AuthorizeResult, TradeAction, OrderType
|
|
5
|
+
|
|
6
|
+
__all__ = ["IRLClient", "AuthorizeRequest", "AuthorizeResult", "TradeAction", "OrderType"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IRLClient — send authorized trade intents to the IRL Engine.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
client = IRLClient(
|
|
6
|
+
irl_url="https://irl.macropulse.live",
|
|
7
|
+
api_token="your-token",
|
|
8
|
+
mta_url="https://api.macropulse.live",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
result = await client.authorize(
|
|
12
|
+
AuthorizeRequest(
|
|
13
|
+
agent_id="550e8400-e29b-41d4-a716-446655440000",
|
|
14
|
+
model_id="my-algo-v1",
|
|
15
|
+
model_hash_hex="abc123...",
|
|
16
|
+
action=TradeAction.LONG,
|
|
17
|
+
asset="BTC-USD",
|
|
18
|
+
quantity=0.1,
|
|
19
|
+
notional=6500.0,
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
print(result.trace_id, result.authorized)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import time
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
import httpx
|
|
32
|
+
|
|
33
|
+
from .models import AuthorizeRequest, AuthorizeResult
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class IRLClient:
|
|
37
|
+
"""Async client for the IRL Engine /irl/authorize endpoint.
|
|
38
|
+
|
|
39
|
+
Fetches a fresh heartbeat from MacroPulse before each authorize call
|
|
40
|
+
and attaches it automatically. The heartbeat ensures L2 anti-replay
|
|
41
|
+
compliance — do not cache or reuse heartbeats across requests.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
irl_url: str,
|
|
47
|
+
api_token: str,
|
|
48
|
+
mta_url: str,
|
|
49
|
+
timeout: float = 5.0,
|
|
50
|
+
) -> None:
|
|
51
|
+
self._irl_url = irl_url.rstrip("/")
|
|
52
|
+
self._mta_url = mta_url.rstrip("/")
|
|
53
|
+
self._headers = {"Authorization": f"Bearer {api_token}"}
|
|
54
|
+
self._http = httpx.AsyncClient(timeout=timeout)
|
|
55
|
+
|
|
56
|
+
async def authorize(self, req: AuthorizeRequest) -> AuthorizeResult:
|
|
57
|
+
"""Fetch a fresh heartbeat and submit a trade intent for authorization.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
httpx.HTTPStatusError: on 4xx/5xx from IRL Engine
|
|
61
|
+
RuntimeError: if heartbeat fetch fails
|
|
62
|
+
"""
|
|
63
|
+
hb = await self._fetch_heartbeat()
|
|
64
|
+
|
|
65
|
+
if req.agent_valid_time == 0:
|
|
66
|
+
req.agent_valid_time = int(time.time() * 1000)
|
|
67
|
+
|
|
68
|
+
body = self._build_body(req, hb)
|
|
69
|
+
|
|
70
|
+
resp = await self._http.post(
|
|
71
|
+
f"{self._irl_url}/irl/authorize",
|
|
72
|
+
json=body,
|
|
73
|
+
headers=self._headers,
|
|
74
|
+
)
|
|
75
|
+
resp.raise_for_status()
|
|
76
|
+
data = resp.json()
|
|
77
|
+
|
|
78
|
+
return AuthorizeResult(
|
|
79
|
+
trace_id=data["trace_id"],
|
|
80
|
+
reasoning_hash=data["reasoning_hash"],
|
|
81
|
+
authorized=data["authorized"],
|
|
82
|
+
shadow_blocked=data.get("shadow_blocked", False),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def close(self) -> None:
|
|
86
|
+
await self._http.aclose()
|
|
87
|
+
|
|
88
|
+
async def __aenter__(self) -> "IRLClient":
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
async def __aexit__(self, *_: object) -> None:
|
|
92
|
+
await self.close()
|
|
93
|
+
|
|
94
|
+
async def _fetch_heartbeat(self) -> dict:
|
|
95
|
+
resp = await self._http.get(f"{self._mta_url}/v1/irl/heartbeat")
|
|
96
|
+
if resp.status_code != 200:
|
|
97
|
+
raise RuntimeError(
|
|
98
|
+
f"Failed to fetch heartbeat: HTTP {resp.status_code} — {resp.text}"
|
|
99
|
+
)
|
|
100
|
+
return resp.json()
|
|
101
|
+
|
|
102
|
+
def _build_body(self, req: AuthorizeRequest, heartbeat: dict) -> dict:
|
|
103
|
+
# Match the AuthorizeRequest JSON schema expected by the IRL Engine.
|
|
104
|
+
# TradeAction variants are serialized as tagged JSON: {"Long": qty}
|
|
105
|
+
action_value: object
|
|
106
|
+
if req.action.value == "Long":
|
|
107
|
+
action_value = {"Long": req.quantity}
|
|
108
|
+
elif req.action.value == "Short":
|
|
109
|
+
action_value = {"Short": req.quantity}
|
|
110
|
+
elif req.action.value == "Neutral":
|
|
111
|
+
action_value = "Neutral"
|
|
112
|
+
else:
|
|
113
|
+
action_value = req.action.value
|
|
114
|
+
|
|
115
|
+
body: dict = {
|
|
116
|
+
"agent_id": req.agent_id,
|
|
117
|
+
"model_id": req.model_id,
|
|
118
|
+
"model_hash_hex": req.model_hash_hex,
|
|
119
|
+
"prompt_version": req.prompt_version,
|
|
120
|
+
"feature_schema_id": req.feature_schema_id,
|
|
121
|
+
"hyperparameter_checksum": req.hyperparameter_checksum,
|
|
122
|
+
"action": action_value,
|
|
123
|
+
"asset": req.asset,
|
|
124
|
+
"order_type": req.order_type.value,
|
|
125
|
+
"venue_id": req.venue_id,
|
|
126
|
+
"quantity": req.quantity,
|
|
127
|
+
"notional": req.notional,
|
|
128
|
+
"notional_currency": req.notional_currency,
|
|
129
|
+
"multiplier": req.multiplier,
|
|
130
|
+
"reduce_only": req.reduce_only,
|
|
131
|
+
"agent_valid_time": req.agent_valid_time,
|
|
132
|
+
"heartbeat": heartbeat,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
body["client_order_id"] = req.client_order_id or ""
|
|
136
|
+
|
|
137
|
+
if req.limit_price is not None:
|
|
138
|
+
body["limit_price"] = req.limit_price
|
|
139
|
+
if req.stop_price is not None:
|
|
140
|
+
body["stop_price"] = req.stop_price
|
|
141
|
+
if req.regulatory is not None:
|
|
142
|
+
body["regulatory"] = req.regulatory
|
|
143
|
+
|
|
144
|
+
return body
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Data models matching the IRL Engine wire format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TradeAction(str, Enum):
|
|
11
|
+
LONG = "Long"
|
|
12
|
+
SHORT = "Short"
|
|
13
|
+
NEUTRAL = "Neutral"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OrderType(str, Enum):
|
|
17
|
+
MARKET = "MARKET"
|
|
18
|
+
LIMIT = "LIMIT"
|
|
19
|
+
STOP = "STOP"
|
|
20
|
+
STOP_LIMIT = "STOP_LIMIT"
|
|
21
|
+
TWAP = "TWAP"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AuthorizeRequest:
|
|
26
|
+
# Agent identity
|
|
27
|
+
agent_id: str # UUID string — must be registered in the MAR
|
|
28
|
+
model_id: str # Human-readable model identifier
|
|
29
|
+
model_hash_hex: str # SHA-256 of the agent's model weights (hex)
|
|
30
|
+
prompt_version: str = "v1"
|
|
31
|
+
feature_schema_id: str = "default"
|
|
32
|
+
hyperparameter_checksum: str = "0" * 64
|
|
33
|
+
|
|
34
|
+
# Trade intent
|
|
35
|
+
action: TradeAction = TradeAction.NEUTRAL
|
|
36
|
+
asset: str = ""
|
|
37
|
+
order_type: OrderType = OrderType.MARKET
|
|
38
|
+
venue_id: str = ""
|
|
39
|
+
quantity: float = 0.0
|
|
40
|
+
notional: float = 0.0
|
|
41
|
+
notional_currency: str = "USD"
|
|
42
|
+
multiplier: float = 1.0
|
|
43
|
+
limit_price: Optional[float] = None
|
|
44
|
+
stop_price: Optional[float] = None
|
|
45
|
+
client_order_id: Optional[str] = None
|
|
46
|
+
reduce_only: bool = False
|
|
47
|
+
|
|
48
|
+
# Temporal context
|
|
49
|
+
agent_valid_time: int = 0 # Unix ms of the agent's decision time
|
|
50
|
+
|
|
51
|
+
# Filled automatically by IRLClient.authorize()
|
|
52
|
+
heartbeat: Optional[dict] = None
|
|
53
|
+
regulatory: Optional[dict] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class AuthorizeResult:
|
|
58
|
+
trace_id: str
|
|
59
|
+
reasoning_hash: str
|
|
60
|
+
authorized: bool
|
|
61
|
+
shadow_blocked: bool
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "irl-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the IRL Engine — cryptographic pre-execution compliance rail"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "MacroPulse", email = "hello@macropulse.live"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["trading", "compliance", "ai", "crypto", "fintech"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"httpx>=0.27",
|
|
27
|
+
"cryptography>=42",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pytest", "pytest-asyncio", "respx"]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["irl_sdk"]
|