safehands-pharos 1.2.0 → 1.2.4

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 (175) hide show
  1. package/.env.example +26 -0
  2. package/README.md +311 -350
  3. package/contracts/RiskRegistry.json +75 -1
  4. package/contracts/RiskRegistry.sol +29 -1
  5. package/dist/cli.d.ts +6 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +91 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/demo.d.ts +2 -0
  10. package/dist/demo.d.ts.map +1 -0
  11. package/dist/demo.js +172 -0
  12. package/dist/demo.js.map +1 -0
  13. package/dist/index.js +181 -169
  14. package/dist/index.js.map +1 -1
  15. package/dist/init.d.ts +2 -0
  16. package/dist/init.d.ts.map +1 -0
  17. package/dist/init.js +66 -0
  18. package/dist/init.js.map +1 -0
  19. package/dist/lib/constants.d.ts +122 -7
  20. package/dist/lib/constants.d.ts.map +1 -1
  21. package/dist/lib/constants.js +139 -13
  22. package/dist/lib/constants.js.map +1 -1
  23. package/dist/lib/dodoApi.d.ts +14 -0
  24. package/dist/lib/dodoApi.d.ts.map +1 -1
  25. package/dist/lib/dodoApi.js +78 -22
  26. package/dist/lib/dodoApi.js.map +1 -1
  27. package/dist/lib/http.d.ts +15 -0
  28. package/dist/lib/http.d.ts.map +1 -0
  29. package/dist/lib/http.js +119 -0
  30. package/dist/lib/http.js.map +1 -0
  31. package/dist/lib/pharosClient.d.ts +4 -3
  32. package/dist/lib/pharosClient.d.ts.map +1 -1
  33. package/dist/lib/pharosClient.js +8 -5
  34. package/dist/lib/pharosClient.js.map +1 -1
  35. package/dist/lib/policy/actionPolicyEngine.d.ts +54 -0
  36. package/dist/lib/policy/actionPolicyEngine.d.ts.map +1 -0
  37. package/dist/lib/policy/actionPolicyEngine.js +213 -0
  38. package/dist/lib/policy/actionPolicyEngine.js.map +1 -0
  39. package/dist/lib/signer/index.d.ts +25 -0
  40. package/dist/lib/signer/index.d.ts.map +1 -0
  41. package/dist/lib/signer/index.js +90 -0
  42. package/dist/lib/signer/index.js.map +1 -0
  43. package/dist/lib/testDodoLive.d.ts +2 -0
  44. package/dist/lib/testDodoLive.d.ts.map +1 -0
  45. package/dist/lib/testDodoLive.js +105 -0
  46. package/dist/lib/testDodoLive.js.map +1 -0
  47. package/dist/lib/testLiveSafehands.d.ts +2 -0
  48. package/dist/lib/testLiveSafehands.d.ts.map +1 -0
  49. package/dist/lib/testLiveSafehands.js +93 -0
  50. package/dist/lib/testLiveSafehands.js.map +1 -0
  51. package/dist/lib/testRpcLive.d.ts +2 -0
  52. package/dist/lib/testRpcLive.d.ts.map +1 -0
  53. package/dist/lib/testRpcLive.js +89 -0
  54. package/dist/lib/testRpcLive.js.map +1 -0
  55. package/dist/lib/testTools.js +363 -354
  56. package/dist/lib/testTools.js.map +1 -1
  57. package/dist/lib/testX402Live.d.ts +2 -0
  58. package/dist/lib/testX402Live.d.ts.map +1 -0
  59. package/dist/lib/testX402Live.js +160 -0
  60. package/dist/lib/testX402Live.js.map +1 -0
  61. package/dist/lib/toolResponse.d.ts +26 -0
  62. package/dist/lib/toolResponse.d.ts.map +1 -0
  63. package/dist/lib/toolResponse.js +54 -0
  64. package/dist/lib/toolResponse.js.map +1 -0
  65. package/dist/lib/wallet/index.d.ts +19 -0
  66. package/dist/lib/wallet/index.d.ts.map +1 -0
  67. package/dist/lib/wallet/index.js +71 -0
  68. package/dist/lib/wallet/index.js.map +1 -0
  69. package/dist/tools/approveToken.d.ts +19 -20
  70. package/dist/tools/approveToken.d.ts.map +1 -1
  71. package/dist/tools/approveToken.js +44 -21
  72. package/dist/tools/approveToken.js.map +1 -1
  73. package/dist/tools/assessRisk.d.ts +22 -9
  74. package/dist/tools/assessRisk.d.ts.map +1 -1
  75. package/dist/tools/assessRisk.js +32 -9
  76. package/dist/tools/assessRisk.js.map +1 -1
  77. package/dist/tools/checkAllowance.d.ts +6 -6
  78. package/dist/tools/checkTokenSecurity.d.ts +9 -16
  79. package/dist/tools/checkTokenSecurity.d.ts.map +1 -1
  80. package/dist/tools/checkTokenSecurity.js +17 -22
  81. package/dist/tools/checkTokenSecurity.js.map +1 -1
  82. package/dist/tools/createAgentWallet.d.ts +27 -0
  83. package/dist/tools/createAgentWallet.d.ts.map +1 -0
  84. package/dist/tools/createAgentWallet.js +60 -0
  85. package/dist/tools/createAgentWallet.js.map +1 -0
  86. package/dist/tools/estimateGas.d.ts +31 -21
  87. package/dist/tools/estimateGas.d.ts.map +1 -1
  88. package/dist/tools/estimateGas.js +91 -95
  89. package/dist/tools/estimateGas.js.map +1 -1
  90. package/dist/tools/executeSwap.d.ts +13 -29
  91. package/dist/tools/executeSwap.d.ts.map +1 -1
  92. package/dist/tools/executeSwap.js +68 -46
  93. package/dist/tools/executeSwap.js.map +1 -1
  94. package/dist/tools/explainRisk.d.ts +30 -0
  95. package/dist/tools/explainRisk.d.ts.map +1 -0
  96. package/dist/tools/explainRisk.js +33 -0
  97. package/dist/tools/explainRisk.js.map +1 -0
  98. package/dist/tools/getAgentWallet.d.ts +22 -0
  99. package/dist/tools/getAgentWallet.d.ts.map +1 -0
  100. package/dist/tools/getAgentWallet.js +28 -0
  101. package/dist/tools/getAgentWallet.js.map +1 -0
  102. package/dist/tools/getAgentWalletBalance.d.ts +12 -0
  103. package/dist/tools/getAgentWalletBalance.d.ts.map +1 -0
  104. package/dist/tools/getAgentWalletBalance.js +71 -0
  105. package/dist/tools/getAgentWalletBalance.js.map +1 -0
  106. package/dist/tools/getExecutionHistory.d.ts +4 -4
  107. package/dist/tools/getGasPrice.d.ts +26 -8
  108. package/dist/tools/getGasPrice.d.ts.map +1 -1
  109. package/dist/tools/getGasPrice.js +43 -35
  110. package/dist/tools/getGasPrice.js.map +1 -1
  111. package/dist/tools/getPoolInfo.d.ts +47 -59
  112. package/dist/tools/getPoolInfo.d.ts.map +1 -1
  113. package/dist/tools/getPoolInfo.js +96 -57
  114. package/dist/tools/getPoolInfo.js.map +1 -1
  115. package/dist/tools/getTokenPrice.d.ts +95 -9
  116. package/dist/tools/getTokenPrice.d.ts.map +1 -1
  117. package/dist/tools/getTokenPrice.js +95 -56
  118. package/dist/tools/getTokenPrice.js.map +1 -1
  119. package/dist/tools/getWalletBalance.d.ts +40 -11
  120. package/dist/tools/getWalletBalance.d.ts.map +1 -1
  121. package/dist/tools/getWalletBalance.js +64 -47
  122. package/dist/tools/getWalletBalance.js.map +1 -1
  123. package/dist/tools/publishRiskScore.d.ts +12 -10
  124. package/dist/tools/publishRiskScore.d.ts.map +1 -1
  125. package/dist/tools/publishRiskScore.js +33 -19
  126. package/dist/tools/publishRiskScore.js.map +1 -1
  127. package/dist/tools/queryRiskRegistry.d.ts +3 -3
  128. package/dist/tools/safehandsPreflightCheck.d.ts +78 -0
  129. package/dist/tools/safehandsPreflightCheck.d.ts.map +1 -0
  130. package/dist/tools/safehandsPreflightCheck.js +48 -0
  131. package/dist/tools/safehandsPreflightCheck.js.map +1 -0
  132. package/dist/tools/safehandsRiskReport.d.ts +82 -0
  133. package/dist/tools/safehandsRiskReport.d.ts.map +1 -0
  134. package/dist/tools/safehandsRiskReport.js +29 -0
  135. package/dist/tools/safehandsRiskReport.js.map +1 -0
  136. package/dist/tools/safehandsSafeExecute.d.ts +21 -0
  137. package/dist/tools/safehandsSafeExecute.d.ts.map +1 -0
  138. package/dist/tools/safehandsSafeExecute.js +76 -0
  139. package/dist/tools/safehandsSafeExecute.js.map +1 -0
  140. package/dist/tools/safehandsWalletHealth.d.ts +15 -0
  141. package/dist/tools/safehandsWalletHealth.d.ts.map +1 -0
  142. package/dist/tools/safehandsWalletHealth.js +104 -0
  143. package/dist/tools/safehandsWalletHealth.js.map +1 -0
  144. package/dist/tools/safehandsX402Preflight.d.ts +27 -0
  145. package/dist/tools/safehandsX402Preflight.d.ts.map +1 -0
  146. package/dist/tools/safehandsX402Preflight.js +66 -0
  147. package/dist/tools/safehandsX402Preflight.js.map +1 -0
  148. package/dist/tools/sendPayment.d.ts +13 -35
  149. package/dist/tools/sendPayment.d.ts.map +1 -1
  150. package/dist/tools/sendPayment.js +53 -47
  151. package/dist/tools/sendPayment.js.map +1 -1
  152. package/dist/tools/simulateTransaction.d.ts +4 -4
  153. package/dist/tools/tokenRegistryStatus.d.ts +27 -0
  154. package/dist/tools/tokenRegistryStatus.d.ts.map +1 -0
  155. package/dist/tools/tokenRegistryStatus.js +97 -0
  156. package/dist/tools/tokenRegistryStatus.js.map +1 -0
  157. package/dist/tools/x402PayAndFetch.d.ts +40 -16
  158. package/dist/tools/x402PayAndFetch.d.ts.map +1 -1
  159. package/dist/tools/x402PayAndFetch.js +115 -47
  160. package/dist/tools/x402PayAndFetch.js.map +1 -1
  161. package/dist/x402Server.js +149 -115
  162. package/dist/x402Server.js.map +1 -1
  163. package/examples/pharos-skill-engine/SKILL.safehands.md +85 -0
  164. package/examples/pharos-skill-engine/assets/safehands/example-actions.json +49 -0
  165. package/examples/pharos-skill-engine/assets/safehands/policy-defaults.json +11 -0
  166. package/examples/pharos-skill-engine/references/safehands.md +345 -0
  167. package/examples/scenario-hack.ts +38 -0
  168. package/package.json +19 -5
  169. package/skill/SKILL.md +127 -0
  170. package/skill/assets/safehands/example-actions.json +49 -0
  171. package/skill/assets/safehands/policy-defaults.json +11 -0
  172. package/skill/references/safehands.md +345 -0
  173. package/.agents/skill/safehands/SKILL.md +0 -200
  174. package/.agents/skill/safehands/assets/networks.json +0 -24
  175. package/.agents/skill/safehands/assets/tokens.json +0 -60
