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,284 +1,284 @@
1
- // ─── Risk Scoring Engine ───────────────────────────────────────────────
2
- // 5-dimension risk assessment engine for SafeHands.
3
- // Weights: liquidity 25%, slippage 25%, counterparty 20%,
4
- // balance 15%, market conditions 15%
5
- // ────────────────────────────────────────────────────────────────────────
6
- import { publicClient } from "./pharosClient.js";
7
- import { getDodoRoute, isNativeToken, resolveTokenAddress, resolveTokenDecimals, toWei, } from "./dodoApi.js";
8
- import { RISK_WEIGHTS, RISK_BLOCK_THRESHOLD, MAX_SLIPPAGE_PCT, MAX_BALANCE_USAGE_PCT, ERC20_ABI, } from "./constants.js";
9
- import { formatEther, isAddress, parseEther } from "viem";
10
- // ─── Helpers ───────────────────────────────────────────────────────────
11
- function clamp(value, min = 0, max = 100) {
12
- return Math.max(min, Math.min(max, value));
13
- }
14
- function computeRiskLevel(score) {
15
- if (score <= 30)
16
- return "low";
17
- if (score <= 60)
18
- return "medium";
19
- if (score <= 80)
20
- return "high";
21
- return "critical";
22
- }
23
- function computeRecommendation(score) {
24
- if (score <= 30)
25
- return "proceed";
26
- if (score <= RISK_BLOCK_THRESHOLD)
27
- return "caution";
28
- return "block";
29
- }
30
- function weightedAverage(breakdown) {
31
- const w = RISK_WEIGHTS;
32
- return Math.round(breakdown.liquidityRisk * w.liquidityRisk +
33
- breakdown.slippageRisk * w.slippageRisk +
34
- breakdown.counterpartyRisk * w.counterpartyRisk +
35
- breakdown.balanceRisk * w.balanceRisk +
36
- breakdown.marketConditionRisk * w.marketConditionRisk);
37
- }
38
- // ─── Individual Risk Scorers ───────────────────────────────────────────
39
- async function scoreLiquidity(input) {
40
- const reasons = [];
41
- if (input.action !== "swap")
42
- return { score: 0, reasons };
43
- try {
44
- const quote = await getDodoRoute({
45
- fromToken: input.tokenIn,
46
- toToken: input.tokenOut,
47
- amountHuman: input.amount,
48
- walletAddress: input.walletAddress,
49
- });
50
- if (!quote.routeAvailable) {
51
- reasons.push("No swap route available — liquidity may be exhausted");
52
- return { score: 100, reasons };
53
- }
54
- const impact = Math.abs(quote.priceImpact);
55
- if (impact > 5) {
56
- reasons.push(`High price impact: ${impact.toFixed(2)}%`);
57
- return { score: clamp(80 + impact), reasons };
58
- }
59
- if (impact > 2) {
60
- reasons.push(`Moderate price impact: ${impact.toFixed(2)}%`);
61
- return { score: clamp(40 + impact * 10), reasons };
62
- }
63
- reasons.push(`Price impact acceptable: ${impact.toFixed(2)}%`);
64
- return { score: clamp(impact * 15), reasons };
65
- }
66
- catch (err) {
67
- reasons.push(`Failed to fetch liquidity data: ${err.message}`);
68
- return { score: 70, reasons };
69
- }
70
- }
71
- async function scoreSlippage(input) {
72
- const reasons = [];
73
- if (input.action !== "swap")
74
- return { score: 0, reasons };
75
- try {
76
- const quote = await getDodoRoute({
77
- fromToken: input.tokenIn,
78
- toToken: input.tokenOut,
79
- amountHuman: input.amount,
80
- walletAddress: input.walletAddress,
81
- });
82
- if (!quote.routeAvailable) {
83
- reasons.push("Cannot estimate slippage — no route");
84
- return { score: 90, reasons };
85
- }
86
- const impact = Math.abs(quote.priceImpact);
87
- if (impact > MAX_SLIPPAGE_PCT) {
88
- reasons.push(`Slippage exceeds max ${MAX_SLIPPAGE_PCT}%: estimated ${impact.toFixed(2)}%`);
89
- return { score: clamp(80 + (impact - MAX_SLIPPAGE_PCT) * 5), reasons };
90
- }
91
- reasons.push(`Estimated slippage: ${impact.toFixed(2)}%`);
92
- return { score: clamp(impact * 16), reasons };
93
- }
94
- catch {
95
- reasons.push("Slippage estimation failed");
96
- return { score: 60, reasons };
97
- }
98
- }
99
- async function scoreCounterparty(input) {
100
- const reasons = [];
101
- if (input.action === "swap") {
102
- // For swaps, counterparty is the DODO protocol — trusted
103
- reasons.push("Swap routed through DODO protocol (known)");
104
- return { score: 5, reasons };
105
- }
106
- // For transfers, check the recipient address
107
- const to = input.toAddress;
108
- if (!to) {
109
- reasons.push("No recipient address provided");
110
- return { score: 100, reasons };
111
- }
112
- if (!isAddress(to)) {
113
- reasons.push("Invalid EVM address format");
114
- return { score: 100, reasons };
115
- }
116
- // Zero address check
117
- if (to === "0x0000000000000000000000000000000000000000") {
118
- reasons.push("Sending to zero address — tokens will be burned");
119
- return { score: 95, reasons };
120
- }
121
- // Self-send check
122
- if (to.toLowerCase() === input.walletAddress.toLowerCase()) {
123
- reasons.push("Sending to own address — likely unintended");
124
- return { score: 50, reasons };
125
- }
126
- // Check if address has code (is a contract)
127
- try {
128
- const code = await publicClient.getCode({ address: to });
129
- if (code && code !== "0x") {
130
- reasons.push("Recipient is a contract address — verify intent");
131
- return { score: 40, reasons };
132
- }
133
- }
134
- catch {
135
- // RPC error, non-critical
136
- }
137
- // Check if address has any transaction history (has balance)
138
- try {
139
- const balance = await publicClient.getBalance({ address: to });
140
- if (balance === 0n) {
141
- reasons.push("Recipient has zero balance — new or unused address");
142
- return { score: 30, reasons };
143
- }
144
- }
145
- catch {
146
- // Non-critical
147
- }
148
- reasons.push("Recipient address looks valid");
149
- return { score: 10, reasons };
150
- }
151
- async function scoreBalance(input) {
152
- const reasons = [];
153
- try {
154
- const walletAddr = input.walletAddress;
155
- if (input.action === "transfer" || (input.action === "swap" && isNativeToken(input.tokenIn || "PHRS"))) {
156
- // Check native PHRS balance
157
- const balance = await publicClient.getBalance({ address: walletAddr });
158
- const amountWei = parseEther(input.amount);
159
- const gasBuffer = parseEther("0.01"); // ~0.01 PHRS for gas
160
- if (balance < amountWei + gasBuffer) {
161
- const balanceHuman = formatEther(balance);
162
- reasons.push(`Insufficient PHRS balance: have ${balanceHuman}, need ${input.amount} + gas`);
163
- return { score: 100, reasons };
164
- }
165
- const usagePct = Number((amountWei * 100n) / balance);
166
- if (usagePct > MAX_BALANCE_USAGE_PCT) {
167
- reasons.push(`Using ${usagePct}% of wallet balance — high exposure`);
168
- return { score: clamp(60 + (usagePct - MAX_BALANCE_USAGE_PCT)), reasons };
169
- }
170
- reasons.push(`Balance sufficient, using ${usagePct}% of wallet`);
171
- return { score: clamp(usagePct / 3), reasons };
172
- }
173
- // ERC-20 token balance check
174
- if (input.tokenIn) {
175
- const tokenAddress = resolveTokenAddress(input.tokenIn);
176
- const decimals = resolveTokenDecimals(input.tokenIn);
177
- const amountWei = BigInt(toWei(input.amount, decimals));
178
- const balance = (await publicClient.readContract({
179
- address: tokenAddress,
180
- abi: ERC20_ABI,
181
- functionName: "balanceOf",
182
- args: [walletAddr],
183
- }));
184
- if (balance < amountWei) {
185
- reasons.push(`Insufficient ${input.tokenIn} balance`);
186
- return { score: 100, reasons };
187
- }
188
- // Also check gas balance
189
- const ethBalance = await publicClient.getBalance({ address: walletAddr });
190
- if (ethBalance < parseEther("0.005")) {
191
- reasons.push("Very low PHRS balance for gas fees");
192
- return { score: 80, reasons };
193
- }
194
- reasons.push("Token and gas balance sufficient");
195
- return { score: 5, reasons };
196
- }
197
- return { score: 0, reasons: ["Balance check skipped"] };
198
- }
199
- catch (err) {
200
- reasons.push(`Balance check failed: ${err.message}`);
201
- return { score: 50, reasons };
202
- }
203
- }
204
- async function scoreMarketCondition(_input) {
205
- const reasons = [];
206
- try {
207
- // Check recent block production rate as a proxy for chain health
208
- const latestBlock = await publicClient.getBlock({ blockTag: "latest" });
209
- const prevBlock = await publicClient.getBlock({
210
- blockNumber: latestBlock.number - 10n,
211
- });
212
- const timeDiff = Number(latestBlock.timestamp - prevBlock.timestamp);
213
- const avgBlockTime = timeDiff / 10;
214
- if (avgBlockTime > 30) {
215
- reasons.push(`Slow block production: ~${avgBlockTime.toFixed(1)}s/block — chain may be congested`);
216
- return { score: 60, reasons };
217
- }
218
- if (avgBlockTime > 15) {
219
- reasons.push(`Slightly elevated block times: ~${avgBlockTime.toFixed(1)}s/block`);
220
- return { score: 30, reasons };
221
- }
222
- // Check gas price
223
- const gasPrice = await publicClient.getGasPrice();
224
- const gasPriceGwei = Number(gasPrice) / 1e9;
225
- if (gasPriceGwei > 100) {
226
- reasons.push(`High gas price: ${gasPriceGwei.toFixed(1)} Gwei`);
227
- return { score: 50, reasons };
228
- }
229
- reasons.push(`Chain healthy: ~${avgBlockTime.toFixed(1)}s/block, ${gasPriceGwei.toFixed(1)} Gwei gas`);
230
- return { score: 5, reasons };
231
- }
232
- catch {
233
- reasons.push("Could not assess market conditions");
234
- return { score: 20, reasons };
235
- }
236
- }
237
- // ─── Main Risk Assessment ──────────────────────────────────────────────
238
- export async function assessRisk(input) {
239
- const [liquidity, slippage, counterparty, balance, market] = await Promise.all([
240
- scoreLiquidity(input),
241
- scoreSlippage(input),
242
- scoreCounterparty(input),
243
- scoreBalance(input),
244
- scoreMarketCondition(input),
245
- ]);
246
- const breakdown = {
247
- liquidityRisk: liquidity.score,
248
- slippageRisk: slippage.score,
249
- counterpartyRisk: counterparty.score,
250
- balanceRisk: balance.score,
251
- marketConditionRisk: market.score,
252
- };
253
- const riskScore = clamp(weightedAverage(breakdown));
254
- const riskLevel = computeRiskLevel(riskScore);
255
- const recommendation = computeRecommendation(riskScore);
256
- const reasons = [
257
- ...liquidity.reasons,
258
- ...slippage.reasons,
259
- ...counterparty.reasons,
260
- ...balance.reasons,
261
- ...market.reasons,
262
- ];
263
- let suggestion;
264
- if (recommendation === "proceed") {
265
- suggestion = "Risk is within acceptable range. Proceed with the transaction.";
266
- }
267
- else if (recommendation === "caution") {
268
- suggestion =
269
- "Moderate risk detected. Review the risk breakdown carefully before proceeding. Consider reducing the amount or adjusting parameters.";
270
- }
271
- else {
272
- suggestion =
273
- "High risk detected — execution blocked for safety. Review the reasons and adjust your transaction parameters significantly before retrying.";
274
- }
275
- return {
276
- riskScore,
277
- riskLevel,
278
- recommendation,
279
- breakdown,
280
- reasons,
281
- suggestion,
282
- };
283
- }
1
+ // ─── Risk Scoring Engine ───────────────────────────────────────────────
2
+ // 5-dimension risk assessment engine for SafeHands.
3
+ // Weights: liquidity 25%, slippage 25%, counterparty 20%,
4
+ // balance 15%, market conditions 15%
5
+ // ────────────────────────────────────────────────────────────────────────
6
+ import { publicClient } from "./pharosClient.js";
7
+ import { getDodoRoute, isNativeToken, resolveTokenAddress, resolveTokenDecimals, toWei, } from "./dodoApi.js";
8
+ import { RISK_WEIGHTS, RISK_BLOCK_THRESHOLD, MAX_SLIPPAGE_PCT, MAX_BALANCE_USAGE_PCT, ERC20_ABI, } from "./constants.js";
9
+ import { formatEther, isAddress, parseEther } from "viem";
10
+ // ─── Helpers ───────────────────────────────────────────────────────────
11
+ function clamp(value, min = 0, max = 100) {
12
+ return Math.max(min, Math.min(max, value));
13
+ }
14
+ function computeRiskLevel(score) {
15
+ if (score <= 30)
16
+ return "low";
17
+ if (score <= 60)
18
+ return "medium";
19
+ if (score <= 80)
20
+ return "high";
21
+ return "critical";
22
+ }
23
+ function computeRecommendation(score) {
24
+ if (score <= 30)
25
+ return "proceed";
26
+ if (score <= RISK_BLOCK_THRESHOLD)
27
+ return "caution";
28
+ return "block";
29
+ }
30
+ function weightedAverage(breakdown) {
31
+ const w = RISK_WEIGHTS;
32
+ return Math.round(breakdown.liquidityRisk * w.liquidityRisk +
33
+ breakdown.slippageRisk * w.slippageRisk +
34
+ breakdown.counterpartyRisk * w.counterpartyRisk +
35
+ breakdown.balanceRisk * w.balanceRisk +
36
+ breakdown.marketConditionRisk * w.marketConditionRisk);
37
+ }
38
+ // ─── Individual Risk Scorers ───────────────────────────────────────────
39
+ async function scoreLiquidity(input) {
40
+ const reasons = [];
41
+ if (input.action !== "swap")
42
+ return { score: 0, reasons };
43
+ try {
44
+ const quote = await getDodoRoute({
45
+ fromToken: input.tokenIn,
46
+ toToken: input.tokenOut,
47
+ amountHuman: input.amount,
48
+ walletAddress: input.walletAddress,
49
+ });
50
+ if (!quote.routeAvailable) {
51
+ reasons.push("No swap route available — liquidity may be exhausted");
52
+ return { score: 100, reasons };
53
+ }
54
+ const impact = Math.abs(quote.priceImpact);
55
+ if (impact > 5) {
56
+ reasons.push(`High price impact: ${impact.toFixed(2)}%`);
57
+ return { score: clamp(80 + impact), reasons };
58
+ }
59
+ if (impact > 2) {
60
+ reasons.push(`Moderate price impact: ${impact.toFixed(2)}%`);
61
+ return { score: clamp(40 + impact * 10), reasons };
62
+ }
63
+ reasons.push(`Price impact acceptable: ${impact.toFixed(2)}%`);
64
+ return { score: clamp(impact * 15), reasons };
65
+ }
66
+ catch (err) {
67
+ reasons.push(`Failed to fetch liquidity data: ${err.message}`);
68
+ return { score: 70, reasons };
69
+ }
70
+ }
71
+ async function scoreSlippage(input) {
72
+ const reasons = [];
73
+ if (input.action !== "swap")
74
+ return { score: 0, reasons };
75
+ try {
76
+ const quote = await getDodoRoute({
77
+ fromToken: input.tokenIn,
78
+ toToken: input.tokenOut,
79
+ amountHuman: input.amount,
80
+ walletAddress: input.walletAddress,
81
+ });
82
+ if (!quote.routeAvailable) {
83
+ reasons.push("Cannot estimate slippage — no route");
84
+ return { score: 90, reasons };
85
+ }
86
+ const impact = Math.abs(quote.priceImpact);
87
+ if (impact > MAX_SLIPPAGE_PCT) {
88
+ reasons.push(`Slippage exceeds max ${MAX_SLIPPAGE_PCT}%: estimated ${impact.toFixed(2)}%`);
89
+ return { score: clamp(80 + (impact - MAX_SLIPPAGE_PCT) * 5), reasons };
90
+ }
91
+ reasons.push(`Estimated slippage: ${impact.toFixed(2)}%`);
92
+ return { score: clamp(impact * 16), reasons };
93
+ }
94
+ catch {
95
+ reasons.push("Slippage estimation failed");
96
+ return { score: 60, reasons };
97
+ }
98
+ }
99
+ async function scoreCounterparty(input) {
100
+ const reasons = [];
101
+ if (input.action === "swap") {
102
+ // For swaps, counterparty is the DODO protocol — trusted
103
+ reasons.push("Swap routed through DODO protocol (known)");
104
+ return { score: 5, reasons };
105
+ }
106
+ // For transfers, check the recipient address
107
+ const to = input.toAddress;
108
+ if (!to) {
109
+ reasons.push("No recipient address provided");
110
+ return { score: 100, reasons };
111
+ }
112
+ if (!isAddress(to)) {
113
+ reasons.push("Invalid EVM address format");
114
+ return { score: 100, reasons };
115
+ }
116
+ // Zero address check
117
+ if (to === "0x0000000000000000000000000000000000000000") {
118
+ reasons.push("Sending to zero address — tokens will be burned");
119
+ return { score: 95, reasons };
120
+ }
121
+ // Self-send check
122
+ if (to.toLowerCase() === input.walletAddress.toLowerCase()) {
123
+ reasons.push("Sending to own address — likely unintended");
124
+ return { score: 50, reasons };
125
+ }
126
+ // Check if address has code (is a contract)
127
+ try {
128
+ const code = await publicClient.getCode({ address: to });
129
+ if (code && code !== "0x") {
130
+ reasons.push("Recipient is a contract address — verify intent");
131
+ return { score: 40, reasons };
132
+ }
133
+ }
134
+ catch {
135
+ // RPC error, non-critical
136
+ }
137
+ // Check if address has any transaction history (has balance)
138
+ try {
139
+ const balance = await publicClient.getBalance({ address: to });
140
+ if (balance === 0n) {
141
+ reasons.push("Recipient has zero balance — new or unused address");
142
+ return { score: 30, reasons };
143
+ }
144
+ }
145
+ catch {
146
+ // Non-critical
147
+ }
148
+ reasons.push("Recipient address looks valid");
149
+ return { score: 10, reasons };
150
+ }
151
+ async function scoreBalance(input) {
152
+ const reasons = [];
153
+ try {
154
+ const walletAddr = input.walletAddress;
155
+ if (input.action === "transfer" || (input.action === "swap" && isNativeToken(input.tokenIn || "PHRS"))) {
156
+ // Check native PHRS balance
157
+ const balance = await publicClient.getBalance({ address: walletAddr });
158
+ const amountWei = parseEther(input.amount);
159
+ const gasBuffer = parseEther("0.01"); // ~0.01 PHRS for gas
160
+ if (balance < amountWei + gasBuffer) {
161
+ const balanceHuman = formatEther(balance);
162
+ reasons.push(`Insufficient PHRS balance: have ${balanceHuman}, need ${input.amount} + gas`);
163
+ return { score: 100, reasons };
164
+ }
165
+ const usagePct = Number((amountWei * 100n) / balance);
166
+ if (usagePct > MAX_BALANCE_USAGE_PCT) {
167
+ reasons.push(`Using ${usagePct}% of wallet balance — high exposure`);
168
+ return { score: clamp(60 + (usagePct - MAX_BALANCE_USAGE_PCT)), reasons };
169
+ }
170
+ reasons.push(`Balance sufficient, using ${usagePct}% of wallet`);
171
+ return { score: clamp(usagePct / 3), reasons };
172
+ }
173
+ // ERC-20 token balance check
174
+ if (input.tokenIn) {
175
+ const tokenAddress = resolveTokenAddress(input.tokenIn);
176
+ const decimals = resolveTokenDecimals(input.tokenIn);
177
+ const amountWei = BigInt(toWei(input.amount, decimals));
178
+ const balance = (await publicClient.readContract({
179
+ address: tokenAddress,
180
+ abi: ERC20_ABI,
181
+ functionName: "balanceOf",
182
+ args: [walletAddr],
183
+ }));
184
+ if (balance < amountWei) {
185
+ reasons.push(`Insufficient ${input.tokenIn} balance`);
186
+ return { score: 100, reasons };
187
+ }
188
+ // Also check gas balance
189
+ const ethBalance = await publicClient.getBalance({ address: walletAddr });
190
+ if (ethBalance < parseEther("0.005")) {
191
+ reasons.push("Very low PHRS balance for gas fees");
192
+ return { score: 80, reasons };
193
+ }
194
+ reasons.push("Token and gas balance sufficient");
195
+ return { score: 5, reasons };
196
+ }
197
+ return { score: 0, reasons: ["Balance check skipped"] };
198
+ }
199
+ catch (err) {
200
+ reasons.push(`Balance check failed: ${err.message}`);
201
+ return { score: 50, reasons };
202
+ }
203
+ }
204
+ async function scoreMarketCondition(_input) {
205
+ const reasons = [];
206
+ try {
207
+ // Check recent block production rate as a proxy for chain health
208
+ const latestBlock = await publicClient.getBlock({ blockTag: "latest" });
209
+ const prevBlock = await publicClient.getBlock({
210
+ blockNumber: latestBlock.number - 10n,
211
+ });
212
+ const timeDiff = Number(latestBlock.timestamp - prevBlock.timestamp);
213
+ const avgBlockTime = timeDiff / 10;
214
+ if (avgBlockTime > 30) {
215
+ reasons.push(`Slow block production: ~${avgBlockTime.toFixed(1)}s/block — chain may be congested`);
216
+ return { score: 60, reasons };
217
+ }
218
+ if (avgBlockTime > 15) {
219
+ reasons.push(`Slightly elevated block times: ~${avgBlockTime.toFixed(1)}s/block`);
220
+ return { score: 30, reasons };
221
+ }
222
+ // Check gas price
223
+ const gasPrice = await publicClient.getGasPrice();
224
+ const gasPriceGwei = Number(gasPrice) / 1e9;
225
+ if (gasPriceGwei > 100) {
226
+ reasons.push(`High gas price: ${gasPriceGwei.toFixed(1)} Gwei`);
227
+ return { score: 50, reasons };
228
+ }
229
+ reasons.push(`Chain healthy: ~${avgBlockTime.toFixed(1)}s/block, ${gasPriceGwei.toFixed(1)} Gwei gas`);
230
+ return { score: 5, reasons };
231
+ }
232
+ catch {
233
+ reasons.push("Could not assess market conditions");
234
+ return { score: 20, reasons };
235
+ }
236
+ }
237
+ // ─── Main Risk Assessment ──────────────────────────────────────────────
238
+ export async function assessRisk(input) {
239
+ const [liquidity, slippage, counterparty, balance, market] = await Promise.all([
240
+ scoreLiquidity(input),
241
+ scoreSlippage(input),
242
+ scoreCounterparty(input),
243
+ scoreBalance(input),
244
+ scoreMarketCondition(input),
245
+ ]);
246
+ const breakdown = {
247
+ liquidityRisk: liquidity.score,
248
+ slippageRisk: slippage.score,
249
+ counterpartyRisk: counterparty.score,
250
+ balanceRisk: balance.score,
251
+ marketConditionRisk: market.score,
252
+ };
253
+ const riskScore = clamp(weightedAverage(breakdown));
254
+ const riskLevel = computeRiskLevel(riskScore);
255
+ const recommendation = computeRecommendation(riskScore);
256
+ const reasons = [
257
+ ...liquidity.reasons,
258
+ ...slippage.reasons,
259
+ ...counterparty.reasons,
260
+ ...balance.reasons,
261
+ ...market.reasons,
262
+ ];
263
+ let suggestion;
264
+ if (recommendation === "proceed") {
265
+ suggestion = "Risk is within acceptable range. Proceed with the transaction.";
266
+ }
267
+ else if (recommendation === "caution") {
268
+ suggestion =
269
+ "Moderate risk detected. Review the risk breakdown carefully before proceeding. Consider reducing the amount or adjusting parameters.";
270
+ }
271
+ else {
272
+ suggestion =
273
+ "High risk detected — execution blocked for safety. Review the reasons and adjust your transaction parameters significantly before retrying.";
274
+ }
275
+ return {
276
+ riskScore,
277
+ riskLevel,
278
+ recommendation,
279
+ breakdown,
280
+ reasons,
281
+ suggestion,
282
+ };
283
+ }
284
284
  //# sourceMappingURL=riskEngine.js.map
