Skip to content

hypnex-forge

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).

bash
pip install hypnex-forge
# or
npm install hypnex-forge

Status

ComponentStatusWhere
ForgeToolRegistry (Phase 1)Live since 2026-05-09Arb + Base mainnet
forge.hypnex.xyz UI (Phase 2)Liveforge.hypnex.xyz ↗
ForgeEscrow (Phase 3)Live since 2026-05-09Arb + Base mainnet (verified)
relayer.hypnex.xyz (Phase 3)LiveSee apps/relayer/

Deployed addresses

ContractArbitrum OneBase mainnet
ForgeToolRegistry (Phase 1)0xa08ca3…013D20x0c617b…45aC
ForgeEscrow (Phase 3)0x88E32F…89Da50xCC258a…454F

Per-chain MOR token addresses are baked into MOR_ADDRESSES and used as the fee token by both contracts.

Phase 1 — list a tool

A one-time 1 MOR fee (admin-settable) routes directly to Hypnex Labs in the same register() tx. Per-call revenue stays with the tool owner; with Phase 3 on, settlement adds a small protocol fee.

python
from hypnex_forge import Forge

f = Forge(chain="arbitrum", private_key="0x...")

tx = f.register(
    namespace="myorg",
    name="websearch",
    mcp_uri="https://mcp.example.com/v1",
    categories=["search", "data"],
    price_per_call_wei=10**15,         # 0.001 MOR/call (end users pay you)
)
ts
import { Forge } from "hypnex-forge";

const f = Forge.withPrivateKey(process.env.PK as `0x${string}`, { chain: "arbitrum" });

await f.register({
  namespace: "myorg",
  name: "websearch",
  mcpURI: "https://mcp.example.com/v1",
  categories: ["search", "data"],
  pricePerCallWei: 10n ** 15n,
});

Phase 3 — pre-paid escrow + per-call settlement

Three actors, four phases:

agent ──deposit──▶ ForgeEscrow ◀──signature── tool owner
                       ▲                            ▲
                       │                            │
              relayer.hypnex.xyz ◀────POST /receipt─┘

                       └─── settle() every 4h ──▶ chain

                                                  ├──▶ 90% to tool owner
                                                  └──▶ 10% to Hypnex treasury

The protocol fee is 10% at launch, hard-capped at 20% in the contract bytecode — admin cannot rug. Each receipt is signed off-chain over:

keccak256(abi.encode(chainid, escrow, toolId, agent, callCount, periodId))

wrapped with EIP-191. The contract recovers the signer using the same encoding and rejects mismatches.

Tool owner — register a signer

python
from hypnex_forge import Escrow, tool_id

esc = Escrow(chain="arbitrum", private_key=os.environ["TOOL_OWNER_PK"])
esc.set_signer(tool_id("myorg", "websearch"), "0xYourSessionKeyOrEoa")

Agent — pre-fund per-tool escrow

Auto-approves MOR allowance on first call:

python
esc = Escrow(chain="arbitrum", private_key=os.environ["AGENT_PK"])
esc.deposit(tool_id("myorg", "websearch"), 10 * 10**18)   # 10 MOR runway

Tool server — sign + post a receipt

After every N calls, the tool's authorized signer key signs a receipt and posts it to the relayer:

python
esc.call_with_receipt(
    tool_id=tool_id("myorg", "websearch"),
    agent="0xAgentAddress",
    call_count=100,           # cumulative calls in this period
    period_id=42,             # strictly increasing per (toolId, agent)
)

The Hypnex relayer batches receipts and submits settle() on a 4-hourly cron. Each settle() debits callCount * pricePerCallWei from the agent's escrow and routes 90 / 10 to the tool owner / treasury.

Writing your own (non-Hypnex) receipt client

The official SDKs handle this for you. If you POST to relayer.hypnex.xyz/receipt from your own client, you must send a User-Agent header — Cloudflare's default WAF rejects generic Python-urllib/X.Y and Node fetch UAs with HTTP 403 + error code: 1010 before the request ever reaches the Worker. Set anything reasonable, e.g. User-Agent: my-tool-server/1.0 (+https://example.com).

Receipt format (signed by the tool's setSigner key):

keccak256(abi.encode(chainid, escrow, toolId, agent, callCount, periodId))

wrapped in the EIP-191 \x19Ethereum Signed Message:\n32 prefix. POST body schema:

json
{
  "chain": "arbitrum" | "base",
  "escrow": "0x...",
  "toolId": "0x...",
  "agent": "0x...",
  "callCount": "100",
  "periodId": "42",
  "signature": "0x..."
}

Tool owner — withdraw accrued revenue

python
print(esc.tool_revenue(tid) / 1e18, "MOR pending")
esc.withdraw_tool(tid, "0xColdWallet")

Agent — 24h-delayed self-custody exit

If a tool owner misbehaves or you want to stop using a tool, request a delayed exit. The relayer has 24h to flush pending settlements before you can pull whatever's left:

python
esc.agent_request_withdraw(tid)        # opens the 24h window
# ... 24h later ...
esc.agent_execute_withdraw(tid, "0xAgentAddress")

Pure helpers (no RPC)

Compute IDs off-chain — they match the on-chain encoding 1:1:

python
from hypnex_forge import tool_id, category_tag, receipt_struct_hash

tool_id("hypnex", "websearch")        # bytes32 keccak256("hypnex/websearch")
category_tag("search")                # bytes32 keccak256("search")

# Phase 3 receipt hash (pre-EIP-191):
receipt_struct_hash(
    chain_id=42161,
    escrow_address="0x...",
    tool_id=tool_id("myorg", "websearch"),
    agent="0xAgent",
    call_count=100,
    period_id=42,
)
ts
import { toolId, categoryTag, receiptStructHash } from "hypnex-forge";

receiptStructHash({
  chainId: 42161,
  escrow: "0x...",
  toolId: toolId("myorg", "websearch"),
  agent: "0xAgent",
  callCount: 100n,
  periodId: 42n,
});

The TypeScript and Python helpers produce byte-identical output to the Solidity keccak256(abi.encode(...)) recovery — verified by 29 Foundry tests + 7 anvil-based Python E2E tests + 26 worker tests.

Discovery

python
# All tools owned by a wallet
f.tools_of("0x...")

# All tools in a category
f.tools_in_category("search")

# Pagination over all tools (cheap)
f.list_tools(offset=0, limit=50)

Source

Released under the MIT License.