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,79 +1,79 @@
1
- export interface DodoRouteResponse {
2
- status: number;
3
- data: {
4
- resAmount: string | number;
5
- priceImpact: string | number;
6
- targetDecimals: number;
7
- targetApproveAddr: string;
8
- to: string;
9
- data: string;
10
- value: string | number;
11
- gasLimit: string | number;
12
- } | null;
13
- message?: string;
14
- }
15
- export interface DodoQuote {
16
- amountOut: string;
17
- amountOutWei: string;
18
- priceImpact: number;
19
- gasLimit: string;
20
- value: string;
21
- calldata: string;
22
- to: string;
23
- approveAddress: string;
24
- routeAvailable: boolean;
25
- sourceStatus: "ok" | "no_route_available" | "auth_required" | "unavailable";
26
- usedApiKey: boolean;
27
- /** Actual on-chain address used for fromToken (may differ from input if USDC fallback triggered) */
28
- usedFromToken: string;
29
- /** Actual on-chain address used for toToken (may differ from input if USDC fallback triggered) */
30
- usedToToken: string;
31
- /** True when a silent token substitution occurred (e.g. canonical USDC → altUSDC) */
32
- wasSubstituted: boolean;
33
- /** Human-readable explanation when wasSubstituted is true */
34
- substitutionNote?: string;
35
- rawResponse: DodoRouteResponse;
36
- }
37
- /**
38
- * Resolves a token symbol (e.g. "PHRS") or address to its on-chain address.
39
- */
40
- export declare function resolveTokenAddress(token: string): `0x${string}`;
41
- /**
42
- * Resolves the decimal count for a token symbol or address.
43
- */
44
- export declare function resolveTokenDecimals(token: string): number;
45
- /**
46
- * Converts a human-readable amount to wei string.
47
- */
48
- export declare function toWei(amount: string, decimals: number): string;
49
- /**
50
- * Converts a wei string to human-readable amount.
51
- * Also handles the case where the API returns a decimal string directly.
52
- */
53
- export declare function fromWei(weiInput: string | number, decimals: number): string;
54
- /**
55
- * Fetches a swap route from the DODO aggregation API.
56
- */
57
- export declare function _getDodoRouteCore(params: {
58
- fromToken: string;
59
- toToken: string;
60
- amountHuman: string;
61
- walletAddress: string;
62
- slippage?: number;
63
- }): Promise<DodoQuote>;
64
- /**
65
- * Smart wrapper around _getDodoRouteCore that automatically falls back to alternative token sources
66
- * (like Circle USDC) if the canonical tokens have no liquidity on the testnet.
67
- */
68
- export declare function getDodoRoute(params: {
69
- fromToken: string;
70
- toToken: string;
71
- amountHuman: string;
72
- walletAddress: string;
73
- slippage?: number;
74
- }): Promise<DodoQuote>;
75
- /**
76
- * Checks whether a token is the native PHRS token.
77
- */
78
- export declare function isNativeToken(token: string): boolean;
1
+ export interface DodoRouteResponse {
2
+ status: number;
3
+ data: {
4
+ resAmount: string | number;
5
+ priceImpact: string | number;
6
+ targetDecimals: number;
7
+ targetApproveAddr: string;
8
+ to: string;
9
+ data: string;
10
+ value: string | number;
11
+ gasLimit: string | number;
12
+ } | null;
13
+ message?: string;
14
+ }
15
+ export interface DodoQuote {
16
+ amountOut: string;
17
+ amountOutWei: string;
18
+ priceImpact: number;
19
+ gasLimit: string;
20
+ value: string;
21
+ calldata: string;
22
+ to: string;
23
+ approveAddress: string;
24
+ routeAvailable: boolean;
25
+ sourceStatus: "ok" | "no_route_available" | "auth_required" | "unavailable";
26
+ usedApiKey: boolean;
27
+ /** Actual on-chain address used for fromToken (may differ from input if USDC fallback triggered) */
28
+ usedFromToken: string;
29
+ /** Actual on-chain address used for toToken (may differ from input if USDC fallback triggered) */
30
+ usedToToken: string;
31
+ /** True when a silent token substitution occurred (e.g. canonical USDC → altUSDC) */
32
+ wasSubstituted: boolean;
33
+ /** Human-readable explanation when wasSubstituted is true */
34
+ substitutionNote?: string;
35
+ rawResponse: DodoRouteResponse;
36
+ }
37
+ /**
38
+ * Resolves a token symbol (e.g. "PHRS") or address to its on-chain address.
39
+ */
40
+ export declare function resolveTokenAddress(token: string): `0x${string}`;
41
+ /**
42
+ * Resolves the decimal count for a token symbol or address.
43
+ */
44
+ export declare function resolveTokenDecimals(token: string): number;
45
+ /**
46
+ * Converts a human-readable amount to wei string.
47
+ */
48
+ export declare function toWei(amount: string, decimals: number): string;
49
+ /**
50
+ * Converts a wei string to human-readable amount.
51
+ * Also handles the case where the API returns a decimal string directly.
52
+ */
53
+ export declare function fromWei(weiInput: string | number, decimals: number): string;
54
+ /**
55
+ * Fetches a swap route from the DODO aggregation API.
56
+ */
57
+ export declare function _getDodoRouteCore(params: {
58
+ fromToken: string;
59
+ toToken: string;
60
+ amountHuman: string;
61
+ walletAddress: string;
62
+ slippage?: number;
63
+ }): Promise<DodoQuote>;
64
+ /**
65
+ * Smart wrapper around _getDodoRouteCore that automatically falls back to alternative token sources
66
+ * (like Circle USDC) if the canonical tokens have no liquidity on the testnet.
67
+ */
68
+ export declare function getDodoRoute(params: {
69
+ fromToken: string;
70
+ toToken: string;
71
+ amountHuman: string;
72
+ walletAddress: string;
73
+ slippage?: number;
74
+ }): Promise<DodoQuote>;
75
+ /**
76
+ * Checks whether a token is the native PHRS token.
77
+ */
78
+ export declare function isNativeToken(token: string): boolean;
79
79
  //# sourceMappingURL=dodoApi.d.ts.map
