safehands-pharos 1.4.0 → 2.6.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 (571) hide show
  1. package/.agents/policies/default.json +15 -0
  2. package/.env.example +81 -64
  3. package/README.md +301 -333
  4. package/SECURITY.md +112 -0
  5. package/SKILL.md +59 -0
  6. package/contracts/SafeHandsAttestation.sol +182 -0
  7. package/contracts/SafeHandsRegistry.sol +149 -0
  8. package/dist/agent/SafeHandsGuardianAgent.d.ts +39 -0
  9. package/dist/agent/SafeHandsGuardianAgent.d.ts.map +1 -0
  10. package/dist/agent/SafeHandsGuardianAgent.js +98 -0
  11. package/dist/agent/SafeHandsGuardianAgent.js.map +1 -0
  12. package/dist/agent/agentDecisionFormatter.d.ts +43 -0
  13. package/dist/agent/agentDecisionFormatter.d.ts.map +1 -0
  14. package/dist/agent/agentDecisionFormatter.js +96 -0
  15. package/dist/agent/agentDecisionFormatter.js.map +1 -0
  16. package/dist/agent/agentEnrich.d.ts +28 -0
  17. package/dist/agent/agentEnrich.d.ts.map +1 -0
  18. package/dist/agent/agentEnrich.js +99 -0
  19. package/dist/agent/agentEnrich.js.map +1 -0
  20. package/dist/agent/agentIntentClassifier.d.ts +44 -0
  21. package/dist/agent/agentIntentClassifier.d.ts.map +1 -0
  22. package/dist/agent/agentIntentClassifier.js +77 -0
  23. package/dist/agent/agentIntentClassifier.js.map +1 -0
  24. package/dist/agent/agentPolicyResolver.d.ts +44 -0
  25. package/dist/agent/agentPolicyResolver.d.ts.map +1 -0
  26. package/dist/agent/agentPolicyResolver.js +94 -0
  27. package/dist/agent/agentPolicyResolver.js.map +1 -0
  28. package/dist/agent/agentRuntime.d.ts +12 -0
  29. package/dist/agent/agentRuntime.d.ts.map +1 -0
  30. package/dist/agent/agentRuntime.js +31 -0
  31. package/dist/agent/agentRuntime.js.map +1 -0
  32. package/dist/agent/agentToolRouter.d.ts +24 -0
  33. package/dist/agent/agentToolRouter.d.ts.map +1 -0
  34. package/dist/agent/agentToolRouter.js +89 -0
  35. package/dist/agent/agentToolRouter.js.map +1 -0
  36. package/dist/agent/guardianOperator.d.ts +35 -0
  37. package/dist/agent/guardianOperator.d.ts.map +1 -0
  38. package/dist/agent/guardianOperator.js +65 -0
  39. package/dist/agent/guardianOperator.js.map +1 -0
  40. package/dist/agent/index.d.ts +8 -0
  41. package/dist/agent/index.d.ts.map +1 -0
  42. package/dist/agent/index.js +14 -0
  43. package/dist/agent/index.js.map +1 -0
  44. package/dist/api/activityRoutes.d.ts +70 -0
  45. package/dist/api/activityRoutes.d.ts.map +1 -0
  46. package/dist/api/activityRoutes.js +100 -0
  47. package/dist/api/activityRoutes.js.map +1 -0
  48. package/dist/api/agentRoutes.d.ts +16 -0
  49. package/dist/api/agentRoutes.d.ts.map +1 -0
  50. package/dist/api/agentRoutes.js +37 -0
  51. package/dist/api/agentRoutes.js.map +1 -0
  52. package/dist/api/broadcastRoutes.d.ts +34 -0
  53. package/dist/api/broadcastRoutes.d.ts.map +1 -0
  54. package/dist/api/broadcastRoutes.js +147 -0
  55. package/dist/api/broadcastRoutes.js.map +1 -0
  56. package/dist/api/consolePage.d.ts +2 -0
  57. package/dist/api/consolePage.d.ts.map +1 -0
  58. package/dist/api/consolePage.js +206 -0
  59. package/dist/api/consolePage.js.map +1 -0
  60. package/dist/api/httpHardening.d.ts +75 -0
  61. package/dist/api/httpHardening.d.ts.map +1 -0
  62. package/dist/api/httpHardening.js +122 -0
  63. package/dist/api/httpHardening.js.map +1 -0
  64. package/dist/api/indexerRoutes.d.ts +46 -0
  65. package/dist/api/indexerRoutes.d.ts.map +1 -0
  66. package/dist/api/indexerRoutes.js +75 -0
  67. package/dist/api/indexerRoutes.js.map +1 -0
  68. package/dist/api/paidRoutes.d.ts +8 -0
  69. package/dist/api/paidRoutes.d.ts.map +1 -0
  70. package/dist/api/paidRoutes.js +175 -0
  71. package/dist/api/paidRoutes.js.map +1 -0
  72. package/dist/api/prepareRoutes.d.ts +58 -0
  73. package/dist/api/prepareRoutes.d.ts.map +1 -0
  74. package/dist/api/prepareRoutes.js +90 -0
  75. package/dist/api/prepareRoutes.js.map +1 -0
  76. package/dist/api/response.d.ts +39 -0
  77. package/dist/api/response.d.ts.map +1 -0
  78. package/dist/api/response.js +53 -0
  79. package/dist/api/response.js.map +1 -0
  80. package/dist/api/routes.d.ts +175 -0
  81. package/dist/api/routes.d.ts.map +1 -0
  82. package/dist/api/routes.js +276 -0
  83. package/dist/api/routes.js.map +1 -0
  84. package/dist/api/schemas.d.ts +147 -0
  85. package/dist/api/schemas.d.ts.map +1 -0
  86. package/dist/api/schemas.js +81 -0
  87. package/dist/api/schemas.js.map +1 -0
  88. package/dist/api/server.d.ts +11 -0
  89. package/dist/api/server.d.ts.map +1 -0
  90. package/dist/api/server.js +505 -0
  91. package/dist/api/server.js.map +1 -0
  92. package/dist/api/toolRoutes.d.ts +14 -0
  93. package/dist/api/toolRoutes.d.ts.map +1 -0
  94. package/dist/api/toolRoutes.js +138 -0
  95. package/dist/api/toolRoutes.js.map +1 -0
  96. package/dist/api/walletPrepareRoutes.d.ts +74 -0
  97. package/dist/api/walletPrepareRoutes.d.ts.map +1 -0
  98. package/dist/api/walletPrepareRoutes.js +127 -0
  99. package/dist/api/walletPrepareRoutes.js.map +1 -0
  100. package/dist/api/x402Gate.d.ts +51 -0
  101. package/dist/api/x402Gate.d.ts.map +1 -0
  102. package/dist/api/x402Gate.js +184 -0
  103. package/dist/api/x402Gate.js.map +1 -0
  104. package/dist/cli.d.ts +5 -5
  105. package/dist/cli.d.ts.map +1 -1
  106. package/dist/cli.js +154 -124
  107. package/dist/cli.js.map +1 -1
  108. package/dist/data/ecosystemRegistry.data.d.ts +3 -0
  109. package/dist/data/ecosystemRegistry.data.d.ts.map +1 -0
  110. package/dist/data/ecosystemRegistry.data.js +582 -0
  111. package/dist/data/ecosystemRegistry.data.js.map +1 -0
  112. package/dist/demo.d.ts +1 -1
  113. package/dist/demo.d.ts.map +1 -1
  114. package/dist/demo.js +206 -171
  115. package/dist/demo.js.map +1 -1
  116. package/dist/index.d.ts +2 -2
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +249 -164
  119. package/dist/index.js.map +1 -1
  120. package/dist/init.d.ts +1 -1
  121. package/dist/init.d.ts.map +1 -1
  122. package/dist/init.js +127 -65
  123. package/dist/init.js.map +1 -1
  124. package/dist/lib/analysis/approval.d.ts +29 -0
  125. package/dist/lib/analysis/approval.d.ts.map +1 -0
  126. package/dist/lib/analysis/approval.js +162 -0
  127. package/dist/lib/analysis/approval.js.map +1 -0
  128. package/dist/lib/analysis/calldata.d.ts +60 -0
  129. package/dist/lib/analysis/calldata.d.ts.map +1 -0
  130. package/dist/lib/analysis/calldata.js +349 -0
  131. package/dist/lib/analysis/calldata.js.map +1 -0
  132. package/dist/lib/analysis/contractIntel.d.ts +65 -0
  133. package/dist/lib/analysis/contractIntel.d.ts.map +1 -0
  134. package/dist/lib/analysis/contractIntel.js +320 -0
  135. package/dist/lib/analysis/contractIntel.js.map +1 -0
  136. package/dist/lib/analysis/evm.d.ts +42 -0
  137. package/dist/lib/analysis/evm.d.ts.map +1 -0
  138. package/dist/lib/analysis/evm.js +185 -0
  139. package/dist/lib/analysis/evm.js.map +1 -0
  140. package/dist/lib/analysis/gas.d.ts +4 -0
  141. package/dist/lib/analysis/gas.d.ts.map +1 -0
  142. package/dist/lib/analysis/gas.js +13 -0
  143. package/dist/lib/analysis/gas.js.map +1 -0
  144. package/dist/lib/analysis/index.d.ts +11 -0
  145. package/dist/lib/analysis/index.d.ts.map +1 -0
  146. package/dist/lib/analysis/index.js +16 -0
  147. package/dist/lib/analysis/index.js.map +1 -0
  148. package/dist/lib/analysis/pharosTokens.d.ts +31 -0
  149. package/dist/lib/analysis/pharosTokens.d.ts.map +1 -0
  150. package/dist/lib/analysis/pharosTokens.js +78 -0
  151. package/dist/lib/analysis/pharosTokens.js.map +1 -0
  152. package/dist/lib/analysis/safeTx.d.ts +27 -0
  153. package/dist/lib/analysis/safeTx.d.ts.map +1 -0
  154. package/dist/lib/analysis/safeTx.js +118 -0
  155. package/dist/lib/analysis/safeTx.js.map +1 -0
  156. package/dist/lib/analysis/token.d.ts +21 -0
  157. package/dist/lib/analysis/token.d.ts.map +1 -0
  158. package/dist/lib/analysis/token.js +151 -0
  159. package/dist/lib/analysis/token.js.map +1 -0
  160. package/dist/lib/analysis/types.d.ts +49 -0
  161. package/dist/lib/analysis/types.d.ts.map +1 -0
  162. package/dist/lib/analysis/types.js +10 -0
  163. package/dist/lib/analysis/types.js.map +1 -0
  164. package/dist/lib/analysis/x402.d.ts +24 -0
  165. package/dist/lib/analysis/x402.d.ts.map +1 -0
  166. package/dist/lib/analysis/x402.js +60 -0
  167. package/dist/lib/analysis/x402.js.map +1 -0
  168. package/dist/lib/auditLog.d.ts +1 -0
  169. package/dist/lib/auditLog.d.ts.map +1 -1
  170. package/dist/lib/auditLog.js +0 -0
  171. package/dist/lib/auditLog.js.map +1 -1
  172. package/dist/lib/config.d.ts +77 -0
  173. package/dist/lib/config.d.ts.map +1 -0
  174. package/dist/lib/config.js +120 -0
  175. package/dist/lib/config.js.map +1 -0
  176. package/dist/lib/constants.d.ts +452 -291
  177. package/dist/lib/constants.d.ts.map +1 -1
  178. package/dist/lib/constants.js +509 -292
  179. package/dist/lib/constants.js.map +1 -1
  180. package/dist/lib/dodoApi.d.ts +101 -78
  181. package/dist/lib/dodoApi.d.ts.map +1 -1
  182. package/dist/lib/dodoApi.js +259 -196
  183. package/dist/lib/dodoApi.js.map +1 -1
  184. package/dist/lib/ecosystemRegistry.d.ts +116 -0
  185. package/dist/lib/ecosystemRegistry.d.ts.map +1 -0
  186. package/dist/lib/ecosystemRegistry.js +178 -0
  187. package/dist/lib/ecosystemRegistry.js.map +1 -0
  188. package/dist/lib/envLoader.d.ts +2 -0
  189. package/dist/lib/envLoader.d.ts.map +1 -0
  190. package/dist/lib/envLoader.js +65 -0
  191. package/dist/lib/envLoader.js.map +1 -0
  192. package/dist/lib/goldsky.d.ts +58 -0
  193. package/dist/lib/goldsky.d.ts.map +1 -0
  194. package/dist/lib/goldsky.js +275 -0
  195. package/dist/lib/goldsky.js.map +1 -0
  196. package/dist/lib/guardian/decision.d.ts +23 -0
  197. package/dist/lib/guardian/decision.d.ts.map +1 -0
  198. package/dist/lib/guardian/decision.js +40 -0
  199. package/dist/lib/guardian/decision.js.map +1 -0
  200. package/dist/lib/http.d.ts +13 -14
  201. package/dist/lib/http.d.ts.map +1 -1
  202. package/dist/lib/http.js +383 -118
  203. package/dist/lib/http.js.map +1 -1
  204. package/dist/lib/managedExecution.d.ts +19 -0
  205. package/dist/lib/managedExecution.d.ts.map +1 -0
  206. package/dist/lib/managedExecution.js +64 -0
  207. package/dist/lib/managedExecution.js.map +1 -0
  208. package/dist/lib/merkleBatcher.d.ts +23 -0
  209. package/dist/lib/merkleBatcher.d.ts.map +1 -0
  210. package/dist/lib/merkleBatcher.js +62 -0
  211. package/dist/lib/merkleBatcher.js.map +1 -0
  212. package/dist/lib/networks.d.ts +48 -0
  213. package/dist/lib/networks.d.ts.map +1 -0
  214. package/dist/lib/networks.js +103 -0
  215. package/dist/lib/networks.js.map +1 -0
  216. package/dist/lib/observability/accessContext.d.ts +29 -0
  217. package/dist/lib/observability/accessContext.d.ts.map +1 -0
  218. package/dist/lib/observability/accessContext.js +50 -0
  219. package/dist/lib/observability/accessContext.js.map +1 -0
  220. package/dist/lib/observability/activityStore.d.ts +70 -0
  221. package/dist/lib/observability/activityStore.d.ts.map +1 -0
  222. package/dist/lib/observability/activityStore.js +149 -0
  223. package/dist/lib/observability/activityStore.js.map +1 -0
  224. package/dist/lib/observability/apiKey.d.ts +55 -0
  225. package/dist/lib/observability/apiKey.d.ts.map +1 -0
  226. package/dist/lib/observability/apiKey.js +141 -0
  227. package/dist/lib/observability/apiKey.js.map +1 -0
  228. package/dist/lib/observability/logger.d.ts +30 -0
  229. package/dist/lib/observability/logger.d.ts.map +1 -0
  230. package/dist/lib/observability/logger.js +30 -0
  231. package/dist/lib/observability/logger.js.map +1 -0
  232. package/dist/lib/observability/quota.d.ts +33 -0
  233. package/dist/lib/observability/quota.d.ts.map +1 -0
  234. package/dist/lib/observability/quota.js +49 -0
  235. package/dist/lib/observability/quota.js.map +1 -0
  236. package/dist/lib/observability/requestId.d.ts +8 -0
  237. package/dist/lib/observability/requestId.d.ts.map +1 -0
  238. package/dist/lib/observability/requestId.js +24 -0
  239. package/dist/lib/observability/requestId.js.map +1 -0
  240. package/dist/lib/observability/sanitize.d.ts +77 -0
  241. package/dist/lib/observability/sanitize.d.ts.map +1 -0
  242. package/dist/lib/observability/sanitize.js +228 -0
  243. package/dist/lib/observability/sanitize.js.map +1 -0
  244. package/dist/lib/observability/scopes.d.ts +20 -0
  245. package/dist/lib/observability/scopes.d.ts.map +1 -0
  246. package/dist/lib/observability/scopes.js +77 -0
  247. package/dist/lib/observability/scopes.js.map +1 -0
  248. package/dist/lib/okxDexApi.d.ts +106 -0
  249. package/dist/lib/okxDexApi.d.ts.map +1 -0
  250. package/dist/lib/okxDexApi.js +221 -0
  251. package/dist/lib/okxDexApi.js.map +1 -0
  252. package/dist/lib/persistentJsonStore.d.ts +5 -0
  253. package/dist/lib/persistentJsonStore.d.ts.map +1 -0
  254. package/dist/lib/persistentJsonStore.js +39 -0
  255. package/dist/lib/persistentJsonStore.js.map +1 -0
  256. package/dist/lib/pharos/attestationPublisher.d.ts +50 -0
  257. package/dist/lib/pharos/attestationPublisher.d.ts.map +1 -0
  258. package/dist/lib/pharos/attestationPublisher.js +262 -0
  259. package/dist/lib/pharos/attestationPublisher.js.map +1 -0
  260. package/dist/lib/pharos/ecosystem.d.ts +52 -0
  261. package/dist/lib/pharos/ecosystem.d.ts.map +1 -0
  262. package/dist/lib/pharos/ecosystem.js +431 -0
  263. package/dist/lib/pharos/ecosystem.js.map +1 -0
  264. package/dist/lib/pharos/ecosystemEvidence.d.ts +53 -0
  265. package/dist/lib/pharos/ecosystemEvidence.d.ts.map +1 -0
  266. package/dist/lib/pharos/ecosystemEvidence.js +133 -0
  267. package/dist/lib/pharos/ecosystemEvidence.js.map +1 -0
  268. package/dist/lib/pharos/rpc.d.ts +44 -0
  269. package/dist/lib/pharos/rpc.d.ts.map +1 -0
  270. package/dist/lib/pharos/rpc.js +90 -0
  271. package/dist/lib/pharos/rpc.js.map +1 -0
  272. package/dist/lib/pharos/rpcEvidence.d.ts +49 -0
  273. package/dist/lib/pharos/rpcEvidence.d.ts.map +1 -0
  274. package/dist/lib/pharos/rpcEvidence.js +46 -0
  275. package/dist/lib/pharos/rpcEvidence.js.map +1 -0
  276. package/dist/lib/pharos/rpcMethods.d.ts +46 -0
  277. package/dist/lib/pharos/rpcMethods.d.ts.map +1 -0
  278. package/dist/lib/pharos/rpcMethods.js +176 -0
  279. package/dist/lib/pharos/rpcMethods.js.map +1 -0
  280. package/dist/lib/pharos/spvVerifier.d.ts +47 -0
  281. package/dist/lib/pharos/spvVerifier.d.ts.map +1 -0
  282. package/dist/lib/pharos/spvVerifier.js +105 -0
  283. package/dist/lib/pharos/spvVerifier.js.map +1 -0
  284. package/dist/lib/pharos/userSignedBroadcaster.d.ts +10 -0
  285. package/dist/lib/pharos/userSignedBroadcaster.d.ts.map +1 -0
  286. package/dist/lib/pharos/userSignedBroadcaster.js +50 -0
  287. package/dist/lib/pharos/userSignedBroadcaster.js.map +1 -0
  288. package/dist/lib/pharosClient.d.ts +53 -58
  289. package/dist/lib/pharosClient.d.ts.map +1 -1
  290. package/dist/lib/pharosClient.js +56 -63
  291. package/dist/lib/pharosClient.js.map +1 -1
  292. package/dist/lib/policy/actionPolicyEngine.d.ts +89 -53
  293. package/dist/lib/policy/actionPolicyEngine.d.ts.map +1 -1
  294. package/dist/lib/policy/actionPolicyEngine.js +373 -212
  295. package/dist/lib/policy/actionPolicyEngine.js.map +1 -1
  296. package/dist/lib/policy/agentPolicy.d.ts +29 -0
  297. package/dist/lib/policy/agentPolicy.d.ts.map +1 -0
  298. package/dist/lib/policy/agentPolicy.js +154 -0
  299. package/dist/lib/policy/agentPolicy.js.map +1 -0
  300. package/dist/lib/policy/policyPresets.d.ts +30 -0
  301. package/dist/lib/policy/policyPresets.d.ts.map +1 -0
  302. package/dist/lib/policy/policyPresets.js +124 -0
  303. package/dist/lib/policy/policyPresets.js.map +1 -0
  304. package/dist/lib/policy/writeExecutionGate.d.ts +23 -0
  305. package/dist/lib/policy/writeExecutionGate.d.ts.map +1 -0
  306. package/dist/lib/policy/writeExecutionGate.js +65 -0
  307. package/dist/lib/policy/writeExecutionGate.js.map +1 -0
  308. package/dist/lib/preparedTxStore.d.ts +21 -0
  309. package/dist/lib/preparedTxStore.d.ts.map +1 -0
  310. package/dist/lib/preparedTxStore.js +90 -0
  311. package/dist/lib/preparedTxStore.js.map +1 -0
  312. package/dist/lib/price/chainlinkPushProvider.d.ts +34 -0
  313. package/dist/lib/price/chainlinkPushProvider.d.ts.map +1 -0
  314. package/dist/lib/price/chainlinkPushProvider.js +36 -0
  315. package/dist/lib/price/chainlinkPushProvider.js.map +1 -0
  316. package/dist/lib/price/feedRegistry.d.ts +26 -0
  317. package/dist/lib/price/feedRegistry.d.ts.map +1 -0
  318. package/dist/lib/price/feedRegistry.js +67 -0
  319. package/dist/lib/price/feedRegistry.js.map +1 -0
  320. package/dist/lib/price/priceResolver.d.ts +35 -0
  321. package/dist/lib/price/priceResolver.d.ts.map +1 -0
  322. package/dist/lib/price/priceResolver.js +246 -0
  323. package/dist/lib/price/priceResolver.js.map +1 -0
  324. package/dist/lib/price/supraProvider.d.ts +9 -0
  325. package/dist/lib/price/supraProvider.d.ts.map +1 -0
  326. package/dist/lib/price/supraProvider.js +17 -0
  327. package/dist/lib/price/supraProvider.js.map +1 -0
  328. package/dist/lib/price/types.d.ts +67 -0
  329. package/dist/lib/price/types.d.ts.map +1 -0
  330. package/dist/lib/price/types.js +7 -0
  331. package/dist/lib/price/types.js.map +1 -0
  332. package/dist/lib/productionGuards.d.ts +16 -0
  333. package/dist/lib/productionGuards.d.ts.map +1 -0
  334. package/dist/lib/productionGuards.js +93 -0
  335. package/dist/lib/productionGuards.js.map +1 -0
  336. package/dist/lib/recipientSafety.d.ts +5 -0
  337. package/dist/lib/recipientSafety.d.ts.map +1 -0
  338. package/dist/lib/recipientSafety.js +28 -0
  339. package/dist/lib/recipientSafety.js.map +1 -0
  340. package/dist/lib/riskEngine.d.ts +45 -26
  341. package/dist/lib/riskEngine.d.ts.map +1 -1
  342. package/dist/lib/riskEngine.js +346 -283
  343. package/dist/lib/riskEngine.js.map +1 -1
  344. package/dist/lib/riskInclusion.d.ts +72 -0
  345. package/dist/lib/riskInclusion.d.ts.map +1 -0
  346. package/dist/lib/riskInclusion.js +252 -0
  347. package/dist/lib/riskInclusion.js.map +1 -0
  348. package/dist/lib/safeHandsRegistry.d.ts +32 -0
  349. package/dist/lib/safeHandsRegistry.d.ts.map +1 -0
  350. package/dist/lib/safeHandsRegistry.js +151 -0
  351. package/dist/lib/safeHandsRegistry.js.map +1 -0
  352. package/dist/lib/signer/index.d.ts +24 -24
  353. package/dist/lib/signer/index.d.ts.map +1 -1
  354. package/dist/lib/signer/index.js +110 -88
  355. package/dist/lib/signer/index.js.map +1 -1
  356. package/dist/lib/spendAccumulator.d.ts +24 -1
  357. package/dist/lib/spendAccumulator.d.ts.map +1 -1
  358. package/dist/lib/spendAccumulator.js +76 -6
  359. package/dist/lib/spendAccumulator.js.map +1 -1
  360. package/dist/lib/toolResponse.d.ts +30 -25
  361. package/dist/lib/toolResponse.d.ts.map +1 -1
  362. package/dist/lib/toolResponse.js +57 -53
  363. package/dist/lib/toolResponse.js.map +1 -1
  364. package/dist/lib/validation.d.ts +7 -0
  365. package/dist/lib/validation.d.ts.map +1 -0
  366. package/dist/lib/validation.js +62 -0
  367. package/dist/lib/validation.js.map +1 -0
  368. package/dist/lib/wallet/index.d.ts +33 -37
  369. package/dist/lib/wallet/index.d.ts.map +1 -1
  370. package/dist/lib/wallet/index.js +141 -128
  371. package/dist/lib/wallet/index.js.map +1 -1
  372. package/dist/lib/x402ReplayStore.d.ts +6 -0
  373. package/dist/lib/x402ReplayStore.d.ts.map +1 -0
  374. package/dist/lib/x402ReplayStore.js +79 -0
  375. package/dist/lib/x402ReplayStore.js.map +1 -0
  376. package/dist/safehands.zip +0 -0
  377. package/dist/sdk.d.ts +14 -0
  378. package/dist/sdk.d.ts.map +1 -0
  379. package/dist/sdk.js +25 -0
  380. package/dist/sdk.js.map +1 -0
  381. package/dist/tools/approveToken.d.ts +66 -45
  382. package/dist/tools/approveToken.d.ts.map +1 -1
  383. package/dist/tools/approveToken.js +152 -85
  384. package/dist/tools/approveToken.js.map +1 -1
  385. package/dist/tools/assessRisk.d.ts +66 -79
  386. package/dist/tools/assessRisk.d.ts.map +1 -1
  387. package/dist/tools/assessRisk.js +63 -104
  388. package/dist/tools/assessRisk.js.map +1 -1
  389. package/dist/tools/checkAllowance.d.ts +49 -43
  390. package/dist/tools/checkAllowance.d.ts.map +1 -1
  391. package/dist/tools/checkAllowance.js +78 -56
  392. package/dist/tools/checkAllowance.js.map +1 -1
  393. package/dist/tools/checkTokenSecurity.d.ts +103 -46
  394. package/dist/tools/checkTokenSecurity.d.ts.map +1 -1
  395. package/dist/tools/checkTokenSecurity.js +240 -95
  396. package/dist/tools/checkTokenSecurity.js.map +1 -1
  397. package/dist/tools/createAgentWallet.d.ts +30 -26
  398. package/dist/tools/createAgentWallet.d.ts.map +1 -1
  399. package/dist/tools/createAgentWallet.js +87 -58
  400. package/dist/tools/createAgentWallet.js.map +1 -1
  401. package/dist/tools/estimateGas.d.ts +84 -79
  402. package/dist/tools/estimateGas.d.ts.map +1 -1
  403. package/dist/tools/estimateGas.js +130 -124
  404. package/dist/tools/estimateGas.js.map +1 -1
  405. package/dist/tools/executeSwap.d.ts +109 -61
  406. package/dist/tools/executeSwap.d.ts.map +1 -1
  407. package/dist/tools/executeSwap.js +289 -141
  408. package/dist/tools/executeSwap.js.map +1 -1
  409. package/dist/tools/explainRisk.d.ts +29 -29
  410. package/dist/tools/explainRisk.d.ts.map +1 -1
  411. package/dist/tools/explainRisk.js +33 -32
  412. package/dist/tools/explainRisk.js.map +1 -1
  413. package/dist/tools/getAgentPolicy.d.ts +15 -0
  414. package/dist/tools/getAgentPolicy.d.ts.map +1 -0
  415. package/dist/tools/getAgentPolicy.js +16 -0
  416. package/dist/tools/getAgentPolicy.js.map +1 -0
  417. package/dist/tools/getAgentReputation.d.ts +31 -0
  418. package/dist/tools/getAgentReputation.d.ts.map +1 -0
  419. package/dist/tools/getAgentReputation.js +44 -0
  420. package/dist/tools/getAgentReputation.js.map +1 -0
  421. package/dist/tools/getAgentWallet.d.ts +21 -21
  422. package/dist/tools/getAgentWallet.d.ts.map +1 -1
  423. package/dist/tools/getAgentWallet.js +37 -27
  424. package/dist/tools/getAgentWallet.js.map +1 -1
  425. package/dist/tools/getAgentWalletBalance.d.ts +11 -11
  426. package/dist/tools/getAgentWalletBalance.d.ts.map +1 -1
  427. package/dist/tools/getAgentWalletBalance.js +102 -70
  428. package/dist/tools/getAgentWalletBalance.js.map +1 -1
  429. package/dist/tools/getExecutionHistory.d.ts +49 -49
  430. package/dist/tools/getExecutionHistory.d.ts.map +1 -1
  431. package/dist/tools/getExecutionHistory.js +148 -154
  432. package/dist/tools/getExecutionHistory.js.map +1 -1
  433. package/dist/tools/getGasPrice.d.ts +43 -43
  434. package/dist/tools/getGasPrice.d.ts.map +0 -0
  435. package/dist/tools/getGasPrice.js +59 -59
  436. package/dist/tools/getGasPrice.js.map +1 -1
  437. package/dist/tools/getPoolInfo.d.ts +75 -75
  438. package/dist/tools/getPoolInfo.d.ts.map +1 -1
  439. package/dist/tools/getPoolInfo.js +142 -137
  440. package/dist/tools/getPoolInfo.js.map +1 -1
  441. package/dist/tools/getSpvProof.d.ts +24 -0
  442. package/dist/tools/getSpvProof.d.ts.map +1 -0
  443. package/dist/tools/getSpvProof.js +64 -0
  444. package/dist/tools/getSpvProof.js.map +1 -0
  445. package/dist/tools/getTokenPrice.d.ts +56 -113
  446. package/dist/tools/getTokenPrice.d.ts.map +1 -1
  447. package/dist/tools/getTokenPrice.js +110 -117
  448. package/dist/tools/getTokenPrice.js.map +1 -1
  449. package/dist/tools/getTransactionStatus.d.ts +43 -43
  450. package/dist/tools/getTransactionStatus.d.ts.map +0 -0
  451. package/dist/tools/getTransactionStatus.js +59 -59
  452. package/dist/tools/getTransactionStatus.js.map +0 -0
  453. package/dist/tools/getWalletBalance.d.ts +103 -68
  454. package/dist/tools/getWalletBalance.d.ts.map +1 -1
  455. package/dist/tools/getWalletBalance.js +127 -87
  456. package/dist/tools/getWalletBalance.js.map +1 -1
  457. package/dist/tools/publishRiskScore.d.ts +85 -63
  458. package/dist/tools/publishRiskScore.d.ts.map +1 -1
  459. package/dist/tools/publishRiskScore.js +136 -88
  460. package/dist/tools/publishRiskScore.js.map +1 -1
  461. package/dist/tools/queryGoldsky.d.ts +20 -0
  462. package/dist/tools/queryGoldsky.d.ts.map +1 -0
  463. package/dist/tools/queryGoldsky.js +29 -0
  464. package/dist/tools/queryGoldsky.js.map +1 -0
  465. package/dist/tools/queryRiskRegistry.d.ts +38 -38
  466. package/dist/tools/queryRiskRegistry.d.ts.map +1 -1
  467. package/dist/tools/queryRiskRegistry.js +59 -55
  468. package/dist/tools/queryRiskRegistry.js.map +1 -1
  469. package/dist/tools/safehandsPreflightCheck.d.ts +92 -77
  470. package/dist/tools/safehandsPreflightCheck.d.ts.map +1 -1
  471. package/dist/tools/safehandsPreflightCheck.js +174 -47
  472. package/dist/tools/safehandsPreflightCheck.js.map +1 -1
  473. package/dist/tools/safehandsRiskReport.d.ts +96 -81
  474. package/dist/tools/safehandsRiskReport.d.ts.map +1 -1
  475. package/dist/tools/safehandsRiskReport.js +48 -28
  476. package/dist/tools/safehandsRiskReport.js.map +1 -1
  477. package/dist/tools/safehandsSafeExecute.d.ts +20 -20
  478. package/dist/tools/safehandsSafeExecute.d.ts.map +1 -1
  479. package/dist/tools/safehandsSafeExecute.js +164 -81
  480. package/dist/tools/safehandsSafeExecute.js.map +1 -1
  481. package/dist/tools/safehandsWalletHealth.d.ts +28 -14
  482. package/dist/tools/safehandsWalletHealth.d.ts.map +1 -1
  483. package/dist/tools/safehandsWalletHealth.js +145 -103
  484. package/dist/tools/safehandsWalletHealth.js.map +1 -1
  485. package/dist/tools/safehandsX402Preflight.d.ts +26 -26
  486. package/dist/tools/safehandsX402Preflight.d.ts.map +1 -1
  487. package/dist/tools/safehandsX402Preflight.js +86 -65
  488. package/dist/tools/safehandsX402Preflight.js.map +1 -1
  489. package/dist/tools/sendPayment.d.ts +63 -57
  490. package/dist/tools/sendPayment.d.ts.map +1 -1
  491. package/dist/tools/sendPayment.js +144 -117
  492. package/dist/tools/sendPayment.js.map +1 -1
  493. package/dist/tools/setAgentPolicy.d.ts +75 -0
  494. package/dist/tools/setAgentPolicy.d.ts.map +1 -0
  495. package/dist/tools/setAgentPolicy.js +80 -0
  496. package/dist/tools/setAgentPolicy.js.map +1 -0
  497. package/dist/tools/simulateTransaction.d.ts +64 -60
  498. package/dist/tools/simulateTransaction.d.ts.map +1 -1
  499. package/dist/tools/simulateTransaction.js +116 -83
  500. package/dist/tools/simulateTransaction.js.map +1 -1
  501. package/dist/tools/tokenRegistryStatus.d.ts +46 -26
  502. package/dist/tools/tokenRegistryStatus.d.ts.map +1 -1
  503. package/dist/tools/tokenRegistryStatus.js +126 -96
  504. package/dist/tools/tokenRegistryStatus.js.map +1 -1
  505. package/dist/tools/verifyRiskInclusion.d.ts +59 -0
  506. package/dist/tools/verifyRiskInclusion.d.ts.map +1 -0
  507. package/dist/tools/verifyRiskInclusion.js +40 -0
  508. package/dist/tools/verifyRiskInclusion.js.map +1 -0
  509. package/dist/tools/x402PayAndFetch.d.ts +87 -81
  510. package/dist/tools/x402PayAndFetch.d.ts.map +1 -1
  511. package/dist/tools/x402PayAndFetch.js +295 -152
  512. package/dist/tools/x402PayAndFetch.js.map +1 -1
  513. package/dist/worker.d.ts +3 -0
  514. package/dist/worker.d.ts.map +1 -0
  515. package/dist/worker.js +39 -0
  516. package/dist/worker.js.map +1 -0
  517. package/dist/x402Server.d.ts +1 -1
  518. package/dist/x402Server.d.ts.map +1 -1
  519. package/dist/x402Server.js +409 -252
  520. package/dist/x402Server.js.map +1 -1
  521. package/docs/REVIEWER_QUICKSTART.md +113 -0
  522. package/docs/SAFEHANDS_REVIEWER_DEMO_SCRIPT.md +228 -0
  523. package/package.json +130 -83
  524. package/contracts/RiskRegistry.json +0 -223
  525. package/contracts/RiskRegistry.sol +0 -58
  526. package/dist/lib/testDodoLive.d.ts +0 -2
  527. package/dist/lib/testDodoLive.d.ts.map +0 -1
  528. package/dist/lib/testDodoLive.js +0 -105
  529. package/dist/lib/testDodoLive.js.map +0 -1
  530. package/dist/lib/testLiveSafehands.d.ts +0 -2
  531. package/dist/lib/testLiveSafehands.d.ts.map +0 -1
  532. package/dist/lib/testLiveSafehands.js +0 -93
  533. package/dist/lib/testLiveSafehands.js.map +0 -1
  534. package/dist/lib/testRpc.d.ts +0 -2
  535. package/dist/lib/testRpc.d.ts.map +0 -1
  536. package/dist/lib/testRpc.js +0 -30
  537. package/dist/lib/testRpc.js.map +0 -1
  538. package/dist/lib/testRpcLive.d.ts +0 -2
  539. package/dist/lib/testRpcLive.d.ts.map +0 -1
  540. package/dist/lib/testRpcLive.js +0 -89
  541. package/dist/lib/testRpcLive.js.map +0 -1
  542. package/dist/lib/testTools.d.ts +0 -2
  543. package/dist/lib/testTools.d.ts.map +0 -1
  544. package/dist/lib/testTools.js +0 -398
  545. package/dist/lib/testTools.js.map +0 -1
  546. package/dist/lib/testX402Live.d.ts +0 -2
  547. package/dist/lib/testX402Live.d.ts.map +0 -1
  548. package/dist/lib/testX402Live.js +0 -160
  549. package/dist/lib/testX402Live.js.map +0 -1
  550. package/dist/scripts/checkDeploy.d.ts +0 -2
  551. package/dist/scripts/checkDeploy.d.ts.map +0 -1
  552. package/dist/scripts/checkDeploy.js +0 -25
  553. package/dist/scripts/checkDeploy.js.map +0 -1
  554. package/dist/scripts/deployRegistry.d.ts +0 -2
  555. package/dist/scripts/deployRegistry.d.ts.map +0 -1
  556. package/dist/scripts/deployRegistry.js +0 -101
  557. package/dist/scripts/deployRegistry.js.map +0 -1
  558. package/dist/scripts/testRegistry.d.ts +0 -2
  559. package/dist/scripts/testRegistry.d.ts.map +0 -1
  560. package/dist/scripts/testRegistry.js +0 -44
  561. package/dist/scripts/testRegistry.js.map +0 -1
  562. package/examples/dashboard/index.html +0 -337
  563. package/examples/pharos-skill-engine/SKILL.safehands.md +0 -85
  564. package/examples/pharos-skill-engine/assets/safehands/example-actions.json +0 -49
  565. package/examples/pharos-skill-engine/assets/safehands/policy-defaults.json +0 -11
  566. package/examples/pharos-skill-engine/references/safehands.md +0 -345
  567. package/examples/scenario-hack.ts +0 -38
  568. package/skill/SKILL.md +0 -133
  569. package/skill/assets/safehands/example-actions.json +0 -49
  570. package/skill/assets/safehands/policy-defaults.json +0 -11
  571. package/skill/references/safehands.md +0 -345