@@ -1,25 +1,25 @@
1
- import type { Account } from "viem";
2
- export type SignerMode = "none" | "env" | "managed-testnet" | "external-signer" | "x402-env";
3
- export type SignerPurpose = "write" | "x402";
4
- export interface SignerResult {
5
- account: Account;
6
- address: `0x${string}`;
7
- mode: SignerMode;
8
- }
9
- export interface SignerFailure {
10
- error: {
11
- code: string;
12
- message: string;
13
- };
14
- }
15
- export type GetSignerResult = SignerResult | SignerFailure;
16
- export declare function isSignerFailure(r: GetSignerResult): r is SignerFailure;
17
- /**
18
- * Get a signer for write/payment operations.
19
- * Priority for x402: managed wallet > X402_SIGNER_PRIVATE_KEY > PRIVATE_KEY fallback.
20
- * Priority for writes: managed wallet > PRIVATE_KEY fallback.
21
- */
22
- export declare function getSigner(agentId?: string, options?: {
23
- purpose?: SignerPurpose;
24
- }): Promise<GetSignerResult>;
1
+ import type { Account } from "viem";
2
+ export type SignerMode = "none" | "env" | "managed-testnet" | "external-signer" | "x402-env";
3
+ export type SignerPurpose = "write" | "x402";
4
+ export interface SignerResult {
5
+ account: Account;
6
+ address: `0x${string}`;
7
+ mode: SignerMode;
8
+ }
9
+ export interface SignerFailure {
10
+ error: {
11
+ code: string;
12
+ message: string;
13
+ };
14
+ }
15
+ export type GetSignerResult = SignerResult | SignerFailure;
16
+ export declare function isSignerFailure(r: GetSignerResult): r is SignerFailure;
17
+ /**
18
+ * Get a signer for write/payment operations.
19
+ * Priority for x402: managed wallet > X402_SIGNER_PRIVATE_KEY > PRIVATE_KEY fallback.
20
+ * Priority for writes: managed wallet > PRIVATE_KEY fallback.
21
+ */
22
+ export declare function getSigner(agentId?: string, options?: {
23
+ purpose?: SignerPurpose;
24
+ }): Promise<GetSignerResult>;
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/signer/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAC7F,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;AAE3D,wBAAgB,eAAe,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,IAAI,aAAa,CAEtE;AAsDD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,aAAa,CAAA;CAAO,GACxC,OAAO,CAAC,eAAe,CAAC,CA0B1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/signer/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAC7F,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;AAE3D,wBAAgB,eAAe,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,IAAI,aAAa,CAEtE;AAqDD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,aAAa,CAAA;CAAO,GACxC,OAAO,CAAC,eAAe,CAAC,CA0B1B"}