dominusnode 2.0.0__tar.gz → 2.0.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.
Files changed (29) hide show
  1. {dominusnode-2.0.0 → dominusnode-2.0.1}/CHANGELOG.md +25 -0
  2. {dominusnode-2.0.0/dominusnode.egg-info → dominusnode-2.0.1}/PKG-INFO +19 -1
  3. {dominusnode-2.0.0 → dominusnode-2.0.1}/README.md +18 -0
  4. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/constants.py +1 -1
  5. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/http_client.py +11 -3
  6. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/resources.py +132 -8
  7. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/types.py +49 -0
  8. {dominusnode-2.0.0 → dominusnode-2.0.1/dominusnode.egg-info}/PKG-INFO +19 -1
  9. {dominusnode-2.0.0 → dominusnode-2.0.1}/pyproject.toml +1 -1
  10. {dominusnode-2.0.0 → dominusnode-2.0.1}/LICENSE +0 -0
  11. {dominusnode-2.0.0 → dominusnode-2.0.1}/MANIFEST.in +0 -0
  12. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/__init__.py +0 -0
  13. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/_ops.py +0 -0
  14. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/client.py +0 -0
  15. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/errors.py +0 -0
  16. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/messages.py +0 -0
  17. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/pow.py +0 -0
  18. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/proxy_fetch.py +0 -0
  19. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/py.typed +0 -0
  20. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/secret.py +0 -0
  21. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/signer.py +0 -0
  22. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/ssrf.py +0 -0
  23. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/token_manager.py +0 -0
  24. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode/validation.py +0 -0
  25. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode.egg-info/SOURCES.txt +0 -0
  26. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode.egg-info/dependency_links.txt +0 -0
  27. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode.egg-info/requires.txt +0 -0
  28. {dominusnode-2.0.0 → dominusnode-2.0.1}/dominusnode.egg-info/top_level.txt +0 -0
  29. {dominusnode-2.0.0 → dominusnode-2.0.1}/setup.cfg +0 -0
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.0.1] - 2026-11-12
9
+
10
+ No breaking changes: every 2.0.0 public name and type still exists.
11
+
12
+ ### Fixed
13
+ - Team keys read the server's `prefix` field (falls back to `keyPrefix`); key lists
14
+ expose `userId`, `teamId` and `revokedAt`.
15
+ - Proxy health returns `{ status }` only, matching the server.
16
+ - Team invites carry `invitedBy`.
17
+ - Credits cursor validation matches the server (`^[1-9][0-9]{0,18}$`).
18
+ - 409 responses (e.g. `REPORT_LIMIT`) are never retried and surface with their `code`;
19
+ 429 is retried at most once.
20
+ - A malformed `Content-Length` response header raises `ServerError` instead of being ignored.
21
+
22
+ ### Added
23
+ - `buyerDiscountBps` on credits balance and tier views, `buyerDiscounts` on pricing,
24
+ `seed`/`community` blocks on network stats.
25
+ - Payout wallet `chain` accepts `base-sepolia` as an explicit opt-in (server decides).
26
+ - Pagination helpers for credit transactions, payouts, nodes, usage records and
27
+ wallet transactions.
28
+ - README: PoW/client-IP note and a troubleshooting table.
29
+
30
+ ### Deprecated
31
+ - The `keyPrefix` key in team-key results: use `prefix` (same value).
32
+
8
33
  ## [2.0.0] - 2026-11-06
9
34
 
10
35
  Breaking: rewritten for the v2 relay-network contract (`sdks/SDK_SURFACE.md`, reference `sdks/node` 2.0.0).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Official DominusNode SDK for Python: AI-agent relay network (proxy, nodes, credits, payouts)
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/sdks/python
@@ -175,3 +175,21 @@ pytest
175
175
 
176
176
  `tests/fixtures/signed_messages.json` holds the three signed messages as produced by the Node
177
177
  SDK builders. `tests/test_signers.py` checks the Python builders against it byte for byte.
