safehands-pharos 1.4.0 → 1.5.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 (124) hide show
  1. package/README.md +174 -63
  2. package/dist/cli.d.ts +5 -5
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +126 -124
  5. package/dist/cli.js.map +1 -1
  6. package/dist/demo.d.ts +1 -1
  7. package/dist/demo.js +171 -171
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +73 -65
  11. package/dist/index.js.map +1 -1
  12. package/dist/init.d.ts +1 -1
  13. package/dist/init.js +65 -65
  14. package/dist/lib/constants.d.ts +303 -291
  15. package/dist/lib/constants.d.ts.map +1 -1
  16. package/dist/lib/constants.js +302 -292
  17. package/dist/lib/constants.js.map +1 -1
  18. package/dist/lib/dodoApi.d.ts +78 -78
  19. package/dist/lib/dodoApi.js +196 -196
  20. package/dist/lib/envLoader.d.ts +2 -0
  21. package/dist/lib/envLoader.d.ts.map +1 -0
  22. package/dist/lib/envLoader.js +44 -0
  23. package/dist/lib/envLoader.js.map +1 -0
  24. package/dist/lib/http.d.ts +14 -14
  25. package/dist/lib/http.js +118 -118
  26. package/dist/lib/pharosClient.d.ts +58 -58
  27. package/dist/lib/pharosClient.js +63 -63
  28. package/dist/lib/policy/actionPolicyEngine.d.ts +53 -53
  29. package/dist/lib/policy/actionPolicyEngine.js +212 -212
  30. package/dist/lib/riskEngine.d.ts +26 -26
  31. package/dist/lib/riskEngine.d.ts.map +1 -1
  32. package/dist/lib/riskEngine.js +288 -283
  33. package/dist/lib/riskEngine.js.map +1 -1
  34. package/dist/lib/signer/index.d.ts +24 -24
  35. package/dist/lib/signer/index.js +88 -88
  36. package/dist/lib/testDodoLive.d.ts +1 -1
  37. package/dist/lib/testDodoLive.js +104 -104
  38. package/dist/lib/testLiveSafehands.d.ts +1 -1
  39. package/dist/lib/testLiveSafehands.js +92 -92
  40. package/dist/lib/testRpcLive.d.ts +1 -1
  41. package/dist/lib/testRpcLive.js +88 -88
  42. package/dist/lib/testTools.js +397 -398
  43. package/dist/lib/testX402Live.d.ts +1 -1
  44. package/dist/lib/testX402Live.js +159 -159
  45. package/dist/lib/toolResponse.d.ts +25 -25
  46. package/dist/lib/toolResponse.js +53 -53
  47. package/dist/lib/wallet/index.d.ts +37 -37
  48. package/dist/lib/wallet/index.js +128 -128
  49. package/dist/scripts/checkDeploy.d.ts +1 -1
  50. package/dist/scripts/checkDeploy.js +24 -24
  51. package/dist/scripts/deployRegistry.d.ts +1 -1
  52. package/dist/scripts/deployRegistry.js +100 -100
  53. package/dist/scripts/testRegistry.d.ts +1 -1
  54. package/dist/scripts/testRegistry.js +43 -43
  55. package/dist/tools/approveToken.d.ts +45 -45
  56. package/dist/tools/approveToken.js +85 -85
  57. package/dist/tools/assessRisk.d.ts +79 -79
  58. package/dist/tools/assessRisk.js +104 -104
  59. package/dist/tools/checkAllowance.d.ts +43 -43
  60. package/dist/tools/checkAllowance.js +56 -56
  61. package/dist/tools/checkTokenSecurity.d.ts +46 -46
  62. package/dist/tools/checkTokenSecurity.js +95 -95
  63. package/dist/tools/createAgentWallet.d.ts +28 -26
  64. package/dist/tools/createAgentWallet.d.ts.map +1 -1
  65. package/dist/tools/createAgentWallet.js +82 -58
  66. package/dist/tools/createAgentWallet.js.map +1 -1
  67. package/dist/tools/estimateGas.d.ts +79 -79
  68. package/dist/tools/estimateGas.js +124 -124
  69. package/dist/tools/executeSwap.d.ts +61 -61
  70. package/dist/tools/executeSwap.js +141 -141
  71. package/dist/tools/explainRisk.d.ts +29 -29
  72. package/dist/tools/explainRisk.js +32 -32
  73. package/dist/tools/getAgentWallet.d.ts +21 -21
  74. package/dist/tools/getAgentWallet.js +27 -27
  75. package/dist/tools/getAgentWalletBalance.d.ts +11 -11
  76. package/dist/tools/getAgentWalletBalance.js +70 -70
  77. package/dist/tools/getExecutionHistory.d.ts +49 -49
  78. package/dist/tools/getExecutionHistory.js +154 -154
  79. package/dist/tools/getGasPrice.d.ts +43 -43
  80. package/dist/tools/getGasPrice.js +59 -59
  81. package/dist/tools/getPoolInfo.d.ts +75 -75
  82. package/dist/tools/getPoolInfo.js +137 -137
  83. package/dist/tools/getTokenPrice.d.ts +113 -113
  84. package/dist/tools/getTokenPrice.js +117 -117
  85. package/dist/tools/getTransactionStatus.d.ts +43 -43
  86. package/dist/tools/getTransactionStatus.js +59 -59
  87. package/dist/tools/getWalletBalance.d.ts +68 -68
  88. package/dist/tools/getWalletBalance.js +87 -87
  89. package/dist/tools/publishRiskScore.d.ts +63 -63
  90. package/dist/tools/publishRiskScore.js +88 -88
  91. package/dist/tools/queryRiskRegistry.d.ts +38 -38
  92. package/dist/tools/queryRiskRegistry.js +55 -55
  93. package/dist/tools/safehandsPreflightCheck.d.ts +77 -77
  94. package/dist/tools/safehandsPreflightCheck.js +47 -47
  95. package/dist/tools/safehandsRiskReport.d.ts +81 -81
  96. package/dist/tools/safehandsRiskReport.js +28 -28
  97. package/dist/tools/safehandsSafeExecute.d.ts +20 -20
  98. package/dist/tools/safehandsSafeExecute.js +81 -81
  99. package/dist/tools/safehandsWalletHealth.d.ts +14 -14
  100. package/dist/tools/safehandsWalletHealth.js +103 -103
  101. package/dist/tools/safehandsX402Preflight.d.ts +26 -26
  102. package/dist/tools/safehandsX402Preflight.js +65 -65
  103. package/dist/tools/sendPayment.d.ts +57 -57
  104. package/dist/tools/sendPayment.js +117 -117
  105. package/dist/tools/simulateTransaction.d.ts +60 -60
  106. package/dist/tools/simulateTransaction.js +83 -83
  107. package/dist/tools/tokenRegistryStatus.d.ts +26 -26
  108. package/dist/tools/tokenRegistryStatus.js +96 -96
  109. package/dist/tools/x402PayAndFetch.d.ts +81 -81
  110. package/dist/tools/x402PayAndFetch.js +152 -152
  111. package/dist/x402Server.d.ts +1 -1
  112. package/dist/x402Server.js +300 -252
  113. package/dist/x402Server.js.map +1 -1
  114. package/package.json +6 -16
  115. package/examples/dashboard/index.html +0 -337
  116. package/examples/pharos-skill-engine/SKILL.safehands.md +0 -85
  117. package/examples/pharos-skill-engine/assets/safehands/example-actions.json +0 -49
  118. package/examples/pharos-skill-engine/assets/safehands/policy-defaults.json +0 -11
  119. package/examples/pharos-skill-engine/references/safehands.md +0 -345
  120. package/examples/scenario-hack.ts +0 -38
  121. package/skill/SKILL.md +0 -133
  122. package/skill/assets/safehands/example-actions.json +0 -49
  123. package/skill/assets/safehands/policy-defaults.json +0 -11
  124. package/skill/references/safehands.md +0 -345
