dominusnode-moltbook 2.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.
@@ -0,0 +1,28 @@
1
+ """DominusNode v2 tool set for agents on Moltbook (framework-neutral).
2
+
3
+ 2.0.0 removes the old ``MoltbookClient``: it registered Moltbook accounts and
4
+ posted, commented and voted through rotating IPs. None of that remains. What
5
+ is left is the 19 standard DominusNode tools as JSON-schema definitions plus a
6
+ dispatcher, for any agent runtime (including agents active on Moltbook) that
7
+ wants relay fetching, pricing, wallet and node-earning tools::
8
+
9
+ from dominusnode_moltbook import DominusNodeTools, tool_definitions
10
+
11
+ tools = DominusNodeTools() # key from DOMINUSNODE_API_KEY
12
+ defs = tool_definitions() # [{name, description, parameters}, ...]
13
+ tools.call("dominusnode_network_pricing", {})
14
+ """
15
+
16
+ from typing import Any, Dict, List
17
+
18
+ from dominusnode_moltbook._core import PREFIX, TOOL_NAMES, TOOL_SPECS, DominusNodeTools, make_tool_callables
19
+
20
+
21
+ def tool_definitions() -> List[Dict[str, Any]]:
22
+ """The 19 tools as ``{name, description, parameters}`` (JSON Schema), names prefixed ``dominusnode_``."""
23
+ return [{"name": PREFIX + s["name"], "description": s["description"], "parameters": s["parameters"]}
24
+ for s in TOOL_SPECS]
25
+
26
+
27
+ __version__ = "2.2.0"
28
+ __all__ = ["DominusNodeTools", "TOOL_NAMES", "tool_definitions", "make_tool_callables", "__version__"]
@@ -0,0 +1,351 @@
1
+ """DominusNode v2 standard tool set (19 tools), built on the ``dominusnode`` Python SDK.
2
+
3
+ Framework-neutral: each tool has a name, an LLM-facing description and a JSON
4
+ Schema for its arguments. ``DominusNodeTools.call(name, args)`` dispatches to
5
+ the SDK and always returns a JSON string with credentials scrubbed. HTTP,
6
+ SSRF/DNS-rebinding checks, OFAC refusal and signing all live in the SDK.
7
+
8
+ The API key is read only from ``DOMINUSNODE_API_KEY`` (or passed by the host
9
+ application as configuration); it is never a tool argument or tool output.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import threading
17
+ from typing import Any, Callable, Dict, List, Optional
18
+
19
+ from dominusnode import DominusNodeClient, DominusNodeError, scrub_credentials, json_default
20
+
21
+ PREFIX = "dominusnode_"
22
+ MAX_BODY_CHARS = 20_000
23
+ _KEY_PLACEHOLDER = "${DOMINUSNODE_API_KEY}"
24
+
25
+ _COUNTRY = {"type": "string", "description": "ISO 3166-1 alpha-2 exit country, e.g. US. Sanctioned countries are refused."}
26
+ _IP_CLASS = {"type": "string", "enum": ["residential", "dc", "mobile", "masked"],
27
+ "description": "Exit IP class. Each class has its own per-GB price (see network_pricing); masked = cheapest, VPN/anonymizer exits, opt-in."}
28
+ _SESSION = {"type": "string", "description": "Sticky session id (1-64 alphanumeric) to keep the same exit IP."}
29
+ _ALLOW_HTTP = {"type": "boolean",
30
+ "description": "Opt in to plaintext http:// targets. Plaintext is visible to the exit node; prefer https."}
31
+ _LIMIT = {"type": "integer", "minimum": 1, "description": "Maximum number of items."}
32
+ _OFFSET = {"type": "integer", "minimum": 0, "description": "Items to skip."}
33
+
34
+
35
+ #: credits_convert needs a wallet signature; signing flows are not exposed as LLM tools.
36
+ CREDITS_CONVERT_SIGNING_MESSAGE = (
37
+ "Converting credits needs a fresh signature from the wallet linked to this account, so this tool does not convert. Use the DominusNode MCP server (tool dominusnode_credits_convert, with DOMINUSNODE_WALLET_KEY_FILE pointing at a chmod-600 wallet key file) or a DominusNode SDK's credits.convert with a signer key file. Check available credit with credits_balance."
38
+ )
39
+
40
+
41
+ def _obj(props: Optional[Dict[str, Any]] = None, required: Optional[List[str]] = None) -> Dict[str, Any]:
42
+ return {"type": "object", "properties": props or {}, "required": required or [], "additionalProperties": False}
43
+
44
+
45
+ TOOL_SPECS: List[Dict[str, Any]] = [
46
+ {"name": "fetch", "description": (
47
+ "Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects "
48
+ "followed, 10 MB cap, private/internal targets refused). Billed per GB at the live price for the exit IP "
49
+ "class (see network_pricing). http:// targets need allow_http=true and are visible to the exit node."),
50
+ "parameters": _obj({"url": {"type": "string", "description": "https:// URL (or http:// with allow_http)."},
51
+ "method": {"type": "string", "enum": ["GET", "HEAD"], "description": "HTTP method."},
52
+ "country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION,
53
+ "allow_http": _ALLOW_HTTP}, ["url"])},
54
+ {"name": "proxy_url", "description": (
55
+ "Build a relay gateway proxy URL for use in other HTTP clients. Routing flags go in the username "
56
+ "(country-XX, class-..., session-..., allowhttp-1). The password is shown as a ${DOMINUSNODE_API_KEY} "
57
+ "placeholder: substitute the key from the environment, never paste it into chat."),
58
+ "parameters": _obj({"country": _COUNTRY, "ip_class": _IP_CLASS, "session_id": _SESSION, "allow_http": _ALLOW_HTTP,
59
+ "protocol": {"type": "string", "enum": ["https", "socks5", "http"],
60
+ "description": "https (default: TLS to the gateway), socks5, or http (plaintext gateway; local dev only)."}})},
61
+ {"name": "network_policy", "description": (
62
+ "Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries "
63
+ "and node attestation terms."), "parameters": _obj()},
64
+ {"name": "network_pricing", "description": (
65
+ "Live per-GB prices by exit IP class (residential, dc, mobile, masked) and node tier revenue shares. Always use "
66
+ "this for prices; never assume them."), "parameters": _obj()},
67
+ {"name": "network_stats", "description": "Relay network statistics: online nodes, exit countries and IP classes.",
68
+ "parameters": _obj()},
69
+ {"name": "balance", "description": "Prepaid requester wallet balance (cents) used to pay for relayed traffic.",
70
+ "parameters": _obj()},
71
+ {"name": "transactions", "description": "Requester wallet transaction history (top-ups, usage debits, credit conversions).",
72
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
73
+ {"name": "topup_crypto", "description": (
74
+ "Create a crypto invoice to top up the requester wallet ($10-$1,000). Returns a payment address/URL; "
75
+ "the wallet is credited after on-chain confirmation (check with check_payment)."),
76
+ "parameters": _obj({"amount_cents": {"type": "integer", "minimum": 1000, "maximum": 100000,
77
+ "description": "Amount in US cents (1000 = $10)."},
78
+ "currency": {"type": "string", "enum": ["btc", "eth", "ltc", "xmr", "zec", "usdc", "sol",
79
+ "usdt", "dai", "bnb", "link"],
80
+ "description": "Cryptocurrency to pay with."}}, ["amount_cents", "currency"])},
81
+ {"name": "check_payment", "description": "Status of a crypto top-up invoice created by topup_crypto.",
82
+ "parameters": _obj({"invoice_id": {"type": "string", "description": "Invoice UUID."}}, ["invoice_id"])},
83
+ {"name": "usage", "description": "Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range.",
84
+ "parameters": _obj({"from": {"type": "string", "description": "ISO 8601 start."},
85
+ "to": {"type": "string", "description": "ISO 8601 end."}})},
86
+ {"name": "active_sessions", "description": "Your currently active relay sessions (id, start time, status).",
87
+ "parameters": _obj()},
88
+ {"name": "report_session", "description": (
89
+ "Report a burned or bad exit for one of your relay sessions so the network can rotate it."),
90
+ "parameters": _obj({"session_id": {"type": "string", "description": "Session UUID from active_sessions."},
91
+ "reason": {"type": "string", "enum": ["blocked", "captcha", "rate_limited", "slow",
92
+ "bad_content", "other"],
93
+ "description": "Why the exit is bad."}}, ["session_id", "reason"])},
94
+ {"name": "node_list", "description": (
95
+ "Relay nodes you contribute (status, observed IP class, last seen). Contributing IPs earns credit."),
96
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
97
+ {"name": "node_tier", "description": (
98
+ "Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation)."),
99
+ "parameters": _obj()},
100
+ {"name": "node_setup_guide", "description": (
101
+ "Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, "
102
+ "and how tiers and the self-dealing rule work."), "parameters": _obj()},
103
+ {"name": "credits_balance", "description": "Node-operator earnings: pending and available credit (µUSD).",
104
+ "parameters": _obj()},
105
+ {"name": "credits_convert", "description": (
106
+ "Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool."),
107
+ "parameters": _obj({"amount_musd": {"type": "string",
108
+ "description": "Amount in micro-USD as a digit string (min 10000 = $0.01)."}},
109
+ ["amount_musd"])},
110
+ {"name": "payouts_list", "description": "Your USDC payouts of node earnings with status and transaction hash.",
111
+ "parameters": _obj({"limit": _LIMIT, "offset": _OFFSET})},
112
+ {"name": "x402_info", "description": "x402 micropayment info: facilitators, networks and server-reported prices.",
113
+ "parameters": _obj()},
114
+ ]
115
+
116
+ TOOL_NAMES: List[str] = [t["name"] for t in TOOL_SPECS]
117
+ SPECS_BY_NAME: Dict[str, Dict[str, Any]] = {t["name"]: t for t in TOOL_SPECS}
118
+
119
+ NODE_SETUP_GUIDE = "\n".join([
120
+ "DominusNode relay node setup (earn by contributing an IP)",
121
+ "",
122
+ "How earning works",
123
+ " - Your node dials OUT to the gateway (no inbound ports) and relays other agents' traffic.",
124
+ " - You earn your tier's share of each paid GB relayed; live prices and shares: network_pricing.",
125
+ " - Self-dealing earns nothing: your own, your wallet's, team's or /24's traffic never routes to your node.",
126
+ " - Tiers rise with paid tenure, uptime, active IPs and reputation (node_tier).",
127
+ " - Nodes only dial ports 80/443, never private/metadata targets or SMTP/SSH/RDP (network_policy).",
128
+ "",
129
+ "Steps",
130
+ " 1. Store your owner wallet key in a private file (never pass it to a tool or chat):",
131
+ " umask 077 && printf '%s' \"$WALLET_PRIVATE_KEY_HEX\" > ~/.dominusnode/wallet.key",
132
+ " 2. On the machine whose IP you contribute (Docker), register with the published image",
133
+ " (official coordinator pins baked in; a plain source build has none and refuses to start):",
134
+ " sudo install -d -o 1001 -g 1001 -m 700 /etc/dominusnode",
135
+ " sudo install -o 1001 -g 1001 -m 600 ~/.dominusnode/wallet.key /etc/dominusnode/wallet.key",
136
+ " export DN_API_KEY # your dn_live_ key, from the environment only",
137
+ " docker run --rm -e DN_API_KEY -e DN_WALLET_KEY_FILE=/run/dominusnode/wallet.key \\",
138
+ " -v /etc/dominusnode:/run/dominusnode ghcr.io/0xcircuitbreaker/dominusnode-node register",
139
+ " sudo rm /etc/dominusnode/wallet.key # not needed after registration",
140
+ " 3. Run it and keep it running - uptime drives your tier:",
141
+ " docker run -d --name dominusnode-node --restart unless-stopped \\",
142
+ " -v /etc/dominusnode/node.json:/run/dominusnode/node.json:ro ghcr.io/0xcircuitbreaker/dominusnode-node",
143
+ " 4. Verify with node_list and node_tier; earnings appear in credits_balance.",
144
+ "",
145
+ "Signing flows (node register, withdraw, payout wallet) are not available as tools here; use the",
146
+ "node agent CLI, the DominusNode MCP server or the SDK with a signer key file.",
147
+ ])
148
+
149
+
150
+ #: Gateway TLS (HTTPS proxy) port: production 8443; self-hosted coordinators use 8443 too.
151
+ DEFAULT_TLS_PROXY_PORT = 8443
152
+
153
+
154
+ def resolve_gateway(
155
+ proxy_host: Optional[str] = None,
156
+ tls_port: Optional[int] = None,
157
+ http_port: Optional[int] = None,
158
+ insecure: Optional[bool] = None,
159
+ env: Optional[Any] = None,
160
+ ) -> Dict[str, Any]:
161
+ """Where proxy traffic goes. TLS to the gateway's HTTPS proxy is the default: production
162
+ gateways refuse credentials on plaintext 8080/1080. ``DOMINUSNODE_PROXY_URL=https://host[:port]``
163
+ sets host and TLS port (same variable as the MCP server), ``DOMINUSNODE_PROXY_TLS_PORT`` the TLS
164
+ port, and ``DOMINUSNODE_PROXY_INSECURE=1`` selects plaintext (local development only).
165
+
166
+ Returns ``{"proxy_host", "http_port", "gateway_tls"}`` where ``gateway_tls`` is ``False`` or
167
+ ``{"port": n}``, ready for ``DominusNodeClient(gateway_tls=...)``.
168
+ """
169
+ from urllib.parse import urlsplit
170
+
171
+ e = os.environ if env is None else env
172
+
173
+ def _port(v: Any) -> Optional[int]:
174
+ try:
175
+ n = int(v)
176
+ except (TypeError, ValueError):
177
+ return None
178
+ return n if 1 <= n <= 65535 else None
179
+
180
+ url_host: Optional[str] = None
181
+ url_port: Optional[int] = None
182
+ raw = (e.get("DOMINUSNODE_PROXY_URL") or "").strip()
183
+ if raw:
184
+ u = urlsplit(raw)
185
+ if u.scheme != "https" or not u.hostname:
186
+ raise ValueError("DOMINUSNODE_PROXY_URL must be https://host[:port] (TLS to the gateway)")
187
+ if u.username or u.password:
188
+ raise ValueError("DOMINUSNODE_PROXY_URL must not contain credentials")
189
+ url_host = u.hostname.lower()
190
+ try:
191
+ url_port = _port(u.port) if u.port else DEFAULT_TLS_PROXY_PORT
192
+ except ValueError:
193
+ raise ValueError("DOMINUSNODE_PROXY_URL has an invalid port") from None
194
+ if insecure is None:
195
+ insecure = (e.get("DOMINUSNODE_PROXY_INSECURE") or "").strip() == "1"
196
+ if insecure and raw:
197
+ raise ValueError("DOMINUSNODE_PROXY_URL (TLS) and DOMINUSNODE_PROXY_INSECURE=1 are mutually exclusive")
198
+ tls = _port(tls_port) or url_port or _port(e.get("DOMINUSNODE_PROXY_TLS_PORT")) or DEFAULT_TLS_PROXY_PORT
199
+ return {
200
+ "proxy_host": proxy_host or url_host or (e.get("DOMINUSNODE_PROXY_HOST") or "").strip() or None,
201
+ "http_port": _port(http_port) or _port(e.get("DOMINUSNODE_PROXY_PORT")),
202
+ "gateway_tls": False if insecure else {"port": tls},
203
+ }
204
+
205
+
206
+ def _scrub(text: str, key: Optional[str]) -> str:
207
+ out = scrub_credentials(text)
208
+ if key:
209
+ out = out.replace(key, "[REDACTED]")
210
+ return out
211
+
212
+
213
+ def _opt_int(args: Dict[str, Any], name: str) -> Optional[int]:
214
+ v = args.get(name)
215
+ if v is None:
216
+ return None
217
+ if isinstance(v, bool):
218
+ raise ValueError(f"{name} must be an integer")
219
+ return int(v)
220
+
221
+
222
+ class DominusNodeTools:
223
+ """Dispatches the 19 standard tools to a connected ``DominusNodeClient``."""
224
+
225
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None,
226
+ proxy_host: Optional[str] = None, client: Optional[Any] = None,
227
+ tls_proxy_port: Optional[int] = None, insecure_proxy: Optional[bool] = None) -> None:
228
+ self._api_key = api_key or os.environ.get("DOMINUSNODE_API_KEY") or None
229
+ self._base_url = base_url or os.environ.get("DOMINUSNODE_BASE_URL") or None
230
+ self._gateway = resolve_gateway(proxy_host, tls_proxy_port, None, insecure_proxy)
231
+ self._proxy_host = self._gateway["proxy_host"]
232
+ self._client = client
233
+ self._connected = client is not None
234
+ self._lock = threading.Lock()
235
+
236
+ def __repr__(self) -> str:
237
+ return "DominusNodeTools(api_key=[REDACTED])"
238
+
239
+ __str__ = __repr__
240
+
241
+ def _sdk(self) -> Any:
242
+ with self._lock:
243
+ if self._client is None:
244
+ kw: Dict[str, Any] = {}
245
+ if self._base_url:
246
+ kw["base_url"] = self._base_url
247
+ if self._proxy_host:
248
+ kw["proxy_host"] = self._proxy_host
249
+ if self._gateway["http_port"]:
250
+ kw["http_proxy_port"] = self._gateway["http_port"]
251
+ kw["gateway_tls"] = self._gateway["gateway_tls"]
252
+ self._client = DominusNodeClient(**kw)
253
+ if not self._connected:
254
+ if not self._api_key:
255
+ raise DominusNodeError("DOMINUSNODE_API_KEY is not set")
256
+ self._client.connect_with_key(self._api_key)
257
+ self._connected = True
258
+ return self._client
259
+
260
+ # ---- tool implementations -------------------------------------------------
261
+ def _fetch(self, a: Dict[str, Any]) -> Any:
262
+ r = self._sdk().proxy.fetch(
263
+ str(a["url"]), method=str(a.get("method") or "GET").upper(), country=a.get("country"),
264
+ ip_class=a.get("ip_class"), session_id=a.get("session_id"), allow_http=bool(a.get("allow_http", False)))
265
+ body = r.text()
266
+ cut = len(body) > MAX_BODY_CHARS
267
+ return {"status": r.status, "statusText": r.status_text,
268
+ "contentType": (r.headers or {}).get("content-type"),
269
+ "location": (r.headers or {}).get("location"),
270
+ "truncated": bool(r.truncated or cut), "body": body[:MAX_BODY_CHARS]}
271
+
272
+ def _proxy_url(self, a: Dict[str, Any]) -> Any:
273
+ c = self._sdk()
274
+ url = c.proxy.build_url(country=a.get("country"), ip_class=a.get("ip_class"), session_id=a.get("session_id"),
275
+ allow_http=bool(a.get("allow_http", False)), protocol=a.get("protocol") or None)
276
+ user = url.split("://", 1)[1].split(":", 1)[0]
277
+ prefix, rest = url.split("@", 1)[0], url.split("@", 1)[1]
278
+ scheme = prefix.split("://", 1)[0]
279
+ return {"proxyUrl": f"{scheme}://{user}:{_KEY_PLACEHOLDER}@{rest}", "username": user,
280
+ "note": "Replace the placeholder with DOMINUSNODE_API_KEY from your environment."}
281
+
282
+ def _dispatch(self, name: str, a: Dict[str, Any]) -> Any:
283
+ if name == "fetch":
284
+ return self._fetch(a)
285
+ if name == "proxy_url":
286
+ return self._proxy_url(a)
287
+ if name == "node_setup_guide":
288
+ return {"guide": NODE_SETUP_GUIDE}
289
+ if name == "credits_convert":
290
+ return {"status": "signing_required", "message": CREDITS_CONVERT_SIGNING_MESSAGE}
291
+ c = self._sdk()
292
+ if name == "network_policy":
293
+ return c.network.policy()
294
+ if name == "network_pricing":
295
+ return c.network.pricing()
296
+ if name == "network_stats":
297
+ return c.network.stats()
298
+ if name == "balance":
299
+ return c.wallet.get_balance()
300
+ if name == "transactions":
301
+ return c.wallet.get_transactions(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
302
+ if name == "topup_crypto":
303
+ return c.wallet.top_up_crypto(int(a["amount_cents"]), str(a["currency"]))
304
+ if name == "check_payment":
305
+ return c.wallet.get_crypto_status(str(a["invoice_id"]))
306
+ if name == "usage":
307
+ return c.usage.get(from_=a.get("from"), to=a.get("to"))
308
+ if name == "active_sessions":
309
+ return c.sessions.get_active()
310
+ if name == "report_session":
311
+ return c.sessions.report(str(a["session_id"]), str(a["reason"]))
312
+ if name == "node_list":
313
+ return c.nodes.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
314
+ if name == "node_tier":
315
+ return c.nodes.tier()
316
+ if name == "credits_balance":
317
+ return c.credits.balance()
318
+ if name == "payouts_list":
319
+ return c.payouts.list(limit=_opt_int(a, "limit"), offset=_opt_int(a, "offset"))
320
+ if name == "x402_info":
321
+ return c.x402.get_info()
322
+ raise ValueError(f"unknown tool: {name}")
323
+
324
+ def call(self, name: str, args: Optional[Dict[str, Any]] = None) -> str:
325
+ """Run a tool by its standard name (prefix optional). Always returns a scrubbed JSON string."""
326
+ if name.startswith(PREFIX):
327
+ name = name[len(PREFIX):]
328
+ args = {k: v for k, v in (args or {}).items() if v is not None}
329
+ try:
330
+ if name not in SPECS_BY_NAME:
331
+ raise ValueError(f"unknown tool: {name}")
332
+ allowed = SPECS_BY_NAME[name]["parameters"]["properties"]
333
+ unknown = [k for k in args if k not in allowed]
334
+ if unknown:
335
+ raise ValueError(f"unexpected argument(s): {', '.join(sorted(unknown))}")
336
+ out = json.dumps(self._dispatch(name, args), default=json_default)
337
+ except Exception as err: # noqa: BLE001 - every error is returned to the model, scrubbed
338
+ msg = f"{type(err).__name__}: {err}" if not isinstance(err, DominusNodeError) else str(err)
339
+ out = json.dumps({"error": msg})
340
+ return _scrub(out, self._api_key)
341
+
342
+
343
+ def make_tool_callables(tools: DominusNodeTools) -> Dict[str, Callable[..., str]]:
344
+ """``{prefixed_name: fn(**kwargs) -> str}`` for frameworks that take plain functions."""
345
+ def mk(n: str) -> Callable[..., str]:
346
+ def fn(**kwargs: Any) -> str:
347
+ return tools.call(n, kwargs)
348
+ fn.__name__ = PREFIX + n
349
+ fn.__doc__ = SPECS_BY_NAME[n]["description"]
350
+ return fn
351
+ return {PREFIX + n: mk(n) for n in TOOL_NAMES}
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: dominusnode-moltbook
3
+ Version: 2.2.0
4
+ Summary: DominusNode v2 standard tool set (19 relay-network tools) for agent runtimes, built on the dominusnode SDK
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/integrations/moltbook
7
+ Project-URL: Repository, https://github.com/0xcircuitbreaker/DomiNode
8
+ Project-URL: Issues, https://github.com/0xcircuitbreaker/DomiNode/issues
9
+ Project-URL: Changelog, https://github.com/0xcircuitbreaker/DomiNode/blob/master/integrations/moltbook/CHANGELOG.md
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dominusnode<3,>=2.2.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.0; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # dominusnode-moltbook
19
+
20
+ Gives agents the DominusNode AI-agent relay network: fetch public web pages through exit IPs contributed by
21
+ other agents' nodes, pay per GB from a prepaid crypto wallet, and earn by contributing your own IP with the
22
+ node agent. Built on the official DominusNode SDK v2; HTTP, SSRF/DNS-rebinding protection, OFAC refusal and
23
+ credential scrubbing all come from the SDK.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install dominusnode-moltbook
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ | Variable | Required | Purpose |
34
+ |---|---|---|
35
+ | `DOMINUSNODE_API_KEY` | yes | Your `dn_live_` API key. Read from the environment/config only; never a tool argument, never in tool output. |
36
+ | `DOMINUSNODE_BASE_URL` | no | API base URL (default `https://api.dominusnode.com`). |
37
+ | `DOMINUSNODE_PROXY_HOST` | no | Gateway host (default `proxy.dominusnode.com`). |
38
+ | `DOMINUSNODE_PROXY_URL` | no | `https://host[:port]` of the gateway's TLS proxy (default port 8443; self-hosted coordinators use 8443 too). |
39
+ | `DOMINUSNODE_PROXY_INSECURE` | no | `1` = plaintext gateway on 8080 (local development only; production refuses it). |
40
+
41
+ ## Usage
42
+
43
+ > **2.0.0 scope change.** Versions 1.x shipped a `MoltbookClient` that registered Moltbook accounts and created
44
+ > posts, comments, submolts and votes, routing each request through a different rotating IP. That code is
45
+ > removed and will not return: this package no longer talks to the Moltbook API, registers accounts, signs
46
+ > with wallet keys or posts anything. Agents can still read public pages with `dominusnode_fetch`.
47
+
48
+ The package is a framework-neutral tool surface: JSON-schema definitions plus a dispatcher.
49
+
50
+ ```python
51
+ from dominusnode_moltbook import DominusNodeTools, tool_definitions
52
+
53
+ tools = DominusNodeTools() # key from DOMINUSNODE_API_KEY
54
+ for d in tool_definitions(): # hand these to your agent runtime's tool registry
55
+ print(d["name"], d["parameters"])
56
+ print(tools.call("dominusnode_network_pricing", {}))
57
+ ```
58
+
59
+ `make_tool_callables(tools)` returns `{name: fn(**kwargs) -> str}` for runtimes that take plain functions.
60
+ Every result is a JSON string; errors are `{"error": "..."}` with credentials scrubbed.
61
+
62
+ ## Tools (v2 standard set, 19)
63
+
64
+ | # | Tool | What it does |
65
+ |---|---|---|
66
+ | 1 | `dominusnode_fetch` | Fetch a public URL through the DominusNode relay network (GET or HEAD only, ports 80/443, no redirects followed, 10 MB cap, private/internal targets refused). |
67
+ | 2 | `dominusnode_proxy_url` | Build a relay gateway proxy URL for use in other HTTP clients. |
68
+ | 3 | `dominusnode_network_policy` | Relay network policy: allowed ports, plaintext (allowhttp) rules, blocked ranges, sanctioned countries and node attestation terms. |
69
+ | 4 | `dominusnode_network_pricing` | Live per-GB prices by exit IP class (residential, dc, mobile) and node tier revenue shares. |
70
+ | 5 | `dominusnode_network_stats` | Relay network statistics: online nodes, exit countries and IP classes. |
71
+ | 6 | `dominusnode_balance` | Prepaid requester wallet balance (cents) used to pay for relayed traffic. |
72
+ | 7 | `dominusnode_transactions` | Requester wallet transaction history (top-ups, usage debits, credit conversions). |
73
+ | 8 | `dominusnode_topup_crypto` | Create a crypto invoice to top up the requester wallet ($10-$1,000). |
74
+ | 9 | `dominusnode_check_payment` | Status of a crypto top-up invoice created by topup_crypto. |
75
+ | 10 | `dominusnode_usage` | Relayed traffic usage summary (bytes and cost) for an optional ISO 8601 date range. |
76
+ | 11 | `dominusnode_active_sessions` | Your currently active relay sessions (id, start time, status). |
77
+ | 12 | `dominusnode_report_session` | Report a burned or bad exit for one of your relay sessions so the network can rotate it. |
78
+ | 13 | `dominusnode_node_list` | Relay nodes you contribute (status, observed IP class, last seen). |
79
+ | 14 | `dominusnode_node_tier` | Your node-operator tier, revenue share and progress toward the next tier (tenure, uptime, active IPs, reputation). |
80
+ | 15 | `dominusnode_node_setup_guide` | Step-by-step instructions to earn by contributing an IP: run the DominusNode node agent, register a node, and how tiers and the self-dealing rule work. |
81
+ | 16 | `dominusnode_credits_balance` | Node-operator earnings: pending and available credit (µUSD). |
82
+ | 17 | `dominusnode_credits_convert` | Explains how to convert node credits into wallet balance. Conversion needs a wallet signature, so it runs through the DominusNode MCP server or SDK with a signer key file, not through this tool. |
83
+ | 18 | `dominusnode_payouts_list` | Your USDC payouts of node earnings with status and transaction hash. |
84
+ | 19 | `dominusnode_x402_info` | x402 micropayment info: facilitators, networks and server-reported prices. |
85
+
86
+ Notes for agents:
87
+
88
+ - Prices are never hardcoded: call `network_pricing` for the live per-GB price by IP class (residential, dc, mobile).
89
+ - `fetch` is GET/HEAD only on ports 80/443, follows no redirects and caps bodies at 10 MB. Plaintext `http://`
90
+ needs `allow_http` and is visible to the exit node.
91
+ - Routing: `country` (ISO code; sanctioned countries refused), `ip_class`, `session_id` (sticky exit).
92
+ - Signing flows (node register, withdraw, payout wallet) are deliberately not tools here; use the node agent CLI,
93
+ the DominusNode MCP server, or the SDK with a signer key file.
94
+
95
+ ## Testing
96
+
97
+ ```bash
98
+ pip install -e ../../sdks/python -e '.[dev]' # or npm install for the JS part
99
+ python -m pytest -q
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,7 @@
1
+ dominusnode_moltbook/__init__.py,sha256=ouSq50xrgAJ3-nAU_PI1wG2bMjy5tp0QR6Hg4z31WvY,1301
2
+ dominusnode_moltbook/_core.py,sha256=HrGhV0kcDUJIDDDxjdCRUte8ve4RU9GSdWR0n5Z7-FA,20101
3
+ dominusnode_moltbook-2.2.0.dist-info/licenses/LICENSE,sha256=Mm2J1HyFPxXtVvOQ0qhlZpESwW2Ct-oKRW0XMcISMi8,1069
4
+ dominusnode_moltbook-2.2.0.dist-info/METADATA,sha256=tjuH53dlOB41XdCr_KWz4XJCZCc0vazn1BjY03UpWHI,6220
5
+ dominusnode_moltbook-2.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ dominusnode_moltbook-2.2.0.dist-info/top_level.txt,sha256=lQLyiqGrh9PEnf_O3XwLqBW6MSuDCZNBh349AzrzNfQ,21
7
+ dominusnode_moltbook-2.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominus Node
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 @@
1
+ dominusnode_moltbook