178
+
179
+ ## Node registration and the client IP
180
+
181
+ The node-registration proof-of-work is bound to the client IP that requested it.
182
+ Call the register-challenge and register methods from the same egress (same machine,
183
+ same outbound IP, no proxy or VPN switch in between); otherwise the server rejects
184
+ the solution with `INVALID_CHALLENGE`. Register from the host that will run the node.
185
+
186
+ ## Troubleshooting
187
+
188
+ | Symptom | Meaning | What to do |
189
+ |---|---|---|
190
+ | HTTP 402 | API call: requester or agentic wallet balance is too low | Top up with crypto or fund the agentic wallet |
191
+ | HTTP 407 (proxy) | Gateway rejected the proxy credentials | Check the API key in the proxy password and the username flags (`country-XX-class-dc-...`, or `relay`) |
192
+ | `EMAIL_NOT_VERIFIED` | The route needs a verified account | Verify the account before financial calls |
193
+ | `NO_NODES_AVAILABLE` | No eligible exit node for the requested country/class | Relax the country/class flags or retry later; check network stats |
194
+ | `PAYOUTS_DISABLED` | Withdrawals are switched off server-side | Keep earning; the credits balance `payoutsEnabled` field shows the state |
195
+ | `REPORT_LIMIT` | Per-node daily session-report cap reached (HTTP 409 conflict error; older servers 429) | Do not retry today; the SDK never retries it |
@@ -157,3 +157,21 @@ pytest
157
157
 
158
158
  `tests/fixtures/signed_messages.json` holds the three signed messages as produced by the Node
159
159
  SDK builders. `tests/test_signers.py` checks the Python builders against it byte for byte.
160
+
161
+ ## Node registration and the client IP
162
+
163
+ The node-registration proof-of-work is bound to the client IP that requested it.
164
+ Call the register-challenge and register methods from the same egress (same machine,
165
+ same outbound IP, no proxy or VPN switch in between); otherwise the server rejects
166
+ the solution with `INVALID_CHALLENGE`. Register from the host that will run the node.
167
+
168
+ ## Troubleshooting
169
+
170
+ | Symptom | Meaning | What to do |
171
+ |---|---|---|
172
+ | HTTP 402 | API call: requester or agentic wallet balance is too low | Top up with crypto or fund the agentic wallet |
173
+ | HTTP 407 (proxy) | Gateway rejected the proxy credentials | Check the API key in the proxy password and the username flags (`country-XX-class-dc-...`, or `relay`) |
174
+ | `EMAIL_NOT_VERIFIED` | The route needs a verified account | Verify the account before financial calls |
175
+ | `NO_NODES_AVAILABLE` | No eligible exit node for the requested country/class | Relax the country/class flags or retry later; check network stats |
176
+ | `PAYOUTS_DISABLED` | Withdrawals are switched off server-side | Keep earning; the credits balance `payoutsEnabled` field shows the state |
177
+ | `REPORT_LIMIT` | Per-node daily session-report cap reached (HTTP 409 conflict error; older servers 429) | Do not retry today; the SDK never retries it |
@@ -1,6 +1,6 @@
1
1
  """SDK-wide constants (mirrors sdks/node/src/constants.ts)."""
2
2
 
3
- SDK_VERSION = "2.0.0"
3
+ SDK_VERSION = "2.0.1"
4
4
  USER_AGENT = f"dominusnode-sdk-python/{SDK_VERSION}"
5
5
  DEFAULT_BASE_URL = "https://api.dominusnode.daedalusdevelopmentgroup.com"
6
6
  DEFAULT_PROXY_HOST = "proxy.dominusnode.daedalusdevelopmentgroup.com"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import json
7
7
  import random
8
+ import re
8
9
  import time
9
10
  from dataclasses import dataclass, field
10
11
  from typing import Any, Callable, Dict, Optional, Union
@@ -157,11 +158,18 @@ def _content(req: Request) -> Optional[bytes]:
157
158
  return None if req.body is _NO_BODY else json.dumps(req.body).encode("utf-8")
158
159
 
159
160
 
