safehands-pharos 1.3.0 → 1.4.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 (159) hide show
  1. package/.env.example +64 -26
  2. package/README.md +333 -445
  3. package/dist/cli.d.ts +5 -5
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +124 -98
  6. package/dist/cli.js.map +1 -1
  7. package/dist/demo.d.ts +1 -1
  8. package/dist/demo.js +171 -171
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +138 -85
  11. package/dist/index.js.map +1 -1
  12. package/dist/init.d.ts +1 -1
  13. package/dist/init.js +65 -65
  14. package/dist/lib/auditLog.d.ts +9 -0
  15. package/dist/lib/auditLog.d.ts.map +1 -0
  16. package/dist/lib/auditLog.js +30 -0
  17. package/dist/lib/auditLog.js.map +1 -0
  18. package/dist/lib/constants.d.ts +291 -291
  19. package/dist/lib/constants.js +292 -292
  20. package/dist/lib/dodoApi.d.ts +78 -70
  21. package/dist/lib/dodoApi.d.ts.map +1 -1
  22. package/dist/lib/dodoApi.js +196 -178
  23. package/dist/lib/dodoApi.js.map +1 -1
  24. package/dist/lib/http.d.ts +14 -14
  25. package/dist/lib/http.js +118 -118
  26. package/dist/lib/pharosClient.d.ts +58 -58
  27. package/dist/lib/pharosClient.d.ts.map +1 -1
  28. package/dist/lib/pharosClient.js +63 -53
  29. package/dist/lib/pharosClient.js.map +1 -1
  30. package/dist/lib/policy/actionPolicyEngine.d.ts +53 -53
  31. package/dist/lib/policy/actionPolicyEngine.js +212 -212
  32. package/dist/lib/riskEngine.d.ts +26 -26
  33. package/dist/lib/riskEngine.js +283 -283
  34. package/dist/lib/signer/index.d.ts +24 -24
  35. package/dist/lib/signer/index.d.ts.map +1 -1
  36. package/dist/lib/signer/index.js +88 -89
  37. package/dist/lib/signer/index.js.map +1 -1
  38. package/dist/lib/spendAccumulator.d.ts +10 -0
  39. package/dist/lib/spendAccumulator.d.ts.map +1 -0
  40. package/dist/lib/spendAccumulator.js +54 -0
  41. package/dist/lib/spendAccumulator.js.map +1 -0
  42. package/dist/lib/testDodoLive.d.ts +1 -1
  43. package/dist/lib/testDodoLive.js +104 -104
  44. package/dist/lib/testLiveSafehands.d.ts +1 -1
  45. package/dist/lib/testLiveSafehands.js +92 -92
  46. package/dist/lib/testRpc.d.ts +1 -1
  47. package/dist/lib/testRpc.js +29 -29
  48. package/dist/lib/testRpcLive.d.ts +1 -1
  49. package/dist/lib/testRpcLive.js +88 -88
  50. package/dist/lib/testTools.d.ts +1 -1
  51. package/dist/lib/testTools.js +397 -397
  52. package/dist/lib/testX402Live.d.ts +1 -1
  53. package/dist/lib/testX402Live.js +159 -159
  54. package/dist/lib/toolResponse.d.ts +25 -25
  55. package/dist/lib/toolResponse.js +53 -53
  56. package/dist/lib/wallet/index.d.ts +37 -18
  57. package/dist/lib/wallet/index.d.ts.map +1 -1
  58. package/dist/lib/wallet/index.js +128 -70
  59. package/dist/lib/wallet/index.js.map +1 -1
  60. package/dist/scripts/checkDeploy.d.ts +1 -1
  61. package/dist/scripts/checkDeploy.js +24 -24
  62. package/dist/scripts/deployRegistry.d.ts +1 -1
  63. package/dist/scripts/deployRegistry.js +100 -100
  64. package/dist/scripts/testRegistry.d.ts +1 -1
  65. package/dist/scripts/testRegistry.js +43 -43
  66. package/dist/tools/approveToken.d.ts +45 -46
  67. package/dist/tools/approveToken.d.ts.map +1 -1
  68. package/dist/tools/approveToken.js +85 -83
  69. package/dist/tools/approveToken.js.map +1 -1
  70. package/dist/tools/assessRisk.d.ts +79 -79
  71. package/dist/tools/assessRisk.d.ts.map +1 -1
  72. package/dist/tools/assessRisk.js +104 -93
  73. package/dist/tools/assessRisk.js.map +1 -1
  74. package/dist/tools/checkAllowance.d.ts +43 -36
  75. package/dist/tools/checkAllowance.d.ts.map +1 -1
  76. package/dist/tools/checkAllowance.js +56 -42
  77. package/dist/tools/checkAllowance.js.map +1 -1
  78. package/dist/tools/checkTokenSecurity.d.ts +46 -46
  79. package/dist/tools/checkTokenSecurity.d.ts.map +1 -1
  80. package/dist/tools/checkTokenSecurity.js +95 -88
  81. package/dist/tools/checkTokenSecurity.js.map +1 -1
  82. package/dist/tools/createAgentWallet.d.ts +26 -26
  83. package/dist/tools/createAgentWallet.d.ts.map +1 -1
  84. package/dist/tools/createAgentWallet.js +58 -59
  85. package/dist/tools/createAgentWallet.js.map +1 -1
  86. package/dist/tools/estimateGas.d.ts +79 -79
  87. package/dist/tools/estimateGas.js +124 -124
  88. package/dist/tools/executeSwap.d.ts +61 -59
  89. package/dist/tools/executeSwap.d.ts.map +1 -1
  90. package/dist/tools/executeSwap.js +141 -129
  91. package/dist/tools/executeSwap.js.map +1 -1
  92. package/dist/tools/explainRisk.d.ts +29 -29
  93. package/dist/tools/explainRisk.js +32 -32
  94. package/dist/tools/getAgentWallet.d.ts +21 -21
  95. package/dist/tools/getAgentWallet.js +27 -27
  96. package/dist/tools/getAgentWalletBalance.d.ts +11 -11
  97. package/dist/tools/getAgentWalletBalance.js +70 -70
  98. package/dist/tools/getExecutionHistory.d.ts +49 -51
  99. package/dist/tools/getExecutionHistory.d.ts.map +1 -1
  100. package/dist/tools/getExecutionHistory.js +154 -93
  101. package/dist/tools/getExecutionHistory.js.map +1 -1
  102. package/dist/tools/getGasPrice.d.ts +43 -43
  103. package/dist/tools/getGasPrice.js +59 -59
  104. package/dist/tools/getPoolInfo.d.ts +75 -75
  105. package/dist/tools/getPoolInfo.js +137 -137
  106. package/dist/tools/getTokenPrice.d.ts +113 -113
  107. package/dist/tools/getTokenPrice.js +117 -117
  108. package/dist/tools/getTransactionStatus.d.ts +43 -57
  109. package/dist/tools/getTransactionStatus.d.ts.map +1 -1
  110. package/dist/tools/getTransactionStatus.js +59 -67
  111. package/dist/tools/getTransactionStatus.js.map +1 -1
  112. package/dist/tools/getWalletBalance.d.ts +68 -68
  113. package/dist/tools/getWalletBalance.js +87 -87
  114. package/dist/tools/publishRiskScore.d.ts +63 -63
  115. package/dist/tools/publishRiskScore.d.ts.map +1 -1
  116. package/dist/tools/publishRiskScore.js +88 -85
  117. package/dist/tools/publishRiskScore.js.map +1 -1
  118. package/dist/tools/queryRiskRegistry.d.ts +38 -48
  119. package/dist/tools/queryRiskRegistry.d.ts.map +1 -1
  120. package/dist/tools/queryRiskRegistry.js +55 -60
  121. package/dist/tools/queryRiskRegistry.js.map +1 -1
  122. package/dist/tools/safehandsPreflightCheck.d.ts +77 -77
  123. package/dist/tools/safehandsPreflightCheck.js +47 -47
  124. package/dist/tools/safehandsRiskReport.d.ts +81 -81
  125. package/dist/tools/safehandsRiskReport.js +28 -28
  126. package/dist/tools/safehandsSafeExecute.d.ts +20 -20
  127. package/dist/tools/safehandsSafeExecute.d.ts.map +1 -1
  128. package/dist/tools/safehandsSafeExecute.js +81 -75
  129. package/dist/tools/safehandsSafeExecute.js.map +1 -1
  130. package/dist/tools/safehandsWalletHealth.d.ts +14 -14
  131. package/dist/tools/safehandsWalletHealth.js +103 -103
  132. package/dist/tools/safehandsX402Preflight.d.ts +26 -26
  133. package/dist/tools/safehandsX402Preflight.js +65 -65
  134. package/dist/tools/sendPayment.d.ts +57 -58
  135. package/dist/tools/sendPayment.d.ts.map +1 -1
  136. package/dist/tools/sendPayment.js +117 -108
  137. package/dist/tools/sendPayment.js.map +1 -1
  138. package/dist/tools/simulateTransaction.d.ts +60 -81
  139. package/dist/tools/simulateTransaction.d.ts.map +1 -1
  140. package/dist/tools/simulateTransaction.js +83 -88
  141. package/dist/tools/simulateTransaction.js.map +1 -1
  142. package/dist/tools/tokenRegistryStatus.d.ts +26 -26
  143. package/dist/tools/tokenRegistryStatus.js +96 -96
  144. package/dist/tools/x402PayAndFetch.d.ts +81 -81
  145. package/dist/tools/x402PayAndFetch.d.ts.map +1 -1
  146. package/dist/tools/x402PayAndFetch.js +152 -149
  147. package/dist/tools/x402PayAndFetch.js.map +1 -1
  148. package/dist/x402Server.d.ts +1 -1
  149. package/dist/x402Server.js +252 -252
  150. package/examples/dashboard/index.html +337 -0
  151. package/package.json +83 -84
  152. package/.agents/skill/safehands/SKILL.md +0 -212
  153. package/.agents/skill/safehands/assets/networks.json +0 -24
  154. package/.agents/skill/safehands/assets/tokens.json +0 -66
  155. package/.agents/wallets.json +0 -20
  156. package/docs/reports/OFFICIAL_DOCS_ALIGNMENT_REPORT.md +0 -137
  157. package/docs/reports/final_audit_report.md +0 -307
  158. package/docs/reports/live_verification_report.md +0 -147
  159. package/docs/reports/pharos_skill_engine_alignment_report.md +0 -85