@@ -1,213 +1,374 @@
1
- // ─── SafeHands Action Policy Engine ───────────────────────────────────
2
- // Reusable transaction safety firewall for AI agent actions on Pharos.
3
- // It is intentionally deterministic and testnet-only.
4
- // ───────────────────────────────────────────────────────────────────────
5
- import { isAddress, parseUnits } from "viem";
6
- import { CHAIN_ID, PHAROS_ENVIRONMENT, IS_MAINNET, MAX_APPROVAL_AMOUNT_USDC, MAX_TX_AMOUNT_PHRS, MAX_X402_PAYMENT_USDC, USDC_ADDRESS, TEST_USDC_ADDRESS, } from "../constants.js";
7
- function numeric(value) {
8
- if (!value)
9
- return null;
10
- const n = Number(value);
11
- return Number.isFinite(n) ? n : null;
12
- }
13
- function pushCheck(checks, name, status, message, reasons, requiredActions, reason, action) {
14
- checks.push({ name, status, message });
15
- if (status === "fail" && reasons && reason)
16
- reasons.push(reason);
17
- if ((status === "fail" || status === "warn") && requiredActions && action)
18
- requiredActions.push(action);
19
- }
20
- export function isUnlimitedApprovalAmount(value) {
21
- if (!value)
22
- return false;
23
- const normalized = value.trim().toLowerCase();
24
- if (["max", "unlimited", "infinite", "uint256_max"].includes(normalized))
25
- return true;
26
- try {
27
- return BigInt(normalized) >= 2n ** 255n;
28
- }
29
- catch {
30
- return false;
31
- }
32
- }
33
- function isSuspiciousUrl(rawUrl) {
34
- if (!rawUrl)
35
- return false;
36
- if (process.env.ALLOW_LOCAL_X402_FETCH === "true")
37
- return false;
38
- try {
39
- const parsed = new URL(rawUrl);
40
- const host = parsed.hostname.toLowerCase();
41
- if (!["http:", "https:"].includes(parsed.protocol))
42
- return true;
43
- if (host === "localhost" || host.endsWith(".localhost"))
44
- return true;
45
- if (host === "127.0.0.1" || host.startsWith("127.") || host === "0.0.0.0")
46
- return true;
47
- if (host.startsWith("10.") || host.startsWith("192.168."))
48
- return true;
49
- if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host))
50
- return true;
51
- if (host === "[::1]" || host === "::1")
52
- return true;
53
- return false;
54
- }
55
- catch {
56
- return true;
57
- }
58
- }
59
- function classifyRisk(checks) {
60
- const fails = checks.filter((c) => c.status === "fail").length;
61
- const warns = checks.filter((c) => c.status === "warn").length;
62
- const unknowns = checks.filter((c) => c.status === "unknown").length;
63
- if (fails >= 3)
64
- return "CRITICAL";
65
- if (fails > 0)
66
- return "HIGH";
67
- if (warns >= 2)
68
- return "MEDIUM";
69
- if (warns > 0 || unknowns > 0)
70
- return "MEDIUM";
71
- return "LOW";
72
- }
73
- function defaultDecision(riskLevel, checks) {
74
- if (checks.some((c) => c.status === "fail"))
75
- return "BLOCK";
76
- if (riskLevel === "MEDIUM")
77
- return "REQUIRE_CONFIRMATION";
78
- if (riskLevel === "UNKNOWN")
79
- return "REQUIRE_TOKEN_REVIEW";
80
- return "ALLOW";
81
- }
82
- export function evaluateActionPolicy(input) {
83
- const environment = input.environment || PHAROS_ENVIRONMENT;
84
- const chainId = input.chainId ?? CHAIN_ID;
85
- const isMainnet = input.isMainnet ?? IS_MAINNET;
86
- const checks = [];
87
- const reasons = [];
88
- const requiredActions = [];
89
- if (isMainnet) {
90
- pushCheck(checks, "mainnet_guard", "fail", "Mainnet actions are blocked by SafeHands.", reasons, requiredActions, "Mainnet actions are not supported.", "Switch to Pharos Atlantic Testnet.");
91
- }
92
- else {
93
- pushCheck(checks, "mainnet_guard", "pass", "Action is not targeting mainnet.");
94
- }
95
- if (chainId !== CHAIN_ID) {
96
- pushCheck(checks, "chain_id", "fail", `Expected chain ID ${CHAIN_ID}, received ${chainId}.`, reasons, requiredActions, "Chain ID mismatch.", "Switch wallet/RPC to Pharos Atlantic Testnet.");
97
- }
98
- else {
99
- pushCheck(checks, "chain_id", "pass", `Chain ID is Pharos Atlantic Testnet (${CHAIN_ID}).`);
100
- }
101
- if (environment !== PHAROS_ENVIRONMENT) {
102
- pushCheck(checks, "environment", "warn", `Expected ${PHAROS_ENVIRONMENT}, received ${environment}.`, reasons, requiredActions, undefined, "Verify the runtime environment before execution.");
103
- }
104
- else {
105
- pushCheck(checks, "environment", "pass", `Environment is ${PHAROS_ENVIRONMENT}.`);
106
- }
107
- if (input.requiresSigner && !input.signerAvailable) {
108
- pushCheck(checks, "signer", "fail", "No signer is available for this write/payment action.", reasons, requiredActions, "No signer available.", "Configure WALLET_MODE=managed-testnet, X402_SIGNER_PRIVATE_KEY, or PRIVATE_KEY for testnet only.");
109
- }
110
- if (input.actionType === "send_payment") {
111
- const amount = numeric(input.amount);
112
- if (amount !== null && amount > Number(MAX_TX_AMOUNT_PHRS)) {
113
- pushCheck(checks, "payment_limit", "fail", `Payment ${amount} PHRS exceeds limit ${MAX_TX_AMOUNT_PHRS} PHRS.`, reasons, requiredActions, "Payment exceeds configured PHRS limit.", "Reduce amount or increase MAX_TX_AMOUNT_PHRS consciously for testnet.");
114
- }
115
- else {
116
- pushCheck(checks, "payment_limit", "pass", `Payment is within ${MAX_TX_AMOUNT_PHRS} PHRS limit.`);
117
- }
118
- if (input.recipient && !isAddress(input.recipient)) {
119
- pushCheck(checks, "recipient_address", "fail", "Recipient address is invalid.", reasons, requiredActions, "Invalid recipient address.", "Provide a valid EVM address.");
120
- }
121
- else if (input.recipientVerified === false) {
122
- pushCheck(checks, "recipient_reputation", "warn", "Recipient is unverified.", reasons, requiredActions, undefined, "Verify recipient before sending funds.");
123
- }
124
- }
125
- if (input.actionType === "approve_token") {
126
- const unlimited = input.approvalUnlimited || isUnlimitedApprovalAmount(input.approvalAmount) || isUnlimitedApprovalAmount(input.amount);
127
- if (unlimited && input.allowUnlimitedApproval !== true) {
128
- pushCheck(checks, "approval_amount", "fail", "Unlimited approval is blocked by default.", reasons, requiredActions, "Unlimited approval requested.", "Use a limited approval amount.");
129
- }
130
- else {
131
- const approvalAmount = numeric(input.approvalAmount) ?? numeric(input.amount);
132
- if (approvalAmount !== null && approvalAmount > Number(MAX_APPROVAL_AMOUNT_USDC)) {
133
- pushCheck(checks, "approval_limit", "fail", `Approval ${approvalAmount} exceeds limit ${MAX_APPROVAL_AMOUNT_USDC}.`, reasons, requiredActions, "Approval exceeds configured limit.", "Reduce approval or increase MAX_APPROVAL_AMOUNT_USDC consciously for testnet.");
134
- }
135
- else {
136
- pushCheck(checks, "approval_limit", "pass", `Approval is within ${MAX_APPROVAL_AMOUNT_USDC} USDC-equivalent limit.`);
137
- }
138
- }
139
- if (input.spender && !isAddress(input.spender)) {
140
- pushCheck(checks, "spender_address", "fail", "Spender address is invalid.", reasons, requiredActions, "Invalid spender address.", "Provide a valid spender address.");
141
- }
142
- else if (input.spenderVerified === false) {
143
- pushCheck(checks, "spender_reputation", "warn", "Spender is unverified.", reasons, requiredActions, undefined, "Verify spender contract before approving.");
144
- }
145
- }
146
- if (input.actionType === "execute_swap") {
147
- const amount = numeric(input.amount);
148
- if (amount !== null && amount > Number(MAX_TX_AMOUNT_PHRS) && (input.tokenIn || "").toUpperCase() === "PHRS") {
149
- pushCheck(checks, "swap_amount_limit", "fail", `Swap ${amount} PHRS exceeds limit ${MAX_TX_AMOUNT_PHRS} PHRS.`, reasons, requiredActions, "Swap exceeds configured PHRS limit.", "Reduce amount or increase MAX_TX_AMOUNT_PHRS consciously for testnet.");
150
- }
151
- }
152
- if (input.actionType === "x402_pay_and_fetch") {
153
- if (isSuspiciousUrl(input.url)) {
154
- pushCheck(checks, "x402_url", "fail", "x402 URL is SSRF-sensitive or invalid.", reasons, requiredActions, "SSRF-sensitive x402 URL blocked.", "Use a public HTTPS/HTTP endpoint or set ALLOW_LOCAL_X402_FETCH only for local tests.");
155
- }
156
- else {
157
- pushCheck(checks, "x402_url", "pass", "x402 URL passed static SSRF checks.");
158
- }
159
- const payment = numeric(input.paymentAmountUsdc);
160
- if (payment !== null && payment > Number(MAX_X402_PAYMENT_USDC)) {
161
- pushCheck(checks, "x402_payment_limit", "fail", `x402 payment ${payment} USDC exceeds limit ${MAX_X402_PAYMENT_USDC} USDC.`, reasons, requiredActions, "x402 payment exceeds configured limit.", "Reduce payment amount or increase MAX_X402_PAYMENT_USDC consciously for testnet.");
162
- }
163
- else {
164
- pushCheck(checks, "x402_payment_limit", "pass", `x402 payment is within ${MAX_X402_PAYMENT_USDC} USDC limit.`);
165
- }
166
- if (input.paymentTokenAddress && input.paymentTokenAddress.toLowerCase() !== USDC_ADDRESS.toLowerCase()) {
167
- const status = input.paymentTokenAddress.toLowerCase() === TEST_USDC_ADDRESS.toLowerCase() ? "warn" : "fail";
168
- pushCheck(checks, "x402_payment_token", status, `Payment token ${input.paymentTokenAddress} is not canonical Circle USDC ${USDC_ADDRESS}.`, reasons, requiredActions, status === "fail" ? "x402 payment token is not canonical USDC." : undefined, "Use docs-verified Pharos testnet USDC or label the token as project-configured.");
169
- }
170
- }
171
- const tokenStatus = input.tokenRegistryStatus;
172
- if (tokenStatus === "CUSTOM_NON_REGISTRY" || tokenStatus === "UNKNOWN") {
173
- pushCheck(checks, "token_registry", "warn", `Token registry status is ${tokenStatus}.`, reasons, requiredActions, undefined, "Review token contract before execution.");
174
- }
175
- if (input.tokenSecurityStatus === "unavailable" || input.tokenSecurityStatus === "unknown") {
176
- pushCheck(checks, "token_security_provider", "warn", "Token security provider is unavailable or unknown.", reasons, requiredActions, undefined, "Proceed only after manual token review.");
177
- }
178
- const riskLevel = classifyRisk(checks);
179
- let decision = defaultDecision(riskLevel, checks);
180
- if (checks.some((c) => c.name.includes("funding") && c.status === "fail"))
181
- decision = "REQUIRE_FUNDING";
182
- if (checks.some((c) => c.name.includes("token") && c.status === "unknown"))
183
- decision = "REQUIRE_TOKEN_REVIEW";
184
- return {
185
- decision,
186
- riskLevel,
187
- safeToExecute: decision === "ALLOW",
188
- reasons: [...new Set(reasons)],
189
- requiredActions: [...new Set(requiredActions)],
190
- checks,
191
- environment,
192
- chainId,
193
- isMainnet,
194
- };
195
- }
196
- export function explainPolicyResult(result) {
197
- if (result.decision === "ALLOW") {
198
- return "This action was allowed because it targets Pharos Atlantic Testnet, passed policy checks, and stayed within configured safety limits.";
199
- }
200
- const reasonText = result.reasons.length > 0 ? result.reasons.join(" ") : "one or more safety checks failed or require review.";
201
- const actionText = result.requiredActions.length > 0 ? ` SafeHands recommends: ${result.requiredActions.join(" ")}` : "";
202
- const verb = result.decision === "BLOCK" ? "blocked" : result.decision.toLowerCase().replaceAll("_", " ");
203
- return `This action was ${verb} because ${reasonText}${actionText}`;
204
- }
205
- export function parseTokenAmountToUnits(amount, decimals = 6) {
206
- try {
207
- return parseUnits(amount, decimals);
208
- }
209
- catch {
210
- return null;
211
- }
212
- }
1
+ // ─── SafeHands Action Policy Engine ───────────────────────────────────
2
+ // Reusable transaction safety firewall for AI agent actions on Pharos.
3
+ // It is intentionally deterministic. Mainnet-first (Pharos Pacific, chain 1672).
4
+ // ───────────────────────────────────────────────────────────────────────
5
+ import { isAddress, parseUnits } from "viem";
6
+ import { isDenylistedRecipient } from "../recipientSafety.js";
7
+ import { CHAIN_ID, PHAROS_ENVIRONMENT, COUNTERPARTY_CRITICAL_THRESHOLD, RISK_BLOCK_THRESHOLD, activeX402AllowedTokenAddresses, PACIFIC_USDC_ADDRESS, PACIFIC_WPROS_ADDRESS, USDC_ADDRESS, WPROS_ADDRESS, isCanonicalInfrastructure, } from "../constants.js";
8
+ import { getNetworkByChainId } from "../networks.js";
9
+ import { resolvePriceableToken } from "../spendAccumulator.js";
10
+ import { loadAgentPolicy, resolveEffectiveLimits } from "./agentPolicy.js";
11
+ /** Projects a RiskAssessment into the policy-evidence shape. Single mapper write tools must use this rather than hand-rolling the projection. */
12
+ export function riskEvidenceFromAssessment(a) {
13
+ return {
14
+ score: a.riskScore,
15
+ degraded: a.degraded,
16
+ degradedReasons: a.degradedReasons,
17
+ counterpartyRisk: a.breakdown.counterpartyRisk,
18
+ swapProviderNotConfigured: a.swapProviderNotConfigured,
19
+ };
20
+ }
21
+ function numeric(value) {
22
+ if (!value)
23
+ return null;
24
+ const n = Number(value);
25
+ return Number.isFinite(n) ? n : null;
26
+ }
27
+ function pushCheck(checks, name, status, message, reasons, requiredActions, reason, action) {
28
+ checks.push({ name, status, message });
29
+ if (status === "fail" && reasons && reason)
30
+ reasons.push(reason);
31
+ if ((status === "fail" || status === "warn") && requiredActions && action)
32
+ requiredActions.push(action);
33
+ }
34
+ export function isUnlimitedApprovalAmount(value) {
35
+ if (!value)
36
+ return false;
37
+ const normalized = value.trim().toLowerCase();
38
+ if (["max", "unlimited", "infinite", "uint256_max"].includes(normalized))
39
+ return true;
40
+ try {
41
+ return BigInt(normalized) >= 2n ** 255n;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function isSuspiciousUrl(rawUrl) {
48
+ if (!rawUrl)
49
+ return false;
50
+ let parsed;
51
+ try {
52
+ parsed = new URL(rawUrl);
53
+ }
54
+ catch {
55
+ return true;
56
+ }
57
+ const host = parsed.hostname.toLowerCase();
58
+ if (!["http:", "https:"].includes(parsed.protocol))
59
+ return true;
60
+ // ALLOW_LOCAL_X402_FETCH relaxes ONLY loopback, and only outside production.
61
+ // Cloud-metadata / private / link-local ranges are ALWAYS suspicious — the flag
62
+ // can no longer wave through 169.254.169.254 or RFC1918 hosts.
63
+ const isLoopback = host === "localhost" || host.endsWith(".localhost") ||
64
+ host === "127.0.0.1" || host.startsWith("127.") ||
65
+ host === "[::1]" || host === "::1";
66
+ const loopbackBypass = process.env.ALLOW_LOCAL_X402_FETCH === "true" &&
67
+ (process.env.NODE_ENV || "").toLowerCase() !== "production";
68
+ if (isLoopback)
69
+ return loopbackBypass ? false : true;
70
+ if (host === "0.0.0.0")
71
+ return true;
72
+ if (/^0\./.test(host))
73
+ return true;
74
+ if (host.startsWith("10.") || host.startsWith("192.168."))
75
+ return true;
76
+ if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(host))
77
+ return true;
78
+ if (host.startsWith("169.254."))
79
+ return true;
80
+ if (host.startsWith("[fc") || host.startsWith("[fd") || host.startsWith("[fe80:"))
81
+ return true;
82
+ if (host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80:"))
83
+ return true;
84
+ return false;
85
+ }
86
+ function classifyRisk(checks) {
87
+ const fails = checks.filter((c) => c.status === "fail").length;
88
+ const warns = checks.filter((c) => c.status === "warn").length;
89
+ const unknowns = checks.filter((c) => c.status === "unknown").length;
90
+ if (fails >= 3)
91
+ return "CRITICAL";
92
+ if (fails > 0)
93
+ return "HIGH";
94
+ if (warns >= 2)
95
+ return "MEDIUM";
96
+ if (warns > 0 || unknowns > 0)
97
+ return "MEDIUM";
98
+ return "LOW";
99
+ }
100
+ function defaultDecision(riskLevel, checks) {
101
+ if (checks.some((c) => c.status === "fail"))
102
+ return "BLOCK";
103
+ if (riskLevel === "LOW")
104
+ return "ALLOW";
105
+ // classifyRisk only produces MEDIUM here (HIGH/CRITICAL imply a fail, caught
106
+ // above). Any other level, including a future UNKNOWN, lands here fail-closed
107
+ // rather than falling through to ALLOW.
108
+ return "REQUIRE_CONFIRMATION";
109
+ }
110
+ export function evaluateActionPolicy(input) {
111
+ const environment = input.environment || PHAROS_ENVIRONMENT;
112
+ const chainId = input.chainId ?? CHAIN_ID;
113
+ const network = getNetworkByChainId(chainId);
114
+ const isMainnet = input.isMainnet ?? network?.isMainnet ?? false;
115
+ const checks = [];
116
+ const reasons = [];
117
+ const requiredActions = [];
118
+ const policy = input.agentPolicy ?? loadAgentPolicy(input.agentId);
119
+ const effectiveLimits = resolveEffectiveLimits(policy);
120
+ // Chain guard: only supported Pharos networks pass. Unknown chains (e.g.
121
+ // Ethereum mainnet, chain 1) are blocked. Pharos Pacific Mainnet (1672) is a
122
+ // supported network for read-only SafeHands checks.
123
+ if (!network) {
124
+ pushCheck(checks, "chain_id", "fail", `Chain ID ${chainId} is not a supported Pharos network.`, reasons, requiredActions, "Unsupported chain.", "Use Pharos Pacific Mainnet (1672) or Atlantic Testnet (688689).");
125
+ }
126
+ else {
127
+ pushCheck(checks, "chain_id", "pass", `Chain ID is ${network.label} (${chainId}).`);
128
+ }
129
+ // Mainnet read-only SafeHands checks are allowed. Mainnet execution / write /
130
+ // payment is disabled by default and gated separately (WRITE_TOOLS_ENABLED and
131
+ // the network executionAllowed flag) it is never live here.
132
+ if (isMainnet) {
133
+ pushCheck(checks, "mainnet_guard", "pass", `${network ? network.label : "Pharos mainnet"}: read-only SafeHands checks allowed; execution/write/payment is gated and disabled by default.`);
134
+ }
135
+ else {
136
+ pushCheck(checks, "mainnet_guard", "pass", "Action is not targeting mainnet.");
137
+ }
138
+ if (environment !== PHAROS_ENVIRONMENT) {
139
+ pushCheck(checks, "environment", "warn", `Expected ${PHAROS_ENVIRONMENT}, received ${environment}.`, reasons, requiredActions, undefined, "Verify the runtime environment before execution.");
140
+ }
141
+ else {
142
+ pushCheck(checks, "environment", "pass", `Environment is ${PHAROS_ENVIRONMENT}.`);
143
+ }
144
+ if (input.requiresSigner && input.signerAvailable === false) {
145
+ pushCheck(checks, "signer", "fail", "No signer is available for this write/payment action.", reasons, requiredActions, "No signer available.", "Configure WALLET_MODE=managed-mainnet, X402_SIGNER_PRIVATE_KEY, or PRIVATE_KEY for mainnet only.");
146
+ }
147
+ else if (input.requiresSigner && input.signerAvailable === undefined) {
148
+ pushCheck(checks, "signer_unknown", "warn", "Signer availability is unknown; a signer will be required for execution.", reasons, requiredActions, undefined, "Ensure a signer is available before executing this write/payment action.");
149
+ }
150
+ if (input.actionType === "send_payment") {
151
+ const amount = numeric(input.amount);
152
+ const paymentLimit = effectiveLimits.maxPaymentPROS;
153
+ if (amount !== null && amount > paymentLimit) {
154
+ pushCheck(checks, "payment_limit", "fail", `Payment ${amount} PROS exceeds limit ${paymentLimit} PROS (policy: ${policy.profile}).`, reasons, requiredActions, "Payment exceeds configured PROS limit.", "Reduce amount or adjust agent policy.");
155
+ }
156
+ else {
157
+ pushCheck(checks, "payment_limit", "pass", `Payment is within ${paymentLimit} PROS limit (policy: ${policy.profile}).`);
158
+ }
159
+ const balance = numeric(input.walletBalancePhs);
160
+ if (amount !== null && balance !== null) {
161
+ if (amount > balance) {
162
+ pushCheck(checks, "funding_balance", "fail", `Insufficient PROS balance. Have ${balance}, need ${amount}.`, reasons, requiredActions, "Insufficient PROS balance.", "Fund the wallet before executing.");
163
+ }
164
+ else {
165
+ pushCheck(checks, "funding_balance", "pass", "Sufficient PROS balance for payment.");
166
+ }
167
+ }
168
+ if (input.recipient && !isAddress(input.recipient)) {
169
+ pushCheck(checks, "recipient_address", "fail", "Recipient address is invalid.", reasons, requiredActions, "Invalid recipient address.", "Provide a valid EVM address.");
170
+ }
171
+ else if (isDenylistedRecipient(input.recipient)) {
172
+ pushCheck(checks, "recipient_denylist", "fail", "Recipient is on the operator denylist (known-bad address).", reasons, requiredActions, "Recipient address is denylisted.", "Do not send to this address; it is flagged as known-bad by the operator.");
173
+ }
174
+ else if (input.recipientVerified === false) {
175
+ pushCheck(checks, "recipient_reputation", "warn", "Recipient is unverified.", reasons, requiredActions, undefined, "Verify recipient before sending funds.");
176
+ }
177
+ else if (input.recipient && input.recipientVerified === undefined) {
178
+ // Fail-closed: recipientVerified is a caller CLAIM. When it is omitted, no
179
+ // verification happened treat exactly like an unverified recipient instead
180
+ // of silently skipping the reputation check (truth-model rule 5).
181
+ pushCheck(checks, "recipient_reputation", "unknown", "Recipient verification was not asserted; the recipient is treated as an unverified claim.", reasons, requiredActions, undefined, "Verify the recipient against chain/registry evidence before sending funds.");
182
+ }
183
+ else if (input.recipient && input.recipientVerified === true) {
184
+ pushCheck(checks, "recipient_reputation", "pass", "Recipient verification asserted by the caller (caller-attested claim; not independently verified by SafeHands).");
185
+ }
186
+ }
187
+ if (input.actionType === "approve_token") {
188
+ const unlimited = input.approvalUnlimited || isUnlimitedApprovalAmount(input.approvalAmount) || isUnlimitedApprovalAmount(input.amount);
189
+ if (unlimited && input.allowUnlimitedApproval !== true) {
190
+ pushCheck(checks, "approval_amount", "fail", "Unlimited approval is blocked by default.", reasons, requiredActions, "Unlimited approval requested.", "Use a limited approval amount.");
191
+ }
192
+ else {
193
+ const approvalAmount = numeric(input.approvalAmount) ?? numeric(input.amount);
194
+ const approvalLimit = effectiveLimits.maxApprovalUSDC;
195
+ if (approvalAmount !== null && approvalAmount > approvalLimit) {
196
+ pushCheck(checks, "approval_limit", "fail", `Approval ${approvalAmount} exceeds limit ${approvalLimit} (policy: ${policy.profile}).`, reasons, requiredActions, "Approval exceeds configured limit.", "Reduce approval or adjust agent policy.");
197
+ }
198
+ else {
199
+ pushCheck(checks, "approval_limit", "pass", `Approval is within ${approvalLimit} USDC-equivalent limit (policy: ${policy.profile}).`);
200
+ }
201
+ }
202
+ if (input.spender && !isAddress(input.spender)) {
203
+ pushCheck(checks, "spender_address", "fail", "Spender address is invalid.", reasons, requiredActions, "Invalid spender address.", "Provide a valid spender address.");
204
+ }
205
+ else if (input.spenderVerified === false) {
206
+ pushCheck(checks, "spender_reputation", "warn", "Spender is unverified.", reasons, requiredActions, undefined, "Verify spender contract before approving.");
207
+ }
208
+ else if (input.spender && input.spenderVerified === undefined) {
209
+ // Fail-closed: same rule as recipientVerified — an omitted claim is not evidence.
210
+ pushCheck(checks, "spender_reputation", "unknown", "Spender verification was not asserted; the spender is treated as an unverified claim.", reasons, requiredActions, undefined, "Verify the spender contract against chain/registry evidence before approving.");
211
+ }
212
+ else if (input.spender && input.spenderVerified === true) {
213
+ pushCheck(checks, "spender_reputation", "pass", "Spender verification asserted by the caller (caller-attested claim; not independently verified by SafeHands).");
214
+ }
215
+ }
216
+ if (input.actionType === "execute_swap") {
217
+ const amount = numeric(input.amount);
218
+ const swapLimit = effectiveLimits.maxSwapPROS;
219
+ if (amount !== null && amount > swapLimit && (input.tokenIn || "").toUpperCase() === "PROS") {
220
+ pushCheck(checks, "swap_amount_limit", "fail", `Swap ${amount} PROS exceeds limit ${swapLimit} PROS (policy: ${policy.profile}).`, reasons, requiredActions, "Swap exceeds configured PROS limit.", "Reduce amount or adjust agent policy.");
221
+ }
222
+ // An input token that cannot be priced in USD contributes $0 to
223
+ // MAX_DAILY_SPEND_USD and has no per-transaction ceiling — the spend caps would
224
+ // silently not apply. Deny by default (fail → BLOCK, never confirmable).
225
+ if (input.tokenIn && resolvePriceableToken(input.tokenIn) === null) {
226
+ pushCheck(checks, "swap_notional_unpriceable", "fail", `Swap input token ${input.tokenIn} cannot be priced in USD, so the daily USD cap and per-transaction ceiling cannot bound this spend; denied by default.`, reasons, requiredActions, "Unpriceable input token would escape spend caps.", "Swap a priceable token (PROS, WPROS, USDC, USDT) or add the token to the price map before enabling it.");
227
+ }
228
+ const balance = numeric(input.walletBalancePhs);
229
+ if (amount !== null && balance !== null && (input.tokenIn || "").toUpperCase() === "PROS") {
230
+ if (amount > balance) {
231
+ pushCheck(checks, "funding_balance", "fail", `Insufficient PROS balance. Have ${balance}, need ${amount}.`, reasons, requiredActions, "Insufficient PROS balance.", "Fund the wallet before executing.");
232
+ }
233
+ else {
234
+ pushCheck(checks, "funding_balance", "pass", "Sufficient PROS balance for swap.");
235
+ }
236
+ }
237
+ }
238
+ if (input.actionType === "custom_contract_call" && input.targetContract) {
239
+ if (isCanonicalInfrastructure(input.targetContract, isMainnet)) {
240
+ pushCheck(checks, "canonical_contract", "pass", `Contract ${input.targetContract} is recognized as official Pharos canonical infrastructure.`);
241
+ }
242
+ else {
243
+ pushCheck(checks, "unknown_contract", "warn", `Contract ${input.targetContract} is not recognized as canonical Pharos infrastructure.`, reasons, requiredActions, undefined, "Review the contract interaction carefully before confirming.");
244
+ }
245
+ }
246
+ if (input.actionType === "x402_pay_and_fetch") {
247
+ if (isSuspiciousUrl(input.url)) {
248
+ pushCheck(checks, "x402_url", "fail", "x402 URL is SSRF-sensitive or invalid.", reasons, requiredActions, "SSRF-sensitive x402 URL blocked.", "Use a public HTTPS/HTTP endpoint or set ALLOW_LOCAL_X402_FETCH only for local tests.");
249
+ }
250
+ else {
251
+ pushCheck(checks, "x402_url", "pass", "x402 URL passed static SSRF checks.");
252
+ }
253
+ const payment = numeric(input.paymentAmountUsdc);
254
+ const x402Limit = effectiveLimits.maxX402PaymentUSDC;
255
+ if (payment !== null && payment > x402Limit) {
256
+ pushCheck(checks, "x402_payment_limit", "fail", `x402 payment ${payment} USDC exceeds limit ${x402Limit} USDC (policy: ${policy.profile}).`, reasons, requiredActions, "x402 payment exceeds configured limit.", "Reduce payment amount or adjust agent policy.");
257
+ }
258
+ else {
259
+ pushCheck(checks, "x402_payment_limit", "pass", `x402 payment is within ${x402Limit} USDC limit (policy: ${policy.profile}).`);
260
+ }
261
+ const balance = numeric(input.walletBalancePhs);
262
+ if (payment !== null && balance !== null && (input.paymentTokenAddress || "").toLowerCase() === PACIFIC_USDC_ADDRESS.toLowerCase()) {
263
+ if (payment > balance) {
264
+ pushCheck(checks, "funding_balance_x402", "fail", `Insufficient USDC balance for x402 payment. Have ${balance}, need ${payment}.`, reasons, requiredActions, "Insufficient USDC balance.", "Fund the wallet with USDC.");
265
+ }
266
+ else {
267
+ pushCheck(checks, "funding_balance_x402", "pass", "Sufficient USDC balance for x402 payment.");
268
+ }
269
+ }
270
+ const allowedX402Tokens = activeX402AllowedTokenAddresses().map((a) => a.toLowerCase());
271
+ if (input.paymentTokenAddress && !allowedX402Tokens.includes(input.paymentTokenAddress.toLowerCase())) {
272
+ const mainnetMsg = `supported x402 payment tokens are Pacific USDC (${PACIFIC_USDC_ADDRESS}) or Pacific WPROS (${PACIFIC_WPROS_ADDRESS})`;
273
+ const testnetMsg = `supported x402 payment tokens are USDC (${USDC_ADDRESS}) or WPROS (${WPROS_ADDRESS})`;
274
+ pushCheck(checks, "x402_payment_token", "fail", `Payment token ${input.paymentTokenAddress} is not a supported x402 payment token; ${isMainnet ? mainnetMsg : testnetMsg}.`, reasons, requiredActions, "x402 payment token is not supported for this network.", isMainnet ? "Use Pacific USDC or Pacific WPROS for x402 payments." : "Use the active network USDC or WPROS token for x402 payments.");
275
+ }
276
+ else if (input.paymentTokenAddress) {
277
+ pushCheck(checks, "x402_payment_token", "pass", `Payment token ${input.paymentTokenAddress} is supported for x402 on ${isMainnet ? "Pharos Pacific Mainnet" : "the active Pharos network"}.`);
278
+ }
279
+ }
280
+ const tokenStatus = input.tokenRegistryStatus;
281
+ if (tokenStatus === "CUSTOM_NON_REGISTRY" || tokenStatus === "UNKNOWN") {
282
+ pushCheck(checks, "token_registry", "warn", `Token registry status is ${tokenStatus}.`, reasons, requiredActions, undefined, "Review token contract before execution.");
283
+ }
284
+ if (input.tokenSecurityStatus === "unavailable") {
285
+ // Intel is MISSING (provider outage / token not indexed) — nothing was
286
+ // reviewed. Distinct check name so the write-execution gate can fail closed on it
287
+ // (a caller confirmation cannot substitute for intel that never existed).
288
+ pushCheck(checks, "token_security_intel_missing", "unknown", "Token security intelligence is unavailable (provider outage or token not indexed); the token was NOT reviewed.", reasons, requiredActions, undefined, "Retry when the token-security provider is reachable, or verify the token independently before executing.");
289
+ }
290
+ else if (input.tokenSecurityStatus === "unknown") {
291
+ pushCheck(checks, "token_security_provider", "unknown", "Token security provider is unavailable or unknown.", reasons, requiredActions, undefined, "Proceed only after manual token review.");
292
+ }
293
+ // ─── Advisory risk-engine evidence (write paths) ─────────────────────
294
+ // The risk engine never gates execution itself: its output arrives here as
295
+ // evidence and these checks are where the score/degradation actually decide.
296
+ if (input.risk) {
297
+ const risk = input.risk;
298
+ if (risk.score > RISK_BLOCK_THRESHOLD) {
299
+ pushCheck(checks, "risk_score", "fail", `Weighted risk score ${risk.score}/100 exceeds the block threshold (${RISK_BLOCK_THRESHOLD}).`, reasons, requiredActions, `Risk score ${risk.score}/100 exceeds the block threshold (${RISK_BLOCK_THRESHOLD}).`, "Review the risk reasons and adjust the transaction parameters significantly before retrying.");
300
+ }
301
+ else {
302
+ pushCheck(checks, "risk_score", "pass", `Weighted risk score ${risk.score}/100 is within the block threshold (${RISK_BLOCK_THRESHOLD}).`);
303
+ }
304
+ if (typeof risk.counterpartyRisk === "number" && risk.counterpartyRisk >= COUNTERPARTY_CRITICAL_THRESHOLD) {
305
+ pushCheck(checks, "risk_counterparty", "fail", `Counterparty risk ${risk.counterpartyRisk}/100 is critical (≥ ${COUNTERPARTY_CRITICAL_THRESHOLD}): missing, invalid, or zero-address counterparty.`, reasons, requiredActions, "Critically risky counterparty (missing/invalid/zero recipient).", "Provide a valid, verified counterparty address.");
306
+ }
307
+ else if (typeof risk.counterpartyRisk === "number") {
308
+ pushCheck(checks, "risk_counterparty", "pass", `Counterparty risk ${risk.counterpartyRisk}/100 is below the critical threshold (${COUNTERPARTY_CRITICAL_THRESHOLD}).`);
309
+ }
310
+ if (risk.degraded) {
311
+ let detail = (risk.degradedReasons ?? []).join(" ") || "A critical risk signal could not be evaluated.";
312
+ if (risk.swapProviderNotConfigured && !detail.includes("SWAP_LIQUIDITY_NOT_CONFIGURED")) {
313
+ detail += " The swap liquidity provider is not configured for this chain (SWAP_LIQUIDITY_NOT_CONFIGURED), a permanent configuration state, not a transient outage.";
314
+ }
315
+ pushCheck(checks, "risk_degraded", "unknown", `Risk assessment is degraded: ${detail}`, reasons, requiredActions, undefined, "Re-invoke with confirm=true only after manually reviewing the degraded risk data.");
316
+ // Unknown-status checks do not populate `reasons`; surface the degradation
317
+ // detail there anyway so the CONFIRMATION_REQUIRED envelope explains itself
318
+ // (parity with the former tool-level degraded message).
319
+ reasons.push(`Risk assessment is degraded: ${detail}`);
320
+ }
321
+ else {
322
+ pushCheck(checks, "risk_degraded", "pass", "No critical risk signal was missing during assessment.");
323
+ }
324
+ }
325
+ const riskLevel = classifyRisk(checks);
326
+ let decision = defaultDecision(riskLevel, checks);
327
+ // Soft, recoverable signals must NEVER downgrade a hard BLOCK. Only remap when
328
+ // the block is *solely* a funding shortfall (fundable) or when no hard fail
329
+ // exists at all (token-security unresolved → review before proceeding).
330
+ const hardFails = checks.filter((c) => c.status === "fail");
331
+ const onlyFundingFails = hardFails.length > 0 && hardFails.every((c) => c.name.includes("funding"));
332
+ const hasTokenUnknown = checks.some((c) => c.name.includes("token") && c.status === "unknown");
333
+ if (decision === "BLOCK" && onlyFundingFails) {
334
+ decision = "REQUIRE_FUNDING";
335
+ }
336
+ else if (decision !== "BLOCK" && hasTokenUnknown) {
337
+ decision = "REQUIRE_TOKEN_REVIEW";
338
+ }
339
+ return {
340
+ decision,
341
+ riskLevel,
342
+ safeToExecute: decision === "ALLOW",
343
+ reasons: [...new Set(reasons)],
344
+ requiredActions: [...new Set(requiredActions)],
345
+ checks,
346
+ environment,
347
+ chainId,
348
+ isMainnet,
349
+ };
350
+ }
351
+ /** Network-aware phrase for explanations — never hardcodes a single network. */
352
+ function networkPhrase(isMainnet) {
353
+ if (typeof isMainnet !== "boolean")
354
+ return "the Pharos network";
355
+ return isMainnet ? "Pharos Pacific Mainnet" : "Pharos Atlantic Testnet";
356
+ }
357
+ export function explainPolicyResult(result) {
358
+ if (result.decision === "ALLOW") {
359
+ return `This action was allowed because it targets ${networkPhrase(result.isMainnet)}, passed policy checks, and stayed within configured safety limits.`;
360
+ }
361
+ const reasonText = result.reasons.length > 0 ? result.reasons.join(" ") : "one or more safety checks failed or require review.";
362
+ const actionText = result.requiredActions.length > 0 ? ` SafeHands recommends: ${result.requiredActions.join(" ")}` : "";
363
+ const verb = result.decision === "BLOCK" ? "blocked" : result.decision.toLowerCase().replaceAll("_", " ");
364
+ return `This action was ${verb} because ${reasonText}${actionText}`;
365
+ }
366
+ export function parseTokenAmountToUnits(amount, decimals = 6) {
367
+ try {
368
+ return parseUnits(amount, decimals);
369
+ }
370
+ catch {
371
+ return null;
372
+ }
373
+ }
213
374
  //# sourceMappingURL=actionPolicyEngine.js.map