@@ -1,389 +1,398 @@
1
- // ─── SafeHands — Full 17-Tool Integration Test ────────────────────────
2
- import { readFileSync } from "fs";
3
- import { resolve } from "path";
4
- import { publicClient } from "../lib/pharosClient.js";
5
- import { formatEther, defineChain, createWalletClient, http, publicActions } from "viem";
6
- import { privateKeyToAccount } from "viem/accounts";
1
+ // ─── SafeHands — Non-destructive smoke test for tool handlers ───────────
2
+ // This script intentionally avoids broadcasting transactions. It checks that
3
+ // tool handlers can be invoked safely and return predictable AI-readable data
4
+ // or structured failures when external services/env vars are unavailable.
5
+ // ────────────────────────────────────────────────────────────────────────
7
6
  import express from "express";
8
- import { x402Facilitator } from "@x402/core/facilitator";
9
- import { toFacilitatorEvmSigner } from "@x402/evm";
10
- import { ExactEvmScheme as FacilitatorExactEvmScheme } from "@x402/evm/exact/facilitator";
11
- import { ExactEvmScheme as ServerExactEvmScheme } from "@x402/evm/exact/server";
12
- import { HTTPFacilitatorClient } from "@x402/core/server";
13
- import { paymentMiddleware, x402ResourceServer } from "@x402/express";
14
7
  import { handleAssessRisk } from "../tools/assessRisk.js";
15
8
  import { handleExecuteSwap } from "../tools/executeSwap.js";
16
9
  import { handleSendPayment } from "../tools/sendPayment.js";
17
10
  import { handleSimulateTransaction } from "../tools/simulateTransaction.js";
18
- import { handleGetExecutionHistory } from "../tools/getExecutionHistory.js";
19
11
  import { handleGetTokenPrice } from "../tools/getTokenPrice.js";
20
12
  import { handleGetWalletBalance } from "../tools/getWalletBalance.js";
21
- import { handleCheckAllowance } from "../tools/checkAllowance.js";
22
- import { handleGetTransactionStatus } from "../tools/getTransactionStatus.js";
23
13
  import { handleEstimateGas } from "../tools/estimateGas.js";
24
14
  import { handlePublishRiskScore } from "../tools/publishRiskScore.js";
25
- import { handleQueryRiskRegistry } from "../tools/queryRiskRegistry.js";
26
15
  import { handleApproveToken } from "../tools/approveToken.js";
27
- import { handleGetGasPrice } from "../tools/getGasPrice.js";
28
- import { handleGetPoolInfo } from "../tools/getPoolInfo.js";
29
16
  import { handleCheckTokenSecurity } from "../tools/checkTokenSecurity.js";
30
17
  import { handleX402PayAndFetch } from "../tools/x402PayAndFetch.js";