161
+ _CONTENT_LENGTH_RE = re.compile(r"^[0-9]{1,19}\Z")
162
+
163
+
160
164
  def _too_large_by_header(res: httpx.Response) -> bool:
161
- try:
162
- return int(res.headers.get("content-length", "0")) > MAX_RESPONSE_BYTES
163
- except ValueError:
165
+ """True when Content-Length exceeds the cap; a malformed value raises ServerError."""
166
+ raw = res.headers.get("content-length")
167
+ if raw is None:
164
168
  return False
169
+ value = raw.strip()
170
+ if not _CONTENT_LENGTH_RE.match(value):
171
+ raise ServerError("Malformed Content-Length header in response", res.status_code)
172
+ return int(value) > MAX_RESPONSE_BYTES
165
173
 
166
174
 
167
175
  def _finish(req: Request, res: httpx.Response, data: bytes) -> Any:
@@ -7,7 +7,7 @@ Results are the server's JSON as dicts (camelCase keys, as on the wire); see typ
7
7
  from __future__ import annotations
8
8
 
9
9
  import re
10
- from typing import Any, Dict, List, Mapping, Optional
10
+ from typing import Any, AsyncIterator, Dict, Iterator, List, Mapping, Optional
11
11
 
12
12
  from ._ops import Call, Gen, make_variants
13
13
  from .constants import DEFAULT_HTTP_PROXY_PORT, DEFAULT_PROXY_HOST, DEFAULT_SOCKS5_PROXY_PORT, DEFAULT_TLS_PROXY_PORT
@@ -254,6 +254,24 @@ class _AgenticWalletResource(_Base):
254
254
 
255
255
 
256
256
  # ---------------------------------------------------------------- 3.6 teams
257
+ def _key_prefix(r: Any) -> str:
258
+ """Server sends ``prefix``; older servers sent ``keyPrefix``."""
259
+ if not isinstance(r, dict):
260
+ return ""
261
+ p = r.get("prefix", r.get("keyPrefix"))
262
+ return p if isinstance(p, str) else ""
263
+
264
+
265
+ def _team_key(k: Dict[str, Any]) -> Dict[str, Any]:
266
+ prefix = _key_prefix(k)
267
+ revoked = k.get("revokedAt")
268
+ return {
269
+ "id": k.get("id"), "prefix": prefix, "keyPrefix": prefix, "label": k.get("label"),
270
+ "userId": k.get("userId"), "teamId": k.get("teamId"), "createdAt": k.get("createdAt"),
271
+ "revokedAt": revoked if isinstance(revoked, str) else None,
272
+ }
273
+
274
+
257
275
  class _TeamsResource(_Base):
258
276
  def _t(self, team_id: str) -> str:
259
277
  return f"/api/teams/{_id(team_id, 'team_id')}"
@@ -321,13 +339,17 @@ class _TeamsResource(_Base):
321
339
  return (yield _post(f"/api/teams/invites/{_id(token, 'token')}/accept", {}))
322
340
 
323
341
  def create_key(self, team_id: str, label: str) -> Gen:
324
- """POST /{teamId}/keys -> {id, keyPrefix, label, key: Secret}. Team keys cannot call /v1 routes."""
342
+ """POST /{teamId}/keys -> {id, prefix, keyPrefix (alias), label, key: Secret}. Team keys cannot call /v1 routes."""
325
343
  validate_non_empty(label, "label", 100)
326
344
  r = yield _post(f"{self._t(team_id)}/keys", {"label": label})
327
- return {**r, "key": Secret(str(r.get("key", "")))}
345
+ prefix = _key_prefix(r)
346
+ return {**r, "prefix": prefix, "keyPrefix": prefix, "key": Secret(str(r.get("key", "")))}
328
347
 
329
348
  def list_keys(self, team_id: str) -> Gen:
