@dominusnode/openclaw-plugin 1.2.0 → 1.3.0

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 (3) hide show
  1. package/README.md +89 -67
  2. package/dist/plugin.js +361 -47
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -37,10 +37,10 @@ cd Dominus Node/integrations/openclaw
37
37
 
38
38
  ## Configuration
39
39
 
40
- | Environment Variable | Required | Default | Description |
41
- |---|---|---|---|
42
- | `DOMINUSNODE_API_KEY` | Yes | -- | Your Dominus Node API key (starts with `dn_live_` or `dn_test_`) |
43
- | `DOMINUSNODE_BASE_URL` | No | `https://api.dominusnode.com` | Base URL for the Dominus Node REST API |
40
+ | Environment Variable | Required | Default | Description |
41
+ | ---------------------- | -------- | ----------------------------- | ---------------------------------------------------------------- |
42
+ | `DOMINUSNODE_API_KEY` | Yes | -- | Your Dominus Node API key (starts with `dn_live_` or `dn_test_`) |
43
+ | `DOMINUSNODE_BASE_URL` | No | `https://api.dominusnode.com` | Base URL for the Dominus Node REST API |
44
44
 
45
45
  ### Getting an API Key
46
46
 
@@ -54,147 +54,169 @@ cd Dominus Node/integrations/openclaw
54
54
  ### Proxy Tools
55
55
 
56
56
  #### `proxied_fetch`
57
+
57
58
  Fetch a URL through the proxy network with optional geo-targeting.
58
59
 
59
- | Parameter | Type | Required | Description |
60
- |---|---|---|---|
61
- | `url` | string | Yes | The URL to fetch (http or https) |
62
- | `method` | string | No | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD). Default: GET |
63
- | `country` | string | No | ISO 3166-1 alpha-2 country code (e.g., US, GB, DE) |
64
- | `pool` | string | No | Proxy pool: `dc` (datacenter, $3/GB) or `residential` ($5/GB). Default: dc |
65
- | `headers` | object | No | Additional HTTP headers as key-value pairs |
66
- | `body` | string | No | Request body for POST/PUT/PATCH |
60
+ | Parameter | Type | Required | Description |
61
+ | --------- | ------ | -------- | -------------------------------------------------------------------------- |
62
+ | `url` | string | Yes | The URL to fetch (http or https) |
63
+ | `method` | string | No | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD). Default: GET |
64
+ | `country` | string | No | ISO 3166-1 alpha-2 country code (e.g., US, GB, DE) |
65
+ | `pool` | string | No | Proxy pool: `dc` (datacenter, $3/GB) or `residential` ($5/GB). Default: dc |
66
+ | `headers` | object | No | Additional HTTP headers as key-value pairs |
67
+ | `body` | string | No | Request body for POST/PUT/PATCH |
67
68
 
68
69
  #### `get_proxy_config`
70
+
69
71
  View available proxy pools, pricing, supported countries, and endpoint configuration. No parameters.
70
72
 
71
73
  #### `list_sessions`
74
+
72
75
  List all active proxy sessions with status, target hosts, and bandwidth usage. No parameters.
73
76
 
74
77
  ### Wallet Tools
75
78
 
76
79
  #### `check_balance`
80
+
77
81
  Check your wallet balance and estimated remaining bandwidth at current pricing. No parameters.
78
82
 
79
83
  #### `check_usage`
84
+
80
85
  View bandwidth usage statistics for a time period.
81
86
 
82
- | Parameter | Type | Required | Description |
83
- |---|---|---|---|
84
- | `days` | number | No | Number of days to look back (1-365). Default: 30 |
87
+ | Parameter | Type | Required | Description |
88
+ | --------- | ------ | -------- | ------------------------------------------------ |
89
+ | `days` | number | No | Number of days to look back (1-365). Default: 30 |
85
90
 
86
91
  ### Agentic Wallet Tools
87
92
 
88
93
  Agentic wallets are server-side custodial sub-wallets designed for autonomous AI agents. They have per-transaction spending limits for safety and are funded from your main wallet.
89
94
 
90
95
  #### `create_agentic_wallet`
96
+
91
97
  Create a new agentic wallet.
92
98
 
93
- | Parameter | Type | Required | Description |
94
- |---|---|---|---|
95
- | `label` | string | Yes | Label for the wallet (1-100 chars) |
96
- | `spending_limit_cents` | number | No | Max spend per transaction in cents. Default: 10000 ($100) |
99
+ | Parameter | Type | Required | Description |
100
+ | ---------------------- | ------ | -------- | --------------------------------------------------------- |
101
+ | `label` | string | Yes | Label for the wallet (1-100 chars) |
102
+ | `spending_limit_cents` | number | No | Max spend per transaction in cents. Default: 10000 ($100) |
97
103
 
98
104
  #### `fund_agentic_wallet`
105
+
99
106
  Transfer funds from your main wallet to an agentic wallet.
100
107
 
101
- | Parameter | Type | Required | Description |
102
- |---|---|---|---|
103
- | `wallet_id` | string | Yes | Agentic wallet UUID |
104
- | `amount_cents` | number | Yes | Amount in cents (100-1000000) |
108
+ | Parameter | Type | Required | Description |
109
+ | -------------- | ------ | -------- | ----------------------------- |
110
+ | `wallet_id` | string | Yes | Agentic wallet UUID |
111
+ | `amount_cents` | number | Yes | Amount in cents (100-1000000) |
105
112
 
106
113
  #### `check_agentic_balance`
114
+
107
115
  Check an agentic wallet's balance and status.
108
116
 