@@ -1,197 +1,197 @@
1
- // ─── DODO Route API Client ─────────────────────────────────────────────
2
- // Fetches swap routes from DODO's aggregation API for FaroSwap on
3
- // Pharos Atlantic Testnet.
4
- // ────────────────────────────────────────────────────────────────────────
5
- import { isAddress } from "viem";
6
- import { DODO_API_BASE, DODO_API_KEY, DODO_DEFAULT_SLIPPAGE, DODO_ROUTE_ENDPOINT, CHAIN_ID, TOKEN_MAP, TOKEN_DECIMALS, PHRS_ADDRESS, USDC_ADDRESS, CIRCLE_USDC_ADDRESS, } from "./constants.js";
7
- import { fetchWithTimeoutAndRetry } from "./http.js";
8
- // ─── Helpers ───────────────────────────────────────────────────────────
9
- /**
10
- * Resolves a token symbol (e.g. "PHRS") or address to its on-chain address.
11
- */
12
- export function resolveTokenAddress(token) {
13
- const upper = token.toUpperCase();
14
- if (TOKEN_MAP[upper])
15
- return TOKEN_MAP[upper];
16
- if (isAddress(token))
17
- return token;
18
- throw new Error(`INVALID_TOKEN_ADDRESS: Unknown token or invalid address: ${token}`);
19
- }
20
- /**
21
- * Resolves the decimal count for a token symbol or address.
22
- */
23
- export function resolveTokenDecimals(token) {
24
- const upper = token.toUpperCase();
25
- if (TOKEN_DECIMALS[upper] !== undefined)
26
- return TOKEN_DECIMALS[upper];
27
- const lower = token.toLowerCase();
28
- if (TOKEN_DECIMALS[lower] !== undefined)
29
- return TOKEN_DECIMALS[lower];
30
- return 18; // unknown custom token fallback
31
- }
32
- /**
33
- * Converts a human-readable amount to wei string.
34
- */
35
- export function toWei(amount, decimals) {
36
- if (!/^\d+(\.\d+)?$/.test(amount)) {
37
- throw new Error(`INVALID_AMOUNT: ${amount}`);
38
- }
39
- const [whole, frac = ""] = amount.split(".");
40
- const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
41
- return BigInt(whole + paddedFrac).toString();
42
- }
43
- /**
44
- * Converts a wei string to human-readable amount.
45
- * Also handles the case where the API returns a decimal string directly.
46
- */
47
- export function fromWei(weiInput, decimals) {
48
- const weiStr = String(weiInput);
49
- if (weiStr.includes("."))
50
- return weiStr;
51
- const wei = BigInt(weiStr);
52
- const divisor = BigInt(10) ** BigInt(decimals);
53
- const whole = wei / divisor;
54
- const remainder = wei % divisor;
55
- if (remainder === 0n)
56
- return whole.toString();
57
- const fracStr = remainder.toString().padStart(decimals, "0").replace(/0+$/, "");
58
- return `${whole}.${fracStr}`;
59
- }
60
- // ─── DODO Route Fetch ──────────────────────────────────────────────────
61
- /**
62
- * Fetches a swap route from the DODO aggregation API.
63
- */
64
- export async function _getDodoRouteCore(params) {
65
- const trimmedApiKey = DODO_API_KEY?.trim();
66
- const usedApiKey = Boolean(trimmedApiKey);
67
- if (!isAddress(params.walletAddress)) {
68
- throw new Error(`INVALID_WALLET_ADDRESS: ${params.walletAddress}`);
69
- }
70
- const fromAddress = resolveTokenAddress(params.fromToken);
71
- const toAddress = resolveTokenAddress(params.toToken);
72
- const usedFromToken = fromAddress;
73
- const usedToToken = toAddress;
74
- const fromDecimals = resolveTokenDecimals(params.fromToken);
75
- const toDecimals = resolveTokenDecimals(params.toToken);
76
- const amountInWei = toWei(params.amountHuman, fromDecimals);
77
- const slippage = params.slippage ?? DODO_DEFAULT_SLIPPAGE;
78
- const deadline = Math.floor(Date.now() / 1000) + 600;
79
- const url = new URL(`${DODO_API_BASE}${DODO_ROUTE_ENDPOINT}`);
80
- url.searchParams.set("chainId", String(CHAIN_ID));
81
- url.searchParams.set("fromTokenAddress", fromAddress);
82
- url.searchParams.set("toTokenAddress", toAddress);
83
- url.searchParams.set("fromAmount", amountInWei);
84
- url.searchParams.set("userAddr", params.walletAddress);
85
- url.searchParams.set("slippage", String(slippage));
86
- url.searchParams.set("source", "dodoV2AndMixWasm");
87
- url.searchParams.set("estimateGas", "true");
88
- // DODO/FaroSwap read-only route queries should prefer public mode when no
89
- // API key is configured. Never hardcode a default credential. If the
90
- // provider rejects public mode, surface a structured auth-required error
91
- // at the tool layer.
92
- if (trimmedApiKey) {
93
- url.searchParams.set("apikey", trimmedApiKey);
94
- }
95
- url.searchParams.set("deadLine", String(deadline));
96
- const response = await fetchWithTimeoutAndRetry(url, {
97
- timeoutMs: 10_000,
98
- retries: 2,
99
- retryDelayMs: 300,
100
- });
101
- if (!response.ok) {
102
- if (response.status === 401 || response.status === 403) {
103
- throw new Error(`DODO_API_AUTH_REQUIRED: DODO route API rejected public mode${usedApiKey ? " or provided key" : "; configure DODO_API_KEY if this endpoint requires authentication"}`);
104
- }
105
- if (response.status === 429) {
106
- throw new Error(`DODO_API_RATE_LIMITED: ${response.status} ${response.statusText}`);
107
- }
108
- throw new Error(`DODO_API_UNAVAILABLE: ${response.status} ${response.statusText}`);
109
- }
110
- const json = (await response.json());
111
- if (!json.data || json.status !== 200) {
112
- const message = (json.message || "").toLowerCase();
113
- if (message.includes("api key") || message.includes("apikey") || message.includes("unauthorized") || message.includes("forbidden")) {
114
- throw new Error(`DODO_API_AUTH_REQUIRED: DODO route API rejected public mode${usedApiKey ? " or provided key" : "; configure DODO_API_KEY if this endpoint requires authentication"}`);
115
- }
116
- return {
117
- amountOut: "0",
118
- amountOutWei: "0",
119
- priceImpact: 100,
120
- gasLimit: "0",
121
- value: "0",
122
- calldata: "0x",
123
- to: "",
124
- approveAddress: "",
125
- routeAvailable: false,
126
- sourceStatus: "no_route_available",
127
- usedApiKey,
128
- usedFromToken,
129
- usedToToken,
130
- wasSubstituted: false,
131
- rawResponse: json,
132
- };
133
- }
134
- const d = json.data;
135
- const resAmountStr = String(d.resAmount);
136
- return {
137
- amountOut: fromWei(resAmountStr, toDecimals),
138
- amountOutWei: resAmountStr,
139
- priceImpact: parseFloat(String(d.priceImpact)) || 0,
140
- gasLimit: String(d.gasLimit),
141
- value: String(d.value),
142
- calldata: d.data,
143
- to: d.to,
144
- approveAddress: d.targetApproveAddr,
145
- routeAvailable: true,
146
- sourceStatus: "ok",
147
- usedApiKey,
148
- usedFromToken,
149
- usedToToken,
150
- wasSubstituted: false,
151
- rawResponse: json,
152
- };
153
- }
154
- /**
155
- * Smart wrapper around _getDodoRouteCore that automatically falls back to alternative token sources
156
- * (like Circle USDC) if the canonical tokens have no liquidity on the testnet.
157
- */
158
- export async function getDodoRoute(params) {
159
- let quote = await _getDodoRouteCore(params);
160
- if (!quote.routeAvailable) {
161
- const fromUpper = params.fromToken.toUpperCase();
162
- const toUpper = params.toToken.toUpperCase();
163
- // Smart Fallback: If swapping TO canonical USDC failed, try Alternate Circle USDC
164
- if (toUpper === "USDC" || toUpper === USDC_ADDRESS.toUpperCase()) {
165
- const fallbackQuote = await _getDodoRouteCore({ ...params, toToken: CIRCLE_USDC_ADDRESS });
166
- if (fallbackQuote.routeAvailable) {
167
- return {
168
- ...fallbackQuote,
169
- wasSubstituted: true,
170
- substitutionNote: `toToken substituted: canonical USDC (${USDC_ADDRESS}) had no route; used altUSDC (${CIRCLE_USDC_ADDRESS}). Ensure your approval targets the correct address.`,
171
- };
172
- }
173
- }
174
- // Smart Fallback: If swapping FROM canonical USDC failed, try Alternate Circle USDC
175
- if (fromUpper === "USDC" || fromUpper === USDC_ADDRESS.toUpperCase()) {
176
- const fallbackQuote = await _getDodoRouteCore({ ...params, fromToken: CIRCLE_USDC_ADDRESS });
177
- if (fallbackQuote.routeAvailable) {
178
- return {
179
- ...fallbackQuote,
180
- wasSubstituted: true,
181
- substitutionNote: `fromToken substituted: canonical USDC (${USDC_ADDRESS}) had no route; used altUSDC (${CIRCLE_USDC_ADDRESS}). Ensure your allowance covers the correct address.`,
182
- };
183
- }
184
- }
185
- }
186
- return quote;
187
- }
188
- /**
189
- * Checks whether a token is the native PHRS token.
190
- */
191
- export function isNativeToken(token) {
192
- const upper = token.toUpperCase();
193
- if (upper === "PHRS")
194
- return true;
195
- return token.toLowerCase() === PHRS_ADDRESS.toLowerCase();
196
- }
1
+ // ─── DODO Route API Client ─────────────────────────────────────────────
2
+ // Fetches swap routes from DODO's aggregation API for FaroSwap on
3
+ // Pharos Atlantic Testnet.
4
+ // ────────────────────────────────────────────────────────────────────────
5
+ import { isAddress } from "viem";
6
+ import { DODO_API_BASE, DODO_API_KEY, DODO_DEFAULT_SLIPPAGE, DODO_ROUTE_ENDPOINT, CHAIN_ID, TOKEN_MAP, TOKEN_DECIMALS, PHRS_ADDRESS, USDC_ADDRESS, CIRCLE_USDC_ADDRESS, } from "./constants.js";
7
+ import { fetchWithTimeoutAndRetry } from "./http.js";
8
+ // ─── Helpers ───────────────────────────────────────────────────────────
9
+ /**
10
+ * Resolves a token symbol (e.g. "PHRS") or address to its on-chain address.
11
+ */
12
+ export function resolveTokenAddress(token) {
13
+ const upper = token.toUpperCase();
14
+ if (TOKEN_MAP[upper])
15
+ return TOKEN_MAP[upper];
16
+ if (isAddress(token))
17
+ return token;
18
+ throw new Error(`INVALID_TOKEN_ADDRESS: Unknown token or invalid address: ${token}`);
19
+ }
20
+ /**
21
+ * Resolves the decimal count for a token symbol or address.
22
+ */
23
+ export function resolveTokenDecimals(token) {
24
+ const upper = token.toUpperCase();
25
+ if (TOKEN_DECIMALS[upper] !== undefined)
26
+ return TOKEN_DECIMALS[upper];
27
+ const lower = token.toLowerCase();
28
+ if (TOKEN_DECIMALS[lower] !== undefined)
29
+ return TOKEN_DECIMALS[lower];
30
+ return 18; // unknown custom token fallback
31
+ }
32
+ /**
33
+ * Converts a human-readable amount to wei string.
34
+ */
35
+ export function toWei(amount, decimals) {
36
+ if (!/^\d+(\.\d+)?$/.test(amount)) {
37
+ throw new Error(`INVALID_AMOUNT: ${amount}`);
38
+ }
39
+ const [whole, frac = ""] = amount.split(".");
40
+ const paddedFrac = frac.padEnd(decimals, "0").slice(0, decimals);
41
+ return BigInt(whole + paddedFrac).toString();
42
+ }
43
+ /**
44
+ * Converts a wei string to human-readable amount.
45
+ * Also handles the case where the API returns a decimal string directly.
46
+ */
47
+ export function fromWei(weiInput, decimals) {
48
+ const weiStr = String(weiInput);
49
+ if (weiStr.includes("."))
50
+ return weiStr;
51
+ const wei = BigInt(weiStr);
52
+ const divisor = BigInt(10) ** BigInt(decimals);
53
+ const whole = wei / divisor;
54
+ const remainder = wei % divisor;
55
+ if (remainder === 0n)
56
+ return whole.toString();
57
+ const fracStr = remainder.toString().padStart(decimals, "0").replace(/0+$/, "");
58
+ return `${whole}.${fracStr}`;
59
+ }
60
+ // ─── DODO Route Fetch ──────────────────────────────────────────────────
61
+ /**
62
+ * Fetches a swap route from the DODO aggregation API.
63
+ */
64
+ export async function _getDodoRouteCore(params) {
65
+ const trimmedApiKey = DODO_API_KEY?.trim();
66
+ const usedApiKey = Boolean(trimmedApiKey);
67
+ if (!isAddress(params.walletAddress)) {
68
+ throw new Error(`INVALID_WALLET_ADDRESS: ${params.walletAddress}`);
69
+ }
70
+ const fromAddress = resolveTokenAddress(params.fromToken);
71
+ const toAddress = resolveTokenAddress(params.toToken);
72
+ const usedFromToken = fromAddress;
73
+ const usedToToken = toAddress;
74
+ const fromDecimals = resolveTokenDecimals(params.fromToken);
75
+ const toDecimals = resolveTokenDecimals(params.toToken);
76
+ const amountInWei = toWei(params.amountHuman, fromDecimals);
77
+ const slippage = params.slippage ?? DODO_DEFAULT_SLIPPAGE;
78
+ const deadline = Math.floor(Date.now() / 1000) + 600;
79
+ const url = new URL(`${DODO_API_BASE}${DODO_ROUTE_ENDPOINT}`);
80
+ url.searchParams.set("chainId", String(CHAIN_ID));
81
+ url.searchParams.set("fromTokenAddress", fromAddress);
82
+ url.searchParams.set("toTokenAddress", toAddress);
83
+ url.searchParams.set("fromAmount", amountInWei);
84
+ url.searchParams.set("userAddr", params.walletAddress);
85
+ url.searchParams.set("slippage", String(slippage));
86
+ url.searchParams.set("source", "dodoV2AndMixWasm");
87
+ url.searchParams.set("estimateGas", "true");
88
+ // DODO/FaroSwap read-only route queries should prefer public mode when no
89
+ // API key is configured. Never hardcode a default credential. If the
90
+ // provider rejects public mode, surface a structured auth-required error
91
+ // at the tool layer.
92
+ if (trimmedApiKey) {
93
+ url.searchParams.set("apikey", trimmedApiKey);
94
+ }
95
+ url.searchParams.set("deadLine", String(deadline));
96
+ const response = await fetchWithTimeoutAndRetry(url, {
97
+ timeoutMs: 10_000,
98
+ retries: 2,
99
+ retryDelayMs: 300,
100
+ });
101
+ if (!response.ok) {
102
+ if (response.status === 401 || response.status === 403) {
103
+ throw new Error(`DODO_API_AUTH_REQUIRED: DODO route API rejected public mode${usedApiKey ? " or provided key" : "; configure DODO_API_KEY if this endpoint requires authentication"}`);
104
+ }
105
+ if (response.status === 429) {
106
+ throw new Error(`DODO_API_RATE_LIMITED: ${response.status} ${response.statusText}`);
107
+ }
108
+ throw new Error(`DODO_API_UNAVAILABLE: ${response.status} ${response.statusText}`);
109
+ }
110
+ const json = (await response.json());
111
+ if (!json.data || json.status !== 200) {
112
+ const message = (json.message || "").toLowerCase();
113
+ if (message.includes("api key") || message.includes("apikey") || message.includes("unauthorized") || message.includes("forbidden")) {
114
+ throw new Error(`DODO_API_AUTH_REQUIRED: DODO route API rejected public mode${usedApiKey ? " or provided key" : "; configure DODO_API_KEY if this endpoint requires authentication"}`);
115
+ }
116
+ return {
117
+ amountOut: "0",
118
+ amountOutWei: "0",
119
+ priceImpact: 100,
120
+ gasLimit: "0",
121
+ value: "0",
122
+ calldata: "0x",
123
+ to: "",
124
+ approveAddress: "",
125
+ routeAvailable: false,
126
+ sourceStatus: "no_route_available",
127
+ usedApiKey,
128
+ usedFromToken,
129
+ usedToToken,
130
+ wasSubstituted: false,
131
+ rawResponse: json,
132
+ };
133
+ }
134
+ const d = json.data;
135
+ const resAmountStr = String(d.resAmount);
136
+ return {
137
+ amountOut: fromWei(resAmountStr, toDecimals),
138
+ amountOutWei: resAmountStr,
139
+ priceImpact: parseFloat(String(d.priceImpact)) || 0,
140
+ gasLimit: String(d.gasLimit),
141
+ value: String(d.value),
142
+ calldata: d.data,
143
+ to: d.to,
144
+ approveAddress: d.targetApproveAddr,
145
+ routeAvailable: true,
146
+ sourceStatus: "ok",
147
+ usedApiKey,
148
+ usedFromToken,
149
+ usedToToken,
150
+ wasSubstituted: false,
151
+ rawResponse: json,
152
+ };
153
+ }
154
+ /**
155
+ * Smart wrapper around _getDodoRouteCore that automatically falls back to alternative token sources
156
+ * (like Circle USDC) if the canonical tokens have no liquidity on the testnet.
157
+ */
158
+ export async function getDodoRoute(params) {
159
+ let quote = await _getDodoRouteCore(params);
160
+ if (!quote.routeAvailable) {
161
+ const fromUpper = params.fromToken.toUpperCase();
162
+ const toUpper = params.toToken.toUpperCase();
163
+ // Smart Fallback: If swapping TO canonical USDC failed, try Alternate Circle USDC
164
+ if (toUpper === "USDC" || toUpper === USDC_ADDRESS.toUpperCase()) {
165
+ const fallbackQuote = await _getDodoRouteCore({ ...params, toToken: CIRCLE_USDC_ADDRESS });
166
+ if (fallbackQuote.routeAvailable) {
167
+ return {
168
+ ...fallbackQuote,
169
+ wasSubstituted: true,
170
+ substitutionNote: `toToken substituted: canonical USDC (${USDC_ADDRESS}) had no route; used altUSDC (${CIRCLE_USDC_ADDRESS}). Ensure your approval targets the correct address.`,
171
+ };
172
+ }
173
+ }
174
+ // Smart Fallback: If swapping FROM canonical USDC failed, try Alternate Circle USDC
175
+ if (fromUpper === "USDC" || fromUpper === USDC_ADDRESS.toUpperCase()) {
176
+ const fallbackQuote = await _getDodoRouteCore({ ...params, fromToken: CIRCLE_USDC_ADDRESS });
177
+ if (fallbackQuote.routeAvailable) {
178
+ return {
179
+ ...fallbackQuote,
180
+ wasSubstituted: true,
181
+ substitutionNote: `fromToken substituted: canonical USDC (${USDC_ADDRESS}) had no route; used altUSDC (${CIRCLE_USDC_ADDRESS}). Ensure your allowance covers the correct address.`,
182
+ };
183
+ }
184
+ }
185
+ }
186
+ return quote;
187
+ }
188
+ /**
189
+ * Checks whether a token is the native PHRS token.
190
+ */
191
+ export function isNativeToken(token) {
192
+ const upper = token.toUpperCase();
193
+ if (upper === "PHRS")
194
+ return true;
195
+ return token.toLowerCase() === PHRS_ADDRESS.toLowerCase();
196
+ }
197
197
  //# sourceMappingURL=dodoApi.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=envLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envLoader.d.ts","sourceRoot":"","sources":["../../src/lib/envLoader.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ // Synchronously load .env from cwd before any other module reads process.env.