330
- return (yield _get(f"{self._t(team_id)}/keys"))
349
+ """-> {keys: [{id, prefix, keyPrefix (alias), label, userId, teamId, createdAt, revokedAt}]}."""
350
+ r = yield _get(f"{self._t(team_id)}/keys")
351
+ keys = r.get("keys") if isinstance(r, dict) else None
352
+ return {"keys": [_team_key(k) for k in keys if isinstance(k, dict)] if isinstance(keys, list) else []}
331
353
 
332
354
  def revoke_key(self, team_id: str, key_id: str) -> Gen:
333
355
  yield _delete(f"{self._t(team_id)}/keys/{_id(key_id, 'key_id')}")
@@ -544,7 +566,7 @@ MIN_CONVERT_MUSD = 10_000
544
566
  MAX_CONVERT_MUSD = 10_000_000_000
545
567
  MIN_WITHDRAW_MUSD = 1_000_000
546
568
  MAX_WITHDRAW_MUSD = 100_000_000_000
547
- _CURSOR_RE = re.compile(r"^[0-9]{1,20}\Z")
569
+ _CURSOR_RE = re.compile(r"^[1-9][0-9]{0,18}\Z")
548
570
 
549
571
 
550
572
  def _check_signed_challenge(ch: Any, signer: Any) -> Dict[str, Any]:
@@ -595,6 +617,10 @@ class _CreditsResource(_Base):
595
617
 
596
618
 
597
619
  # ---------------------------------------------------------------- 3.13 payoutWallet
620
+ # base-sepolia is an explicit opt-in for staging; the server decides whether it is accepted.
621
+ PAYOUT_CHAINS = ("base", "base-sepolia")
622
+
623
+
598
624
  class _PayoutWalletResource(_Base):
599
625
  def get(self) -> Gen:
600
626
  return (yield _get("/v1/payout-wallet", "agent"))
@@ -603,8 +629,8 @@ class _PayoutWalletResource(_Base):
603
629
  """Register or change the payout wallet (old wallet co-signs the SAME message when required)."""
604
630
  assert_signer(signer)
605
631
  addr = validate_address(address).lower()
606
- if chain != "base":
607
- raise invalid_arg("chain must be 'base'")
632
+ if chain not in PAYOUT_CHAINS:
633
+ raise invalid_arg("chain must be 'base' or 'base-sepolia'")
608
634
  if old_wallet_signer is not None:
609
635
  assert_signer(old_wallet_signer, "old_wallet_signer")
610
636
  ch = yield _post("/v1/payout-wallet/challenge", {"address": addr, "chain": chain}, "agent")
@@ -768,7 +794,10 @@ class _ProxyResource(_Base):
768
794
  return result
769
795
 
770
796
  def get_health(self) -> Gen:
771
- return (yield _get("/api/proxy/health", "none"))
797
+ """-> {status}. The server returns only the gateway status."""
798
+ r = yield _get("/api/proxy/health", "none")
799
+ status = r.get("status") if isinstance(r, dict) else None
800
+ return {"status": status if isinstance(status, str) else "unknown"}
772
801
 
773
802
  def get_config(self) -> Gen:
774
803
  return (yield _get("/api/proxy/config"))
@@ -789,3 +818,98 @@ CreditsResource, AsyncCreditsResource = make_variants(_CreditsResource)
789
818
  PayoutWalletResource, AsyncPayoutWalletResource = make_variants(_PayoutWalletResource)
790
819
  PayoutsResource, AsyncPayoutsResource = make_variants(_PayoutsResource)
791
820
  ProxyResource, AsyncProxyResource = make_variants(_ProxyResource)