109
- | Parameter | Type | Required | Description |
110
- |---|---|---|---|
111
- | `wallet_id` | string | Yes | Agentic wallet UUID |
117
+ | Parameter | Type | Required | Description |
118
+ | ----------- | ------ | -------- | ------------------- |
119
+ | `wallet_id` | string | Yes | Agentic wallet UUID |
112
120
 
113
121
  #### `list_agentic_wallets`
122
+
114
123
  List all your agentic wallets with balances and status. No parameters.
115
124
 
116
125
  #### `agentic_transactions`
126
+
117
127
  View transaction history for an agentic wallet.
118
128
 
119
- | Parameter | Type | Required | Description |
120
- |---|---|---|---|
121
- | `wallet_id` | string | Yes | Agentic wallet UUID |
122
- | `limit` | number | No | Number of transactions (1-100). Default: 20 |
129
+ | Parameter | Type | Required | Description |
130
+ | ----------- | ------ | -------- | ------------------------------------------- |
131
+ | `wallet_id` | string | Yes | Agentic wallet UUID |
132
+ | `limit` | number | No | Number of transactions (1-100). Default: 20 |
123
133
 
124
134
  ### Team Tools
125
135
 
126
136
  Teams enable shared wallet billing across multiple users. Owners and admins can manage members and create shared API keys.
127
137
 
128
138
  #### `create_team`
139
+
129
140
  Create a new team.
130
141
 
131
- | Parameter | Type | Required | Description |
132
- |---|---|---|---|
133
- | `name` | string | Yes | Team name (1-100 chars) |
134
- | `max_members` | number | No | Maximum members (1-1000). Default: unlimited |
142
+ | Parameter | Type | Required | Description |
143
+ | ------------- | ------ | -------- | -------------------------------------------- |
144
+ | `name` | string | Yes | Team name (1-100 chars) |
145
+ | `max_members` | number | No | Maximum members (1-1000). Default: unlimited |
135
146
 
136
147
  #### `list_teams`
148
+
137
149
  List all teams you belong to. No parameters.
138
150
 
139
151
  #### `team_details`
152
+
140
153
  Get detailed info about a team.
141
154
 
142
- | Parameter | Type | Required | Description |
143
- |---|---|---|---|
144
- | `team_id` | string | Yes | Team UUID |
155
+ | Parameter | Type | Required | Description |
156
+ | --------- | ------ | -------- | ----------- |
157
+ | `team_id` | string | Yes | Team UUID |
145
158
 
146
159
  #### `team_fund`
160
+
147
161
  Transfer funds from your personal wallet to a team wallet.
148
162
 
149
- | Parameter | Type | Required | Description |
150
- |---|---|---|---|
151
- | `team_id` | string | Yes | Team UUID |
152
- | `amount_cents` | number | Yes | Amount in cents (100-1000000) |
163
+ | Parameter | Type | Required | Description |
164
+ | -------------- | ------ | -------- | ----------------------------- |
165
+ | `team_id` | string | Yes | Team UUID |
166
+ | `amount_cents` | number | Yes | Amount in cents (100-1000000) |
153
167
 
154
168
  #### `team_create_key`
169
+
155
170
  Create a shared API key billed to the team wallet.
156
171
 
157
- | Parameter | Type | Required | Description |
158
- |---|---|---|---|
159
- | `team_id` | string | Yes | Team UUID |
160
- | `label` | string | Yes | Key label (1-100 chars) |
172
+ | Parameter | Type | Required | Description |
173
+ | --------- | ------ | -------- | ----------------------- |
174
+ | `team_id` | string | Yes | Team UUID |
175
+ | `label` | string | Yes | Key label (1-100 chars) |
161
176
 
162
177
  #### `team_usage`
178
+
163
179
  View team wallet transaction history.
164
180
 
165
- | Parameter | Type | Required | Description |
166
- |---|---|---|---|
167
- | `team_id` | string | Yes | Team UUID |
168
- | `limit` | number | No | Number of transactions (1-100). Default: 20 |
181
+ | Parameter | Type | Required | Description |
182
+ | --------- | ------ | -------- | ------------------------------------------- |
183
+ | `team_id` | string | Yes | Team UUID |
184
+ | `limit` | number | No | Number of transactions (1-100). Default: 20 |
169
185
 
170
186
  ## Usage Examples
171
187
 
172
188
  ### Fetch a page through a US proxy
189
+
173
190
  ```
174
191
  Use proxied_fetch to get https://httpbin.org/ip through a US datacenter proxy
175
192
  ```
176
193
 
177
194
  ### Check your budget
195
+
178
196
  ```
179
197
  Check my Dominus Node balance and tell me how much residential browsing I have left
180
198
  ```
181
199
 
182
200
  ### Set up a research team
201
+
183
202
  ```
184
203
  Create a team called "Web Research" with max 5 members, then fund it with $25
185
204
  ```
186
205
 
187
206
  ### Create an agent wallet for automated scraping
207
+
188
208
  ```
189
209
  Create an agentic wallet called "price-monitor" with a $5 spending limit, fund it with $10
190
210
  ```
191
211
 
192
212
  ### View usage breakdown
213
+
193
214
  ```
194
215
  Show my Dominus Node usage for the last 7 days
195
216
  ```
196
217
 
197
218
  ### Fetch with residential proxy and geo-targeting
219
+
198
220
  ```
199
221
  Use proxied_fetch to get https://example.co.uk through a GB residential proxy
200
222
  ```
@@ -218,24 +240,24 @@ The plugin implements comprehensive security measures:
218
240
 
219
241
  ## API Endpoints Used
220
242
 