2
+ // Must be imported as the very first import in index.ts so that wallet store,
3
+ // constants, and other modules that read env vars at module-init time see the values.
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
5
+ import { join, resolve, dirname } from "path";
6
+ import { randomBytes } from "crypto";
7
+ // 1. Load .env
8
+ try {
9
+ const text = readFileSync(join(process.cwd(), ".env"), "utf-8");
10
+ for (const line of text.split(/\r?\n/)) {
11
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
12
+ if (m && m[1] && !process.env[m[1]]) {
13
+ // Strip surrounding quotes if present
14
+ process.env[m[1]] = m[2].replace(/^(['"])(.*)\1$/, "$2");
15
+ }
16
+ }
17
+ }
18
+ catch {
19
+ // No .env file — environment vars must be set externally (CI, shell, MCP host)
20
+ }
21
+ // 2. Auto-resolve wallet persistence so managed wallets survive restarts
22
+ // without requiring explicit config from the user.
23
+ if (!process.env.WALLET_STORE_PATH) {
24
+ process.env.WALLET_STORE_PATH = "./.agents/wallets.json";
25
+ }
26
+ if (!process.env.WALLET_ENCRYPTION_KEY) {
27
+ const storePath = resolve(process.cwd(), process.env.WALLET_STORE_PATH);
28
+ const keyPath = join(dirname(storePath), ".key");
29
+ try {
30
+ if (existsSync(keyPath)) {
31
+ process.env.WALLET_ENCRYPTION_KEY = readFileSync(keyPath, "utf-8").trim();
32
+ }
33
+ else {
34
+ mkdirSync(dirname(keyPath), { recursive: true });
35
+ const key = randomBytes(32).toString("hex");
36
+ writeFileSync(keyPath, key, { mode: 0o600 });
37
+ process.env.WALLET_ENCRYPTION_KEY = key;
38
+ }
39
+ }
40
+ catch {
41
+ // File ops failed (read-only FS, permission error) — fall back to in-memory behavior
42
+ }
43
+ }
44
+ //# sourceMappingURL=envLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envLoader.js","sourceRoot":"","sources":["../../src/lib/envLoader.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,sFAAsF;AACtF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,eAAe;AACf,IAAI,CAAC;IACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACpC,sCAAsC;YACtC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;AACH,CAAC;AAAC,MAAM,CAAC;IACP,+EAA+E;AACjF,CAAC;AAED,yEAAyE;AACzE,sDAAsD;AACtD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,wBAAwB,CAAC;AAC3D,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,qBAAqB,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC5C,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,qBAAqB,GAAG,GAAG,CAAC;QAC1C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qFAAqF;IACvF,CAAC;AACH,CAAC"}
@@ -1,15 +1,15 @@
1
- export declare class HttpError extends Error {
2
- readonly status?: number | undefined;
3
- readonly statusText?: string | undefined;
4
- constructor(message: string, status?: number | undefined, statusText?: string | undefined);
5
- }
6
- export interface FetchWithRetryOptions extends RequestInit {
7
- timeoutMs?: number;
8
- retries?: number;
9
- retryDelayMs?: number;
10
- }
11
- export declare function fetchWithTimeoutAndRetry(url: string | URL, options?: FetchWithRetryOptions): Promise<Response>;
12
- export declare function redactSensitive(value: unknown): unknown;
13
- export declare function isBlockedIp(ip: string): boolean;
14
- export declare function assertSafeFetchUrl(rawUrl: string): Promise<void>;
1
+ export declare class HttpError extends Error {
2
+ readonly status?: number | undefined;
3
+ readonly statusText?: string | undefined;
4
+ constructor(message: string, status?: number | undefined, statusText?: string | undefined);
5
+ }
6
+ export interface FetchWithRetryOptions extends RequestInit {
7
+ timeoutMs?: number;
8
+ retries?: number;
9
+ retryDelayMs?: number;
10
+ }
11
+ export declare function fetchWithTimeoutAndRetry(url: string | URL, options?: FetchWithRetryOptions): Promise<Response>;
12
+ export declare function redactSensitive(value: unknown): unknown;
13
+ export declare function isBlockedIp(ip: string): boolean;
14
+ export declare function assertSafeFetchUrl(rawUrl: string): Promise<void>;
15
15
  //# sourceMappingURL=http.d.ts.map