821
+
822
+
823
+ # ---------------------------------------------------------------- pagination helpers
824
+ # Sync variants are generators, async variants async generators. ``limit`` is the page size.
825
+ _MAX_OFFSET = 1_000_000
826
+
827
+
828
+ def _offset_pager(list_method: str, items_key: str, default_limit: int, max_limit: int, total: bool = False) -> Any:
829
+ def _done(page: Any, items: List[Any], limit: int, offset: int) -> bool:
830
+ if len(items) < limit:
831
+ return True
832
+ if total and isinstance(page, dict):
833
+ t = (page.get("pagination") or {}).get("total")
834
+ if isinstance(t, int) and offset + len(items) >= t:
835
+ return True
836
+ return False
837
+
838
+ def _items(page: Any) -> List[Any]:
839
+ v = page.get(items_key) if isinstance(page, dict) else None
840
+ return v if isinstance(v, list) else []
841
+
842
+ def sync_iter(self: Any, limit: Optional[int] = None, **kwargs: Any) -> Iterator[Any]:
843
+ lim = validate_int_range(default_limit if limit is None else limit, "limit", 1, max_limit)
844
+ offset = 0
845
+ while offset <= _MAX_OFFSET:
846
+ page = getattr(self, list_method)(limit=lim, offset=offset, **kwargs)
847
+ items = _items(page)
848
+ yield from items
849
+ if _done(page, items, lim, offset):
850
+ return
851
+ offset += lim
852
+
853
+ async def async_iter(self: Any, limit: Optional[int] = None, **kwargs: Any) -> AsyncIterator[Any]:
854
+ lim = validate_int_range(default_limit if limit is None else limit, "limit", 1, max_limit)
855
+ offset = 0
856
+ while offset <= _MAX_OFFSET:
857
+ page = await getattr(self, list_method)(limit=lim, offset=offset, **kwargs)
858
+ items = _items(page)
859
+ for item in items:
860
+ yield item
861
+ if _done(page, items, lim, offset):
862
+ return
863
+ offset += lim
864
+
865
+ return sync_iter, async_iter
866
+
867
+
868
+ def _next_cursor(page: Any, items: List[Any], cursor: Optional[str]) -> Optional[str]:
869
+ nxt = page.get("nextCursor") if isinstance(page, dict) else None
870
+ if not items or not isinstance(nxt, str) or not _CURSOR_RE.match(nxt) or nxt == cursor:
871
+ return None
872
+ return nxt
873
+
874
+
875
+ def _credit_items(page: Any) -> List[Any]:
876
+ v = page.get("transactions") if isinstance(page, dict) else None
877
+ return v if isinstance(v, list) else []
878
+
879
+
880
+ def _credits_iter_sync(self: Any, limit: Optional[int] = None, type: Optional[str] = None, cursor: Optional[str] = None) -> Iterator[Any]: # noqa: A002
881
+ """Every credit transaction (newest first), following nextCursor."""
882
+ while True:
883
+ page = self.transactions(limit=limit, cursor=cursor, type=type)
884
+ items = _credit_items(page)
885
+ yield from items
886
+ cursor = _next_cursor(page, items, cursor)
887
+ if cursor is None:
888
+ return
889
+
890
+
891
+ async def _credits_iter_async(self: Any, limit: Optional[int] = None, type: Optional[str] = None, cursor: Optional[str] = None) -> AsyncIterator[Any]: # noqa: A002
892
+ """Every credit transaction (newest first), following nextCursor."""
893
+ while True:
894
+ page = await self.transactions(limit=limit, cursor=cursor, type=type)
895
+ items = _credit_items(page)
896
+ for item in items:
897
+ yield item
898
+ cursor = _next_cursor(page, items, cursor)
899
+ if cursor is None:
900
+ return
901
+
902
+
903
+ CreditsResource.iter_transactions = _credits_iter_sync # type: ignore[attr-defined]
904
+ AsyncCreditsResource.iter_transactions = _credits_iter_async # type: ignore[attr-defined]
905
+ for _sync_cls, _async_cls, _name, _args in (
906
+ (PayoutsResource, AsyncPayoutsResource, "iterate", ("list", "payouts", 50, 200)),
907
+ (NodesResource, AsyncNodesResource, "iterate", ("list", "nodes", 100, 500)),
908
+ (WalletResource, AsyncWalletResource, "iter_transactions", ("get_transactions", "transactions", 50, 100)),
909
+ ):
910
+ _s, _a = _offset_pager(*_args)
911
+ setattr(_sync_cls, _name, _s)
912
+ setattr(_async_cls, _name, _a)
913
+ _s, _a = _offset_pager("get", "records", 200, 1000, total=True)
914
+ UsageResource.iter_records = _s # type: ignore[attr-defined]
915
+ AsyncUsageResource.iter_records = _a # type: ignore[attr-defined]
@@ -95,5 +95,54 @@ class NetworkPricing(TypedDict):
95
95
  unit: str