221
- | Tool | Method | Endpoint |
222
- |---|---|---|
223
- | `proxied_fetch` | POST | `/api/proxy/fetch` |
224
- | `check_balance` | GET | `/api/wallet` |
225
- | `check_usage` | GET | `/api/usage` |
226
- | `get_proxy_config` | GET | `/api/proxy/config` |
227
- | `list_sessions` | GET | `/api/sessions/active` |
228
- | `create_agentic_wallet` | POST | `/api/agent-wallet` |
229
- | `fund_agentic_wallet` | POST | `/api/agent-wallet/:id/fund` |
230
- | `check_agentic_balance` | GET | `/api/agent-wallet/:id` |
231
- | `list_agentic_wallets` | GET | `/api/agent-wallet` |
232
- | `agentic_transactions` | GET | `/api/agent-wallet/:id/transactions` |
233
- | `create_team` | POST | `/api/teams` |
234
- | `list_teams` | GET | `/api/teams` |
235
- | `team_details` | GET | `/api/teams/:id` |
236
- | `team_fund` | POST | `/api/teams/:id/wallet/fund` |
237
- | `team_create_key` | POST | `/api/teams/:id/keys` |
238
- | `team_usage` | GET | `/api/teams/:id/wallet/transactions` |
243
+ | Tool | Method | Endpoint |
244
+ | ----------------------- | ------ | ------------------------------------ |
245
+ | `proxied_fetch` | POST | `/api/proxy/fetch` |
246
+ | `check_balance` | GET | `/api/wallet` |
247
+ | `check_usage` | GET | `/api/usage` |
248
+ | `get_proxy_config` | GET | `/api/proxy/config` |
249
+ | `list_sessions` | GET | `/api/sessions/active` |
250
+ | `create_agentic_wallet` | POST | `/api/agent-wallet` |
251
+ | `fund_agentic_wallet` | POST | `/api/agent-wallet/:id/fund` |
252
+ | `check_agentic_balance` | GET | `/api/agent-wallet/:id` |
253
+ | `list_agentic_wallets` | GET | `/api/agent-wallet` |
254
+ | `agentic_transactions` | GET | `/api/agent-wallet/:id/transactions` |
255
+ | `create_team` | POST | `/api/teams` |
256
+ | `list_teams` | GET | `/api/teams` |
257
+ | `team_details` | GET | `/api/teams/:id` |
258
+ | `team_fund` | POST | `/api/teams/:id/wallet/fund` |
259
+ | `team_create_key` | POST | `/api/teams/:id/keys` |
260
+ | `team_usage` | GET | `/api/teams/:id/wallet/transactions` |
239
261
 
240
262
  ## Requirements
241
263
 
package/dist/plugin.js CHANGED
@@ -12,10 +12,63 @@
12
12
  * - Response truncation at 4000 chars for LLM context efficiency
13
13
  * - OFAC sanctioned country validation
14
14
  */
15
+ import * as crypto from "node:crypto";
15
16
  import * as http from "node:http";
16
17
  import * as tls from "node:tls";
17
18
  import * as dns from "dns/promises";
18
19
  // ---------------------------------------------------------------------------
