safehands-pharos 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +174 -63
  2. package/dist/cli.d.ts +5 -5
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +126 -124
  5. package/dist/cli.js.map +1 -1
  6. package/dist/demo.d.ts +1 -1
  7. package/dist/demo.js +171 -171
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +73 -65
  11. package/dist/index.js.map +1 -1
  12. package/dist/init.d.ts +1 -1
  13. package/dist/init.js +65 -65
  14. package/dist/lib/constants.d.ts +303 -291
  15. package/dist/lib/constants.d.ts.map +1 -1
  16. package/dist/lib/constants.js +302 -292
  17. package/dist/lib/constants.js.map +1 -1
  18. package/dist/lib/dodoApi.d.ts +78 -78
  19. package/dist/lib/dodoApi.js +196 -196
  20. package/dist/lib/envLoader.d.ts +2 -0
  21. package/dist/lib/envLoader.d.ts.map +1 -0
  22. package/dist/lib/envLoader.js +44 -0
  23. package/dist/lib/envLoader.js.map +1 -0
  24. package/dist/lib/http.d.ts +14 -14
  25. package/dist/lib/http.js +118 -118
  26. package/dist/lib/pharosClient.d.ts +58 -58
  27. package/dist/lib/pharosClient.js +63 -63
  28. package/dist/lib/policy/actionPolicyEngine.d.ts +53 -53
  29. package/dist/lib/policy/actionPolicyEngine.js +212 -212
  30. package/dist/lib/riskEngine.d.ts +26 -26
  31. package/dist/lib/riskEngine.d.ts.map +1 -1
  32. package/dist/lib/riskEngine.js +288 -283
  33. package/dist/lib/riskEngine.js.map +1 -1
  34. package/dist/lib/signer/index.d.ts +24 -24
  35. package/dist/lib/signer/index.js +88 -88
  36. package/dist/lib/testDodoLive.d.ts +1 -1
  37. package/dist/lib/testDodoLive.js +104 -104
  38. package/dist/lib/testLiveSafehands.d.ts +1 -1
  39. package/dist/lib/testLiveSafehands.js +92 -92
  40. package/dist/lib/testRpcLive.d.ts +1 -1
  41. package/dist/lib/testRpcLive.js +88 -88
  42. package/dist/lib/testTools.js +397 -398
  43. package/dist/lib/testX402Live.d.ts +1 -1
  44. package/dist/lib/testX402Live.js +159 -159
  45. package/dist/lib/toolResponse.d.ts +25 -25
  46. package/dist/lib/toolResponse.js +53 -53
  47. package/dist/lib/wallet/index.d.ts +37 -37
  48. package/dist/lib/wallet/index.js +128 -128
  49. package/dist/scripts/checkDeploy.d.ts +1 -1
  50. package/dist/scripts/checkDeploy.js +24 -24
  51. package/dist/scripts/deployRegistry.d.ts +1 -1
  52. package/dist/scripts/deployRegistry.js +100 -100
  53. package/dist/scripts/testRegistry.d.ts +1 -1
  54. package/dist/scripts/testRegistry.js +43 -43
  55. package/dist/tools/approveToken.d.ts +45 -45
  56. package/dist/tools/approveToken.js +85 -85
  57. package/dist/tools/assessRisk.d.ts +79 -79
  58. package/dist/tools/assessRisk.js +104 -104
  59. package/dist/tools/checkAllowance.d.ts +43 -43
  60. package/dist/tools/checkAllowance.js +56 -56
  61. package/dist/tools/checkTokenSecurity.d.ts +46 -46
  62. package/dist/tools/checkTokenSecurity.js +95 -95
  63. package/dist/tools/createAgentWallet.d.ts +28 -26
  64. package/dist/tools/createAgentWallet.d.ts.map +1 -1
  65. package/dist/tools/createAgentWallet.js +82 -58
  66. package/dist/tools/createAgentWallet.js.map +1 -1
  67. package/dist/tools/estimateGas.d.ts +79 -79
  68. package/dist/tools/estimateGas.js +124 -124
  69. package/dist/tools/executeSwap.d.ts +61 -61
  70. package/dist/tools/executeSwap.js +141 -141
  71. package/dist/tools/explainRisk.d.ts +29 -29
  72. package/dist/tools/explainRisk.js +32 -32
  73. package/dist/tools/getAgentWallet.d.ts +21 -21
  74. package/dist/tools/getAgentWallet.js +27 -27
  75. package/dist/tools/getAgentWalletBalance.d.ts +11 -11
  76. package/dist/tools/getAgentWalletBalance.js +70 -70
  77. package/dist/tools/getExecutionHistory.d.ts +49 -49
  78. package/dist/tools/getExecutionHistory.js +154 -154
  79. package/dist/tools/getGasPrice.d.ts +43 -43
  80. package/dist/tools/getGasPrice.js +59 -59
  81. package/dist/tools/getPoolInfo.d.ts +75 -75
  82. package/dist/tools/getPoolInfo.js +137 -137
  83. package/dist/tools/getTokenPrice.d.ts +113 -113
  84. package/dist/tools/getTokenPrice.js +117 -117
  85. package/dist/tools/getTransactionStatus.d.ts +43 -43
  86. package/dist/tools/getTransactionStatus.js +59 -59
  87. package/dist/tools/getWalletBalance.d.ts +68 -68
  88. package/dist/tools/getWalletBalance.js +87 -87
  89. package/dist/tools/publishRiskScore.d.ts +63 -63
  90. package/dist/tools/publishRiskScore.js +88 -88
  91. package/dist/tools/queryRiskRegistry.d.ts +38 -38
  92. package/dist/tools/queryRiskRegistry.js +55 -55
  93. package/dist/tools/safehandsPreflightCheck.d.ts +77 -77
  94. package/dist/tools/safehandsPreflightCheck.js +47 -47
  95. package/dist/tools/safehandsRiskReport.d.ts +81 -81
  96. package/dist/tools/safehandsRiskReport.js +28 -28
  97. package/dist/tools/safehandsSafeExecute.d.ts +20 -20
  98. package/dist/tools/safehandsSafeExecute.js +81 -81
  99. package/dist/tools/safehandsWalletHealth.d.ts +14 -14
  100. package/dist/tools/safehandsWalletHealth.js +103 -103
  101. package/dist/tools/safehandsX402Preflight.d.ts +26 -26
  102. package/dist/tools/safehandsX402Preflight.js +65 -65
  103. package/dist/tools/sendPayment.d.ts +57 -57
  104. package/dist/tools/sendPayment.js +117 -117
  105. package/dist/tools/simulateTransaction.d.ts +60 -60
  106. package/dist/tools/simulateTransaction.js +83 -83
  107. package/dist/tools/tokenRegistryStatus.d.ts +26 -26
  108. package/dist/tools/tokenRegistryStatus.js +96 -96
  109. package/dist/tools/x402PayAndFetch.d.ts +81 -81
  110. package/dist/tools/x402PayAndFetch.js +152 -152
  111. package/dist/x402Server.d.ts +1 -1
  112. package/dist/x402Server.js +300 -252
  113. package/dist/x402Server.js.map +1 -1
  114. package/package.json +6 -16
  115. package/examples/dashboard/index.html +0 -337
  116. package/examples/pharos-skill-engine/SKILL.safehands.md +0 -85
  117. package/examples/pharos-skill-engine/assets/safehands/example-actions.json +0 -49
  118. package/examples/pharos-skill-engine/assets/safehands/policy-defaults.json +0 -11
  119. package/examples/pharos-skill-engine/references/safehands.md +0 -345
  120. package/examples/scenario-hack.ts +0 -38
  121. package/skill/SKILL.md +0 -133
  122. package/skill/assets/safehands/example-actions.json +0 -49
  123. package/skill/assets/safehands/policy-defaults.json +0 -11
  124. package/skill/references/safehands.md +0 -345
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"riskEngine.d.ts","sourceRoot":"","sources":["../../src/lib/riskEngine.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAC/D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,kBAAkB,CAAC;IACnC,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AA2QD,wBAAsB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAgD1E"}
1
+ {"version":3,"file":"riskEngine.d.ts","sourceRoot":"","sources":["../../src/lib/riskEngine.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAC/D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,kBAAkB,CAAC;IACnC,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AA+QD,wBAAsB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAoD1E"}