package/dist/demo.js CHANGED
@@ -1,172 +1,172 @@
1
- // ─── SafeHands Deterministic Hackathon Demo ────────────────────────────
2
- // Non-destructive demo for judges and reviewers. It uses offline/sample-safe
3
- // paths and a local HTTP server only when explicitly allowed in-process.
4
- // No real transaction is broadcast.
5
- // ────────────────────────────────────────────────────────────────────────
6
- import express from "express";
7
- import { handleSafeHandsWalletHealth } from "./tools/safehandsWalletHealth.js";
8
- import { handleSafeHandsPreflightCheck } from "./tools/safehandsPreflightCheck.js";
9
- import { handleTokenRegistryStatus } from "./tools/tokenRegistryStatus.js";
10
- import { handleSafeHandsX402Preflight } from "./tools/safehandsX402Preflight.js";
11
- import { handleX402PayAndFetch } from "./tools/x402PayAndFetch.js";
12
- import { handleSendPayment } from "./tools/sendPayment.js";
13
- import { handleExplainRisk } from "./tools/explainRisk.js";
14
- import { CHAIN_ID, PHAROS_ENVIRONMENT, USDC_ADDRESS } from "./lib/constants.js";
15
- const RECIPIENT = "0x0000000000000000000000000000000000000001";
16
- function section(title) {
17
- console.log(`\n${"═".repeat(78)}`);
18
- console.log(` ${title}`);
19
- console.log("═".repeat(78));
20
- }
21
- function print(label, value) {
22
- console.log(`\n${label}`);
23
- console.log(JSON.stringify(value, null, 2));
24
- }
25
- function setEnv(key, value) {
26
- const previous = process.env[key];
27
- if (value === undefined)
28
- delete process.env[key];
29
- else
30
- process.env[key] = value;
31
- return () => {
32
- if (previous === undefined)
33
- delete process.env[key];
34
- else
35
- process.env[key] = previous;
36
- };
37
- }
38
- async function withLocalX402DemoServer(handler) {
39
- const app = express();
40
- app.get("/supported", (_req, res) => {
41
- res.json({
42
- ok: true,
43
- demo: true,
44
- paymentRequired: false,
45
- endpoints: ["/supported", "/paid"],
46
- network: `eip155:${CHAIN_ID}`,
47
- });
48
- });
49
- app.get("/paid", (_req, res) => {
50
- res.status(402).json({
51
- error: "Payment Required",
52
- demo: true,
53
- price: "0.001",
54
- asset: USDC_ADDRESS,
55
- network: `eip155:${CHAIN_ID}`,
56
- });
57
- });
58
- const server = await new Promise((resolve) => {
59
- const instance = app.listen(0, "127.0.0.1", () => resolve(instance));
60
- });
61
- try {
62
- const address = server.address();
63
- if (!address || typeof address === "string")
64
- throw new Error("Failed to open local demo server");
65
- return await handler(`http://127.0.0.1:${address.port}`);
66
- }
67
- finally {
68
- if ("closeAllConnections" in server)
69
- server.closeAllConnections();
70
- await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
71
- }
72
- }
73
- export async function runDemo() {
74
- const restoreWalletMode = setEnv("WALLET_MODE", process.env.WALLET_MODE || "none");
75
- const restoreWrite = setEnv("WRITE_TOOLS_ENABLED", "false");
76
- const restorePrivateKey = setEnv("PRIVATE_KEY", undefined);
77
- const restoreX402Signer = setEnv("X402_SIGNER_PRIVATE_KEY", undefined);
78
- try {
79
- console.log("\n🛡️ SafeHands-Pharos Deterministic Demo");
80
- console.log(` Environment: ${PHAROS_ENVIRONMENT}`);
81
- console.log(` Chain ID: ${CHAIN_ID}`);
82
- console.log(" Mode: non-destructive demo, no real transactions broadcast");
83
- section("1. Wallet Health");
84
- print("safehands_wallet_health", await handleSafeHandsWalletHealth({}));
85
- section("2. Safe Payment Preflight: ALLOW");
86
- print("safehands_preflight_check", await handleSafeHandsPreflightCheck({
87
- actionType: "send_payment",
88
- chainId: CHAIN_ID,
89
- isMainnet: false,
90
- amount: "0.001",
91
- amountUnit: "PHRS",
92
- recipient: RECIPIENT,
93
- }));
94
- section("3. Unlimited Approval Preflight: BLOCK");
95
- const blockedApproval = await handleSafeHandsPreflightCheck({
96
- actionType: "approve_token",
97
- chainId: CHAIN_ID,
98
- isMainnet: false,
99
- approvalAmount: "max",
100
- spender: RECIPIENT,
101
- });
102
- print("safehands_preflight_check", blockedApproval);
103
- section("4. Token Registry Status");
104
- print("token_registry_status", await handleTokenRegistryStatus({ tokenAddress: USDC_ADDRESS }));
105
- section("5. x402 Preflight Without Private Key");
106
- await withLocalX402DemoServer(async (baseUrl) => {
107
- const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
108
- try {
109
- print("safehands_x402_preflight", await handleSafeHandsX402Preflight({
110
- url: `${baseUrl}/paid`,
111
- paymentAmountUsdc: "0.001",
112
- probeEndpoint: false,
113
- }));
114
- }
115
- finally {
116
- restoreLocal();
117
- }
118
- });
119
- section("6. x402 /supported Free Endpoint Without Private Key");
120
- await withLocalX402DemoServer(async (baseUrl) => {
121
- const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
122
- try {
123
- print("x402_pay_and_fetch free endpoint", await handleX402PayAndFetch({ url: `${baseUrl}/supported` }));
124
- }
125
- finally {
126
- restoreLocal();
127
- }
128
- });
129
- section("7. x402 Paid Endpoint Without Signer");
130
- await withLocalX402DemoServer(async (baseUrl) => {
131
- const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
132
- try {
133
- print("x402_pay_and_fetch paid endpoint", await handleX402PayAndFetch({ url: `${baseUrl}/paid`, maxPaymentUsdc: "0.001" }));
134
- }
135
- finally {
136
- restoreLocal();
137
- }
138
- });
139
- section("8. SSRF_BLOCKED");
140
- const restoreLocalBlocked = setEnv("ALLOW_LOCAL_X402_FETCH", undefined);
141
- try {
142
- print("safehands_x402_preflight localhost blocked", await handleSafeHandsX402Preflight({ url: "http://127.0.0.1:4021/supported" }));
143
- }
144
- finally {
145
- restoreLocalBlocked();
146
- }
147
- section("9. WRITE_TOOLS_DISABLED");
148
- print("send_payment guarded", await handleSendPayment({ toAddress: RECIPIENT, amount: "0.001" }));
149
- section("10. Human-Readable Risk Explanation");
150
- print("explain_risk", await handleExplainRisk({
151
- decision: "BLOCK",
152
- riskLevel: "HIGH",
153
- reasons: ["Unlimited approval requested"],
154
- requiredActions: ["Use a limited approval amount before approving a spender"],
155
- }));
156
- section("Demo Complete");
157
- console.log("SafeHands demonstrated wallet health, ALLOW/BLOCK policy, token registry, x402 safety, SSRF blocking, write-tool guardrails, and explainable risk output.");
158
- }
159
- finally {
160
- restoreWalletMode();
161
- restoreWrite();
162
- restorePrivateKey();
163
- restoreX402Signer();
164
- }
165
- }
166
- if (import.meta.url === `file://${process.argv[1]}`) {
167
- runDemo().catch((err) => {
168
- console.error("Demo error:", err instanceof Error ? err.message : String(err));
169
- process.exit(1);
170
- });
171
- }
1
+ // ─── SafeHands Deterministic Hackathon Demo ────────────────────────────
2
+ // Non-destructive demo for judges and reviewers. It uses offline/sample-safe
3
+ // paths and a local HTTP server only when explicitly allowed in-process.
4
+ // No real transaction is broadcast.
5
+ // ────────────────────────────────────────────────────────────────────────
6
+ import express from "express";
7
+ import { handleSafeHandsWalletHealth } from "./tools/safehandsWalletHealth.js";
8
+ import { handleSafeHandsPreflightCheck } from "./tools/safehandsPreflightCheck.js";
9
+ import { handleTokenRegistryStatus } from "./tools/tokenRegistryStatus.js";
10
+ import { handleSafeHandsX402Preflight } from "./tools/safehandsX402Preflight.js";
11
+ import { handleX402PayAndFetch } from "./tools/x402PayAndFetch.js";
12
+ import { handleSendPayment } from "./tools/sendPayment.js";
13
+ import { handleExplainRisk } from "./tools/explainRisk.js";
14
+ import { CHAIN_ID, PHAROS_ENVIRONMENT, USDC_ADDRESS } from "./lib/constants.js";
15
+ const RECIPIENT = "0x0000000000000000000000000000000000000001";
16
+ function section(title) {
17
+ console.log(`\n${"═".repeat(78)}`);
18
+ console.log(` ${title}`);
19
+ console.log("═".repeat(78));
20
+ }
21
+ function print(label, value) {
22
+ console.log(`\n${label}`);
23
+ console.log(JSON.stringify(value, null, 2));
24
+ }
25
+ function setEnv(key, value) {
26
+ const previous = process.env[key];
27
+ if (value === undefined)
28
+ delete process.env[key];
29
+ else
30
+ process.env[key] = value;
31
+ return () => {
32
+ if (previous === undefined)
33
+ delete process.env[key];
34
+ else
35
+ process.env[key] = previous;
36
+ };
37
+ }
38
+ async function withLocalX402DemoServer(handler) {
39
+ const app = express();
40
+ app.get("/supported", (_req, res) => {
41
+ res.json({
42
+ ok: true,
43
+ demo: true,
44
+ paymentRequired: false,
45
+ endpoints: ["/supported", "/paid"],
46
+ network: `eip155:${CHAIN_ID}`,
47
+ });
48
+ });
49
+ app.get("/paid", (_req, res) => {
50
+ res.status(402).json({
51
+ error: "Payment Required",
52
+ demo: true,
53
+ price: "0.001",
54
+ asset: USDC_ADDRESS,
55
+ network: `eip155:${CHAIN_ID}`,
56
+ });
57
+ });
58
+ const server = await new Promise((resolve) => {
59
+ const instance = app.listen(0, "127.0.0.1", () => resolve(instance));
60
+ });
61
+ try {
62
+ const address = server.address();
63
+ if (!address || typeof address === "string")
64
+ throw new Error("Failed to open local demo server");
65
+ return await handler(`http://127.0.0.1:${address.port}`);
66
+ }
67
+ finally {
68
+ if ("closeAllConnections" in server)
69
+ server.closeAllConnections();
70
+ await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
71
+ }
72
+ }
73
+ export async function runDemo() {
74
+ const restoreWalletMode = setEnv("WALLET_MODE", process.env.WALLET_MODE || "none");
75
+ const restoreWrite = setEnv("WRITE_TOOLS_ENABLED", "false");
76
+ const restorePrivateKey = setEnv("PRIVATE_KEY", undefined);
77
+ const restoreX402Signer = setEnv("X402_SIGNER_PRIVATE_KEY", undefined);
78
+ try {
79
+ console.log("\n🛡️ SafeHands-Pharos Deterministic Demo");
80
+ console.log(` Environment: ${PHAROS_ENVIRONMENT}`);
81
+ console.log(` Chain ID: ${CHAIN_ID}`);
82
+ console.log(" Mode: non-destructive demo, no real transactions broadcast");
83
+ section("1. Wallet Health");
84
+ print("safehands_wallet_health", await handleSafeHandsWalletHealth({}));
85
+ section("2. Safe Payment Preflight: ALLOW");
86
+ print("safehands_preflight_check", await handleSafeHandsPreflightCheck({
87
+ actionType: "send_payment",
88
+ chainId: CHAIN_ID,
89
+ isMainnet: false,
90
+ amount: "0.001",
91
+ amountUnit: "PHRS",
92
+ recipient: RECIPIENT,
93
+ }));
94
+ section("3. Unlimited Approval Preflight: BLOCK");
95
+ const blockedApproval = await handleSafeHandsPreflightCheck({
96
+ actionType: "approve_token",
97
+ chainId: CHAIN_ID,
98
+ isMainnet: false,
99
+ approvalAmount: "max",
100
+ spender: RECIPIENT,
101
+ });
102
+ print("safehands_preflight_check", blockedApproval);
103
+ section("4. Token Registry Status");
104
+ print("token_registry_status", await handleTokenRegistryStatus({ tokenAddress: USDC_ADDRESS }));
105
+ section("5. x402 Preflight Without Private Key");
106
+ await withLocalX402DemoServer(async (baseUrl) => {
107
+ const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
108
+ try {
109
+ print("safehands_x402_preflight", await handleSafeHandsX402Preflight({
110
+ url: `${baseUrl}/paid`,
111
+ paymentAmountUsdc: "0.001",
112
+ probeEndpoint: false,
113
+ }));
114
+ }
115
+ finally {
116
+ restoreLocal();
117
+ }
118
+ });
119
+ section("6. x402 /supported Free Endpoint Without Private Key");
120
+ await withLocalX402DemoServer(async (baseUrl) => {
121
+ const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
122
+ try {
123
+ print("x402_pay_and_fetch free endpoint", await handleX402PayAndFetch({ url: `${baseUrl}/supported` }));
124
+ }
125
+ finally {
126
+ restoreLocal();
127
+ }
128
+ });
129
+ section("7. x402 Paid Endpoint Without Signer");
130
+ await withLocalX402DemoServer(async (baseUrl) => {
131
+ const restoreLocal = setEnv("ALLOW_LOCAL_X402_FETCH", "true");
132
+ try {
133
+ print("x402_pay_and_fetch paid endpoint", await handleX402PayAndFetch({ url: `${baseUrl}/paid`, maxPaymentUsdc: "0.001" }));
134
+ }
135
+ finally {
136
+ restoreLocal();
137
+ }
138
+ });
139
+ section("8. SSRF_BLOCKED");
140
+ const restoreLocalBlocked = setEnv("ALLOW_LOCAL_X402_FETCH", undefined);
141
+ try {
142
+ print("safehands_x402_preflight localhost blocked", await handleSafeHandsX402Preflight({ url: "http://127.0.0.1:4021/supported" }));
143
+ }
144
+ finally {
145
+ restoreLocalBlocked();
146
+ }
147
+ section("9. WRITE_TOOLS_DISABLED");
148
+ print("send_payment guarded", await handleSendPayment({ toAddress: RECIPIENT, amount: "0.001" }));
149
+ section("10. Human-Readable Risk Explanation");
150
+ print("explain_risk", await handleExplainRisk({
151
+ decision: "BLOCK",
152
+ riskLevel: "HIGH",
153
+ reasons: ["Unlimited approval requested"],
154
+ requiredActions: ["Use a limited approval amount before approving a spender"],
155
+ }));
156
+ section("Demo Complete");
157
+ console.log("SafeHands demonstrated wallet health, ALLOW/BLOCK policy, token registry, x402 safety, SSRF blocking, write-tool guardrails, and explainable risk output.");
158
+ }
159
+ finally {
160
+ restoreWalletMode();
161
+ restoreWrite();
162
+ restorePrivateKey();
163
+ restoreX402Signer();
164
+ }
165
+ }
166
+ if (import.meta.url === `file://${process.argv[1]}`) {
167
+ runDemo().catch((err) => {
168
+ console.error("Demo error:", err instanceof Error ? err.message : String(err));
169
+ process.exit(1);
170
+ });
171
+ }
172
172
  //# sourceMappingURL=demo.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