20
+ // SHA-256 Proof-of-Work solver
21
+ // ---------------------------------------------------------------------------
22
+ function countLeadingZeroBits(buf) {
23
+ let count = 0;
24
+ for (const byte of buf) {
25
+ if (byte === 0) {
26
+ count += 8;
27
+ continue;
28
+ }
29
+ let mask = 0x80;
30
+ while (mask && !(byte & mask)) {
31
+ count++;
32
+ mask >>= 1;
33
+ }
34
+ break;
35
+ }
36
+ return count;
37
+ }
38
+ async function solvePoW(solveBaseUrl) {
39
+ try {
40
+ const resp = await fetch(`${solveBaseUrl}/api/auth/pow/challenge`, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ redirect: "error",
44
+ });
45
+ if (!resp.ok)
46
+ return null;
47
+ const text = await resp.text();
48
+ if (text.length > 10_485_760)
49
+ return null;
50
+ const challenge = JSON.parse(text);
51
+ const prefix = challenge.prefix ?? "";
52
+ const difficulty = challenge.difficulty ?? 20;
53
+ const challengeId = challenge.challengeId ?? "";
54
+ if (!prefix || !challengeId)
55
+ return null;
56
+ for (let nonce = 0; nonce < 100_000_000; nonce++) {
57
+ const hash = crypto
58
+ .createHash("sha256")
59
+ .update(prefix + nonce.toString())
60
+ .digest();
61
+ if (countLeadingZeroBits(hash) >= difficulty) {
62
+ return { challengeId, nonce: nonce.toString() };
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ // ---------------------------------------------------------------------------
19
72
  // Configuration
20
73
  // ---------------------------------------------------------------------------
21
74
  const MAX_RESPONSE_CHARS = 4000;
@@ -69,7 +122,7 @@ function getAgentSecret() {
69
122
  // ---------------------------------------------------------------------------
70
123
  /** Remove any dn_live_* or dn_test_* tokens from error messages. */
71
124
  function scrubCredentials(msg) {
72
- return msg.replace(/dn_(live|test)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
125
+ return msg.replace(/dn_(live|test|proxy)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
73
126
  }
74
127
  function safeError(err) {
75
128
  const raw = err instanceof Error ? err.message : String(err);
@@ -81,7 +134,8 @@ function safeError(err) {
81
134
  function truncate(text, max = MAX_RESPONSE_CHARS) {
82
135
  if (text.length <= max)
83
136
  return text;
84
- return text.slice(0, max) + `\n\n... [truncated, ${text.length - max} chars omitted]`;
137
+ return (text.slice(0, max) +
138
+ `\n\n... [truncated, ${text.length - max} chars omitted]`);
85
139
  }
86
140
  // ---------------------------------------------------------------------------
87
141
  // SSRF Protection
@@ -352,7 +406,9 @@ let jwtExpiresAt = 0;
352
406
  let cachedApiKeyPrefix = null;
353
407
  async function ensureAuth(apiKey, baseUrl) {
354
408
  const keyPrefix = apiKey.slice(0, 16);
355
- if (cachedJwt && Date.now() < jwtExpiresAt && cachedApiKeyPrefix === keyPrefix)
409
+ if (cachedJwt &&
410
+ Date.now() < jwtExpiresAt &&
411
+ cachedApiKeyPrefix === keyPrefix)
356
412
  return cachedJwt;
357
413
  const authHeaders = {
358
414
  "Content-Type": "application/json",
@@ -373,7 +429,7 @@ async function ensureAuth(apiKey, baseUrl) {
373
429
  const text = await res.text().catch(() => "");
374
430
  throw new Error(`Auth failed (${res.status}): ${scrubCredentials(text.slice(0, 500))}`);
375
431
  }
376
- const data = await res.json();
432
+ const data = (await res.json());
377
433
  cachedApiKeyPrefix = keyPrefix;
378
434
  cachedJwt = data.token;
379
435
  // JWT expires in 15 min, refresh at 14 min for safety
@@ -386,8 +442,8 @@ async function apiRequest(method, path, body) {
386
442
  const url = `${baseUrl}${path}`;
387
443
  const jwt = await ensureAuth(apiKey, baseUrl);
388
444
  const headers = {
389
- "Authorization": `Bearer ${jwt}`,
390
- "Accept": "application/json",
445
+ Authorization: `Bearer ${jwt}`,
446
+ Accept: "application/json",
391
447
  "User-Agent": "dominusnode-openclaw-plugin/1.0.0",
392
448
  };
393
449
  const agentSecret = getAgentSecret();
@@ -477,7 +533,7 @@ async function unauthenticatedRequest(method, path, body) {
477
533
  const baseUrl = getBaseUrl();
478
534
  const url = `${baseUrl}${path}`;
479
535
  const headers = {
480
- "Accept": "application/json",
536
+ Accept: "application/json",
481
537
  "User-Agent": "dominusnode-openclaw-plugin/1.0.0",
482
538
  };
483
539
  const agentSecret = getAgentSecret();
@@ -612,9 +668,19 @@ const proxiedFetchTool = {
612
668
  }
613
669
  const proxyType = String(args.pool ?? "dc");
614
670
  // Validate and collect custom headers
615
- const STRIPPED_HEADERS = new Set(["host", "connection", "content-length", "transfer-encoding", "proxy-authorization", "authorization", "user-agent"]);
671
+ const STRIPPED_HEADERS = new Set([
672
+ "host",
673
+ "connection",
674
+ "content-length",
675
+ "transfer-encoding",
676
+ "proxy-authorization",
677
+ "authorization",
678
+ "user-agent",
679
+ ]);
616
680
  const customHeaders = {};
617
- if (args.headers && typeof args.headers === "object" && !Array.isArray(args.headers)) {
681
+ if (args.headers &&
682
+ typeof args.headers === "object" &&
683
+ !Array.isArray(args.headers)) {
618
684
  for (const [k, v] of Object.entries(args.headers)) {
619
685
  const key = String(k);
620
686
  const val = String(v ?? "");
@@ -639,15 +705,24 @@ const proxiedFetchTool = {
639
705
  const parsed = new URL(url);
640
706
  const MAX_RESP = 1_048_576; // 1MB
641
707
  // Build custom header lines for raw HTTP request
642
- const customHeaderLines = Object.entries(customHeaders).map(([k, v]) => `${k}: ${v}\r\n`).join("");
708
+ const customHeaderLines = Object.entries(customHeaders)
709
+ .map(([k, v]) => `${k}: ${v}\r\n`)
710
+ .join("");
643
711
  const result = await new Promise((resolve, reject) => {
644
712
  const timer = setTimeout(() => reject(new Error("Proxy request timed out")), 30_000);
645
713
  if (parsed.protocol === "https:") {
646
- const connectHost = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
714
+ const connectHost = parsed.hostname.includes(":")
715
+ ? `[${parsed.hostname}]`
716
+ : parsed.hostname;
647
717
  const connectReq = http.request({
648
- hostname: proxyHost, port: proxyPort, method: "CONNECT",
718
+ hostname: proxyHost,
719
+ port: proxyPort,
720
+ method: "CONNECT",
649
721
  path: `${connectHost}:${parsed.port || 443}`,
650
- headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${parsed.port || 443}` },
722
+ headers: {
723
+ "Proxy-Authorization": proxyAuth,
724
+ Host: `${connectHost}:${parsed.port || 443}`,
725
+ },
651
726
  });
652
727
  connectReq.on("connect", (_res, sock) => {
653
728
  if (_res.statusCode !== 200) {
@@ -656,13 +731,21 @@ const proxiedFetchTool = {
656
731
  reject(new Error(`CONNECT failed: ${_res.statusCode}`));
657
732
  return;
658
733
  }
659
- const tlsSock = tls.connect({ host: parsed.hostname, socket: sock, servername: parsed.hostname, minVersion: "TLSv1.2" }, () => {
734
+ const tlsSock = tls.connect({
735
+ host: parsed.hostname,
736
+ socket: sock,
737
+ servername: parsed.hostname,
738
+ minVersion: "TLSv1.2",
739
+ }, () => {
660
740
  const reqLine = `${method} ${parsed.pathname + parsed.search} HTTP/1.1\r\nHost: ${parsed.host}\r\nUser-Agent: dominusnode-openclaw/1.0.0\r\n${customHeaderLines}Connection: close\r\n\r\n`;
661
741
  tlsSock.write(reqLine);
662
742
  const chunks = [];
663
743
  let bytes = 0;
664
- tlsSock.on("data", (c) => { bytes += c.length; if (bytes <= MAX_RESP + 16384)
665
- chunks.push(c); });
744
+ tlsSock.on("data", (c) => {
745
+ bytes += c.length;
746
+ if (bytes <= MAX_RESP + 16384)
747
+ chunks.push(c);
748
+ });
666
749
  let done = false;
667
750
  const fin = () => {
668
751
  if (done)
@@ -677,40 +760,69 @@ const proxiedFetchTool = {
677
760
  }
678
761
  const hdr = raw.substring(0, hEnd);
679
762
  const body = raw.substring(hEnd + 4).substring(0, MAX_RESP);
680
- const sm = hdr.split("\r\n")[0].match(/^HTTP\/\d\.\d\s+(\d+)/);
763
+ const sm = hdr
764
+ .split("\r\n")[0]
765
+ .match(/^HTTP\/\d\.\d\s+(\d+)/);
681
766
  const hdrs = {};
682
767
  for (const l of hdr.split("\r\n").slice(1)) {
683
768
  const ci = l.indexOf(":");
684
769
  if (ci > 0)
685
- hdrs[l.substring(0, ci).trim().toLowerCase()] = l.substring(ci + 1).trim();
770
+ hdrs[l.substring(0, ci).trim().toLowerCase()] = l
771
+ .substring(ci + 1)
772
+ .trim();
686
773
  }
687
- resolve({ status: sm ? parseInt(sm[1], 10) : 0, headers: hdrs, body });
774
+ resolve({
775
+ status: sm ? parseInt(sm[1], 10) : 0,
776
+ headers: hdrs,
777
+ body,
778
+ });
688
779
  };
689
780
  tlsSock.on("end", fin);
690
781
  tlsSock.on("close", fin);
691
- tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
782
+ tlsSock.on("error", (e) => {
783
+ clearTimeout(timer);
784
+ reject(e);
785
+ });
692
786
  });
693
- tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
787
+ tlsSock.on("error", (e) => {
788
+ clearTimeout(timer);
789
+ reject(e);
790
+ });
791
+ });
792
+ connectReq.on("error", (e) => {
793
+ clearTimeout(timer);
794
+ reject(e);
694
795
  });
695
- connectReq.on("error", (e) => { clearTimeout(timer); reject(e); });
696
796
  connectReq.end();
697
797
  }
698
798
  else {
699
799
  const req = http.request({
700
- hostname: proxyHost, port: proxyPort, method, path: url,
701
- headers: { "Proxy-Authorization": proxyAuth, Host: parsed.host ?? "", ...customHeaders },
800
+ hostname: proxyHost,
801
+ port: proxyPort,
802
+ method,
803
+ path: url,
804
+ headers: {
805
+ "Proxy-Authorization": proxyAuth,
806
+ Host: parsed.host ?? "",
807
+ ...customHeaders,
808
+ },
702
809
  }, (res) => {
703
810
  const chunks = [];
704
811
  let bytes = 0;
705
- res.on("data", (c) => { bytes += c.length; if (bytes <= MAX_RESP)
706
- chunks.push(c); });
812
+ res.on("data", (c) => {
813
+ bytes += c.length;
814
+ if (bytes <= MAX_RESP)
815
+ chunks.push(c);
816
+ });
707
817
  let done = false;
708
818
  const fin = () => {
709
819
  if (done)
710
820
  return;
711
821
  done = true;
712
822
  clearTimeout(timer);
713
- const body = Buffer.concat(chunks).toString("utf-8").substring(0, MAX_RESP);
823
+ const body = Buffer.concat(chunks)
824
+ .toString("utf-8")
825
+ .substring(0, MAX_RESP);
714
826
  const hdrs = {};
715
827
  for (const [k, v] of Object.entries(res.headers)) {
716
828
  if (v)
@@ -720,9 +832,15 @@ const proxiedFetchTool = {
720
832
  };
721
833
  res.on("end", fin);
722
834
  res.on("close", fin);
723
- res.on("error", (e) => { clearTimeout(timer); reject(e); });
835
+ res.on("error", (e) => {
836
+ clearTimeout(timer);
837
+ reject(e);
838
+ });
839
+ });
840
+ req.on("error", (e) => {
841
+ clearTimeout(timer);
842
+ reject(e);
724
843
  });
725
- req.on("error", (e) => { clearTimeout(timer); reject(e); });
726
844
  req.end();
727
845
  }
728
846
  });
@@ -733,7 +851,13 @@ const proxiedFetchTool = {
733
851
  ];
734
852
  // Include relevant response headers
735
853
  if (result.headers) {
736
- const showHeaders = ["content-type", "content-length", "server", "x-cache", "cache-control"];
854
+ const showHeaders = [
855
+ "content-type",
856
+ "content-length",
857
+ "server",
858
+ "x-cache",
859
+ "cache-control",
860
+ ];
737
861
  for (const h of showHeaders) {
738
862
  if (result.headers[h]) {
739
863
  lines.push(`${h}: ${result.headers[h]}`);
@@ -920,9 +1044,12 @@ const createAgenticWalletTool = {
920
1044
  spendingLimitCents,
921
1045
  };
922
1046
  // Validate optional daily_limit_cents
923
- if (args.daily_limit_cents !== undefined && args.daily_limit_cents !== null) {
1047
+ if (args.daily_limit_cents !== undefined &&
1048
+ args.daily_limit_cents !== null) {
924
1049
  const dailyLimit = Number(args.daily_limit_cents);
925
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1000000) {
1050
+ if (!Number.isInteger(dailyLimit) ||
1051
+ dailyLimit < 1 ||
1052
+ dailyLimit > 1000000) {
926
1053
  return "Error: daily_limit_cents must be a positive integer between 1 and 1000000.";
927
1054
  }
928
1055
  body.dailyLimitCents = dailyLimit;
@@ -989,7 +1116,9 @@ const fundAgenticWalletTool = {
989
1116
  try {
990
1117
  const walletId = validateUuid(String(args.wallet_id ?? ""), "wallet_id");
991
1118
  const amountCents = Number(args.amount_cents ?? 0);
992
- if (!Number.isInteger(amountCents) || amountCents < 100 || amountCents > 1000000) {
1119
+ if (!Number.isInteger(amountCents) ||
1120
+ amountCents < 100 ||
1121
+ amountCents > 1000000) {
993
1122
  return "Error: amount_cents must be an integer between 100 ($1) and 1000000 ($10,000).";
994
1123
  }
995
1124
  const data = await apiPost(`/api/agent-wallet/${encodeURIComponent(walletId)}/fund`, {
@@ -1099,7 +1228,9 @@ const agenticTransactionsTool = {
1099
1228
  const lines = [`Wallet Transactions (${txs.length})`, ""];
1100
1229
  for (const tx of txs) {
1101
1230
  const sign = tx.type === "fund" || tx.type === "refund" ? "+" : "-";
1102
- const session = tx.sessionId ? ` | Session: ${tx.sessionId.slice(0, 8)}` : "";
1231
+ const session = tx.sessionId
1232
+ ? ` | Session: ${tx.sessionId.slice(0, 8)}`
1233
+ : "";
1103
1234
  lines.push(` ${sign}${formatCents(tx.amountCents)} [${tx.type}] ${tx.description}`);
1104
1235
  lines.push(` ${tx.createdAt}${session}`);
1105
1236
  }
@@ -1138,7 +1269,9 @@ const createTeamTool = {
1138
1269
  const body = { name };
1139
1270
  if (args.max_members !== undefined) {
1140
1271
  const maxMembers = Number(args.max_members);
1141
- if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
1272
+ if (!Number.isInteger(maxMembers) ||
1273
+ maxMembers < 1 ||
1274
+ maxMembers > 100) {
1142
1275
  return "Error: max_members must be an integer between 1 and 100.";
1143
1276
  }
1144
1277
  body.maxMembers = maxMembers;
@@ -1250,7 +1383,9 @@ const teamFundTool = {
1250
1383
  try {
1251
1384
  const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
1252
1385
  const amountCents = Number(args.amount_cents ?? 0);
1253
- if (!Number.isInteger(amountCents) || amountCents < 100 || amountCents > 1000000) {
1386
+ if (!Number.isInteger(amountCents) ||
1387
+ amountCents < 100 ||
1388
+ amountCents > 1000000) {
1254
1389
  return "Error: amount_cents must be an integer between 100 ($1) and 1000000 ($10,000).";
1255
1390
  }
1256
1391
  const data = await apiPost(`/api/teams/${encodeURIComponent(teamId)}/wallet/fund`, {
@@ -1490,7 +1625,9 @@ const updateTeamTool = {
1490
1625
  }
1491
1626
  if (args.max_members !== undefined) {
1492
1627
  const maxMembers = Number(args.max_members);
1493
- if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
1628
+ if (!Number.isInteger(maxMembers) ||
1629
+ maxMembers < 1 ||
1630
+ maxMembers > 100) {
1494
1631
  return "Error: max_members must be an integer between 1 and 100.";
1495
1632
  }
1496
1633
  body.maxMembers = maxMembers;
@@ -1528,7 +1665,9 @@ const topupPaypalTool = {
1528
1665
  execute: async (args) => {
1529
1666
  try {
1530
1667
  const amountCents = Number(args.amount_cents ?? 0);
1531
- if (!Number.isInteger(amountCents) || amountCents < 500 || amountCents > 100000) {
1668
+ if (!Number.isInteger(amountCents) ||
1669
+ amountCents < 500 ||
1670
+ amountCents > 100000) {
1532
1671
  return "Error: amount_cents must be an integer between 500 ($5) and 100000 ($1,000).";
1533
1672
  }
1534
1673
  const data = await apiPost("/api/wallet/topup/paypal", { amountCents });
@@ -1563,7 +1702,9 @@ const topupStripeTool = {
1563
1702
  execute: async (args) => {
1564
1703
  try {
1565
1704
  const amountCents = Number(args.amount_cents ?? 0);
1566
- if (!Number.isInteger(amountCents) || amountCents < 500 || amountCents > 100000) {
1705
+ if (!Number.isInteger(amountCents) ||
1706
+ amountCents < 500 ||
1707
+ amountCents > 100000) {
1567
1708
  return "Error: amount_cents must be an integer between 500 ($5) and 100000 ($1,000).";
1568
1709
  }
1569
1710
  const data = await apiPost("/api/wallet/topup/stripe", { amountCents });
@@ -1585,7 +1726,17 @@ const topupStripeTool = {
1585
1726
  };
1586
1727
  // 23. topup_crypto
1587
1728
  const VALID_CRYPTO_CURRENCIES = new Set([
1588
- "BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK",
1729
+ "BTC",
1730
+ "ETH",
1731
+ "LTC",
1732
+ "XMR",
1733
+ "ZEC",
1734
+ "USDC",
1735
+ "SOL",
1736
+ "USDT",
1737
+ "DAI",
1738
+ "BNB",
1739
+ "LINK",
1589
1740
  ]);
1590
1741
  const topupCryptoTool = {
1591
1742
  name: "topup_crypto",
@@ -1602,20 +1753,38 @@ const topupCryptoTool = {
1602
1753
  type: "string",
1603
1754
  description: "Cryptocurrency to pay with",
1604
1755
  required: true,
1605
- enum: ["BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK"],
1756
+ enum: [
1757
+ "BTC",
1758
+ "ETH",
1759
+ "LTC",
1760
+ "XMR",
1761
+ "ZEC",
1762
+ "USDC",
1763
+ "SOL",
1764
+ "USDT",
1765
+ "DAI",
1766
+ "BNB",
1767
+ "LINK",
1768
+ ],
1606
1769
  },
1607
1770
  },
1608
1771
  execute: async (args) => {
1609
1772
  try {
1610
1773
  const amountUsd = Number(args.amount_usd ?? 0);
1611
- if (typeof amountUsd !== "number" || !Number.isFinite(amountUsd) || amountUsd < 5 || amountUsd > 1000) {
1774
+ if (typeof amountUsd !== "number" ||
1775
+ !Number.isFinite(amountUsd) ||
1776
+ amountUsd < 5 ||
1777
+ amountUsd > 1000) {
1612
1778
  return "Error: amount_usd must be a number between 5 and 1000.";
1613
1779
  }
1614
1780
  const currency = String(args.currency ?? "").toUpperCase();
1615
1781
  if (!VALID_CRYPTO_CURRENCIES.has(currency)) {
1616
1782
  return `Error: currency must be one of: ${[...VALID_CRYPTO_CURRENCIES].join(", ")}.`;
1617
1783
  }
1618
- const data = await apiPost("/api/wallet/topup/crypto", { amountUsd, currency: currency.toLowerCase() });
1784
+ const data = await apiPost("/api/wallet/topup/crypto", {
1785
+ amountUsd,
1786
+ currency: currency.toLowerCase(),
1787
+ });
1619
1788
  return [
1620
1789
  "Crypto Payment Invoice Created",
1621
1790
  "",
@@ -1727,7 +1896,9 @@ const updateWalletPolicyTool = {
1727
1896
  }
1728
1897
  else {
1729
1898
  const dailyLimit = Number(args.daily_limit_cents);
1730
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1000000) {
1899
+ if (!Number.isInteger(dailyLimit) ||
1900
+ dailyLimit < 1 ||
1901
+ dailyLimit > 1000000) {
1731
1902
  return "Error: daily_limit_cents must be a positive integer between 1 and 1000000, or null to clear.";
1732
1903
  }
1733
1904
  body.dailyLimitCents = dailyLimit;
@@ -1827,7 +1998,9 @@ const getTransactionsTool = {
1827
1998
  }
1828
1999
  const lines = [`Wallet Transactions (${txs.length})`, ""];
1829
2000
  for (const tx of txs) {
1830
- const sign = tx.type === "topup" || tx.type === "refund" || tx.type === "fund" ? "+" : "-";
2001
+ const sign = tx.type === "topup" || tx.type === "refund" || tx.type === "fund"
2002
+ ? "+"
2003
+ : "-";
1831
2004
  lines.push(` ${sign}${formatCents(Math.abs(tx.amountCents))} [${tx.type}] ${tx.description}`);
1832
2005
  lines.push(` ${tx.createdAt}`);
1833
2006
  }
@@ -1998,7 +2171,12 @@ const registerTool = {
1998
2171
  if (password.length < 8 || password.length > 128) {
1999
2172
  return "Error: Password must be 8-128 characters.";
2000
2173
  }
2001
- const data = await unauthenticatedRequest("POST", "/api/auth/register", { email, password });
2174
+ // Solve PoW for CAPTCHA-free registration
2175
+ const pow = await solvePoW(getBaseUrl());
2176
+ const regBody = { email, password };
2177
+ if (pow)
2178
+ regBody.pow = pow;
2179
+ const data = await unauthenticatedRequest("POST", "/api/auth/register", regBody);
2002
2180
  return [
2003
2181
  "Account Registered",
2004
2182
  "",
@@ -2130,7 +2308,10 @@ const verifyEmailTool = {
2130
2308
  if (/[\x00-\x1f\x7f]/.test(code)) {
2131
2309
  return "Error: Verification code contains invalid control characters.";
2132
2310
  }
2133
- await unauthenticatedRequest("POST", "/api/auth/verify-email", { email, code });
2311
+ await unauthenticatedRequest("POST", "/api/auth/verify-email", {
2312
+ email,
2313
+ code,
2314
+ });
2134
2315
  return [
2135
2316
  "Email Verified Successfully",
2136
2317
  "",
@@ -2735,6 +2916,134 @@ const teamCancelInviteTool = {
2735
2916
  },
2736
2917
  };
2737
2918
  // ---------------------------------------------------------------------------
2919
+ // MPP (Machine Payment Protocol) tools
2920
+ // ---------------------------------------------------------------------------
2921
+ const mppInfoTool = {
2922
+ name: "mpp_info",
2923
+ description: "Get Machine Payment Protocol (MPP) information including enabled status, " +
2924
+ "supported payment methods, pricing, and session limits.",
2925
+ parameters: {},
2926
+ execute: async () => {
2927
+ try {
2928
+ const data = await apiGet("/api/mpp/info");
2929
+ return JSON.stringify(data, null, 2);
2930
+ }
2931
+ catch (err) {
2932
+ return `Error: ${safeError(err)}`;
2933
+ }
2934
+ },
2935
+ };
2936
+ const payMppTool = {
2937
+ name: "pay_mpp",
2938
+ description: "Top up wallet via Machine Payment Protocol (MPP). " +
2939
+ "Supports tempo, stripe_spt, and lightning payment methods.",
2940
+ parameters: {
2941
+ amount_cents: {
2942
+ type: "number",
2943
+ description: "Amount in cents to top up (min 500 = $5, max 100000 = $1,000)",
2944
+ required: true,
2945
+ },
2946
+ method: {
2947
+ type: "string",
2948
+ description: "MPP payment method",
2949
+ required: true,
2950
+ enum: ["tempo", "stripe_spt", "lightning"],
2951
+ },
2952
+ },
2953
+ execute: async (args) => {
2954
+ const amountCents = Number(args.amount_cents);
2955
+ const method = String(args.method ?? "");
2956
+ if (!Number.isInteger(amountCents) ||
2957
+ amountCents < 500 ||
2958
+ amountCents > 100_000) {
2959
+ return "Error: amount_cents must be an integer between 500 and 100,000";
2960
+ }
2961
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
2962
+ return "Error: method must be one of: tempo, stripe_spt, lightning";
2963
+ }
2964
+ try {
2965
+ const data = await apiPost("/api/mpp/topup", {
2966
+ amountCents,
2967
+ method,
2968
+ });
2969
+ return JSON.stringify(data, null, 2);
2970
+ }
2971
+ catch (err) {
2972
+ return `Error: ${safeError(err)}`;
2973
+ }
2974
+ },
2975
+ };
2976
+ const mppSessionOpenTool = {
2977
+ name: "mpp_session_open",
2978
+ description: "Open a pay-as-you-go MPP session. Returns a channelId for metered proxy usage.",
2979
+ parameters: {
2980
+ max_deposit_cents: {
2981
+ type: "number",
2982
+ description: "Maximum deposit in cents (min 500, max 100000)",
2983
+ required: true,
2984
+ },
2985
+ method: {
2986
+ type: "string",
2987
+ description: "MPP payment method",
2988
+ required: true,
2989
+ enum: ["tempo", "stripe_spt", "lightning"],
2990
+ },
2991
+ pool_type: {
2992
+ type: "string",
2993
+ description: "Proxy pool type: dc ($3/GB) or residential ($5/GB)",
2994
+ default: "dc",
2995
+ enum: ["dc", "residential"],
2996
+ },
2997
+ },
2998
+ execute: async (args) => {
2999
+ const maxDepositCents = Number(args.max_deposit_cents);
3000
+ const method = String(args.method ?? "");
3001
+ const poolType = String(args.pool_type ?? "dc");
3002
+ if (!Number.isInteger(maxDepositCents) ||
3003
+ maxDepositCents < 500 ||
3004
+ maxDepositCents > 100_000) {
3005
+ return "Error: max_deposit_cents must be an integer between 500 and 100,000";
3006
+ }
3007
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
3008
+ return "Error: method must be one of: tempo, stripe_spt, lightning";
3009
+ }
3010
+ if (!["dc", "residential"].includes(poolType)) {
3011
+ return "Error: pool_type must be dc or residential";
3012
+ }
3013
+ try {
3014
+ const data = await apiPost("/api/mpp/session/open", { maxDepositCents, method, poolType });
3015
+ return JSON.stringify(data, null, 2);
3016
+ }
3017
+ catch (err) {
3018
+ return `Error: ${safeError(err)}`;
3019
+ }
3020
+ },
3021
+ };
3022
+ const mppSessionCloseTool = {
3023
+ name: "mpp_session_close",
3024
+ description: "Close an MPP pay-as-you-go session. Returns the amount spent and refunded.",
3025
+ parameters: {
3026
+ channel_id: {
3027
+ type: "string",
3028
+ description: "The channelId returned from mpp_session_open",
3029
+ required: true,
3030
+ },
3031
+ },
3032
+ execute: async (args) => {
3033
+ const channelId = String(args.channel_id ?? "");
3034
+ if (!channelId) {
3035
+ return "Error: channel_id is required";
3036
+ }
3037
+ try {
3038
+ const data = await apiPost("/api/mpp/session/close", { channelId });
3039
+ return JSON.stringify(data, null, 2);
3040
+ }
3041
+ catch (err) {
3042
+ return `Error: ${safeError(err)}`;
3043
+ }
3044
+ },
3045
+ };
3046
+ // ---------------------------------------------------------------------------
2738
3047
  // Plugin export — the tools array that OpenClaw discovers
2739
3048
  // ---------------------------------------------------------------------------
2740
3049
  export const tools = [
@@ -2800,6 +3109,11 @@ export const tools = [
2800
3109
  teamInviteMemberTool,
2801
3110
  teamListInvitesTool,
2802
3111
  teamCancelInviteTool,
3112
+ // MPP (4)
3113
+ mppInfoTool,
3114
+ payMppTool,
3115
+ mppSessionOpenTool,
3116
+ mppSessionCloseTool,
2803
3117
  ];
2804
3118
  /**
2805
3119
  * Plugin metadata for OpenClaw discovery.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dominusnode/openclaw-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Dominus Node proxy plugin for OpenClaw — route web requests through rotating proxy networks",
5
5
  "main": "dist/plugin.js",
6
6
  "types": "dist/plugin.d.ts",
@@ -8,9 +8,11 @@
8
8
  "license": "MIT",
9
9
  "scripts": {
10
10
  "build": "tsc",
11
+ "prepare": "npm run build",
11
12
  "test": "vitest run"
12
13
  },
13
14
  "devDependencies": {
15
+ "@types/node": "^22.0.0",
14
16
  "typescript": "^5.4.0",
15
17
  "vitest": "^1.6.0"
16
18
  },