31
- // ─── Load .env ─────────────────────────────────────────────────────────
32
- function loadEnv() {
33
- const f = readFileSync(resolve(process.cwd(), ".env"), "utf-8");
34
- const v = {};
35
- for (const l of f.split("\n")) {
36
- const t = l.trim();
37
- if (!t || t.startsWith("#"))
38
- continue;
39
- const i = t.indexOf("=");
40
- if (i === -1)
41
- continue;
42
- v[t.slice(0, i).trim()] = t.slice(i + 1).trim();
43
- }
44
- return v;
18
+ import { fail, ok } from "./toolResponse.js";
19
+ import { handleSafeHandsPreflightCheck } from "../tools/safehandsPreflightCheck.js";
20
+ import { handleSafeHandsX402Preflight } from "../tools/safehandsX402Preflight.js";
21
+ import { handleSafeHandsWalletHealth } from "../tools/safehandsWalletHealth.js";
22
+ import { handleSafeHandsRiskReport } from "../tools/safehandsRiskReport.js";
23
+ import { handleExplainRisk } from "../tools/explainRisk.js";
24
+ import { handleTokenRegistryStatus } from "../tools/tokenRegistryStatus.js";
25
+ import { handleCreateAgentWallet } from "../tools/createAgentWallet.js";
26
+ import { handleGetAgentWallet } from "../tools/getAgentWallet.js";
27
+ import { getSigner, isSignerFailure } from "./signer/index.js";
28
+ import { USDC_ADDRESS } from "./constants.js";
29
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
30
+ import { join } from "node:path";
31
+ import { spawnSync } from "node:child_process";
32
+ const WALLET = process.env.WALLET_ADDRESS || "0x0000000000000000000000000000000000000001";
33
+ const RECIPIENT = "0x000000000000000000000000000000000000dEaD";
34
+ const TEST_TX = "0x0000000000000000000000000000000000000000000000000000000000000000";
35
+ const rows = [];
36
+ function isStructured(value) {
37
+ return !!value && typeof value === "object" && "success" in value && "error" in value && "timestamp" in value;
45
38
  }
46
- const env = loadEnv();
47
- const PK = env.PRIVATE_KEY;
48
- const WALLET = env.WALLET_ADDRESS;
49
- // ─── Results tracker ───────────────────────────────────────────────────
50
- const results = [];
51
- let swapTxHash = "";
52
- function ok(tool, key) { results.push({ tool, status: "✅ PASS", key }); }
53
- function fail(tool, key) { results.push({ tool, status: "❌ FAIL", key }); }
54
- // ─── Tests ─────────────────────────────────────────────────────────────
55
- async function run() {
56
- console.log("\n🛡️ SafeHands 17-Tool Integration Test");
57
- console.log("─".repeat(64));
58
- const bal = formatEther(await publicClient.getBalance({ address: WALLET }));
59
- console.log(` Wallet: ${WALLET}\n Balance: ${bal} PHRS\n`);
60
- // 1. assess_risk
61
- try {
62
- const r = await handleAssessRisk({ action: "swap", tokenIn: "PHRS", tokenOut: "USDC", amount: "0.01" });
63
- console.log(`1. assess_risk → score=${r.riskScore} level=${r.riskLevel}`);
64
- ok("assess_risk", `score=${r.riskScore}, ${r.riskLevel}, ${r.recommendation}`);
65
- }
66
- catch (e) {
67
- console.log(`1. assess_risk → ❌`);
68
- fail("assess_risk", e.message);
69
- }
70
- // 2. simulate_transaction (swap)
71
- try {
72
- const r = await handleSimulateTransaction({ action: "swap", tokenIn: "PHRS", tokenOut: "USDC", amount: "0.01" });
73
- console.log(`2. simulate_tx (swap) → success=${r.wouldSucceed} gas=${r.gasEstimate}`);
74
- ok("simulate_transaction", `wouldSucceed=${r.wouldSucceed}, gas=${r.gasEstimate}`);
75
- }
76
- catch (e) {
77
- console.log(`2. simulate_tx → ❌`);
78
- fail("simulate_transaction", e.message);
79
- }
80
- // 3. simulate_transaction (transfer)
81
- try {
82
- const r = await handleSimulateTransaction({ action: "transfer", amount: "0.001", toAddress: "0x000000000000000000000000000000000000dEaD" });
83
- console.log(`3. simulate_tx (xfer) → success=${r.wouldSucceed} gas=${r.gasEstimate}`);
84
- ok("simulate_tx (xfer)", `wouldSucceed=${r.wouldSucceed}, gas=${r.gasEstimate}`);
85
- }
86
- catch (e) {
87
- console.log(`3. simulate_tx (xfer) → ❌`);
88
- fail("simulate_tx (xfer)", e.message);
89
- }
90
- // 4. get_token_price
91
- try {
92
- const r = await handleGetTokenPrice({ token: "PHRS" });
93
- console.log(`4. get_token_price → PHRS=$${r.priceUsd}`);
94
- ok("get_token_price", `PHRS=$${r.priceUsd}`);
95
- }
96
- catch (e) {
97
- console.log(`4. get_token_price → ❌`);
98
- fail("get_token_price", e.message);
99
- }
100
- // 5. get_wallet_balance
101
- try {
102
- const r = await handleGetWalletBalance({ walletAddress: WALLET });
103
- console.log(`5. get_wallet_balance → PHRS=${r.balances.PHRS.balance} USDC=${r.balances.USDC.balance} USDT=${r.balances.USDT.balance}`);
104
- ok("get_wallet_balance", `PHRS=${r.balances.PHRS.balance}, USDC=${r.balances.USDC.balance}, total=$${r.totalUsd}`);
105
- }
106
- catch (e) {
107
- console.log(`5. get_wallet_balance → ❌`);
108
- fail("get_wallet_balance", e.message);
109
- }
110
- // 6. check_allowance
111
- try {
112
- const r = await handleCheckAllowance({ token: "USDC" });
113
- console.log(`6. check_allowance → USDC allowance=${r.allowance} needsApproval=${r.needsApproval}`);
114
- ok("check_allowance", `allowance=${r.allowance}, needsApproval=${r.needsApproval}`);
115
- }
116
- catch (e) {
117
- console.log(`6. check_allowance → ❌`);
118
- fail("check_allowance", e.message);
119
- }
120
- // 7. estimate_gas (swap)
121
- try {
122
- const r = await handleEstimateGas({ walletAddress: WALLET, action: "swap", tokenIn: "PHRS", tokenOut: "USDC", amount: "0.01" });
123
- console.log(`7. estimate_gas → gas=${r.gasEstimate} cost=${r.gasCostPHRS} PHRS`);
124
- ok("estimate_gas", `gas=${r.gasEstimate}, cost=${r.gasCostPHRS} PHRS, sufficient=${r.isSufficient}`);
125
- }
126
- catch (e) {
127
- console.log(`7. estimate_gas → ❌`);
128
- fail("estimate_gas", e.message);
129
- }
130
- // 8. get_gas_price
131
- try {
132
- const r = await handleGetGasPrice({});
133
- console.log(`8. get_gas_price → ${r.gasPriceGwei} Gwei, trend=${r.trend}`);
134
- ok("get_gas_price", `${r.gasPriceGwei} Gwei, trend=${r.trend}`);
135
- }
136
- catch (e) {
137
- console.log(`8. get_gas_price → ❌`);
138
- fail("get_gas_price", e.message);
139
- }
140
- // 9. get_pool_info
141
- try {
142
- const r = await handleGetPoolInfo({ tokenA: "PHRS", tokenB: "USDC" });
143
- const ratio = r.available ? r.priceRatio : "no pool";
144
- console.log(`9. get_pool_info → ${ratio}`);
145
- ok("get_pool_info", `available=${r.available}, ${ratio}`);
146
- }
147
- catch (e) {
148
- console.log(`9. get_pool_info → ❌`);
149
- fail("get_pool_info", e.message);
150
- }
151
- // 10. execute_swap (LIVE — 0.01 PHRS → USDC)
152
- try {
153
- const r = await handleExecuteSwap({ tokenIn: "PHRS", tokenOut: "USDC", amountIn: "0.01" });
154
- if (r.success) {
155
- swapTxHash = r.txHash;
156
- console.log(`10. execute_swap → ✅ out=${r.amountOut} USDC tx=${r.txHash.slice(0, 18)}...`);
157
- ok("execute_swap", `out=${r.amountOut} USDC, tx=${r.txHash}`);
158
- }
159
- else {
160
- console.log(`10. execute_swap → not executed: ${r.error}`);
161
- ok("execute_swap", `handled: ${r.error}`);
39
+ function normalize(value) {
40
+ if (isStructured(value))
41
+ return value;
42
+ return ok(value);
43
+ }
44
+ function readAllToolSourceFiles() {
45
+ const root = join(process.cwd(), "src", "tools");
46
+ const out = [];
47
+ function walk(dir) {
48
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
49
+ const full = join(dir, entry.name);
50
+ if (entry.isDirectory())
51
+ walk(full);
52
+ else if (entry.isFile() && entry.name.endsWith(".ts")) {
53
+ const rel = full.replace(process.cwd(), "").replace(/^[\\/]/, "").replace(/\\/g, "/");
54
+ out.push({ path: rel, content: readFileSync(full, "utf8") });
55
+ }
162
56
  }
163
57
  }
164
- catch (e) {
165
- console.log(`10. execute_swap → ❌`);
166
- fail("execute_swap", e.message);
167
- }
168
- // 11. send_payment (LIVE 0.001 PHRS)
58
+ walk(root);
59
+ return out;
60
+ }
61
+ function runBuiltCli(args) {
62
+ const entry = join(process.cwd(), "dist", "index.js");
63
+ if (!existsSync(entry))
64
+ return fail("CLI_DIST_MISSING", "Run npm run build before CLI smoke tests.", false, "cli_smoke");
65
+ const result = spawnSync(process.execPath, [entry, ...args], {
66
+ cwd: process.cwd(),
67
+ env: { ...process.env, WALLET_MODE: "none", WRITE_TOOLS_ENABLED: "false", PRIVATE_KEY: "" },
68
+ encoding: "utf8",
69
+ });
70
+ const stdout = result.stdout.trim();
169
71
  try {
170
- const r = await handleSendPayment({ toAddress: "0x000000000000000000000000000000000000dEaD", amount: "0.001" });
171
- if (r.success) {
172
- console.log(`11. send_payment → ✅ tx=${r.txHash.slice(0, 18)}...`);
173
- ok("send_payment", `sent 0.001 PHRS, tx=${r.txHash}`);
174
- }
175
- else {
176
- console.log(`11. send_payment → not sent: ${r.error}`);
177
- ok("send_payment", `handled: ${r.error}`);
178
- }
72
+ return JSON.parse(stdout);
179
73
  }
180
- catch (e) {
181
- console.log(`11. send_payment → ❌`);
182
- fail("send_payment", e.message);
74
+ catch {
75
+ return fail("CLI_INVALID_STDOUT", `exit=${result.status} stdout=${stdout.slice(0, 240)} stderr=${result.stderr.slice(0, 240)}`, false, "cli_smoke");
183
76
  }
184
- // 12. approve_token (LIVE — approve 100 USDC)
77
+ }
78
+ function runBuiltCliRaw(args) {
79
+ const entry = join(process.cwd(), "dist", "index.js");
80
+ if (!existsSync(entry))
81
+ return { status: 127, stdout: "", stderr: "dist/index.js missing" };
82
+ const result = spawnSync(process.execPath, [entry, ...args], {
83
+ cwd: process.cwd(),
84
+ env: { ...process.env, WALLET_MODE: "none", WRITE_TOOLS_ENABLED: "false", PRIVATE_KEY: "", X402_SIGNER_PRIVATE_KEY: "" },
85
+ encoding: "utf8",
86
+ timeout: 60_000,
87
+ });
88
+ return { status: result.status, stdout: result.stdout, stderr: result.stderr };
89
+ }
90
+ function runNpmPackDryRun() {
91
+ const result = spawnSync("npm", ["pack", "--dry-run", "--json"], {
92
+ cwd: process.cwd(),
93
+ encoding: "utf8",
94
+ timeout: 60_000,
95
+ shell: true,
96
+ });
97
+ if (result.status !== 0 || result.error)
98
+ return fail("NPM_PACK_DRY_RUN_FAILED", result.error?.message || result.stderr || result.stdout, false, "npm_pack");
185
99
  try {
186
- const r = await handleApproveToken({ token: "USDC", amount: "100" });
187
- if (r.success) {
188
- console.log(`12. approve_token tx=${r.txHash.slice(0, 18)}...`);
189
- ok("approve_token", `approved 100 USDC, tx=${r.txHash}`);
190
- }
191
- else {
192
- console.log(`12. approve_token → not approved: ${r.error}`);
193
- fail("approve_token", r.error || "unknown");
194
- }
100
+ const parsed = JSON.parse(result.stdout.trim());
101
+ const files = (parsed[0]?.files || []).map((f) => f.path);
102
+ const unsafeExact = new Set([".env", ".env.local", "wallet-store.json"]);
103
+ const unsafe = files.filter((file) => unsafeExact.has(file) ||
104
+ file.endsWith(".pem") ||
105
+ file.endsWith(".key") ||
106
+ file.includes("node_modules/") ||
107
+ file.startsWith("logs/") ||
108
+ file.endsWith(".log"));
109
+ return unsafe.length ? fail("NPM_PACK_UNSAFE_FILES", unsafe.join(", "), false, "npm_pack") : ok({ totalFiles: files.length, unsafe, includesEnvExample: files.includes(".env.example") });
110
+ }
111
+ catch (err) {
112
+ return fail("NPM_PACK_PARSE_FAILED", err instanceof Error ? err.message : String(err), false, "npm_pack");
195
113
  }
196
- catch (e) {
197
- console.log(`12. approve_token → ❌`);
198
- fail("approve_token", e.message);
114
+ }
115
+ function hasAll(text, parts) {
116
+ return parts.every((part) => text.includes(part));
117
+ }
118
+ function summarize(value) {
119
+ if (!value.success)
120
+ return `${value.error.code}: ${value.error.message}`;
121
+ if (value.data && typeof value.data === "object") {
122
+ const keys = Object.keys(value.data).slice(0, 6).join(", ");
123
+ return `success data keys: ${keys || "none"}`;
124
+ }
125
+ return "success";
126
+ }
127
+ async function record(tool, fn, expect) {
128
+ try {
129
+ const result = normalize(await fn());
130
+ const passed = expect ? expect(result) : true;
131
+ rows.push({ tool, status: passed ? "PASS" : "FAIL", note: summarize(result) });
132
+ }
133
+ catch (err) {
134
+ const result = fail("SMOKE_TEST_HANDLER_THROW", err instanceof Error ? err.message : String(err), false, tool);
135
+ const passed = expect ? expect(result) : false;
136
+ rows.push({
137
+ tool,
138
+ status: passed ? "PASS" : "FAIL",
139
+ note: summarize(result),
140
+ });
199
141
  }
200
- // 13. get_transaction_status (use swap tx if we got one, else a known tx)
142
+ }
143
+ async function withLocalServer(handler) {
144
+ const app = express();
145
+ app.get("/supported", (_req, res) => {
146
+ res.json({ ok: true, kinds: [{ x402Version: 2, scheme: "exact", network: "eip155:688689" }] });
147
+ });
148
+ const server = await new Promise((resolve) => {
149
+ const instance = app.listen(0, "127.0.0.1", () => resolve(instance));
150
+ });
201
151
  try {
202
- const hash = swapTxHash || "0xe628679b65f8c5d34cc570ba16292f972ae8c38687fb4931a69ef63d37ff119d";
203
- const r = await handleGetTransactionStatus({ txHash: hash });
204
- console.log(`13. get_tx_status → status=${r.status} block=${r.blockNumber}`);
205
- ok("get_transaction_status", `status=${r.status}, block=${r.blockNumber}`);
152
+ const address = server.address();
153
+ if (!address || typeof address === "string")
154
+ throw new Error("Failed to open local test server");
155
+ return await handler(`http://127.0.0.1:${address.port}/supported`);
206
156
  }
207
- catch (e) {
208
- console.log(`13. get_tx_status → ❌`);
209
- fail("get_transaction_status", e.message);
157
+ finally {
158
+ if ("closeAllConnections" in server)
159
+ server.closeAllConnections();
160
+ await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
210
161
  }
211
- // 14. publish_risk_score (LIVE — publish to registry)
212
- try {
213
- const r = await handlePublishRiskScore({ action: "swap", tokenIn: "PHRS", tokenOut: "USDC", amount: "1" });
214
- if (r.success && r.onChain) {
215
- console.log(`14. publish_risk_score → score=${r.assessment.riskScore} tx=${r.onChain.txHash.slice(0, 18)}...`);
216
- ok("publish_risk_score", `score=${r.assessment.riskScore}, tx=${r.onChain.txHash}`);
162
+ }
163
+ async function run() {
164
+ console.log("\n🛡️ SafeHands non-destructive tool smoke test");
165
+ console.log(`Wallet used for dry/smoke checks: ${WALLET}`);
166
+ await record("assess_risk_validation", () => handleAssessRisk({ action: "swap", amount: "0.001", walletAddress: WALLET }), (res) => !res.success && res.error.code === "SMOKE_TEST_HANDLER_THROW");
167
+ await record("simulate_transaction_validation", () => handleSimulateTransaction({ action: "transfer", amount: "0.001", walletAddress: WALLET }));
168
+ await record("get_token_price_public_mode", () => handleGetTokenPrice({ token: "PHRS" }), (res) => (res.success && typeof res.data === "object" && res.data !== null && "publicMode" in res.data) ||
169
+ (!res.success && ["DODO_API_AUTH_REQUIRED", "DODO_API_UNAVAILABLE", "DODO_API_TIMEOUT", "DODO_API_RATE_LIMITED"].includes(res.error.code)));
170
+ await record("get_wallet_balance_invalid_wallet", () => handleGetWalletBalance({ walletAddress: "not-an-address" }), (res) => !res.success && res.error.code === "INVALID_WALLET_ADDRESS");
171
+ await record("estimate_gas_invalid_wallet", () => handleEstimateGas({ walletAddress: "not-an-address", action: "transfer", amount: "0.001", toAddress: RECIPIENT }), (res) => !res.success && res.error.code === "INVALID_WALLET_ADDRESS");
172
+ await record("check_token_security_invalid_address", () => handleCheckTokenSecurity({ tokenAddress: "not-an-address" }), (res) => !res.success && res.error.code === "INVALID_TOKEN_ADDRESS");
173
+ await record("execute_swap_guard", () => handleExecuteSwap({ tokenIn: "PHRS", tokenOut: "USDC", amountIn: "0.001" }), (res) => !res.success && res.error.code === "WRITE_TOOLS_DISABLED");
174
+ await record("send_payment_guard", () => handleSendPayment({ toAddress: RECIPIENT, amount: "0.001" }), (res) => !res.success && res.error.code === "WRITE_TOOLS_DISABLED");
175
+ await record("approve_token_guard", () => handleApproveToken({ token: "USDC", amount: "max" }), (res) => !res.success && res.error.code === "WRITE_TOOLS_DISABLED");
176
+ await record("publish_risk_score_guard", () => handlePublishRiskScore({ action: "transfer", amount: "0.001", toAddress: RECIPIENT }), (res) => !res.success && res.error.code === "WRITE_TOOLS_DISABLED");
177
+ await record("x402_ssrf_block", () => handleX402PayAndFetch({ url: "http://127.0.0.1:4021/supported" }), (res) => !res.success && res.error.code === "SSRF_BLOCKED");
178
+ await record("x402_free_local_allowed", async () => withLocalServer(async (url) => {
179
+ const previous = process.env.ALLOW_LOCAL_X402_FETCH;
180
+ process.env.ALLOW_LOCAL_X402_FETCH = "true";
181
+ try {
182
+ return await handleX402PayAndFetch({ url });
217
183
  }
218
- else {
219
- console.log(`14. publish_risk_score → ${r.error}`);
220
- fail("publish_risk_score", r.error || "unknown");
184
+ finally {
185
+ if (previous === undefined)
186
+ delete process.env.ALLOW_LOCAL_X402_FETCH;
187
+ else
188
+ process.env.ALLOW_LOCAL_X402_FETCH = previous;
221
189
  }
222
- }
223
- catch (e) {
224
- console.log(`14. publish_risk_score → ❌`);
225
- fail("publish_risk_score", e.message);
226
- }
227
- // 15. query_risk_registry
228
- try {
229
- const r = await handleQueryRiskRegistry({ walletAddress: WALLET });
230
- if (r.found && r.record) {
231
- console.log(`15. query_risk_registry → score=${r.record.score} level=${r.record.riskLevel}`);
232
- ok("query_risk_registry", `score=${r.record.score}, level=${r.record.riskLevel}, ts=${r.record.timestamp}`);
190
+ }), (res) => res.success === true && res.data.paymentExecuted === false);
191
+ await record("safehands_preflight_allow", () => handleSafeHandsPreflightCheck({ actionType: "send_payment", amount: "0.001", recipient: RECIPIENT, chainId: 688689, isMainnet: false }), (res) => res.success && res.data.decision !== "BLOCK");
192
+ await record("safehands_preflight_block_mainnet", () => handleSafeHandsPreflightCheck({ actionType: "send_payment", amount: "0.001", recipient: RECIPIENT, chainId: 1, isMainnet: true }), (res) => res.success && res.data.decision === "BLOCK" && res.data.riskLevel !== "LOW");
193
+ await record("safehands_preflight_block_unlimited_approval", () => handleSafeHandsPreflightCheck({ actionType: "approve_token", approvalAmount: "max", spender: RECIPIENT }), (res) => res.success && res.data.decision === "BLOCK");
194
+ await record("token_registry_status_canonical_usdc", () => handleTokenRegistryStatus({ token: USDC_ADDRESS }), (res) => res.success && res.data.status === "SKILL_ENGINE_CANONICAL_TOKEN" && res.data.verificationStatus === "DOCS_VERIFIED_FROM_PHAROS_SKILL_ENGINE");
195
+ await record("token_registry_status_custom_token", () => handleTokenRegistryStatus({ token: RECIPIENT }), (res) => res.success && res.data.status === "CUSTOM_NON_REGISTRY");
196
+ await record("token_registry_status_invalid", () => handleTokenRegistryStatus({ token: "not-an-address" }), (res) => res.success && res.data.status === "INVALID_ADDRESS");
197
+ await record("safehands_x402_preflight_ssrf", () => handleSafeHandsX402Preflight({ url: "http://127.0.0.1:4021/supported" }), (res) => !res.success && res.error.code === "SSRF_BLOCKED");
198
+ await record("safehands_wallet_health_no_wallet", () => handleSafeHandsWalletHealth({}), (res) => res.success && ["NOT_READY", "DEGRADED"].includes(String(res.data.status)));
199
+ await record("safehands_risk_report", () => handleSafeHandsRiskReport({ actionType: "approve_token", approvalAmount: "max", spender: RECIPIENT }), (res) => res.success && typeof res.data.summary === "string" && res.data.decision === "BLOCK");
200
+ await record("explain_risk", () => handleExplainRisk({ decision: "BLOCK", riskLevel: "HIGH", reasons: ["Unlimited approval requested"], requiredActions: ["Use limited approval"] }), (res) => res.success && String(res.data.explanation).includes("blocked"));
201
+ await record("managed_wallet_signer_deobfuscates", async () => {
202
+ const prevMode = process.env.WALLET_MODE;
203
+ const prevKey = process.env.WALLET_ENCRYPTION_KEY;
204
+ process.env.WALLET_MODE = "managed-testnet";
205
+ process.env.WALLET_ENCRYPTION_KEY = "test-only-key-for-smoke";
206
+ try {
207
+ const created = await handleCreateAgentWallet({ agentId: "smoke-agent", overwrite: true });
208
+ if (!created.success)
209
+ return created;
210
+ const signer = await getSigner("smoke-agent");
211
+ if (isSignerFailure(signer))
212
+ return fail(signer.error.code, signer.error.message, false, "managed_wallet_signer");
213
+ return ok({ address: signer.address, mode: signer.mode });
233
214
  }
234
- else {
235
- console.log(`15. query_risk_registry → not found`);
236
- ok("query_risk_registry", "no record (expected if never published)");
215
+ finally {
216
+ if (prevMode === undefined)
217
+ delete process.env.WALLET_MODE;
218
+ else
219
+ process.env.WALLET_MODE = prevMode;
220
+ if (prevKey === undefined)
221
+ delete process.env.WALLET_ENCRYPTION_KEY;
222
+ else
223
+ process.env.WALLET_ENCRYPTION_KEY = prevKey;
237
224
  }
238
- }
239
- catch (e) {
240
- console.log(`15. query_risk_registry → ❌`);
241
- fail("query_risk_registry", e.message);
242
- }
243
- // 16. check_token_security (GoPlus API — check mainnet USDC)
244
- try {
245
- const r = await handleCheckTokenSecurity({ tokenAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", chainId: 1 });
246
- if (r.success && r.securityProfile) {
247
- console.log(`16. check_token_sec → safetyScore=${r.securityProfile.safetyScore}`);
248
- ok("check_token_security", `score=${r.securityProfile.safetyScore}, honeypot=${r.securityProfile.isHoneypot}`);
225
+ }, (res) => res.success && res.data.mode === "managed-testnet");
226
+ await record("private_key_never_returned_in_wallet_response", async () => {
227
+ const prevMode = process.env.WALLET_MODE;
228
+ const prevKey = process.env.WALLET_ENCRYPTION_KEY;
229
+ process.env.WALLET_MODE = "managed-testnet";
230
+ process.env.WALLET_ENCRYPTION_KEY = "test-only-key-for-smoke";
231
+ try {
232
+ const created = await handleCreateAgentWallet({ agentId: "smoke-agent-no-key", overwrite: true });
233
+ const wallet = await handleGetAgentWallet({ agentId: "smoke-agent-no-key" });
234
+ return ok({ created, wallet, serialized: JSON.stringify({ created, wallet }) });
249
235
  }
250
- else {
251
- console.log(`16. check_token_sec → ${r.error}`);
252
- fail("check_token_security", r.error || "unknown");
236
+ finally {
237
+ if (prevMode === undefined)
238
+ delete process.env.WALLET_MODE;
239
+ else
240
+ process.env.WALLET_MODE = prevMode;
241
+ if (prevKey === undefined)
242
+ delete process.env.WALLET_ENCRYPTION_KEY;
243
+ else
244
+ process.env.WALLET_ENCRYPTION_KEY = prevKey;
253
245
  }
254
- }
255
- catch (e) {
256
- console.log(`16. check_token_sec → ❌`);
257
- fail("check_token_security", e.message);
258
- }
259
- // 17. x402_pay_and_fetch (E2E payment verification loop)
260
- try {
261
- const testPort = 4022;
262
- const testApp = express();
263
- testApp.use(express.json());
264
- const pharos = defineChain({
265
- id: 688_689,
266
- name: "Pharos Atlantic",
267
- nativeCurrency: { name: "PHRS", symbol: "PHRS", decimals: 18 },
268
- rpcUrls: { default: { http: ["https://atlantic.dplabs-internal.com/"] } },
269
- testnet: true,
270
- });
271
- const account = privateKeyToAccount(PK);
272
- const walletClient = createWalletClient({
273
- account,
274
- chain: pharos,
275
- transport: http(undefined, { timeout: 30_000 }),
276
- }).extend(publicActions);
277
- const evmSigner = toFacilitatorEvmSigner({
278
- address: account.address,
279
- getCode: (args) => walletClient.getCode(args),
280
- readContract: (args) => walletClient.readContract({ ...args, args: args.args || [] }),
281
- verifyTypedData: (args) => walletClient.verifyTypedData(args),
282
- writeContract: (args) => walletClient.writeContract({ ...args, args: args.args || [] }),
283
- sendTransaction: (args) => walletClient.sendTransaction(args),
284
- waitForTransactionReceipt: (args) => walletClient.waitForTransactionReceipt(args),
285
- });
286
- const testFacilitator = new x402Facilitator();
287
- testFacilitator.register("eip155:688689", new FacilitatorExactEvmScheme(evmSigner, {}));
288
- const testFacilitatorClient = new HTTPFacilitatorClient({ url: `http://localhost:${testPort}` });
289
- const testResourceServer = new x402ResourceServer(testFacilitatorClient);
290
- const testEvmScheme = new ServerExactEvmScheme();
291
- testEvmScheme.registerMoneyParser(async (amount, network) => {
292
- if (network === "eip155:688689") {
293
- return {
294
- amount: Math.round(amount * 1e6).toString(),
295
- asset: "0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B",
296
- extra: { token: "USDC", name: "USDC", version: "2" },
297
- };
298
- }
299
- return null;
300
- });
301
- testResourceServer.register("eip155:688689", testEvmScheme);
302
- testApp.post("/verify", async (req, res) => {
303
- try {
304
- const { paymentPayload, paymentRequirements } = req.body;
305
- const result = await testFacilitator.verify(paymentPayload, paymentRequirements);
306
- res.json(result);
246
+ }, (res) => res.success && !String(res.data.serialized).toLowerCase().includes("privatekey") && !String(res.data.serialized).toLowerCase().includes("encryptedkey"));
247
+ await record("write_tools_use_signer_provider", async () => {
248
+ const offenders = readAllToolSourceFiles()
249
+ .filter((f) => /process\.env\.PRIVATE_KEY/.test(f.content) && !f.path.endsWith("x402Server.ts"))
250
+ .map((f) => f.path);
251
+ return offenders.length ? fail("PRIVATE_KEY_DIRECT_USAGE", offenders.join(", "), false, "source_scan") : ok({ offenders });
252
+ }, (res) => res.success);
253
+ await record("no_private_key_usage_outside_signer_provider", async () => {
254
+ const root = join(process.cwd(), "src");
255
+ const offenders = [];
256
+ function walk(dir) {
257
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
258
+ const full = join(dir, entry.name);
259
+ if (entry.isDirectory())
260
+ walk(full);
261
+ else if (entry.isFile() && entry.name.endsWith(".ts")) {
262
+ const rel = full.replace(process.cwd(), "").replace(/^[\\/]/, "").replace(/\\/g, "/");
263
+ if (rel.startsWith("src/lib/signer/"))
264
+ continue;
265
+ const content = readFileSync(full, "utf8");
266
+ if (/process\.env\.PRIVATE_KEY/.test(content))
267
+ offenders.push(rel);
268
+ }
307
269
  }
308
- catch (e) {
309
- res.status(500).json({ error: e.message });
310
- }
311
- });
312
- testApp.post("/settle", async (req, res) => {
313
- try {
314
- const { paymentPayload, paymentRequirements } = req.body;
315
- const result = await testFacilitator.settle(paymentPayload, paymentRequirements);
316
- res.json(result);
317
- }
318
- catch (e) {
319
- res.status(500).json({ error: e.message });
320
- }
321
- });
322
- testApp.get("/supported", (req, res) => {
323
- res.json(testFacilitator.getSupported());
324
- });
325
- testApp.use(paymentMiddleware({
326
- "GET /test-paid": {
327
- accepts: {
328
- scheme: "exact",
329
- price: "0.001",
330
- network: "eip155:688689",
331
- payTo: WALLET,
332
- },
333
- description: "Test paid endpoint",
334
- mimeType: "application/json",
335
- },
336
- }, testResourceServer));
337
- testApp.get("/test-paid", (req, res) => {
338
- res.json({ secret: "safehands-x402-passphrase" });
339
- });
340
- const serverListener = testApp.listen(testPort);
341
- const r = await handleX402PayAndFetch({
342
- url: `http://localhost:${testPort}/test-paid`,
343
- method: "GET",
344
- });
345
- serverListener.close();
346
- if (r.success && r.data && r.data.secret === "safehands-x402-passphrase") {
347
- console.log(`17. x402_pay_and_fetch → ✅ success, payment=${r.paymentExecuted}`);
348
- ok("x402_pay_and_fetch", `paymentExecuted=${r.paymentExecuted}`);
349
270
  }
350
- else {
351
- console.log(`17. x402_pay_and_fetch → ❌`);
352
- fail("x402_pay_and_fetch", r.error || "status mismatch");
353
- }
354
- }
355
- catch (e) {
356
- console.log(`17. x402_pay_and_fetch → ❌`);
357
- fail("x402_pay_and_fetch", e.message);
358
- }
359
- // 18. get_execution_history (last — slowest due to scan)
360
- try {
361
- const r = await handleGetExecutionHistory({ walletAddress: WALLET, limit: 5, filter: "all" });
362
- console.log(`18. get_exec_history → ${r.totalFetched} txs found`);
363
- ok("get_execution_history", `${r.totalFetched} txs`);
364
- }
365
- catch (e) {
366
- console.log(`18. get_exec_history → ❌`);
367
- fail("get_execution_history", e.message);
368
- }
369
- // ─── Summary ──────────────────────────────────────────────────────────
370
- const postBal = formatEther(await publicClient.getBalance({ address: WALLET }));
371
- console.log("\n" + "═".repeat(90));
372
- console.log(" # │ Status │ Tool │ Key Output");
373
- console.log("".repeat(90));
374
- results.forEach((r, i) => {
375
- const num = String(i + 1).padStart(3);
376
- const tool = r.tool.padEnd(24);
377
- const key = r.key.length > 46 ? r.key.slice(0, 43) + "..." : r.key;
378
- console.log(`${num} ${r.status} │ ${tool}│ ${key}`);
271
+ walk(root);
272
+ return offenders.length ? fail("PRIVATE_KEY_DIRECT_USAGE", offenders.join(", "), false, "source_scan") : ok({ offenders });
273
+ }, (res) => res.success);
274
+ await record("skill_cli_preflight_valid_json", async () => runBuiltCli(["skill", "safehands_preflight_check", "--input-json", JSON.stringify({ actionType: "send_payment", chainId: 688689, isMainnet: false, amount: "0.001", recipient: RECIPIENT })]), (res) => res.success && res.data.decision !== undefined && res.data.chainId === 688689);
275
+ await record("skill_cli_invalid_json_structured_error", async () => runBuiltCli(["skill", "safehands_preflight_check", "--input-json", "{not-json"]), (res) => !res.success && res.error.code === "INVALID_INPUT_JSON" && res.error.source === "safehands_cli");
276
+ await record("skill_cli_readonly_without_private_key", async () => runBuiltCli(["skill", "token_registry_status", "--input-json", JSON.stringify({ token: USDC_ADDRESS })]), (res) => res.success && res.data.status === "SKILL_ENGINE_CANONICAL_TOKEN");
277
+ await record("skill_engine_example_files_exist", async () => {
278
+ const files = [
279
+ "examples/pharos-skill-engine/SKILL.safehands.md",
280
+ "examples/pharos-skill-engine/references/safehands.md",
281
+ "examples/pharos-skill-engine/assets/safehands/policy-defaults.json",
282
+ "examples/pharos-skill-engine/assets/safehands/example-actions.json",
283
+ ];
284
+ const missing = files.filter((file) => !existsSync(join(process.cwd(), file)));
285
+ return missing.length ? fail("SKILL_ENGINE_FILES_MISSING", missing.join(", "), false, "skill_engine_examples") : ok({ files });
286
+ }, (res) => res.success);
287
+ await record("skill_engine_reference_required_sections", async () => {
288
+ const text = readFileSync(join(process.cwd(), "examples/pharos-skill-engine/references/safehands.md"), "utf8");
289
+ const required = [
290
+ "## Overview",
291
+ "## Command Template",
292
+ "## SafeHands Preflight Check",
293
+ "## SafeHands x402 Preflight",
294
+ "## SafeHands Wallet Health",
295
+ "## Token Registry Status",
296
+ "## Explain Risk",
297
+ "## SafeHands Risk Report",
298
+ "### Error Handling",
299
+ "### Agent Guidelines",
300
+ ];
301
+ return hasAll(text, required) ? ok({ required }) : fail("SKILL_ENGINE_REFERENCE_INCOMPLETE", "Missing one or more required reference sections.", false, "skill_engine_examples");
302
+ }, (res) => res.success);
303
+ await record("skill_engine_skill_capability_rows", async () => {
304
+ const text = readFileSync(join(process.cwd(), "examples/pharos-skill-engine/SKILL.safehands.md"), "utf8");
305
+ const required = [
306
+ "SafeHands Preflight Check",
307
+ "SafeHands x402 Preflight",
308
+ "SafeHands Wallet Health",
309
+ "Token Registry Status",
310
+ "Explain Risk",
311
+ "SafeHands Risk Report",
312
+ "references/safehands.md#safehands-preflight-check",
313
+ ];
314
+ return hasAll(text, required) ? ok({ required }) : fail("SKILL_ENGINE_CAPABILITY_ROWS_MISSING", "SKILL.safehands.md is missing required capability rows.", false, "skill_engine_examples");
315
+ }, (res) => res.success);
316
+ // ── New: skill/ package structure tests ─────────────────────────────
317
+ await record("skill_package_skill_md_exists", async () => {
318
+ const path = join(process.cwd(), "skill/SKILL.md");
319
+ return existsSync(path) ? ok({ file: "skill/SKILL.md" }) : fail("SKILL_PACKAGE_MISSING", "skill/SKILL.md not found", false, "skill_package");
320
+ }, (res) => res.success);
321
+ await record("skill_package_yaml_frontmatter", async () => {
322
+ const text = readFileSync(join(process.cwd(), "skill/SKILL.md"), "utf8");
323
+ const hasYaml = text.startsWith("---") && text.includes("name: safehands-pharos-guard");
324
+ return hasYaml ? ok({ frontmatter: true }) : fail("SKILL_PACKAGE_NO_FRONTMATTER", "YAML frontmatter missing or name mismatch", false, "skill_package");
325
+ }, (res) => res.success);
326
+ await record("skill_package_references_exist", async () => {
327
+ const path = join(process.cwd(), "skill/references/safehands.md");
328
+ return existsSync(path) ? ok({ file: "skill/references/safehands.md" }) : fail("SKILL_PACKAGE_REF_MISSING", "skill/references/safehands.md not found", false, "skill_package");
329
+ }, (res) => res.success);
330
+ await record("skill_package_assets_exist", async () => {
331
+ const files = [
332
+ "skill/assets/safehands/policy-defaults.json",
333
+ "skill/assets/safehands/example-actions.json",
334
+ ];
335
+ const missing = files.filter((f) => !existsSync(join(process.cwd(), f)));
336
+ return missing.length === 0 ? ok({ files }) : fail("SKILL_PACKAGE_ASSETS_MISSING", missing.join(", "), false, "skill_package");
337
+ }, (res) => res.success);
338
+ await record("skill_package_example_uses_skill_engine_usdc", async () => {
339
+ const text = readFileSync(join(process.cwd(), "skill/assets/safehands/example-actions.json"), "utf8");
340
+ const usesSkillEngineUsdc = text.includes("0xE0BE08c77f415F577A1B3A9aD7a1Df1479564ec8");
341
+ const usesCircleUsdc = text.includes("0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B");
342
+ return usesSkillEngineUsdc && !usesCircleUsdc
343
+ ? ok({ correctUsdc: true })
344
+ : fail("SKILL_EXAMPLE_WRONG_USDC", "example-actions.json should use Skill Engine USDC (0xE0BE...), not Circle USDC (0xcfC8...)", false, "skill_package");
345
+ }, (res) => res.success);
346
+ await record("token_registry_circle_usdc_alternate", () => handleTokenRegistryStatus({ token: "0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B" }), (res) => res.success && res.data.status === "ALTERNATE_SOURCE_TOKEN" && res.data.verificationStatus === "CIRCLE_REFERENCED_USDC");
347
+ await record("cli_help_works", async () => {
348
+ const raw = runBuiltCliRaw(["--help"]);
349
+ return raw.status === 0 && raw.stdout.includes("Transaction Safety Firewall") && raw.stdout.includes("safehands_preflight_check")
350
+ ? ok({ stdoutBytes: raw.stdout.length })
351
+ : fail("CLI_HELP_FAILED", `status=${raw.status} stdout=${raw.stdout.slice(0, 200)} stderr=${raw.stderr.slice(0, 200)}`, false, "cli_help");
352
+ }, (res) => res.success);
353
+ await record("demo_runs_or_fails_gracefully", async () => {
354
+ const raw = runBuiltCliRaw(["--demo"]);
355
+ return raw.status === 0 && raw.stdout.includes("Demo Complete") && raw.stdout.includes("SSRF_BLOCKED")
356
+ ? ok({ stdoutBytes: raw.stdout.length })
357
+ : fail("DEMO_FAILED", `status=${raw.status} stdout=${raw.stdout.slice(0, 300)} stderr=${raw.stderr.slice(0, 300)}`, false, "demo");
358
+ }, (res) => res.success);
359
+ await record("readme_contains_final_positioning", async () => {
360
+ const text = readFileSync(join(process.cwd(), "README.md"), "utf8");
361
+ const required = [
362
+ "# SafeHands-Pharos: Transaction Safety Firewall for AI Agents",
363
+ "Pharos Skill Engine-compatible MCP package",
364
+ "SafeHands is a guardrail layer",
365
+ "WRITE_TOOLS_ENABLED=false",
366
+ "Testnet scope",
367
+ "Using SafeHands with Pharos Skill Engine",
368
+ ];
369
+ return hasAll(text, required) ? ok({ required }) : fail("README_POSITIONING_INCOMPLETE", "Missing final positioning text.", false, "readme");
370
+ }, (res) => res.success);
371
+ await record("env_example_safe_placeholders", async () => {
372
+ const text = readFileSync(join(process.cwd(), ".env.example"), "utf8");
373
+ const required = ["WALLET_MODE=none", "WRITE_TOOLS_ENABLED=false", "PRIVATE_KEY=", "MAX_X402_PAYMENT_USDC=0.01"];
374
+ const hasFakeSecret = /PRIVATE_KEY=0x[0-9a-fA-F]{16,}/.test(text) || /WALLET_ENCRYPTION_KEY=.+\S/.test(text);
375
+ return hasAll(text, required) && !hasFakeSecret ? ok({ required }) : fail("ENV_EXAMPLE_UNSAFE", "Missing safe defaults or contains secret-looking placeholders.", false, "env_example");
376
+ }, (res) => res.success);
377
+ await record("npm_pack_excludes_secrets", async () => runNpmPackDryRun(), (res) => res.success && res.data.unsafe.length === 0);
378
+ console.log("\n# Status Tool Note");
379
+ console.log("─".repeat(100));
380
+ rows.forEach((r, i) => {
381
+ console.log(`${String(i + 1).padStart(2, "0")} ${r.status.padEnd(6)} ${r.tool.padEnd(28)} ${r.note}`);
379
382
  });
380
- console.log("".repeat(90));
381
- const passed = results.filter(r => r.status.includes("PASS")).length;
382
- const failed = results.filter(r => r.status.includes("FAIL")).length;
383
- console.log(`\n Balance: ${bal} ${postBal} PHRS`);
384
- console.log(` ${passed}/${results.length} passed, ${failed} failed`);
385
- console.log(` ${failed === 0 ? "🎉 ALL 17 TOOLS PASSED" : "⚠️ SOME TOOLS FAILED"}\n`);
386
- process.exit(failed === 0 ? 0 : 1);
383
+ const failed = rows.filter((r) => r.status === "FAIL");
384
+ console.log("─".repeat(100));
385
+ console.log(`${rows.length - failed.length}/${rows.length} smoke checks passed.`);
386
+ if (failed.length > 0) {
387
+ console.error("\nFailed smoke checks:");
388
+ for (const f of failed)
389
+ console.error(`- ${f.tool}: ${f.note}`);
390
+ process.exit(1);
391
+ }
387
392
  }
388
- run().catch(e => { console.error("Fatal:", e); process.exit(1); });
393
+ run().catch((err) => {
394
+ const structured = fail("SMOKE_TEST_FAILED", err instanceof Error ? err.message : String(err), false, "testTools");
395
+ console.error(JSON.stringify(structured, null, 2));
396
+ process.exit(1);
397
+ });
389
398
  //# sourceMappingURL=testTools.js.map