2
- export {};
1
+ #!/usr/bin/env node
2
+ import "./lib/envLoader.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAMA,OAAO,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,11 @@
2
2
  // ─── SafeHands MCP Server ──────────────────────────────────────────────
3
3
  // Entry point — registers 27 tools (17 legacy/core + 3 managed wallet + 7 SafeHands guardrail tools) and starts the MCP server.
4
4
  // ────────────────────────────────────────────────────────────────────────
5
+ // Must be first import — loads .env before any module reads process.env at init time
6
+ import "./lib/envLoader.js";
7
+ import { createRequire } from "module";
8
+ const require = createRequire(import.meta.url);
9
+ const PKG_VERSION = require("../package.json").version;
5
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
12
  import { assessRiskSchema, handleAssessRisk } from "./tools/assessRisk.js";
@@ -129,18 +134,19 @@ if (process.argv[2] === "init") {
129
134
  process.exit(0);
130
135
  }
131
136
  // ─── Deterministic Demo CLI ───────────────────────────────────────────
132
- if (process.argv.includes("--demo")) {
137
+ const isDemoMode = process.argv.includes("--demo");
138
+ if (isDemoMode) {
133
139
  await runDemo();
134
- // Brief delay allows libuv handles (express sockets) to close cleanly on Windows
135
- setTimeout(() => process.exit(0), 100);
136
- // Wait indefinitely so the script doesn't continue down to start the MCP server
137
- await new Promise(() => { });
140
+ // Set exit code and let the event loop drain naturally.
141
+ // Avoids Windows libuv UV_HANDLE_CLOSING assertion that occurs with process.exit() while
142
+ // async handles (express close, viem HTTP keep-alive) are still winding down.
143
+ process.exitCode = 0;
138
144
  }
139
145
  // ─── CLI Help ──────────────────────────────────────────────────────────
140
146
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
141
147
  process.stdout.write(`
142
148
  🛡️ SafeHands-Pharos — Transaction Safety Firewall for AI Agents
143
- v1.3.0 | Pharos Atlantic Testnet | Chain ID 688689
149
+ v${PKG_VERSION} | Pharos Atlantic Testnet | Chain ID 688689
144
150
 
145
151
  USAGE
146
152
  npx safehands-pharos
@@ -221,63 +227,65 @@ DOCS
221
227
  `);
222
228
  process.exit(0);
223
229
  }