@@ -1,213 +1,213 @@
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 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
+ }
213
213
  //# sourceMappingURL=actionPolicyEngine.js.map
@@ -1,27 +1,27 @@
1
- export interface RiskBreakdown {
2
- liquidityRisk: number;
3
- slippageRisk: number;
4
- counterpartyRisk: number;
5
- balanceRisk: number;
6
- marketConditionRisk: number;
7
- }
8
- export type RiskLevel = "low" | "medium" | "high" | "critical";
9
- export type RiskRecommendation = "proceed" | "caution" | "block";
10
- export interface RiskAssessment {
11
- riskScore: number;
12
- riskLevel: RiskLevel;
13
- recommendation: RiskRecommendation;
14
- breakdown: RiskBreakdown;
15
- reasons: string[];
16
- suggestion: string;
17
- }
18
- export interface RiskInput {
19
- action: "swap" | "transfer";
20
- tokenIn?: string;
21
- tokenOut?: string;
22
- amount: string;
23
- toAddress?: string;
24
- walletAddress: string;
25
- }
26
- export declare function assessRisk(input: RiskInput): Promise<RiskAssessment>;
1
+ export interface RiskBreakdown {
2
+ liquidityRisk: number;
3
+ slippageRisk: number;
4
+ counterpartyRisk: number;
5
+ balanceRisk: number;
6
+ marketConditionRisk: number;
7
+ }
8
+ export type RiskLevel = "low" | "medium" | "high" | "critical";
9
+ export type RiskRecommendation = "proceed" | "caution" | "block";
10
+ export interface RiskAssessment {
11
+ riskScore: number;
12
+ riskLevel: RiskLevel;
13
+ recommendation: RiskRecommendation;
14
+ breakdown: RiskBreakdown;
15
+ reasons: string[];
16
+ suggestion: string;
17
+ }
18
+ export interface RiskInput {
19
+ action: "swap" | "transfer";
20
+ tokenIn?: string;
21
+ tokenOut?: string;
22
+ amount: string;
23
+ toAddress?: string;
24
+ walletAddress: string;
25
+ }
26
+ export declare function assessRisk(input: RiskInput): Promise<RiskAssessment>;
27
27
  //# sourceMappingURL=riskEngine.d.ts.map