hypnex-forge 0.2.1__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.
- hypnex_forge-0.2.1/.gitignore +69 -0
- hypnex_forge-0.2.1/LICENSE +21 -0
- hypnex_forge-0.2.1/PKG-INFO +248 -0
- hypnex_forge-0.2.1/README.md +221 -0
- hypnex_forge-0.2.1/conftest.py +10 -0
- hypnex_forge-0.2.1/pyproject.toml +48 -0
- hypnex_forge-0.2.1/src/hypnex_forge/__init__.py +74 -0
- hypnex_forge-0.2.1/src/hypnex_forge/_abi.py +353 -0
- hypnex_forge-0.2.1/src/hypnex_forge/_constants.py +86 -0
- hypnex_forge-0.2.1/src/hypnex_forge/escrow.py +385 -0
- hypnex_forge-0.2.1/src/hypnex_forge/forge.py +304 -0
- hypnex_forge-0.2.1/tests/test_basic.py +47 -0
- hypnex_forge-0.2.1/tests/test_escrow_e2e.py +290 -0
- hypnex_forge-0.2.1/tests/test_escrow_unit.py +321 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# --- Python ---
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.eggs/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.tox/
|
|
12
|
+
.coverage
|
|
13
|
+
.coverage.*
|
|
14
|
+
htmlcov/
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# --- Node / TypeScript ---
|
|
20
|
+
node_modules/
|
|
21
|
+
*.tsbuildinfo
|
|
22
|
+
.npm/
|
|
23
|
+
.yarn/
|
|
24
|
+
.pnpm-store/
|
|
25
|
+
.parcel-cache/
|
|
26
|
+
|
|
27
|
+
# --- Next.js (apps/stake) ---
|
|
28
|
+
.next/
|
|
29
|
+
out/
|
|
30
|
+
.vercel/
|
|
31
|
+
|
|
32
|
+
# --- Wrangler / Cloudflare ---
|
|
33
|
+
.wrangler/
|
|
34
|
+
|
|
35
|
+
# --- Foundry (registry-contracts) ---
|
|
36
|
+
forge-cache/
|
|
37
|
+
broadcast/
|
|
38
|
+
registry-contracts/cache/
|
|
39
|
+
registry-contracts/lib/
|
|
40
|
+
registry-contracts/out/
|
|
41
|
+
|
|
42
|
+
# --- Editor / OS ---
|
|
43
|
+
.idea/
|
|
44
|
+
.vscode/
|
|
45
|
+
.DS_Store
|
|
46
|
+
*.swp
|
|
47
|
+
*.swo
|
|
48
|
+
*~
|
|
49
|
+
|
|
50
|
+
# --- Local secrets — never commit ---
|
|
51
|
+
.env
|
|
52
|
+
.env.*
|
|
53
|
+
!.env.example
|
|
54
|
+
.env.local
|
|
55
|
+
.env.production
|
|
56
|
+
|
|
57
|
+
# --- Log + tmp ---
|
|
58
|
+
*.log
|
|
59
|
+
logs/
|
|
60
|
+
tmp/
|
|
61
|
+
|
|
62
|
+
# --- Build outputs in apps/bench ---
|
|
63
|
+
apps/bench/out/
|
|
64
|
+
|
|
65
|
+
# --- Generated bench data is committed (audit trail) but local runs aren't ---
|
|
66
|
+
python-bench/data/*.jsonl
|
|
67
|
+
|
|
68
|
+
# Claude Code session artifacts — never commit
|
|
69
|
+
.claude/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hypnex Labs
|
|
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,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hypnex-forge
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python SDK for Forge — the paid tool registry on Morpheus AI (MRC 60). Phase 1 listing rail + Phase 3 pre-paid escrow with relayer-attested per-call settlement on Arbitrum + Base.
|
|
5
|
+
Project-URL: Homepage, https://hypnex.xyz
|
|
6
|
+
Project-URL: Documentation, https://docs.hypnex.xyz/forge
|
|
7
|
+
Project-URL: Repository, https://github.com/hypnex-labs/hypnex
|
|
8
|
+
Project-URL: Issues, https://github.com/hypnex-labs/hypnex/issues
|
|
9
|
+
Author: Hypnex Labs
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,forge,hypnex,marketplace,mcp,mor,morpheus,mrc-60,tools,web3
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: eth-account>=0.10.0
|
|
22
|
+
Requires-Dist: web3<8.0,>=6.15.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# hypnex-forge (Python)
|
|
29
|
+
|
|
30
|
+
Python SDK for **Forge** — the paid tool registry on Morpheus AI. Implements **MRC 60** Phase 1 (paid listings) and Phase 3 (on-chain per-call settlement via pre-paid escrow).
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install hypnex-forge
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Affiliation & monetization
|
|
37
|
+
|
|
38
|
+
This SDK is published by **Hypnex Labs**. Hypnex is not affiliated with the Morpheus AI Foundation.
|
|
39
|
+
|
|
40
|
+
- `register()` costs **1 MOR** (configurable on-chain by claim-admin), forwarded directly to the Hypnex Labs treasury at `0x22B5C0075372E743042b2d62b3D254425Eb957D8` inside the same tx. The SDK auto-approves the MOR allowance on first use.
|
|
41
|
+
- `pricePerCallWei` is what end users pay YOU per call. With Phase 3 escrow, on-chain settlement debits the agent's escrow at this rate, with **90% routed to the tool owner and 10% to the Hypnex treasury** as a protocol fee. The fee is hard-capped at 20% in the contract; admin cannot rug.
|
|
42
|
+
|
|
43
|
+
## Quickstart — Phase 1 (listings)
|
|
44
|
+
|
|
45
|
+
### Read-only
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from hypnex_forge import Forge
|
|
49
|
+
|
|
50
|
+
f = Forge(chain="arbitrum")
|
|
51
|
+
print(f.total_tools())
|
|
52
|
+
print(f.list_tools(0, 100))
|
|
53
|
+
print(f.get_listing_fee_wei()) # 10**18 = 1 MOR
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Register a tool (with signer)
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import os
|
|
60
|
+
from hypnex_forge import Forge
|
|
61
|
+
|
|
62
|
+
f = Forge(
|
|
63
|
+
chain="arbitrum",
|
|
64
|
+
rpc_url=os.environ["ETH_RPC_URL"],
|
|
65
|
+
private_key=os.environ["PRIVATE_KEY"],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
tx = f.register(
|
|
69
|
+
namespace="myorg",
|
|
70
|
+
name="websearch",
|
|
71
|
+
mcp_uri="https://mcp.example.com/v1",
|
|
72
|
+
categories=["search", "data"],
|
|
73
|
+
price_per_call_wei=int(0.001 * 10**18),
|
|
74
|
+
)
|
|
75
|
+
print("tool registered:", tx)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quickstart — Phase 3 (escrow + per-call settlement)
|
|
79
|
+
|
|
80
|
+
Three actors: the **agent** (who consumes the tool), the **tool owner** (who runs it), and the **Hypnex relayer** (which batches receipts and submits `settle()` on a 4-hourly cron).
|
|
81
|
+
|
|
82
|
+
### 1. Tool owner: register a signer key
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from hypnex_forge import Escrow, tool_id
|
|
86
|
+
|
|
87
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["TOOL_OWNER_PK"])
|
|
88
|
+
tid = tool_id("myorg", "websearch")
|
|
89
|
+
|
|
90
|
+
esc.set_signer(tid, "0xYourSessionKeyOrEoa")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Agent: pre-fund per-tool escrow
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from hypnex_forge import Escrow, tool_id
|
|
97
|
+
|
|
98
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["AGENT_PK"])
|
|
99
|
+
tid = tool_id("myorg", "websearch")
|
|
100
|
+
|
|
101
|
+
# Auto-approves MOR allowance on first call.
|
|
102
|
+
esc.deposit(tid, 10 * 10**18) # 10 MOR runway
|
|
103
|
+
print("escrow balance:", esc.balance(tid))
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Tool server: sign + post receipts
|
|
107
|
+
|
|
108
|
+
After every N calls, the tool's signer key signs a receipt and posts it
|
|
109
|
+
to `relayer.hypnex.xyz`:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["TOOL_OWNER_PK"])
|
|
113
|
+
esc.call_with_receipt(
|
|
114
|
+
tool_id=tid,
|
|
115
|
+
agent="0xAgentAddress",
|
|
116
|
+
call_count=100, # cumulative for this period
|
|
117
|
+
period_id=42, # strictly increasing per (toolId, agent)
|
|
118
|
+
)
|
|
119
|
+
# The relayer batches and submits settle() on the next cron tick.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4. Tool owner: withdraw accrued revenue
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
print("accrued:", esc.tool_revenue(tid) / 1e18, "MOR")
|
|
126
|
+
esc.withdraw_tool(tid, "0xYourColdWallet")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 5. Agent: 24h-delayed self-custody exit
|
|
130
|
+
|
|
131
|
+
If a tool owner misbehaves, agents can pull remaining escrow back:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
esc.agent_request_withdraw(tid) # opens a 24h window
|
|
135
|
+
# ... 24 hours later ...
|
|
136
|
+
esc.agent_execute_withdraw(tid, "0xAgentAddress")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The relayer has the full 24h to flush pending settlements before the
|
|
140
|
+
exit becomes executable.
|
|
141
|
+
|
|
142
|
+
## Public API
|
|
143
|
+
|
|
144
|
+
### `Forge` (Phase 1 — listings)
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Forge(address=None, chain="arbitrum", w3=None, rpc_url=None,
|
|
148
|
+
account=None, private_key=None)
|
|
149
|
+
|
|
150
|
+
# read
|
|
151
|
+
.total_tools() -> int
|
|
152
|
+
.list_tools(offset=0, limit=100) -> list[bytes32]
|
|
153
|
+
.tools_of(owner) -> list[bytes32]
|
|
154
|
+
.tools_in_category(label_or_bytes32) -> list[bytes32]
|
|
155
|
+
.get_tool(tool_id) -> Tool
|
|
156
|
+
.is_registered(tool_id) -> bool
|
|
157
|
+
.tool_id_of(namespace, name) -> bytes32
|
|
158
|
+
.get_listing_fee_wei() -> int
|
|
159
|
+
.get_claim_admin() -> str
|
|
160
|
+
.get_mor_balance(address=None) -> int
|
|
161
|
+
.get_mor_allowance(owner=None) -> int
|
|
162
|
+
|
|
163
|
+
# write (signer required)
|
|
164
|
+
.register(namespace, name, mcp_uri, categories, price_per_call_wei=0) -> tx
|
|
165
|
+
.update_tool(tool_id, mcp_uri, price_per_call_wei) -> tx
|
|
166
|
+
.set_categories(tool_id, categories) -> tx
|
|
167
|
+
.deactivate(tool_id) -> tx
|
|
168
|
+
.reactivate(tool_id) -> tx
|
|
169
|
+
.transfer_ownership(tool_id, new_owner) -> tx
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `Escrow` (Phase 3 — per-call settlement)
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Escrow(address=None, chain="arbitrum", w3=None, rpc_url=None,
|
|
176
|
+
account=None, private_key=None, relayer_url=None)
|
|
177
|
+
|
|
178
|
+
# read
|
|
179
|
+
.balance(tool_id, agent=None) -> int
|
|
180
|
+
.tool_revenue(tool_id) -> int
|
|
181
|
+
.treasury_revenue() -> int
|
|
182
|
+
.signer_of(tool_id) -> str
|
|
183
|
+
.last_period_id(tool_id, agent) -> int
|
|
184
|
+
.withdraw_unlock_at(tool_id, agent) -> int
|
|
185
|
+
.protocol_fee_bps() -> int
|
|
186
|
+
.relayer_address() -> str
|
|
187
|
+
.claim_admin() -> str
|
|
188
|
+
.quote_settle(tool_id, call_count) -> (total, tool_payout, protocol_fee)
|
|
189
|
+
|
|
190
|
+
# agent-side write
|
|
191
|
+
.deposit(tool_id, amount, auto_approve=True) -> tx
|
|
192
|
+
.agent_request_withdraw(tool_id) -> tx
|
|
193
|
+
.agent_execute_withdraw(tool_id, to=None) -> tx
|
|
194
|
+
|
|
195
|
+
# tool-owner-side write
|
|
196
|
+
.set_signer(tool_id, new_signer) -> tx
|
|
197
|
+
.withdraw_tool(tool_id, to) -> tx
|
|
198
|
+
.sign_receipt(tool_id, agent, call_count, period_id) -> bytes
|
|
199
|
+
.post_receipt(tool_id, agent, call_count, period_id, signature, ...) -> dict
|
|
200
|
+
.call_with_receipt(tool_id, agent, call_count, period_id, ...) -> dict
|
|
201
|
+
|
|
202
|
+
# relayer / claim-admin write
|
|
203
|
+
.settle(tool_id, agent, call_count, period_id, signature) -> tx
|
|
204
|
+
.withdraw_treasury(to) -> tx
|
|
205
|
+
.set_relayer(new_relayer) -> tx
|
|
206
|
+
.set_protocol_fee_bps(new_bps) -> tx
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Plus a pure helper:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
receipt_struct_hash(*, chain_id, escrow_address, tool_id,
|
|
213
|
+
agent, call_count, period_id) -> bytes
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The bytes returned match the on-chain Solidity recovery exactly:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
keccak256(abi.encode(chainid, escrow, toolId, agent, callCount, periodId))
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
wrapped with the EIP-191 `\x19Ethereum Signed Message:\n32` prefix when
|
|
223
|
+
signed.
|
|
224
|
+
|
|
225
|
+
## Environment variables
|
|
226
|
+
|
|
227
|
+
| Variable | Purpose |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `HYPNEX_FORGE_CHAIN` | Default chain (`arbitrum` or `base`) |
|
|
230
|
+
| `HYPNEX_FORGE_RPC_URL` | Override RPC URL |
|
|
231
|
+
| `HYPNEX_FORGE_ADDRESS` | Override `ForgeToolRegistry` address |
|
|
232
|
+
| `HYPNEX_FORGE_ESCROW_ADDRESS` | Override `ForgeEscrow` address |
|
|
233
|
+
| `HYPNEX_FORGE_RELAYER_URL` | Override relayer endpoint (default `https://relayer.hypnex.xyz`) |
|
|
234
|
+
| `PRIVATE_KEY` | Required for write methods |
|
|
235
|
+
|
|
236
|
+
## Status
|
|
237
|
+
|
|
238
|
+
- **Phase 1** — paid listings on Arbitrum One + Base mainnet (live since 2026-05-09).
|
|
239
|
+
- **Phase 2** — discovery UI at `forge.hypnex.xyz` (live).
|
|
240
|
+
- **Phase 3** — pre-paid escrow + per-call settlement (`ForgeEscrow`). Live since 2026-05-09:
|
|
241
|
+
- Arbitrum One: [`0x88E32F332A5140Af16c6273d8131bC84BC089Da5`](https://arbiscan.io/address/0x88E32F332A5140Af16c6273d8131bC84BC089Da5#code)
|
|
242
|
+
- Base mainnet: [`0xCC258a7BBF361fd824e87D4b3C0D394De6Fd454F`](https://basescan.org/address/0xCC258a7BBF361fd824e87D4b3C0D394De6Fd454F#code)
|
|
243
|
+
|
|
244
|
+
SDK 0.2.1+ resolves these automatically when you pass `chain="arbitrum"` or `chain="base"`. The Cloudflare Worker relayer at `relayer.hypnex.xyz` is the last operational piece; until it's live, receipts can be submitted directly to `Escrow.settle()` by the configured relayer EOA.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT (Copyright (c) 2026 Hypnex Labs). The MIT-licensed source includes hard-coded fee routers to the Hypnex Labs treasury for both the listing fee and the Phase-3 protocol fee — see "Affiliation & monetization" above.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# hypnex-forge (Python)
|
|
2
|
+
|
|
3
|
+
Python SDK for **Forge** — the paid tool registry on Morpheus AI. Implements **MRC 60** Phase 1 (paid listings) and Phase 3 (on-chain per-call settlement via pre-paid escrow).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install hypnex-forge
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Affiliation & monetization
|
|
10
|
+
|
|
11
|
+
This SDK is published by **Hypnex Labs**. Hypnex is not affiliated with the Morpheus AI Foundation.
|
|
12
|
+
|
|
13
|
+
- `register()` costs **1 MOR** (configurable on-chain by claim-admin), forwarded directly to the Hypnex Labs treasury at `0x22B5C0075372E743042b2d62b3D254425Eb957D8` inside the same tx. The SDK auto-approves the MOR allowance on first use.
|
|
14
|
+
- `pricePerCallWei` is what end users pay YOU per call. With Phase 3 escrow, on-chain settlement debits the agent's escrow at this rate, with **90% routed to the tool owner and 10% to the Hypnex treasury** as a protocol fee. The fee is hard-capped at 20% in the contract; admin cannot rug.
|
|
15
|
+
|
|
16
|
+
## Quickstart — Phase 1 (listings)
|
|
17
|
+
|
|
18
|
+
### Read-only
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from hypnex_forge import Forge
|
|
22
|
+
|
|
23
|
+
f = Forge(chain="arbitrum")
|
|
24
|
+
print(f.total_tools())
|
|
25
|
+
print(f.list_tools(0, 100))
|
|
26
|
+
print(f.get_listing_fee_wei()) # 10**18 = 1 MOR
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Register a tool (with signer)
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import os
|
|
33
|
+
from hypnex_forge import Forge
|
|
34
|
+
|
|
35
|
+
f = Forge(
|
|
36
|
+
chain="arbitrum",
|
|
37
|
+
rpc_url=os.environ["ETH_RPC_URL"],
|
|
38
|
+
private_key=os.environ["PRIVATE_KEY"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
tx = f.register(
|
|
42
|
+
namespace="myorg",
|
|
43
|
+
name="websearch",
|
|
44
|
+
mcp_uri="https://mcp.example.com/v1",
|
|
45
|
+
categories=["search", "data"],
|
|
46
|
+
price_per_call_wei=int(0.001 * 10**18),
|
|
47
|
+
)
|
|
48
|
+
print("tool registered:", tx)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quickstart — Phase 3 (escrow + per-call settlement)
|
|
52
|
+
|
|
53
|
+
Three actors: the **agent** (who consumes the tool), the **tool owner** (who runs it), and the **Hypnex relayer** (which batches receipts and submits `settle()` on a 4-hourly cron).
|
|
54
|
+
|
|
55
|
+
### 1. Tool owner: register a signer key
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from hypnex_forge import Escrow, tool_id
|
|
59
|
+
|
|
60
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["TOOL_OWNER_PK"])
|
|
61
|
+
tid = tool_id("myorg", "websearch")
|
|
62
|
+
|
|
63
|
+
esc.set_signer(tid, "0xYourSessionKeyOrEoa")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Agent: pre-fund per-tool escrow
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from hypnex_forge import Escrow, tool_id
|
|
70
|
+
|
|
71
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["AGENT_PK"])
|
|
72
|
+
tid = tool_id("myorg", "websearch")
|
|
73
|
+
|
|
74
|
+
# Auto-approves MOR allowance on first call.
|
|
75
|
+
esc.deposit(tid, 10 * 10**18) # 10 MOR runway
|
|
76
|
+
print("escrow balance:", esc.balance(tid))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Tool server: sign + post receipts
|
|
80
|
+
|
|
81
|
+
After every N calls, the tool's signer key signs a receipt and posts it
|
|
82
|
+
to `relayer.hypnex.xyz`:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
esc = Escrow(chain="arbitrum", private_key=os.environ["TOOL_OWNER_PK"])
|
|
86
|
+
esc.call_with_receipt(
|
|
87
|
+
tool_id=tid,
|
|
88
|
+
agent="0xAgentAddress",
|
|
89
|
+
call_count=100, # cumulative for this period
|
|
90
|
+
period_id=42, # strictly increasing per (toolId, agent)
|
|
91
|
+
)
|
|
92
|
+
# The relayer batches and submits settle() on the next cron tick.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Tool owner: withdraw accrued revenue
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
print("accrued:", esc.tool_revenue(tid) / 1e18, "MOR")
|
|
99
|
+
esc.withdraw_tool(tid, "0xYourColdWallet")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 5. Agent: 24h-delayed self-custody exit
|
|
103
|
+
|
|
104
|
+
If a tool owner misbehaves, agents can pull remaining escrow back:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
esc.agent_request_withdraw(tid) # opens a 24h window
|
|
108
|
+
# ... 24 hours later ...
|
|
109
|
+
esc.agent_execute_withdraw(tid, "0xAgentAddress")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The relayer has the full 24h to flush pending settlements before the
|
|
113
|
+
exit becomes executable.
|
|
114
|
+
|
|
115
|
+
## Public API
|
|
116
|
+
|
|
117
|
+
### `Forge` (Phase 1 — listings)
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Forge(address=None, chain="arbitrum", w3=None, rpc_url=None,
|
|
121
|
+
account=None, private_key=None)
|
|
122
|
+
|
|
123
|
+
# read
|
|
124
|
+
.total_tools() -> int
|
|
125
|
+
.list_tools(offset=0, limit=100) -> list[bytes32]
|
|
126
|
+
.tools_of(owner) -> list[bytes32]
|
|
127
|
+
.tools_in_category(label_or_bytes32) -> list[bytes32]
|
|
128
|
+
.get_tool(tool_id) -> Tool
|
|
129
|
+
.is_registered(tool_id) -> bool
|
|
130
|
+
.tool_id_of(namespace, name) -> bytes32
|
|
131
|
+
.get_listing_fee_wei() -> int
|
|
132
|
+
.get_claim_admin() -> str
|
|
133
|
+
.get_mor_balance(address=None) -> int
|
|
134
|
+
.get_mor_allowance(owner=None) -> int
|
|
135
|
+
|
|
136
|
+
# write (signer required)
|
|
137
|
+
.register(namespace, name, mcp_uri, categories, price_per_call_wei=0) -> tx
|
|
138
|
+
.update_tool(tool_id, mcp_uri, price_per_call_wei) -> tx
|
|
139
|
+
.set_categories(tool_id, categories) -> tx
|
|
140
|
+
.deactivate(tool_id) -> tx
|
|
141
|
+
.reactivate(tool_id) -> tx
|
|
142
|
+
.transfer_ownership(tool_id, new_owner) -> tx
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `Escrow` (Phase 3 — per-call settlement)
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Escrow(address=None, chain="arbitrum", w3=None, rpc_url=None,
|
|
149
|
+
account=None, private_key=None, relayer_url=None)
|
|
150
|
+
|
|
151
|
+
# read
|
|
152
|
+
.balance(tool_id, agent=None) -> int
|
|
153
|
+
.tool_revenue(tool_id) -> int
|
|
154
|
+
.treasury_revenue() -> int
|
|
155
|
+
.signer_of(tool_id) -> str
|
|
156
|
+
.last_period_id(tool_id, agent) -> int
|
|
157
|
+
.withdraw_unlock_at(tool_id, agent) -> int
|
|
158
|
+
.protocol_fee_bps() -> int
|
|
159
|
+
.relayer_address() -> str
|
|
160
|
+
.claim_admin() -> str
|
|
161
|
+
.quote_settle(tool_id, call_count) -> (total, tool_payout, protocol_fee)
|
|
162
|
+
|
|
163
|
+
# agent-side write
|
|
164
|
+
.deposit(tool_id, amount, auto_approve=True) -> tx
|
|
165
|
+
.agent_request_withdraw(tool_id) -> tx
|
|
166
|
+
.agent_execute_withdraw(tool_id, to=None) -> tx
|
|
167
|
+
|
|
168
|
+
# tool-owner-side write
|
|
169
|
+
.set_signer(tool_id, new_signer) -> tx
|
|
170
|
+
.withdraw_tool(tool_id, to) -> tx
|
|
171
|
+
.sign_receipt(tool_id, agent, call_count, period_id) -> bytes
|
|
172
|
+
.post_receipt(tool_id, agent, call_count, period_id, signature, ...) -> dict
|
|
173
|
+
.call_with_receipt(tool_id, agent, call_count, period_id, ...) -> dict
|
|
174
|
+
|
|
175
|
+
# relayer / claim-admin write
|
|
176
|
+
.settle(tool_id, agent, call_count, period_id, signature) -> tx
|
|
177
|
+
.withdraw_treasury(to) -> tx
|
|
178
|
+
.set_relayer(new_relayer) -> tx
|
|
179
|
+
.set_protocol_fee_bps(new_bps) -> tx
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Plus a pure helper:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
receipt_struct_hash(*, chain_id, escrow_address, tool_id,
|
|
186
|
+
agent, call_count, period_id) -> bytes
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The bytes returned match the on-chain Solidity recovery exactly:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
keccak256(abi.encode(chainid, escrow, toolId, agent, callCount, periodId))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
wrapped with the EIP-191 `\x19Ethereum Signed Message:\n32` prefix when
|
|
196
|
+
signed.
|
|
197
|
+
|
|
198
|
+
## Environment variables
|
|
199
|
+
|
|
200
|
+
| Variable | Purpose |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `HYPNEX_FORGE_CHAIN` | Default chain (`arbitrum` or `base`) |
|
|
203
|
+
| `HYPNEX_FORGE_RPC_URL` | Override RPC URL |
|
|
204
|
+
| `HYPNEX_FORGE_ADDRESS` | Override `ForgeToolRegistry` address |
|
|
205
|
+
| `HYPNEX_FORGE_ESCROW_ADDRESS` | Override `ForgeEscrow` address |
|
|
206
|
+
| `HYPNEX_FORGE_RELAYER_URL` | Override relayer endpoint (default `https://relayer.hypnex.xyz`) |
|
|
207
|
+
| `PRIVATE_KEY` | Required for write methods |
|
|
208
|
+
|
|
209
|
+
## Status
|
|
210
|
+
|
|
211
|
+
- **Phase 1** — paid listings on Arbitrum One + Base mainnet (live since 2026-05-09).
|
|
212
|
+
- **Phase 2** — discovery UI at `forge.hypnex.xyz` (live).
|
|
213
|
+
- **Phase 3** — pre-paid escrow + per-call settlement (`ForgeEscrow`). Live since 2026-05-09:
|
|
214
|
+
- Arbitrum One: [`0x88E32F332A5140Af16c6273d8131bC84BC089Da5`](https://arbiscan.io/address/0x88E32F332A5140Af16c6273d8131bC84BC089Da5#code)
|
|
215
|
+
- Base mainnet: [`0xCC258a7BBF361fd824e87D4b3C0D394De6Fd454F`](https://basescan.org/address/0xCC258a7BBF361fd824e87D4b3C0D394De6Fd454F#code)
|
|
216
|
+
|
|
217
|
+
SDK 0.2.1+ resolves these automatically when you pass `chain="arbitrum"` or `chain="base"`. The Cloudflare Worker relayer at `relayer.hypnex.xyz` is the last operational piece; until it's live, receipts can be submitted directly to `Escrow.settle()` by the configured relayer EOA.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT (Copyright (c) 2026 Hypnex Labs). The MIT-licensed source includes hard-coded fee routers to the Hypnex Labs treasury for both the listing fee and the Phase-3 protocol fee — see "Affiliation & monetization" above.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Make the in-tree SDK importable when running pytest without `pip install -e .`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SRC = Path(__file__).resolve().parent / "src"
|
|
9
|
+
if SRC.is_dir() and str(SRC) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(SRC))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hypnex-forge"
|
|
3
|
+
version = "0.2.1"
|
|
4
|
+
description = "Python SDK for Forge — the paid tool registry on Morpheus AI (MRC 60). Phase 1 listing rail + Phase 3 pre-paid escrow with relayer-attested per-call settlement on Arbitrum + Base."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [{ name = "Hypnex Labs" }]
|
|
9
|
+
keywords = ["morpheus", "mor", "forge", "tools", "marketplace", "mcp", "mrc-60", "hypnex", "web3", "agents"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"web3>=6.15.0,<8.0",
|
|
21
|
+
"eth-account>=0.10.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=9.0.3",
|
|
27
|
+
"ruff>=0.5",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://hypnex.xyz"
|
|
32
|
+
Documentation = "https://docs.hypnex.xyz/forge"
|
|
33
|
+
Repository = "https://github.com/hypnex-labs/hypnex"
|
|
34
|
+
Issues = "https://github.com/hypnex-labs/hypnex/issues"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/hypnex_forge"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py39"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""hypnex-forge — Python SDK for the Forge tool registry on Morpheus AI.
|
|
2
|
+
|
|
3
|
+
Forge is the Hypnex Labs tools marketplace for the Morpheus AI ecosystem
|
|
4
|
+
and the broader MCP (Model-Context-Protocol) world. Each tool listing on
|
|
5
|
+
the on-chain registry pays a one-time fee in MOR to Hypnex Labs.
|
|
6
|
+
|
|
7
|
+
Monetization notice:
|
|
8
|
+
This SDK routes all listing fees from `register()` calls to the Hypnex
|
|
9
|
+
Labs treasury (0x22B5C0075372E743042b2d62b3D254425Eb957D8). The
|
|
10
|
+
listing fee is configurable on-chain by the claim-admin (default 1
|
|
11
|
+
MOR per listing). The on-chain `pricePerCallWei` you set on a tool
|
|
12
|
+
is what END USERS pay you for calls; that revenue stays with the
|
|
13
|
+
tool owner (Phase 1 — Phase 3 will add on-chain per-call settlement
|
|
14
|
+
with a Hypnex protocol fee).
|
|
15
|
+
|
|
16
|
+
Quickstart (read-only):
|
|
17
|
+
|
|
18
|
+
from hypnex_forge import Forge
|
|
19
|
+
|
|
20
|
+
f = Forge(chain="arbitrum")
|
|
21
|
+
print(f.total_tools())
|
|
22
|
+
print(f.list_tools(0, 100))
|
|
23
|
+
print(f.get_listing_fee_wei()) # 10**18 = 1 MOR
|
|
24
|
+
|
|
25
|
+
Quickstart (with signer):
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
from hypnex_forge import Forge
|
|
29
|
+
|
|
30
|
+
f = Forge(
|
|
31
|
+
chain="arbitrum",
|
|
32
|
+
rpc_url=os.environ["ETH_RPC_URL"],
|
|
33
|
+
private_key=os.environ["PRIVATE_KEY"],
|
|
34
|
+
)
|
|
35
|
+
tx = f.register(
|
|
36
|
+
namespace="myorg",
|
|
37
|
+
name="websearch",
|
|
38
|
+
mcp_uri="https://mcp.example.com/v1",
|
|
39
|
+
categories=["search", "data"],
|
|
40
|
+
price_per_call_wei=int(0.001 * 10**18), # users pay 0.001 MOR per call
|
|
41
|
+
)
|
|
42
|
+
# SDK auto-approves the listing fee, then submits register().
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from ._constants import (
|
|
46
|
+
CHAIN_IDS,
|
|
47
|
+
DEFAULT_CHAIN,
|
|
48
|
+
DEFAULT_RELAYER_URL,
|
|
49
|
+
ESCROW_ADDRESSES,
|
|
50
|
+
FORGE_ADDRESSES,
|
|
51
|
+
MOR_ADDRESSES,
|
|
52
|
+
PUBLIC_RPCS,
|
|
53
|
+
)
|
|
54
|
+
from .escrow import Escrow, receipt_struct_hash
|
|
55
|
+
from .forge import Forge, Tool, category_tag, tool_id
|
|
56
|
+
|
|
57
|
+
__version__ = "0.2.1"
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"Forge",
|
|
61
|
+
"Escrow",
|
|
62
|
+
"Tool",
|
|
63
|
+
"category_tag",
|
|
64
|
+
"tool_id",
|
|
65
|
+
"receipt_struct_hash",
|
|
66
|
+
"CHAIN_IDS",
|
|
67
|
+
"DEFAULT_CHAIN",
|
|
68
|
+
"DEFAULT_RELAYER_URL",
|
|
69
|
+
"FORGE_ADDRESSES",
|
|
70
|
+
"ESCROW_ADDRESSES",
|
|
71
|
+
"MOR_ADDRESSES",
|
|
72
|
+
"PUBLIC_RPCS",
|
|
73
|
+
"__version__",
|
|
74
|
+
]
|