96
96
  prices: List[PricingEntry]
97
97
  nodeShare: List[Dict[str, object]]
98
+ buyerDiscounts: List["BuyerDiscount"]
98
99
  maturationHours: int
99
100
  rails: List[str]
101
+
102
+
103
+ class BuyerDiscount(TypedDict):
104
+ tier: str
105
+ rank: int
106
+ buyerDiscountBps: int
107
+
108
+
109
+ class NetworkGroupStats(TypedDict):
110
+ """Online nodes and paid bytes (decimal string) in the last 24h for one operator group."""
111
+
112
+ online: int
113
+ paidBytes24h: str
114
+
115
+
116
+ class NetworkStats(TypedDict):
117
+ onlineNodes: int
118
+ byClass: Dict[str, int]
119
+ byCountry: List[Dict[str, object]]
120
+ otherCountries: int
121
+ seed: NetworkGroupStats
122
+ community: NetworkGroupStats
123
+ generatedAt: str
124
+
125
+
126
+ class TeamKey(TypedDict):
127
+ id: str
128
+ prefix: str
129
+ keyPrefix: str
130
+ label: str
131
+ userId: str
132
+ teamId: str
133
+ createdAt: str
134
+ revokedAt: Optional[str]
135
+
136
+
137
+ class TeamInvite(TypedDict):
138
+ id: str
139
+ teamId: str
140
+ email: str
141
+ role: str
142
+ invitedBy: str
143
+ expiresAt: str
144
+ createdAt: str
145
+
146
+
147
+ class ProxyHealth(TypedDict):
148
+ status: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Official DominusNode SDK for Python: AI-agent relay network (proxy, nodes, credits, payouts)
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/0xcircuitbreaker/DomiNode/tree/master/sdks/python
@@ -175,3 +175,21 @@ pytest
175
175
 
176
176
  `tests/fixtures/signed_messages.json` holds the three signed messages as produced by the Node
177
177
  SDK builders. `tests/test_signers.py` checks the Python builders against it byte for byte.
178
+
179
+ ## Node registration and the client IP
180
+
181
+ The node-registration proof-of-work is bound to the client IP that requested it.
182
+ Call the register-challenge and register methods from the same egress (same machine,
183
+ same outbound IP, no proxy or VPN switch in between); otherwise the server rejects
184
+ the solution with `INVALID_CHALLENGE`. Register from the host that will run the node.
185
+
186
+ ## Troubleshooting
187
+
188
+ | Symptom | Meaning | What to do |
189
+ |---|---|---|
190
+ | HTTP 402 | API call: requester or agentic wallet balance is too low | Top up with crypto or fund the agentic wallet |
191
+ | HTTP 407 (proxy) | Gateway rejected the proxy credentials | Check the API key in the proxy password and the username flags (`country-XX-class-dc-...`, or `relay`) |
192
+ | `EMAIL_NOT_VERIFIED` | The route needs a verified account | Verify the account before financial calls |
193
+ | `NO_NODES_AVAILABLE` | No eligible exit node for the requested country/class | Relax the country/class flags or retry later; check network stats |
194
+ | `PAYOUTS_DISABLED` | Withdrawals are switched off server-side | Keep earning; the credits balance `payoutsEnabled` field shows the state |
195
+ | `REPORT_LIMIT` | Per-node daily session-report cap reached (HTTP 409 conflict error; older servers 429) | Do not retry today; the SDK never retries it |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominusnode"
7
- version = "2.0.0"
7
+ version = "2.0.1"
8
8
  readme = "README.md"
9
9
  description = "Official DominusNode SDK for Python: AI-agent relay network (proxy, nodes, credits, payouts)"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes