@dominusnode/pi-extension 1.2.0 → 1.5.2
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.
- package/CHANGELOG.md +1 -0
- package/README.md +60 -58
- package/dist/index.js +150 -42
- package/dist/toolkit.d.ts +6 -0
- package/dist/toolkit.js +309 -49
- package/package.json +3 -1
- package/skills/use-dominus-proxy.md +37 -31
package/dist/toolkit.js
CHANGED
|
@@ -65,6 +65,7 @@ exports.checkDnsRebinding = checkDnsRebinding;
|
|
|
65
65
|
exports.stripDangerousKeys = stripDangerousKeys;
|
|
66
66
|
exports.formatBytes = formatBytes;
|
|
67
67
|
exports.formatCents = formatCents;
|
|
68
|
+
const crypto = __importStar(require("node:crypto"));
|
|
68
69
|
const http = __importStar(require("node:http"));
|
|
69
70
|
const tls = __importStar(require("node:tls"));
|
|
70
71
|
const dns = __importStar(require("dns/promises"));
|
|
@@ -97,7 +98,7 @@ exports.BLOCKED_HOSTNAMES = new Set([
|
|
|
97
98
|
// ---------------------------------------------------------------------------
|
|
98
99
|
/** Remove any dn_live_* or dn_test_* tokens from error messages. */
|
|
99
100
|
function scrubCredentials(msg) {
|
|
100
|
-
return msg.replace(/dn_(live|test)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
|
|
101
|
+
return msg.replace(/dn_(live|test|proxy)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
|
|
101
102
|
}
|
|
102
103
|
function safeError(err) {
|
|
103
104
|
const raw = err instanceof Error ? err.message : String(err);
|
|
@@ -109,7 +110,8 @@ function safeError(err) {
|
|
|
109
110
|
function truncate(text, max = MAX_RESPONSE_CHARS) {
|
|
110
111
|
if (text.length <= max)
|
|
111
112
|
return text;
|
|
112
|
-
return text.slice(0, max) +
|
|
113
|
+
return (text.slice(0, max) +
|
|
114
|
+
`\n\n... [truncated, ${text.length - max} chars omitted]`);
|
|
113
115
|
}
|
|
114
116
|
// ---------------------------------------------------------------------------
|
|
115
117
|
// SSRF Protection
|
|
@@ -270,7 +272,9 @@ function validateTargetUrl(url) {
|
|
|
270
272
|
if (hostname.endsWith(".localhost")) {
|
|
271
273
|
throw new Error("Requests to localhost/loopback addresses are blocked");
|
|
272
274
|
}
|
|
273
|
-
if (hostname.endsWith(".local") ||
|
|
275
|
+
if (hostname.endsWith(".local") ||
|
|
276
|
+
hostname.endsWith(".internal") ||
|
|
277
|
+
hostname.endsWith(".arpa")) {
|
|
274
278
|
throw new Error("Requests to internal network hostnames are blocked");
|
|
275
279
|
}
|
|
276
280
|
return parsed;
|
|
@@ -306,11 +310,15 @@ async function checkDnsRebinding(hostname) {
|
|
|
306
310
|
try {
|
|
307
311
|
resolvedIps.push(...(await dns.resolve4(stripped)));
|
|
308
312
|
}
|
|
309
|
-
catch {
|
|
313
|
+
catch {
|
|
314
|
+
/* NODATA/NXDOMAIN for A is fine */
|
|
315
|
+
}
|
|
310
316
|
try {
|
|
311
317
|
resolvedIps.push(...(await dns.resolve6(stripped)));
|
|
312
318
|
}
|
|
313
|
-
catch {
|
|
319
|
+
catch {
|
|
320
|
+
/* NODATA/NXDOMAIN for AAAA is fine */
|
|
321
|
+
}
|
|
314
322
|
for (const ip of resolvedIps) {
|
|
315
323
|
if (isPrivateIp(ip)) {
|
|
316
324
|
throw new Error(`DNS rebinding detected: hostname resolves to private IP ${ip}`);
|
|
@@ -388,7 +396,9 @@ class DominusNodeToolkit {
|
|
|
388
396
|
constructor(options = {}) {
|
|
389
397
|
this.apiKey = options.apiKey || process.env["DOMINUSNODE_API_KEY"] || "";
|
|
390
398
|
// H-6: Validate baseUrl
|
|
391
|
-
const rawBase = (options.baseUrl ||
|
|
399
|
+
const rawBase = (options.baseUrl ||
|
|
400
|
+
process.env["DOMINUSNODE_BASE_URL"] ||
|
|
401
|
+
"https://api.dominusnode.com").replace(/\/+$/, "");
|
|
392
402
|
try {
|
|
393
403
|
const parsedBase = new URL(rawBase);
|
|
394
404
|
if (parsedBase.protocol !== "https:" && parsedBase.protocol !== "http:")
|
|
@@ -396,7 +406,9 @@ class DominusNodeToolkit {
|
|
|
396
406
|
if (parsedBase.username || parsedBase.password)
|
|
397
407
|
throw new Error("embedded credentials not allowed in base URL");
|
|
398
408
|
// H-1: Warn on HTTP base URL in production
|
|
399
|
-
if (parsedBase.protocol === "http:" &&
|
|
409
|
+
if (parsedBase.protocol === "http:" &&
|
|
410
|
+
parsedBase.hostname !== "localhost" &&
|
|
411
|
+
parsedBase.hostname !== "127.0.0.1") {
|
|
400
412
|
console.warn("[DominusNode] WARNING: baseUrl uses HTTP — API key will be sent unencrypted");
|
|
401
413
|
}
|
|
402
414
|
}
|
|
@@ -405,21 +417,81 @@ class DominusNodeToolkit {
|
|
|
405
417
|
}
|
|
406
418
|
this.baseUrl = rawBase;
|
|
407
419
|
// H-2: Validate proxyHost against private/internal IPs
|
|
408
|
-
const rawProxyHost = options.proxyHost ||
|
|
409
|
-
|
|
420
|
+
const rawProxyHost = options.proxyHost ||
|
|
421
|
+
process.env["DOMINUSNODE_PROXY_HOST"] ||
|
|
422
|
+
"proxy.dominusnode.com";
|
|
423
|
+
if (exports.BLOCKED_HOSTNAMES.has(rawProxyHost.toLowerCase()) ||
|
|
424
|
+
isPrivateIp(rawProxyHost)) {
|
|
410
425
|
throw new Error("DOMINUSNODE_PROXY_HOST must not be a private or loopback address");
|
|
411
426
|
}
|
|
412
|
-
if (rawProxyHost.endsWith(".localhost") ||
|
|
413
|
-
rawProxyHost.endsWith(".
|
|
427
|
+
if (rawProxyHost.endsWith(".localhost") ||
|
|
428
|
+
rawProxyHost.endsWith(".local") ||
|
|
429
|
+
rawProxyHost.endsWith(".internal") ||
|
|
430
|
+
rawProxyHost.endsWith(".arpa")) {
|
|
414
431
|
throw new Error("DOMINUSNODE_PROXY_HOST must not use internal network TLDs");
|
|
415
432
|
}
|
|
416
433
|
this.proxyHost = rawProxyHost;
|
|
417
434
|
const portVal = Number(options.proxyPort ?? process.env["DOMINUSNODE_PROXY_PORT"] ?? 8080);
|
|
418
|
-
this.proxyPort =
|
|
435
|
+
this.proxyPort =
|
|
436
|
+
isNaN(portVal) || portVal < 1 || portVal > 65535 ? 8080 : portVal;
|
|
419
437
|
// H-2: Validate timeout option; store but use REQUEST_TIMEOUT_MS constant for requests
|
|
420
438
|
const tVal = Number(options.timeout ?? 30000);
|
|
421
|
-
this.timeout =
|
|
422
|
-
|
|
439
|
+
this.timeout =
|
|
440
|
+
Number.isFinite(tVal) && tVal >= 1000 && tVal <= 120000 ? tVal : 30000;
|
|
441
|
+
this.agentSecret =
|
|
442
|
+
options.agentSecret || process.env["DOMINUSNODE_AGENT_SECRET"];
|
|
443
|
+
}
|
|
444
|
+
// -----------------------------------------------------------------------
|
|
445
|
+
// SHA-256 Proof-of-Work solver
|
|
446
|
+
// -----------------------------------------------------------------------
|
|
447
|
+
static _countLeadingZeroBits(buf) {
|
|
448
|
+
let count = 0;
|
|
449
|
+
for (const byte of buf) {
|
|
450
|
+
if (byte === 0) {
|
|
451
|
+
count += 8;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
let mask = 0x80;
|
|
455
|
+
while (mask && !(byte & mask)) {
|
|
456
|
+
count++;
|
|
457
|
+
mask >>= 1;
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
return count;
|
|
462
|
+
}
|
|
463
|
+
async _solvePoW() {
|
|
464
|
+
try {
|
|
465
|
+
const resp = await fetch(`${this.baseUrl}/api/auth/pow/challenge`, {
|
|
466
|
+
method: "POST",
|
|
467
|
+
headers: { "Content-Type": "application/json" },
|
|
468
|
+
redirect: "error",
|
|
469
|
+
});
|
|
470
|
+
if (!resp.ok)
|
|
471
|
+
return null;
|
|
472
|
+
const text = await resp.text();
|
|
473
|
+
if (text.length > 10_485_760)
|
|
474
|
+
return null;
|
|
475
|
+
const challenge = JSON.parse(text);
|
|
476
|
+
const prefix = challenge.prefix ?? "";
|
|
477
|
+
const difficulty = challenge.difficulty ?? 20;
|
|
478
|
+
const challengeId = challenge.challengeId ?? "";
|
|
479
|
+
if (!prefix || !challengeId)
|
|
480
|
+
return null;
|
|
481
|
+
for (let nonce = 0; nonce < 100_000_000; nonce++) {
|
|
482
|
+
const hash = crypto
|
|
483
|
+
.createHash("sha256")
|
|
484
|
+
.update(prefix + nonce.toString())
|
|
485
|
+
.digest();
|
|
486
|
+
if (DominusNodeToolkit._countLeadingZeroBits(hash) >= difficulty) {
|
|
487
|
+
return { challengeId, nonce: nonce.toString() };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
423
495
|
}
|
|
424
496
|
// -----------------------------------------------------------------------
|
|
425
497
|
// Authentication
|
|
@@ -427,7 +499,8 @@ class DominusNodeToolkit {
|
|
|
427
499
|
async _authenticate() {
|
|
428
500
|
if (!this.apiKey)
|
|
429
501
|
throw new Error("Dominus Node API key is required. Set DOMINUSNODE_API_KEY env var.");
|
|
430
|
-
if (!this.apiKey.startsWith("dn_live_") &&
|
|
502
|
+
if (!this.apiKey.startsWith("dn_live_") &&
|
|
503
|
+
!this.apiKey.startsWith("dn_test_")) {
|
|
431
504
|
throw new Error('DOMINUSNODE_API_KEY must start with "dn_live_" or "dn_test_".');
|
|
432
505
|
}
|
|
433
506
|
const authHeaders = {
|
|
@@ -461,7 +534,9 @@ class DominusNodeToolkit {
|
|
|
461
534
|
parts.push(value);
|
|
462
535
|
}
|
|
463
536
|
}
|
|
464
|
-
catch {
|
|
537
|
+
catch {
|
|
538
|
+
/* ignore */
|
|
539
|
+
}
|
|
465
540
|
reader.cancel().catch(() => { });
|
|
466
541
|
errorText = Buffer.concat(parts).toString("utf-8").slice(0, 500);
|
|
467
542
|
}
|
|
@@ -473,7 +548,9 @@ class DominusNodeToolkit {
|
|
|
473
548
|
throw new Error("Auth response too large");
|
|
474
549
|
const data = JSON.parse(rawText);
|
|
475
550
|
stripDangerousKeys(data);
|
|
476
|
-
if (!data.token ||
|
|
551
|
+
if (!data.token ||
|
|
552
|
+
typeof data.token !== "string" ||
|
|
553
|
+
data.token.length > 4096) {
|
|
477
554
|
throw new Error("Invalid token in auth response");
|
|
478
555
|
}
|
|
479
556
|
this.token = data.token;
|
|
@@ -489,8 +566,13 @@ class DominusNodeToolkit {
|
|
|
489
566
|
return;
|
|
490
567
|
}
|
|
491
568
|
this._authPromise = this._authenticate()
|
|
492
|
-
.then(() => {
|
|
493
|
-
|
|
569
|
+
.then(() => {
|
|
570
|
+
this._authPromise = null;
|
|
571
|
+
})
|
|
572
|
+
.catch((e) => {
|
|
573
|
+
this._authPromise = null;
|
|
574
|
+
throw e;
|
|
575
|
+
});
|
|
494
576
|
await this._authPromise;
|
|
495
577
|
}
|
|
496
578
|
async _apiRequest(method, path, body) {
|
|
@@ -505,9 +587,16 @@ class DominusNodeToolkit {
|
|
|
505
587
|
headers["X-DominusNode-Agent"] = "mcp";
|
|
506
588
|
headers["X-DominusNode-Agent-Secret"] = this.agentSecret;
|
|
507
589
|
}
|
|
508
|
-
const init = {
|
|
590
|
+
const init = {
|
|
591
|
+
method,
|
|
592
|
+
headers,
|
|
593
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
594
|
+
redirect: "error",
|
|
595
|
+
};
|
|
509
596
|
// M-6: Drop body on safe HTTP methods
|
|
510
|
-
if (body &&
|
|
597
|
+
if (body &&
|
|
598
|
+
!["GET", "HEAD"].includes(method) &&
|
|
599
|
+
["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
|
511
600
|
headers["Content-Type"] = "application/json";
|
|
512
601
|
init.body = JSON.stringify(body);
|
|
513
602
|
}
|
|
@@ -617,11 +706,18 @@ class DominusNodeToolkit {
|
|
|
617
706
|
reject(new Error("Proxy request timed out"));
|
|
618
707
|
}, REQUEST_TIMEOUT_MS);
|
|
619
708
|
if (parsed.protocol === "https:") {
|
|
620
|
-
const connectHost = parsed.hostname.includes(":")
|
|
709
|
+
const connectHost = parsed.hostname.includes(":")
|
|
710
|
+
? `[${parsed.hostname}]`
|
|
711
|
+
: parsed.hostname;
|
|
621
712
|
const connectReq = http.request({
|
|
622
|
-
hostname: this.proxyHost,
|
|
713
|
+
hostname: this.proxyHost,
|
|
714
|
+
port: this.proxyPort,
|
|
715
|
+
method: "CONNECT",
|
|
623
716
|
path: `${connectHost}:${parsed.port || 443}`,
|
|
624
|
-
headers: {
|
|
717
|
+
headers: {
|
|
718
|
+
"Proxy-Authorization": proxyAuth,
|
|
719
|
+
Host: `${connectHost}:${parsed.port || 443}`,
|
|
720
|
+
},
|
|
625
721
|
});
|
|
626
722
|
cleanupFn = () => connectReq.destroy();
|
|
627
723
|
connectReq.on("connect", (_res, sock) => {
|
|
@@ -631,7 +727,12 @@ class DominusNodeToolkit {
|
|
|
631
727
|
reject(new Error(`CONNECT failed: ${_res.statusCode}`));
|
|
632
728
|
return;
|
|
633
729
|
}
|
|
634
|
-
const tlsSock = tls.connect({
|
|
730
|
+
const tlsSock = tls.connect({
|
|
731
|
+
host: parsed.hostname,
|
|
732
|
+
socket: sock,
|
|
733
|
+
servername: parsed.hostname,
|
|
734
|
+
minVersion: "TLSv1.2",
|
|
735
|
+
}, () => {
|
|
635
736
|
const reqLine = `${methodUpper} ${parsed.pathname + parsed.search} HTTP/1.1\r\nHost: ${parsed.host}\r\nUser-Agent: dominusnode-pi/1.0.0\r\nConnection: close\r\n\r\n`;
|
|
636
737
|
tlsSock.write(reqLine);
|
|
637
738
|
const chunks = [];
|
|
@@ -657,27 +758,47 @@ class DominusNodeToolkit {
|
|
|
657
758
|
return;
|
|
658
759
|
}
|
|
659
760
|
const hdr = raw.substring(0, hEnd);
|
|
660
|
-
const body = raw
|
|
661
|
-
|
|
761
|
+
const body = raw
|
|
762
|
+
.substring(hEnd + 4)
|
|
763
|
+
.substring(0, MAX_PROXY_RESPONSE_BYTES);
|
|
764
|
+
const sm = hdr
|
|
765
|
+
.split("\r\n")[0]
|
|
766
|
+
.match(/^HTTP\/\d\.\d\s+(\d+)/);
|
|
662
767
|
resolve({ status: sm ? parseInt(sm[1], 10) : 0, body });
|
|
663
768
|
};
|
|
664
769
|
tlsSock.on("end", fin);
|
|
665
770
|
tlsSock.on("close", fin);
|
|
666
|
-
tlsSock.on("error", (e) => {
|
|
771
|
+
tlsSock.on("error", (e) => {
|
|
772
|
+
clearTimeout(timer);
|
|
773
|
+
reject(e);
|
|
774
|
+
});
|
|
667
775
|
});
|
|
668
776
|
// M-1: Update cleanupFn to destroy tlsSock — covers the TLS handshake window
|
|
669
777
|
cleanupFn = () => tlsSock.destroy();
|
|
670
|
-
tlsSock.on("error", (e) => {
|
|
778
|
+
tlsSock.on("error", (e) => {
|
|
779
|
+
clearTimeout(timer);
|
|
780
|
+
reject(e);
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
connectReq.on("error", (e) => {
|
|
784
|
+
clearTimeout(timer);
|
|
785
|
+
reject(e);
|
|
671
786
|
});
|
|
672
|
-
connectReq.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
673
787
|
connectReq.end();
|
|
674
788
|
}
|
|
675
789
|
else {
|
|
676
790
|
const reqPath = parsed.pathname + parsed.search;
|
|
677
791
|
const proxyReq = http.request({
|
|
678
|
-
hostname: this.proxyHost,
|
|
792
|
+
hostname: this.proxyHost,
|
|
793
|
+
port: this.proxyPort,
|
|
794
|
+
method: methodUpper,
|
|
679
795
|
path: `http://${parsed.host}${reqPath}`,
|
|
680
|
-
headers: {
|
|
796
|
+
headers: {
|
|
797
|
+
"Proxy-Authorization": proxyAuth,
|
|
798
|
+
Host: parsed.host,
|
|
799
|
+
"User-Agent": "dominusnode-pi/1.0.0",
|
|
800
|
+
Connection: "close",
|
|
801
|
+
},
|
|
681
802
|
});
|
|
682
803
|
cleanupFn = () => proxyReq.destroy(); // H-3: destroy on timeout
|
|
683
804
|
const chunks = [];
|
|
@@ -691,10 +812,22 @@ class DominusNodeToolkit {
|
|
|
691
812
|
} // H-4: destroy on oversize
|
|
692
813
|
chunks.push(c);
|
|
693
814
|
});
|
|
694
|
-
r.on("end", () => {
|
|
695
|
-
|
|
815
|
+
r.on("end", () => {
|
|
816
|
+
clearTimeout(timer);
|
|
817
|
+
const body = Buffer.concat(chunks)
|
|
818
|
+
.toString("utf-8")
|
|
819
|
+
.substring(0, MAX_PROXY_RESPONSE_BYTES);
|
|
820
|
+
resolve({ status: r.statusCode ?? 0, body });
|
|
821
|
+
});
|
|
822
|
+
r.on("error", (e) => {
|
|
823
|
+
clearTimeout(timer);
|
|
824
|
+
reject(e);
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
proxyReq.on("error", (e) => {
|
|
828
|
+
clearTimeout(timer);
|
|
829
|
+
reject(e);
|
|
696
830
|
});
|
|
697
|
-
proxyReq.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
698
831
|
proxyReq.end();
|
|
699
832
|
}
|
|
700
833
|
});
|
|
@@ -888,7 +1021,10 @@ class DominusNodeToolkit {
|
|
|
888
1021
|
const max = Math.max(1, Math.min(100, Math.floor(Number(maxMembers))));
|
|
889
1022
|
if (isNaN(max))
|
|
890
1023
|
return err("maxMembers must be a number between 1 and 100");
|
|
891
|
-
const data = await this._req("POST", "/api/teams", {
|
|
1024
|
+
const data = await this._req("POST", "/api/teams", {
|
|
1025
|
+
name: name.trim(),
|
|
1026
|
+
maxMembers: max,
|
|
1027
|
+
});
|
|
892
1028
|
return ok(JSON.stringify(data, null, 2));
|
|
893
1029
|
}
|
|
894
1030
|
catch (e) {
|
|
@@ -950,7 +1086,8 @@ class DominusNodeToolkit {
|
|
|
950
1086
|
if (labelErr)
|
|
951
1087
|
return err(labelErr);
|
|
952
1088
|
const data = await this._req("POST", `/api/teams/${encodeURIComponent(id)}/keys`, { label: label.trim() });
|
|
953
|
-
return ok(JSON.stringify(data, null, 2) +
|
|
1089
|
+
return ok(JSON.stringify(data, null, 2) +
|
|
1090
|
+
"\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
|
|
954
1091
|
}
|
|
955
1092
|
catch (e) {
|
|
956
1093
|
return err(e);
|
|
@@ -1027,7 +1164,9 @@ class DominusNodeToolkit {
|
|
|
1027
1164
|
const amount = Math.floor(Number(amountCents));
|
|
1028
1165
|
if (isNaN(amount) || amount < 500 || amount > 1000000)
|
|
1029
1166
|
return err("amountCents must be between 500 ($5.00) and 1,000,000 ($10,000.00)");
|
|
1030
|
-
const data = await this._req("POST", "/api/wallet/topup/paypal", {
|
|
1167
|
+
const data = await this._req("POST", "/api/wallet/topup/paypal", {
|
|
1168
|
+
amountCents: amount,
|
|
1169
|
+
});
|
|
1031
1170
|
return ok(JSON.stringify(data, null, 2));
|
|
1032
1171
|
}
|
|
1033
1172
|
catch (e) {
|
|
@@ -1042,7 +1181,9 @@ class DominusNodeToolkit {
|
|
|
1042
1181
|
const amount = Math.floor(Number(amountCents));
|
|
1043
1182
|
if (isNaN(amount) || amount < 500 || amount > 1000000)
|
|
1044
1183
|
return err("amountCents must be between 500 ($5.00) and 1,000,000 ($10,000.00)");
|
|
1045
|
-
const data = await this._req("POST", "/api/wallet/topup/stripe", {
|
|
1184
|
+
const data = await this._req("POST", "/api/wallet/topup/stripe", {
|
|
1185
|
+
amountCents: amount,
|
|
1186
|
+
});
|
|
1046
1187
|
return ok(JSON.stringify(data, null, 2));
|
|
1047
1188
|
}
|
|
1048
1189
|
catch (e) {
|
|
@@ -1059,11 +1200,26 @@ class DominusNodeToolkit {
|
|
|
1059
1200
|
if (!Number.isInteger(rawAmount) || rawAmount < 5 || rawAmount > 10000)
|
|
1060
1201
|
return err("amountUsd must be a whole number between 5 and 10,000");
|
|
1061
1202
|
const amount = rawAmount;
|
|
1062
|
-
const VALID_CURRENCIES = [
|
|
1203
|
+
const VALID_CURRENCIES = [
|
|
1204
|
+
"BTC",
|
|
1205
|
+
"ETH",
|
|
1206
|
+
"LTC",
|
|
1207
|
+
"XMR",
|
|
1208
|
+
"ZEC",
|
|
1209
|
+
"USDC",
|
|
1210
|
+
"SOL",
|
|
1211
|
+
"USDT",
|
|
1212
|
+
"DAI",
|
|
1213
|
+
"BNB",
|
|
1214
|
+
"LINK",
|
|
1215
|
+
];
|
|
1063
1216
|
const cur = (currency || "").toUpperCase().trim();
|
|
1064
1217
|
if (!VALID_CURRENCIES.includes(cur))
|
|
1065
1218
|
return err(`currency must be one of: ${VALID_CURRENCIES.join(", ")}`);
|
|
1066
|
-
const data = await this._req("POST", "/api/wallet/topup/crypto", {
|
|
1219
|
+
const data = await this._req("POST", "/api/wallet/topup/crypto", {
|
|
1220
|
+
amountUsd: amount,
|
|
1221
|
+
currency: cur,
|
|
1222
|
+
});
|
|
1067
1223
|
return ok(JSON.stringify(data, null, 2));
|
|
1068
1224
|
}
|
|
1069
1225
|
catch (e) {
|
|
@@ -1132,7 +1288,12 @@ class DominusNodeToolkit {
|
|
|
1132
1288
|
headers["X-DominusNode-Agent"] = "mcp";
|
|
1133
1289
|
headers["X-DominusNode-Agent-Secret"] = this.agentSecret;
|
|
1134
1290
|
}
|
|
1135
|
-
const init = {
|
|
1291
|
+
const init = {
|
|
1292
|
+
method,
|
|
1293
|
+
headers,
|
|
1294
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
1295
|
+
redirect: "error",
|
|
1296
|
+
};
|
|
1136
1297
|
if (body && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
|
1137
1298
|
headers["Content-Type"] = "application/json";
|
|
1138
1299
|
init.body = JSON.stringify(body);
|
|
@@ -1309,7 +1470,15 @@ class DominusNodeToolkit {
|
|
|
1309
1470
|
try {
|
|
1310
1471
|
const safeEmail = DominusNodeToolkit.validateEmail(email);
|
|
1311
1472
|
const safePassword = DominusNodeToolkit.validatePassword(password);
|
|
1312
|
-
|
|
1473
|
+
// Solve PoW for CAPTCHA-free registration
|
|
1474
|
+
const pow = await this._solvePoW();
|
|
1475
|
+
const regBody = {
|
|
1476
|
+
email: safeEmail,
|
|
1477
|
+
password: safePassword,
|
|
1478
|
+
};
|
|
1479
|
+
if (pow)
|
|
1480
|
+
regBody.pow = pow;
|
|
1481
|
+
const data = await this._rawRequest("POST", "/api/auth/register", regBody);
|
|
1313
1482
|
return ok(JSON.stringify(data, null, 2));
|
|
1314
1483
|
}
|
|
1315
1484
|
catch (e) {
|
|
@@ -1323,7 +1492,10 @@ class DominusNodeToolkit {
|
|
|
1323
1492
|
try {
|
|
1324
1493
|
const safeEmail = DominusNodeToolkit.validateEmail(email);
|
|
1325
1494
|
const safePassword = DominusNodeToolkit.validatePassword(password);
|
|
1326
|
-
const data = await this._rawRequest("POST", "/api/auth/login", {
|
|
1495
|
+
const data = await this._rawRequest("POST", "/api/auth/login", {
|
|
1496
|
+
email: safeEmail,
|
|
1497
|
+
password: safePassword,
|
|
1498
|
+
});
|
|
1327
1499
|
return ok(JSON.stringify(data, null, 2));
|
|
1328
1500
|
}
|
|
1329
1501
|
catch (e) {
|
|
@@ -1353,7 +1525,9 @@ class DominusNodeToolkit {
|
|
|
1353
1525
|
if (/[\x00-\x1f\x7f]/.test(token)) {
|
|
1354
1526
|
return err("Token contains invalid control characters.");
|
|
1355
1527
|
}
|
|
1356
|
-
const data = await this._rawRequest("POST", "/api/auth/verify-email", {
|
|
1528
|
+
const data = await this._rawRequest("POST", "/api/auth/verify-email", {
|
|
1529
|
+
token: token.trim(),
|
|
1530
|
+
});
|
|
1357
1531
|
return ok(JSON.stringify(data, null, 2));
|
|
1358
1532
|
}
|
|
1359
1533
|
catch (e) {
|
|
@@ -1379,7 +1553,10 @@ class DominusNodeToolkit {
|
|
|
1379
1553
|
try {
|
|
1380
1554
|
DominusNodeToolkit.validatePassword(currentPassword);
|
|
1381
1555
|
DominusNodeToolkit.validatePassword(newPassword);
|
|
1382
|
-
const data = await this._req("POST", "/api/auth/change-password", {
|
|
1556
|
+
const data = await this._req("POST", "/api/auth/change-password", {
|
|
1557
|
+
currentPassword,
|
|
1558
|
+
newPassword,
|
|
1559
|
+
});
|
|
1383
1560
|
return ok(JSON.stringify(data, null, 2));
|
|
1384
1561
|
}
|
|
1385
1562
|
catch (e) {
|
|
@@ -1406,8 +1583,11 @@ class DominusNodeToolkit {
|
|
|
1406
1583
|
const labelErr = validateLabel(label);
|
|
1407
1584
|
if (labelErr)
|
|
1408
1585
|
return err(labelErr);
|
|
1409
|
-
const data = await this._req("POST", "/api/keys", {
|
|
1410
|
-
|
|
1586
|
+
const data = await this._req("POST", "/api/keys", {
|
|
1587
|
+
label: label.trim(),
|
|
1588
|
+
});
|
|
1589
|
+
return ok(JSON.stringify(data, null, 2) +
|
|
1590
|
+
"\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
|
|
1411
1591
|
}
|
|
1412
1592
|
catch (e) {
|
|
1413
1593
|
return err(e);
|
|
@@ -1456,7 +1636,9 @@ class DominusNodeToolkit {
|
|
|
1456
1636
|
async changePlan(planId) {
|
|
1457
1637
|
try {
|
|
1458
1638
|
const id = validateUuid(planId, "planId");
|
|
1459
|
-
const data = await this._req("PUT", "/api/plans/user/plan", {
|
|
1639
|
+
const data = await this._req("PUT", "/api/plans/user/plan", {
|
|
1640
|
+
planId: id,
|
|
1641
|
+
});
|
|
1460
1642
|
return ok(JSON.stringify(data, null, 2));
|
|
1461
1643
|
}
|
|
1462
1644
|
catch (e) {
|
|
@@ -1589,5 +1771,83 @@ class DominusNodeToolkit {
|
|
|
1589
1771
|
return err(e);
|
|
1590
1772
|
}
|
|
1591
1773
|
}
|
|
1774
|
+
// -----------------------------------------------------------------------
|
|
1775
|
+
// Tool 54 (MPP): mppInfo
|
|
1776
|
+
// -----------------------------------------------------------------------
|
|
1777
|
+
async mppInfo() {
|
|
1778
|
+
try {
|
|
1779
|
+
const data = await this._req("GET", "/api/mpp/info");
|
|
1780
|
+
return ok(JSON.stringify(data, null, 2));
|
|
1781
|
+
}
|
|
1782
|
+
catch (e) {
|
|
1783
|
+
return err(e);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
// -----------------------------------------------------------------------
|
|
1787
|
+
// Tool 55 (MPP): payMpp
|
|
1788
|
+
// -----------------------------------------------------------------------
|
|
1789
|
+
async payMpp(amountCents, method) {
|
|
1790
|
+
if (!Number.isInteger(amountCents) ||
|
|
1791
|
+
amountCents < 500 ||
|
|
1792
|
+
amountCents > 100_000) {
|
|
1793
|
+
return err("amount_cents must be an integer between 500 and 100,000");
|
|
1794
|
+
}
|
|
1795
|
+
if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
|
|
1796
|
+
return err("method must be one of: tempo, stripe_spt, lightning");
|
|
1797
|
+
}
|
|
1798
|
+
try {
|
|
1799
|
+
const data = await this._req("POST", "/api/mpp/topup", {
|
|
1800
|
+
amountCents,
|
|
1801
|
+
method,
|
|
1802
|
+
});
|
|
1803
|
+
return ok(JSON.stringify(data, null, 2));
|
|
1804
|
+
}
|
|
1805
|
+
catch (e) {
|
|
1806
|
+
return err(e);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
// -----------------------------------------------------------------------
|
|
1810
|
+
// Tool 56 (MPP): mppSessionOpen
|
|
1811
|
+
// -----------------------------------------------------------------------
|
|
1812
|
+
async mppSessionOpen(maxDepositCents, method, poolType = "dc") {
|
|
1813
|
+
if (!Number.isInteger(maxDepositCents) ||
|
|
1814
|
+
maxDepositCents < 500 ||
|
|
1815
|
+
maxDepositCents > 100_000) {
|
|
1816
|
+
return err("max_deposit_cents must be an integer between 500 and 100,000");
|
|
1817
|
+
}
|
|
1818
|
+
if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
|
|
1819
|
+
return err("method must be one of: tempo, stripe_spt, lightning");
|
|
1820
|
+
}
|
|
1821
|
+
if (!["dc", "residential"].includes(poolType)) {
|
|
1822
|
+
return err("pool_type must be dc or residential");
|
|
1823
|
+
}
|
|
1824
|
+
try {
|
|
1825
|
+
const data = await this._req("POST", "/api/mpp/session/open", {
|
|
1826
|
+
maxDepositCents,
|
|
1827
|
+
method,
|
|
1828
|
+
poolType,
|
|
1829
|
+
});
|
|
1830
|
+
return ok(JSON.stringify(data, null, 2));
|
|
1831
|
+
}
|
|
1832
|
+
catch (e) {
|
|
1833
|
+
return err(e);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
// -----------------------------------------------------------------------
|
|
1837
|
+
// Tool 57 (MPP): mppSessionClose
|
|
1838
|
+
// -----------------------------------------------------------------------
|
|
1839
|
+
async mppSessionClose(channelId) {
|
|
1840
|
+
if (!channelId)
|
|
1841
|
+
return err("channel_id is required");
|
|
1842
|
+
try {
|
|
1843
|
+
const data = await this._req("POST", "/api/mpp/session/close", {
|
|
1844
|
+
channelId,
|
|
1845
|
+
});
|
|
1846
|
+
return ok(JSON.stringify(data, null, 2));
|
|
1847
|
+
}
|
|
1848
|
+
catch (e) {
|
|
1849
|
+
return err(e);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1592
1852
|
}
|
|
1593
1853
|
exports.DominusNodeToolkit = DominusNodeToolkit;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dominusnode/pi-extension",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Dominus Node pi-mono extension — 26 tools for rotating proxy, wallet, agentic wallets, teams, and payments",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc",
|
|
32
|
+
"prepare": "npm run build",
|
|
32
33
|
"test": "vitest run",
|
|
33
34
|
"lint": "tsc --noEmit"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@sinclair/typebox": "^0.34.0",
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
37
39
|
"typescript": "^5.4.0",
|
|
38
40
|
"vitest": "^1.6.0"
|
|
39
41
|
},
|