224
- // ─── Startup Validation ────────────────────────────────────────────────
225
- const walletMode = process.env.WALLET_MODE || "none";
226
- const hasManagedWalletMode = walletMode === "managed-testnet";
227
- const hasExplicitSignerMode = walletMode === "managed-testnet" || walletMode === "env";
228
- if (process.env.WRITE_TOOLS_ENABLED !== "true") {
229
- console.error("⚠️ SafeHands write tools are disabled by default.");
230
- console.error(" Preflight, risk report, token registry, read-only, and x402 free endpoint checks remain available.");
231
- console.error(" To execute trusted testnet actions, set WRITE_TOOLS_ENABLED=true and configure a signer via WALLET_MODE=managed-testnet or WALLET_MODE=env.");
232
- console.error("");
233
- }
234
- else if (!hasExplicitSignerMode) {
235
- console.error("⚠️ SafeHands write tools enabled but no signer mode detected.");
236
- console.error(" Use WALLET_MODE=managed-testnet with create_agent_wallet, or WALLET_MODE=env for testnet developer mode.");
237
- console.error("");
238
- }
239
- // ─── Server Setup ──────────────────────────────────────────────────────
240
- const server = new McpServer({
241
- name: "safehands",
242
- version: "1.3.0",
243
- });
244
- // ─── Tool Registration ─────────────────────────────────────────────────
245
- server.tool("safehands_preflight_check", "Run a SafeHands policy preflight before an AI agent sends payment, approves tokens, swaps, publishes risk data, or pays x402.", safehandsPreflightCheckSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsPreflightCheck, params, "safehands_preflight_check")));
246
- server.tool("safehands_safe_execute", "Guarded execution wrapper: preflight first, then execute only allowed and explicitly confirmed testnet actions.", safehandsSafeExecuteSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsSafeExecute, params, "safehands_safe_execute")));
247
- server.tool("safehands_wallet_health", "Check whether an AI agent wallet is funded, signer-ready, and safe to use on Pharos Atlantic Testnet.", safehandsWalletHealthSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsWalletHealth, params, "safehands_wallet_health")));
248
- server.tool("safehands_x402_preflight", "Check an x402 paid resource for URL safety, payment amount, token, signer, and testnet policy before signing.", safehandsX402PreflightSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsX402Preflight, params, "safehands_x402_preflight")));
249
- server.tool("safehands_risk_report", "Generate an audit-friendly human-readable SafeHands risk report for an AI agent action.", safehandsRiskReportSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsRiskReport, params, "safehands_risk_report")));
250
- server.tool("explain_risk", "Explain a SafeHands risk/policy decision in human-readable language.", explainRiskSchema.shape, async (params) => mcpText(await invokeTool(handleExplainRisk, params, "explain_risk")));
251
- server.tool("token_registry_status", "Classify exact token input as canonical testnet token, test liquidity token, custom, unknown, or invalid.", tokenRegistryStatusSchema.shape, async (params) => mcpText(await invokeTool(handleTokenRegistryStatus, params, "token_registry_status")));
252
- server.tool("assess_risk", "Evaluate the risk of a planned on-chain action (swap or transfer). Returns 0-100 risk score with 5-dimension breakdown.", assessRiskSchema.shape, async (params) => mcpText(await invokeTool(handleAssessRisk, params, "assess_risk")));
253
- server.tool("execute_swap", "Swap tokens via FaroSwap with built-in risk gate. Runs risk assessment first, blocks if score > 80.", executeSwapSchema.shape, async (params) => mcpText(await invokeTool(handleExecuteSwap, params, "execute_swap")));
254
- server.tool("send_payment", "Send native PHRS with pre-flight validation. Checks address, balance, and warns on high exposure.", sendPaymentSchema.shape, async (params) => mcpText(await invokeTool(handleSendPayment, params, "send_payment")));
255
- server.tool("simulate_transaction", "Dry run a swap or transfer via eth_call no gas spent. Returns expected output and revert reasons.", simulateTransactionSchema.shape, async (params) => mcpText(await invokeTool(handleSimulateTransaction, params, "simulate_transaction")));
256
- server.tool("get_execution_history", "Pull on-chain transaction history for a wallet. Filters by swap, transfer, or all.", getExecutionHistorySchema.shape, async (params) => mcpText(await invokeTool(handleGetExecutionHistory, params, "get_execution_history")));
257
- server.tool("get_token_price", "Fetch real-time price of PHRS, USDC, or USDT on Pharos using DODO liquidity quotes.", getTokenPriceSchema.shape, async (params) => mcpText(await invokeTool(handleGetTokenPrice, params, "get_token_price")));
258
- server.tool("get_wallet_balance", "Return PHRS, USDC, USDT balances for a wallet with total USD estimate.", getWalletBalanceSchema.shape, async (params) => mcpText(await invokeTool(handleGetWalletBalance, params, "get_wallet_balance")));
259
- server.tool("check_allowance", "Check ERC-20 token allowance for DODO swap approval. Returns whether approval is needed.", checkAllowanceSchema.shape, async (params) => mcpText(await invokeTool(handleCheckAllowance, params, "check_allowance")));
260
- server.tool("get_transaction_status", "Check on-chain transaction status by hash. Returns status, block, gas, and explorer link.", getTransactionStatusSchema.shape, async (params) => mcpText(await invokeTool(handleGetTransactionStatus, params, "get_transaction_status")));
261
- server.tool("estimate_gas", "Estimate gas cost for a swap or transfer before executing. Returns cost in PHRS and USD.", estimateGasSchema.shape, async (params) => mcpText(await invokeTool(handleEstimateGas, params, "estimate_gas")));
262
- server.tool("publish_risk_score", "Run risk assessment and publish result to the on-chain RiskRegistry. Other agents can query it.", publishRiskScoreSchema.shape, async (params) => mcpText(await invokeTool(handlePublishRiskScore, params, "publish_risk_score")));
263
- server.tool("query_risk_registry", "Query the on-chain RiskRegistry for a wallet's published risk score. Read-only, no private key needed.", queryRiskRegistrySchema.shape, async (params) => mcpText(await invokeTool(handleQueryRiskRegistry, params, "query_risk_registry")));
264
- server.tool("approve_token", "Approve ERC-20 token spending for FaroSwap (DODO) router. Required before swapping non-native tokens.", approveTokenSchema.shape, async (params) => mcpText(await invokeTool(handleApproveToken, params, "approve_token")));
265
- server.tool("get_gas_price", "Get current gas price on Pharos with trend classification and cost estimates.", getGasPriceSchema.shape, async (params) => mcpText(await invokeTool(handleGetGasPrice, params, "get_gas_price")));
266
- server.tool("get_pool_info", "Fetch DODO liquidity pool info for a token pair on Pharos. Returns price ratio, impact, and fees.", getPoolInfoSchema.shape, async (params) => mcpText(await invokeTool(handleGetPoolInfo, params, "get_pool_info")));
267
- server.tool("check_token_security", "Check token contract security (honeypot, mintable, ownership privileges, tax) via GoPlus Security API.", checkTokenSecuritySchema.shape, async (params) => mcpText(await invokeTool(handleCheckTokenSecurity, params, "check_token_security")));
268
- server.tool("x402_pay_and_fetch", "Fetch resources from an HTTP x402 payment-gated server. Automatically handles HTTP 402 payment challenge.", x402PayAndFetchSchema.shape, async (params) => mcpText(await invokeTool(handleX402PayAndFetch, params, "x402_pay_and_fetch")));
269
- // ─── Managed Wallet Tools ──────────────────────────────────────────────
270
- server.tool("create_agent_wallet", "Create a new managed testnet agent wallet. Private key is encrypted and never returned. Fund the wallet with testnet PHRS before using write tools.", createAgentWalletSchema.shape, async (params) => mcpText(await invokeTool(handleCreateAgentWallet, params, "create_agent_wallet")));
271
- server.tool("get_agent_wallet", "Get public info (address, environment, chainId) for a managed agent wallet. Never returns private key.", getAgentWalletSchema.shape, async (params) => mcpText(await invokeTool(handleGetAgentWallet, params, "get_agent_wallet")));
272
- server.tool("get_agent_wallet_balance", "Get PHRS, USDC, and USDT balances for a managed agent wallet on Pharos testnet.", getAgentWalletBalanceSchema.shape, async (params) => mcpText(await invokeTool(handleGetAgentWalletBalance, params, "get_agent_wallet_balance")));
273
- // ─── Start Server ──────────────────────────────────────────────────────
274
- async function main() {
275
- const transport = new StdioServerTransport();
276
- await server.connect(transport);
277
- console.error("SafeHands-Pharos MCP Server v1.3.0 running on stdio");
278
- }
279
- main().catch((error) => {
280
- console.error("Fatal error:", error);
281
- process.exit(1);
282
- });
230
+ if (!isDemoMode) {
231
+ // ─── Startup Validation ────────────────────────────────────────────────
232
+ const walletMode = process.env.WALLET_MODE || "none";
233
+ const hasManagedWalletMode = walletMode === "managed-testnet";
234
+ const hasExplicitSignerMode = walletMode === "managed-testnet" || walletMode === "env";
235
+ if (process.env.WRITE_TOOLS_ENABLED !== "true") {
236
+ console.error("⚠️ SafeHands write tools are disabled by default.");
237
+ console.error(" Preflight, risk report, token registry, read-only, and x402 free endpoint checks remain available.");
238
+ console.error(" To execute trusted testnet actions, set WRITE_TOOLS_ENABLED=true and configure a signer via WALLET_MODE=managed-testnet or WALLET_MODE=env.");
239
+ console.error("");
240
+ }
241
+ else if (!hasExplicitSignerMode) {
242
+ console.error("⚠️ SafeHands write tools enabled but no signer mode detected.");
243
+ console.error(" Use WALLET_MODE=managed-testnet with create_agent_wallet, or WALLET_MODE=env for testnet developer mode.");
244
+ console.error("");
245
+ }
246
+ // ─── Server Setup ──────────────────────────────────────────────────────
247
+ const server = new McpServer({
248
+ name: "safehands",
249
+ version: PKG_VERSION,
250
+ });
251
+ // ─── Tool Registration ─────────────────────────────────────────────────
252
+ server.tool("safehands_preflight_check", "Run a SafeHands policy preflight before an AI agent sends payment, approves tokens, swaps, publishes risk data, or pays x402.", safehandsPreflightCheckSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsPreflightCheck, params, "safehands_preflight_check")));
253
+ server.tool("safehands_safe_execute", "Guarded execution wrapper: preflight first, then execute only allowed and explicitly confirmed testnet actions.", safehandsSafeExecuteSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsSafeExecute, params, "safehands_safe_execute")));
254
+ server.tool("safehands_wallet_health", "Check whether an AI agent wallet is funded, signer-ready, and safe to use on Pharos Atlantic Testnet.", safehandsWalletHealthSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsWalletHealth, params, "safehands_wallet_health")));
255
+ server.tool("safehands_x402_preflight", "Check an x402 paid resource for URL safety, payment amount, token, signer, and testnet policy before signing.", safehandsX402PreflightSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsX402Preflight, params, "safehands_x402_preflight")));
256
+ server.tool("safehands_risk_report", "Generate an audit-friendly human-readable SafeHands risk report for an AI agent action.", safehandsRiskReportSchema.shape, async (params) => mcpText(await invokeTool(handleSafeHandsRiskReport, params, "safehands_risk_report")));
257
+ server.tool("explain_risk", "Explain a SafeHands risk/policy decision in human-readable language.", explainRiskSchema.shape, async (params) => mcpText(await invokeTool(handleExplainRisk, params, "explain_risk")));
258
+ server.tool("token_registry_status", "Classify exact token input as canonical testnet token, test liquidity token, custom, unknown, or invalid.", tokenRegistryStatusSchema.shape, async (params) => mcpText(await invokeTool(handleTokenRegistryStatus, params, "token_registry_status")));
259
+ server.tool("assess_risk", "Evaluate the risk of a planned on-chain action (swap or transfer). Returns 0-100 risk score with 5-dimension breakdown.", assessRiskSchema.shape, async (params) => mcpText(await invokeTool(handleAssessRisk, params, "assess_risk")));
260
+ server.tool("execute_swap", "Swap tokens via FaroSwap with built-in risk gate. Runs risk assessment first, blocks if score > 80.", executeSwapSchema.shape, async (params) => mcpText(await invokeTool(handleExecuteSwap, params, "execute_swap")));
261
+ server.tool("send_payment", "Send native PHRS with pre-flight validation. Checks address, balance, and warns on high exposure.", sendPaymentSchema.shape, async (params) => mcpText(await invokeTool(handleSendPayment, params, "send_payment")));
262
+ server.tool("simulate_transaction", "Dry run a swap or transfer via eth_call — no gas spent. Returns expected output and revert reasons.", simulateTransactionSchema.shape, async (params) => mcpText(await invokeTool(handleSimulateTransaction, params, "simulate_transaction")));
263
+ server.tool("get_execution_history", "Pull on-chain transaction history for a wallet. Filters by swap, transfer, or all.", getExecutionHistorySchema.shape, async (params) => mcpText(await invokeTool(handleGetExecutionHistory, params, "get_execution_history")));
264
+ server.tool("get_token_price", "Fetch real-time price of PHRS, USDC, or USDT on Pharos using DODO liquidity quotes.", getTokenPriceSchema.shape, async (params) => mcpText(await invokeTool(handleGetTokenPrice, params, "get_token_price")));
265
+ server.tool("get_wallet_balance", "Return PHRS, USDC, USDT balances for a wallet with total USD estimate.", getWalletBalanceSchema.shape, async (params) => mcpText(await invokeTool(handleGetWalletBalance, params, "get_wallet_balance")));
266
+ server.tool("check_allowance", "Check ERC-20 token allowance for DODO swap approval. Returns whether approval is needed.", checkAllowanceSchema.shape, async (params) => mcpText(await invokeTool(handleCheckAllowance, params, "check_allowance")));
267
+ server.tool("get_transaction_status", "Check on-chain transaction status by hash. Returns status, block, gas, and explorer link.", getTransactionStatusSchema.shape, async (params) => mcpText(await invokeTool(handleGetTransactionStatus, params, "get_transaction_status")));
268
+ server.tool("estimate_gas", "Estimate gas cost for a swap or transfer before executing. Returns cost in PHRS and USD.", estimateGasSchema.shape, async (params) => mcpText(await invokeTool(handleEstimateGas, params, "estimate_gas")));
269
+ server.tool("publish_risk_score", "Run risk assessment and publish result to the on-chain RiskRegistry. Other agents can query it.", publishRiskScoreSchema.shape, async (params) => mcpText(await invokeTool(handlePublishRiskScore, params, "publish_risk_score")));
270
+ server.tool("query_risk_registry", "Query the on-chain RiskRegistry for a wallet's published risk score. Read-only, no private key needed.", queryRiskRegistrySchema.shape, async (params) => mcpText(await invokeTool(handleQueryRiskRegistry, params, "query_risk_registry")));
271
+ server.tool("approve_token", "Approve ERC-20 token spending for FaroSwap (DODO) router. Required before swapping non-native tokens.", approveTokenSchema.shape, async (params) => mcpText(await invokeTool(handleApproveToken, params, "approve_token")));
272
+ server.tool("get_gas_price", "Get current gas price on Pharos with trend classification and cost estimates.", getGasPriceSchema.shape, async (params) => mcpText(await invokeTool(handleGetGasPrice, params, "get_gas_price")));
273
+ server.tool("get_pool_info", "Fetch DODO liquidity pool info for a token pair on Pharos. Returns price ratio, impact, and fees.", getPoolInfoSchema.shape, async (params) => mcpText(await invokeTool(handleGetPoolInfo, params, "get_pool_info")));
274
+ server.tool("check_token_security", "Check token contract security (honeypot, mintable, ownership privileges, tax) via GoPlus Security API.", checkTokenSecuritySchema.shape, async (params) => mcpText(await invokeTool(handleCheckTokenSecurity, params, "check_token_security")));
275
+ server.tool("x402_pay_and_fetch", "Fetch resources from an HTTP x402 payment-gated server. Automatically handles HTTP 402 payment challenge.", x402PayAndFetchSchema.shape, async (params) => mcpText(await invokeTool(handleX402PayAndFetch, params, "x402_pay_and_fetch")));
276
+ // ─── Managed Wallet Tools ──────────────────────────────────────────────
277
+ server.tool("create_agent_wallet", "Create a new managed testnet agent wallet. Private key is encrypted and never returned. Fund the wallet with testnet PHRS before using write tools.", createAgentWalletSchema.shape, async (params) => mcpText(await invokeTool(handleCreateAgentWallet, params, "create_agent_wallet")));
278
+ server.tool("get_agent_wallet", "Get public info (address, environment, chainId) for a managed agent wallet. Never returns private key.", getAgentWalletSchema.shape, async (params) => mcpText(await invokeTool(handleGetAgentWallet, params, "get_agent_wallet")));
279
+ server.tool("get_agent_wallet_balance", "Get PHRS, USDC, and USDT balances for a managed agent wallet on Pharos testnet.", getAgentWalletBalanceSchema.shape, async (params) => mcpText(await invokeTool(handleGetAgentWalletBalance, params, "get_agent_wallet_balance")));
280
+ // ─── Start Server ──────────────────────────────────────────────────────
281
+ async function main() {
282
+ const transport = new StdioServerTransport();
283
+ await server.connect(transport);
284
+ console.error(`SafeHands-Pharos MCP Server v${PKG_VERSION} running on stdio`);
285
+ }
286
+ main().catch((error) => {
287
+ console.error("Fatal error:", error);
288
+ process.exit(1);
289
+ });
290
+ } // end: !isDemoMode
283
291
  //# sourceMappingURL=index.js.map