amped-defi

by Amped Finance · v1.0.0

25 DeFi tools for cross-chain swaps, bridging, and money market operations via SODAX SDK. Supply on Chain A, borrow to Chain B. Supports Ethereum, Arbitrum, Base, Optimism, Avalanche, BSC, Polygon, Sonic, LightLink, HyperEVM, Kaia.

Blocked
Risk
Critical
Status
failed
Findings
46
Last Scanned
2/11/2026

Discussion

Sign in to join the discussion.

No comments yet. Be the first to share your thoughts.

Scan Report

Duration
292.5s
Rules checked
147
Scanned at
2/11/2026, 6:41:10 PM

Scanners5/5 ran

clawguard-rules
34 findings9ms
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:86)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:87)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:302)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:303)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:331)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:332)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:370)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:378)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:379)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:393)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:394)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:721)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:727)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:871)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:872)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:888)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:889)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:890)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934)
View logs
clawguard-rules9ms
1[2026-02-11T18:36:18.254Z] Running @yourclaw/clawguard-rules pattern matcher
2Scanning: /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md
3Content length: 37724 chars
4Patterns matched: 34
5 [high] SEC-004: Potential secret in key-value assignment
6 [high] SEC-004: Potential secret in key-value assignment
7 [high] SEC-004: Potential secret in key-value assignment
8 [high] SEC-004: Potential secret in key-value assignment
9 [high] SEC-004: Potential secret in key-value assignment
10 [high] SEC-004: Potential secret in key-value assignment
11 [high] SEC-004: Potential secret in key-value assignment
12 [high] SEC-004: Potential secret in key-value assignment
13 [low] PI-041: Possible base64-encoded payload
14 [low] PI-041: Possible base64-encoded payload
15 [low] PI-041: Possible base64-encoded payload
16 [low] PI-041: Possible base64-encoded payload
17 [low] PI-041: Possible base64-encoded payload
18 [low] PI-041: Possible base64-encoded payload
19 [low] PI-041: Possible base64-encoded payload
20 [low] PI-041: Possible base64-encoded payload
21 [low] PI-041: Possible base64-encoded payload
22 [low] PI-041: Possible base64-encoded payload
23 [low] PI-041: Possible base64-encoded payload
24 [low] PI-041: Possible base64-encoded payload
25 [low] PI-041: Possible base64-encoded payload
26 [low] PI-041: Possible base64-encoded payload
27 [low] PI-041: Possible base64-encoded payload
28 [low] PI-041: Possible base64-encoded payload
29 [low] PI-041: Possible base64-encoded payload
30 [low] PI-041: Possible base64-encoded payload
31 [low] PI-041: Possible base64-encoded payload
32 [low] PI-041: Possible base64-encoded payload
33 [low] PI-041: Possible base64-encoded payload
34 [low] PI-041: Possible base64-encoded payload
35 [low] PI-041: Possible base64-encoded payload
36 [low] PI-041: Possible base64-encoded payload
37 [low] PI-041: Possible base64-encoded payload
38 [low] PI-041: Possible base64-encoded payload
39✓ Completed in 9ms
gitleaks
0 findings163533ms
No findings — all checks passed.
View logs
gitleaks163533ms
1[2026-02-11T18:39:01.787Z] $ gitleaks detect --source /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish --report-format json --report-path /dev/stdout --no-git
2
3⚠ stderr output:
4
5 │╲
6 │ ○
7 ○ ░
8 ░ gitleaks
9
106:39PM FTL Report path is not writable: /dev/stdout error="open /dev/stdout: no such device or address"
11
12Process exited with code 1
13✓ Completed in 163533ms
semgrep
9 findings292443ms
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:181)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:224)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:392)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:481)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:511)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:185)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:331)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts:153)
javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversalDetected possible user input going into a `path.join` or `path.resolve` function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts:22)
View logs
semgrep292443ms
1[2026-02-11T18:41:10.700Z] $ semgrep scan --json --quiet --config auto /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish
2{"version":"1.151.0","results":[{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":181,"col":19,"offset":6407},"end":{"line":181,"col":81,"offset":6469},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":224,"col":19,"offset":7811},"end":{"line":224,"col":64,"offset":7856},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":392,"col":23,"offset":13041},"end":{"line":392,"col":65,"offset":13083},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":481,"col":23,"offset":16555},"end":{"line":481,"col":89,"offset":16621},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":511,"col":21,"offset":17972},"end":{"line":511,"col":84,"offset":18035},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","start":{"line":185,"col":20,"offset":5216},"end":{"line":185,"col":75,"offset":5271},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","start":{"line":331,"col":19,"offset":11090},"end":{"line":331,"col":67,"offset":11138},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts","start":{"line":153,"col":19,"offset":6839},"end":{"line":153,"col":80,"offset":6900},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts","start":{"line":22,"col":71,"offset":838},"end":{"line":22,"col":102,"offset":869},"extra":{"message":"Detected possible user input going into a `path.join` or `path.resolve` function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.","metadata":{"owasp":["A05:2017 - Broken Access Control","A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"cwe":["CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"],"category":"security","references":["https://owasp.org/www-community/attacks/Path_Traversal"],"technology":["javascript","node.js"],"cwe2022-top25":true,"cwe2021-top25":true,"subcategory":["vuln"],"likelihood":"HIGH","impact":"MEDIUM","confidence":"LOW","license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Path Traversal"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal","shortlink":"https://sg.run/OPqk"},"severity":"WARNING","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}}],"errors":[],"paths":{"scanned":["/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/README.md","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/_meta.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/examples/basic-usage.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/index.js","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/openclaw.plugin.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/package.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__mocks__/@sodax/sdk.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/errors.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/policyEngine.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/positionAggregator.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/setup.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/sodaxApi.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/walletRegistry.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/policy/policyEngine.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/providers/spokeProviderFactory.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/sodax/client.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/bridge.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/discovery.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/moneyMarket.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/swap.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/walletManagement.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/errorUtils.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/errors.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/priceService.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/sodaxApi.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backendConfig.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrWalletProvider.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/EnvBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/EvmWalletSkillBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/AmpedWalletProvider.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/BankrBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/LocalKeyBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/chainConfig.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/skillWalletAdapter.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/walletManager.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/walletRegistry.ts"]},"time":{"rules":[],"rules_parse_time":29.759514808654785,"profiling_times":{"config_time":39.555999517440796,"core_time":59.95686173439026,"ignores_time":0.0030536651611328125,"total_time":99.5768826007843},"parsing_time":{"total_time":0.0,"per_file_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_files":[]},"scanning_time":{"total_time":31.35837483406067,"per_file_time":{"mean":0.22083362559197658,"std_dev":0.2457130224013178},"very_slow_stats":{"time_ratio":0.44541932318657135,"count_ratio":0.04225352112676056},"very_slow_files":[{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","ftime":1.7254259586334229},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/policy/policyEngine.ts","ftime":1.980241060256958},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","ftime":2.2785351276397705},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/walletManagement.ts","ftime":2.3493080139160156},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/swap.ts","ftime":2.6788549423217773},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/moneyMarket.ts","ftime":2.955260992050171}]},"matching_time":{"total_time":0.0,"per_file_and_rule_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_rules_on_files":[]},"tainting_time":{"total_time":0.0,"per_def_and_rule_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_rules_on_defs":[]},"fixpoint_timeouts":[],"prefiltering":{"project_level_time":0.0,"file_level_time":0.0,"rules_with_project_prefilters_ratio":0.0,"rules_with_file_prefilters_ratio":0.9854888253362991,"rules_selected_ratio":0.039190763690287046,"rules_matched_ratio":0.039190763690287046},"targets":[],"total_bytes":0,"max_memory_bytes":1379013312},"engine_requested":"OSS","skipped_rules":[],"profiling_results":[]}
3
4Process exited with code 0
5✓ Completed in 292443ms
mcp-scan
3 findings218643ms
MCP-W004The MCP server is not in our registry.
MCP-W011Third-party content exposure detected (high risk: 0.70). The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.
MCP-W009Direct money access detected (high risk: 1.00). The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets — i.e., direct financial execution (crypto/blockchain). Therefore it meets the "Direct Financial Execution" criteria.
View logs
mcp-scan218643ms
1[2026-02-11T18:39:56.903Z] $ mcp-scan --skills /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish --json
2{
3 "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance": {
4 "client": "not-available",
5 "path": "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance",
6 "servers": [
7 {
8 "name": "amped-defi-publish",
9 "server": {
10 "path": "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish",
11 "type": "skill"
12 },
13 "signature": {
14 "metadata": {
15 "meta": null,
16 "protocolVersion": "built-in",
17 "capabilities": {
18 "experimental": null,
19 "logging": null,
20 "prompts": null,
21 "resources": null,
22 "tools": {
23 "listChanged": false
24 },
25 "completions": null,
26 "tasks": null
27 },
28 "serverInfo": {
29 "name": "amped-defi",
30 "title": null,
31 "version": "skills",
32 "websiteUrl": null,
33 "icons": null
34 },
35 "instructions": "25 DeFi tools for cross-chain swaps, bridging, and money market operations via SODAX SDK. Supply on Chain A, borrow to Chain B. Supports Ethereum, Arbitrum, Base, Optimism, Avalanche, BSC, Polygon, Sonic, LightLink, HyperEVM, Kaia.",
36 "prompts": {
37 "listChanged": false
38 },
39 "resources": {
40 "subscribe": null,
41 "listChanged": false
42 }
43 },
44 "prompts": [
45 {
46 "name": "SKILL.md",
47 "title": null,
48 "description": "\n\n# Amped DeFi Skill\n\n## Overview\n\nThe **Amped DeFi** skill provides on-chain DeFi operations capabilities for agents, enabling seamless **swaps**, **bridging**, and **money market** (supply/borrow/repay/withdraw) actions across multiple chains using the SODAX SDK. This skill abstracts the complexity of cross-chain intent flows, allowance handling, and policy enforcement, allowing agents to execute DeFi operations safely and efficiently.\n\n**Key capabilities:**\n- Cross-chain and same-chain token swaps via solver network\n- Token bridging between spoke chains and the Sonic hub chain\n- **Cross-chain money market operations** - supply on one chain, borrow to another!\n- Money market operations (supply, withdraw, borrow, repay) with position tracking\n- Policy enforcement (spend limits, slippage caps, allowlists)\n- Support for both execution mode (agent signs) and prepare mode (unsigned txs for external signing)\n\n## Tool Categories\n\n### Discovery Tools\n\nUse these tools to explore supported chains, tokens, and wallet state before executing operations.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_supported_chains` | List all supported spoke chains (e.g., ethereum, arbitrum, sonic) |\n| `amped_supported_tokens` | Get supported tokens for a specific module (swaps/bridge/moneyMarket) on a chain |\n| `amped_wallet_address` | Resolve wallet address by walletId (validates private key \u2194 address match in execute mode) |\n| `amped_money_market_reserves` | View available money market reserves (collateral/borrow markets) |\n| `amped_money_market_positions` | View user's money market positions on a SINGLE chain |\n| `amped_cross_chain_positions` | **RECOMMENDED**: View aggregated positions across ALL chains with total supply/borrow, health factor, borrowing power, net APY, and risk metrics |\n| `amped_user_intents` | Query user's swap/bridge intent history from SODAX backend API. Shows open, filled, cancelled intents with full event details. |\n\n**When to use:** Always start with discovery tools to verify chain/token support before attempting any operation.\n\n### User Intent History (SODAX API)\n\nQuery the SODAX backend API to retrieve complete intent history for a wallet:\n\n```\n\u2192 amped_user_intents(\n walletId=\"main\",\n status=\"all\", // \"all\", \"open\", or \"closed\"\n limit=10, // Number of results (max 100)\n offset=0 // For pagination\n )\n\u2190 Returns: {\n pagination: { total: 1545, offset: 0, limit: 10, hasMore: true },\n intents: [\n {\n intentHash: \"0x5b18d04a545f089e6de59106fa79498cfc0b0274...\",\n txHash: \"0x1c4a8ded456b97ba9fa2b95ee954ed7e92a40365...\",\n chainId: 146,\n blockNumber: 57622027,\n status: \"closed\",\n createdAt: \"2025-12-10T19:44:00.380Z\",\n input: { token: \"0x654D...\", amount: \"10000000000000000000\", chainId: 1768124270 },\n output: { token: \"0x9Ee1...\", minAmount: \"78684607057391028830\", chainId: 5 },\n deadline: \"2026-12-10T19:48:32.000Z\",\n events: [\n { type: \"intent-filled\", txHash: \"0x7981...\", blockNumber: 57622086, ... }\n ]\n }\n ],\n summary: { totalIntents: 1545, returned: 10, openIntents: 3, closedIntents: 1537 }\n }\n```\n\n**When to use:**\n- Track status of pending swap/bridge operations\n- View historical intent execution history\n- Debug failed or cancelled intents\n- Monitor solver performance and fill rates\n\n### Swap Tools\n\nCross-chain and same-chain token swaps via SODAX's intent-based solver network.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_swap_quote` | Get an exact-in or exact-out swap quote with slippage and fee estimates |\n| `amped_swap_execute` | Execute a swap (handles allowance, approval, and execution automatically) |\n| `amped_swap_status` | Check the status of a swap transaction or intent |\n| `amped_swap_cancel` | Cancel an active swap intent (where supported) |\n\n**When to use swaps:**\n- Exchanging one token for another on the same chain\n- Cross-chain swaps (e.g., USDC on Ethereum \u2192 USDT on Arbitrum)\n- When you need competitive pricing via solver competition\n\n**When NOT to use swaps:**\n- Moving the same token across chains (use bridge tools instead)\n- Borrowing/lending operations (use money market tools instead)\n\n### Bridge Tools\n\nBridge tokens between chains via the swap infrastructure.\n\n> **Note:** In SODAX, bridges and cross-chain swaps use the same underlying intent-based messaging system. The `amped_bridge_execute` tool internally delegates to the swap infrastructure, which provides better routing and reliability.\n>\n> **Recommendation:** Use cross-chain swaps (`amped_swap_quote` + `amped_swap_execute`) directly for bridging. You can swap USDC on one chain directly to native tokens (ETH, AVAX, POL, etc.) on another chain in a single operation.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_bridge_discover` | Discover bridgeable tokens between two chains |\n| `amped_bridge_quote` | Check bridgeability, limits, and max bridgeable amount |\n| `amped_bridge_execute` | Execute bridge (delegates to swap infrastructure) |\n\n**When to use bridging/cross-chain swaps:**\n- Moving tokens from one chain to another (e.g., USDC on Base \u2192 ETH on Arbitrum)\n- Getting native gas tokens on a new chain (e.g., USDC \u2192 POL on Polygon)\n- Transferring assets to/from the Sonic hub chain\n\n**Preferred approach for gas distribution:**\n```\n// Get gas tokens on multiple chains from a single source\n\u2192 amped_swap_quote(srcChainId=\"base\", dstChainId=\"polygon\", srcToken=\"USDC\", dstToken=\"POL\", amount=\"0.5\", ...)\n\u2192 amped_swap_execute(quote)\n// Result: 0.5 USDC on Base \u2192 ~4 POL on Polygon\n```\n\n### Money Market Tools\n\nSupply, borrow, repay, and withdraw assets from the SODAX money market with **cross-chain capabilities**.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_mm_supply` | Supply tokens as collateral to the money market. Supports cross-chain supply. |\n| `amped_mm_withdraw` | Withdraw supplied tokens from the money market. Supports cross-chain withdraw. |\n| `amped_mm_borrow` | Borrow tokens against supplied collateral. **KEY FEATURE: Can borrow to a different chain!** |\n| `amped_mm_repay` | Repay borrowed tokens. Use amount='-1' or repayAll=true for full repay. |\n| `amped_mm_create_supply_intent` | [Advanced] Create a supply intent without executing (for custom flows) |\n| `amped_mm_create_borrow_intent` | [Advanced] Create a borrow intent without executing (supports cross-chain) |\n\n**Cross-Chain Money Market Capabilities:**\n\nThe SODAX money market supports powerful cross-chain operations:\n\n1. **Cross-Chain Borrow** (Most powerful feature)\n - Supply collateral on Chain A (e.g., Ethereum)\n - Borrow tokens to Chain B (e.g., Arbitrum)\n - Your collateral stays on Chain A, but you receive borrowed tokens on Chain B\n - Use `dstChainId` parameter to specify the destination chain\n\n2. **Cross-Chain Supply**\n - Supply tokens on Chain A\n - Collateral is recorded on Chain B (if different)\n - Use `dstChainId` parameter\n\n3. **Cross-Chain Withdraw**\n - Withdraw collateral from Chain A\n - Receive tokens on Chain B\n - Use `dstChainId` parameter\n\n**When to use money market:**\n- Earning yield by supplying assets\n- Borrowing against existing collateral\n- **Accessing liquidity on Chain B without moving collateral from Chain A**\n- Arbitraging interest rates across chains\n- Managing leveraged positions\n- Repaying debt to improve health factor\n\n**When NOT to use money market:**\n- Simple token exchanges (use swap tools)\n- Moving assets across chains without borrowing (use bridge tools)\n\n### Wallet Management Tools\n\nManage multiple wallets with nicknames for easy identification.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_list_wallets` | List all configured wallets with their nicknames and addresses |\n| `amped_add_wallet` | Add a new wallet with a nickname (supports private key or Bankr wallets) |\n| `amped_rename_wallet` | Rename an existing wallet's nickname |\n| `amped_remove_wallet` | Remove a wallet from configuration |\n| `amped_set_default_wallet` | Set which wallet is used by default for operations |\n\n**When to use wallet management:**\n- Setting up multiple wallets for different purposes (trading, holding, testing)\n- Organizing wallets with memorable nicknames instead of addresses\n- Switching between wallets for different operations\n- Managing a portfolio across multiple addresses\n\n**Example workflow:**\n```\n1. amped_add_wallet(nickname=\"trading\", address=\"0x...\", privateKey=\"0x...\")\n2. amped_add_wallet(nickname=\"vault\", address=\"0x...\")\n3. amped_set_default_wallet(nickname=\"trading\")\n4. amped_list_wallets() // Shows all wallets with default indicator\n5. amped_swap_execute(walletId=\"trading\", ...) // Uses trading wallet\n```\n\n## Safety Rules\n\n\u26a0\ufe0f **MUST FOLLOW \u2014 These rules are enforced by the Policy Engine:**\n\n1. **Always get a quote before executing**\n - Never execute a swap without first calling `amped_swap_quote`\n - Never execute a bridge without first calling `amped_bridge_quote`\n - Review the quote output for acceptable slippage and output amounts\n\n2. **Verify chain and token are supported**\n - Call `amped_supported_chains` and `amped_supported_tokens` before operations\n - Unsupported chains/tokens will return clear errors\n\n3. **Check slippage is within acceptable bounds**\n - Slippage is specified in basis points (bps): 100 bps = 1%\n - Default max slippage: 100 bps (1%)\n - Quotes with slippage exceeding configured caps will be rejected\n - Policy violations return structured errors with remediation guidance\n\n4. **Never attempt to drain entire wallet**\n - Leave sufficient balance for gas fees\n - Spend limits are enforced per-transaction and per-day\n - Policy caps: `maxSwapInputUsd`, `maxBridgeAmountToken`, `maxBorrowUsd`\n\n5. **Always verify transaction status after execution**\n - Use `amped_swap_status` to track swap completion\n - Check `amped_money_market_positions` to verify position updates\n - Never assume success based on transaction hash alone\n\n6. **Enforce allowlist compliance**\n - Only operate on `allowedChains` and `allowedTokensByChain` per policy\n - Blocked recipients are rejected\n - Policy failures return structured errors with remediation text\n\n7. **Simulation is enabled by default**\n - `skipSimulation=false` unless operator override\n - Simulations catch revert conditions before broadcast\n\n8. **Monitor health factor for money market positions**\n - Health factor < 1.0 = liquidation risk\n - Keep health factor > 1.5 for safety margin\n - Use `amped_money_market_positions` to monitor\n\n## Parameter Conventions\n\n### Amount Units\n- **Amounts are in human-readable units** (e.g., `\"100\"` for 100 USDC, `\"0.5\"` for 0.5 ETH)\n- The SDK internally converts to raw units using token decimals from SODAX config\n- Examples:\n - `\"1000\"` USDC (USDC has 6 decimals) \u2192 1000000000 raw units\n - `\"1.5\"` ETH (ETH has 18 decimals) \u2192 1500000000000000000 raw units\n\n### Slippage (Basis Points)\n- Slippage is specified in **basis points (bps)** where 100 bps = 1%\n- Common values:\n - `50` = 0.5% (tight, for stable pairs)\n - `100` = 1% (standard)\n - `300` = 3% (volatile pairs or cross-chain)\n- Quotes exceeding configured `maxSlippageBps` will be rejected\n\n### Chain Identifiers\n- Chain IDs are **string identifiers**, not numeric chain IDs:\n - `\"ethereum\"` (Ethereum mainnet)\n - `\"arbitrum\"` (Arbitrum One)\n - `\"sonic\"` (Sonic hub chain)\n - `\"base\"` (Base)\n - `\"optimism\"` (Optimism)\n - `\"avalanche\"` (Avalanche)\n - `\"bsc\"` (BNB Smart Chain)\n\n### Token Addresses\n- Token addresses should be **checksum addresses** (mixed-case per EIP-55)\n- Examples:\n - `\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"` (USDC on Ethereum)\n - `\"0x4200000000000000000000000000000000000006\"` (WETH on Base)\n\n### Wallet Identification\n- All execute tools require a `walletId` string\n- Wallet resolution is by ID; private keys are never exposed in tool parameters\n\n### Optional Parameters\n- `recipient`: Optional destination address (defaults to wallet address)\n- `timeoutMs`: Optional operation timeout in milliseconds\n- `policyId`: Optional policy profile selector for custom limits\n- `dstChainId`: **For cross-chain money market** - destination chain for the operation\n\n## Workflows\n\n### Swap Workflow\n\nComplete workflow for executing a token swap:\n\n```\nStep 1: Discovery (if needed)\n \u2192 amped_supported_chains\n \u2192 amped_supported_tokens(module=\"swaps\", chainId=\"ethereum\")\n\nStep 2: Get Quote\n \u2192 amped_swap_quote(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"arbitrum\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\",\n amount=\"1000\",\n type=\"exact_input\",\n slippageBps=100\n )\n \u2190 Returns: { quoteId, expectedOutput, slippageBps, fees, deadline }\n\nStep 3: Review Quote\n \u2713 Check slippageBps \u2264 maxSlippageBps (configurable, default 100)\n \u2713 Verify expectedOutput meets requirements\n \u2713 Confirm fees are acceptable\n\nStep 4: Execute Swap\n \u2192 amped_swap_execute(\n walletId=\"main\",\n quote=<quote from step 2>,\n maxSlippageBps=100,\n skipSimulation=false\n )\n \u2190 Returns: { spokeTxHash, hubTxHash, intentHash, status }\n\nStep 5: Verify Status\n \u2192 amped_swap_status(txHash=spokeTxHash)\n \u2190 Returns: { status, confirmations, filledAmount, remainingAmount }\n\nStep 6: Handle Failures (if needed)\n \u2192 amped_swap_cancel(walletId=\"main\", intent=<intent>, srcChainId=\"ethereum\")\n```\n\n### Bridge Workflow\n\nComplete workflow for bridging tokens between chains:\n\n```\nStep 1: Discover Routes\n \u2192 amped_bridge_discover(\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"\n )\n \u2190 Returns: { bridgeableTokens: [...] }\n\nStep 2: Get Bridge Quote\n \u2192 amped_bridge_quote(\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\"\n )\n \u2190 Returns: { isBridgeable: true, maxBridgeableAmount: \"1000000\" }\n\nStep 3: Review Limits\n \u2713 Verify isBridgeable === true\n \u2713 Check amount \u2264 maxBridgeableAmount\n \u2713 Confirm amount within policy limits\n\nStep 4: Execute Bridge\n \u2192 amped_bridge_execute(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\",\n amount=\"5000\",\n recipient=\"0x...\" // optional, defaults to wallet\n )\n \u2190 Returns: { spokeTxHash, hubTxHash }\n```\n\n### Money Market Supply Workflow\n\nComplete workflow for supplying to and monitoring money market positions:\n\n```\nStep 1: View Available Markets\n \u2192 amped_money_market_reserves(chainId=\"sonic\")\n \u2190 Returns: { reserves: [\n { token: \"USDC\", supplyAPY: \"4.5%\", totalSupplied: \"...\" },\n { token: \"WETH\", supplyAPY: \"2.1%\", totalSupplied: \"...\" }\n ]}\n\nStep 2: Check Current Positions (RECOMMENDED: use cross-chain view)\n \u2192 amped_cross_chain_positions(walletId=\"main\")\n \u2190 Returns: { \n summary: {\n totalSupplyUsd: \"15000.00\",\n totalBorrowUsd: \"5000.00\",\n netWorthUsd: \"10000.00\",\n availableBorrowUsd: \"7000.00\",\n healthFactor: \"2.55\",\n healthFactorStatus: { status: \"healthy\", color: \"green\" },\n liquidationRisk: \"none\",\n weightedSupplyApy: \"4.25%\",\n weightedBorrowApy: \"3.50%\",\n netApy: \"1.08%\"\n },\n chainBreakdown: [...],\n collateralUtilization: {...},\n riskMetrics: {...},\n positions: [...],\n recommendations: [\"\ud83d\udca1 You have $7000.00 in available borrowing power.\"]\n }\n\nStep 3: Supply Tokens\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\",\n amount=\"1000\",\n useAsCollateral=true // Use as collateral for borrowing\n )\n \u2190 Returns: { txHash, spokeTxHash, hubTxHash }\n\nStep 4: Verify Position Update (cross-chain view)\n \u2192 amped_cross_chain_positions(walletId=\"main\")\n \u2190 Returns: Updated positions reflecting the new supply across all chains\n```\n\n### Cross-Chain Positions View (Recommended)\n\nThe `amped_cross_chain_positions` tool provides a **unified portfolio view** across all chains. This is the recommended way to check money market positions.\n\n**What it shows:**\n- **Total Portfolio Summary**: Supply, borrow, net worth across ALL chains\n- **Health Metrics**: Health factor with status indicator, liquidation risk level\n- **Borrowing Power**: Available borrow amount based on collateral\n- **Yield Metrics**: Weighted supply/borrow APY, net APY\n- **Chain Breakdown**: Per-chain position summaries\n- **Collateral Utilization**: How much of your collateral is being used\n- **Risk Metrics**: Current LTV, buffer until liquidation, safe max borrow\n- **Personalized Recommendations**: AI-generated suggestions based on your position\n\n**Example Response:**\n```json\n{\n \"success\": true,\n \"walletId\": \"main\",\n \"address\": \"0x...\",\n \"timestamp\": \"2026-02-02T12:58:27.999Z\",\n \"summary\": {\n \"totalSupplyUsd\": \"25000.00\",\n \"totalBorrowUsd\": \"8000.00\",\n \"netWorthUsd\": \"17000.00\",\n \"availableBorrowUsd\": \"12000.00\",\n \"healthFactor\": \"2.65\",\n \"healthFactorStatus\": { \"status\": \"healthy\", \"color\": \"green\" },\n \"liquidationRisk\": \"none\",\n \"weightedSupplyApy\": \"4.52%\",\n \"weightedBorrowApy\": \"3.21%\",\n \"netApy\": \"2.89%\"\n },\n \"chainBreakdown\": [\n { \"chainId\": \"ethereum\", \"supplyUsd\": \"15000.00\", \"borrowUsd\": \"5000.00\", \"healthFactor\": \"2.80\" },\n { \"chainId\": \"arbitrum\", \"supplyUsd\": \"5000.00\", \"borrowUsd\": \"2000.00\", \"healthFactor\": \"2.50\" },\n { \"chainId\": \"sonic\", \"supplyUsd\": \"5000.00\", \"borrowUsd\": \"1000.00\", \"healthFactor\": \"5.00\" }\n ],\n \"collateralUtilization\": {\n \"totalCollateralUsd\": \"20000.00\",\n \"usedCollateralUsd\": \"8000.00\",\n \"availableCollateralUsd\": \"12000.00\",\n \"utilizationRate\": \"40.00%\"\n },\n \"riskMetrics\": {\n \"maxLtv\": \"80.00%\",\n \"currentLtv\": \"32.00%\",\n \"bufferUntilLiquidation\": \"53.00%\",\n \"safeMaxBorrowUsd\": \"13600.00\"\n },\n \"recommendations\": [\n \"\ud83d\udca1 You have $12000.00 in available borrowing power.\",\n \"\ud83c\udf10 You have positions across 3 chains. Monitor each chain's health factor independently.\"\n ]\n}\n```\n\n**When to use:**\n- Always start here to get a complete picture of money market positions\n- Before any borrow/withdraw operation to check health factor\n- To monitor portfolio performance across all chains\n- To identify opportunities (available borrowing power, low utilization)\n\n### Cross-Chain Money Market Borrow Workflow (Advanced)\n\n**Key Feature:** Borrow tokens to a different chain than where your collateral is supplied!\n\n```\nScenario: Supply USDC on Ethereum, borrow USDT to Arbitrum\n\nStep 1: Verify Collateral Position on Source Chain\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2190 Returns: { positions: [...], totalCollateralUSD, availableBorrowUSD, healthFactor }\n\nStep 2: Check Borrow Capacity\n \u2713 Verify availableBorrowUSD > desired borrow amount\n \u2713 Check healthFactor will remain safe after borrow\n\nStep 3: Cross-Chain Borrow\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Source chain (where collateral is)\n dstChainId=\"arbitrum\", // Destination chain (where you receive borrowed tokens)\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT on Arbitrum\n amount=\"500\",\n interestRateMode=2 // Variable rate\n )\n \u2190 Returns: { \n txHash, \n spokeTxHash, // On Ethereum (source)\n hubTxHash, \n dstSpokeTxHash, // On Arbitrum (destination)\n isCrossChain: true \n }\n\nStep 4: Verify Position\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2190 Returns: Updated positions with new borrow recorded\n\nStep 5: Verify Received Tokens on Destination Chain\n \u2192 amped_wallet_address(walletId=\"main\")\n \u2190 Check USDT balance on Arbitrum via external means or position query\n```\n\n### Cross-Chain Money Market Supply Workflow\n\n```\nScenario: Supply tokens on Arbitrum, collateral recorded on Sonic\n\nStep 1: Supply with Cross-Chain Flag\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"arbitrum\", // Source chain (where tokens are)\n dstChainId=\"sonic\", // Destination chain (where collateral is recorded)\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\",\n amount=\"1000\",\n useAsCollateral=true\n )\n \u2190 Returns: {\n txHash,\n isCrossChain: true,\n message: \"Tokens supplied on arbitrum. Collateral available on sonic.\"\n }\n\nStep 2: Verify on Destination Chain\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Collateral should appear on Sonic\n```\n\n### Money Market Repay Workflow\n\nComplete workflow for repaying borrowed tokens:\n\n```\nStep 1: Check Borrow Position\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: { positions: [...], totalBorrowUSD, healthFactor }\n\nStep 2: Repay (Full or Partial)\n Option A - Partial Repay:\n \u2192 amped_mm_repay(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"500\"\n )\n \n Option B - Full Repay:\n \u2192 amped_mm_repay(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"-1\", // Special value for max\n repayAll=true\n )\n\nStep 3: Verify Repayment\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Updated positions with reduced borrow, improved healthFactor\n```\n\n### Money Market Withdraw Workflow\n\nComplete workflow for withdrawing supplied tokens:\n\n```\nStep 1: Check Position and Available Liquidity\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Verify: withdrawal won't cause healthFactor to drop below safe level\n \u2190 Verify: sufficient available liquidity in reserve\n\nStep 2: Withdraw\n \u2192 amped_mm_withdraw(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"500\",\n withdrawType=\"default\" // Options: default, collateral, all\n )\n \u2190 Returns: { txHash, spokeTxHash, hubTxHash }\n\nStep 3: Verify Withdrawal\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Updated positions with reduced supply\n```\n\n## Cross-Chain Money Market Examples\n\n### Example 1: Supply on Ethereum, Borrow to Base\n\n```\nUser: \"I have USDC on Ethereum. I want to borrow USDC on Base without moving my collateral.\"\n\nAgent actions:\n1. Check positions on Ethereum\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n\n2. Supply USDC on Ethereum\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"ethereum\",\n token=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n amount=\"10000\",\n useAsCollateral=true\n )\n\n3. Cross-chain borrow to Base\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Collateral is here\n dstChainId=\"base\", // Receive borrowed tokens here\n token=\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\", // USDC on Base\n amount=\"5000\",\n interestRateMode=2\n )\n\n4. Verify positions\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"base\")\n```\n\n### Example 2: Cross-Chain Withdraw\n\n```\nUser: \"I have collateral on Sonic but I want to withdraw to Arbitrum.\"\n\nAgent actions:\n\u2192 amped_mm_withdraw(\n walletId=\"main\",\n chainId=\"sonic\", // Collateral source\n dstChainId=\"arbitrum\", // Token destination\n token=\"0x...\",\n amount=\"1000\"\n )\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `AMPED_OC_MODE` | Operation mode: `'execute'` (agent signs) or `'prepare'` (returns unsigned txs) | `execute` |\n| `AMPED_OC_WALLETS_JSON` | JSON map of wallet configurations keyed by walletId | `{}` |\n| `AMPED_OC_RPC_URLS_JSON` | JSON map of RPC URLs by chainId | `{}` |\n| `AMPED_OC_LIMITS_JSON` | Policy limits configuration | `{}` |\n| `AMPED_OC_SODAX_DYNAMIC_CONFIG` | Enable dynamic config via `sodax.initialize()` | `false` |\n\n### Wallet Configuration (`AMPED_OC_WALLETS_JSON`)\n\n```json\n{\n \"main\": {\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\" // Required for execute mode\n },\n \"trading\": {\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\"\n }\n}\n```\n\n**Security:** Private keys are never logged. In prepare mode, only address is required.\n\n### Policy Limits (`AMPED_OC_LIMITS_JSON`)\n\n```json\n{\n \"maxSwapInputUsd\": 10000,\n \"maxBridgeAmountToken\": {\n \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\": 50000\n },\n \"maxBorrowUsd\": 5000,\n \"maxSlippageBps\": 100,\n \"allowedChains\": [\"ethereum\", \"arbitrum\", \"sonic\", \"base\"],\n \"allowedTokensByChain\": {\n \"ethereum\": [\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", \"0x...\"]\n },\n \"blockedRecipients\": [\"0x...\"]\n}\n```\n\n### RPC Configuration (`AMPED_OC_RPC_URLS_JSON`)\n\n```json\n{\n \"ethereum\": \"https://eth-mainnet.g.alchemy.com/v2/...\",\n \"arbitrum\": \"https://arb-mainnet.g.alchemy.com/v2/...\",\n \"base\": \"https://base-mainnet.g.alchemy.com/v2/...\",\n \"sonic\": \"https://rpc.soniclabs.com\"\n}\n```\n\n## Error Handling\n\n### Policy Violations\n\nPolicy violations return structured errors with:\n- `code`: Error code (e.g., `POLICY_SLIPPAGE_EXCEEDED`)\n- `message`: Human-readable description\n- `remediation`: Suggested action to resolve\n- `current`: Current value that violated policy\n- `limit`: Configured limit\n\n### Common Error Codes\n\n| Code | Description | Remediation |\n|------|-------------|-------------|\n| `POLICY_SLIPPAGE_EXCEEDED` | Quote slippage exceeds maxSlippageBps | Increase maxSlippageBps or wait for better conditions |\n| `POLICY_SPEND_LIMIT_EXCEEDED` | Amount exceeds per-transaction or daily limit | Reduce amount or request limit increase |\n| `POLICY_CHAIN_NOT_ALLOWED` | Chain not in allowedChains | Add chain to allowedChains or use different chain |\n| `POLICY_TOKEN_NOT_ALLOWED` | Token not in allowedTokensByChain | Add token to allowlist or use different token |\n| `INSUFFICIENT_BALANCE` | Wallet balance < requested amount | Reduce amount or fund wallet |\n| `INSUFFICIENT_ALLOWANCE` | Token allowance < requested amount | Tool will auto-approve, or approve manually |\n| `QUOTE_EXPIRED` | Quote deadline has passed | Get fresh quote |\n| `BRIDGE_NOT_AVAILABLE` | Token pair not bridgeable | Use swap for different tokens or different route |\n| `MM_HEALTH_FACTOR_LOW` | Operation would cause liquidation risk | Repay debt or add collateral first |\n| `MM_CROSS_CHAIN_NOT_SUPPORTED` | Cross-chain operation not supported for this pair | Use same-chain operation or different token/chain |\n\n## Idempotency and Retries\n\n### Client Operation ID\n\nExecute tools accept an optional `clientOperationId` parameter for idempotency:\n- Duplicate operations with the same ID within the cache window return the cached result\n- Prevents duplicate broadcasts on retries\n- Recommended for automated workflows\n\n### Retry Guidance\n\n- **Read operations** (quotes, status, positions): Safe to retry with exponential backoff\n- **Write operations** (execute, supply, borrow): Use `clientOperationId` to prevent duplicates\n- **Timeout handling**: Bridge and money market operations specify timeouts; respect SDK defaults\n\n## Security Model\n\n### Key Segregation\n\n- Each agent workspace has distinct wallet configurations\n- Spoke providers are cached per `walletId` and never shared across agents\n- Private keys are resolved by `walletId` only; never passed as parameters\n\n### Execution vs Prepare Mode\n\n| Mode | Signing | Use Case |\n|------|---------|----------|\n| `execute` | Agent signs with private key | Automated agents, server-side operations |\n| `prepare` | Returns unsigned tx for external signing | Hardware wallets, air-gapped signing, multi-sig |\n\n### Logging\n\nStructured logs include:\n- `requestId`: Unique request identifier\n- `agentId`: Agent identifier (if available)\n- `walletId`: Wallet identifier\n- `opType`: Operation type (swap, bridge, supply, etc.)\n- `chainIds`, `tokenAddresses`: Operation context\n- `txHashes`: Transaction hashes (for tracing)\n\n**Never logged:** Private keys, full wallet JSON, sensitive configuration\n\n## Chain-Specific Notes\n\n### Sonic Hub Chain\n\n- Sonic is the **hub chain** for SODAX operations\n- Uses `SonicSpokeProvider` instead of `EvmSpokeProvider`\n- Special handling required for hub chain operations\n- Money market operations are hub-centric but support cross-chain interactions\n\n### EVM Spoke Chains\n\n- Use `EvmSpokeProvider` for standard EVM chains (Ethereum, Arbitrum, Base, etc.)\n- Standard allowance/approval flow applies\n- Bridge operations go: Spoke \u2192 Hub \u2192 Destination Spoke\n- Cross-chain money market operations leverage the hub for state management\n\n## Cross-Chain Money Market Architecture\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 SODAX Money Market Flow \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502\n\u2502 Cross-Chain Borrow Example: \u2502\n\u2502 Supply USDC on Ethereum \u2192 Borrow USDT on Arbitrum \u2502\n\u2502 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 Ethereum \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Sonic \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Arbitrum \u2502 \u2502\n\u2502 \u2502 (Supply) \u2502 \u2502 (Hub) \u2502 \u2502 (Borrow) \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2502 1. Supply USDC \u2502 \u2502 \u2502\n\u2502 \u2502 2. Record collateral\u2502 \u2502 \u2502\n\u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502 \u2502\n\u2502 \u2502 \u2502 3. Verify collateral\u2502 \u2502\n\u2502 \u2502 \u2502 4. Process borrow \u2502 \u2502\n\u2502 \u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502\n\u2502 \u2502 \u2502 \u2502 5. Deliver\u2502\n\u2502 \u2502 \u2502 \u2502 USDT \u2502\n\u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2502\n\u2502 Key Insight: Your collateral stays on the source chain, \u2502\n\u2502 but you receive borrowed tokens on the destination chain! \u2502\n\u2502 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n## Examples\n\n### Example 1: Simple Same-Chain Swap\n\n```\nUser: \"Swap 100 USDC for ETH on Ethereum\"\n\nAgent actions:\n1. amped_swap_quote(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"ethereum\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\",\n amount=\"100\",\n type=\"exact_input\",\n slippageBps=100\n )\n2. Review quote (slippage 0.8%, expected output 0.042 ETH)\n3. amped_swap_execute(walletId=\"main\", quote=<quote>, maxSlippageBps=100)\n4. amped_swap_status(txHash=<spokeTxHash>)\n```\n\n### Example 2: Cross-Chain Bridge\n\n```\nUser: \"Bridge 1000 USDC from Ethereum to Sonic\"\n\nAgent actions:\n1. amped_bridge_discover(srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\")\n2. amped_bridge_quote(srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\")\n3. amped_bridge_execute(walletId=\"main\", srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\", amount=\"1000\")\n```\n\n### Example 3: Supply and Borrow Loop (Same Chain)\n\n```\nUser: \"Supply 5000 USDC and borrow 2000 USDT on Sonic\"\n\nAgent actions:\n1. amped_money_market_reserves(chainId=\"sonic\")\n2. amped_mm_supply(walletId=\"main\", chainId=\"sonic\", token=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\", amount=\"5000\")\n3. amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n4. amped_mm_borrow(walletId=\"main\", chainId=\"sonic\", token=\"0x...usdt...\", amount=\"2000\")\n5. amped_money_market_positions(walletId=\"main\", chainId=\"sonic\") // Verify new health factor\n```\n\n### Example 4: Cross-Chain Money Market (Advanced)\n\n```\nUser: \"I want to use my USDC on Ethereum as collateral to borrow USDC on Arbitrum for a trading opportunity\"\n\nAgent actions:\n1. Verify supported chains and tokens\n \u2192 amped_supported_tokens(module=\"moneyMarket\", chainId=\"ethereum\")\n \u2192 amped_supported_tokens(module=\"moneyMarket\", chainId=\"arbitrum\")\n\n2. Check current positions\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"arbitrum\")\n\n3. Supply USDC on Ethereum\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"ethereum\",\n token=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n amount=\"50000\",\n useAsCollateral=true\n )\n\n4. Cross-chain borrow to Arbitrum\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Source: collateral is here\n dstChainId=\"arbitrum\", // Destination: receive tokens here\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDC on Arbitrum\n amount=\"20000\",\n interestRateMode=2\n )\n\n5. Verify the cross-chain borrow worked\n \u2192 Check positions on Ethereum (collateral + debt recorded)\n \u2192 Check USDC balance on Arbitrum (borrowed tokens received)\n\nResult: User has 20k USDC on Arbitrum to trade with, while their 50k USDC collateral remains on Ethereum!\n```\n\n## Transaction Execution Architecture\n\n### SODAX-First Routing (Mandatory)\n\n**Critical:** ALL DeFi operations MUST route through the SODAX SDK first. External wallet backends (like Bankr) are used ONLY for transaction execution\u2014never for routing decisions.\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 SODAX SDK (Routing Layer) \u2502\n\u2502 \u2713 Calculates optimal swap routes \u2502\n\u2502 \u2713 Determines bridge paths \u2502\n\u2502 \u2713 Manages money market intents \u2502\n\u2502 \u2713 Handles slippage, fees, deadlines \u2502\n\u2502 \u2713 Prepares transaction data (to, data, value) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25bc raw transaction data\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Wallet Backend (Execution Layer ONLY) \u2502\n\u2502 \u2713 Signs the pre-computed transaction \u2502\n\u2502 \u2713 Submits to blockchain \u2502\n\u2502 \u2713 Returns transaction hash \u2502\n\u2502 \u2717 NO routing decisions \u2502\n\u2502 \u2717 NO token swapping logic \u2502\n\u2502 \u2717 NO DeFi protocol selection \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### Supported Backends\n\n| Backend | Description | Use Case |\n|---------|-------------|----------|\n| `localKey` | Direct signing via private key from `~/.evm-wallet.json` or config | Default, self-custody |\n| `bankr` | Bankr API for transaction submission | Managed wallets via Bankr |\n\n### Backend Selection\n\nThe wallet backend is selected via:\n1. `config.json` \u2192 `walletBackend: \"bankr\" | \"localKey\"`\n2. Environment: `AMPED_OC_WALLET_BACKEND`\n3. Default: `localKey`\n\n### Bankr Integration\n\nWhen `walletBackend: \"bankr\"` is configured:\n\n1. **SODAX SDK prepares the transaction** - All routing, calculation, and intent creation happens in SODAX\n2. **Transaction data is passed to Bankr** - Only the raw `{to, data, value, chainId}` is sent\n3. **Bankr signs and submits** - Bankr executes exactly what SODAX prepared\n4. **No Bankr routing** - Bankr does NOT interpret or re-route the transaction\n\nThis ensures:\n- Consistent behavior across all backends\n- SODAX optimizations always apply\n- Audit trail shows SODAX as routing authority\n- Backend is a pure execution layer\n\n### Configuration Example (Bankr)\n\n```json\n// ~/.openclaw/extensions/amped-defi/config.json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"bk_...\",\n \"bankrApiUrl\": \"https://api.bankr.bot\",\n \"bankrWalletAddress\": \"0x...\"\n}\n```\n\n**Security Note:** The Bankr API key is stored locally and never exposed in tool parameters or logs.\n",
49 "arguments": [],
50 "icons": null,
51 "meta": null
52 },
53 {
54 "name": "README.md",
55 "title": null,
56 "description": "# Amped DeFi Plugin\n\n[![npm version](https://img.shields.io/npm/v/amped-defi.svg)](https://www.npmjs.com/package/amped-defi)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)\n\nDeFi operations plugin for [OpenClaw](https://openclaw.ai) enabling seamless cross-chain swaps, bridging, and money market operations via the [SODAX SDK](https://docs.sodax.com).\n\n## Features\n\n- **\ud83d\udd01 Cross-Chain Swaps** - Execute token swaps across multiple chains via SODAX's intent-based solver network\n- **\ud83c\udf09 Token Bridging** - Bridge assets between spoke chains and the Sonic hub chain\n- **\ud83c\udfe6 Cross-Chain Money Market** - Supply on Chain A, borrow to Chain B - your collateral stays put!\n- **\ud83d\udcca Unified Portfolio View** - Cross-chain position aggregator with health metrics, risk analysis & recommendations\n- **\ud83d\udcdc Intent History** - Query complete swap/bridge history via SODAX API\n- **\ud83d\udd10 Security First** - Policy engine with spend limits, slippage caps, allowlists\n- **\u26a1 Dual Mode** - Execute mode (agent signs) or prepare mode (unsigned txs for external signing)\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Quick Start](#quick-start)\n- [Available Tools](#available-tools)\n- [Usage Examples](#usage-examples)\n- [Cross-Chain Money Market](#cross-chain-money-market)\n- [API Integration](#api-integration)\n- [Error Handling](#error-handling)\n- [Testing](#testing)\n- [Project Structure](#project-structure)\n- [License](#license)\n\n## Installation\n\n### Prerequisites\n\n- Node.js >= 18.0.0\n- [OpenClaw](https://openclaw.ai) installed and configured\n- **[evm-wallet-skill](https://github.com/surfer77/evm-wallet-skill)** (recommended) - For wallet and RPC configuration\n\n### Quick Install\n\n```bash\nopenclaw plugins install amped-defi\n```\n\nVerify with `openclaw plugins list`.\n\n### Install from Source\n\nIf you prefer installing from source:\n\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n#### 4. Verify Installation\n\n```bash\n# List loaded plugins\nopenclaw plugins list\n\n# Check for amped tools (should see 24 tools)\nopenclaw tools list | grep amped_\n```\n\nYou should see tools like:\n- `amped_supported_chains`\n- `amped_swap_quote`\n- `amped_mm_supply`\n- `amped_cross_chain_positions`\n- etc.\n\n### Updating the Plugin\n\n```bash\nopenclaw plugins uninstall amped-defi\nopenclaw plugins install amped-defi\n```\n\n### Uninstalling\n\n```bash\nopenclaw plugins uninstall amped-defi\n```\n\n### Troubleshooting Installation\n\n**\"Cannot find module 'viem'\" or similar errors:**\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n**\"plugin not found\" after uninstall:**\nEdit `~/.openclaw/openclaw.json` and remove the `amped-defi` entry from `plugins.entries`.\n\n**Plugin not loading:**\nCheck OpenClaw logs for errors:\n```bash\ntail -f ~/.openclaw/logs/openclaw.log\n```\n\n## Configuration\n\n### Wallet Setup (Optional)\n\nThe plugin works without a wallet for **read-only operations** (quotes, balances, discovery). To execute transactions, configure a wallet using one of the options below.\n\n> **No wallet configured?** The agent will prompt you to install [evm-wallet-skill](https://github.com/amped-finance/evm-wallet-skill) when you try to execute a transaction.\n\n### \ud83d\udd0c evm-wallet-skill Integration (Recommended)\n\nInstall [evm-wallet-skill](https://github.com/amped-finance/evm-wallet-skill) for self-sovereign wallet management:\n\n```bash\ngit clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\ncd ~/.openclaw/skills/evm-wallet-skill && npm install\nnode src/setup.js # Generate a new wallet\n```\n\nThe plugin automatically detects wallets from `~/.evm-wallet.json`.\n\n**Supported chains:** Ethereum, Base, Arbitrum, Optimism, Polygon, Sonic, LightLink, HyperEVM, Avalanche, BSC, MegaETH, and more.\n\n**Add custom chains via natural language:**\n> \"Add Berachain with chain ID 80094 and RPC https://rpc.berachain.com\"\n\nOr directly:\n```bash\nnode src/add-chain.js berachain 80094 https://rpc.berachain.com --native-token BERA\n```\n\nThe plugin will automatically detect and use:\n- `EVM_WALLETS_JSON` or `WALLET_CONFIG_JSON`\n- `EVM_RPC_URLS_JSON` or `RPC_URLS_JSON`\n\n### \ud83e\udd16 Bankr Integration\n\nFor users with [Bankr](https://bankr.bot) wallets, the plugin supports the Bankr Agent API for transaction execution. This allows agents to execute transactions through Bankr's managed infrastructure.\n\n**Option 1: Environment Variable**\n```bash\nexport BANKR_API_KEY=your-bankr-api-key\n```\n\n**Option 2: Config File**\n\nCreate `~/.openclaw/extensions/amped-defi/config.json`:\n```json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"your-bankr-api-key\"\n}\n```\n\n> \u26a0\ufe0f **Important:** Your Bankr API key must have **\"Agent API\" access enabled** in your Bankr dashboard. A standard bot key won't work.\n\nWhen configured, the plugin automatically uses Bankr for transaction execution instead of local key signing.\n\n### Manual Configuration\n\nIf you're not using evm-wallet-skill, set these environment variables:\n\n#### Required\n\n```bash\n# Wallet configuration (JSON map of walletId -> {address, privateKey})\nexport AMPED_OC_WALLETS_JSON='{\n \"main\": {\n \"address\": \"0xYourWalletAddress\",\n \"privateKey\": \"0xYourPrivateKey\"\n },\n \"trading\": {\n \"address\": \"0xAnotherAddress\",\n \"privateKey\": \"0xAnotherPrivateKey\"\n }\n}'\n\n# RPC URLs for all supported chains\nexport AMPED_OC_RPC_URLS_JSON='{\n \"ethereum\": \"https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"arbitrum\": \"https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"base\": \"https://base-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"sonic\": \"https://rpc.soniclabs.com\"\n}'\n```\n\n#### Optional\n\n```bash\n# Operation mode: \"execute\" (default) or \"prepare\"\nexport AMPED_OC_MODE=execute\n\n# Enable dynamic SODAX config (fetches from API)\nexport AMPED_OC_SODAX_DYNAMIC_CONFIG=true\n\n# Policy limits (JSON)\nexport AMPED_OC_LIMITS_JSON='{\n \"default\": {\n \"maxSlippageBps\": 100,\n \"maxSwapInputUsd\": 10000,\n \"maxBorrowUsd\": 50000,\n \"allowedChains\": [\"ethereum\", \"arbitrum\", \"base\", \"sonic\"],\n \"allowedTokensByChain\": {\n \"ethereum\": [\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"]\n }\n }\n}'\n\n# SODAX API configuration\nexport SODAX_API_URL=https://canary-api.sodax.com # or https://api.sodax.com\nexport SODAX_API_KEY=your-api-key # if required\n```\n\n### \ud83c\udfe6 Bankr Integration (Alternative Wallet Backend)\n\nInstead of using local private keys, you can use [Bankr](https://bankr.bot) as your wallet backend. Bankr manages keys securely and executes transactions on your behalf via their AI Agent API.\n\n#### Why Bankr?\n\n- **No private key exposure** - Keys stay with Bankr, never in your config\n- **Multi-chain support** - Base, Ethereum, Polygon, Solana, and more\n- **Built-in trading features** - DCA, limit orders, stop-loss\n- **Social trading** - Send to ENS, Twitter handles, Farcaster\n\n#### Setup\n\n1. **Create a Bankr account** at [bankr.bot](https://bankr.bot)\n\n2. **Generate an API key** at [bankr.bot/api](https://bankr.bot/api)\n - Enable **\"Agent API\"** access on the key\n\n3. **Configure the plugin:**\n\n```bash\n# Set your Bankr API key\nexport BANKR_API_KEY=bk_your_api_key_here\n\n# Optionally specify the backend explicitly\nexport AMPED_OC_WALLET_BACKEND=bankr\n```\n\nOr via config file (`~/.openclaw/extensions/amped-defi/config.json`):\n\n```json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"bk_your_api_key_here\",\n \"bankrApiUrl\": \"https://api.bankr.bot\"\n}\n```\n\n#### How It Works\n\nWhen using Bankr backend:\n1. Plugin prepares transaction calldata (approvals, swaps, etc.)\n2. Submits to Bankr Agent API as an execution request\n3. Bankr signs and broadcasts the transaction\n4. Plugin receives transaction hash on completion\n\n#### Important Notes\n\n- **Separate wallet**: Your Bankr wallet address is different from your personal wallets\n- **Check your address**: Run `\"What is my wallet address?\"` in Bankr terminal\n- **Fund the wallet**: Send ETH/gas tokens to your Bankr wallet before trading\n- **Rate limits**: Agent API may have rate limits depending on your plan\n\n#### Bankr vs Local Keys\n\n| Feature | Local Keys | Bankr |\n|---------|-----------|-------|\n| Key storage | Your machine | Bankr servers |\n| Setup | Configure private key | API key only |\n| Security | You manage keys | Bankr manages keys |\n| Execution | Direct RPC calls | Via Bankr API |\n| Speed | Instant | ~2-45 seconds |\n| Features | Basic tx signing | Full trading suite |\n\n\n## Quick Start\n\n```typescript\nimport { activate, deactivate } from 'amped-defi';\n\n// In your OpenClaw agent setup\nasync function setupAgent(agentTools) {\n // Activate the plugin\n await activate(agentTools);\n \n // Plugin is now ready with all tools registered\n // Tools can be called via the agent\n}\n\n// Cleanup when done\nawait deactivate();\n```\n\n## Available Tools\n\n### Wallet Management Tools (5)\n\n| Tool | Description |\n|------|-------------|\n| `amped_list_wallets` | List all configured wallets with nicknames and addresses |\n| `amped_add_wallet` | Add a new wallet with a nickname |\n| `amped_rename_wallet` | Rename an existing wallet |\n| `amped_remove_wallet` | Remove a wallet from configuration |\n| `amped_set_default_wallet` | Set which wallet to use by default |\n\n### Discovery Tools (8)\n\n| Tool | Description |\n|------|-------------|\n| `amped_supported_chains` | List all supported spoke chains |\n| `amped_supported_tokens` | Get supported tokens by module and chain |\n| `amped_wallet_address` | Resolve wallet address by walletId |\n| `amped_money_market_reserves` | View market reserves and liquidity |\n| `amped_money_market_positions` | View positions on a single chain |\n| `amped_cross_chain_positions` | \u2b50 **Unified portfolio view across ALL chains** |\n| `amped_user_intents` | Query intent history via SODAX API |\n\n### Swap Tools (4)\n\n| Tool | Description |\n|------|-------------|\n| `amped_swap_quote` | Get exact-in/exact-out swap quote |\n| `amped_swap_execute` | Execute swap with policy enforcement |\n| `amped_swap_status` | Check swap status by txHash/intentHash |\n| `amped_swap_cancel` | Cancel pending swap intent |\n\n### Bridge Tools (3)\n\n| Tool | Description |\n|------|-------------|\n| `amped_bridge_discover` | Discover bridgeable tokens for a route |\n| `amped_bridge_quote` | Check bridgeability and max amount |\n| `amped_bridge_execute` | Execute bridge operation |\n\n### Money Market Tools (4)\n\n| Tool | Description |\n|------|-------------|\n| `amped_mm_supply` | Supply tokens as collateral |\n| `amped_mm_withdraw` | Withdraw supplied tokens |\n| `amped_mm_borrow` | Borrow tokens (cross-chain capable!) |\n| `amped_mm_repay` | Repay borrowed tokens |\n\n## Wallet Management\n\nManage wallets through natural language or tool calls:\n\n### Natural Language Examples\n\n```\n\"What wallets do I have?\"\n\"Add a wallet called trading with address 0x... and private key 0x...\"\n\"Rename main to savings\"\n\"Make bankr my default wallet\"\n\"Remove the trading wallet\"\n```\n\n### Multiple Wallet Support\n\n| Source | Default Nickname | Supported Chains |\n|--------|-----------------|------------------|\n| evm-wallet-skill | `main` | All SODAX chains |\n| Bankr | `bankr` | Ethereum, Base, Polygon |\n| Environment | Custom | All SODAX chains |\n\n### Using Wallets in Operations\n\nSpecify a wallet nickname in any operation:\n\n```typescript\n// Swap using a specific wallet\nawait agent.call('amped_swap_execute', {\n walletId: 'trading', // Use the \"trading\" wallet\n quote: quoteResult,\n maxSlippageBps: 50\n});\n```\n\nOr in natural language:\n```\n\"Swap 100 USDC to ETH using trading\"\n\"Check balance on bankr\"\n```\n\n### Wallet Config File\n\nConfigurations persist to `~/.openclaw/extensions/amped-defi/wallets.json`:\n\n```json\n{\n \"wallets\": {\n \"trading\": {\n \"source\": \"env\",\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\"\n }\n },\n \"default\": \"main\"\n}\n```\n\n## Usage Examples\n\n### 1. Cross-Chain Position View (Recommended)\n\nGet a complete portfolio overview across all chains:\n\n```typescript\nconst positions = await agentTools.call('amped_cross_chain_positions', {\n walletId: 'main'\n});\n\n// Response:\n{\n summary: {\n totalSupplyUsd: \"25000.00\",\n totalBorrowUsd: \"8000.00\",\n netWorthUsd: \"17000.00\",\n availableBorrowUsd: \"12000.00\",\n healthFactor: \"2.65\",\n healthFactorStatus: { status: \"healthy\", color: \"green\" },\n liquidationRisk: \"none\",\n weightedSupplyApy: \"4.52%\",\n weightedBorrowApy: \"3.21%\",\n netApy: \"2.89%\"\n },\n chainBreakdown: [\n { chainId: \"ethereum\", supplyUsd: \"15000.00\", borrowUsd: \"5000.00\", healthFactor: \"2.80\" },\n { chainId: \"arbitrum\", supplyUsd: \"5000.00\", borrowUsd: \"2000.00\", healthFactor: \"2.50\" },\n { chainId: \"sonic\", supplyUsd: \"5000.00\", borrowUsd: \"1000.00\", healthFactor: \"5.00\" }\n ],\n collateralUtilization: {\n totalCollateralUsd: \"20000.00\",\n usedCollateralUsd: \"8000.00\",\n utilizationRate: \"40.00%\"\n },\n riskMetrics: {\n maxLtv: \"80.00%\",\n currentLtv: \"32.00%\",\n bufferUntilLiquidation: \"53.00%\",\n safeMaxBorrowUsd: \"13600.00\"\n },\n recommendations: [\n \"\ud83d\udca1 You have $12000.00 in available borrowing power.\",\n \"\ud83c\udf10 You have positions across 3 chains.\"\n ]\n}\n```\n\n### 2. Cross-Chain Swap\n\n```typescript\n// Step 1: Get quote\nconst quote = await agentTools.call('amped_swap_quote', {\n walletId: 'main',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDT\n amount: '1000',\n type: 'exact_input',\n slippageBps: 100\n});\n\n// Step 2: Execute\nconst result = await agentTools.call('amped_swap_execute', {\n walletId: 'main',\n quote: quote,\n maxSlippageBps: 100\n});\n\n// Returns: { spokeTxHash, hubTxHash, intentHash, status }\n```\n\n### 3. Query Intent History\n\n```typescript\nconst history = await agentTools.call('amped_user_intents', {\n walletId: 'main',\n status: 'all', // 'all', 'open', or 'closed'\n limit: 50,\n offset: 0\n});\n\n// Returns paginated intent history with event details\n```\n\n## Cross-Chain Money Market\n\nThe plugin's standout feature is **cross-chain money market operations**:\n\n```\nSupply USDC on Ethereum \u2192 Borrow USDT on Arbitrum\n```\n\nYour collateral stays on the source chain, but you receive borrowed tokens on the destination chain.\n\n### Example: Cross-Chain Borrow\n\n```typescript\n// Step 1: Supply on Ethereum\nawait agentTools.call('amped_mm_supply', {\n walletId: 'main',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC\n amount: '50000',\n useAsCollateral: true\n});\n\n// Step 2: Borrow to Arbitrum (different chain!)\nawait agentTools.call('amped_mm_borrow', {\n walletId: 'main',\n chainId: 'ethereum', // Collateral source\n dstChainId: 'arbitrum', // Borrowed tokens destination\n token: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDT\n amount: '20000',\n interestRateMode: 2 // Variable rate\n});\n\n// Result: User has 20k USDT on Arbitrum, 50k USDC collateral remains on Ethereum\n```\n\n## API Integration\n\nThe plugin integrates with the SODAX backend API for enhanced querying:\n\n| Environment | Base URL |\n|-------------|----------|\n| Canary (Pre-release) | `https://canary-api.sodax.com` |\n| Production | `https://api.sodax.com` |\n\n### Features:\n- **Paginated Intent History** - Query all intents with offset/limit\n- **Status Filtering** - Filter by open/closed status\n- **Chain/Token Filtering** - Filter by source/destination chain or token\n- **Event History** - Full event log for each intent (fills, cancels, etc.)\n\n### API Configuration:\n\n```bash\nexport SODAX_API_URL=https://canary-api.sodax.com\nexport SODAX_API_KEY=your-api-key # if required\n```\n\n## Partner Fee Configuration\n\nTo earn partner fees from swaps and bridges, modify the hardcoded values in `src/sodax/client.ts`:\n\n```typescript\n// src/sodax/client.ts\n\n// HARDCODED PARTNER CONFIGURATION\nconst PARTNER_ADDRESS: string | undefined = '0xYourPartnerWalletAddress';\nconst PARTNER_FEE_BPS: number | undefined = 10; // 0.1%\n```\n\nPartner fees are automatically collected from swap and bridge operations and sent to the specified address. These values are hardcoded to ensure consistent fee collection across all plugin instances.\n\n## Error Handling\n\nThe plugin provides structured error codes for better debugging:\n\n```typescript\nimport { ErrorCode, wrapError } from '@amped/openclaw-plugin';\n\ntry {\n await agentTools.call('amped_swap_execute', params);\n} catch (error) {\n const ampedError = wrapError(error);\n \n console.log(ampedError.code); // POLICY_SLIPPAGE_EXCEEDED\n console.log(ampedError.message); // Human-readable message\n console.log(ampedError.remediation); // Suggestion to fix\n}\n```\n\n### Error Categories:\n- **Policy Errors** - Slippage exceeded, spend limits, chain/token restrictions\n- **Wallet Errors** - Not found, invalid address, missing private key\n- **Transaction Errors** - Failed, timeout, rejected, simulation failed\n- **SDK Errors** - Not initialized, configuration errors\n\n## Troubleshooting\n\n### Plugin Not Loading in OpenClaw\n\n**Issue:** Tools not showing up in `openclaw tools list`\n\n**Solutions:**\n1. Ensure dependencies are installed in the extension directory:\n ```bash\n cd ~/.openclaw/extensions/amped-defi\n npm install\n ```\n\n2. Check OpenClaw config has the plugin enabled:\n ```yaml\n plugins:\n entries:\n amped-defi:\n enabled: true\n ```\n\n3. Check OpenClaw logs for errors:\n ```bash\n tail -100 ~/.openclaw/logs/openclaw.log\n ```\n\n### \"Cannot find module\" Errors\n\nThis means dependencies weren't installed in the extension directory:\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n### \"plugin not found\" After Uninstall\n\nEdit `~/.openclaw/openclaw.json` and remove the stale entry:\n```bash\n# Remove the amped-defi entry from plugins.entries\nnano ~/.openclaw/openclaw.json\n```\n\n### Wallet Not Found Errors\n\nEnsure you have either:\n- `EVM_WALLETS_JSON` set (if using evm-wallet-skill)\n- `AMPED_OC_WALLETS_JSON` set (plugin-specific)\n- Or configure via OpenClaw plugin settings\n\n### RPC URL Not Configured\n\nEnsure you have either:\n- `EVM_RPC_URLS_JSON` set (if using evm-wallet-skill) \n- `AMPED_OC_RPC_URLS_JSON` set (plugin-specific)\n- Or configure via OpenClaw plugin settings\n\n## Testing\n\n```bash\n# Install dependencies\nnpm install\n\n# Run tests\nnpm test\n\n# Run tests with coverage\nnpm run test:coverage\n\n# Run tests in watch mode\nnpm run test:watch\n\n# Type check\nnpm run typecheck\n\n# Lint\nnpm run lint\n```\n\n### Test Coverage\n\n- \u2705 Error handling utilities\n- \u2705 Policy engine\n- \u2705 Position aggregator\n- \u2705 Wallet registry\n- \u2705 SODAX API client\n\n## Project Structure\n\n```\nsrc/\n\u251c\u2500\u2500 index.ts # Plugin entry point\n\u251c\u2500\u2500 types.ts # Shared TypeScript types\n\u251c\u2500\u2500 sodax/\n\u2502 \u2514\u2500\u2500 client.ts # SODAX SDK singleton client\n\u251c\u2500\u2500 providers/\n\u2502 \u2514\u2500\u2500 spokeProviderFactory.ts # Evm/Sonic spoke provider factory\n\u251c\u2500\u2500 wallet/\n\u2502 \u2514\u2500\u2500 walletRegistry.ts # Wallet resolution by walletId\n\u251c\u2500\u2500 policy/\n\u2502 \u2514\u2500\u2500 policyEngine.ts # Security policy enforcement\n\u251c\u2500\u2500 tools/\n\u2502 \u251c\u2500\u2500 discovery.ts # Discovery/read tools\n\u2502 \u251c\u2500\u2500 swap.ts # Swap operations\n\u2502 \u251c\u2500\u2500 bridge.ts # Bridge operations\n\u2502 \u2514\u2500\u2500 moneyMarket.ts # Money market operations\n\u251c\u2500\u2500 utils/\n\u2502 \u251c\u2500\u2500 errors.ts # Error handling utilities\n\u2502 \u251c\u2500\u2500 positionAggregator.ts # Cross-chain position aggregation\n\u2502 \u2514\u2500\u2500 sodaxApi.ts # SODAX backend API client\n\u2514\u2500\u2500 __tests__/ # Test suite\n```\n\n## Architecture\n\n### Cross-Chain Money Market Flow\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Ethereum \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Sonic \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Arbitrum \u2502\n\u2502 (Supply) \u2502 \u2502 (Hub) \u2502 \u2502 (Borrow) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502 \u2502\n \u2502 1. Supply USDC \u2502 \u2502\n \u2502 2. Record collateral\u2502 \u2502\n \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502\n \u2502 \u2502 3. Verify collateral\n \u2502 \u2502 4. Process borrow \u2502\n \u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502\n \u2502 \u2502 \u2502 5. Deliver USDT\n```\n\n### Security Model\n\n- **Key Segregation** - Each agent workspace has distinct wallet configurations\n- **Spoke Provider Caching** - Cached per `walletId`, never shared across agents\n- **Policy Enforcement** - Spend limits, slippage caps, chain/token allowlists\n- **Simulation** - Transactions simulated before execution by default\n- **No Key Logging** - Private keys never logged or exposed\n\n## Supported Chains\n\n| Chain | Chain ID | Type |\n|-------|----------|------|\n| Ethereum | `ethereum` | EVM Spoke |\n| Arbitrum | `arbitrum` | EVM Spoke |\n| Base | `base` | EVM Spoke |\n| Optimism | `optimism` | EVM Spoke |\n| Polygon | `polygon` | EVM Spoke |\n| Avalanche | `avalanche` | EVM Spoke |\n| BSC | `bsc` | EVM Spoke |\n| LightLink | `lightlink` | EVM Spoke |\n| HyperEVM | `hyperevm` | EVM Spoke |\n| Kaia | `kaia` | EVM Spoke |\n| Sonic | `sonic` | Hub |\n| Solana | `solana` | Solana (receive only) |\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- **Documentation**: [https://docs.sodax.com](https://docs.sodax.com)\n- **Issues**: [GitHub Issues](https://github.com/amped-finance/amped-openclaw/issues)\n- **Discord**: [Amped Finance Community](https://discord.gg/amped)\n",
57 "arguments": null,
58 "icons": null,
59 "meta": null
60 }
61 ],
62 "resources": [
63 {
64 "name": "openclaw.plugin.json",
65 "title": null,
66 "uri": "skill://openclaw.plugin.json",
67 "description": "{\n \"id\": \"amped-defi\",\n \"kind\": \"tools\",\n \"uiHints\": {\n \"walletsJson\": {\n \"label\": \"Wallets JSON\",\n \"sensitive\": true,\n \"placeholder\": \"{\\\"default\\\": {\\\"privateKey\\\": \\\"0x...\\\"}}\",\n \"help\": \"JSON object mapping wallet IDs to configs. Or use ${AMPED_OC_WALLETS_JSON}\"\n },\n \"rpcUrlsJson\": {\n \"label\": \"RPC URLs JSON\",\n \"placeholder\": \"{\\\"sonic\\\": \\\"https://rpc.sonic.fantom.network\\\"}\",\n \"help\": \"JSON object mapping chain IDs to RPC URLs. Or use ${AMPED_OC_RPC_URLS_JSON}\"\n },\n \"mode\": {\n \"label\": \"Execution Mode\",\n \"help\": \"'execute' (default) = execute transactions, 'simulate' = dry-run only\"\n },\n \"dynamicConfig\": {\n \"label\": \"Dynamic SDK Config\",\n \"help\": \"Fetch fresh configuration from SODAX API on startup\",\n \"advanced\": true\n }\n },\n \"configSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"walletsJson\": { \"type\": \"string\" },\n \"rpcUrlsJson\": { \"type\": \"string\" },\n \"mode\": { \"type\": \"string\", \"enum\": [\"execute\", \"simulate\"] },\n \"dynamicConfig\": { \"type\": \"boolean\" }\n },\n \"required\": []\n }\n}\n",
68 "mimeType": null,
69 "size": null,
70 "icons": null,
71 "annotations": null,
72 "meta": null
73 },
74 {
75 "name": "package.json",
76 "title": null,
77 "uri": "skill://package.json",
78 "description": "{\n \"name\": \"amped-defi\",\n \"version\": \"1.0.0\",\n \"description\": \"Amped DeFi plugin for cross-chain swaps, bridging, and money markets via SODAX SDK\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\",\n \"index.js\",\n \"README.md\",\n \"openclaw.plugin.json\"\n ],\n \"scripts\": {\n \"build\": \"tsc\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src --ext .ts\",\n \"test\": \"jest\",\n \"test:watch\": \"jest --watch\",\n \"test:coverage\": \"jest --coverage\"\n },\n \"openclaw\": {\n \"extensions\": [\n \"./index.js\"\n ]\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/amped-finance/amped-openclaw.git\"\n },\n \"homepage\": \"https://github.com/amped-finance/amped-openclaw#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/amped-finance/amped-openclaw/issues\"\n },\n \"author\": \"Amped Finance\",\n \"dependencies\": {\n \"@sinclair/typebox\": \"^0.32.0\",\n \"@sodax/sdk\": \"1.1.0-beta-rc2\",\n \"@sodax/wallet-sdk-core\": \"1.1.0-beta-rc2\",\n \"@sodax/types\": \"1.1.0-beta-rc2\",\n \"viem\": \"^2.0.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.3.0\",\n \"eslint\": \"^8.0.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.0.0\",\n \"@typescript-eslint/parser\": \"^6.0.0\",\n \"jest\": \"^29.0.0\",\n \"@types/jest\": \"^29.0.0\",\n \"ts-jest\": \"^29.0.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"keywords\": [\n \"openclaw\",\n \"plugin\",\n \"defi\",\n \"sodax\",\n \"bridge\",\n \"swap\",\n \"money-market\",\n \"cross-chain\",\n \"intent\"\n ],\n \"license\": \"MIT\"\n}\n",
79 "mimeType": null,
80 "size": null,
81 "icons": null,
82 "annotations": null,
83 "meta": null
84 },
85 {
86 "name": "_meta.json",
87 "title": null,
88 "uri": "skill://_meta.json",
89 "description": "{\n \"owner\": \"ampedfinance\",\n \"slug\": \"amped-defi-publish\",\n \"displayName\": \"Amped Defi Publish\",\n \"latest\": {\n \"version\": \"1.0.0\",\n \"publishedAt\": 1770262924877,\n \"commit\": \"https://github.com/clawdbot/skills/commit/87437abcc55c98d55f40b83567fa5446b7392d10\"\n },\n \"history\": []\n}\n",
90 "mimeType": null,
91 "size": null,
92 "icons": null,
93 "annotations": null,
94 "meta": null
95 }
96 ],
97 "resource_templates": [],
98 "tools": [
99 {
100 "name": "sodaxApi.js",
101 "title": null,
102 "description": "Script: sodaxApi.js. Code:\n/**\n * SODAX API Client\n *\n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\nimport { ErrorCode, AmpedDefiError } from './errors';\nconst DEFAULT_BASE_URL = 'https://canary-api.sodax.com';\nconst API_VERSION = 'v1';\nexport class SodaxApiClient {\n baseUrl;\n apiKey;\n timeoutMs;\n constructor(config = {}) {\n this.baseUrl = config.baseUrl || process.env.SODAX_API_URL || DEFAULT_BASE_URL;\n this.apiKey = config.apiKey || process.env.SODAX_API_KEY;\n this.timeoutMs = config.timeoutMs || 30000;\n }\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n async getIntentByHash(intentHash) {\n const normalizedHash = intentHash.startsWith('0x') ? intentHash : `0x${intentHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/${normalizedHash}`;\n console.log('[sodaxApi] Fetching intent by hash:', { intentHash: normalizedHash });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch intent by hash: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n async getIntentByTxHash(txHash) {\n const normalizedHash = txHash.startsWith('0x') ? txHash : `0x${txHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/tx/${normalizedHash}`;\n console.log('[sodaxApi] Fetching intent by txHash:', { txHash: normalizedHash });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch intent by txHash: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n async getUserIntents(userAddress, pagination = {}, filters) {\n if (!this.isValidAddress(userAddress)) {\n throw new AmpedDefiError(ErrorCode.WALLET_INVALID_ADDRESS, `Invalid user address: ${userAddress}`);\n }\n const normalizedAddress = userAddress.toLowerCase();\n const queryParams = new URLSearchParams();\n if (pagination.offset !== undefined) {\n queryParams.set('offset', pagination.offset.toString());\n }\n if (pagination.limit !== undefined) {\n queryParams.set('limit', pagination.limit.toString());\n }\n if (filters) {\n if (filters.open !== undefined)\n queryParams.set('open', filters.open.toString());\n if (filters.srcChain !== undefined)\n queryParams.set('srcChain', filters.srcChain.toString());\n if (filters.dstChain !== undefined)\n queryParams.set('dstChain', filters.dstChain.toString());\n if (filters.inputToken)\n queryParams.set('inputToken', filters.inputToken.toLowerCase());\n if (filters.outputToken)\n queryParams.set('outputToken', filters.outputToken.toLowerCase());\n }\n const queryString = queryParams.toString();\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/user/${normalizedAddress}${queryString ? `?${queryString}` : ''}`;\n console.log('[sodaxApi] Fetching user intents:', { userAddress: normalizedAddress });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch user intents: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n async getOpenIntents(userAddress, pagination = {}) {\n return this.getUserIntents(userAddress, pagination, { open: true });\n }\n async getIntentHistory(userAddress, pagination = {}) {\n return this.getUserIntents(userAddress, pagination, { open: false });\n }\n async fetchWithTimeout(url) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const headers = { 'Accept': 'application/json' };\n if (this.apiKey)\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n return await fetch(url, { signal: controller.signal, headers });\n }\n finally {\n clearTimeout(timeoutId);\n }\n }\n isValidAddress(address) {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n }\n}\nlet apiClient = null;\nexport function getSodaxApiClient(config) {\n if (!apiClient) {\n apiClient = new SodaxApiClient(config);\n }\n return apiClient;\n}\nexport function resetSodaxApiClient() {\n apiClient = null;\n}\n//# sourceMappingURL=sodaxApi.js.map",
103 "inputSchema": {},
104 "outputSchema": null,
105 "icons": null,
106 "annotations": null,
107 "meta": null,
108 "execution": null
109 },
110 {
111 "name": "positionAggregator.d.ts",
112 "title": null,
113 "description": "Script: positionAggregator.d.ts. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n *\n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\n/**\n * Position data for a single token on a single chain\n */\nexport interface TokenPosition {\n chainId: string;\n token: {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n };\n supply: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n isCollateral: boolean;\n };\n borrow: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n };\n loanToValue: number;\n liquidationThreshold: number;\n}\n/**\n * Aggregated position summary across all chains\n */\nexport interface AggregatedPositionSummary {\n totalSupplyUsd: number;\n totalBorrowUsd: number;\n netWorthUsd: number;\n availableBorrowUsd: number;\n healthFactor: number | null;\n liquidationRisk: 'none' | 'low' | 'medium' | 'high';\n weightedSupplyApy: number;\n weightedBorrowApy: number;\n netApy: number;\n}\n/**\n * Chain-specific position summary\n */\nexport interface ChainPositionSummary {\n chainId: string;\n supplyUsd: number;\n borrowUsd: number;\n netWorthUsd: number;\n healthFactor: number | null;\n positionCount: number;\n}\n/**\n * Complete cross-chain position view\n */\nexport interface CrossChainPositionView {\n walletId: string;\n address: string;\n timestamp: string;\n summary: AggregatedPositionSummary;\n chainSummaries: ChainPositionSummary[];\n positions: TokenPosition[];\n collateralUtilization: {\n totalCollateralUsd: number;\n usedCollateralUsd: number;\n availableCollateralUsd: number;\n utilizationRate: number;\n };\n riskMetrics: {\n maxLtv: number;\n currentLtv: number;\n bufferUntilLiquidation: number;\n safeMaxBorrowUsd: number;\n };\n}\n/**\n * Options for position aggregation\n */\nexport interface AggregationOptions {\n /** Specific chains to query (defaults to all supported chains) */\n chainIds?: string[];\n /** Include zero-balance positions */\n includeZeroBalances?: boolean;\n /** Minimum USD value to include (positions below this are filtered out unless includeZeroBalances is true) */\n minUsdValue?: number;\n}\n/**\n * Aggregate money market positions across all supported chains\n *\n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport declare function aggregateCrossChainPositions(walletId: string, options?: AggregationOptions): Promise<CrossChainPositionView>;\n/**\n * Format health factor for display\n */\nexport declare function formatHealthFactor(hf: number | null): string;\n/**\n * Get health factor color/styling indicator\n */\nexport declare function getHealthFactorStatus(hf: number | null): {\n status: 'healthy' | 'caution' | 'danger' | 'critical';\n color: 'green' | 'yellow' | 'orange' | 'red';\n};\n/**\n * Get recommendation based on position health\n */\nexport declare function getPositionRecommendation(view: CrossChainPositionView): string[];\n//# sourceMappingURL=positionAggregator.d.ts.map",
114 "inputSchema": {},
115 "outputSchema": null,
116 "icons": null,
117 "annotations": null,
118 "meta": null,
119 "execution": null
120 },
121 {
122 "name": "errors.d.ts",
123 "title": null,
124 "description": "Script: errors.d.ts. Code:\n/**\n * Error Handling Utilities\n *\n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport declare enum ErrorCode {\n POLICY_SLIPPAGE_EXCEEDED = \"POLICY_SLIPPAGE_EXCEEDED\",\n POLICY_SPEND_LIMIT_EXCEEDED = \"POLICY_SPEND_LIMIT_EXCEEDED\",\n POLICY_CHAIN_NOT_ALLOWED = \"POLICY_CHAIN_NOT_ALLOWED\",\n POLICY_TOKEN_NOT_ALLOWED = \"POLICY_TOKEN_NOT_ALLOWED\",\n POLICY_RECIPIENT_BLOCKED = \"POLICY_RECIPIENT_BLOCKED\",\n WALLET_NOT_FOUND = \"WALLET_NOT_FOUND\",\n WALLET_INVALID_ADDRESS = \"WALLET_INVALID_ADDRESS\",\n WALLET_MISSING_PRIVATE_KEY = \"WALLET_MISSING_PRIVATE_KEY\",\n WALLET_RESOLUTION_FAILED = \"WALLET_RESOLUTION_FAILED\",\n CHAIN_NOT_SUPPORTED = \"CHAIN_NOT_SUPPORTED\",\n RPC_URL_NOT_CONFIGURED = \"RPC_URL_NOT_CONFIGURED\",\n PROVIDER_CREATION_FAILED = \"PROVIDER_CREATION_FAILED\",\n SONIC_PROVIDER_REQUIRED = \"SONIC_PROVIDER_REQUIRED\",\n INSUFFICIENT_BALANCE = \"INSUFFICIENT_BALANCE\",\n INSUFFICIENT_ALLOWANCE = \"INSUFFICIENT_ALLOWANCE\",\n TOKEN_NOT_SUPPORTED = \"TOKEN_NOT_SUPPORTED\",\n TOKEN_DECIMALS_NOT_FOUND = \"TOKEN_DECIMALS_NOT_FOUND\",\n QUOTE_EXPIRED = \"QUOTE_EXPIRED\",\n QUOTE_NOT_FOUND = \"QUOTE_NOT_FOUND\",\n BRIDGE_NOT_AVAILABLE = \"BRIDGE_NOT_AVAILABLE\",\n SWAP_EXECUTION_FAILED = \"SWAP_EXECUTION_FAILED\",\n BRIDGE_EXECUTION_FAILED = \"BRIDGE_EXECUTION_FAILED\",\n MM_HEALTH_FACTOR_LOW = \"MM_HEALTH_FACTOR_LOW\",\n MM_CROSS_CHAIN_NOT_SUPPORTED = \"MM_CROSS_CHAIN_NOT_SUPPORTED\",\n MM_INSUFFICIENT_COLLATERAL = \"MM_INSUFFICIENT_COLLATERAL\",\n MM_POSITION_NOT_FOUND = \"MM_POSITION_NOT_FOUND\",\n TRANSACTION_FAILED = \"TRANSACTION_FAILED\",\n TRANSACTION_TIMEOUT = \"TRANSACTION_TIMEOUT\",\n TRANSACTION_REJECTED = \"TRANSACTION_REJECTED\",\n TRANSACTION_SIMULATION_FAILED = \"TRANSACTION_SIMULATION_FAILED\",\n SDK_NOT_INITIALIZED = \"SDK_NOT_INITIALIZED\",\n SDK_INITIALIZATION_FAILED = \"SDK_INITIALIZATION_FAILED\",\n INVALID_CONFIGURATION = \"INVALID_CONFIGURATION\",\n CONFIG_PARSE_ERROR = \"CONFIG_PARSE_ERROR\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n OPERATION_CANCELLED = \"OPERATION_CANCELLED\"\n}\n/**\n * Error severity levels\n */\nexport declare enum ErrorSeverity {\n INFO = \"info\",\n WARNING = \"warning\",\n ERROR = \"error\",\n CRITICAL = \"critical\"\n}\n/**\n * Structured error information\n */\nexport interface AmpedError {\n code: ErrorCode;\n message: string;\n severity: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n}\n/**\n * Error context for logging\n */\nexport interface ErrorContext {\n operation?: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokens?: string[];\n amount?: string;\n requestId?: string;\n txHash?: string;\n [key: string]: unknown;\n}\n/**\n * Amped DeFi Error class\n */\nexport declare class AmpedDefiError extends Error {\n readonly code: ErrorCode;\n readonly severity: ErrorSeverity;\n readonly remediation?: string;\n readonly details?: Record<string, unknown>;\n readonly context?: ErrorContext;\n constructor(code: ErrorCode, message: string, options?: {\n severity?: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n context?: ErrorContext;\n cause?: Error;\n });\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): AmpedError;\n /**\n * Get user-friendly error message\n */\n toUserMessage(): string;\n}\n/**\n * Create a policy error\n */\nexport declare function createPolicyError(code: ErrorCode, message: string, details?: {\n current?: unknown;\n limit?: unknown;\n [key: string]: unknown;\n}, context?: ErrorContext): AmpedDefiError;\n/**\n * Create a wallet error\n */\nexport declare function createWalletError(code: ErrorCode, walletId: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Create a transaction error\n */\nexport declare function createTransactionError(code: ErrorCode, message: string, txHash?: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Create an SDK error\n */\nexport declare function createSDKError(code: ErrorCode, message: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport declare function wrapError(error: unknown, fallbackCode?: ErrorCode, context?: ErrorContext): AmpedDefiError;\n/**\n * Log an error with structured context\n */\nexport declare function logError(error: AmpedDefiError | Error, context?: ErrorContext): void;\n/**\n * Check if an error is retryable\n */\nexport declare function isRetryableError(error: AmpedDefiError | Error): boolean;\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport declare function getRetryDelay(attempt: number, baseDelay?: number): number;\n//# sourceMappingURL=errors.d.ts.map",
125 "inputSchema": {},
126 "outputSchema": null,
127 "icons": null,
128 "annotations": null,
129 "meta": null,
130 "execution": null
131 },
132 {
133 "name": "errorUtils.js",
134 "title": null,
135 "description": "Script: errorUtils.js. Code:\n/**\n * Serialize SDK error objects for readable error messages\n */\nfunction bigintReplacer(key, value) {\n return typeof value === 'bigint' ? value.toString() : value;\n}\nexport function serializeError(error) {\n if (error instanceof Error)\n return error.message;\n if (typeof error === 'string')\n return error;\n try {\n return JSON.stringify(error, bigintReplacer, 2);\n }\n catch {\n return String(error);\n }\n}\n//# sourceMappingURL=errorUtils.js.map",
136 "inputSchema": {},
137 "outputSchema": null,
138 "icons": null,
139 "annotations": null,
140 "meta": null,
141 "execution": null
142 },
143 {
144 "name": "errors.js",
145 "title": null,
146 "description": "Script: errors.js. Code:\n/**\n * Error Handling Utilities\n *\n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport var ErrorCode;\n(function (ErrorCode) {\n // Policy errors\n ErrorCode[\"POLICY_SLIPPAGE_EXCEEDED\"] = \"POLICY_SLIPPAGE_EXCEEDED\";\n ErrorCode[\"POLICY_SPEND_LIMIT_EXCEEDED\"] = \"POLICY_SPEND_LIMIT_EXCEEDED\";\n ErrorCode[\"POLICY_CHAIN_NOT_ALLOWED\"] = \"POLICY_CHAIN_NOT_ALLOWED\";\n ErrorCode[\"POLICY_TOKEN_NOT_ALLOWED\"] = \"POLICY_TOKEN_NOT_ALLOWED\";\n ErrorCode[\"POLICY_RECIPIENT_BLOCKED\"] = \"POLICY_RECIPIENT_BLOCKED\";\n // Wallet errors\n ErrorCode[\"WALLET_NOT_FOUND\"] = \"WALLET_NOT_FOUND\";\n ErrorCode[\"WALLET_INVALID_ADDRESS\"] = \"WALLET_INVALID_ADDRESS\";\n ErrorCode[\"WALLET_MISSING_PRIVATE_KEY\"] = \"WALLET_MISSING_PRIVATE_KEY\";\n ErrorCode[\"WALLET_RESOLUTION_FAILED\"] = \"WALLET_RESOLUTION_FAILED\";\n // Chain/Provider errors\n ErrorCode[\"CHAIN_NOT_SUPPORTED\"] = \"CHAIN_NOT_SUPPORTED\";\n ErrorCode[\"RPC_URL_NOT_CONFIGURED\"] = \"RPC_URL_NOT_CONFIGURED\";\n ErrorCode[\"PROVIDER_CREATION_FAILED\"] = \"PROVIDER_CREATION_FAILED\";\n ErrorCode[\"SONIC_PROVIDER_REQUIRED\"] = \"SONIC_PROVIDER_REQUIRED\";\n // Token errors\n ErrorCode[\"INSUFFICIENT_BALANCE\"] = \"INSUFFICIENT_BALANCE\";\n ErrorCode[\"INSUFFICIENT_ALLOWANCE\"] = \"INSUFFICIENT_ALLOWANCE\";\n ErrorCode[\"TOKEN_NOT_SUPPORTED\"] = \"TOKEN_NOT_SUPPORTED\";\n ErrorCode[\"TOKEN_DECIMALS_NOT_FOUND\"] = \"TOKEN_DECIMALS_NOT_FOUND\";\n // Operation errors\n ErrorCode[\"QUOTE_EXPIRED\"] = \"QUOTE_EXPIRED\";\n ErrorCode[\"QUOTE_NOT_FOUND\"] = \"QUOTE_NOT_FOUND\";\n ErrorCode[\"BRIDGE_NOT_AVAILABLE\"] = \"BRIDGE_NOT_AVAILABLE\";\n ErrorCode[\"SWAP_EXECUTION_FAILED\"] = \"SWAP_EXECUTION_FAILED\";\n ErrorCode[\"BRIDGE_EXECUTION_FAILED\"] = \"BRIDGE_EXECUTION_FAILED\";\n ErrorCode[\"MM_HEALTH_FACTOR_LOW\"] = \"MM_HEALTH_FACTOR_LOW\";\n ErrorCode[\"MM_CROSS_CHAIN_NOT_SUPPORTED\"] = \"MM_CROSS_CHAIN_NOT_SUPPORTED\";\n ErrorCode[\"MM_INSUFFICIENT_COLLATERAL\"] = \"MM_INSUFFICIENT_COLLATERAL\";\n ErrorCode[\"MM_POSITION_NOT_FOUND\"] = \"MM_POSITION_NOT_FOUND\";\n // Transaction errors\n ErrorCode[\"TRANSACTION_FAILED\"] = \"TRANSACTION_FAILED\";\n ErrorCode[\"TRANSACTION_TIMEOUT\"] = \"TRANSACTION_TIMEOUT\";\n ErrorCode[\"TRANSACTION_REJECTED\"] = \"TRANSACTION_REJECTED\";\n ErrorCode[\"TRANSACTION_SIMULATION_FAILED\"] = \"TRANSACTION_SIMULATION_FAILED\";\n // SDK/Configuration errors\n ErrorCode[\"SDK_NOT_INITIALIZED\"] = \"SDK_NOT_INITIALIZED\";\n ErrorCode[\"SDK_INITIALIZATION_FAILED\"] = \"SDK_INITIALIZATION_FAILED\";\n ErrorCode[\"INVALID_CONFIGURATION\"] = \"INVALID_CONFIGURATION\";\n ErrorCode[\"CONFIG_PARSE_ERROR\"] = \"CONFIG_PARSE_ERROR\";\n // Unknown/Generic\n ErrorCode[\"UNKNOWN_ERROR\"] = \"UNKNOWN_ERROR\";\n ErrorCode[\"OPERATION_CANCELLED\"] = \"OPERATION_CANCELLED\";\n})(ErrorCode || (ErrorCode = {}));\n/**\n * Error severity levels\n */\nexport var ErrorSeverity;\n(function (ErrorSeverity) {\n ErrorSeverity[\"INFO\"] = \"info\";\n ErrorSeverity[\"WARNING\"] = \"warning\";\n ErrorSeverity[\"ERROR\"] = \"error\";\n ErrorSeverity[\"CRITICAL\"] = \"critical\";\n})(ErrorSeverity || (ErrorSeverity = {}));\n/**\n * Amped DeFi Error class\n */\nexport class AmpedDefiError extends Error {\n code;\n severity;\n remediation;\n details;\n context;\n constructor(code, message, options) {\n super(message, { cause: options?.cause });\n this.name = 'AmpedDefiError';\n this.code = code;\n this.severity = options?.severity || ErrorSeverity.ERROR;\n this.remediation = options?.remediation;\n this.details = options?.details;\n this.context = options?.context;\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, AmpedDefiError);\n }\n }\n /**\n * Convert to JSON-serializable object\n */\n toJSON() {\n return {\n code: this.code,\n message: this.message,\n severity: this.severity,\n remediation: this.remediation,\n details: this.details,\n };\n }\n /**\n * Get user-friendly error message\n */\n toUserMessage() {\n let msg = `[${this.code}] ${this.message}`;\n if (this.remediation) {\n msg += `\\n\\nSuggestion: ${this.remediation}`;\n }\n return msg;\n }\n}\n// ============================================================================\n// Error Factory Functions\n// ============================================================================\n/**\n * Create a policy error\n */\nexport function createPolicyError(code, message, details, context) {\n const remediation = getPolicyRemediation(code, details);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.WARNING,\n remediation,\n details,\n context,\n });\n}\n/**\n * Create a wallet error\n */\nexport function createWalletError(code, walletId, cause, context) {\n const message = getWalletErrorMessage(code, walletId);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getWalletRemediation(code),\n context: { ...context, walletId },\n cause,\n });\n}\n/**\n * Create a transaction error\n */\nexport function createTransactionError(code, message, txHash, cause, context) {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getTransactionRemediation(code),\n details: txHash ? { txHash } : undefined,\n context: txHash ? { ...context, txHash } : context,\n cause,\n });\n}\n/**\n * Create an SDK error\n */\nexport function createSDKError(code, message, cause, context) {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.CRITICAL,\n remediation: 'Please check your configuration and try again. If the issue persists, contact support.',\n context,\n cause,\n });\n}\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport function wrapError(error, fallbackCode = ErrorCode.UNKNOWN_ERROR, context) {\n if (error instanceof AmpedDefiError) {\n return error;\n }\n if (error instanceof Error) {\n // Try to infer error code from message\n const code = inferErrorCode(error.message) || fallbackCode;\n return new AmpedDefiError(code, error.message, {\n severity: ErrorSeverity.ERROR,\n context,\n cause: error,\n });\n }\n return new AmpedDefiError(fallbackCode, String(error), {\n severity: ErrorSeverity.ERROR,\n context,\n });\n}\n// ============================================================================\n// Remediation Helpers\n// ============================================================================\nfunction getPolicyRemediation(code, details) {\n switch (code) {\n case ErrorCode.POLICY_SLIPPAGE_EXCEEDED:\n return `Slippage ${details?.current} bps exceeds limit of ${details?.limit} bps. Increase maxSlippageBps in your policy configuration or wait for better market conditions.`;\n case ErrorCode.POLICY_SPEND_LIMIT_EXCEEDED:\n return `Reduce the operation amount or request a policy limit increase. Current limit: ${details?.limit}`;\n case ErrorCode.POLICY_CHAIN_NOT_ALLOWED:\n return `Add the chain to your allowedChains policy configuration or use a different chain.`;\n case ErrorCode.POLICY_TOKEN_NOT_ALLOWED:\n return `Add the token to your allowedTokensByChain policy configuration or use a different token.`;\n case ErrorCode.POLICY_RECIPIENT_BLOCKED:\n return `Use a different recipient address. This address has been blocked by policy.`;\n default:\n return 'Review your policy configuration or contact your administrator.';\n }\n}\nfunction getWalletRemediation(code) {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return 'Check your AMPED_OC_WALLETS_JSON configuration and ensure the walletId is correct.';\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return 'Verify the wallet address format (should be 0x-prefixed Ethereum address).';\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return 'Add the private key to your wallet configuration for execute mode, or switch to prepare mode.';\n default:\n return 'Check your wallet configuration and try again.';\n }\n}\nfunction getTransactionRemediation(code) {\n switch (code) {\n case ErrorCode.TRANSACTION_FAILED:\n return 'Check the transaction on a block explorer for revert reasons. You may need to adjust parameters or try again later.';\n case ErrorCode.TRANSACTION_TIMEOUT:\n return 'The operation timed out. You can check the status later using the transaction hash.';\n case ErrorCode.TRANSACTION_REJECTED:\n return 'The transaction was rejected. This may be due to network congestion or insufficient gas.';\n case ErrorCode.TRANSACTION_SIMULATION_FAILED:\n return 'The transaction would fail if executed. Check your balances, allowances, and parameters.';\n default:\n return 'Try again or contact support if the issue persists.';\n }\n}\nfunction getWalletErrorMessage(code, walletId) {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return `Wallet not found: ${walletId}`;\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return `Wallet ${walletId} has an invalid address`;\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return `Wallet ${walletId} is missing private key (required in execute mode)`;\n default:\n return `Wallet error for ${walletId}`;\n }\n}\nfunction inferErrorCode(message) {\n const lowerMsg = message.toLowerCase();\n if (lowerMsg.includes('insufficient balance'))\n return ErrorCode.INSUFFICIENT_BALANCE;\n if (lowerMsg.includes('allowance'))\n return ErrorCode.INSUFFICIENT_ALLOWANCE;\n if (lowerMsg.includes('slippage'))\n return ErrorCode.POLICY_SLIPPAGE_EXCEEDED;\n if (lowerMsg.includes('health factor'))\n return ErrorCode.MM_HEALTH_FACTOR_LOW;\n if (lowerMsg.includes('timeout'))\n return ErrorCode.TRANSACTION_TIMEOUT;\n if (lowerMsg.includes('rejected'))\n return ErrorCode.TRANSACTION_REJECTED;\n if (lowerMsg.includes('simulation'))\n return ErrorCode.TRANSACTION_SIMULATION_FAILED;\n if (lowerMsg.includes('not initialized'))\n return ErrorCode.SDK_NOT_INITIALIZED;\n if (lowerMsg.includes('bridge') && lowerMsg.includes('not'))\n return ErrorCode.BRIDGE_NOT_AVAILABLE;\n if (lowerMsg.includes('quote') && lowerMsg.includes('expir'))\n return ErrorCode.QUOTE_EXPIRED;\n return null;\n}\n// ============================================================================\n// Logging and Observability\n// ============================================================================\n/**\n * Log an error with structured context\n */\nexport function logError(error, context) {\n const structuredLog = {\n timestamp: new Date().toISOString(),\n component: 'amped-defi',\n level: error instanceof AmpedDefiError ? error.severity : 'error',\n code: error instanceof AmpedDefiError ? error.code : ErrorCode.UNKNOWN_ERROR,\n message: error.message,\n context,\n stack: error.stack,\n cause: error.cause,\n };\n // Log as JSON for structured logging systems\n console.error(JSON.stringify(structuredLog, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n/**\n * Check if an error is retryable\n */\nexport function isRetryableError(error) {\n if (error instanceof AmpedDefiError) {\n const retryableCodes = [\n ErrorCode.TRANSACTION_TIMEOUT,\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n ErrorCode.SDK_NOT_INITIALIZED,\n ErrorCode.UNKNOWN_ERROR,\n ];\n return retryableCodes.includes(error.code);\n }\n // For generic errors, check message patterns\n const lowerMsg = error.message.toLowerCase();\n return lowerMsg.includes('timeout') ||\n lowerMsg.includes('network') ||\n lowerMsg.includes('connection') ||\n lowerMsg.includes('rate limit');\n}\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport function getRetryDelay(attempt, baseDelay = 1000) {\n return Math.min(baseDelay * Math.pow(2, attempt), 30000); // Cap at 30 seconds\n}\n//# sourceMappingURL=errors.js.map",
147 "inputSchema": {},
148 "outputSchema": null,
149 "icons": null,
150 "annotations": null,
151 "meta": null,
152 "execution": null
153 },
154 {
155 "name": "errorUtils.d.ts",
156 "title": null,
157 "description": "Script: errorUtils.d.ts. Code:\nexport declare function serializeError(error: unknown): string;\n//# sourceMappingURL=errorUtils.d.ts.map",
158 "inputSchema": {},
159 "outputSchema": null,
160 "icons": null,
161 "annotations": null,
162 "meta": null,
163 "execution": null
164 },
165 {
166 "name": "sodaxApi.d.ts",
167 "title": null,
168 "description": "Script: sodaxApi.d.ts. Code:\n/**\n * SODAX API Client\n *\n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\nexport interface SodaxApiConfig {\n baseUrl?: string;\n apiKey?: string;\n timeoutMs?: number;\n}\nexport interface PaginationParams {\n offset?: number;\n limit?: number;\n}\nexport interface PaginatedResponse<T> {\n items: T[];\n total: number;\n offset: number;\n limit: number;\n}\nexport interface IntentState {\n exists: boolean;\n remainingInput: string;\n receivedOutput: string;\n pendingPayment: boolean;\n}\nexport interface IntentEvent {\n eventType: string;\n txHash: string;\n logIndex: number;\n blockNumber: number;\n intentState: IntentState;\n}\nexport interface IntentDetails {\n intentId: string;\n creator: string;\n inputToken: string;\n outputToken: string;\n inputAmount: string;\n minOutputAmount: string;\n deadline: string;\n allowPartialFill: boolean;\n srcChain: number;\n dstChain: number;\n srcAddress: string;\n dstAddress: string;\n solver: string;\n data: string;\n}\nexport interface UserIntent {\n intentHash: string;\n txHash: string;\n logIndex: number;\n chainId: number;\n blockNumber: number;\n open: boolean;\n intent: IntentDetails;\n events: IntentEvent[];\n createdAt: string;\n}\nexport interface UserIntentFilters {\n open?: boolean;\n srcChain?: number;\n dstChain?: number;\n inputToken?: string;\n outputToken?: string;\n}\nexport declare class SodaxApiClient {\n private baseUrl;\n private apiKey?;\n private timeoutMs;\n constructor(config?: SodaxApiConfig);\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n getIntentByHash(intentHash: string): Promise<UserIntent | null>;\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n getIntentByTxHash(txHash: string): Promise<UserIntent | null>;\n getUserIntents(userAddress: string, pagination?: PaginationParams, filters?: UserIntentFilters): Promise<PaginatedResponse<UserIntent>>;\n getOpenIntents(userAddress: string, pagination?: PaginationParams): Promise<PaginatedResponse<UserIntent>>;\n getIntentHistory(userAddress: string, pagination?: PaginationParams): Promise<PaginatedResponse<UserIntent>>;\n private fetchWithTimeout;\n private isValidAddress;\n}\nexport declare function getSodaxApiClient(config?: SodaxApiConfig): SodaxApiClient;\nexport declare function resetSodaxApiClient(): void;\n//# sourceMappingURL=sodaxApi.d.ts.map",
169 "inputSchema": {},
170 "outputSchema": null,
171 "icons": null,
172 "annotations": null,
173 "meta": null,
174 "execution": null
175 },
176 {
177 "name": "tokenResolver.d.ts",
178 "title": null,
179 "description": "Script: tokenResolver.d.ts. Code:\n/**\n * Token Resolution Utility\n *\n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\nimport type { Token } from '@sodax/types';\ndeclare function isSolanaChain(chainId: string): boolean;\n/**\n * Check if a string is a valid EVM address (0x format)\n */\ndeclare function isEvmAddress(value: string): boolean;\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\ndeclare function isSolanaAddress(value: string): boolean;\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\ndeclare function isValidTokenAddress(value: string, chainId?: string): boolean;\n/**\n * Resolve a token symbol or address to a normalized address\n *\n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport declare function resolveToken(chainId: string, tokenInput: string): Promise<string>;\n/**\n * Resolve multiple tokens at once (for efficiency)\n *\n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport declare function resolveTokens(chainId: string, tokenInputs: string[]): Promise<string[]>;\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport declare function getTokenInfo(chainId: string, tokenInput: string): Promise<Token | null>;\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport declare function clearTokenCache(): void;\n/**\n * Get all cached tokens for a chain\n */\nexport declare function getCachedTokens(chainId: string): Token[] | undefined;\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n//# sourceMappingURL=tokenResolver.d.ts.map",
180 "inputSchema": {},
181 "outputSchema": null,
182 "icons": null,
183 "annotations": null,
184 "meta": null,
185 "execution": null
186 },
187 {
188 "name": "priceService.d.ts",
189 "title": null,
190 "description": "Script: priceService.d.ts. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\nexport interface TokenPrice {\n symbol: string;\n priceUsd: number;\n underlyingAsset: string;\n}\nexport interface PriceMap {\n /** Map of symbol (lowercase) to USD price */\n bySymbol: Map<string, number>;\n /** Map of address (lowercase) to USD price */\n byAddress: Map<string, number>;\n /** Last update timestamp */\n timestamp: number;\n}\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport declare function fetchTokenPrices(): Promise<PriceMap>;\n/**\n * Get USD price for a token by symbol\n */\nexport declare function getTokenPriceBySymbol(symbol: string): Promise<number | null>;\n/**\n * Get USD price for a token by address\n */\nexport declare function getTokenPriceByAddress(address: string): Promise<number | null>;\n/**\n * Calculate USD value for a token amount\n */\nexport declare function calculateUsdValue(symbol: string, amount: string | number): Promise<number | null>;\n/**\n * Clear the price cache (useful for testing)\n */\nexport declare function clearPriceCache(): void;\n//# sourceMappingURL=priceService.d.ts.map",
191 "inputSchema": {},
192 "outputSchema": null,
193 "icons": null,
194 "annotations": null,
195 "meta": null,
196 "execution": null
197 },
198 {
199 "name": "priceService.js",
200 "title": null,
201 "description": "Script: priceService.js. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\nimport { getSodaxClient } from '../sodax/client';\n// ============================================================================\n// Cache\n// ============================================================================\nlet cachedPrices = null;\nconst CACHE_TTL_MS = 60_000; // 1 minute cache\n// ============================================================================\n// Price Fetching\n// ============================================================================\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport async function fetchTokenPrices() {\n // Return cached if fresh\n if (cachedPrices && Date.now() - cachedPrices.timestamp < CACHE_TTL_MS) {\n return cachedPrices;\n }\n console.log('[priceService] Fetching token prices from SODAX reserves');\n const sodax = await getSodaxClient();\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n const bySymbol = new Map();\n const byAddress = new Map();\n // Market reference currency decimals (typically 8)\n const PRICE_DECIMALS = 8;\n for (const reserve of reserves.reservesData) {\n // priceInMarketReferenceCurrency is a string representing the raw value\n const priceRaw = BigInt(reserve.priceInMarketReferenceCurrency);\n const priceUsd = Number(priceRaw) / Math.pow(10, PRICE_DECIMALS);\n // Use symbol for matching (e.g., \"sodaUSDC\" -> \"USDC\")\n const symbol = reserve.symbol.toLowerCase();\n const normalizedSymbol = normalizeSymbol(reserve.symbol);\n const address = reserve.underlyingAsset.toLowerCase();\n bySymbol.set(symbol, priceUsd);\n bySymbol.set(normalizedSymbol, priceUsd);\n byAddress.set(address, priceUsd);\n console.log(`[priceService] ${reserve.symbol}: $${priceUsd.toFixed(4)}`);\n }\n cachedPrices = {\n bySymbol,\n byAddress,\n timestamp: Date.now(),\n };\n console.log(`[priceService] Cached ${bySymbol.size} token prices`);\n return cachedPrices;\n}\n/**\n * Normalize SODAX symbol to standard symbol\n * e.g., \"sodaUSDC\" -> \"usdc\", \"sodaETH\" -> \"eth\"\n */\nfunction normalizeSymbol(symbol) {\n const lower = symbol.toLowerCase();\n if (lower.startsWith('soda')) {\n return lower.slice(4); // Remove 'soda' prefix\n }\n return lower;\n}\n/**\n * Get USD price for a token by symbol\n */\nexport async function getTokenPriceBySymbol(symbol) {\n const prices = await fetchTokenPrices();\n const normalizedSymbol = symbol.toLowerCase();\n // Try exact match first\n if (prices.bySymbol.has(normalizedSymbol)) {\n return prices.bySymbol.get(normalizedSymbol);\n }\n // Try with 'soda' prefix\n const sodaSymbol = 'soda' + normalizedSymbol;\n if (prices.bySymbol.has(sodaSymbol)) {\n return prices.bySymbol.get(sodaSymbol);\n }\n return null;\n}\n/**\n * Get USD price for a token by address\n */\nexport async function getTokenPriceByAddress(address) {\n const prices = await fetchTokenPrices();\n return prices.byAddress.get(address.toLowerCase()) ?? null;\n}\n/**\n * Calculate USD value for a token amount\n */\nexport async function calculateUsdValue(symbol, amount) {\n const price = await getTokenPriceBySymbol(symbol);\n if (price === null)\n return null;\n const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;\n return amountNum * price;\n}\n/**\n * Clear the price cache (useful for testing)\n */\nexport function clearPriceCache() {\n cachedPrices = null;\n}\n//# sourceMappingURL=priceService.js.map",
202 "inputSchema": {},
203 "outputSchema": null,
204 "icons": null,
205 "annotations": null,
206 "meta": null,
207 "execution": null
208 },
209 {
210 "name": "tokenResolver.js",
211 "title": null,
212 "description": "Script: tokenResolver.js. Code:\n/**\n * Token Resolution Utility\n *\n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\n// Cache tokens per chain to avoid repeated lookups\nconst tokenCache = new Map();\n// Native token addresses\nconst EVM_NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';\nconst SOLANA_NATIVE_ADDRESS = '11111111111111111111111111111111';\n// Native token configs per chain (18 decimals for all EVM chains, 9 for Solana)\nconst NATIVE_TOKENS = {\n sonic: { symbol: 'S', name: 'Sonic', decimals: 18, address: EVM_NATIVE_ADDRESS },\n ethereum: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa4b1.arbitrum': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x2105.base': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa.optimism': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x38.bsc': { symbol: 'BNB', name: 'BNB', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x89.polygon': { symbol: 'POL', name: 'POL', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa86a.avax': { symbol: 'AVAX', name: 'Avalanche', decimals: 18, address: EVM_NATIVE_ADDRESS },\n hyper: { symbol: 'HYPE', name: 'Hyperliquid', decimals: 18, address: EVM_NATIVE_ADDRESS },\n lightlink: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n solana: { symbol: 'SOL', name: 'Solana', decimals: 9, address: SOLANA_NATIVE_ADDRESS },\n};\n// Fallback token list for common chains when SDK config is unavailable\nconst FALLBACK_TOKENS = {\n '0x2105.base': [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'ethereum': [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', symbol: 'DAI', name: 'Dai Stablecoin', decimals: 18 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n '0xa4b1.arbitrum': [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'sonic': [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x6047828dc181963ba44974801FF68e538dA5eaF9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'S', name: 'Sonic', decimals: 18 },\n ],\n 'solana': [\n { address: SOLANA_NATIVE_ADDRESS, symbol: 'SOL', name: 'Solana', decimals: 9 },\n { address: '3rSPCLNEF7Quw4wX8S1NyKivELoyij8eYA2gJwBgt4V5', symbol: 'bnUSD', name: 'bnUSD', decimals: 9 },\n { address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n ],\n};\n// Chain type detection\nconst SOLANA_CHAINS = new Set(['solana']);\nfunction isSolanaChain(chainId) {\n return SOLANA_CHAINS.has(chainId.toLowerCase());\n}\n/**\n * Check if an address is a native token for the given chain\n */\nfunction isNativeToken(address, chainId) {\n const addrLower = address.toLowerCase();\n if (chainId && isSolanaChain(chainId)) {\n return addrLower === SOLANA_NATIVE_ADDRESS.toLowerCase();\n }\n return addrLower === EVM_NATIVE_ADDRESS;\n}\n/**\n * Get native token info for a chain\n */\nfunction getNativeTokenInfo(chainId) {\n const native = NATIVE_TOKENS[chainId];\n if (!native)\n return null;\n return { ...native };\n}\n/**\n * Check if a string is a valid EVM address (0x format)\n */\nfunction isEvmAddress(value) {\n return /^0x[a-fA-F0-9]{40}$/i.test(value);\n}\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\nfunction isSolanaAddress(value) {\n // Base58 charset: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\n // Excludes: 0, O, I, l\n return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);\n}\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\nfunction isValidTokenAddress(value, chainId) {\n if (chainId && isSolanaChain(chainId)) {\n return isSolanaAddress(value);\n }\n // For EVM chains or unknown chains, check both formats\n return isEvmAddress(value) || isSolanaAddress(value);\n}\n/**\n * Populate the token cache for a chain from SDK config service\n * This is the canonical way to get tokens - used by both resolveToken and getTokenInfo\n */\nfunction populateTokenCache(chainId) {\n // Convert to SDK chain ID format (e.g., \"base\" -> \"0x2105.base\")\n const sdkChainId = toSodaxChainId(chainId);\n let tokens = tokenCache.get(chainId);\n if (tokens)\n return tokens;\n try {\n const client = getSodaxClient();\n const configService = client.config;\n if (configService?.getSupportedSwapTokensByChainId) {\n // Preferred method - returns readonly Token[]\n tokens = [...configService.getSupportedSwapTokensByChainId(sdkChainId)];\n }\n else if (configService?.getSwapTokensByChainId) {\n tokens = configService.getSwapTokensByChainId(sdkChainId);\n }\n else if (configService?.getSwapTokens) {\n const allTokens = configService.getSwapTokens();\n tokens = allTokens[sdkChainId] || [];\n }\n else {\n console.warn(`[tokenResolver] configService not available for chain ${chainId}`);\n tokens = [];\n }\n // Log what we got from SDK\n if (tokens && tokens.length > 0) {\n console.log(`[tokenResolver] Loaded ${tokens.length} tokens from SDK for ${chainId}`);\n }\n }\n catch (err) {\n console.error(`[tokenResolver] Failed to fetch tokens for chain ${chainId}:`, err);\n tokens = [];\n }\n // Use fallback tokens if SDK returned empty list\n if ((!tokens || tokens.length === 0) && FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId]) {\n console.log(`[tokenResolver] Using fallback token list for ${chainId}`);\n tokens = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n }\n tokenCache.set(chainId, tokens || []);\n return tokens || [];\n}\n/**\n * Resolve a token symbol or address to a normalized address\n *\n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport async function resolveToken(chainId, tokenInput) {\n // If already a valid address, normalize and return\n if (isValidTokenAddress(tokenInput, chainId)) {\n // EVM addresses are lowercased, Solana addresses preserve case\n return isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n }\n // Get tokens from cache or SDK\n const tokens = populateTokenCache(chainId);\n // Find by symbol (case-insensitive)\n const symbolUpper = tokenInput.toUpperCase();\n const token = tokens.find(t => t.symbol.toUpperCase() === symbolUpper);\n if (!token) {\n // Build helpful error message with available tokens\n const available = tokens.length > 0\n ? tokens.map(t => t.symbol).join(', ')\n : 'No tokens loaded';\n throw new Error(`Unknown token \"${tokenInput}\" on chain ${chainId}. ` +\n `Available: ${available}. ` +\n `Alternatively, provide the token address directly.`);\n }\n return isEvmAddress(token.address) ? token.address.toLowerCase() : token.address;\n}\n/**\n * Resolve multiple tokens at once (for efficiency)\n *\n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport async function resolveTokens(chainId, tokenInputs) {\n return Promise.all(tokenInputs.map(t => resolveToken(chainId, t)));\n}\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport async function getTokenInfo(chainId, tokenInput) {\n const sdkChainId = toSodaxChainId(chainId);\n // Handle native tokens first\n if (isValidTokenAddress(tokenInput, chainId) && isNativeToken(tokenInput, chainId)) {\n const nativeInfo = getNativeTokenInfo(chainId);\n if (nativeInfo) {\n return nativeInfo;\n }\n }\n // Get tokens from cache or SDK (same path as resolveToken)\n const tokens = populateTokenCache(chainId);\n // Find by address or symbol\n if (isValidTokenAddress(tokenInput, chainId)) {\n // Normalize address for comparison\n const addrNorm = isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n const found = tokens.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (found) {\n return found;\n }\n // Check fallback tokens even if SDK tokens were loaded\n const fallback = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n if (fallback) {\n const fallbackToken = fallback.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (fallbackToken) {\n console.log(`[tokenResolver] Found ${fallbackToken.symbol} in fallback for ${chainId}`);\n return fallbackToken;\n }\n }\n return null;\n }\n else {\n const symbolUpper = tokenInput.toUpperCase();\n return tokens.find(t => t.symbol.toUpperCase() === symbolUpper) || null;\n }\n}\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport function clearTokenCache() {\n tokenCache.clear();\n}\n/**\n * Get all cached tokens for a chain\n */\nexport function getCachedTokens(chainId) {\n return tokenCache.get(chainId);\n}\n// Export address validation utilities for use by other modules\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n//# sourceMappingURL=tokenResolver.js.map",
213 "inputSchema": {},
214 "outputSchema": null,
215 "icons": null,
216 "annotations": null,
217 "meta": null,
218 "execution": null
219 },
220 {
221 "name": "positionAggregator.js",
222 "title": null,
223 "description": "Script: positionAggregator.js. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n *\n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { normalizeChainId } from '../wallet/types';\n// ============================================================================\n// Position Aggregation Functions\n// ============================================================================\n/**\n * Aggregate money market positions across all supported chains\n *\n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport async function aggregateCrossChainPositions(walletId, options = {}) {\n const startTime = Date.now();\n // Get wallet\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Get supported chains from SODAX\n const sodax = getSodaxClient();\n const sodaxChains = sodax.config.getSupportedSpokeChains();\n // Map SDK chains to string IDs\n const allSodaxChains = sodaxChains.map((c) => typeof c === 'string' ? c : c.id);\n // Filter chains by what the wallet supports\n // This is important for Bankr which only supports ethereum/polygon/base\n const walletSupportedChains = wallet.supportedChains;\n const filteredChains = allSodaxChains.filter((chainId) => wallet.supportsChain(normalizeChainId(chainId)));\n // Determine which chains to query\n const chainsToQuery = options.chainIds || filteredChains;\n console.log('[positionAggregator] Wallet chain filter', {\n walletType: wallet.type,\n walletSupports: walletSupportedChains,\n sodaxChains: allSodaxChains,\n filteredChains: filteredChains,\n normalizedFiltered: filteredChains.map(normalizeChainId),\n });\n console.log('[positionAggregator] Querying positions across chains', {\n walletId,\n address: walletAddress,\n chains: chainsToQuery,\n });\n // Query positions from all chains in parallel\n const chainResults = await Promise.allSettled(chainsToQuery.map(chainId => queryChainPositions(walletId, walletAddress, chainId)));\n // Collect all positions\n const allPositions = [];\n const chainSummaries = [];\n chainResults.forEach((result, index) => {\n const chainId = chainsToQuery[index];\n if (result.status === 'fulfilled') {\n const { positions, summary } = result.value;\n if (positions.length > 0 || options.includeZeroBalances) {\n allPositions.push(...positions);\n chainSummaries.push(summary);\n }\n }\n else {\n console.warn(`[positionAggregator] Failed to query chain ${chainId}:`, result.reason);\n }\n });\n // Calculate aggregated summary\n const summary = calculateAggregatedSummary(allPositions);\n // Calculate collateral utilization\n const collateralUtilization = calculateCollateralUtilization(allPositions, summary);\n // Calculate risk metrics\n const riskMetrics = calculateRiskMetrics(allPositions, summary);\n const view = {\n walletId,\n address: walletAddress,\n timestamp: new Date().toISOString(),\n summary,\n chainSummaries: chainSummaries.sort((a, b) => b.netWorthUsd - a.netWorthUsd),\n positions: allPositions.sort((a, b) => (parseFloat(b.supply.balanceUsd) + parseFloat(b.borrow.balanceUsd)) -\n (parseFloat(a.supply.balanceUsd) + parseFloat(a.borrow.balanceUsd))),\n collateralUtilization,\n riskMetrics,\n };\n console.log('[positionAggregator] Aggregation complete', {\n durationMs: Date.now() - startTime,\n totalPositions: allPositions.length,\n totalSupplyUsd: summary.totalSupplyUsd,\n totalBorrowUsd: summary.totalBorrowUsd,\n healthFactor: summary.healthFactor,\n });\n return view;\n}\n/**\n * Query positions for a single chain\n *\n * IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n * To get token symbols/names, we must:\n * 1. Fetch getReservesHumanized() for token metadata\n * 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n * 3. Join with formatUserSummary(buildUserSummaryRequest())\n *\n * Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n */\nasync function queryChainPositions(walletId, address, chainId) {\n try {\n // Use address for spoke provider lookup\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n // This is the key fix - getUserReservesHumanized alone doesn't include token metadata\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(sodax.moneyMarket.data.buildReserveDataWithPrice(reserves));\n // Step 3: Fetch user-specific balances\n const userReserves = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReserves));\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = userSummary.userReservesData || [];\n // Convert to TokenPosition format\n const positions = userReservesData.map((reserve) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n return {\n chainId,\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n logoURI: reserve.reserve?.iconSymbol || undefined,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n balanceRaw: reserve.scaledATokenBalance || '0',\n apy: supplyApy,\n isCollateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n balanceRaw: reserve.scaledVariableDebt || '0',\n apy: borrowApy,\n },\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n // Filter out positions with zero balance (unless explicitly requested)\n const activePositions = positions.filter(p => parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0);\n // Calculate chain summary\n const supplyUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const borrowUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'), 0);\n // Calculate health factor for this chain\n const healthFactor = calculateChainHealthFactor(activePositions);\n const summary = {\n chainId,\n supplyUsd,\n borrowUsd,\n netWorthUsd: supplyUsd - borrowUsd,\n healthFactor,\n positionCount: activePositions.length,\n };\n console.log(`[positionAggregator] Chain ${chainId}: ${activePositions.length} positions, supply=$${supplyUsd.toFixed(2)}, borrow=$${borrowUsd.toFixed(2)}`);\n return { positions: activePositions, summary };\n }\n catch (error) {\n console.error(`[positionAggregator] Error querying ${chainId}:`, error);\n throw error;\n }\n}\n// ============================================================================\n// Calculation Helpers\n// ============================================================================\n/**\n * Calculate aggregated summary across all positions\n */\nfunction calculateAggregatedSummary(positions) {\n let totalSupplyUsd = 0;\n let totalBorrowUsd = 0;\n let weightedSupplyApy = 0;\n let weightedBorrowApy = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n totalSupplyUsd += supplyUsd;\n totalBorrowUsd += borrowUsd;\n weightedSupplyApy += supplyUsd * pos.supply.apy;\n weightedBorrowApy += borrowUsd * pos.borrow.apy;\n });\n // Calculate weighted average APYs\n const avgSupplyApy = totalSupplyUsd > 0 ? weightedSupplyApy / totalSupplyUsd : 0;\n const avgBorrowApy = totalBorrowUsd > 0 ? weightedBorrowApy / totalBorrowUsd : 0;\n // Calculate health factor\n const healthFactor = calculateHealthFactor(positions);\n // Determine liquidation risk\n let liquidationRisk = 'none';\n if (healthFactor !== null) {\n if (healthFactor < 1.1)\n liquidationRisk = 'high';\n else if (healthFactor < 1.5)\n liquidationRisk = 'medium';\n else if (healthFactor < 2)\n liquidationRisk = 'low';\n }\n // Calculate available borrow (simplified - would need proper oracle prices)\n // This is a conservative estimate based on average LTV\n const avgLtv = positions.length > 0\n ? positions.reduce((sum, p) => sum + p.loanToValue, 0) / positions.length\n : 0;\n const availableBorrowUsd = totalSupplyUsd * avgLtv - totalBorrowUsd;\n return {\n totalSupplyUsd,\n totalBorrowUsd,\n netWorthUsd: totalSupplyUsd - totalBorrowUsd,\n availableBorrowUsd: Math.max(0, availableBorrowUsd),\n healthFactor,\n liquidationRisk,\n weightedSupplyApy: avgSupplyApy,\n weightedBorrowApy: avgBorrowApy,\n netApy: totalSupplyUsd > 0\n ? (avgSupplyApy * totalSupplyUsd - avgBorrowApy * totalBorrowUsd) / totalSupplyUsd\n : 0,\n };\n}\n/**\n * Calculate collateral utilization metrics\n */\nfunction calculateCollateralUtilization(positions, summary) {\n // Only count collateral-enabled supplies\n const totalCollateralUsd = positions\n .filter(p => p.supply.isCollateral)\n .reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const usedCollateralUsd = summary.totalBorrowUsd;\n const availableCollateralUsd = Math.max(0, totalCollateralUsd - usedCollateralUsd);\n const utilizationRate = totalCollateralUsd > 0 ? (usedCollateralUsd / totalCollateralUsd) * 100 : 0;\n return {\n totalCollateralUsd,\n usedCollateralUsd,\n availableCollateralUsd,\n utilizationRate,\n };\n}\n/**\n * Calculate risk metrics\n */\nfunction calculateRiskMetrics(positions, summary) {\n // Calculate max LTV across all positions (weighted by supply)\n let totalSupply = 0;\n let weightedLtvSum = 0;\n let liquidationThresholdSum = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n totalSupply += supplyUsd;\n weightedLtvSum += supplyUsd * pos.loanToValue;\n liquidationThresholdSum += supplyUsd * pos.liquidationThreshold;\n });\n const maxLtv = totalSupply > 0 ? weightedLtvSum / totalSupply : 0;\n const avgLiquidationThreshold = totalSupply > 0 ? liquidationThresholdSum / totalSupply : 0;\n // Current LTV\n const currentLtv = summary.totalSupplyUsd > 0\n ? summary.totalBorrowUsd / summary.totalSupplyUsd\n : 0;\n // Buffer until liquidation (percentage points)\n const bufferUntilLiquidation = Math.max(0, avgLiquidationThreshold - currentLtv) * 100;\n // Safe max borrow (at 80% of liquidation threshold for safety)\n const safeMaxBorrowUsd = summary.totalSupplyUsd * avgLiquidationThreshold * 0.8;\n return {\n maxLtv,\n currentLtv,\n bufferUntilLiquidation,\n safeMaxBorrowUsd,\n };\n}\n/**\n * Calculate health factor for a set of positions\n * Health Factor = (Total Collateral in ETH * Liquidation Threshold) / Total Borrow in ETH\n */\nfunction calculateHealthFactor(positions) {\n let totalCollateralEth = 0;\n let totalBorrowEth = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n // Only count collateral-enabled supplies\n if (pos.supply.isCollateral) {\n totalCollateralEth += supplyUsd * pos.liquidationThreshold;\n }\n totalBorrowEth += borrowUsd;\n });\n if (totalBorrowEth === 0) {\n return totalCollateralEth > 0 ? Infinity : null;\n }\n return totalCollateralEth / totalBorrowEth;\n}\n/**\n * Calculate health factor for a single chain\n */\nfunction calculateChainHealthFactor(positions) {\n return calculateHealthFactor(positions);\n}\n// ============================================================================\n// Utility Functions\n// ============================================================================\n/**\n * Format health factor for display\n */\nexport function formatHealthFactor(hf) {\n if (hf === null)\n return 'N/A';\n if (hf === Infinity)\n return '\u221e';\n return hf.toFixed(2);\n}\n/**\n * Get health factor color/styling indicator\n */\nexport function getHealthFactorStatus(hf) {\n if (hf === null)\n return { status: 'healthy', color: 'green' };\n if (hf === Infinity)\n return { status: 'healthy', color: 'green' };\n if (hf < 1.1)\n return { status: 'critical', color: 'red' };\n if (hf < 1.5)\n return { status: 'danger', color: 'orange' };\n if (hf < 2)\n return { status: 'caution', color: 'yellow' };\n return { status: 'healthy', color: 'green' };\n}\n/**\n * Get recommendation based on position health\n */\nexport function getPositionRecommendation(view) {\n const recommendations = [];\n const { summary } = view;\n // Health factor recommendations\n if (summary.healthFactor !== null && summary.healthFactor < 1.5) {\n recommendations.push('\u26a0\ufe0f Health factor is low. Consider repaying debt or adding collateral.');\n }\n // Borrowing capacity recommendations\n if (summary.availableBorrowUsd > 1000 && summary.healthFactor !== null && summary.healthFactor > 2) {\n recommendations.push(`\ud83d\udca1 You have $${summary.availableBorrowUsd.toFixed(2)} in available borrowing power.`);\n }\n // Collateral utilization\n if (view.collateralUtilization.utilizationRate > 80) {\n recommendations.push('\u26a0\ufe0f High collateral utilization. Avoid borrowing more to maintain safety margin.');\n }\n // Net APY optimization\n if (summary.netApy < 0) {\n recommendations.push('\ud83d\udcc9 Your borrowing costs exceed supply earnings. Consider reducing debt or finding higher APY supply opportunities.');\n }\n // Cross-chain opportunities\n const highApyChains = view.chainSummaries\n .filter(cs => cs.supplyUsd > 100)\n .sort((a, b) => (b.healthFactor || Infinity) - (a.healthFactor || Infinity));\n if (highApyChains.length > 1) {\n recommendations.push(`\ud83c\udf10 You have positions across ${highApyChains.length} chains. Monitor each chain's health factor independently.`);\n }\n return recommendations;\n}\n//# sourceMappingURL=positionAggregator.js.map",
224 "inputSchema": {},
225 "outputSchema": null,
226 "icons": null,
227 "annotations": null,
228 "meta": null,
229 "execution": null
230 },
231 {
232 "name": "walletRegistry.d.ts",
233 "title": null,
234 "description": "Script: walletRegistry.d.ts. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n *\n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport { WalletConfig } from '../types';\n/**\n * Wallet registry entry\n */\ninterface WalletEntry extends WalletConfig {\n mode: 'execute' | 'prepare';\n}\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport declare class WalletRegistry {\n private wallets;\n private skillAdapter;\n constructor();\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n private loadWallets;\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId: string): WalletEntry | null;\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n resolveWallet(walletId: string): Promise<WalletEntry | null>;\n /**\n * Validate a wallet entry\n */\n private validateWallet;\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode(): 'execute' | 'prepare';\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode(): boolean;\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode(): boolean;\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds(): string[];\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount(): number;\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload(): void;\n}\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport declare function getWalletRegistry(): WalletRegistry;\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport declare function resetWalletRegistry(): void;\nexport {};\n//# sourceMappingURL=walletRegistry.d.ts.map",
235 "inputSchema": {},
236 "outputSchema": null,
237 "icons": null,
238 "annotations": null,
239 "meta": null,
240 "execution": null
241 },
242 {
243 "name": "walletRegistry.js",
244 "title": null,
245 "description": "Script: walletRegistry.js. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n *\n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport { getWalletAdapter } from './skillWalletAdapter';\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport class WalletRegistry {\n wallets;\n skillAdapter;\n constructor() {\n this.skillAdapter = getWalletAdapter();\n this.wallets = this.loadWallets();\n // Log skill adapter status\n if (this.skillAdapter.isUsingSkillWallets()) {\n console.log('[walletRegistry] evm-wallet-skill integration active');\n }\n }\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n loadWallets() {\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (!walletsJson) {\n console.warn('[walletRegistry] AMPED_OC_WALLETS_JSON not set');\n return new Map();\n }\n try {\n const walletConfigs = JSON.parse(walletsJson);\n const wallets = new Map();\n for (const [walletId, config] of Object.entries(walletConfigs)) {\n wallets.set(walletId, {\n ...config,\n mode,\n });\n }\n console.log(`[walletRegistry] Loaded ${wallets.size} wallet(s) in ${mode} mode`);\n return wallets;\n }\n catch (error) {\n console.error('[walletRegistry] Failed to parse AMPED_OC_WALLETS_JSON', error);\n return new Map();\n }\n }\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId) {\n const wallet = this.wallets.get(walletId);\n if (wallet) {\n return this.validateWallet(wallet, walletId);\n }\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n async resolveWallet(walletId) {\n // Try local registry first (synchronous)\n const wallet = this.getWallet(walletId);\n if (wallet) {\n return wallet;\n }\n // Try skill adapter (includes ~/.evm-wallet.json)\n if (this.skillAdapter.isUsingSkillWallets()) {\n try {\n const config = await this.skillAdapter.getWalletConfig(walletId);\n const mode = this.getMode();\n return {\n address: config.address,\n privateKey: config.privateKey,\n mode,\n };\n }\n catch (error) {\n console.error(`[walletRegistry] Skill wallet resolution failed: ${error}`);\n }\n }\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n /**\n * Validate a wallet entry\n */\n validateWallet(wallet, walletId) {\n // In execute mode, validate that private key is present\n if (wallet.mode === 'execute' && !wallet.privateKey) {\n console.error(`[walletRegistry] Wallet ${walletId} missing privateKey in execute mode`);\n return null;\n }\n // Validate address format (basic check)\n if (!wallet.address || !wallet.address.startsWith('0x')) {\n console.error(`[walletRegistry] Wallet ${walletId} has invalid address: ${wallet.address}`);\n return null;\n }\n return wallet;\n }\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode() {\n return process.env.AMPED_OC_MODE || 'execute';\n }\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode() {\n return this.getMode() === 'execute';\n }\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode() {\n return this.getMode() === 'prepare';\n }\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds() {\n const localIds = Array.from(this.wallets.keys());\n const skillIds = this.skillAdapter.getWalletIds();\n // Merge unique IDs\n return [...new Set([...localIds, ...skillIds])];\n }\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount() {\n return this.getWalletIds().length;\n }\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload() {\n this.wallets = this.loadWallets();\n }\n}\n// Singleton instance\nlet walletRegistryInstance = null;\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport function getWalletRegistry() {\n if (!walletRegistryInstance) {\n walletRegistryInstance = new WalletRegistry();\n }\n return walletRegistryInstance;\n}\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport function resetWalletRegistry() {\n walletRegistryInstance = null;\n}\n//# sourceMappingURL=walletRegistry.js.map",
246 "inputSchema": {},
247 "outputSchema": null,
248 "icons": null,
249 "annotations": null,
250 "meta": null,
251 "execution": null
252 },
253 {
254 "name": "walletManager.js",
255 "title": null,
256 "description": "Script: walletManager.js. Code:\n/**\n * Unified Wallet Manager\n *\n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n *\n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport { createEvmWalletSkillBackend, createBankrBackend, createEnvBackend, loadWalletsFromEnv } from './backends';\n/**\n * Config file path\n */\nconst CONFIG_PATH = join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'wallets.json');\nconst EVM_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n/**\n * Singleton WalletManager instance\n */\nlet instance = null;\n/**\n * Unified wallet manager\n */\nexport class WalletManager {\n wallets = new Map();\n defaultWallet = null;\n initialized = false;\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n async initialize() {\n if (this.initialized)\n return;\n console.log('[WalletManager] Initializing...');\n // 1. Load from config file if exists\n await this.loadConfigFile();\n // 2. Auto-discover from environment\n await this.autoDiscover();\n // 3. Set default\n this.determineDefault();\n this.initialized = true;\n console.log(`[WalletManager] Initialized with ${this.wallets.size} wallet(s)`);\n if (this.defaultWallet) {\n console.log(`[WalletManager] Default wallet: ${this.defaultWallet}`);\n }\n }\n /**\n * Load wallets from config file\n */\n async loadConfigFile() {\n if (!existsSync(CONFIG_PATH))\n return;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const config = JSON.parse(content);\n for (const [name, walletConfig] of Object.entries(config.wallets)) {\n const backend = this.createBackendFromConfig(name, walletConfig);\n if (backend) {\n this.wallets.set(name.toLowerCase(), backend);\n console.log(`[WalletManager] Loaded wallet \"${name}\" from config`);\n }\n }\n if (config.default) {\n this.defaultWallet = config.default.toLowerCase();\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Failed to load config: ${error}`);\n }\n }\n /**\n * Create backend from config entry\n */\n createBackendFromConfig(name, config) {\n try {\n switch (config.source) {\n case 'evm-wallet-skill':\n return createEvmWalletSkillBackend({\n nickname: name,\n path: config.path,\n chains: config.chains,\n });\n case 'bankr':\n if (!config.apiKey) {\n console.warn(`[WalletManager] Bankr wallet \"${name}\" missing apiKey`);\n return null;\n }\n return createBankrBackend({\n nickname: name,\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n });\n case 'env':\n return createEnvBackend({\n nickname: name,\n address: config.address,\n privateKey: config.privateKey,\n envVar: config.envVar,\n chains: config.chains,\n });\n default:\n console.warn(`[WalletManager] Unknown wallet source: ${config.source}`);\n return null;\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Failed to create backend for \"${name}\": ${error}`);\n return null;\n }\n }\n /**\n * Auto-discover wallets from environment\n */\n async autoDiscover() {\n // evm-wallet-skill (if not already configured)\n if (!this.wallets.has('main') && existsSync(EVM_WALLET_PATH)) {\n try {\n const backend = createEvmWalletSkillBackend({ nickname: 'main' });\n if (await backend.isReady()) {\n this.wallets.set('main', backend);\n console.log('[WalletManager] Auto-discovered: evm-wallet-skill \u2192 \"main\"');\n }\n }\n catch (error) {\n console.debug(`[WalletManager] evm-wallet-skill not available: ${error}`);\n }\n }\n // Bankr (if API key present and not already configured)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (!this.wallets.has('bankr') && bankrApiKey) {\n console.log('[WalletManager] Found BANKR_API_KEY, attempting to add Bankr wallet...');\n try {\n const backend = createBankrBackend({\n nickname: 'bankr',\n apiKey: bankrApiKey,\n apiUrl: process.env.BANKR_API_URL,\n });\n const ready = await backend.isReady();\n if (ready) {\n this.wallets.set('bankr', backend);\n console.log('[WalletManager] Auto-discovered: BANKR_API_KEY \u2192 \"bankr\"');\n }\n else {\n console.warn('[WalletManager] Bankr API key present but connectivity check failed');\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Bankr auto-discovery failed: ${error}`);\n }\n }\n // Environment variable wallets\n const envWallets = loadWalletsFromEnv();\n for (const [name, backend] of envWallets) {\n if (!this.wallets.has(name)) {\n this.wallets.set(name, backend);\n console.log(`[WalletManager] Auto-discovered: AMPED_OC_WALLETS_JSON \u2192 \"${name}\"`);\n }\n }\n }\n /**\n * Determine default wallet\n */\n determineDefault() {\n // If already set from config, verify it exists\n if (this.defaultWallet && this.wallets.has(this.defaultWallet)) {\n return;\n }\n // Priority: main > first available\n if (this.wallets.has('main')) {\n this.defaultWallet = 'main';\n }\n else if (this.wallets.size > 0) {\n this.defaultWallet = Array.from(this.wallets.keys())[0];\n }\n else {\n this.defaultWallet = null;\n }\n }\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n async resolve(nickname) {\n await this.initialize();\n const name = (nickname || this.defaultWallet)?.toLowerCase();\n if (!name) {\n throw new Error('No wallet configured.\\n\\n' +\n 'To set up a wallet, install evm-wallet-skill:\\n' +\n ' git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n' +\n ' cd ~/.openclaw/skills/evm-wallet-skill && npm install\\n' +\n ' node src/setup.js');\n }\n const wallet = this.wallets.get(name);\n if (!wallet) {\n const available = Array.from(this.wallets.keys()).join(', ') || '(none)';\n throw new Error(`Wallet \"${name}\" not found. Available wallets: ${available}`);\n }\n return wallet;\n }\n /**\n * Check if a wallet exists\n */\n async has(nickname) {\n await this.initialize();\n return this.wallets.has(nickname.toLowerCase());\n }\n /**\n * List all available wallets\n */\n async listWallets() {\n await this.initialize();\n const wallets = [];\n for (const [name, backend] of this.wallets) {\n try {\n // Add timeout for slow backends (like Bankr)\n const addressPromise = backend.getAddress();\n const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 30000));\n const address = await Promise.race([addressPromise, timeoutPromise]);\n // Get Solana address for Bankr wallets\n let solanaAddress;\n if (backend.type === 'bankr' && backend.getSolanaAddress) {\n try {\n solanaAddress = await backend.getSolanaAddress() || undefined;\n }\n catch (e) {\n console.warn(`[WalletManager] Failed to get Solana address for ${name}`);\n }\n }\n wallets.push({\n nickname: name,\n type: backend.type,\n address,\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n solanaAddress,\n });\n }\n catch (error) {\n // Include wallet with placeholder address if we can't get it\n console.warn(`[WalletManager] Failed to get address for \"${name}\": ${error}`);\n wallets.push({\n nickname: name,\n type: backend.type,\n address: '0x...', // Placeholder\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n });\n }\n }\n return wallets;\n }\n /**\n * Get the default wallet nickname\n */\n async getDefaultWalletName() {\n await this.initialize();\n return this.defaultWallet;\n }\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname, backend) {\n this.wallets.set(nickname.toLowerCase(), backend);\n console.log(`[WalletManager] Registered wallet: ${nickname}`);\n }\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds() {\n return Array.from(this.wallets.keys());\n }\n /**\n * Add a new wallet to the config file\n */\n async addWallet(nickname, config) {\n await this.initialize();\n const normalizedName = nickname.toLowerCase();\n // Check if wallet already exists\n if (this.wallets.has(normalizedName)) {\n throw new Error(`Wallet \"${nickname}\" already exists. Use rename to change it.`);\n }\n // Create the backend to validate config\n const backend = this.createBackendFromConfig(normalizedName, config);\n if (!backend) {\n throw new Error(`Failed to create wallet backend for \"${nickname}\"`);\n }\n // Validate the backend works\n const ready = await backend.isReady();\n if (!ready) {\n throw new Error(`Wallet \"${nickname}\" configuration is invalid or not accessible`);\n }\n // Load existing config\n const fileConfig = this.loadConfigFromFile();\n // Add new wallet\n fileConfig.wallets[normalizedName] = config;\n // Save config\n this.saveConfigToFile(fileConfig);\n // Register in memory\n this.wallets.set(normalizedName, backend);\n console.log(`[WalletManager] Added wallet \"${nickname}\"`);\n }\n /**\n * Rename a wallet\n */\n async renameWallet(currentNickname, newNickname) {\n await this.initialize();\n const currentName = currentNickname.toLowerCase();\n const newName = newNickname.toLowerCase();\n // Check source exists\n if (!this.wallets.has(currentName)) {\n throw new Error(`Wallet \"${currentNickname}\" not found`);\n }\n // Check target doesn't exist\n if (this.wallets.has(newName)) {\n throw new Error(`Wallet \"${newNickname}\" already exists`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Move wallet config\n if (fileConfig.wallets[currentName]) {\n fileConfig.wallets[newName] = fileConfig.wallets[currentName];\n delete fileConfig.wallets[currentName];\n }\n else {\n // Wallet was auto-discovered, need to add it to config\n const backend = this.wallets.get(currentName);\n const config = await this.backendToConfig(backend);\n fileConfig.wallets[newName] = config;\n }\n // Update default if needed\n if (fileConfig.default === currentName) {\n fileConfig.default = newName;\n }\n if (this.defaultWallet === currentName) {\n this.defaultWallet = newName;\n }\n // Save config\n this.saveConfigToFile(fileConfig);\n // Update in-memory\n const backend = this.wallets.get(currentName);\n this.wallets.delete(currentName);\n this.wallets.set(newName, backend);\n console.log(`[WalletManager] Renamed wallet \"${currentNickname}\" to \"${newNickname}\"`);\n }\n /**\n * Remove a wallet from config\n */\n async removeWallet(nickname) {\n await this.initialize();\n const name = nickname.toLowerCase();\n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Remove from config\n delete fileConfig.wallets[name];\n // Update default if needed\n if (fileConfig.default === name) {\n delete fileConfig.default;\n }\n if (this.defaultWallet === name) {\n this.defaultWallet = null;\n this.determineDefault();\n }\n // Save config\n this.saveConfigToFile(fileConfig);\n // Remove from memory\n this.wallets.delete(name);\n console.log(`[WalletManager] Removed wallet \"${nickname}\"`);\n }\n /**\n * Set the default wallet\n */\n async setDefaultWallet(nickname) {\n await this.initialize();\n const name = nickname.toLowerCase();\n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Update default\n fileConfig.default = name;\n this.defaultWallet = name;\n // Save config\n this.saveConfigToFile(fileConfig);\n console.log(`[WalletManager] Set default wallet to \"${nickname}\"`);\n }\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n loadConfigFromFile() {\n if (!existsSync(CONFIG_PATH)) {\n return { wallets: {} };\n }\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n return JSON.parse(content);\n }\n catch {\n return { wallets: {} };\n }\n }\n /**\n * Save config to file\n */\n saveConfigToFile(config) {\n // Ensure directory exists\n const dir = dirname(CONFIG_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n console.log(`[WalletManager] Config saved to ${CONFIG_PATH}`);\n }\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n async backendToConfig(backend) {\n const config = {\n source: backend.type,\n chains: [...backend.supportedChains],\n };\n // For evm-wallet-skill, just reference the default path\n if (backend.type === 'evm-wallet-skill') {\n config.path = EVM_WALLET_PATH;\n }\n // For env backends, we need address (privateKey should NOT be saved)\n if (backend.type === 'env') {\n config.address = await backend.getAddress();\n // Note: We don't save privateKey to config for security\n }\n // For bankr, we need the API key\n if (backend.type === 'bankr') {\n config.apiKey = process.env.BANKR_API_KEY;\n }\n return config;\n }\n /**\n * Reset the manager (for testing)\n */\n reset() {\n this.wallets.clear();\n this.defaultWallet = null;\n this.initialized = false;\n }\n}\n/**\n * Get the singleton WalletManager instance\n */\nexport function getWalletManager() {\n if (!instance) {\n instance = new WalletManager();\n }\n return instance;\n}\n/**\n * Reset the singleton (for testing)\n */\nexport function resetWalletManager() {\n if (instance) {\n instance.reset();\n instance = null;\n }\n}\n//# sourceMappingURL=walletManager.js.map",
257 "inputSchema": {},
258 "outputSchema": null,
259 "icons": null,
260 "annotations": null,
261 "meta": null,
262 "execution": null
263 },
264 {
265 "name": "skillWalletAdapter.js",
266 "title": null,
267 "description": "Script: skillWalletAdapter.js. Code:\n/**\n * EVM Wallet Skill Adapter\n *\n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n *\n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n *\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ErrorCode, AmpedDefiError } from '../utils/errors';\nimport { normalizeChainId } from './types';\n// Try to import viem for address derivation\nlet privateKeyToAccount = null;\ntry {\n const viem = require('viem/accounts');\n privateKeyToAccount = viem.privateKeyToAccount;\n}\ncatch {\n // viem not available, will use address from config\n}\n/**\n * FALLBACK RPC URLs - primary RPCs come from evm-wallet-skill\n * These are only used when evm-wallet-skill does not provide an RPC\n */\nconst FALLBACK_RPCS = {\n // SODAX supported spoke chains\n ethereum: 'https://ethereum.publicnode.com',\n arbitrum: 'https://arb1.arbitrum.io/rpc',\n base: 'https://mainnet.base.org',\n optimism: 'https://mainnet.optimism.io',\n avalanche: 'https://api.avax.network/ext/bc/C/rpc',\n bsc: 'https://bsc-dataseed.binance.org',\n polygon: 'https://polygon-bor-rpc.publicnode.com',\n // Sonic hub chain\n sonic: 'https://rpc.soniclabs.com',\n // Additional chains (may not be SODAX-supported but useful)\n lightlink: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n};\n/**\n * EVM Wallet Skill Adapter\n */\nexport class EvmWalletSkillAdapter {\n skillWallets = new Map();\n skillRpcs = new Map();\n useSkill;\n constructor(options = {}) {\n this.useSkill = options.preferSkill !== false;\n if (this.useSkill) {\n this.loadSkillConfig();\n }\n }\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n loadSkillConfig() {\n // 1. Try ~/.evm-wallet.json first (evm-wallet-skill default location)\n this.loadEvmWalletFile();\n // 2. Try environment variables\n this.loadEnvWallets();\n // 3. Load RPC URLs from environment\n this.loadEnvRpcs();\n }\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n loadEvmWalletFile() {\n try {\n const walletPath = path.join(os.homedir(), '.evm-wallet.json');\n if (!fs.existsSync(walletPath)) {\n return;\n }\n const content = fs.readFileSync(walletPath, 'utf-8');\n const walletData = JSON.parse(content);\n // evm-wallet-skill stores: { privateKey: \"0x...\" } or { privateKey: \"0x...\", address: \"0x...\" }\n if (walletData.privateKey) {\n let address = walletData.address;\n // Derive address from private key if not provided\n if (!address && privateKeyToAccount) {\n try {\n const account = privateKeyToAccount(walletData.privateKey);\n address = account.address;\n }\n catch (e) {\n console.warn('[walletAdapter] Failed to derive address from private key');\n }\n }\n if (address) {\n this.skillWallets.set('default', {\n id: 'default',\n address,\n provider: 'privateKey',\n });\n // Store private key for later use\n this.skillWallets.get('default').privateKey = walletData.privateKey;\n console.log(`[walletAdapter] Loaded wallet from ~/.evm-wallet.json (${address.slice(0, 8)}...)`);\n }\n }\n }\n catch (error) {\n // Silently ignore - file may not exist\n }\n }\n /**\n * Load wallets from environment variables\n */\n loadEnvWallets() {\n try {\n const skillWalletsJson = process.env.EVM_WALLETS_JSON || process.env.WALLET_CONFIG_JSON;\n if (skillWalletsJson) {\n const wallets = JSON.parse(skillWalletsJson);\n if (Array.isArray(wallets)) {\n wallets.forEach(w => {\n this.skillWallets.set(w.id || w.name || 'default', {\n id: w.id || w.name || 'default',\n address: w.address,\n chainId: w.chainId,\n provider: w.provider || w.type,\n });\n });\n }\n else if (typeof wallets === 'object') {\n Object.entries(wallets).forEach(([id, config]) => {\n this.skillWallets.set(id, {\n id,\n address: config.address,\n chainId: config.chainId,\n provider: config.provider || 'privateKey',\n });\n });\n }\n console.log(`[walletAdapter] Loaded ${this.skillWallets.size} wallets from environment`);\n }\n }\n catch (error) {\n console.warn('[walletAdapter] Failed to parse wallet environment variables:', error);\n }\n }\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n loadEnvRpcs() {\n // Start with default RPCs\n Object.entries(FALLBACK_RPCS).forEach(([chain, url]) => {\n this.skillRpcs.set(chain.toLowerCase(), url);\n });\n // Override with environment variables if provided\n try {\n const skillRpcsJson = process.env.AMPED_OC_RPC_URLS_JSON ||\n process.env.EVM_RPC_URLS_JSON ||\n process.env.RPC_URLS_JSON;\n if (skillRpcsJson) {\n const rpcs = JSON.parse(skillRpcsJson);\n Object.entries(rpcs).forEach(([chain, url]) => {\n this.skillRpcs.set(String(chain).toLowerCase(), url);\n });\n console.log(`[walletAdapter] Custom RPC URLs configured for: ${Object.keys(rpcs).join(', ')}`);\n }\n }\n catch (error) {\n console.warn('[walletAdapter] Failed to parse RPC environment variables:', error);\n }\n console.log(`[walletAdapter] ${this.skillRpcs.size} RPC URLs available (includes defaults)`);\n }\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n async getWalletAddress(walletId) {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') ||\n Array.from(this.skillWallets.values())[0];\n if (wallet)\n return wallet.address;\n }\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.address)\n return wallet.address;\n }\n throw new AmpedDefiError(ErrorCode.WALLET_NOT_FOUND, `Wallet not found: ${walletId || 'default'}`, { remediation: 'Configure ~/.evm-wallet.json, EVM_WALLETS_JSON, or AMPED_OC_WALLETS_JSON' });\n }\n /**\n * Get wallet private key - for signing transactions\n */\n async getPrivateKey(walletId) {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') ||\n Array.from(this.skillWallets.values())[0];\n if (wallet && wallet.privateKey) {\n return wallet.privateKey;\n }\n }\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.privateKey)\n return wallet.privateKey;\n }\n return null;\n }\n /**\n * Get full wallet config (address + privateKey if available)\n */\n async getWalletConfig(walletId) {\n const address = await this.getWalletAddress(walletId);\n const privateKey = await this.getPrivateKey(walletId);\n return { address, privateKey: privateKey || undefined };\n }\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n async getRpcUrl(chainId) {\n const key = normalizeChainId(String(chainId)).toLowerCase();\n // Try skill RPCs\n if (this.skillRpcs.has(key)) {\n return this.skillRpcs.get(key);\n }\n // Fallback to AMPED_OC_RPC_URLS_JSON\n const legacy = process.env.AMPED_OC_RPC_URLS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n if (config[key] || config[chainId])\n return config[key] || config[chainId];\n }\n throw new AmpedDefiError(ErrorCode.RPC_URL_NOT_CONFIGURED, `RPC URL not configured for chain: ${chainId}`, { remediation: 'Configure EVM_RPC_URLS_JSON or AMPED_OC_RPC_URLS_JSON' });\n }\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets() {\n return this.skillWallets.size > 0;\n }\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs() {\n return this.skillRpcs.size > 0;\n }\n /**\n * Get all skill wallet IDs\n */\n getWalletIds() {\n return Array.from(this.skillWallets.keys());\n }\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds() {\n return Array.from(this.skillRpcs.keys());\n }\n}\n// Singleton\nlet adapter = null;\nexport function getWalletAdapter(options) {\n if (!adapter) {\n adapter = new EvmWalletSkillAdapter(options);\n }\n return adapter;\n}\nexport function resetWalletAdapter() {\n adapter = null;\n}\n//# sourceMappingURL=skillWalletAdapter.js.map",
268 "inputSchema": {},
269 "outputSchema": null,
270 "icons": null,
271 "annotations": null,
272 "meta": null,
273 "execution": null
274 },
275 {
276 "name": "types.d.ts",
277 "title": null,
278 "description": "Script: types.d.ts. Code:\n/**\n * Wallet Types - Multi-source wallet management\n *\n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\nimport type { Address, Hash } from 'viem';\n/**\n * Supported wallet backend types\n */\nexport type WalletBackendType = 'evm-wallet-skill' | 'bankr' | 'env';\n/**\n * Raw transaction for Bankr submission\n */\nexport interface RawTransaction {\n to: Address;\n data: `0x${string}`;\n value: string;\n chainId: number;\n}\n/**\n * Wallet info returned by list operations\n */\nexport interface WalletInfo {\n nickname: string;\n type: WalletBackendType;\n address: Address;\n chains: string[];\n isDefault: boolean;\n /** Solana address (if wallet has one, e.g., Bankr) */\n solanaAddress?: string;\n}\n/**\n * Wallet backend interface\n * Different implementations for different sources\n */\nexport interface IWalletBackend {\n readonly type: WalletBackendType;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n /**\n * Check if this wallet supports a specific chain\n */\n supportsChain(chainId: string): boolean;\n /**\n * Get private key (for local/env wallets)\n * Returns undefined for Bankr (no local key access)\n */\n getPrivateKey?(): Promise<`0x${string}`>;\n /**\n * Send raw transaction via Bankr API\n * Only available for Bankr backend\n */\n sendRawTransaction?(tx: RawTransaction): Promise<Hash>;\n /**\n * Check if backend is ready/configured\n */\n isReady(): Promise<boolean>;\n}\n/**\n * Wallet configuration from wallets.json\n */\nexport interface WalletConfig {\n source: WalletBackendType;\n path?: string;\n apiKey?: string;\n apiUrl?: string;\n envVar?: string;\n address?: Address;\n privateKey?: `0x${string}`;\n chains?: string[];\n}\n/**\n * Wallets config file structure\n */\nexport interface WalletsConfigFile {\n wallets: Record<string, WalletConfig>;\n default?: string;\n}\n/**\n * Chain IDs for Bankr submission\n */\nexport declare const BANKR_CHAIN_IDS: Record<string, number>;\n/**\n * Chains supported by Bankr\n */\nexport declare const BANKR_SUPPORTED_CHAINS: readonly [\"ethereum\", \"polygon\", \"base\"];\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport declare const SODAX_SUPPORTED_CHAINS: readonly [\"ethereum\", \"base\", \"polygon\", \"arbitrum\", \"optimism\", \"sonic\", \"avalanche\", \"bsc\", \"lightlink\", \"hyper\", \"kaia\"];\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport declare const SODAX_TO_SIMPLE_CHAIN: Record<string, string>;\n/**\n * Simple to SODAX chain ID mapping\n */\nexport declare const SIMPLE_TO_SODAX_CHAIN: Record<string, string>;\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport declare function normalizeChainId(chainId: string): string;\n/**\n * Convert simple chain ID to SODAX format\n */\nexport declare function toSodaxChainId(chainId: string): string;\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport declare function isSameChain(chainId1: string, chainId2: string): boolean;\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport declare function isBankrSupportedChain(chainId: string): boolean;\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport declare function getBankrChainId(chainId: string): number;\n//# sourceMappingURL=types.d.ts.map",
279 "inputSchema": {},
280 "outputSchema": null,
281 "icons": null,
282 "annotations": null,
283 "meta": null,
284 "execution": null
285 },
286 {
287 "name": "index.d.ts",
288 "title": null,
289 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Module\n *\n * Multi-source wallet management with nicknames\n */\nexport * from './types';\nexport * from './backends';\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n//# sourceMappingURL=index.d.ts.map",
290 "inputSchema": {},
291 "outputSchema": null,
292 "icons": null,
293 "annotations": null,
294 "meta": null,
295 "execution": null
296 },
297 {
298 "name": "types.js",
299 "title": null,
300 "description": "Script: types.js. Code:\n/**\n * Wallet Types - Multi-source wallet management\n *\n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\n/**\n * Chain IDs for Bankr submission\n */\nexport const BANKR_CHAIN_IDS = {\n ethereum: 1,\n polygon: 137,\n base: 8453,\n unichain: 130,\n};\n/**\n * Chains supported by Bankr\n */\nexport const BANKR_SUPPORTED_CHAINS = ['ethereum', 'polygon', 'base'];\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport const SODAX_SUPPORTED_CHAINS = [\n 'ethereum',\n 'base',\n 'polygon',\n 'arbitrum',\n 'optimism',\n 'sonic',\n 'avalanche',\n 'bsc',\n 'lightlink',\n 'hyper',\n 'kaia',\n];\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport const SODAX_TO_SIMPLE_CHAIN = {\n // SODAX format -> simple\n '0x2105.base': 'base',\n '0x89.polygon': 'polygon',\n '0xa4b1.arbitrum': 'arbitrum',\n '0xa.optimism': 'optimism',\n '0x38.bsc': 'bsc',\n '0xa86a.avax': 'avalanche',\n '0x2019.kaia': 'kaia',\n // These don't have prefixes in SODAX\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyper': 'hyperevm',\n 'kaia': 'kaia',\n};\n/**\n * Simple to SODAX chain ID mapping\n */\nexport const SIMPLE_TO_SODAX_CHAIN = {\n // Simple -> SODAX format\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'kaia': '0x2019.kaia',\n // No prefix needed\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport function normalizeChainId(chainId) {\n // Already in mapping\n if (SODAX_TO_SIMPLE_CHAIN[chainId]) {\n return SODAX_TO_SIMPLE_CHAIN[chainId];\n }\n // Check if it's already simple format\n if (SIMPLE_TO_SODAX_CHAIN[chainId]) {\n return chainId;\n }\n // Try to extract from prefixed format (0xNNN.name -> name)\n const match = chainId.match(/^0x[a-fA-F0-9]+\\.(.+)$/);\n if (match) {\n return match[1];\n }\n // Return as-is\n return chainId;\n}\n/**\n * Convert simple chain ID to SODAX format\n */\nexport function toSodaxChainId(chainId) {\n // Already in SODAX format\n if (chainId.startsWith('0x') && chainId.includes('.')) {\n return chainId;\n }\n return SIMPLE_TO_SODAX_CHAIN[chainId] || chainId;\n}\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport function isSameChain(chainId1, chainId2) {\n return normalizeChainId(chainId1) === normalizeChainId(chainId2);\n}\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function isBankrSupportedChain(chainId) {\n const normalized = normalizeChainId(chainId);\n return BANKR_SUPPORTED_CHAINS.includes(normalized);\n}\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function getBankrChainId(chainId) {\n const normalized = normalizeChainId(chainId);\n const id = BANKR_CHAIN_IDS[normalized];\n if (!id) {\n throw new Error(`Chain ${chainId} (normalized: ${normalized}) not supported by Bankr. Supported: ${BANKR_SUPPORTED_CHAINS.join(', ')}`);\n }\n return id;\n}\n//# sourceMappingURL=types.js.map",
301 "inputSchema": {},
302 "outputSchema": null,
303 "icons": null,
304 "annotations": null,
305 "meta": null,
306 "execution": null
307 },
308 {
309 "name": "EvmWalletSkillBackend.d.ts",
310 "title": null,
311 "description": "Script: EvmWalletSkillBackend.d.ts. Code:\n/**\n * EVM Wallet Skill Backend\n *\n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport declare class EvmWalletSkillBackend implements IWalletBackend {\n readonly type: \"evm-wallet-skill\";\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n private walletPath;\n private cachedWallet;\n constructor(options: {\n nickname: string;\n path?: string;\n chains?: string[];\n });\n /**\n * Load wallet from file (cached)\n */\n private loadWallet;\n getAddress(): Promise<Address>;\n supportsChain(chainId: string): boolean;\n getPrivateKey(): Promise<`0x${string}`>;\n isReady(): Promise<boolean>;\n}\n/**\n * Create an evm-wallet-skill backend\n */\nexport declare function createEvmWalletSkillBackend(options?: {\n nickname?: string;\n path?: string;\n chains?: string[];\n}): EvmWalletSkillBackend;\n//# sourceMappingURL=EvmWalletSkillBackend.d.ts.map",
312 "inputSchema": {},
313 "outputSchema": null,
314 "icons": null,
315 "annotations": null,
316 "meta": null,
317 "execution": null
318 },
319 {
320 "name": "EnvBackend.d.ts",
321 "title": null,
322 "description": "Script: EnvBackend.d.ts. Code:\n/**\n * Environment Variable Backend\n *\n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\n/**\n * Environment variable backend configuration\n */\nexport interface EnvBackendConfig {\n nickname: string;\n address?: Address;\n privateKey?: `0x${string}`;\n envVar?: string;\n chains?: string[];\n}\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport declare class EnvBackend implements IWalletBackend {\n readonly type: \"env\";\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n private address;\n private privateKey;\n private envVar;\n constructor(config: EnvBackendConfig);\n /**\n * Load wallet from environment variable if needed\n */\n private loadFromEnv;\n getAddress(): Promise<Address>;\n supportsChain(chainId: string): boolean;\n getPrivateKey(): Promise<`0x${string}`>;\n isReady(): Promise<boolean>;\n}\n/**\n * Create env backend from direct config\n */\nexport declare function createEnvBackend(config: EnvBackendConfig): EnvBackend;\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport declare function loadWalletsFromEnv(): Map<string, EnvBackend>;\n//# sourceMappingURL=EnvBackend.d.ts.map",
323 "inputSchema": {},
324 "outputSchema": null,
325 "icons": null,
326 "annotations": null,
327 "meta": null,
328 "execution": null
329 },
330 {
331 "name": "BankrBackend.js",
332 "title": null,
333 "description": "Script: BankrBackend.js. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport { BANKR_SUPPORTED_CHAINS, isBankrSupportedChain } from '../types';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n/**\n * Disk cache path for bankr address\n */\nconst BANKR_CACHE_DIR = join(homedir(), '.openclaw', 'cache');\nconst getBankrCachePath = (nickname) => join(BANKR_CACHE_DIR, `bankr-${nickname}-address.json`);\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport class BankrBackend {\n type = 'bankr';\n nickname;\n supportedChains = BANKR_SUPPORTED_CHAINS;\n apiUrl;\n apiKey;\n cachedAddress = null;\n cachedSolanaAddress = null;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.nickname = config.nickname || 'bankr';\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.apiKey = config.apiKey;\n // Try to load cached address from disk\n this.loadCachedAddress();\n console.log(`[BankrBackend] Initialized as \"${this.nickname}\"`);\n console.log(`[BankrBackend] Supported chains: ${this.supportedChains.join(', ')}`);\n if (this.cachedAddress) {\n console.log(`[BankrBackend] Loaded cached address: ${this.cachedAddress}`);\n }\n }\n /**\n * Load cached address from disk\n */\n loadCachedAddress() {\n const cachePath = getBankrCachePath(this.nickname);\n if (existsSync(cachePath)) {\n try {\n const data = JSON.parse(readFileSync(cachePath, 'utf-8'));\n if (data.address && data.address.match(/^0x[a-fA-F0-9]{40}$/)) {\n this.cachedAddress = data.address;\n }\n }\n catch (e) {\n console.warn(`[BankrBackend] Failed to load cached address: ${e}`);\n }\n }\n }\n /**\n * Save address to disk cache\n */\n saveCachedAddress(address) {\n const cachePath = getBankrCachePath(this.nickname);\n try {\n if (!existsSync(BANKR_CACHE_DIR)) {\n mkdirSync(BANKR_CACHE_DIR, { recursive: true });\n }\n writeFileSync(cachePath, JSON.stringify({ address, timestamp: Date.now() }));\n console.log(`[BankrBackend] Cached address to ${cachePath}`);\n }\n catch (e) {\n console.warn(`[BankrBackend] Failed to cache address: ${e}`);\n }\n }\n async getAddress() {\n if (this.cachedAddress)\n return this.cachedAddress;\n // Query Bankr for the wallet address\n console.log('[BankrBackend] Fetching wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n console.warn('[BankrBackend] Could not parse address from response:', response.slice(0, 100));\n throw new Error('[BankrBackend] Could not determine wallet address from Bankr');\n }\n this.cachedAddress = addressMatch[0];\n // Save to disk for next time\n this.saveCachedAddress(this.cachedAddress);\n console.log(`[BankrBackend] Wallet address: ${this.cachedAddress}`);\n return this.cachedAddress;\n }\n catch (error) {\n console.error('[BankrBackend] Failed to get address:', error);\n throw error;\n }\n }\n /**\n * Get the Solana wallet address from Bankr\n */\n async getSolanaAddress() {\n if (this.cachedSolanaAddress)\n return this.cachedSolanaAddress;\n // Check for cached address on disk\n const cachePath = `${process.env.HOME}/.openclaw/cache/bankr-${this.nickname}-solana-address.json`;\n try {\n const fs = await import('fs');\n if (fs.existsSync(cachePath)) {\n const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));\n if (cached.address && Date.now() - cached.timestamp < 86400000) {\n this.cachedSolanaAddress = cached.address;\n console.log(`[BankrBackend] Loaded cached Solana address: ${this.cachedSolanaAddress}`);\n return this.cachedSolanaAddress;\n }\n }\n }\n catch (e) {\n // Cache miss, continue to query\n }\n console.log('[BankrBackend] Fetching Solana wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my Solana wallet address?');\n // Solana addresses are base58, typically 32-44 chars, no 0x prefix\n const solanaMatch = response.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/);\n if (!solanaMatch) {\n console.warn('[BankrBackend] Could not parse Solana address from response');\n return null;\n }\n this.cachedSolanaAddress = solanaMatch[0];\n console.log(`[BankrBackend] Solana address: ${this.cachedSolanaAddress}`);\n // Cache to disk\n try {\n const fs = await import('fs');\n const path = await import('path');\n const cacheDir = path.dirname(cachePath);\n if (!fs.existsSync(cacheDir))\n fs.mkdirSync(cacheDir, { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify({ address: this.cachedSolanaAddress, timestamp: Date.now() }));\n }\n catch (e) {\n console.warn('[BankrBackend] Failed to cache Solana address:', e);\n }\n return this.cachedSolanaAddress;\n }\n catch (error) {\n console.error('[BankrBackend] Failed to get Solana address:', error);\n return null;\n }\n }\n supportsChain(chainId) {\n // Normalize chain ID to handle SODAX format (0x2105.base -> base)\n return isBankrSupportedChain(chainId);\n }\n async isReady() {\n if (!this.apiKey)\n return false;\n try {\n // Test API connectivity\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n return response.status !== 503 && response.status !== 502;\n }\n catch {\n return false;\n }\n }\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n async sendRawTransaction(tx) {\n console.log(`[BankrBackend] Sending raw transaction`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Chain: ${tx.chainId}`);\n console.log(`[BankrBackend] Value: ${tx.value}`);\n console.log(`[BankrBackend] Data: ${tx.data.slice(0, 20)}...`);\n // Format as documented in arbitrary-transaction.md\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data,\n value: tx.value,\n chainId: tx.chainId,\n }, null, 2);\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n console.log(`[BankrBackend] Submitting to Bankr API...`);\n const result = await this.submitAndWaitForJob(prompt);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`[BankrBackend] Transaction failed: ${errorMsg}`);\n }\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Submit prompt and wait for text response\n */\n async submitAndWait(prompt) {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n /**\n * Submit prompt and wait for job completion\n */\n async submitAndWaitForJob(prompt) {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${submitResponse.status} ${error}`);\n }\n const submitData = await submitResponse.json();\n if (!submitData.success || !submitData.jobId) {\n throw new Error(`[BankrBackend] Invalid job response: ${JSON.stringify(submitData)}`);\n }\n const jobId = submitData.jobId;\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n // Poll for completion\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${statusResponse.status} ${error}`);\n }\n const result = await statusResponse.json();\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n // Log progress updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n }\n }\n throw new Error(`[BankrBackend] Job ${jobId} timed out`);\n }\n /**\n * Extract transaction hash from Bankr response\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash)\n return item.transactionHash;\n if (item.txHash)\n return item.txHash;\n if (item.hash)\n return item.hash;\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch)\n return hashMatch[0];\n // Check for failure indicators\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n return null;\n }\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a Bankr backend from API key\n */\nexport function createBankrBackend(config) {\n return new BankrBackend(config);\n}\n//# sourceMappingURL=BankrBackend.js.map",
334 "inputSchema": {},
335 "outputSchema": null,
336 "icons": null,
337 "annotations": null,
338 "meta": null,
339 "execution": null
340 },
341 {
342 "name": "EvmWalletSkillBackend.js",
343 "title": null,
344 "description": "Script: EvmWalletSkillBackend.js. Code:\n/**\n * EVM Wallet Skill Backend\n *\n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n/**\n * Default path to evm-wallet-skill wallet file\n */\nconst DEFAULT_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport class EvmWalletSkillBackend {\n type = 'evm-wallet-skill';\n nickname;\n supportedChains;\n walletPath;\n cachedWallet = null;\n constructor(options) {\n this.nickname = options.nickname;\n this.walletPath = options.path || DEFAULT_WALLET_PATH;\n this.supportedChains = options.chains || [...SODAX_SUPPORTED_CHAINS];\n }\n /**\n * Load wallet from file (cached)\n */\n loadWallet() {\n if (this.cachedWallet)\n return this.cachedWallet;\n if (!existsSync(this.walletPath)) {\n throw new Error(`Wallet file not found: ${this.walletPath}\\n` +\n `Run: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n` +\n ` cd ~/.openclaw/skills/evm-wallet-skill && npm install && node src/setup.js`);\n }\n try {\n const content = readFileSync(this.walletPath, 'utf-8');\n this.cachedWallet = JSON.parse(content);\n return this.cachedWallet;\n }\n catch (error) {\n throw new Error(`Failed to load wallet from ${this.walletPath}: ${error}`);\n }\n }\n async getAddress() {\n const wallet = this.loadWallet();\n return wallet.address;\n }\n supportsChain(chainId) {\n return this.supportedChains.includes(chainId);\n }\n async getPrivateKey() {\n const wallet = this.loadWallet();\n const key = wallet.privateKey;\n return key.startsWith('0x') ? key : `0x${key}`;\n }\n async isReady() {\n try {\n this.loadWallet();\n return true;\n }\n catch {\n return false;\n }\n }\n}\n/**\n * Create an evm-wallet-skill backend\n */\nexport function createEvmWalletSkillBackend(options = {}) {\n return new EvmWalletSkillBackend({\n nickname: options.nickname || 'main',\n path: options.path,\n chains: options.chains,\n });\n}\n//# sourceMappingURL=EvmWalletSkillBackend.js.map",
345 "inputSchema": {},
346 "outputSchema": null,
347 "icons": null,
348 "annotations": null,
349 "meta": null,
350 "execution": null
351 },
352 {
353 "name": "index.d.ts",
354 "title": null,
355 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Backends\n *\n * Export all wallet backend implementations\n */\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend, type BankrBackendConfig } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv, type EnvBackendConfig } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider, type BankrWalletProviderConfig } from './BankrWalletProvider';\n//# sourceMappingURL=index.d.ts.map",
356 "inputSchema": {},
357 "outputSchema": null,
358 "icons": null,
359 "annotations": null,
360 "meta": null,
361 "execution": null
362 },
363 {
364 "name": "BankrBackend.d.ts",
365 "title": null,
366 "description": "Script: BankrBackend.d.ts. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport type { Address, Hash } from 'viem';\nimport type { IWalletBackend, RawTransaction } from '../types';\n/**\n * Bankr backend configuration\n */\nexport interface BankrBackendConfig {\n nickname?: string;\n apiKey: string;\n apiUrl?: string;\n}\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport declare class BankrBackend implements IWalletBackend {\n readonly type: \"bankr\";\n readonly nickname: string;\n readonly supportedChains: readonly [\"ethereum\", \"polygon\", \"base\"];\n private readonly apiUrl;\n private readonly apiKey;\n private cachedAddress;\n private cachedSolanaAddress;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrBackendConfig);\n /**\n * Load cached address from disk\n */\n private loadCachedAddress;\n /**\n * Save address to disk cache\n */\n private saveCachedAddress;\n getAddress(): Promise<Address>;\n /**\n * Get the Solana wallet address from Bankr\n */\n getSolanaAddress(): Promise<string | null>;\n supportsChain(chainId: string): boolean;\n isReady(): Promise<boolean>;\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n sendRawTransaction(tx: RawTransaction): Promise<Hash>;\n /**\n * Submit prompt and wait for text response\n */\n private submitAndWait;\n /**\n * Submit prompt and wait for job completion\n */\n private submitAndWaitForJob;\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash;\n private sleep;\n}\n/**\n * Create a Bankr backend from API key\n */\nexport declare function createBankrBackend(config: BankrBackendConfig): BankrBackend;\n//# sourceMappingURL=BankrBackend.d.ts.map",
367 "inputSchema": {},
368 "outputSchema": null,
369 "icons": null,
370 "annotations": null,
371 "meta": null,
372 "execution": null
373 },
374 {
375 "name": "BankrWalletProvider.js",
376 "title": null,
377 "description": "Script: BankrWalletProvider.js. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n *\n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n *\n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport { createPublicClient, http } from 'viem';\nimport { mainnet, polygon, base } from 'viem/chains';\n/**\n * Chain configurations for Bankr\n */\nconst BANKR_CHAINS = {\n 1: { chain: mainnet, name: 'ethereum' },\n 137: { chain: polygon, name: 'polygon' },\n 8453: { chain: base, name: 'base' },\n};\n/**\n * Bankr Wallet Provider\n *\n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport class BankrWalletProvider {\n publicClient;\n apiUrl;\n apiKey;\n chainId;\n cachedAddress;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.apiKey = config.apiKey;\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.chainId = config.chainId;\n this.cachedAddress = config.cachedAddress || null;\n // Validate chain support\n const chainConfig = BANKR_CHAINS[config.chainId];\n if (!chainConfig) {\n throw new Error(`Bankr does not support chainId ${config.chainId}. ` +\n `Supported: Ethereum (1), Polygon (137), Base (8453)`);\n }\n // Create public client for read operations\n this.publicClient = createPublicClient({\n chain: chainConfig.chain,\n transport: http(config.rpcUrl),\n });\n console.log(`[BankrWalletProvider] Initialized for ${chainConfig.name} (${config.chainId})`);\n }\n /**\n * Get the Bankr wallet address\n */\n async getWalletAddress() {\n if (this.cachedAddress)\n return this.cachedAddress;\n console.log('[BankrWalletProvider] Fetching wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n throw new Error('Could not parse wallet address from Bankr response');\n }\n this.cachedAddress = addressMatch[0];\n console.log(`[BankrWalletProvider] Wallet address: ${this.cachedAddress}`);\n return this.cachedAddress;\n }\n catch (error) {\n console.error('[BankrWalletProvider] Failed to get address:', error);\n throw error;\n }\n }\n /**\n * Send a transaction via Bankr\n *\n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n async sendTransaction(evmRawTx) {\n console.log('[BankrWalletProvider] Sending transaction via Bankr');\n console.log(`[BankrWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[BankrWalletProvider] Value: ${evmRawTx.value}`);\n console.log(`[BankrWalletProvider] Data: ${evmRawTx.data.slice(0, 20)}...`);\n // Format transaction for Bankr's arbitrary transaction endpoint\n const txJson = JSON.stringify({\n to: evmRawTx.to,\n data: evmRawTx.data,\n value: evmRawTx.value.toString(),\n chainId: this.chainId,\n }, null, 2);\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n console.log('[BankrWalletProvider] Submitting to Bankr API...');\n const result = await this.submitAndWaitForJob(prompt);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`Transaction failed: ${errorMsg}`);\n }\n console.log(`[BankrWalletProvider] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Wait for transaction receipt\n *\n * Uses the public client to query the blockchain directly.\n */\n async waitForTransactionReceipt(txHash) {\n console.log(`[BankrWalletProvider] Waiting for receipt: ${txHash}`);\n const receipt = await this.publicClient.waitForTransactionReceipt({\n hash: txHash,\n timeout: 120_000, // 2 minutes\n });\n // Convert viem receipt to SODAX format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: `0x${receipt.transactionIndex.toString(16)}`,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: `0x${receipt.cumulativeGasUsed.toString(16)}`,\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: receipt.contractAddress,\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics || [],\n data: log.data,\n blockHash: log.blockHash,\n blockNumber: log.blockNumber ? `0x${log.blockNumber.toString(16)}` : null,\n logIndex: log.logIndex !== null ? `0x${log.logIndex.toString(16)}` : null,\n transactionHash: log.transactionHash,\n transactionIndex: log.transactionIndex !== null ? `0x${log.transactionIndex.toString(16)}` : null,\n removed: log.removed,\n })),\n logsBloom: receipt.logsBloom,\n status: receipt.status === 'success' ? '0x1' : '0x0',\n type: receipt.type,\n effectiveGasPrice: receipt.effectiveGasPrice ? `0x${receipt.effectiveGasPrice.toString(16)}` : undefined,\n };\n }\n /**\n * Submit prompt and wait for text response\n */\n async submitAndWait(prompt) {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n /**\n * Submit prompt and wait for job completion\n */\n async submitAndWaitForJob(prompt) {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`Failed to submit job: ${submitResponse.status} ${error}`);\n }\n const submitData = await submitResponse.json();\n if (!submitData.success || !submitData.jobId) {\n throw new Error(`Invalid job response: ${JSON.stringify(submitData)}`);\n }\n const jobId = submitData.jobId;\n console.log(`[BankrWalletProvider] Job submitted: ${jobId}`);\n // Poll for completion\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`Failed to get job status: ${statusResponse.status} ${error}`);\n }\n const result = await statusResponse.json();\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrWalletProvider] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`Job was cancelled`);\n }\n }\n throw new Error(`Job ${jobId} timed out`);\n }\n /**\n * Extract transaction hash from Bankr response\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash)\n return item.transactionHash;\n if (item.txHash)\n return item.txHash;\n if (item.hash)\n return item.hash;\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch)\n return hashMatch[0];\n }\n return null;\n }\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a BankrWalletProvider\n */\nexport function createBankrWalletProvider(config) {\n return new BankrWalletProvider(config);\n}\n//# sourceMappingURL=BankrWalletProvider.js.map",
378 "inputSchema": {},
379 "outputSchema": null,
380 "icons": null,
381 "annotations": null,
382 "meta": null,
383 "execution": null
384 },
385 {
386 "name": "BankrWalletProvider.d.ts",
387 "title": null,
388 "description": "Script: BankrWalletProvider.d.ts. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n *\n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n *\n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport type { Address, Hash, PublicClient } from 'viem';\nimport type { IEvmWalletProvider, EvmRawTransaction, EvmRawTransactionReceipt } from '@sodax/types';\n/**\n * Configuration for BankrWalletProvider\n */\nexport interface BankrWalletProviderConfig {\n apiKey: string;\n apiUrl?: string;\n chainId: number;\n rpcUrl?: string;\n /** Pre-cached address (avoids initial API call) */\n cachedAddress?: Address;\n}\n/**\n * Bankr Wallet Provider\n *\n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport declare class BankrWalletProvider implements IEvmWalletProvider {\n readonly publicClient: PublicClient;\n private readonly apiUrl;\n private readonly apiKey;\n private readonly chainId;\n private cachedAddress;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrWalletProviderConfig);\n /**\n * Get the Bankr wallet address\n */\n getWalletAddress(): Promise<Address>;\n /**\n * Send a transaction via Bankr\n *\n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash>;\n /**\n * Wait for transaction receipt\n *\n * Uses the public client to query the blockchain directly.\n */\n waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt>;\n /**\n * Submit prompt and wait for text response\n */\n private submitAndWait;\n /**\n * Submit prompt and wait for job completion\n */\n private submitAndWaitForJob;\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash;\n private sleep;\n}\n/**\n * Create a BankrWalletProvider\n */\nexport declare function createBankrWalletProvider(config: BankrWalletProviderConfig): BankrWalletProvider;\n//# sourceMappingURL=BankrWalletProvider.d.ts.map",
389 "inputSchema": {},
390 "outputSchema": null,
391 "icons": null,
392 "annotations": null,
393 "meta": null,
394 "execution": null
395 },
396 {
397 "name": "index.js",
398 "title": null,
399 "description": "Script: index.js. Code:\n/**\n * Wallet Backends\n *\n * Export all wallet backend implementations\n */\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider } from './BankrWalletProvider';\n//# sourceMappingURL=index.js.map",
400 "inputSchema": {},
401 "outputSchema": null,
402 "icons": null,
403 "annotations": null,
404 "meta": null,
405 "execution": null
406 },
407 {
408 "name": "EnvBackend.js",
409 "title": null,
410 "description": "Script: EnvBackend.js. Code:\n/**\n * Environment Variable Backend\n *\n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport class EnvBackend {\n type = 'env';\n nickname;\n supportedChains;\n address = null;\n privateKey = null;\n envVar = null;\n constructor(config) {\n this.nickname = config.nickname;\n this.supportedChains = config.chains || [...SODAX_SUPPORTED_CHAINS];\n if (config.address && config.privateKey) {\n // Direct address/key provided\n this.address = config.address;\n this.privateKey = config.privateKey;\n }\n else if (config.envVar) {\n // Will load from env var\n this.envVar = config.envVar;\n }\n }\n /**\n * Load wallet from environment variable if needed\n */\n loadFromEnv() {\n if (this.address && this.privateKey) {\n return { address: this.address, privateKey: this.privateKey };\n }\n if (this.envVar) {\n const envValue = process.env[this.envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${this.envVar} not set`);\n }\n try {\n const data = JSON.parse(envValue);\n this.address = data.address;\n this.privateKey = (data.privateKey.startsWith('0x')\n ? data.privateKey\n : `0x${data.privateKey}`);\n return { address: this.address, privateKey: this.privateKey };\n }\n catch (error) {\n throw new Error(`Failed to parse ${this.envVar}: ${error}`);\n }\n }\n throw new Error(`No wallet configuration for \"${this.nickname}\"`);\n }\n async getAddress() {\n const { address } = this.loadFromEnv();\n return address;\n }\n supportsChain(chainId) {\n return this.supportedChains.includes(chainId);\n }\n async getPrivateKey() {\n const { privateKey } = this.loadFromEnv();\n return privateKey;\n }\n async isReady() {\n try {\n this.loadFromEnv();\n return true;\n }\n catch {\n return false;\n }\n }\n}\n/**\n * Create env backend from direct config\n */\nexport function createEnvBackend(config) {\n return new EnvBackend(config);\n}\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport function loadWalletsFromEnv() {\n const wallets = new Map();\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n if (!walletsJson)\n return wallets;\n try {\n const parsed = JSON.parse(walletsJson);\n for (const [name, wallet] of Object.entries(parsed)) {\n const backend = new EnvBackend({\n nickname: name,\n address: wallet.address,\n privateKey: (wallet.privateKey.startsWith('0x')\n ? wallet.privateKey\n : `0x${wallet.privateKey}`),\n });\n wallets.set(name.toLowerCase(), backend);\n }\n console.log(`[EnvBackend] Loaded ${wallets.size} wallet(s) from AMPED_OC_WALLETS_JSON`);\n }\n catch (error) {\n console.warn(`[EnvBackend] Failed to parse AMPED_OC_WALLETS_JSON: ${error}`);\n }\n return wallets;\n}\n//# sourceMappingURL=EnvBackend.js.map",
411 "inputSchema": {},
412 "outputSchema": null,
413 "icons": null,
414 "annotations": null,
415 "meta": null,
416 "execution": null
417 },
418 {
419 "name": "backendConfig.d.ts",
420 "title": null,
421 "description": "Script: backendConfig.d.ts. Code:\n/**\n * Wallet Backend Configuration\n *\n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n *\n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\nimport type { WalletBackendType } from './providers';\nexport interface BackendConfig {\n backend: WalletBackendType;\n bankrApiKey?: string;\n bankrApiUrl?: string;\n}\n/**\n * Get the resolved backend configuration\n *\n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport declare function getBackendConfig(): BackendConfig;\n/**\n * Check if Bankr backend is configured and available\n */\nexport declare function isBankrConfigured(): boolean;\n/**\n * Get Bankr configuration if available\n */\nexport declare function getBankrConfig(): {\n apiKey: string;\n apiUrl: string;\n} | null;\n//# sourceMappingURL=backendConfig.d.ts.map",
422 "inputSchema": {},
423 "outputSchema": null,
424 "icons": null,
425 "annotations": null,
426 "meta": null,
427 "execution": null
428 },
429 {
430 "name": "walletManager.d.ts",
431 "title": null,
432 "description": "Script: walletManager.d.ts. Code:\n/**\n * Unified Wallet Manager\n *\n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n *\n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\nimport type { IWalletBackend, WalletInfo, WalletConfig } from './types';\n/**\n * Unified wallet manager\n */\nexport declare class WalletManager {\n private wallets;\n private defaultWallet;\n private initialized;\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n initialize(): Promise<void>;\n /**\n * Load wallets from config file\n */\n private loadConfigFile;\n /**\n * Create backend from config entry\n */\n private createBackendFromConfig;\n /**\n * Auto-discover wallets from environment\n */\n private autoDiscover;\n /**\n * Determine default wallet\n */\n private determineDefault;\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n resolve(nickname?: string): Promise<IWalletBackend>;\n /**\n * Check if a wallet exists\n */\n has(nickname: string): Promise<boolean>;\n /**\n * List all available wallets\n */\n listWallets(): Promise<WalletInfo[]>;\n /**\n * Get the default wallet nickname\n */\n getDefaultWalletName(): Promise<string | null>;\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname: string, backend: IWalletBackend): void;\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds(): string[];\n /**\n * Add a new wallet to the config file\n */\n addWallet(nickname: string, config: WalletConfig): Promise<void>;\n /**\n * Rename a wallet\n */\n renameWallet(currentNickname: string, newNickname: string): Promise<void>;\n /**\n * Remove a wallet from config\n */\n removeWallet(nickname: string): Promise<void>;\n /**\n * Set the default wallet\n */\n setDefaultWallet(nickname: string): Promise<void>;\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n private loadConfigFromFile;\n /**\n * Save config to file\n */\n private saveConfigToFile;\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n private backendToConfig;\n /**\n * Reset the manager (for testing)\n */\n reset(): void;\n}\n/**\n * Get the singleton WalletManager instance\n */\nexport declare function getWalletManager(): WalletManager;\n/**\n * Reset the singleton (for testing)\n */\nexport declare function resetWalletManager(): void;\n//# sourceMappingURL=walletManager.d.ts.map",
433 "inputSchema": {},
434 "outputSchema": null,
435 "icons": null,
436 "annotations": null,
437 "meta": null,
438 "execution": null
439 },
440 {
441 "name": "AmpedWalletProvider.d.ts",
442 "title": null,
443 "description": "Script: AmpedWalletProvider.d.ts. Code:\n/**\n * Amped Wallet Provider\n *\n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n *\n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n *\n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\nimport { createPublicClient, type Hash, type Address } from 'viem';\nimport type { EvmRawTransaction, EvmRawTransactionReceipt } from '@sodax/types';\nimport type { IWalletBackend, WalletBackendConfig, WalletBackendType, IAmpedWalletProvider, BankrBackendConfig } from './types';\n/**\n * Amped Wallet Provider\n *\n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport declare class AmpedWalletProvider implements IAmpedWalletProvider {\n readonly publicClient: ReturnType<typeof createPublicClient>;\n private readonly backend;\n private readonly chainId;\n private constructor();\n /**\n * Create an AmpedWalletProvider with a local key backend\n *\n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static fromPrivateKey(config: {\n privateKey: `0x${string}`;\n chainId: string | number;\n rpcUrl?: string;\n }): Promise<AmpedWalletProvider>;\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n *\n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static fromBankr(config: {\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n chainId: string | number;\n rpcUrl?: string;\n policy?: BankrBackendConfig['policy'];\n }): Promise<AmpedWalletProvider>;\n /**\n * Create from generic backend configuration\n */\n static fromConfig(config: WalletBackendConfig): Promise<AmpedWalletProvider>;\n /**\n * Get the wallet address\n */\n getWalletAddress(): Promise<Address>;\n /**\n * Send a transaction\n *\n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash>;\n /**\n * Wait for transaction receipt\n *\n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt>;\n /**\n * Get the underlying backend\n */\n getBackend(): IWalletBackend;\n /**\n * Get the backend type\n */\n getBackendType(): WalletBackendType;\n /**\n * Check if ready for transactions\n */\n isReady(): Promise<boolean>;\n /**\n * Get chain ID\n */\n getChainId(): number;\n}\nexport type { IAmpedWalletProvider };\n//# sourceMappingURL=AmpedWalletProvider.d.ts.map",
444 "inputSchema": {},
445 "outputSchema": null,
446 "icons": null,
447 "annotations": null,
448 "meta": null,
449 "execution": null
450 },
451 {
452 "name": "BankrBackend.js",
453 "title": null,
454 "description": "Script: BankrBackend.js. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\nimport { resolveChainId } from './chainConfig';\n/**\n * Serialize error objects for readable error messages\n */\nfunction serializeError(error) {\n if (error instanceof Error)\n return error.message;\n if (typeof error === 'string')\n return error;\n try {\n return JSON.stringify(error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n }\n catch {\n return String(error);\n }\n}\n/**\n * Chain ID to chain name mapping for Bankr prompts\n */\nconst CHAIN_NAMES = {\n 1: 'ethereum',\n 8453: 'base',\n 137: 'polygon',\n 42161: 'arbitrum',\n 10: 'optimism',\n 1890: 'lightlink',\n 146: 'sonic',\n};\n/**\n * Bankr execution backend\n *\n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport class BankrBackend {\n type = 'bankr';\n apiUrl;\n apiKey;\n userAddress;\n chainId;\n policy;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.apiUrl = config.bankrApiUrl || 'https://api.bankr.bot';\n this.apiKey = config.bankrApiKey;\n this.userAddress = config.userAddress;\n this.chainId = resolveChainId(config.chainId);\n this.policy = config.policy;\n console.log(`[BankrBackend] Initialized for chain ${this.chainId}`);\n console.log(`[BankrBackend] User address: ${this.userAddress}`);\n console.log(`[BankrBackend] API URL: ${this.apiUrl}`);\n }\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n async getAddress() {\n return this.userAddress;\n }\n /**\n * Send a transaction via Bankr Agent API\n *\n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n async sendTransaction(tx) {\n console.log(`[BankrBackend] Sending transaction via Bankr API`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Value: ${tx.value || 0n}`);\n console.log(`[BankrBackend] Data: ${tx.data ? tx.data.slice(0, 20) + '...' : '0x'}`);\n // Validate against policy\n if (this.policy) {\n await this.validatePolicy(tx);\n }\n // Format transaction as JSON for Bankr prompt\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data || '0x',\n value: (tx.value || 0n).toString(),\n chainId: this.chainId,\n });\n // Create natural language prompt for Bankr\n const prompt = `Submit this transaction: ${txJson}`;\n console.log(`[BankrBackend] Submitting prompt to Bankr API`);\n // Submit job to Bankr\n const jobId = await this.submitJob(prompt);\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n // Poll for completion\n const result = await this.pollJobUntilComplete(jobId);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n throw new Error(`[BankrBackend] Transaction failed: ${serializeError(result.response || result.error) || 'Unknown error'}`);\n }\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Wait for transaction confirmation\n *\n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n async waitForTransaction(txHash) {\n console.log(`[BankrBackend] waitForTransaction called for: ${txHash}`);\n // Bankr transactions are confirmed when the job completes\n // We return a minimal receipt since we don't have full details\n return {\n transactionHash: txHash,\n blockNumber: 0n, // Unknown - would need to query chain\n blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000',\n from: this.userAddress,\n to: null,\n gasUsed: 0n,\n status: 'success',\n logs: [],\n };\n }\n /**\n * Check if backend is ready\n *\n * Verifies Bankr API connectivity with a simple balance query.\n */\n async isReady() {\n if (!this.apiUrl || !this.apiKey || !this.userAddress) {\n return false;\n }\n try {\n // Test API connectivity with a simple query\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n // Even a 4xx error means API is reachable\n return response.status !== 503 && response.status !== 502;\n }\n catch (error) {\n console.error('[BankrBackend] Connectivity check failed:', error);\n return false;\n }\n }\n /**\n * Get the chain ID\n */\n getChainId() {\n return this.chainId;\n }\n /**\n * Submit a job to Bankr Agent API\n */\n async submitJob(prompt) {\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${response.status} ${error}`);\n }\n const data = await response.json();\n if (!data.success || !data.jobId) {\n throw new Error(`[BankrBackend] Invalid job submission response: ${JSON.stringify(data)}`);\n }\n return data.jobId;\n }\n /**\n * Poll for job completion\n */\n async pollJobUntilComplete(jobId) {\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const result = await this.getJobStatus(jobId);\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId} status: ${result.status}`);\n lastStatus = result.status;\n }\n // Log status updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n // Check for terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${serializeError(result.error) || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n case 'pending':\n case 'processing':\n // Continue polling\n break;\n default:\n console.warn(`[BankrBackend] Unknown status: ${result.status}`);\n }\n }\n throw new Error(`[BankrBackend] Job ${jobId} timed out after ${this.maxPollAttempts * this.pollIntervalMs / 1000} seconds`);\n }\n /**\n * Get job status from Bankr API\n */\n async getJobStatus(jobId) {\n const response = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${response.status} ${error}`);\n }\n return await response.json();\n }\n /**\n * Extract transaction hash from Bankr response\n *\n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) {\n return item.transactionHash;\n }\n if (item.txHash) {\n return item.txHash;\n }\n if (item.hash) {\n return item.hash;\n }\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for hex transaction hash pattern (0x followed by 64 hex chars)\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) {\n return hashMatch[0];\n }\n // Check if response indicates failure\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n return null;\n }\n /**\n * Validate transaction against policy\n */\n async validatePolicy(tx) {\n if (!this.policy)\n return;\n // Check max value per transaction\n if (this.policy.maxValuePerTx && tx.value && tx.value > this.policy.maxValuePerTx) {\n throw new Error(`Transaction value ${tx.value} exceeds max allowed ${this.policy.maxValuePerTx}`);\n }\n // Check allowed contracts\n if (this.policy.allowedContracts && this.policy.allowedContracts.length > 0) {\n if (!this.policy.allowedContracts.includes(tx.to)) {\n throw new Error(`Contract ${tx.to} is not in the allowed contracts list`);\n }\n }\n }\n /**\n * Sleep helper\n */\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a BankrBackend from configuration\n */\nexport async function createBankrBackend(config) {\n const backend = new BankrBackend(config);\n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[BankrBackend] Backend created but connectivity check failed');\n }\n return backend;\n}\n//# sourceMappingURL=BankrBackend.js.map",
455 "inputSchema": {},
456 "outputSchema": null,
457 "icons": null,
458 "annotations": null,
459 "meta": null,
460 "execution": null
461 },
462 {
463 "name": "types.d.ts",
464 "title": null,
465 "description": "Script: types.d.ts. Code:\n/**\n * Wallet Provider Types\n *\n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\nimport type { Hash, Address } from 'viem';\nimport type { IEvmWalletProvider } from '@sodax/types';\n/**\n * Wallet backend type identifiers\n */\nexport type WalletBackendType = 'localKey' | 'bankr' | 'privy' | 'smartWallet';\n/**\n * Base configuration for all backends\n */\nexport interface WalletBackendBaseConfig {\n type: WalletBackendType;\n chainId: string | number;\n rpcUrl?: string;\n}\n/**\n * Configuration for local private key backend\n */\nexport interface LocalKeyBackendConfig extends WalletBackendBaseConfig {\n type: 'localKey';\n privateKey: `0x${string}`;\n}\n/**\n * Configuration for Bankr execution backend\n */\nexport interface BankrBackendConfig extends WalletBackendBaseConfig {\n type: 'bankr';\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n /** Optional: policy limits for transactions */\n policy?: {\n maxValuePerTx?: bigint;\n maxDailyVolume?: bigint;\n allowedContracts?: Address[];\n };\n}\n/**\n * Configuration for Privy server wallet backend (future)\n */\nexport interface PrivyBackendConfig extends WalletBackendBaseConfig {\n type: 'privy';\n appId: string;\n appSecret: string;\n walletId: string;\n}\n/**\n * Configuration for smart contract wallet backend (future)\n */\nexport interface SmartWalletBackendConfig extends WalletBackendBaseConfig {\n type: 'smartWallet';\n walletAddress: Address;\n sessionKey: `0x${string}`;\n entryPointAddress: Address;\n}\n/**\n * Union of all backend configurations\n */\nexport type WalletBackendConfig = LocalKeyBackendConfig | BankrBackendConfig | PrivyBackendConfig | SmartWalletBackendConfig;\n/**\n * Transaction request (simplified)\n */\nexport interface TransactionRequest {\n to: Address;\n value?: bigint;\n data?: `0x${string}`;\n gasLimit?: bigint;\n gasPrice?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n nonce?: number;\n}\n/**\n * Transaction receipt (simplified)\n */\nexport interface TransactionReceipt {\n transactionHash: Hash;\n blockNumber: bigint;\n blockHash: Hash;\n from: Address;\n to: Address | null;\n gasUsed: bigint;\n status: 'success' | 'reverted';\n logs: Array<{\n address: Address;\n topics: `0x${string}`[];\n data: `0x${string}`;\n }>;\n}\n/**\n * Wallet backend interface\n *\n * All wallet backends must implement this interface.\n * This allows AmpedWalletProvider to delegate to different backends\n * without changing its own implementation.\n */\nexport interface IWalletBackend {\n /** Backend type identifier */\n readonly type: WalletBackendType;\n /** Get the wallet address */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction and return the transaction hash\n * The backend is responsible for signing and submitting the transaction.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for a transaction to be confirmed\n * Returns when the transaction is included in a block.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if the backend can execute transactions\n * (e.g., Bankr backend may need API connectivity)\n */\n isReady(): Promise<boolean>;\n /**\n * Get the numeric chain ID this backend is configured for\n */\n getChainId(): number;\n}\n/**\n * Factory function type for creating backends\n */\nexport type WalletBackendFactory = (config: WalletBackendConfig) => Promise<IWalletBackend>;\n/**\n * Amped Wallet Provider configuration\n */\nexport interface AmpedWalletProviderConfig {\n /** Backend configuration */\n backend: WalletBackendConfig;\n /** Optional custom RPC URL (overrides chain default) */\n rpcUrl?: string;\n}\n/**\n * Extended IEvmWalletProvider with Amped-specific methods\n */\nexport interface IAmpedWalletProvider extends IEvmWalletProvider {\n /** Get the underlying backend */\n getBackend(): IWalletBackend;\n /** Get the backend type */\n getBackendType(): WalletBackendType;\n /** Check if ready for transactions */\n isReady(): Promise<boolean>;\n /** Get chain ID */\n getChainId(): number;\n}\n//# sourceMappingURL=types.d.ts.map",
466 "inputSchema": {},
467 "outputSchema": null,
468 "icons": null,
469 "annotations": null,
470 "meta": null,
471 "execution": null
472 },
473 {
474 "name": "chainConfig.d.ts",
475 "title": null,
476 "description": "Script: chainConfig.d.ts. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n *\n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\nimport { type Chain } from 'viem';\n/**\n * Chain ID constants matching @sodax/types\n */\nexport declare const CHAIN_IDS: {\n readonly ETHEREUM: 1;\n readonly ARBITRUM: 42161;\n readonly OPTIMISM: 10;\n readonly BASE: 8453;\n readonly POLYGON: 137;\n readonly BSC: 56;\n readonly AVALANCHE: 43114;\n readonly SONIC: 146;\n readonly LIGHTLINK: 1890;\n readonly HYPEREVM: 999;\n readonly KAIA: 8217;\n};\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport declare const SDK_CHAIN_ID_MAP: Record<string, number>;\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport declare const hyper: {\n blockExplorers: {\n readonly default: {\n readonly name: \"HyperEVMScan\";\n readonly url: \"https://hyperevmscan.io/\";\n };\n };\n blockTime?: number | undefined;\n contracts: {\n readonly multicall3: {\n readonly address: \"0xcA11bde05977b3631167028862bE2a173976CA11\";\n readonly blockCreated: 13051;\n };\n };\n ensTlds?: readonly string[] | undefined;\n id: 999;\n name: \"HyperEVM\";\n nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"HYPE\";\n readonly symbol: \"HYPE\";\n };\n experimental_preconfirmationTime?: number | undefined;\n rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://rpc.hyperliquid.xyz/evm\"];\n };\n };\n sourceId?: number | undefined;\n testnet?: boolean | undefined;\n custom?: Record<string, unknown>;\n extendSchema?: Record<string, unknown>;\n fees?: import(\"viem\").ChainFees<undefined>;\n formatters?: undefined;\n prepareTransactionRequest?: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | [fn: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | undefined, options: {\n runAt: readonly (\"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\")[];\n }] | undefined;\n serializers?: import(\"viem\").ChainSerializers<undefined, import(\"viem\").TransactionSerializable>;\n verifyHash?: ((client: import(\"viem\").Client, parameters: import(\"viem\").VerifyHashActionParameters) => Promise<import(\"viem\").VerifyHashActionReturnType>) | undefined;\n extend: <const extended_1 extends Record<string, unknown>>(extended: extended_1) => import(\"viem\").Assign<import(\"viem\").Assign<Chain<undefined>, {\n readonly id: 999;\n readonly name: \"HyperEVM\";\n readonly nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"HYPE\";\n readonly symbol: \"HYPE\";\n };\n readonly rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://rpc.hyperliquid.xyz/evm\"];\n };\n };\n readonly blockExplorers: {\n readonly default: {\n readonly name: \"HyperEVMScan\";\n readonly url: \"https://hyperevmscan.io/\";\n };\n };\n readonly contracts: {\n readonly multicall3: {\n readonly address: \"0xcA11bde05977b3631167028862bE2a173976CA11\";\n readonly blockCreated: 13051;\n };\n };\n }>, extended_1>;\n};\n/**\n * Kaia chain definition\n */\nexport declare const kaia: {\n blockExplorers: {\n readonly default: {\n readonly name: \"KaiaScan\";\n readonly url: \"https://kaiascan.io/\";\n };\n };\n blockTime?: number | undefined;\n contracts?: import(\"viem\").Prettify<{\n [key: string]: import(\"viem\").ChainContract | {\n [sourceId: number]: import(\"viem\").ChainContract | undefined;\n } | undefined;\n } & {\n ensRegistry?: import(\"viem\").ChainContract | undefined;\n ensUniversalResolver?: import(\"viem\").ChainContract | undefined;\n multicall3?: import(\"viem\").ChainContract | undefined;\n erc6492Verifier?: import(\"viem\").ChainContract | undefined;\n }> | undefined;\n ensTlds?: readonly string[] | undefined;\n id: 8217;\n name: \"Kaia\";\n nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"KAIA\";\n readonly symbol: \"KAIA\";\n };\n experimental_preconfirmationTime?: number | undefined;\n rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://public-en.node.kaia.io\"];\n };\n };\n sourceId?: number | undefined;\n testnet?: boolean | undefined;\n custom?: Record<string, unknown>;\n extendSchema?: Record<string, unknown>;\n fees?: import(\"viem\").ChainFees<undefined>;\n formatters?: undefined;\n prepareTransactionRequest?: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | [fn: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | undefined, options: {\n runAt: readonly (\"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\")[];\n }] | undefined;\n serializers?: import(\"viem\").ChainSerializers<undefined, import(\"viem\").TransactionSerializable>;\n verifyHash?: ((client: import(\"viem\").Client, parameters: import(\"viem\").VerifyHashActionParameters) => Promise<import(\"viem\").VerifyHashActionReturnType>) | undefined;\n extend: <const extended_1 extends Record<string, unknown>>(extended: extended_1) => import(\"viem\").Assign<import(\"viem\").Assign<Chain<undefined>, {\n readonly id: 8217;\n readonly name: \"Kaia\";\n readonly nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"KAIA\";\n readonly symbol: \"KAIA\";\n };\n readonly rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://public-en.node.kaia.io\"];\n };\n };\n readonly blockExplorers: {\n readonly default: {\n readonly name: \"KaiaScan\";\n readonly url: \"https://kaiascan.io/\";\n };\n };\n }>, extended_1>;\n};\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport declare const DEFAULT_RPC_URLS: Record<number, string>;\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport declare function resolveChainId(sdkChainId: string | number): number;\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport declare function getViemChain(chainId: string | number): Chain;\n/**\n * Get default RPC URL for a chain\n */\nexport declare function getDefaultRpcUrl(chainId: string | number): string;\n/**\n * Check if a chain is supported\n */\nexport declare function isChainSupported(chainId: string | number): boolean;\n/**\n * Get all supported chain IDs\n */\nexport declare function getSupportedChainIds(): number[];\n/**\n * Get chain name\n */\nexport declare function getChainName(chainId: string | number): string;\n//# sourceMappingURL=chainConfig.d.ts.map",
477 "inputSchema": {},
478 "outputSchema": null,
479 "icons": null,
480 "annotations": null,
481 "meta": null,
482 "execution": null
483 },
484 {
485 "name": "AmpedWalletProvider.js",
486 "title": null,
487 "description": "Script: AmpedWalletProvider.js. Code:\n/**\n * Amped Wallet Provider\n *\n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n *\n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n *\n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\nimport { createPublicClient, http, } from 'viem';\nimport { createLocalKeyBackend } from './LocalKeyBackend';\nimport { createBankrBackend } from './BankrBackend';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n/**\n * Amped Wallet Provider\n *\n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport class AmpedWalletProvider {\n publicClient;\n backend;\n chainId;\n constructor(backend, publicClient) {\n this.backend = backend;\n this.publicClient = publicClient;\n this.chainId = backend.getChainId();\n console.log(`[AmpedWalletProvider] Initialized with ${backend.type} backend`);\n console.log(`[AmpedWalletProvider] Chain ID: ${this.chainId}`);\n }\n /**\n * Create an AmpedWalletProvider with a local key backend\n *\n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static async fromPrivateKey(config) {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n // Create backend\n const backend = await createLocalKeyBackend({\n type: 'localKey',\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl,\n });\n // Use the backend's public client\n const publicClient = backend.getPublicClient();\n return new AmpedWalletProvider(backend, publicClient);\n }\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n *\n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static async fromBankr(config) {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n // Create backend\n const backend = await createBankrBackend({\n type: 'bankr',\n bankrApiUrl: config.bankrApiUrl,\n bankrApiKey: config.bankrApiKey,\n userAddress: config.userAddress,\n chainId: config.chainId,\n rpcUrl,\n policy: config.policy,\n });\n // Create public client (for read operations)\n // Bankr backend doesn't have its own public client\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n }); // Type cast needed due to viem's strict typing\n return new AmpedWalletProvider(backend, publicClient);\n }\n /**\n * Create from generic backend configuration\n */\n static async fromConfig(config) {\n switch (config.type) {\n case 'localKey':\n return AmpedWalletProvider.fromPrivateKey({\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n });\n case 'bankr':\n const bankrConfig = config;\n return AmpedWalletProvider.fromBankr({\n bankrApiUrl: bankrConfig.bankrApiUrl,\n bankrApiKey: bankrConfig.bankrApiKey,\n userAddress: bankrConfig.userAddress,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n policy: bankrConfig.policy,\n });\n default:\n throw new Error(`Unsupported backend type: ${config.type}`);\n }\n }\n // ===== IEvmWalletProvider Implementation =====\n /**\n * Get the wallet address\n */\n async getWalletAddress() {\n return this.backend.getAddress();\n }\n /**\n * Send a transaction\n *\n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n async sendTransaction(evmRawTx) {\n console.log(`[AmpedWalletProvider] sendTransaction`);\n console.log(`[AmpedWalletProvider] From: ${evmRawTx.from}`);\n console.log(`[AmpedWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[AmpedWalletProvider] Value: ${evmRawTx.value}`);\n return this.backend.sendTransaction({\n to: evmRawTx.to,\n value: evmRawTx.value,\n data: evmRawTx.data,\n });\n }\n /**\n * Wait for transaction receipt\n *\n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n async waitForTransactionReceipt(txHash) {\n console.log(`[AmpedWalletProvider] waitForTransactionReceipt: ${txHash}`);\n const receipt = await this.backend.waitForTransaction(txHash);\n // Convert to SDK format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0', // Not tracked in our simplified receipt\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: '0x0', // Not tracked\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: null, // Would need to check if this was a deployment\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics,\n data: log.data,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n logIndex: '0x0',\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0',\n removed: false,\n })),\n logsBloom: '0x',\n status: receipt.status === 'success' ? '0x1' : '0x0',\n };\n }\n // ===== IAmpedWalletProvider Extensions =====\n /**\n * Get the underlying backend\n */\n getBackend() {\n return this.backend;\n }\n /**\n * Get the backend type\n */\n getBackendType() {\n return this.backend.type;\n }\n /**\n * Check if ready for transactions\n */\n async isReady() {\n return this.backend.isReady();\n }\n /**\n * Get chain ID\n */\n getChainId() {\n return this.chainId;\n }\n}\n//# sourceMappingURL=AmpedWalletProvider.js.map",
488 "inputSchema": {},
489 "outputSchema": null,
490 "icons": null,
491 "annotations": null,
492 "meta": null,
493 "execution": null
494 },
495 {
496 "name": "LocalKeyBackend.d.ts",
497 "title": null,
498 "description": "Script: LocalKeyBackend.d.ts. Code:\n/**\n * Local Key Backend\n *\n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n *\n * Uses viem for transaction signing and submission.\n */\nimport { createPublicClient, createWalletClient, type Hash, type Address } from 'viem';\nimport type { IWalletBackend, LocalKeyBackendConfig, TransactionRequest, TransactionReceipt } from './types';\n/**\n * Local private key wallet backend\n *\n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport declare class LocalKeyBackend implements IWalletBackend {\n readonly type: \"localKey\";\n private readonly account;\n private readonly walletClient;\n private readonly _publicClient;\n private readonly chainId;\n private readonly chain;\n constructor(config: LocalKeyBackendConfig);\n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction\n *\n * Signs locally and submits via RPC.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for transaction confirmation\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if backend is ready\n *\n * For local key backend, we verify RPC connectivity.\n */\n isReady(): Promise<boolean>;\n /**\n * Get the chain ID\n */\n getChainId(): number;\n /**\n * Get the public client (for external use)\n */\n getPublicClient(): ReturnType<typeof createPublicClient>;\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient(): ReturnType<typeof createWalletClient>;\n}\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport declare function createLocalKeyBackend(config: LocalKeyBackendConfig): Promise<LocalKeyBackend>;\n//# sourceMappingURL=LocalKeyBackend.d.ts.map",
499 "inputSchema": {},
500 "outputSchema": null,
501 "icons": null,
502 "annotations": null,
503 "meta": null,
504 "execution": null
505 },
506 {
507 "name": "index.d.ts",
508 "title": null,
509 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Providers\n *\n * Pluggable wallet backend architecture for Amped DeFi plugin.\n *\n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n *\n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n *\n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\nexport { CHAIN_IDS, SDK_CHAIN_ID_MAP, DEFAULT_RPC_URLS, hyper, resolveChainId, getViemChain, getDefaultRpcUrl, isChainSupported, getSupportedChainIds, getChainName, } from './chainConfig';\nexport type { WalletBackendType, WalletBackendBaseConfig, LocalKeyBackendConfig, BankrBackendConfig, PrivyBackendConfig, SmartWalletBackendConfig, WalletBackendConfig, TransactionRequest, TransactionReceipt, IWalletBackend, WalletBackendFactory, AmpedWalletProviderConfig, IAmpedWalletProvider, } from './types';\n//# sourceMappingURL=index.d.ts.map",
510 "inputSchema": {},
511 "outputSchema": null,
512 "icons": null,
513 "annotations": null,
514 "meta": null,
515 "execution": null
516 },
517 {
518 "name": "LocalKeyBackend.js",
519 "title": null,
520 "description": "Script: LocalKeyBackend.js. Code:\n/**\n * Local Key Backend\n *\n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n *\n * Uses viem for transaction signing and submission.\n */\nimport { createPublicClient, createWalletClient, http, } from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n/**\n * Local private key wallet backend\n *\n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport class LocalKeyBackend {\n type = 'localKey';\n account;\n walletClient;\n _publicClient;\n chainId;\n chain;\n constructor(config) {\n // Resolve chain configuration\n this.chainId = resolveChainId(config.chainId);\n this.chain = getViemChain(this.chainId);\n // Get RPC URL (custom or default)\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(this.chainId);\n // Create account from private key\n this.account = privateKeyToAccount(config.privateKey);\n // Create viem clients\n this._publicClient = createPublicClient({\n chain: this.chain,\n transport: http(rpcUrl),\n });\n this.walletClient = createWalletClient({\n account: this.account,\n chain: this.chain,\n transport: http(rpcUrl),\n });\n console.log(`[LocalKeyBackend] Initialized for chain ${this.chain.name} (${this.chainId})`);\n console.log(`[LocalKeyBackend] Address: ${this.account.address}`);\n }\n /**\n * Get the wallet address\n */\n async getAddress() {\n return this.account.address;\n }\n /**\n * Send a transaction\n *\n * Signs locally and submits via RPC.\n */\n async sendTransaction(tx) {\n console.log(`[LocalKeyBackend] Sending transaction to ${tx.to}`);\n // Build transaction params\n const txParams = {\n account: this.account,\n chain: this.chain,\n to: tx.to,\n value: tx.value || 0n,\n data: tx.data,\n };\n // Add optional gas parameters\n if (tx.gasLimit)\n txParams.gas = tx.gasLimit;\n if (tx.gasPrice)\n txParams.gasPrice = tx.gasPrice;\n if (tx.maxFeePerGas)\n txParams.maxFeePerGas = tx.maxFeePerGas;\n if (tx.maxPriorityFeePerGas)\n txParams.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;\n if (tx.nonce !== undefined)\n txParams.nonce = tx.nonce;\n const hash = await this.walletClient.sendTransaction(txParams);\n console.log(`[LocalKeyBackend] Transaction sent: ${hash}`);\n return hash;\n }\n /**\n * Wait for transaction confirmation\n */\n async waitForTransaction(txHash) {\n console.log(`[LocalKeyBackend] Waiting for transaction: ${txHash}`);\n const receipt = await this._publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n console.log(`[LocalKeyBackend] Transaction confirmed in block ${receipt.blockNumber}`);\n return {\n transactionHash: receipt.transactionHash,\n blockNumber: receipt.blockNumber,\n blockHash: receipt.blockHash,\n from: receipt.from,\n to: receipt.to,\n gasUsed: receipt.gasUsed,\n status: receipt.status === 'success' ? 'success' : 'reverted',\n logs: receipt.logs.map((log) => ({\n address: log.address,\n topics: [...(log.topics || [])],\n data: log.data,\n })),\n };\n }\n /**\n * Check if backend is ready\n *\n * For local key backend, we verify RPC connectivity.\n */\n async isReady() {\n try {\n await this._publicClient.getChainId();\n return true;\n }\n catch (error) {\n console.error('[LocalKeyBackend] RPC connectivity check failed:', error);\n return false;\n }\n }\n /**\n * Get the chain ID\n */\n getChainId() {\n return this.chainId;\n }\n /**\n * Get the public client (for external use)\n */\n getPublicClient() {\n return this._publicClient;\n }\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient() {\n return this.walletClient;\n }\n}\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport async function createLocalKeyBackend(config) {\n const backend = new LocalKeyBackend(config);\n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[LocalKeyBackend] Backend created but RPC connectivity check failed');\n }\n return backend;\n}\n//# sourceMappingURL=LocalKeyBackend.js.map",
521 "inputSchema": {},
522 "outputSchema": null,
523 "icons": null,
524 "annotations": null,
525 "meta": null,
526 "execution": null
527 },
528 {
529 "name": "chainConfig.js",
530 "title": null,
531 "description": "Script: chainConfig.js. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n *\n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\nimport { defineChain } from 'viem';\nimport { mainnet, arbitrum, optimism, base, polygon, bsc, avalanche, sonic, lightlinkPhoenix, } from 'viem/chains';\n/**\n * Chain ID constants matching @sodax/types\n */\nexport const CHAIN_IDS = {\n ETHEREUM: 1,\n ARBITRUM: 42161,\n OPTIMISM: 10,\n BASE: 8453,\n POLYGON: 137,\n BSC: 56,\n AVALANCHE: 43114,\n SONIC: 146,\n LIGHTLINK: 1890,\n HYPEREVM: 999,\n KAIA: 8217,\n};\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport const SDK_CHAIN_ID_MAP = {\n 'ethereum': CHAIN_IDS.ETHEREUM,\n 'arbitrum': CHAIN_IDS.ARBITRUM,\n '0xa4b1.arbitrum': CHAIN_IDS.ARBITRUM,\n 'optimism': CHAIN_IDS.OPTIMISM,\n '0xa.optimism': CHAIN_IDS.OPTIMISM,\n 'base': CHAIN_IDS.BASE,\n '0x2105.base': CHAIN_IDS.BASE,\n 'polygon': CHAIN_IDS.POLYGON,\n '0x89.polygon': CHAIN_IDS.POLYGON,\n 'bsc': CHAIN_IDS.BSC,\n '0x38.bsc': CHAIN_IDS.BSC,\n 'avalanche': CHAIN_IDS.AVALANCHE,\n 'avax': CHAIN_IDS.AVALANCHE,\n '0xa86a.avax': CHAIN_IDS.AVALANCHE,\n 'sonic': CHAIN_IDS.SONIC,\n 'lightlink': CHAIN_IDS.LIGHTLINK,\n 'hyperevm': CHAIN_IDS.HYPEREVM,\n 'hyper': CHAIN_IDS.HYPEREVM,\n 'kaia': CHAIN_IDS.KAIA,\n '0x2019.kaia': CHAIN_IDS.KAIA,\n};\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport const hyper = defineChain({\n id: CHAIN_IDS.HYPEREVM,\n name: 'HyperEVM',\n nativeCurrency: { decimals: 18, name: 'HYPE', symbol: 'HYPE' },\n rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },\n blockExplorers: { default: { name: 'HyperEVMScan', url: 'https://hyperevmscan.io/' } },\n contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 13051 } },\n});\n/**\n * Kaia chain definition\n */\nexport const kaia = defineChain({\n id: CHAIN_IDS.KAIA,\n name: 'Kaia',\n nativeCurrency: { decimals: 18, name: 'KAIA', symbol: 'KAIA' },\n rpcUrls: { default: { http: ['https://public-en.node.kaia.io'] } },\n blockExplorers: { default: { name: 'KaiaScan', url: 'https://kaiascan.io/' } },\n});\n/**\n * Chain configuration by numeric ID\n */\nconst CHAIN_CONFIG = {\n [CHAIN_IDS.ETHEREUM]: mainnet,\n [CHAIN_IDS.ARBITRUM]: arbitrum,\n [CHAIN_IDS.OPTIMISM]: optimism,\n [CHAIN_IDS.BASE]: base,\n [CHAIN_IDS.POLYGON]: polygon,\n [CHAIN_IDS.BSC]: bsc,\n [CHAIN_IDS.AVALANCHE]: avalanche,\n [CHAIN_IDS.SONIC]: sonic,\n [CHAIN_IDS.LIGHTLINK]: lightlinkPhoenix,\n [CHAIN_IDS.HYPEREVM]: hyper,\n [CHAIN_IDS.KAIA]: kaia,\n};\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport const DEFAULT_RPC_URLS = {\n [CHAIN_IDS.ETHEREUM]: 'https://ethereum.publicnode.com',\n [CHAIN_IDS.ARBITRUM]: 'https://arb1.arbitrum.io/rpc',\n [CHAIN_IDS.OPTIMISM]: 'https://mainnet.optimism.io',\n [CHAIN_IDS.BASE]: 'https://mainnet.base.org',\n [CHAIN_IDS.POLYGON]: 'https://polygon-bor-rpc.publicnode.com',\n [CHAIN_IDS.BSC]: 'https://bsc-dataseed.binance.org',\n [CHAIN_IDS.AVALANCHE]: 'https://api.avax.network/ext/bc/C/rpc',\n [CHAIN_IDS.SONIC]: 'https://rpc.soniclabs.com',\n [CHAIN_IDS.LIGHTLINK]: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n [CHAIN_IDS.HYPEREVM]: 'https://rpc.hyperliquid.xyz/evm',\n [CHAIN_IDS.KAIA]: 'https://public-en.node.kaia.io',\n};\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport function resolveChainId(sdkChainId) {\n if (typeof sdkChainId === 'number')\n return sdkChainId;\n const lower = sdkChainId.toLowerCase();\n if (SDK_CHAIN_ID_MAP[lower] !== undefined)\n return SDK_CHAIN_ID_MAP[lower];\n if (lower.startsWith('0x')) {\n const parsed = parseInt(lower, 16);\n if (!isNaN(parsed))\n return parsed;\n }\n const parsed = parseInt(sdkChainId, 10);\n if (!isNaN(parsed))\n return parsed;\n throw new Error(`Unable to resolve chain ID: ${sdkChainId}`);\n}\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport function getViemChain(chainId) {\n const numericId = resolveChainId(chainId);\n const chain = CHAIN_CONFIG[numericId];\n if (!chain)\n throw new Error(`Unsupported chain ID: ${chainId} (resolved to ${numericId})`);\n return chain;\n}\n/**\n * Get default RPC URL for a chain\n */\nexport function getDefaultRpcUrl(chainId) {\n const numericId = resolveChainId(chainId);\n const rpcUrl = DEFAULT_RPC_URLS[numericId];\n if (!rpcUrl)\n throw new Error(`No default RPC URL for chain ID: ${chainId}`);\n return rpcUrl;\n}\n/**\n * Check if a chain is supported\n */\nexport function isChainSupported(chainId) {\n try {\n getViemChain(chainId);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Get all supported chain IDs\n */\nexport function getSupportedChainIds() {\n return Object.keys(CHAIN_CONFIG).map(Number);\n}\n/**\n * Get chain name\n */\nexport function getChainName(chainId) {\n return getViemChain(chainId).name;\n}\n//# sourceMappingURL=chainConfig.js.map",
532 "inputSchema": {},
533 "outputSchema": null,
534 "icons": null,
535 "annotations": null,
536 "meta": null,
537 "execution": null
538 },
539 {
540 "name": "BankrBackend.d.ts",
541 "title": null,
542 "description": "Script: BankrBackend.d.ts. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\nimport type { Hash, Address } from 'viem';\nimport type { IWalletBackend, BankrBackendConfig, TransactionRequest, TransactionReceipt } from './types';\n/**\n * Bankr execution backend\n *\n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport declare class BankrBackend implements IWalletBackend {\n readonly type: \"bankr\";\n private readonly apiUrl;\n private readonly apiKey;\n private readonly userAddress;\n private readonly chainId;\n private readonly policy?;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrBackendConfig);\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction via Bankr Agent API\n *\n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for transaction confirmation\n *\n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if backend is ready\n *\n * Verifies Bankr API connectivity with a simple balance query.\n */\n isReady(): Promise<boolean>;\n /**\n * Get the chain ID\n */\n getChainId(): number;\n /**\n * Submit a job to Bankr Agent API\n */\n private submitJob;\n /**\n * Poll for job completion\n */\n private pollJobUntilComplete;\n /**\n * Get job status from Bankr API\n */\n private getJobStatus;\n /**\n * Extract transaction hash from Bankr response\n *\n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n private extractTransactionHash;\n /**\n * Validate transaction against policy\n */\n private validatePolicy;\n /**\n * Sleep helper\n */\n private sleep;\n}\n/**\n * Create a BankrBackend from configuration\n */\nexport declare function createBankrBackend(config: BankrBackendConfig): Promise<BankrBackend>;\n//# sourceMappingURL=BankrBackend.d.ts.map",
543 "inputSchema": {},
544 "outputSchema": null,
545 "icons": null,
546 "annotations": null,
547 "meta": null,
548 "execution": null
549 },
550 {
551 "name": "types.js",
552 "title": null,
553 "description": "Script: types.js. Code:\n/**\n * Wallet Provider Types\n *\n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\nexport {};\n//# sourceMappingURL=types.js.map",
554 "inputSchema": {},
555 "outputSchema": null,
556 "icons": null,
557 "annotations": null,
558 "meta": null,
559 "execution": null
560 },
561 {
562 "name": "index.js",
563 "title": null,
564 "description": "Script: index.js. Code:\n/**\n * Wallet Providers\n *\n * Pluggable wallet backend architecture for Amped DeFi plugin.\n *\n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n *\n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n *\n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\n// Main provider\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\n// Backends\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\n// Chain configuration\nexport { CHAIN_IDS, SDK_CHAIN_ID_MAP, DEFAULT_RPC_URLS, hyper, resolveChainId, getViemChain, getDefaultRpcUrl, isChainSupported, getSupportedChainIds, getChainName, } from './chainConfig';\n//# sourceMappingURL=index.js.map",
565 "inputSchema": {},
566 "outputSchema": null,
567 "icons": null,
568 "annotations": null,
569 "meta": null,
570 "execution": null
571 },
572 {
573 "name": "index.js",
574 "title": null,
575 "description": "Script: index.js. Code:\n/**\n * Wallet Module\n *\n * Multi-source wallet management with nicknames\n */\n// Types\nexport * from './types';\n// Backends\nexport * from './backends';\n// Manager\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n//# sourceMappingURL=index.js.map",
576 "inputSchema": {},
577 "outputSchema": null,
578 "icons": null,
579 "annotations": null,
580 "meta": null,
581 "execution": null
582 },
583 {
584 "name": "backendConfig.js",
585 "title": null,
586 "description": "Script: backendConfig.js. Code:\n/**\n * Wallet Backend Configuration\n *\n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n *\n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG = {\n backend: 'localKey',\n};\n/**\n * Path to plugin config file\n */\nfunction getConfigPath() {\n return join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'config.json');\n}\n/**\n * Load configuration from file\n */\nfunction loadConfigFile() {\n const configPath = getConfigPath();\n if (!existsSync(configPath)) {\n return null;\n }\n try {\n const content = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(content);\n return {\n backend: config.walletBackend,\n bankrApiKey: config.bankrApiKey,\n bankrApiUrl: config.bankrApiUrl,\n };\n }\n catch (error) {\n console.warn('[backendConfig] Failed to load config file:', error);\n return null;\n }\n}\n/**\n * Load configuration from environment variables\n */\nfunction loadEnvConfig() {\n const config = {};\n // Check for explicit backend selection\n const backendEnv = process.env.AMPED_OC_WALLET_BACKEND;\n if (backendEnv === 'bankr' || backendEnv === 'localKey') {\n config.backend = backendEnv;\n }\n // Check for Bankr API key (implies bankr backend)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (bankrApiKey) {\n config.bankrApiKey = bankrApiKey;\n // Auto-select bankr backend if API key is present\n if (!config.backend) {\n config.backend = 'bankr';\n }\n }\n // Optional Bankr API URL override\n const bankrApiUrl = process.env.BANKR_API_URL;\n if (bankrApiUrl) {\n config.bankrApiUrl = bankrApiUrl;\n }\n return config;\n}\n/**\n * Get the resolved backend configuration\n *\n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport function getBackendConfig() {\n const fileConfig = loadConfigFile() || {};\n const envConfig = loadEnvConfig();\n // Merge with priority: env > file > default\n const config = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n ...envConfig,\n };\n // Validate bankr configuration\n if (config.backend === 'bankr' && !config.bankrApiKey) {\n console.warn('[backendConfig] Bankr backend selected but no API key provided');\n console.warn('[backendConfig] Set BANKR_API_KEY environment variable or add bankrApiKey to config.json');\n console.warn('[backendConfig] Falling back to localKey backend');\n config.backend = 'localKey';\n }\n // Set default Bankr API URL if not specified\n if (config.backend === 'bankr' && !config.bankrApiUrl) {\n config.bankrApiUrl = 'https://api.bankr.bot';\n }\n console.log(`[backendConfig] Using wallet backend: ${config.backend}`);\n return config;\n}\n/**\n * Check if Bankr backend is configured and available\n */\nexport function isBankrConfigured() {\n const config = getBackendConfig();\n return config.backend === 'bankr' && !!config.bankrApiKey;\n}\n/**\n * Get Bankr configuration if available\n */\nexport function getBankrConfig() {\n const config = getBackendConfig();\n if (config.backend !== 'bankr' || !config.bankrApiKey) {\n return null;\n }\n return {\n apiKey: config.bankrApiKey,\n apiUrl: config.bankrApiUrl || 'https://api.bankr.bot',\n };\n}\n//# sourceMappingURL=backendConfig.js.map",
587 "inputSchema": {},
588 "outputSchema": null,
589 "icons": null,
590 "annotations": null,
591 "meta": null,
592 "execution": null
593 },
594 {
595 "name": "skillWalletAdapter.d.ts",
596 "title": null,
597 "description": "Script: skillWalletAdapter.d.ts. Code:\n/**\n * EVM Wallet Skill Adapter\n *\n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n *\n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n *\n * @see https://github.com/surfer77/evm-wallet-skill\n */\n/**\n * Wallet information from evm-wallet-skill\n */\nexport interface EvmWalletInfo {\n id: string;\n address: string;\n chainId?: number;\n provider?: 'privateKey' | 'kms' | 'hardware' | 'web3Auth';\n}\n/**\n * Wallet adapter options\n */\nexport interface WalletAdapterOptions {\n preferSkill?: boolean;\n walletId?: string;\n}\n/**\n * EVM Wallet Skill Adapter\n */\nexport declare class EvmWalletSkillAdapter {\n private skillWallets;\n private skillRpcs;\n private useSkill;\n constructor(options?: WalletAdapterOptions);\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n private loadSkillConfig;\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n private loadEvmWalletFile;\n /**\n * Load wallets from environment variables\n */\n private loadEnvWallets;\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n private loadEnvRpcs;\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n getWalletAddress(walletId?: string): Promise<string>;\n /**\n * Get wallet private key - for signing transactions\n */\n getPrivateKey(walletId?: string): Promise<string | null>;\n /**\n * Get full wallet config (address + privateKey if available)\n */\n getWalletConfig(walletId?: string): Promise<{\n address: string;\n privateKey?: string;\n }>;\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n getRpcUrl(chainId: string | number): Promise<string>;\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets(): boolean;\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs(): boolean;\n /**\n * Get all skill wallet IDs\n */\n getWalletIds(): string[];\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds(): string[];\n}\nexport declare function getWalletAdapter(options?: WalletAdapterOptions): EvmWalletSkillAdapter;\nexport declare function resetWalletAdapter(): void;\n//# sourceMappingURL=skillWalletAdapter.d.ts.map",
598 "inputSchema": {},
599 "outputSchema": null,
600 "icons": null,
601 "annotations": null,
602 "meta": null,
603 "execution": null
604 },
605 {
606 "name": "types.d.ts",
607 "title": null,
608 "description": "Script: types.d.ts. Code:\n/**\n * Common types for Amped DeFi plugin\n */\nimport { Static, TSchema } from '@sinclair/typebox';\n/**\n * Tool handler function type\n */\nexport type ToolHandler<T extends TSchema> = (params: Static<T>) => Promise<unknown>;\n/**\n * Agent tools registry interface\n */\nexport interface AgentTools {\n register: <T extends TSchema>(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: T;\n handler: ToolHandler<T>;\n }) => void;\n}\n/**\n * Alternative registration interface with input/output schemas\n */\nexport interface AgentToolsTyped {\n register<T extends TSchema, R extends TSchema>(config: {\n name: string;\n summary: string;\n description?: string;\n schema: {\n input: T;\n output?: R;\n };\n handler: (input: Static<T>) => Promise<Static<R>>;\n }): void;\n}\n/**\n * Standard tool result wrapper\n */\nexport interface ToolResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n/**\n * Transaction status\n */\nexport type TransactionStatus = 'pending' | 'submitted' | 'confirmed' | 'failed' | 'cancelled' | 'unknown';\n/**\n * Intent status from SODAX\n */\nexport interface IntentStatus {\n intentHash: string;\n status: 'pending' | 'filled' | 'cancelled' | 'expired' | 'failed';\n spokeTxHash?: string;\n hubTxHash?: string;\n filledAmount?: string;\n error?: string;\n createdAt?: number;\n updatedAt?: number;\n}\n/**\n * Quote result\n */\nexport interface QuoteResult {\n quoteId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n srcAmount: string;\n dstAmount: string;\n minDstAmount?: string;\n slippageBps: number;\n deadline: number;\n fees: {\n solverFee?: string;\n partnerFee?: string;\n gasFee?: string;\n };\n route?: unknown;\n}\n/**\n * Bridge result\n */\nexport interface BridgeResult {\n spokeTxHash: string;\n hubTxHash?: string;\n status: TransactionStatus;\n}\n/**\n * Money market position\n */\nexport interface MoneyMarketPosition {\n token: string;\n supplied: string;\n borrowed: string;\n supplyApy: number;\n borrowApy: number;\n collateralFactor: number;\n}\n/**\n * Money market reserve\n */\nexport interface MoneyMarketReserve {\n token: string;\n totalSupplied: string;\n totalBorrowed: string;\n supplyApy: number;\n borrowApy: number;\n utilizationRate: number;\n collateralFactor: number;\n liquidationThreshold: number;\n}\n/**\n * Chain configuration\n */\nexport interface ChainConfig {\n chainId: string;\n name: string;\n isHub: boolean;\n nativeCurrency: {\n symbol: string;\n decimals: number;\n };\n rpcUrl?: string;\n}\n/**\n * Token configuration\n */\nexport interface TokenConfig {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n chainId: string;\n}\n/**\n * Wallet information (safe to log)\n */\nexport interface WalletInfo {\n walletId: string;\n address: string;\n mode: 'execute' | 'prepare';\n chains: string[];\n}\n/**\n * Operation context for logging\n */\nexport interface OperationContext {\n requestId: string;\n agentId?: string;\n walletId: string;\n operation: string;\n chainIds: string[];\n tokenAddresses?: string[];\n timestamp: number;\n}\n/**\n * Bridge operation parameters\n */\nexport interface BridgeOperation {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n amount: string;\n recipient?: string;\n timeoutMs?: number;\n policyId?: string;\n}\n/**\n * Wallet configuration\n */\nexport interface WalletConfig {\n address: string;\n privateKey?: string;\n}\n/**\n * Policy configuration\n */\nexport interface PolicyConfig {\n /** Maximum USD value for swap inputs */\n maxSwapInputUsd?: number;\n /** Maximum per-token amount for swap inputs */\n maxSwapInputToken?: Record<string, number>;\n /** Maximum per-token amount for bridge operations */\n maxBridgeAmountToken?: Record<string, number>;\n /** Maximum USD value for borrows */\n maxBorrowUsd?: number;\n /** Maximum per-token amount for borrows */\n maxBorrowToken?: Record<string, number>;\n /** Allowed chain IDs for operations */\n allowedChains?: string[];\n /** Allowed tokens per chain */\n allowedTokensByChain?: Record<string, string[]>;\n /** Blocked recipient addresses */\n blockedRecipients?: string[];\n /** Maximum slippage in basis points (100 = 1%) */\n maxSlippageBps?: number;\n /** Whether to require transaction simulation */\n requireSimulation?: boolean;\n}\n//# sourceMappingURL=types.d.ts.map",
609 "inputSchema": {},
610 "outputSchema": null,
611 "icons": null,
612 "annotations": null,
613 "meta": null,
614 "execution": null
615 },
616 {
617 "name": "client.js",
618 "title": null,
619 "description": "Script: client.js. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\nimport { Sodax } from \"@sodax/sdk\";\n// Singleton instance\nlet sodaxClient = null;\n/**\n * HARDCODED PARTNER CONFIGURATION\n * These values are baked in and cannot be overridden.\n *\n * Fee is 0.2% (20 basis points)\n * SDK expects: percentage in bps where 100 = 1%, so 20 = 0.2%\n */\nconst PARTNER_FEE = {\n address: \"0xd99C871c8130B03C8BB597A74fb5EAA7a46864Bb\",\n percentage: 20, // 20 bps = 0.2%\n};\n/**\n * Initialize the SODAX SDK client\n * Always uses dynamic config to fetch live token lists and routes\n */\nasync function initializeSodax() {\n // Initialize SODAX with hardcoded partner fee on ALL services\n const sodax = new Sodax({\n swaps: { partnerFee: PARTNER_FEE },\n moneyMarket: { partnerFee: PARTNER_FEE },\n bridge: { partnerFee: PARTNER_FEE },\n });\n // Suppress SDK console output during initialization\n const originalWarn = console.warn;\n const originalLog = console.log;\n console.warn = () => { };\n console.log = () => { };\n try {\n // Initialize with dynamic config\n await sodax.initialize();\n }\n finally {\n // Restore console\n console.warn = originalWarn;\n console.log = originalLog;\n }\n return sodax;\n}\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport async function getSodaxClientAsync() {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n return sodaxClient;\n}\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport function getSodaxClient() {\n if (!sodaxClient) {\n throw new Error(\"SODAX client not initialized. Call getSodaxClientAsync() first.\");\n }\n return sodaxClient;\n}\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport async function preInitializeSodax() {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n}\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport function resetSodaxClient() {\n sodaxClient = null;\n}\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport class SodaxClient {\n static instance = null;\n static async getClient() {\n if (!SodaxClient.instance) {\n SodaxClient.instance = await initializeSodax();\n sodaxClient = SodaxClient.instance;\n }\n return SodaxClient.instance;\n }\n static reset() {\n SodaxClient.instance = null;\n sodaxClient = null;\n }\n}\n//# sourceMappingURL=client.js.map",
620 "inputSchema": {},
621 "outputSchema": null,
622 "icons": null,
623 "annotations": null,
624 "meta": null,
625 "execution": null
626 },
627 {
628 "name": "client.d.ts",
629 "title": null,
630 "description": "Script: client.d.ts. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\nimport { Sodax } from \"@sodax/sdk\";\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport declare function getSodaxClientAsync(): Promise<Sodax>;\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport declare function getSodaxClient(): Sodax;\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport declare function preInitializeSodax(): Promise<void>;\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport declare function resetSodaxClient(): void;\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport declare class SodaxClient {\n private static instance;\n static getClient(): Promise<Sodax>;\n static reset(): void;\n}\n//# sourceMappingURL=client.d.ts.map",
631 "inputSchema": {},
632 "outputSchema": null,
633 "icons": null,
634 "annotations": null,
635 "meta": null,
636 "execution": null
637 },
638 {
639 "name": "sdk.d.ts",
640 "title": null,
641 "description": "Script: sdk.d.ts. Code:\n/**\n * Mock for @sodax/sdk\n */\nexport declare class Sodax {\n static initialize: jest.Mock<any, any, any>;\n swaps: {\n getQuote: jest.Mock<any, any, any>;\n executeSwap: jest.Mock<any, any, any>;\n cancelIntent: jest.Mock<any, any, any>;\n };\n bridge: {\n getBridgeableTokens: jest.Mock<any, any, any>;\n bridge: jest.Mock<any, any, any>;\n };\n moneyMarket: {\n supply: jest.Mock<any, any, any>;\n withdraw: jest.Mock<any, any, any>;\n borrow: jest.Mock<any, any, any>;\n repay: jest.Mock<any, any, any>;\n getUserAccountDataOnSpoke: jest.Mock<any, any, any>;\n };\n}\nexport declare const Intent: {};\n//# sourceMappingURL=sdk.d.ts.map",
642 "inputSchema": {},
643 "outputSchema": null,
644 "icons": null,
645 "annotations": null,
646 "meta": null,
647 "execution": null
648 },
649 {
650 "name": "sdk.js",
651 "title": null,
652 "description": "Script: sdk.js. Code:\n/**\n * Mock for @sodax/sdk\n */\nexport class Sodax {\n static initialize = jest.fn().mockResolvedValue(undefined);\n swaps = {\n getQuote: jest.fn(),\n executeSwap: jest.fn(),\n cancelIntent: jest.fn(),\n };\n bridge = {\n getBridgeableTokens: jest.fn(),\n bridge: jest.fn(),\n };\n moneyMarket = {\n supply: jest.fn(),\n withdraw: jest.fn(),\n borrow: jest.fn(),\n repay: jest.fn(),\n getUserAccountDataOnSpoke: jest.fn(),\n };\n}\nexport const Intent = {};\n//# sourceMappingURL=sdk.js.map",
653 "inputSchema": {},
654 "outputSchema": null,
655 "icons": null,
656 "annotations": null,
657 "meta": null,
658 "execution": null
659 },
660 {
661 "name": "index.d.ts",
662 "title": null,
663 "description": "Script: index.d.ts. Code:\n/**\n * Amped DeFi Plugin\n *\n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\nimport { TSchema } from '@sinclair/typebox';\n/**\n * OpenClaw Plugin API (defined locally to avoid SDK dependency)\n */\ninterface OpenClawPluginApi {\n pluginConfig: Record<string, unknown>;\n logger: {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n debug: (msg: string) => void;\n };\n registerTool: (tool: {\n name: string;\n description: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: unknown) => Promise<{\n content: Array<{\n type: 'text';\n text: string;\n }>;\n details?: unknown;\n }>;\n }) => void;\n registerService: (service: {\n id: string;\n start: () => void;\n stop: () => Promise<void> | void;\n }) => void;\n on: (event: string, handler: (event: unknown, ctx: unknown) => unknown) => void;\n}\n/**\n * OpenClaw Plugin Definition\n */\ndeclare const _default: {\n id: string;\n name: string;\n description: string;\n kind: \"tools\";\n configSchema: import(\"@sinclair/typebox\").TObject<{\n walletsJson: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n rpcUrlsJson: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n mode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"execute\">, import(\"@sinclair/typebox\").TLiteral<\"simulate\">]>>;\n dynamicConfig: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n }>;\n register(api: OpenClawPluginApi): void;\n};\nexport default _default;\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport type { SpokeProvider } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\nexport type { IWalletBackend, WalletInfo, WalletBackendType } from './wallet/types';\nexport declare function activate(): Promise<void>;\nexport declare function deactivate(): Promise<void>;\n//# sourceMappingURL=index.d.ts.map",
664 "inputSchema": {},
665 "outputSchema": null,
666 "icons": null,
667 "annotations": null,
668 "meta": null,
669 "execution": null
670 },
671 {
672 "name": "moneyMarket.d.ts",
673 "title": null,
674 "description": "Script: moneyMarket.d.ts. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n *\n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n *\n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n *\n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\nimport { Static } from \"@sinclair/typebox\";\nimport { AgentTools } from \"../types\";\n/**\n * Base schema for money market operations\n */\ndeclare const MoneyMarketBaseSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\ndeclare const MoneyMarketSupplySchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n useAsCollateral: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\ndeclare const MoneyMarketWithdrawSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n withdrawType: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"default\">, import(\"@sinclair/typebox\").TLiteral<\"collateral\">, import(\"@sinclair/typebox\").TLiteral<\"all\">]>>;\n}>;\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n *\n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\ndeclare const MoneyMarketBorrowSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n referralCode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\ndeclare const MoneyMarketRepaySchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n repayAll: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n collateralChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\ndeclare const CreateSupplyIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n useAsCollateral: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateBorrowIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n referralCode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateWithdrawIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n withdrawType: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"default\">, import(\"@sinclair/typebox\").TLiteral<\"collateral\">, import(\"@sinclair/typebox\").TLiteral<\"all\">]>>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateRepayIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n repayAll: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n collateralChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ninterface MoneyMarketOperationResult {\n success: boolean;\n txHash?: string;\n status: \"success\" | \"pending\" | \"failed\";\n spokeTxHash?: string;\n hubTxHash?: string;\n intentHash?: string;\n operation: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n message?: string;\n warnings?: string[];\n isCrossChain?: boolean;\n srcSpokeTxHash?: string;\n dstSpokeTxHash?: string;\n rawIntent?: unknown;\n}\ninterface IntentResult extends MoneyMarketOperationResult {\n intentData: unknown;\n requiresSubmission: boolean;\n}\n/**\n * Supply tokens to the money market\n *\n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\ndeclare function handleSupply(params: Static<typeof MoneyMarketSupplySchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Withdraw tokens from the money market\n *\n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\ndeclare function handleWithdraw(params: Static<typeof MoneyMarketWithdrawSchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Borrow tokens from the money market\n *\n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n *\n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\ndeclare function handleBorrow(params: Static<typeof MoneyMarketBorrowSchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Repay borrowed tokens to the money market\n *\n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\ndeclare function handleRepay(params: Static<typeof MoneyMarketRepaySchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Create a supply intent without executing (for custom flows)\n */\ndeclare function handleCreateSupplyIntent(params: Static<typeof CreateSupplyIntentSchema>): Promise<IntentResult>;\n/**\n * Create a borrow intent without executing (for custom flows)\n */\ndeclare function handleCreateBorrowIntent(params: Static<typeof CreateBorrowIntentSchema>): Promise<IntentResult>;\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport declare function registerMoneyMarketTools(agentTools: AgentTools): void;\nexport { MoneyMarketBaseSchema, MoneyMarketSupplySchema, MoneyMarketWithdrawSchema, MoneyMarketBorrowSchema, MoneyMarketRepaySchema, CreateSupplyIntentSchema, CreateWithdrawIntentSchema, CreateBorrowIntentSchema, CreateRepayIntentSchema, handleSupply, handleWithdraw, handleBorrow, handleRepay, handleCreateSupplyIntent, handleCreateBorrowIntent, };\nexport { MoneyMarketSupplySchema as MmSupplySchema, MoneyMarketWithdrawSchema as MmWithdrawSchema, MoneyMarketBorrowSchema as MmBorrowSchema, MoneyMarketRepaySchema as MmRepaySchema, handleSupply as handleMmSupply, handleWithdraw as handleMmWithdraw, handleBorrow as handleMmBorrow, handleRepay as handleMmRepay, };\nexport type { MoneyMarketOperationResult, IntentResult };\n//# sourceMappingURL=moneyMarket.d.ts.map",
675 "inputSchema": {},
676 "outputSchema": null,
677 "icons": null,
678 "annotations": null,
679 "meta": null,
680 "execution": null
681 },
682 {
683 "name": "bridge.d.ts",
684 "title": null,
685 "description": "Script: bridge.d.ts. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts\n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\nimport { Static } from '@sinclair/typebox';\nimport { AgentTools } from '../types';\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\ndeclare const BridgeDiscoverSchema: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\ndeclare const BridgeQuoteSchema: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\ndeclare const BridgeExecuteSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ntype BridgeDiscoverParams = Static<typeof BridgeDiscoverSchema>;\ntype BridgeQuoteParams = Static<typeof BridgeQuoteSchema>;\ntype BridgeExecuteParams = Static<typeof BridgeExecuteSchema>;\n/**\n * Transaction result type for bridge execute\n */\ninterface TransactionResult {\n spokeTxHash: string;\n hubTxHash?: string;\n}\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\ndeclare function handleBridgeDiscover(params: BridgeDiscoverParams): Promise<{\n bridgeableTokens: string[];\n}>;\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\ndeclare function handleBridgeQuote(params: BridgeQuoteParams): Promise<{\n isBridgeable: boolean;\n maxBridgeableAmount: string;\n}>;\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\ndeclare function handleBridgeExecute(params: BridgeExecuteParams): Promise<TransactionResult>;\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport declare function registerBridgeTools(agentTools: AgentTools): void;\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n//# sourceMappingURL=bridge.d.ts.map",
686 "inputSchema": {},
687 "outputSchema": null,
688 "icons": null,
689 "annotations": null,
690 "meta": null,
691 "execution": null
692 },
693 {
694 "name": "discovery.js",
695 "title": null,
696 "description": "Script: discovery.js. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus, getPositionRecommendation } from '../utils/positionAggregator';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_supported_chains - no parameters required\n */\nconst SupportedChainsSchema = Type.Object({});\n/**\n * Schema for amped_supported_tokens\n */\nconst SupportedTokensSchema = Type.Object({\n module: Type.Union([\n Type.Literal('swaps'),\n Type.Literal('bridge'),\n Type.Literal('moneyMarket'),\n ]),\n chainId: Type.String({\n description: 'Spoke chain ID (e.g., \"ethereum\", \"arbitrum\", \"sonic\")',\n }),\n});\n/**\n * Schema for amped_wallet_address\n */\nconst WalletAddressSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n});\n/**\n * Schema for amped_money_market_positions\n */\nconst MoneyMarketPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainId: Type.String({\n description: 'Spoke chain ID to query positions on',\n }),\n});\n/**\n * Schema for amped_money_market_reserves\n */\nconst MoneyMarketReservesSchema = Type.Object({\n chainId: Type.Optional(Type.String({\n description: 'Optional chain ID. Money market is hub-centric, so this filters results for a specific spoke chain if needed',\n })),\n});\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\nconst CrossChainPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainIds: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional array of specific chain IDs to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include positions with zero balance',\n default: false,\n })),\n minUsdValue: Type.Optional(Type.Number({\n description: 'Minimum USD value threshold for including positions',\n default: 0.01,\n })),\n});\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\nconst UserIntentsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n status: Type.Optional(Type.Union([\n Type.Literal('all', { description: 'All intents (open and closed)' }),\n Type.Literal('open', { description: 'Only open/pending intents' }),\n Type.Literal('closed', { description: 'Only filled/cancelled/expired intents' }),\n ], {\n description: 'Filter by intent status',\n default: 'all',\n })),\n limit: Type.Optional(Type.Number({\n description: 'Number of items to return (default: 50, max: 100)',\n default: 50,\n minimum: 1,\n maximum: 100,\n })),\n offset: Type.Optional(Type.Number({\n description: 'Number of items to skip (for pagination)',\n default: 0,\n minimum: 0,\n })),\n});\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\nconst ListWalletsSchema = Type.Object({});\n// Helper to wrap typed handlers for AgentTools registration\nfunction wrapHandler(handler) {\n return (params) => handler(params);\n}\n// ============================================================================\n// Tool Implementations\n// ============================================================================\n/**\n * Get supported spoke chains from SODAX configuration\n */\nasync function handleSupportedChains(_params) {\n const sodax = getSodaxClient();\n const chains = sodax.config.getSupportedSpokeChains();\n // SDK may return chain IDs as strings or chain objects\n return {\n success: true,\n chains: chains.map((chain) => {\n // Handle both string IDs and chain objects\n if (typeof chain === 'string') {\n return {\n id: chain,\n name: chain,\n type: 'evm',\n isHub: chain === 'sonic',\n nativeCurrency: undefined,\n };\n }\n return {\n id: chain.id || chain,\n name: chain.name || chain.id || chain,\n type: chain.type || 'evm',\n isHub: (chain.id || chain) === 'sonic',\n nativeCurrency: chain.nativeCurrency,\n };\n }),\n };\n}\n/**\n * Get supported tokens for a specific module and chain\n */\nasync function handleSupportedTokens(params) {\n const sodax = getSodaxClient();\n const { module, chainId: rawChainId } = params;\n const chainId = toSodaxChainId(rawChainId);\n let tokens = [];\n // Helper to normalize token data\n const normalizeToken = (token) => ({\n address: token.address || '',\n symbol: token.symbol || '',\n name: token.name || token.symbol || '',\n decimals: token.decimals || 18,\n logoURI: token.logoURI || token.logoUri,\n });\n switch (module) {\n case 'swaps': {\n // Get supported swap tokens by chain ID\n // SDK may require chainId to be cast to specific type\n try {\n const swapTokens = sodax.config.getSupportedSwapTokensByChainId(chainId);\n tokens = (swapTokens || []).map(normalizeToken);\n }\n catch (e) {\n console.warn('[discovery] Failed to get swap tokens:', e);\n tokens = [];\n }\n break;\n }\n case 'bridge': {\n // Get bridgeable tokens via hub assets\n // Hub assets represent tokens that can be bridged between chains\n // Reference: sodax-frontend uses getHubAssets() for bridge token discovery\n try {\n const hubAssets = sodax.config.getHubAssets();\n // Check if this is the hub chain (Sonic)\n const isHubChain = rawChainId === 'sonic' || chainId === 'sonic';\n if (isHubChain) {\n // For Sonic (hub), show all bridgeable assets from all spoke chains\n // These are the assets that can be bridged FROM Sonic to other chains\n const allTokens = [];\n const seenAddresses = new Set();\n for (const spokeChainId of Object.keys(hubAssets)) {\n const chainAssets = hubAssets[spokeChainId] || {};\n for (const asset of Object.values(chainAssets)) {\n // Add the hub asset (on Sonic) - dedupe by hub address\n const hubAddress = asset.asset || asset.hubAddress || asset.address;\n if (hubAddress && !seenAddresses.has(hubAddress.toLowerCase())) {\n seenAddresses.add(hubAddress.toLowerCase());\n allTokens.push(normalizeToken({\n address: hubAddress,\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: asset.decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n }\n }\n tokens = allTokens;\n }\n else {\n // For spoke chains, get assets bridgeable from that specific chain\n const chainAssets = hubAssets[chainId] || {};\n tokens = Object.values(chainAssets).map((asset) => normalizeToken({\n address: asset.asset || asset.address || asset.originalAddress || '',\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: asset.decimal || asset.decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n }\n catch (e) {\n console.warn('[discovery] Failed to get bridge tokens:', e);\n tokens = [];\n }\n break;\n }\n case 'moneyMarket': {\n // Get money market supported tokens from config\n // Reference: sodax-frontend ConfigService.getSupportedMoneyMarketTokensByChainId\n try {\n const mmTokens = sodax.config.getSupportedMoneyMarketTokensByChainId?.(chainId);\n if (mmTokens && Array.isArray(mmTokens)) {\n tokens = mmTokens.map(normalizeToken);\n }\n else {\n // Fallback: try supportedMoneyMarketTokens directly from config\n const allMmTokens = sodax.config.sodaxConfig?.supportedMoneyMarketTokens;\n if (allMmTokens && allMmTokens[chainId]) {\n tokens = allMmTokens[chainId].map(normalizeToken);\n }\n else {\n console.warn('[discovery] No money market tokens found for chain', chainId);\n tokens = [];\n }\n }\n }\n catch (e) {\n console.warn('[discovery] Failed to get money market tokens:', e);\n tokens = [];\n }\n break;\n }\n default:\n throw new Error(`Unknown module: ${module}`);\n }\n return {\n success: true,\n module,\n chainId,\n tokens,\n count: tokens.length,\n };\n}\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\nasync function handleWalletAddress(params) {\n const { walletId } = params;\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const address = await wallet.getAddress();\n return {\n success: true,\n walletId: wallet.nickname,\n address,\n type: wallet.type,\n chains: [...wallet.supportedChains],\n };\n}\n/**\n * Get user money market positions (humanized format)\n */\nasync function handleMoneyMarketPositions(params) {\n const { walletId, chainId } = params;\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Get spoke provider for this wallet and chain\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n // IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n // To get token symbols/names, we must:\n // 1. Fetch getReservesHumanized() for token metadata\n // 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n // 3. Join with formatUserSummary(buildUserSummaryRequest())\n // Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(sodax.moneyMarket.data.buildReserveDataWithPrice(reserves));\n // Step 3: Fetch user-specific balances\n const userReservesResult = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReservesResult));\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = userSummary.userReservesData || [];\n // Format positions for readability\n const positions = userReservesData.map((reserve) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n return {\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n apy: supplyApy,\n collateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n apy: borrowApy,\n },\n // Health indicators\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n // Filter to only positions with activity\n const activePositions = positions.filter((p) => parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0);\n // Calculate summary metrics\n const totalSupplyUsd = activePositions.reduce((sum, p) => sum + (parseFloat(p.supply.balanceUsd) || 0), 0);\n const totalBorrowUsd = activePositions.reduce((sum, p) => sum + (parseFloat(p.borrow.balanceUsd) || 0), 0);\n const netWorthUsd = totalSupplyUsd - totalBorrowUsd;\n const healthFactor = totalBorrowUsd > 0 ? totalSupplyUsd / totalBorrowUsd : Infinity;\n return {\n success: true,\n walletId,\n address: walletAddress,\n chainId,\n positions: activePositions,\n summary: {\n totalSupplyUsd: totalSupplyUsd.toFixed(2),\n totalBorrowUsd: totalBorrowUsd.toFixed(2),\n netWorthUsd: netWorthUsd.toFixed(2),\n healthFactor: healthFactor === Infinity ? '\u221e' : healthFactor.toFixed(2),\n positionCount: activePositions.length,\n },\n };\n}\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\nasync function handleMoneyMarketReserves(params) {\n const { chainId } = params;\n const sodax = getSodaxClient();\n // Get reserves in humanized format (hub-centric)\n const reservesResult = await sodax.moneyMarket.data.getReservesHumanized();\n // SDK may return ReservesDataHumanized object with .reservesData array or just array\n const reservesArray = Array.isArray(reservesResult)\n ? reservesResult\n : reservesResult.reservesData || [];\n // Filter by chainId if provided\n let filteredReserves = reservesArray;\n if (chainId) {\n filteredReserves = reservesArray.filter((r) => r.token?.chainId === chainId || r.hubChainId === chainId || r.chainId === chainId);\n }\n // Format reserves for readability\n const formattedReserves = filteredReserves.map((reserve) => ({\n token: {\n address: reserve.token?.address || reserve.underlyingAsset || '',\n symbol: reserve.token?.symbol || reserve.symbol || '',\n name: reserve.token?.name || reserve.name || '',\n decimals: reserve.token?.decimals || reserve.decimals || 18,\n chainId: reserve.token?.chainId || reserve.chainId || '',\n },\n liquidity: {\n totalSupply: reserve.liquidity?.totalSupply || reserve.totalScaledVariableDebt || '0',\n availableLiquidity: reserve.liquidity?.availableLiquidity || reserve.availableLiquidity || '0',\n totalBorrow: reserve.liquidity?.totalBorrow || reserve.totalVariableDebt || '0',\n utilizationRate: reserve.liquidity?.utilizationRate || reserve.utilizationRate || '0',\n },\n rates: {\n supplyApy: reserve.rates?.supplyApy || reserve.supplyAPY || '0',\n borrowApy: reserve.rates?.borrowApy || reserve.variableBorrowAPY || '0',\n },\n parameters: {\n loanToValue: reserve.parameters?.loanToValue || reserve.baseLTVasCollateral || '0',\n liquidationThreshold: reserve.parameters?.liquidationThreshold || reserve.reserveLiquidationThreshold || '0',\n liquidationBonus: reserve.parameters?.liquidationBonus || reserve.reserveLiquidationBonus || '0',\n },\n hubChainId: reserve.hubChainId || 'sonic',\n }));\n // Calculate aggregate metrics\n const totalAvailableLiquidity = formattedReserves.reduce((sum, r) => sum + (parseFloat(r.liquidity.availableLiquidity) || 0), 0);\n const totalBorrowed = formattedReserves.reduce((sum, r) => sum + (parseFloat(r.liquidity.totalBorrow) || 0), 0);\n return {\n success: true,\n chainId: chainId || 'all',\n reserves: formattedReserves,\n summary: {\n reserveCount: formattedReserves.length,\n totalAvailableLiquidity: totalAvailableLiquidity.toFixed(2),\n totalBorrowed: totalBorrowed.toFixed(2),\n globalUtilizationRate: totalAvailableLiquidity + totalBorrowed > 0\n ? ((totalBorrowed / (totalAvailableLiquidity + totalBorrowed)) *\n 100).toFixed(2) + '%'\n : '0%',\n },\n };\n}\n// ============================================================================\n// Cross-Chain Positions Tool\n// ============================================================================\n/**\n * Get aggregated money market positions across all chains\n *\n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\nasync function handleCrossChainPositions(params) {\n const { walletId, chainIds, includeZeroBalances, minUsdValue } = params;\n console.log('[discovery:crossChainPositions] Aggregating positions', {\n walletId,\n chainIds: chainIds || 'all',\n includeZeroBalances,\n minUsdValue,\n });\n try {\n const view = await aggregateCrossChainPositions(walletId, {\n chainIds,\n includeZeroBalances,\n minUsdValue,\n });\n // Get recommendations\n const recommendations = getPositionRecommendation(view);\n // Format response\n const response = {\n success: true,\n walletId: view.walletId,\n address: view.address,\n timestamp: view.timestamp,\n summary: {\n totalSupplyUsd: view.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: view.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: view.summary.netWorthUsd.toFixed(2),\n availableBorrowUsd: view.summary.availableBorrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(view.summary.healthFactor),\n healthFactorStatus: getHealthFactorStatus(view.summary.healthFactor),\n liquidationRisk: view.summary.liquidationRisk,\n weightedSupplyApy: `${(view.summary.weightedSupplyApy * 100).toFixed(2)}%`,\n weightedBorrowApy: `${(view.summary.weightedBorrowApy * 100).toFixed(2)}%`,\n netApy: `${(view.summary.netApy * 100).toFixed(2)}%`,\n },\n chainBreakdown: view.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n netWorthUsd: cs.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n positionCount: cs.positionCount,\n })),\n collateralUtilization: {\n totalCollateralUsd: view.collateralUtilization.totalCollateralUsd.toFixed(2),\n usedCollateralUsd: view.collateralUtilization.usedCollateralUsd.toFixed(2),\n availableCollateralUsd: view.collateralUtilization.availableCollateralUsd.toFixed(2),\n utilizationRate: `${view.collateralUtilization.utilizationRate.toFixed(2)}%`,\n },\n riskMetrics: {\n maxLtv: `${(view.riskMetrics.maxLtv * 100).toFixed(2)}%`,\n currentLtv: `${(view.riskMetrics.currentLtv * 100).toFixed(2)}%`,\n bufferUntilLiquidation: `${view.riskMetrics.bufferUntilLiquidation.toFixed(2)}%`,\n safeMaxBorrowUsd: view.riskMetrics.safeMaxBorrowUsd.toFixed(2),\n },\n positions: view.positions.map(pos => ({\n chainId: pos.chainId,\n token: pos.token,\n supply: {\n balance: pos.supply.balance,\n balanceUsd: pos.supply.balanceUsd,\n apy: `${(pos.supply.apy * 100).toFixed(2)}%`,\n isCollateral: pos.supply.isCollateral,\n },\n borrow: {\n balance: pos.borrow.balance,\n balanceUsd: pos.borrow.balanceUsd,\n apy: `${(pos.borrow.apy * 100).toFixed(2)}%`,\n },\n loanToValue: `${(pos.loanToValue * 100).toFixed(2)}%`,\n liquidationThreshold: `${(pos.liquidationThreshold * 100).toFixed(2)}%`,\n })),\n recommendations,\n };\n console.log('[discovery:crossChainPositions] Aggregation complete', {\n totalPositions: view.positions.length,\n totalSupplyUsd: view.summary.totalSupplyUsd,\n totalBorrowUsd: view.summary.totalBorrowUsd,\n healthFactor: view.summary.healthFactor,\n });\n return response;\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:crossChainPositions] Failed to aggregate positions', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to aggregate cross-chain positions: ${errorMessage}`);\n }\n}\n// ============================================================================\n// User Intents Tool (SODAX API)\n// ============================================================================\n/**\n * Get user intents from SODAX API\n *\n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\nasync function handleUserIntents(params) {\n const { walletId, status = 'all', limit = 50, offset = 0 } = params;\n console.log('[discovery:userIntents] Fetching user intents', {\n walletId,\n status,\n limit,\n offset,\n });\n try {\n // Get wallet address from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Initialize API client\n const apiClient = getSodaxApiClient();\n // Determine filters based on status\n let filters;\n if (status === 'open') {\n filters = { open: true };\n }\n else if (status === 'closed') {\n filters = { open: false };\n }\n // Fetch intents\n const response = await apiClient.getUserIntents(walletAddress, { limit, offset }, filters);\n // Format response\n const formattedIntents = response.items.map(intent => ({\n intentHash: intent.intentHash,\n txHash: intent.txHash,\n chainId: intent.chainId,\n blockNumber: intent.blockNumber,\n status: intent.open ? 'open' : 'closed',\n createdAt: intent.createdAt,\n input: {\n token: intent.intent.inputToken,\n amount: intent.intent.inputAmount,\n chainId: intent.intent.srcChain,\n },\n output: {\n token: intent.intent.outputToken,\n minAmount: intent.intent.minOutputAmount,\n chainId: intent.intent.dstChain,\n },\n deadline: new Date(parseInt(intent.intent.deadline) * 1000).toISOString(),\n allowPartialFill: intent.intent.allowPartialFill,\n events: intent.events\n .filter((event) => event.intentState != null)\n .map(event => ({\n type: event.eventType,\n txHash: event.txHash,\n blockNumber: event.blockNumber,\n state: {\n remainingInput: event.intentState.remainingInput,\n receivedOutput: event.intentState.receivedOutput,\n pendingPayment: event.intentState.pendingPayment,\n },\n })),\n }));\n const result = {\n success: true,\n walletId,\n address: walletAddress,\n pagination: {\n total: response.total,\n offset: response.offset,\n limit: response.limit,\n hasMore: response.offset + response.items.length < response.total,\n },\n intents: formattedIntents,\n summary: {\n totalIntents: response.total,\n returned: formattedIntents.length,\n openIntents: formattedIntents.filter((i) => i.status === 'open').length,\n closedIntents: formattedIntents.filter((i) => i.status === 'closed').length,\n },\n };\n console.log('[discovery:userIntents] User intents fetched', {\n total: response.total,\n returned: formattedIntents.length,\n });\n return result;\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:userIntents] Failed to fetch user intents', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to fetch user intents: ${errorMessage}`);\n }\n}\n// ============================================================================\n// List Wallets Tool\n// ============================================================================\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\nasync function handleListWallets(_params) {\n console.log('[discovery:listWallets] Listing configured wallets');\n const walletManager = getWalletManager();\n const wallets = await walletManager.listWallets();\n const formattedWallets = wallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n address: w.address,\n addressKnown: w.address !== '0x...',\n supportedChains: w.chains,\n isDefault: w.isDefault,\n note: w.address === '0x...' && w.type === 'bankr'\n ? 'Address pending - will be fetched on first use'\n : undefined,\n }));\n const defaultWallet = await walletManager.getDefaultWalletName();\n // Group by type for summary\n const byType = {\n 'evm-wallet-skill': formattedWallets.filter(w => w.type === 'evm-wallet-skill'),\n 'bankr': formattedWallets.filter(w => w.type === 'bankr'),\n 'env': formattedWallets.filter(w => w.type === 'env'),\n };\n // Check if Bankr is configured but wallet not found\n const bankrKeyPresent = !!process.env.BANKR_API_KEY;\n const bankrWalletFound = byType.bankr.length > 0;\n return {\n success: true,\n wallets: formattedWallets,\n defaultWallet,\n count: formattedWallets.length,\n summary: {\n selfCustody: byType['evm-wallet-skill'].length + byType.env.length,\n bankrManaged: byType.bankr.length,\n },\n sources: {\n evmWalletSkill: byType['evm-wallet-skill'].length > 0,\n bankr: bankrWalletFound,\n bankrKeyConfigured: bankrKeyPresent,\n env: byType.env.length > 0,\n },\n hint: wallets.length === 0\n ? 'No wallets configured. Install evm-wallet-skill: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill'\n : bankrKeyPresent && !bankrWalletFound\n ? 'Bankr API key found but wallet not loaded. Try \"Add my bankr wallet\" to register it.'\n : 'Use wallet nickname in operations, e.g., \"swap 100 USDC to ETH using main\"',\n };\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport function registerDiscoveryTools(agentTools) {\n // 1. amped_supported_chains - Get supported spoke chains\n agentTools.register({\n name: 'amped_supported_chains',\n summary: 'Get a list of all supported spoke chains for swaps, bridging, and money market operations',\n schema: SupportedChainsSchema,\n handler: wrapHandler(handleSupportedChains),\n });\n // 2. amped_supported_tokens - Get supported tokens by module\n agentTools.register({\n name: 'amped_supported_tokens',\n summary: 'Get supported tokens for a specific module (swaps, bridge, or moneyMarket) on a given chain',\n schema: SupportedTokensSchema,\n handler: wrapHandler(handleSupportedTokens),\n });\n // 3. amped_wallet_address - Get wallet address\n agentTools.register({\n name: 'amped_wallet_address',\n summary: 'Get the resolved wallet address for a given walletId. Validates private key matches in execute mode.',\n schema: WalletAddressSchema,\n handler: wrapHandler(handleWalletAddress),\n });\n // 4. amped_money_market_positions - Get user positions (humanized)\n agentTools.register({\n name: 'amped_money_market_positions',\n summary: 'Get humanized money market positions for a wallet on a specific chain, including supply/borrow balances and health metrics',\n schema: MoneyMarketPositionsSchema,\n handler: wrapHandler(handleMoneyMarketPositions),\n });\n // 5. amped_money_market_reserves - Get market reserves (humanized)\n agentTools.register({\n name: 'amped_money_market_reserves',\n summary: 'Get humanized money market reserves data including liquidity, rates, and parameters. Hub-centric with optional chain filtering.',\n schema: MoneyMarketReservesSchema,\n handler: wrapHandler(handleMoneyMarketReserves),\n });\n // 6. amped_cross_chain_positions - Get aggregated positions across all chains\n agentTools.register({\n name: 'amped_cross_chain_positions',\n summary: 'Get a unified view of money market positions across ALL chains. Shows total supply/borrow, health factor, borrowing power, net APY, and risk metrics.',\n description: 'Aggregates money market positions across all supported chains to provide a comprehensive portfolio view. ' +\n 'Includes: total supply/borrow in USD, health factor with risk status, available borrowing power, ' +\n 'weighted APYs, collateral utilization, and personalized recommendations. ' +\n 'This is the recommended tool for getting a complete picture of money market positions.',\n schema: CrossChainPositionsSchema,\n handler: wrapHandler(handleCrossChainPositions),\n });\n // 7. amped_user_intents - Query user intent history from SODAX API\n agentTools.register({\n name: 'amped_user_intents',\n summary: 'Query user swap intent history from SODAX backend API. Shows open, filled, and cancelled intents with event details.',\n description: 'Retrieves the complete intent history for a wallet from the SODAX backend API. ' +\n 'Includes open intents (pending), filled intents (completed), and cancelled/expired intents. ' +\n 'Each intent includes input/output tokens, amounts, chain IDs, and event history. ' +\n 'Use this to track the status of past swaps and bridge operations.',\n schema: UserIntentsSchema,\n handler: wrapHandler(handleUserIntents),\n });\n // 8. amped_list_wallets - List all configured wallets\n agentTools.register({\n name: 'amped_list_wallets',\n summary: 'List all configured wallets with their nicknames, types, addresses, and supported chains.',\n description: 'Shows all available wallets from evm-wallet-skill (~/.evm-wallet.json), Bankr API, ' +\n 'and environment variables (AMPED_OC_WALLETS_JSON). Each wallet has a nickname that can be ' +\n 'used in operations like \"swap 100 USDC using bankr\" or \"check balance on main\". ' +\n 'Also shows which chains each wallet supports.',\n schema: ListWalletsSchema,\n handler: wrapHandler(handleListWallets),\n });\n}\n// Export schemas for testing and reuse\nexport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketPositionsSchema, MoneyMarketReservesSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, };\n// Export handlers for testing\nexport { handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketPositions, handleMoneyMarketReserves, handleCrossChainPositions, handleUserIntents, handleListWallets, };\n//# sourceMappingURL=discovery.js.map",
697 "inputSchema": {},
698 "outputSchema": null,
699 "icons": null,
700 "annotations": null,
701 "meta": null,
702 "execution": null
703 },
704 {
705 "name": "bridge.js",
706 "title": null,
707 "description": "Script: bridge.js. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts\n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\nimport { handleSwapQuote, handleSwapExecute } from './swap';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\nconst BridgeDiscoverSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID (e.g., \"ethereum\", \"arbitrum\")',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID (e.g., \"sonic\", \"optimism\")',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n});\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\nconst BridgeQuoteSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol',\n }),\n});\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\nconst BridgeExecuteSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet to use',\n }),\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol to bridge from',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol to bridge to',\n }),\n amount: Type.String({\n description: 'Amount to bridge in human-readable units (e.g., \"100.5\")',\n }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain (defaults to wallet address)',\n })),\n timeoutMs: Type.Optional(Type.Number({\n description: 'Timeout for bridge operation in milliseconds',\n default: 300000, // 5 minutes\n })),\n policyId: Type.Optional(Type.String({\n description: 'Optional policy profile ID for custom limits',\n })),\n});\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\nasync function handleBridgeDiscover(params) {\n const { srcChainId, dstChainId, srcToken } = params;\n // Resolve token symbol to address\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n console.log('[bridge:discover] Discovering bridgeable tokens', {\n srcChainId,\n dstChainId,\n srcToken,\n });\n try {\n const sodax = getSodaxClient();\n // Get bridgeable tokens from SODAX SDK\n // SDK API: getBridgeableTokens(from: SpokeChainId, to: SpokeChainId, token: string)\n const result = sodax.bridge.getBridgeableTokens(toSodaxChainId(srcChainId), toSodaxChainId(dstChainId), srcTokenAddr);\n // Handle Result type - SDK returns Result<XToken[], unknown>\n if (!result.ok) {\n throw new Error(`Failed to get bridgeable tokens: ${serializeError(result.error) || 'Unknown error'}`);\n }\n const tokens = result.value;\n const bridgeableTokens = tokens.map((t) => t.address || t.symbol || String(t));\n console.log('[bridge:discover] Found bridgeable tokens', {\n count: bridgeableTokens.length,\n tokens: bridgeableTokens,\n });\n return { bridgeableTokens };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:discover] Failed to discover bridgeable tokens', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n });\n throw new Error(`Failed to discover bridgeable tokens: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Bridge Quote Tool\n// ============================================================================\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\nasync function handleBridgeQuote(params) {\n const { srcChainId, dstChainId, srcToken, dstToken } = params;\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n const dstTokenAddr = await resolveToken(dstChainId, dstToken);\n console.log('[bridge:quote] Checking bridge quote', {\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n try {\n const sodax = getSodaxClient();\n // Create XToken objects for the SDK\n const fromToken = { chainId: toSodaxChainId(srcChainId), address: srcTokenAddr };\n const toToken = { chainId: toSodaxChainId(dstChainId), address: dstTokenAddr };\n // Check if the route is bridgeable using isBridgeable\n // SDK may have different signature - adapting based on available methods\n let isBridgeable = false;\n try {\n // Try to get bridgeable tokens to check if route exists\n const result = sodax.bridge.getBridgeableTokens(toSodaxChainId(srcChainId), toSodaxChainId(dstChainId), srcTokenAddr);\n if (result.ok && result.value.length > 0) {\n isBridgeable = result.value.some((t) => t.address?.toLowerCase() === dstTokenAddr.toLowerCase() ||\n t === dstTokenAddr);\n }\n }\n catch {\n isBridgeable = false;\n }\n // Get maximum bridgeable amount\n let maxBridgeableAmount = '0';\n if (isBridgeable) {\n try {\n // SDK API: getBridgeableAmount(from: XToken, to: XToken)\n const maxAmountResult = await sodax.bridge.getBridgeableAmount(fromToken, toToken);\n if (maxAmountResult.ok) {\n const val = maxAmountResult.value;\n // BridgeLimit may have different property names depending on SDK version\n maxBridgeableAmount = val?.max?.toString() ||\n val?.maxAmount?.toString() ||\n val?.limit?.toString() ||\n val?.toString() || '0';\n }\n }\n catch (e) {\n console.warn('[bridge:quote] Could not get max bridgeable amount:', e);\n }\n }\n console.log('[bridge:quote] Bridge quote result', {\n isBridgeable,\n maxBridgeableAmount,\n });\n return { isBridgeable, maxBridgeableAmount };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:quote] Failed to get bridge quote', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n throw new Error(`Failed to get bridge quote: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Bridge Execute Tool (Delegates to Swap)\n// ============================================================================\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\nasync function handleBridgeExecute(params) {\n const { walletId, srcChainId, dstChainId, srcToken, dstToken, amount, recipient, timeoutMs = 300000, policyId, } = params;\n console.log('[bridge:execute] Delegating to swap infrastructure', {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n });\n try {\n // Step 1: Get a swap quote for this bridge route\n const quoteResult = await handleSwapQuote({\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n type: 'exact_input',\n slippageBps: 100, // 1% slippage for bridges\n recipient,\n });\n console.log('[bridge:execute] Got swap quote', quoteResult);\n // Step 2: Execute the swap\n const swapResult = await handleSwapExecute({\n walletId,\n quote: {\n srcChainId,\n dstChainId,\n srcToken: String(quoteResult.srcToken),\n dstToken: String(quoteResult.dstToken),\n inputAmount: String(quoteResult.inputAmount),\n outputAmount: String(quoteResult.outputAmount),\n slippageBps: Number(quoteResult.slippageBps),\n deadline: Number(quoteResult.deadline),\n recipient,\n },\n policyId,\n timeoutMs,\n });\n console.log('[bridge:execute] Swap executed', swapResult);\n // Map swap result to bridge result format\n return {\n spokeTxHash: String(swapResult.initiationTx || swapResult.spokeTxHash || ''),\n hubTxHash: swapResult.hubTxHash ? String(swapResult.hubTxHash) : undefined,\n status: String(swapResult.status),\n message: swapResult.message ? String(swapResult.message) : 'Bridge executed via swap infrastructure',\n sodaxScanUrl: swapResult.sodaxScanUrl ? String(swapResult.sodaxScanUrl) : undefined,\n };\n }\n catch (error) {\n const errorMessage = serializeError(error);\n console.error('[bridge:execute] Bridge via swap failed:', errorMessage);\n throw new Error(`Bridge execution failed: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport function registerBridgeTools(agentTools) {\n // Register bridge discover tool\n agentTools.register({\n name: 'amped_bridge_discover',\n summary: 'Discover bridgeable tokens for a given source chain and token',\n description: 'Retrieves a list of tokens that can be bridged from the specified source chain ' +\n 'to the destination chain, starting from a specific source token. ' +\n 'Use this to find valid bridge routes before requesting a quote.',\n schema: BridgeDiscoverSchema,\n handler: handleBridgeDiscover,\n });\n console.log('[bridge] Registered tool: amped_bridge_discover');\n // Register bridge quote tool\n agentTools.register({\n name: 'amped_bridge_quote',\n summary: 'Check bridgeability and get maximum bridgeable amount',\n description: 'Validates whether a specific bridge route (source chain/token \u2192 destination chain/token) ' +\n 'is supported and returns the maximum amount that can be bridged. ' +\n 'Always call this before executing a bridge to verify the route is valid.',\n schema: BridgeQuoteSchema,\n handler: handleBridgeQuote,\n });\n console.log('[bridge] Registered tool: amped_bridge_quote');\n // Register bridge execute tool\n agentTools.register({\n name: 'amped_bridge_execute',\n summary: 'Execute a cross-chain bridge operation',\n description: 'Executes a bridge operation that moves tokens from a source chain to a destination chain. ' +\n 'This tool handles the complete flow: policy validation, allowance checking, ' +\n 'token approval (if needed), and bridge execution. ' +\n 'Returns transaction hashes for both the spoke chain and hub chain.',\n schema: BridgeExecuteSchema,\n handler: handleBridgeExecute,\n });\n console.log('[bridge] Registered tool: amped_bridge_execute');\n}\n// Export schemas for testing and reuse\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\n// Export handlers\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n//# sourceMappingURL=bridge.js.map",
708 "inputSchema": {},
709 "outputSchema": null,
710 "icons": null,
711 "annotations": null,
712 "meta": null,
713 "execution": null
714 },
715 {
716 "name": "walletManagement.d.ts",
717 "title": null,
718 "description": "Script: walletManagement.d.ts. Code:\n/**\n * Wallet Management Tools\n *\n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n *\n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_add_wallet\n */\ndeclare const AddWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n source: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"evm-wallet-skill\">, import(\"@sinclair/typebox\").TLiteral<\"bankr\">, import(\"@sinclair/typebox\").TLiteral<\"env\">]>;\n path: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n apiKey: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n apiUrl: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n address: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n privateKey: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n chains: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n}>;\n/**\n * Schema for amped_rename_wallet\n */\ndeclare const RenameWalletSchema: import(\"@sinclair/typebox\").TObject<{\n currentNickname: import(\"@sinclair/typebox\").TString;\n newNickname: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_remove_wallet\n */\ndeclare const RemoveWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n confirm: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Schema for amped_set_default_wallet\n */\ndeclare const SetDefaultWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n}>;\ntype AddWalletParams = Static<typeof AddWalletSchema>;\ntype RenameWalletParams = Static<typeof RenameWalletSchema>;\ntype RemoveWalletParams = Static<typeof RemoveWalletSchema>;\ntype SetDefaultWalletParams = Static<typeof SetDefaultWalletSchema>;\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n/**\n * Add a new wallet with a nickname\n */\ndeclare function handleAddWallet(params: AddWalletParams): Promise<unknown>;\n/**\n * Rename a wallet\n */\ndeclare function handleRenameWallet(params: RenameWalletParams): Promise<unknown>;\n/**\n * Remove a wallet\n */\ndeclare function handleRemoveWallet(params: RemoveWalletParams): Promise<unknown>;\n/**\n * Set default wallet\n */\ndeclare function handleSetDefaultWallet(params: SetDefaultWalletParams): Promise<unknown>;\n/**\n * Register wallet management tools\n */\nexport declare function registerWalletManagementTools(agentTools: AgentTools): void;\nexport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet, };\n//# sourceMappingURL=walletManagement.d.ts.map",
719 "inputSchema": {},
720 "outputSchema": null,
721 "icons": null,
722 "annotations": null,
723 "meta": null,
724 "execution": null
725 },
726 {
727 "name": "portfolio.js",
728 "title": null,
729 "description": "Script: portfolio.js. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\nimport { Type } from '@sinclair/typebox';\nimport { createPublicClient, http, formatUnits } from 'viem';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus } from '../utils/positionAggregator';\nimport { fetchTokenPrices } from '../utils/priceService';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId, CHAIN_IDS, } from '../wallet/providers/chainConfig';\n// ============================================================================\n// TypeBox Schema\n// ============================================================================\n/**\n * Schema for amped_portfolio_summary\n */\nexport const PortfolioSummarySchema = Type.Object({\n walletId: Type.Optional(Type.String({\n description: 'Specific wallet to query (defaults to all wallets)',\n })),\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Specific chains to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include tokens with zero balance',\n default: false,\n })),\n});\n/**\n * Major tokens to check on each chain\n */\nconst MAJOR_TOKENS = {\n [CHAIN_IDS.ETHEREUM]: [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 },\n { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BASE]: [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.ARBITRUM]: [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', decimals: 6 },\n { address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.OPTIMISM]: [\n { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', symbol: 'USDC', decimals: 6 },\n { address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', symbol: 'USDT', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.POLYGON]: [\n { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', decimals: 6 },\n { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 },\n { address: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BSC]: [\n { address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', symbol: 'USDC', decimals: 18 },\n { address: '0x55d398326f99059fF775485246999027B3197955', symbol: 'USDT', decimals: 18 },\n { address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.AVALANCHE]: [\n { address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', symbol: 'USDC', decimals: 6 },\n { address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', symbol: 'USDT', decimals: 6 },\n { address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.SONIC]: [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', decimals: 6 },\n ],\n [CHAIN_IDS.LIGHTLINK]: [\n { address: '0xbCF8C1B03bBDDA88D579330BDF236B58F8bb2cFd', symbol: 'USDC', decimals: 6 },\n ],\n};\n/**\n * Native token symbols by chain\n */\nconst NATIVE_SYMBOLS = {\n [CHAIN_IDS.ETHEREUM]: 'ETH',\n [CHAIN_IDS.ARBITRUM]: 'ETH',\n [CHAIN_IDS.OPTIMISM]: 'ETH',\n [CHAIN_IDS.BASE]: 'ETH',\n [CHAIN_IDS.POLYGON]: 'POL',\n [CHAIN_IDS.BSC]: 'BNB',\n [CHAIN_IDS.AVALANCHE]: 'AVAX',\n [CHAIN_IDS.SONIC]: 'S',\n [CHAIN_IDS.LIGHTLINK]: 'ETH',\n [CHAIN_IDS.HYPEREVM]: 'HYPE',\n [CHAIN_IDS.KAIA]: 'KAIA',\n};\n/**\n * Chain ID to name mapping\n */\nconst CHAIN_NAMES = {\n [CHAIN_IDS.ETHEREUM]: 'Ethereum',\n [CHAIN_IDS.ARBITRUM]: 'Arbitrum',\n [CHAIN_IDS.OPTIMISM]: 'Optimism',\n [CHAIN_IDS.BASE]: 'Base',\n [CHAIN_IDS.POLYGON]: 'Polygon',\n [CHAIN_IDS.BSC]: 'BSC',\n [CHAIN_IDS.AVALANCHE]: 'Avalanche',\n [CHAIN_IDS.SONIC]: 'Sonic',\n [CHAIN_IDS.LIGHTLINK]: 'LightLink',\n [CHAIN_IDS.HYPEREVM]: 'HyperEVM',\n [CHAIN_IDS.KAIA]: 'Kaia',\n};\n/**\n * Chain name strings for wallet support check\n */\nconst CHAIN_NAME_STRINGS = {\n [CHAIN_IDS.ETHEREUM]: ['ethereum'],\n [CHAIN_IDS.BASE]: ['base'],\n [CHAIN_IDS.ARBITRUM]: ['arbitrum'],\n [CHAIN_IDS.OPTIMISM]: ['optimism'],\n [CHAIN_IDS.POLYGON]: ['polygon'],\n [CHAIN_IDS.BSC]: ['bsc'],\n [CHAIN_IDS.AVALANCHE]: ['avalanche', 'avax'],\n [CHAIN_IDS.SONIC]: ['sonic'],\n [CHAIN_IDS.LIGHTLINK]: ['lightlink'],\n};\n// ============================================================================\n// Helper Functions\n// ============================================================================\n/**\n * Create a viem public client for a chain\n */\nfunction createClient(chainId) {\n const chain = getViemChain(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n return createPublicClient({\n chain,\n transport: http(rpcUrl, { timeout: 10000 }),\n });\n}\n/**\n * Get native balance for a wallet on a chain\n */\nasync function getNativeBalance(client, address, chainId) {\n try {\n const balance = await client.getBalance({ address });\n return {\n symbol: NATIVE_SYMBOLS[chainId] || 'ETH',\n balance: formatUnits(balance, 18),\n balanceRaw: balance,\n };\n }\n catch (error) {\n console.error(`[portfolio] Failed to get native balance on chain ${chainId}:`, error);\n return { symbol: NATIVE_SYMBOLS[chainId] || 'ETH', balance: '0', balanceRaw: 0n };\n }\n}\n/**\n * Get ERC20 token balance using eth_call directly (avoids viem type issues)\n */\nasync function getTokenBalance(rpcUrl, walletAddress, tokenAddress, decimals, symbol) {\n try {\n // balanceOf(address) selector: 0x70a08231\n const paddedAddress = walletAddress.slice(2).toLowerCase().padStart(64, '0');\n const callData = `0x70a08231${paddedAddress}`;\n const response = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to: tokenAddress, data: callData }, 'latest'],\n id: 1,\n }),\n });\n const json = await response.json();\n const result = json.result;\n if (!result || result === '0x' || result === '0x0') {\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n const balanceRaw = BigInt(result);\n const balance = formatUnits(balanceRaw, decimals);\n return { symbol, balance, balanceRaw, address: tokenAddress };\n }\n catch (error) {\n console.error(`[portfolio] Failed to get ${symbol} balance:`, error);\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n}\n/**\n * Query all balances for a wallet on a specific chain\n */\nasync function getChainBalances(address, chainId, includeZeroBalances) {\n const client = createClient(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n const chainName = CHAIN_NAMES[chainId] || `Chain ${chainId}`;\n // Get native balance\n const native = await getNativeBalance(client, address, chainId);\n // Get token balances\n const tokenConfigs = MAJOR_TOKENS[chainId] || [];\n const tokenPromises = tokenConfigs.map((t) => getTokenBalance(rpcUrl, address, t.address, t.decimals, t.symbol));\n const tokenResults = await Promise.all(tokenPromises);\n // Filter zero balances if requested\n const tokens = includeZeroBalances\n ? tokenResults.map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }))\n : tokenResults\n .filter((t) => t.balanceRaw > 0n)\n .map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }));\n return {\n chainId: chainId.toString(),\n chainName,\n native: {\n symbol: native.symbol,\n balance: parseFloat(native.balance).toFixed(6),\n },\n tokens,\n };\n}\n/**\n * Handle portfolio summary request\n */\nexport async function handlePortfolioSummary(params) {\n const { walletId, chains, includeZeroBalances = false } = params;\n console.log('[portfolio:summary] Fetching portfolio summary', {\n walletId: walletId || 'all',\n chains: chains || 'all',\n includeZeroBalances,\n });\n // Fetch token prices from SODAX (cached, 1 min TTL)\n let priceMap = null;\n try {\n priceMap = await fetchTokenPrices();\n }\n catch (err) {\n console.warn('[portfolio] Failed to fetch prices, USD values will be unavailable:', err);\n }\n const walletManager = getWalletManager();\n const allWallets = await walletManager.listWallets();\n // Filter to specific wallet if requested\n const walletsToQuery = walletId\n ? allWallets.filter((w) => w.nickname === walletId)\n : allWallets;\n if (walletsToQuery.length === 0) {\n return {\n success: false,\n error: walletId ? `Wallet not found: ${walletId}` : 'No wallets configured',\n };\n }\n // Determine chains to query\n // Query all chains with configured tokens by default\n const defaultChains = [\n CHAIN_IDS.BASE,\n CHAIN_IDS.ETHEREUM,\n CHAIN_IDS.ARBITRUM,\n CHAIN_IDS.OPTIMISM,\n CHAIN_IDS.POLYGON,\n CHAIN_IDS.SONIC,\n CHAIN_IDS.BSC,\n CHAIN_IDS.AVALANCHE,\n CHAIN_IDS.LIGHTLINK,\n ];\n const chainIdsToQuery = chains\n ? chains.map((c) => resolveChainId(c))\n : defaultChains;\n const results = [];\n let totalValueUsd = 0;\n // Helper to get USD price for a symbol\n const getPrice = (symbol) => {\n if (!priceMap)\n return null;\n const lower = symbol.toLowerCase();\n return priceMap.bySymbol.get(lower) ?? priceMap.bySymbol.get('soda' + lower) ?? null;\n };\n for (const wallet of walletsToQuery) {\n // Skip wallets without known addresses\n if (wallet.address === '0x...') {\n console.log(`[portfolio] Skipping wallet ${wallet.nickname} - address not resolved`);\n continue;\n }\n const address = wallet.address;\n // Filter chains to those the wallet supports\n const supportedChains = wallet.chains || [];\n const chainsForWallet = chainIdsToQuery.filter((cid) => {\n const names = CHAIN_NAME_STRINGS[cid] || [];\n return supportedChains.length === 0 || names.some((n) => supportedChains.includes(n));\n });\n // Query balances for each chain (in parallel)\n const balancePromises = chainsForWallet.map((cid) => getChainBalances(address, cid, includeZeroBalances).catch((err) => {\n console.error(`[portfolio] Failed to query chain ${cid}:`, err);\n return null;\n }));\n const balanceResults = (await Promise.all(balancePromises)).filter((b) => b !== null);\n // Filter out chains with no balances if not including zeros\n const filteredBalances = includeZeroBalances\n ? balanceResults\n : balanceResults.filter((b) => parseFloat(b.native.balance) > 0 || b.tokens.length > 0);\n // Add USD values to balances\n let walletBalanceUsd = 0;\n const balancesWithUsd = filteredBalances.map((chainBalance) => {\n let chainTotalUsd = 0;\n // Native token USD value\n const nativePrice = getPrice(chainBalance.native.symbol);\n const nativeBalance = parseFloat(chainBalance.native.balance);\n const nativeUsdValue = nativePrice ? nativeBalance * nativePrice : null;\n if (nativeUsdValue)\n chainTotalUsd += nativeUsdValue;\n // Token USD values\n const tokensWithUsd = chainBalance.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue)\n chainTotalUsd += usdValue;\n return {\n ...token,\n usdValue: usdValue ? `$${usdValue.toFixed(2)}` : undefined,\n };\n });\n walletBalanceUsd += chainTotalUsd;\n return {\n chainId: chainBalance.chainId,\n chainName: chainBalance.chainName,\n native: {\n symbol: chainBalance.native.symbol,\n balance: chainBalance.native.balance,\n usdValue: nativeUsdValue ? `$${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: chainTotalUsd > 0 ? `$${chainTotalUsd.toFixed(2)}` : undefined,\n };\n });\n // Query Solana balances if wallet has a Solana address\n // Bankr wallets have a separate Solana address that can be cached\n const solanaAddress = wallet.solanaAddress;\n if (solanaAddress) {\n try {\n const solanaBalances = await getSolanaWalletBalances(solanaAddress, includeZeroBalances);\n if (solanaBalances) {\n // Add USD values for Solana\n let solanaTotalUsd = 0;\n const solPrice = getPrice('SOL');\n const nativeBalance = parseFloat(solanaBalances.native.balance);\n const nativeUsdValue = solPrice ? nativeBalance * solPrice : null;\n if (nativeUsdValue)\n solanaTotalUsd += nativeUsdValue;\n const tokensWithUsd = solanaBalances.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue)\n solanaTotalUsd += usdValue;\n return { ...token, usdValue: usdValue ? `${usdValue.toFixed(2)}` : undefined };\n });\n walletBalanceUsd += solanaTotalUsd;\n balancesWithUsd.push({\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: solanaBalances.native.symbol,\n balance: solanaBalances.native.balance,\n usdValue: nativeUsdValue ? `${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: solanaTotalUsd > 0 ? `${solanaTotalUsd.toFixed(2)}` : undefined,\n });\n }\n }\n catch (err) {\n console.error(`[portfolio] Failed to get Solana balances for ${wallet.nickname}:`, err);\n }\n }\n // Get money market positions (aggregate)\n let mmSummary;\n try {\n const positions = await aggregateCrossChainPositions(wallet.nickname);\n if (positions && (positions.summary.totalSupplyUsd > 0 || positions.summary.totalBorrowUsd > 0)) {\n const hfStatus = getHealthFactorStatus(positions.summary.healthFactor);\n // Build per-chain breakdown with individual health factors\n const chainBreakdown = positions.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n healthStatus: getHealthFactorStatus(cs.healthFactor),\n }));\n mmSummary = {\n totalSupplyUsd: positions.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: positions.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: positions.summary.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(positions.summary.healthFactor),\n healthStatus: hfStatus,\n chainBreakdown,\n };\n // MM net worth is already USD - add to wallet total\n walletBalanceUsd += positions.summary.netWorthUsd;\n }\n }\n catch (err) {\n console.error(`[portfolio] Failed to get MM positions for ${wallet.nickname}:`, err);\n }\n totalValueUsd += walletBalanceUsd;\n results.push({\n wallet: {\n nickname: wallet.nickname,\n address: wallet.address,\n type: wallet.type,\n },\n balances: balancesWithUsd,\n moneyMarket: mmSummary,\n walletTotalUsd: walletBalanceUsd > 0 ? `$${walletBalanceUsd.toFixed(2)}` : undefined,\n });\n }\n // Build summary\n const summary = {\n walletCount: results.length,\n chainsQueried: chainIdsToQuery.length,\n timestamp: new Date().toISOString(),\n estimatedTotalUsd: totalValueUsd > 0 ? `$${totalValueUsd.toFixed(2)}` : 'No positions',\n priceSource: priceMap ? 'SODAX' : 'unavailable',\n };\n return {\n success: true,\n summary,\n wallets: results,\n };\n}\n// ============================================================================\n// Solana Balance Functions\n// ============================================================================\nconst SOLANA_RPC_URL = 'https://api.mainnet-beta.solana.com';\n/**\n * Major SPL tokens to check on Solana\n */\nconst SOLANA_TOKENS = [\n { mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', decimals: 6 },\n { mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', symbol: 'USDT', decimals: 6 },\n];\n/**\n * Get native SOL balance for a Solana wallet\n */\nasync function getSolanaBalance(address) {\n try {\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getBalance',\n params: [address],\n }),\n });\n const json = await response.json();\n const lamports = BigInt(json.result?.value || 0);\n // SOL has 9 decimals\n const balance = Number(lamports) / 1e9;\n return {\n symbol: 'SOL',\n balance: balance.toFixed(6),\n balanceRaw: lamports,\n };\n }\n catch (error) {\n console.error('[portfolio] Failed to get SOL balance:', error);\n return { symbol: 'SOL', balance: '0', balanceRaw: 0n };\n }\n}\n/**\n * Get SPL token balances for a Solana wallet\n */\nasync function getSolanaTokenBalances(address) {\n const results = [];\n try {\n // Query all token accounts owned by this wallet\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [\n address,\n { programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' },\n { encoding: 'jsonParsed' },\n ],\n }),\n });\n const json = await response.json();\n const accounts = json.result?.value || [];\n // Match against known tokens\n for (const tokenConfig of SOLANA_TOKENS) {\n const account = accounts.find((a) => a.account.data.parsed.info.mint === tokenConfig.mint);\n if (account) {\n const amount = account.account.data.parsed.info.tokenAmount.uiAmount || 0;\n if (amount > 0) {\n results.push({\n symbol: tokenConfig.symbol,\n balance: amount.toFixed(6),\n address: tokenConfig.mint,\n });\n }\n }\n }\n }\n catch (error) {\n console.error('[portfolio] Failed to get Solana token balances:', error);\n }\n return results;\n}\n/**\n * Get all Solana balances for a wallet\n */\nexport async function getSolanaWalletBalances(address, includeZeroBalances = false) {\n // Validate Solana address format (base58, 32-44 chars)\n if (!address || address.startsWith('0x') || address.length < 32 || address.length > 44) {\n return null;\n }\n try {\n const native = await getSolanaBalance(address);\n const tokens = await getSolanaTokenBalances(address);\n // Skip if no balances and not including zeros\n if (!includeZeroBalances && native.balanceRaw === 0n && tokens.length === 0) {\n return null;\n }\n return {\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: native.symbol,\n balance: native.balance,\n },\n tokens,\n };\n }\n catch (error) {\n console.error('[portfolio] Failed to get Solana balances:', error);\n return null;\n }\n}\n//# sourceMappingURL=portfolio.js.map",
730 "inputSchema": {},
731 "outputSchema": null,
732 "icons": null,
733 "annotations": null,
734 "meta": null,
735 "execution": null
736 },
737 {
738 "name": "moneyMarket.js",
739 "title": null,
740 "description": "Script: moneyMarket.js. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n *\n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n *\n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n *\n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\nimport { Type } from \"@sinclair/typebox\";\nimport { getSodaxClient } from \"../sodax/client\";\nimport { getSpokeProvider } from \"../providers/spokeProviderFactory\";\nimport { PolicyEngine } from \"../policy/policyEngine\";\nimport { getWalletManager } from '../wallet/walletManager';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Base schema for money market operations\n */\nconst MoneyMarketBaseSchema = Type.Object({\n walletId: Type.String({\n description: \"Unique identifier for the wallet\"\n }),\n chainId: Type.String({\n description: \"Source SODAX spoke chain ID where the operation originates (e.g., 'ethereum', 'arbitrum', 'sonic')\"\n }),\n token: Type.String({\n description: \"Token address or symbol to supply/borrow/withdraw/repay\",\n }),\n amount: Type.String({\n description: \"Amount in human-readable units (e.g., '100.5' for 100.5 USDC). Use '-1' for max repay (repay full debt).\",\n }),\n timeoutMs: Type.Optional(Type.Number({\n description: \"Operation timeout in milliseconds\",\n default: 180000,\n })),\n policyId: Type.Optional(Type.String({ description: \"Optional policy profile identifier for custom limits\" })),\n skipSimulation: Type.Optional(Type.Boolean({\n description: \"Skip transaction simulation (not recommended)\",\n default: false,\n })),\n});\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\nconst MoneyMarketSupplySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n useAsCollateral: Type.Optional(Type.Boolean({\n description: \"Whether to use the supplied tokens as collateral for borrowing (default: true)\",\n default: true,\n })),\n // Cross-chain supply options\n dstChainId: Type.Optional(Type.String({\n description: \"Optional destination chain for the supply operation. If different from chainId, performs cross-chain supply.\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address for the supplied position (defaults to wallet address)\",\n })),\n }),\n]);\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\nconst MoneyMarketWithdrawSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n withdrawType: Type.Optional(Type.Union([\n Type.Literal('default'),\n Type.Literal('collateral'),\n Type.Literal('all'),\n ], {\n description: \"Withdraw type: 'default' (standard), 'collateral' (withdraw collateral only), 'all' (withdraw maximum)\",\n default: 'default',\n })),\n // Cross-chain withdraw options\n dstChainId: Type.Optional(Type.String({\n description: \"Optional destination chain to receive withdrawn tokens. If different from chainId, performs cross-chain withdraw.\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address to receive withdrawn tokens (defaults to wallet address)\",\n })),\n }),\n]);\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n *\n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\nconst MoneyMarketBorrowSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate (recommended)\" }),\n ], {\n description: \"Interest rate mode: 1 = Stable, 2 = Variable\",\n default: 2,\n })),\n referralCode: Type.Optional(Type.String({\n description: \"Optional referral code for the borrow operation\",\n })),\n // Cross-chain borrow options (key feature!)\n dstChainId: Type.Optional(Type.String({\n description: \"Destination chain to receive borrowed tokens. If different from chainId, performs cross-chain borrow (supply on chainId, receive borrowed tokens on dstChainId).\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address to receive borrowed tokens (defaults to wallet address on dstChainId or chainId)\",\n })),\n }),\n]);\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\nconst MoneyMarketRepaySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate\" }),\n ], {\n description: \"Interest rate mode of the debt to repay: 1 = Stable, 2 = Variable\",\n default: 2,\n })),\n repayAll: Type.Optional(Type.Boolean({\n description: \"If true, repays the full debt amount (useful for closing position)\",\n default: false,\n })),\n // Cross-chain repay options\n collateralChainId: Type.Optional(Type.String({\n description: \"Optional chain ID where collateral is held (for cross-chain repay scenarios)\",\n })),\n }),\n]);\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\nconst CreateSupplyIntentSchema = Type.Composite([\n MoneyMarketSupplySchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateBorrowIntentSchema = Type.Composite([\n MoneyMarketBorrowSchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateWithdrawIntentSchema = Type.Composite([\n MoneyMarketWithdrawSchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateRepayIntentSchema = Type.Composite([\n MoneyMarketRepaySchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\n// ============================================================================\n// Helper Functions\n// ============================================================================\n/**\n * Converts human-readable amount to token units (wei)\n */\nfunction parseTokenAmount(amount, decimals = 18) {\n // Handle special case for max repay\n if (amount === '-1') {\n return BigInt(-1);\n }\n const parsed = parseFloat(amount);\n if (isNaN(parsed)) {\n throw new Error(`Invalid amount: ${amount}`);\n }\n const multiplier = Math.pow(10, decimals);\n return BigInt(Math.floor(parsed * multiplier));\n}\n/**\n * Resolves wallet and creates spoke provider for the operation\n */\nasync function resolveWalletAndProvider(walletId, chainId) {\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n return { walletAddress, spokeProvider };\n}\n/**\n * Common pre-operation checks and allowance handling\n */\nasync function prepareMoneyMarketOperation(walletId, chainId, token, amount, operation, policyId) {\n // ============================================================================\n // Hub Chain Validation\n // ============================================================================\n // SODAX architecture: Money market operations must be initiated from spoke chains,\n // not the hub chain (Sonic). The hub chain is the settlement layer where contracts\n // live, but users interact via spoke chains that relay operations to the hub.\n // Reference: sodax-tests/tests/crossChainSdk.test.ts explicitly omits SONIC_MAINNET_CHAIN_ID\n const isHubChainSource = chainId.toLowerCase() === 'sonic' || chainId === '146';\n if (isHubChainSource) {\n throw new Error(`Money market operations cannot be initiated from the hub chain (Sonic). ` +\n `Please use a spoke chain (base, arbitrum, ethereum, optimism, avalanche, bsc, polygon) as the source chain.`);\n }\n // Ensure sodax client is initialized\n const _sodaxClient = getSodaxClient(); // Just verify it's ready\n void _sodaxClient;\n // Normalize chain ID to SDK format for token resolution\n const sdkChainId = toSodaxChainId(chainId);\n // Resolve token symbol to address\n const tokenAddr = await resolveToken(sdkChainId, token);\n // Resolve wallet and create spoke provider\n const { walletAddress, spokeProvider } = await resolveWalletAndProvider(walletId, chainId);\n // Policy check\n const policyEngine = new PolicyEngine();\n const policyResult = await policyEngine.checkMoneyMarket({\n walletId,\n chainId,\n token,\n amount, // Add required amount parameter\n amountUsd: parseFloat(amount), // Simplified - would need actual price lookup\n operation,\n policyId,\n });\n if (!policyResult.allowed) {\n throw new Error(`Policy check failed: ${policyResult.reason || \"Operation not permitted\"}.`);\n }\n return { walletAddress, spokeProvider, policyResult, tokenAddr };\n}\n/**\n * Resolves token and returns its decimals\n * Falls back to 18 decimals if token info not found\n */\nasync function getTokenDecimals(chainId, token) {\n try {\n const sdkChainId = toSodaxChainId(chainId);\n const tokenInfo = await getTokenInfo(sdkChainId, token);\n return tokenInfo?.decimals ?? 18;\n }\n catch {\n // If token info lookup fails, fall back to 18 decimals\n return 18;\n }\n}\n/**\n * Checks and handles token approval if needed\n */\nasync function ensureAllowance(params, spokeProvider, raw = false) {\n const sodaxClient = await getSodaxClient();\n // Check if allowance is sufficient\n const isAllowanceValid = await sodaxClient.moneyMarket.isAllowanceValid(params, spokeProvider);\n if (!isAllowanceValid.ok || !isAllowanceValid.value) {\n if (raw) {\n // Return raw approval transaction\n const rawApproval = await sodaxClient.moneyMarket.approve(params, spokeProvider, true // raw mode\n );\n return { rawApproval };\n }\n else {\n // Execute approval transaction\n const approvalResult = await sodaxClient.moneyMarket.approve(params, spokeProvider, false);\n // Handle Result type from SDK\n const txHash = approvalResult.ok\n ? approvalResult.value\n : approvalResult.txHash || approvalResult;\n return { approvalTxHash: String(txHash) };\n }\n }\n return {};\n}\n/**\n * Determine if operation is cross-chain\n */\nfunction isCrossChainOperation(srcChainId, dstChainId) {\n return !!dstChainId && dstChainId !== srcChainId;\n}\n// ============================================================================\n// Tool Handlers\n// ============================================================================\n/**\n * Supply tokens to the money market\n *\n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\nasync function handleSupply(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, useAsCollateral = true, dstChainId, recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"supply\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n // Check allowance for supply\n const { approvalTxHash } = await ensureAllowance({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n const sodaxClient = await getSodaxClient();\n // Build supply parameters\n const supplyParams = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain supply: tokens supplied on ${chainId}, collateral recorded on ${dstChainId}`);\n }\n // Check and handle allowance (required for ALL supply operations)\n // Reference: sodax-frontend moneymarket-ops.ts - supply ALWAYS checks allowance\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:supply] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:supply] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:supply] Allowance check failed, proceeding anyway:', allowanceError);\n }\n // Execute supply\n const supplyResult = await sodaxClient.moneyMarket.supply(supplyParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (supplyResult.ok === false) {\n throw new Error(`Supply failed: ${serializeError(supplyResult.error)}`);\n }\n const value = supplyResult.ok ? supplyResult.value : supplyResult;\n // SDK may return [spokeTxHash, hubTxHash] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully supplied ${amount} ${token} on ${chainId}. Collateral available on ${dstChainId || chainId}.`\n : `Successfully supplied ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during supply\";\n return {\n success: false,\n status: \"failed\",\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market supply failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Withdraw tokens from the money market\n *\n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\nasync function handleWithdraw(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, withdrawType = 'default', dstChainId, recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"withdraw\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n // Build withdraw parameters\n const withdrawParams = {\n action: 'withdraw',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n withdrawParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain withdraw: withdrawing from ${chainId}, receiving tokens on ${dstChainId}`);\n }\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'withdraw' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:withdraw] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'withdraw' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:withdraw] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:withdraw] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n // Execute withdraw\n const withdrawResult = await sodaxClient.moneyMarket.withdraw(withdrawParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (withdrawResult.ok === false) {\n throw new Error(`Withdraw failed: ${serializeError(withdrawResult.error)}`);\n }\n const value = withdrawResult.ok ? withdrawResult.value : withdrawResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully withdrew ${amount} ${token} from ${chainId} to ${dstChainId}`\n : `Successfully withdrew ${amount} ${token} from money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during withdraw\";\n return {\n success: false,\n status: \"failed\",\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market withdraw failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Borrow tokens from the money market\n *\n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n *\n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\nasync function handleBorrow(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, interestRateMode = 2, referralCode, dstChainId, // KEY: This can be different from chainId for cross-chain borrow!\n recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"borrow\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n // Get user's positions to check health factor (best practice)\n const sodaxClient = await getSodaxClient();\n // For cross-chain borrow, resolve token on DESTINATION chain\n // SDK expects: getMoneyMarketToken(toChainId, params.token)\n // So params.token must be the destination chain's token address\n let borrowTokenAddr = tokenAddr;\n if (crossChain && dstChainId) {\n borrowTokenAddr = await resolveToken(dstChainId, token);\n console.log('[mm:borrow] Cross-chain: resolved token on destination chain', {\n srcChain: chainId,\n dstChain: dstChainId,\n srcTokenAddr: tokenAddr,\n dstTokenAddr: borrowTokenAddr,\n });\n }\n // Build borrow parameters\n const borrowParams = {\n action: 'borrow',\n token: borrowTokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // KEY CROSS-CHAIN FEATURE:\n // If dstChainId is provided and different from chainId, the borrowed tokens\n // will be delivered to dstChainId instead of chainId where the borrow is initiated\n if (crossChain && dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain borrow: Using collateral on ${chainId}, receiving borrowed tokens on ${dstChainId}`);\n warnings.push(`Ensure you have sufficient collateral on ${chainId} to support this borrow`);\n }\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:borrow] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:borrow] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:borrow] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n // Execute borrow\n const borrowResult = await sodaxClient.moneyMarket.borrow(borrowParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (borrowResult.ok === false) {\n throw new Error(`Borrow failed: ${serializeError(borrowResult.error)}`);\n }\n const value = borrowResult.ok ? borrowResult.value : borrowResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully borrowed ${amount} ${token} on ${dstChainId} using collateral from ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`\n : `Successfully borrowed ${amount} ${token} from money market on ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during borrow\";\n return {\n success: false,\n status: \"failed\",\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market borrow failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Repay borrowed tokens to the money market\n *\n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\nasync function handleRepay(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, interestRateMode = 2, repayAll = false, collateralChainId, skipSimulation = false } = params;\n const crossChain = !!collateralChainId && collateralChainId !== chainId;\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"repay\", policyId);\n // Parse amount with actual token decimals (use -1 for max repay if repayAll is true)\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = repayAll ? BigInt(-1) : parseTokenAmount(amount, decimals);\n // Check allowance for repay\n const { approvalTxHash } = await ensureAllowance({ token: tokenAddr, amount: amountBigInt === BigInt(-1) ? BigInt(0) : amountBigInt, action: 'repay' }, spokeProvider);\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n const sodaxClient = await getSodaxClient();\n // Build repay parameters\n const repayParams = {\n action: 'repay',\n token: tokenAddr,\n amount: amountBigInt,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && collateralChainId) {\n repayParams.toChainId = collateralChainId;\n warnings.push(`Cross-chain repay: Repaying debt on ${collateralChainId} using tokens from ${chainId}`);\n }\n // Check and handle allowance (required for ALL repay operations)\n // Reference: sodax-frontend moneymarket-ops.ts - repay ALWAYS checks allowance\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'repay' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:repay] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'repay' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:repay] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:repay] Allowance check failed, proceeding anyway:', allowanceError);\n }\n // Execute repay\n const repayResult = await sodaxClient.moneyMarket.repay(repayParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (repayResult.ok === false) {\n throw new Error(`Repay failed: ${serializeError(repayResult.error)}`);\n }\n const value = repayResult.ok ? repayResult.value : repayResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: repayAll\n ? `Successfully repaid full debt for ${token} on ${chainId}`\n : `Successfully repaid ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during repay\";\n return {\n success: false,\n status: \"failed\",\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: `Money market repay failed: ${errorMessage}`,\n };\n }\n}\n// ============================================================================\n// Intent Creation Handlers (Advanced)\n// ============================================================================\n/**\n * Create a supply intent without executing (for custom flows)\n */\nasync function handleCreateSupplyIntent(params) {\n const { walletId, chainId, token, amount, useAsCollateral = true, dstChainId, recipient, raw = true } = params;\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"supply\");\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n const supplyParams = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n if (dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n }\n const intentData = await sodaxClient.moneyMarket.createSupplyIntent(supplyParams, spokeProvider, raw);\n return {\n success: true,\n status: \"pending\",\n operation: \"createSupplyIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: \"Supply intent created. Submit this intent to execute the supply operation.\",\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create supply intent failed: ${errorMessage}`);\n }\n}\n/**\n * Create a borrow intent without executing (for custom flows)\n */\nasync function handleCreateBorrowIntent(params) {\n const { walletId, chainId, token, amount, interestRateMode = 2, dstChainId, recipient, raw = true } = params;\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"borrow\");\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n const borrowParams = {\n action: 'borrow',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n if (dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n }\n const intentData = await sodaxClient.moneyMarket.createBorrowIntent(borrowParams, spokeProvider, raw);\n return {\n success: true,\n status: \"pending\",\n operation: \"createBorrowIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: dstChainId && dstChainId !== chainId\n ? `Cross-chain borrow intent created. Collateral on ${chainId}, borrowed tokens to ${dstChainId}. Submit this intent to execute.`\n : \"Borrow intent created. Submit this intent to execute the borrow operation.\",\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create borrow intent failed: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport function registerMoneyMarketTools(agentTools) {\n // Supply\n agentTools.register({\n name: \"amped_mm_supply\",\n summary: \"Supply tokens as collateral to the SODAX money market. Supports same-chain and cross-chain supply (supply on chain A, collateral available on chain B).\",\n schema: MoneyMarketSupplySchema,\n handler: handleSupply,\n });\n // Withdraw\n agentTools.register({\n name: \"amped_mm_withdraw\",\n summary: \"Withdraw supplied tokens from the SODAX money market. Supports cross-chain withdraw (withdraw from chain A, receive tokens on chain B).\",\n schema: MoneyMarketWithdrawSchema,\n handler: handleWithdraw,\n });\n // Borrow\n agentTools.register({\n name: \"amped_mm_borrow\",\n summary: \"Borrow tokens from the SODAX money market. KEY FEATURE: Can borrow to a different chain than collateral! Example: Supply on Ethereum, borrow to Arbitrum using dstChainId parameter.\",\n schema: MoneyMarketBorrowSchema,\n handler: handleBorrow,\n });\n // Repay\n agentTools.register({\n name: \"amped_mm_repay\",\n summary: \"Repay borrowed tokens to the SODAX money market. Use amount='-1' or repayAll=true to repay full debt. Supports cross-chain repay.\",\n schema: MoneyMarketRepaySchema,\n handler: handleRepay,\n });\n // Advanced: Create Intent variants for custom flows\n agentTools.register({\n name: \"amped_mm_create_supply_intent\",\n summary: \"[Advanced] Create a supply intent without executing. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateSupplyIntentSchema,\n handler: handleCreateSupplyIntent,\n });\n agentTools.register({\n name: \"amped_mm_create_borrow_intent\",\n summary: \"[Advanced] Create a borrow intent without executing. Supports cross-chain borrow intents. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateBorrowIntentSchema,\n handler: handleCreateBorrowIntent,\n });\n}\n// ============================================================================\n// Re-exports for testing and direct usage\n// ============================================================================\nexport { MoneyMarketBaseSchema, MoneyMarketSupplySchema, MoneyMarketWithdrawSchema, MoneyMarketBorrowSchema, MoneyMarketRepaySchema, CreateSupplyIntentSchema, CreateWithdrawIntentSchema, CreateBorrowIntentSchema, CreateRepayIntentSchema, handleSupply, handleWithdraw, handleBorrow, handleRepay, handleCreateSupplyIntent, handleCreateBorrowIntent, };\n// Aliases for index.ts compatibility\nexport { MoneyMarketSupplySchema as MmSupplySchema, MoneyMarketWithdrawSchema as MmWithdrawSchema, MoneyMarketBorrowSchema as MmBorrowSchema, MoneyMarketRepaySchema as MmRepaySchema, handleSupply as handleMmSupply, handleWithdraw as handleMmWithdraw, handleBorrow as handleMmBorrow, handleRepay as handleMmRepay, };\n//# sourceMappingURL=moneyMarket.js.map",
741 "inputSchema": {},
742 "outputSchema": null,
743 "icons": null,
744 "annotations": null,
745 "meta": null,
746 "execution": null
747 },
748 {
749 "name": "swap.d.ts",
750 "title": null,
751 "description": "Script: swap.d.ts. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n *\n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\nimport { Static } from '@sinclair/typebox';\nimport type { AgentTools } from '../types';\ndeclare const SwapQuoteRequestSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n type: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"exact_input\">, import(\"@sinclair/typebox\").TLiteral<\"exact_output\">]>;\n slippageBps: import(\"@sinclair/typebox\").TNumber;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ndeclare const SwapExecuteParamsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n quote: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n inputAmount: import(\"@sinclair/typebox\").TString;\n outputAmount: import(\"@sinclair/typebox\").TString;\n slippageBps: import(\"@sinclair/typebox\").TNumber;\n deadline: import(\"@sinclair/typebox\").TNumber;\n minOutputAmount: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n maxInputAmount: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n }>;\n maxSlippageBps: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\ndeclare const SwapStatusParamsSchema: import(\"@sinclair/typebox\").TObject<{\n txHash: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n intentHash: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ndeclare const SwapCancelParamsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n intent: import(\"@sinclair/typebox\").TObject<{\n id: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n deadline: import(\"@sinclair/typebox\").TNumber;\n }>;\n srcChainId: import(\"@sinclair/typebox\").TString;\n}>;\ntype SwapQuoteRequest = Static<typeof SwapQuoteRequestSchema>;\ntype SwapExecuteParams = Static<typeof SwapExecuteParamsSchema>;\ntype SwapStatusParams = Static<typeof SwapStatusParamsSchema>;\ntype SwapCancelParams = Static<typeof SwapCancelParamsSchema>;\ndeclare function handleSwapQuote(params: SwapQuoteRequest): Promise<Record<string, unknown>>;\ndeclare function handleSwapExecute(params: SwapExecuteParams): Promise<Record<string, unknown>>;\ndeclare function handleSwapStatus(params: SwapStatusParams): Promise<Record<string, unknown>>;\ndeclare function handleSwapCancel(params: SwapCancelParams): Promise<Record<string, unknown>>;\nexport declare function registerSwapTools(agentTools: AgentTools): void;\nexport { SwapQuoteRequestSchema as SwapQuoteSchema, SwapExecuteParamsSchema as SwapExecuteSchema, SwapStatusParamsSchema as SwapStatusSchema, SwapCancelParamsSchema as SwapCancelSchema, };\nexport { handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel, };\n//# sourceMappingURL=swap.d.ts.map",
752 "inputSchema": {},
753 "outputSchema": null,
754 "icons": null,
755 "annotations": null,
756 "meta": null,
757 "execution": null
758 },
759 {
760 "name": "swap.js",
761 "title": null,
762 "description": "Script: swap.js. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n *\n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\nimport { Type } from '@sinclair/typebox';\nimport { serializeError } from '../utils/errorUtils';\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n// ============================================================================\n// SODAX API & Explorer Links\n// ============================================================================\nconst SODAX_CANARY_API = 'https://canary-api.sodax.com/v1/be';\n// Chain ID to block explorer mapping\nconst CHAIN_EXPLORERS = {\n 'ethereum': 'https://etherscan.io/tx/',\n 'base': 'https://basescan.org/tx/',\n 'arbitrum': 'https://arbiscan.io/tx/',\n 'optimism': 'https://optimistic.etherscan.io/tx/',\n 'polygon': 'https://polygonscan.com/tx/',\n 'sonic': 'https://sonicscan.org/tx/',\n 'avalanche': 'https://snowtrace.io/tx/',\n 'bsc': 'https://bscscan.com/tx/',\n 'solana': 'https://solscan.io/tx/',\n};\nfunction getExplorerLink(chainId, txHash) {\n // Normalize chain ID (remove 0x prefix and suffix if present)\n const normalizedChainId = chainId.replace(/^0x[\\\\da-f]+\\\\./, '').toLowerCase();\n const explorer = CHAIN_EXPLORERS[normalizedChainId];\n return explorer ? `${explorer}${txHash}` : undefined;\n}\nasync function fetchIntentFromSodax(intentHash) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok)\n return null;\n return await response.json();\n }\n catch {\n return null;\n }\n}\n/**\n * Ensure intent hash is in hex format (0x prefixed)\n */\nfunction toHexIntentHash(hash) {\n if (!hash)\n return undefined;\n const str = String(hash);\n // Already hex format\n if (str.startsWith('0x'))\n return str;\n // Convert decimal BigInt string to hex\n try {\n return '0x' + BigInt(str).toString(16);\n }\n catch {\n return str; // Return as-is if conversion fails\n }\n}\nfunction getSodaxScanUrl(txHash) {\n return `https://sodaxscan.com/messages/search?value=${txHash}`;\n}\nfunction getSodaxIntentApiUrl(intentHash) {\n return `${SODAX_CANARY_API}/intent/${intentHash}`;\n}\n// SODAX internal chain ID to block explorer mapping\nconst SODAX_CHAIN_EXPLORERS = {\n 1: 'https://solscan.io/tx/', // Solana\n 30: 'https://basescan.org/tx/', // Base\n 146: 'https://sonicscan.org/tx/', // Sonic (hub)\n 42161: 'https://arbiscan.io/tx/', // Arbitrum\n 10: 'https://optimistic.etherscan.io/tx/', // Optimism\n 137: 'https://polygonscan.com/tx/', // Polygon\n 56: 'https://bscscan.com/tx/', // BSC\n 43114: 'https://snowtrace.io/tx/', // Avalanche\n};\n/**\n * Poll SODAX API until intent is delivered, then return delivery tx explorer link\n */\nasync function pollForDelivery(intentHash, timeoutMs = 60000, pollIntervalMs = 3000) {\n const startTime = Date.now();\n while (Date.now() - startTime < timeoutMs) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n continue;\n }\n const data = await response.json();\n // Check if intent is filled (closed)\n if (data.open === false && data.events?.length > 0) {\n const fillEvent = data.events.find((e) => e.eventType === 'intent-filled');\n if (fillEvent) {\n const dstChainId = data.intent?.dstChain;\n const explorer = SODAX_CHAIN_EXPLORERS[dstChainId] || '';\n return {\n delivered: true,\n deliveryTxHash: fillEvent.txHash,\n deliveryExplorer: explorer ? `${explorer}${fillEvent.txHash}` : undefined,\n dstChainId\n };\n }\n }\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n catch {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n }\n return { delivered: false };\n}\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\nconst SwapTypeSchema = Type.Union([\n Type.Literal('exact_input'),\n Type.Literal('exact_output')\n]);\nconst SwapQuoteRequestSchema = Type.Object({\n walletId: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n type: SwapTypeSchema,\n slippageBps: Type.Number({ default: 50, minimum: 0, maximum: 10000 }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain. For cross-chain swaps to Solana, provide a Solana base58 address. Defaults to wallet address if omitted.'\n }))\n});\n// Result schema for documentation (not used at runtime)\nconst _SwapQuoteResultSchema = Type.Object({\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n fees: Type.Object({\n solverFee: Type.String(),\n protocolFee: Type.Optional(Type.String()),\n partnerFee: Type.Optional(Type.String())\n }),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n});\nvoid _SwapQuoteResultSchema; // Suppress unused warning\nconst SwapExecuteParamsSchema = Type.Object({\n walletId: Type.String(),\n quote: Type.Object({\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n }),\n maxSlippageBps: Type.Optional(Type.Number({ minimum: 0, maximum: 10000 })),\n policyId: Type.Optional(Type.String()),\n skipSimulation: Type.Optional(Type.Boolean({ default: false })),\n timeoutMs: Type.Optional(Type.Number({ default: 120000 }))\n});\nconst SwapExecuteResultSchema = Type.Object({\n spokeTxHash: Type.String(),\n hubTxHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String()),\n status: Type.String(),\n message: Type.Optional(Type.String())\n});\nconst SwapStatusParamsSchema = Type.Object({\n txHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String())\n});\nconst SwapStatusResultSchema = Type.Object({\n status: Type.String(),\n intentHash: Type.Optional(Type.String()),\n spokeTxHash: Type.Optional(Type.String()),\n hubTxHash: Type.Optional(Type.String()),\n filledAmount: Type.Optional(Type.String()),\n error: Type.Optional(Type.String()),\n createdAt: Type.Optional(Type.Number()),\n expiresAt: Type.Optional(Type.Number())\n});\nconst SwapCancelParamsSchema = Type.Object({\n walletId: Type.String(),\n intent: Type.Object({\n id: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n deadline: Type.Number()\n }),\n srcChainId: Type.String()\n});\nconst SwapCancelResultSchema = Type.Object({\n success: Type.Boolean(),\n txHash: Type.Optional(Type.String()),\n message: Type.String()\n});\n// ============================================================================\n// Swap Quote Tool\n// ============================================================================\nasync function handleSwapQuote(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.srcChainId, params.srcToken);\n const dstTokenAddr = await resolveToken(params.dstChainId, params.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.dstChainId, dstTokenAddr);\n // Get token config to determine decimals for amount conversion\n const configService = sodaxClient.configService;\n const decimals = srcTokenInfo?.decimals ?? 18; // Default to 18 (most EVM tokens) if not found\n // Convert human-readable amount to raw amount (bigint)\n const amountFloat = parseFloat(params.amount);\n const rawAmount = BigInt(Math.floor(amountFloat * Math.pow(10, decimals)));\n // Build SDK-compatible request with snake_case parameters\n const quoteRequest = {\n token_src: srcTokenAddr,\n token_src_blockchain_id: toSodaxChainId(params.srcChainId),\n token_dst: dstTokenAddr,\n token_dst_blockchain_id: toSodaxChainId(params.dstChainId),\n amount: rawAmount,\n quote_type: params.type\n };\n console.log('[swap_quote] SDK request:', JSON.stringify(quoteRequest, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n const quoteResult = await sodaxClient.swaps.getQuote(quoteRequest);\n // Handle Result type from SDK\n if (quoteResult.ok === false) {\n const errorMsg = quoteResult.error instanceof Error\n ? quoteResult.error.message\n : typeof quoteResult.error === 'string'\n ? quoteResult.error\n : JSON.stringify(quoteResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Quote failed: ${errorMsg}`);\n }\n const quote = quoteResult.ok ? quoteResult.value : quoteResult;\n console.log('[swap_quote] SDK response:', JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n // Get output token decimals with multiple fallbacks\n // SDK returns quoted_amount as bigint - convert to human-readable string\n let dstDecimals = quote.token_dst_decimals || quote.tokenDstDecimals;\n if (!dstDecimals && dstTokenInfo) {\n dstDecimals = dstTokenInfo.decimals;\n }\n if (!dstDecimals) {\n // Hardcoded decimals for common stablecoins\n const KNOWN_DECIMALS = {\n usdc: 6, USDC: 6, usdt: 6, USDT: 6, sol: 9, SOL: 9,\n dai: 18, DAI: 18, bnusd: 18, bnUSD: 18\n };\n const tokenSymbol = params.dstToken.toUpperCase();\n dstDecimals = KNOWN_DECIMALS[tokenSymbol] || 18;\n console.warn(`[swap_quote] Using fallback decimals (${dstDecimals}) for token ${params.dstToken}`);\n }\n const quotedAmount = quote.quoted_amount || quote.quotedAmount || quote.outputAmount;\n const outputAmountStr = quotedAmount\n ? (Number(quotedAmount) / Math.pow(10, dstDecimals)).toString()\n : '0';\n // Normalize and return quote (SDK uses snake_case, we return camelCase)\n const result = {\n inputAmount: params.amount,\n outputAmount: outputAmountStr,\n srcToken: srcTokenAddr,\n dstToken: dstTokenAddr,\n srcChainId: params.srcChainId,\n dstChainId: params.dstChainId,\n slippageBps: params.slippageBps,\n deadline: quote.deadline || calculateDeadline(300), // 5 min default\n fees: {\n solverFee: quote.solver_fee || quote.fees?.solverFee || '0',\n protocolFee: quote.protocol_fee || quote.fees?.protocolFee,\n partnerFee: quote.partner_fee || quote.fees?.partnerFee\n },\n minOutputAmount: quote.min_output_amount || quote.minOutputAmount,\n maxInputAmount: quote.max_input_amount || quote.maxInputAmount,\n recipient: params.recipient, // Pass through for execute\n // Include raw SDK response for debugging\n _raw: JSON.parse(JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v))\n };\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n tokenAddresses: [params.srcToken, params.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to get swap quote: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Execute Tool\n// ============================================================================\nasync function handleSwapExecute(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n // 1. Initialize dependencies\n const policyEngine = new PolicyEngine();\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.quote.srcChainId, params.quote.srcToken);\n const dstTokenAddr = await resolveToken(params.quote.dstChainId, params.quote.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.quote.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.quote.dstChainId, dstTokenAddr);\n // 2. Resolve wallet\n const wallet = await walletManager.resolve(params.walletId);\n const walletAddress = await wallet.getAddress();\n // 3. Policy check\n const policyCheck = await policyEngine.checkSwap({\n walletId: params.walletId,\n srcChainId: params.quote.srcChainId,\n dstChainId: params.quote.dstChainId,\n srcToken: params.quote.srcToken,\n dstToken: params.quote.dstToken,\n inputAmount: params.quote.inputAmount,\n slippageBps: params.maxSlippageBps || params.quote.slippageBps,\n policyId: params.policyId\n });\n if (!policyCheck.allowed) {\n throw new Error(`Policy check failed: ${policyCheck.reason}`);\n }\n // 4. Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(params.walletId, params.quote.srcChainId);\n // 5. Convert amounts to bigint FIRST (needed for intentParams)\n const srcDecimals = srcTokenInfo?.decimals ?? 18;\n const dstDecimals = dstTokenInfo?.decimals ?? 18;\n const inputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.inputAmount) * Math.pow(10, srcDecimals)));\n const outputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.outputAmount) * Math.pow(10, dstDecimals)));\n // Calculate minOutputAmount with slippage\n const slippageBps = params.maxSlippageBps || params.quote.slippageBps || 100;\n const minOutputAmountRaw = outputAmountRaw - (outputAmountRaw * BigInt(slippageBps) / 10000n);\n console.log(\"[swap_execute] Amount conversion:\", {\n inputAmount: params.quote.inputAmount,\n inputAmountRaw: inputAmountRaw.toString(),\n outputAmount: params.quote.outputAmount,\n outputAmountRaw: outputAmountRaw.toString(),\n minOutputAmountRaw: minOutputAmountRaw.toString(),\n srcDecimals,\n dstDecimals\n });\n // 6. Build intentParams (used for allowance check, approval, and swap)\n const intentParams = {\n srcAddress: walletAddress,\n dstAddress: params.quote.recipient || walletAddress,\n srcChain: toSodaxChainId(params.quote.srcChainId),\n dstChain: toSodaxChainId(params.quote.dstChainId),\n inputToken: srcTokenAddr,\n outputToken: dstTokenAddr,\n inputAmount: inputAmountRaw,\n minOutputAmount: minOutputAmountRaw,\n deadline: BigInt(params.quote.deadline),\n allowPartialFill: false,\n solver: \"0x0000000000000000000000000000000000000000\",\n data: \"0x\"\n };\n // 7. Check allowance using SDK's expected API\n let allowanceValid = false;\n try {\n const allowanceResult = await sodaxClient.swaps.isAllowanceValid({\n intentParams,\n spokeProvider\n });\n allowanceValid = allowanceResult?.ok ? allowanceResult.value : !!allowanceResult;\n }\n catch (e) {\n console.warn('[swap_execute] Allowance check failed, assuming approval needed:', e);\n allowanceValid = false;\n }\n // 8. Approve if needed using SDK's expected API\n if (!allowanceValid) {\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n message: 'Token approval required'\n });\n const approvalResult = await sodaxClient.swaps.approve({\n intentParams,\n spokeProvider\n });\n const approvalTx = approvalResult?.ok ? approvalResult.value : approvalResult;\n // Wait for approval confirmation if possible\n if (spokeProvider.walletProvider?.waitForTransactionReceipt && approvalTx) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTx);\n }\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n approvalTx: String(approvalTx),\n success: true\n });\n }\n // 9. Execute swap\n const swapResult = await sodaxClient.swaps.swap({\n intentParams,\n spokeProvider,\n skipSimulation: params.skipSimulation || false,\n timeout: params.timeoutMs || 120000\n });\n // Handle Result type from SDK\n if (swapResult.ok === false) {\n const errorMsg = swapResult.error instanceof Error\n ? swapResult.error.message\n : typeof swapResult.error === 'string'\n ? swapResult.error\n : JSON.stringify(swapResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Swap failed: ${errorMsg}`);\n }\n const value = swapResult.ok ? swapResult.value : swapResult;\n // SDK may return [response, intent, deliveryInfo] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract internal tracking info\n const srcTxHash = deliveryInfo?.srcTxHash;\n const intentHash = toHexIntentHash(solverResponse?.intent_hash) || toHexIntentHash(intent?.intentId);\n // Poll for delivery confirmation (wait up to 60s)\n let deliveryResult = { delivered: false };\n if (intentHash) {\n console.log('[swap_execute] Waiting for delivery confirmation...');\n deliveryResult = await pollForDelivery(intentHash, 60000, 3000);\n }\n // Build user-friendly result\n const result = {\n status: deliveryResult.delivered ? 'delivered' : 'submitted',\n message: deliveryResult.delivered\n ? 'Swap completed! Funds delivered to destination.'\n : 'Swap submitted, awaiting cross-chain delivery...',\n // User-friendly tracking link\n sodaxScanUrl: srcTxHash ? getSodaxScanUrl(srcTxHash) : undefined,\n // Source chain: where user initiated the swap\n // Destination chain: where user RECEIVED funds\n initiationTx: srcTxHash ? getExplorerLink(params.quote.srcChainId, srcTxHash) : undefined,\n receiptTx: deliveryResult.deliveryExplorer,\n };\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n tokenAddresses: [params.quote.srcToken, params.quote.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Swap execution failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Status Tool\n// ============================================================================\nasync function handleSwapStatus(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n if (!params.txHash && !params.intentHash) {\n throw new Error('Either txHash or intentHash must be provided');\n }\n // Use our SodaxApiClient for Backend API access (not SDK)\n const sodaxApi = getSodaxApiClient();\n let intentData = null;\n // Try intentHash first (most reliable)\n if (params.intentHash) {\n try {\n intentData = await sodaxApi.getIntentByHash(params.intentHash);\n }\n catch (err) {\n console.warn(`[swap_status] getIntentByHash failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n // If intentHash lookup failed or wasn't provided, try txHash\n // Note: txHash should be from the HUB chain (Sonic), not spoke chain\n if (!intentData && params.txHash) {\n try {\n intentData = await sodaxApi.getIntentByTxHash(params.txHash);\n }\n catch (err) {\n // txHash lookup failed - provide helpful error message\n const errorMsg = err instanceof Error ? err.message : String(err);\n throw new Error(`Unable to find intent for txHash: ${params.txHash}. ` +\n `Note: The txHash must be from the HUB chain (Sonic), not the spoke chain. ` +\n `If you have a spoke chain txHash (Base, Arbitrum, etc.), use amped_user_intents to find the intent first. ` +\n `Error: ${errorMsg}`);\n }\n }\n if (!intentData) {\n throw new Error('Unable to retrieve swap status. Provide a valid intentHash or hub chain txHash.');\n }\n // Extract intent details\n const intentHash = intentData.intentHash;\n const hubTxHash = intentData.txHash; // This is the hub chain tx that created the intent\n const isOpen = intentData.open;\n const intent = intentData.intent;\n // Determine status from intent state\n let status = 'unknown';\n if (intentData.open === true) {\n status = 'pending';\n }\n else if (intentData.open === false) {\n // Check events to determine if filled or cancelled/expired\n const filledEvent = intentData.events?.find((e) => e.eventType === 'intent-filled');\n const cancelledEvent = intentData.events?.find((e) => e.eventType === 'intent-cancelled' || e.eventType === 'intent-expired');\n if (filledEvent) {\n status = 'filled';\n }\n else if (cancelledEvent) {\n status = cancelledEvent.eventType === 'intent-cancelled' ? 'cancelled' : 'expired';\n }\n else {\n status = 'closed';\n }\n }\n // Extract fulfillment details if available\n const filledEvent = intentData.events?.find((e) => e.eventType === 'intent-filled');\n const fulfillmentTxHash = filledEvent?.txHash;\n const receivedOutput = filledEvent?.intentState?.receivedOutput;\n // Build result\n const result = {\n status,\n intentHash,\n hubTxHash, // Hub chain tx that created the intent\n spokeTxHash: params.txHash !== hubTxHash ? params.txHash : undefined, // Original spoke tx if different\n open: isOpen,\n // Intent details\n srcChain: intent?.srcChain,\n dstChain: intent?.dstChain,\n inputToken: intent?.inputToken,\n outputToken: intent?.outputToken,\n inputAmount: intent?.inputAmount,\n minOutputAmount: intent?.minOutputAmount,\n receivedOutput: receivedOutput,\n deadline: intent?.deadline ? new Date(parseInt(intent.deadline) * 1000).toISOString() : undefined,\n createdAt: intentData.createdAt,\n // Fulfillment details\n fulfillmentTxHash,\n fulfillmentChain: intent?.dstChain,\n // Tracking links\n sodaxScanUrl: `https://sodaxscan.com/intents/${intentHash}`,\n };\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash,\n txHash: params.txHash,\n status,\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash: params.intentHash,\n txHash: params.txHash,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to get swap status: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Cancel Tool\n// ============================================================================\nasync function handleSwapCancel(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.intent.srcChainId, params.intent.srcToken);\n const dstTokenAddr = await resolveToken(params.intent.dstChainId, params.intent.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.intent.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.intent.dstChainId, dstTokenAddr);\n // Resolve wallet (validates it exists)\n await walletManager.resolve(params.walletId);\n // Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(params.walletId, params.srcChainId);\n // Construct intent object for cancellation\n const intent = {\n id: params.intent.id,\n srcChainId: params.intent.srcChainId,\n dstChainId: params.intent.dstChainId,\n srcToken: params.intent.srcToken,\n dstToken: params.intent.dstToken,\n amount: params.intent.amount,\n deadline: BigInt(params.intent.deadline),\n createdAt: Date.now(),\n status: 'pending'\n };\n // Cancel the intent - SDK expects (intent, spokeProvider)\n const cancelResult = await sodaxClient.swaps.cancelIntent(intent, spokeProvider);\n // Handle Result type\n if (cancelResult.ok === false) {\n throw new Error(`Cancel failed: ${serializeError(cancelResult.error)}`);\n }\n const cancelTx = cancelResult.ok ? cancelResult.value : cancelResult;\n const cancelTxHash = typeof cancelTx === 'string' ? cancelTx : String(cancelTx);\n // Wait for cancellation confirmation if possible\n // SDK may expose waitForTransactionReceipt on the underlying wallet provider\n if (spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(cancelTxHash);\n }\n const result = {\n success: true,\n txHash: cancelTxHash,\n message: 'Intent cancelled successfully'\n };\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n txHash: cancelTxHash,\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to cancel swap: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Utility Functions\n// ============================================================================\nfunction generateRequestId() {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\nfunction calculateDeadline(secondsFromNow) {\n return Math.floor(Date.now() / 1000) + secondsFromNow;\n}\nfunction logStructured(entry) {\n // Structured JSON logging for observability\n // Use replacer to handle BigInt serialization\n console.log(JSON.stringify({\n ...entry,\n timestamp: new Date().toISOString(),\n component: 'amped-defi-swap'\n }, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\nexport function registerSwapTools(agentTools) {\n // Register swap quote tool\n agentTools.register({\n name: 'amped_swap_quote',\n summary: 'Get a swap quote for exact-in or exact-out swaps across chains',\n description: 'Retrieves a quote for swapping tokens across chains using the SODAX swap protocol. ' +\n 'Supports both exact input (specify input amount, get output estimate) and ' +\n 'exact output (specify desired output, get required input) modes.',\n schema: SwapQuoteRequestSchema,\n handler: handleSwapQuote\n });\n // Register swap execute tool\n agentTools.register({\n name: 'amped_swap_execute',\n summary: 'Execute a swap with policy enforcement and allowance handling',\n description: 'Executes a swap using a previously obtained quote. ' +\n 'Performs policy checks, validates allowances, approves tokens if needed, ' +\n 'and executes the swap transaction. Returns transaction hashes and intent status.',\n schema: SwapExecuteParamsSchema,\n handler: handleSwapExecute\n });\n // Register swap status tool\n agentTools.register({\n name: 'amped_swap_status',\n summary: 'Check the status of a swap intent or transaction',\n description: 'Polls the status of a swap by intent hash or transaction hash. ' +\n 'Returns current status, fill amount, error details if failed, and timing information.',\n schema: SwapStatusParamsSchema,\n handler: handleSwapStatus\n });\n // Register swap cancel tool\n agentTools.register({\n name: 'amped_swap_cancel',\n summary: 'Cancel an active swap intent',\n description: 'Cancels a pending swap intent on the source chain. ' +\n 'Requires the intent details and source chain ID. Returns cancellation transaction hash.',\n schema: SwapCancelParamsSchema,\n handler: handleSwapCancel\n });\n}\n// Silence unused variable warnings for result schemas (used for documentation)\nvoid SwapExecuteResultSchema;\nvoid SwapStatusResultSchema;\nvoid SwapCancelResultSchema;\n// Export schemas with consistent naming\nexport { SwapQuoteRequestSchema as SwapQuoteSchema, SwapExecuteParamsSchema as SwapExecuteSchema, SwapStatusParamsSchema as SwapStatusSchema, SwapCancelParamsSchema as SwapCancelSchema, };\n// Export handlers\nexport { handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel, };\n//# sourceMappingURL=swap.js.map",
763 "inputSchema": {},
764 "outputSchema": null,
765 "icons": null,
766 "annotations": null,
767 "meta": null,
768 "execution": null
769 },
770 {
771 "name": "discovery.d.ts",
772 "title": null,
773 "description": "Script: discovery.d.ts. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_supported_chains - no parameters required\n */\ndeclare const SupportedChainsSchema: import(\"@sinclair/typebox\").TObject<{}>;\n/**\n * Schema for amped_supported_tokens\n */\ndeclare const SupportedTokensSchema: import(\"@sinclair/typebox\").TObject<{\n module: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"swaps\">, import(\"@sinclair/typebox\").TLiteral<\"bridge\">, import(\"@sinclair/typebox\").TLiteral<\"moneyMarket\">]>;\n chainId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_wallet_address\n */\ndeclare const WalletAddressSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_money_market_positions\n */\ndeclare const MoneyMarketPositionsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_money_market_reserves\n */\ndeclare const MoneyMarketReservesSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\ndeclare const CrossChainPositionsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainIds: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n includeZeroBalances: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n minUsdValue: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\ndeclare const UserIntentsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n status: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"all\">, import(\"@sinclair/typebox\").TLiteral<\"open\">, import(\"@sinclair/typebox\").TLiteral<\"closed\">]>>;\n limit: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n offset: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\ndeclare const ListWalletsSchema: import(\"@sinclair/typebox\").TObject<{}>;\ntype SupportedChainsParams = Static<typeof SupportedChainsSchema>;\ntype SupportedTokensParams = Static<typeof SupportedTokensSchema>;\ntype WalletAddressParams = Static<typeof WalletAddressSchema>;\ntype MoneyMarketPositionsParams = Static<typeof MoneyMarketPositionsSchema>;\ntype ListWalletsParams = Static<typeof ListWalletsSchema>;\ntype MoneyMarketReservesParams = Static<typeof MoneyMarketReservesSchema>;\ntype CrossChainPositionsParams = Static<typeof CrossChainPositionsSchema>;\ntype UserIntentsParams = Static<typeof UserIntentsSchema>;\n/**\n * AgentTools interface for registering tools with the OpenClaw framework\n */\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n/**\n * Get supported spoke chains from SODAX configuration\n */\ndeclare function handleSupportedChains(_params: SupportedChainsParams): Promise<unknown>;\n/**\n * Get supported tokens for a specific module and chain\n */\ndeclare function handleSupportedTokens(params: SupportedTokensParams): Promise<unknown>;\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\ndeclare function handleWalletAddress(params: WalletAddressParams): Promise<unknown>;\n/**\n * Get user money market positions (humanized format)\n */\ndeclare function handleMoneyMarketPositions(params: MoneyMarketPositionsParams): Promise<unknown>;\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\ndeclare function handleMoneyMarketReserves(params: MoneyMarketReservesParams): Promise<unknown>;\n/**\n * Get aggregated money market positions across all chains\n *\n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\ndeclare function handleCrossChainPositions(params: CrossChainPositionsParams): Promise<unknown>;\n/**\n * Get user intents from SODAX API\n *\n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\ndeclare function handleUserIntents(params: UserIntentsParams): Promise<unknown>;\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\ndeclare function handleListWallets(_params: ListWalletsParams): Promise<unknown>;\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport declare function registerDiscoveryTools(agentTools: AgentTools): void;\nexport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketPositionsSchema, MoneyMarketReservesSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, };\nexport { handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketPositions, handleMoneyMarketReserves, handleCrossChainPositions, handleUserIntents, handleListWallets, };\n//# sourceMappingURL=discovery.d.ts.map",
774 "inputSchema": {},
775 "outputSchema": null,
776 "icons": null,
777 "annotations": null,
778 "meta": null,
779 "execution": null
780 },
781 {
782 "name": "walletManagement.js",
783 "title": null,
784 "description": "Script: walletManagement.js. Code:\n/**\n * Wallet Management Tools\n *\n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n *\n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\nimport { Type } from '@sinclair/typebox';\nimport { getWalletManager } from '../wallet';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_add_wallet\n */\nconst AddWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Nickname for the wallet (e.g., \"trading\", \"savings\", \"degen\")',\n }),\n source: Type.Union([\n Type.Literal('evm-wallet-skill'),\n Type.Literal('bankr'),\n Type.Literal('env'),\n ], {\n description: 'Wallet source type',\n }),\n // For evm-wallet-skill\n path: Type.Optional(Type.String({\n description: 'Path to wallet JSON file (for evm-wallet-skill). Defaults to ~/.evm-wallet.json',\n })),\n // For bankr\n apiKey: Type.Optional(Type.String({\n description: 'Bankr API key (for bankr source)',\n })),\n apiUrl: Type.Optional(Type.String({\n description: 'Bankr API URL (optional, defaults to https://api.bankr.bot)',\n })),\n // For env\n address: Type.Optional(Type.String({\n description: 'Wallet address (for env source)',\n })),\n privateKey: Type.Optional(Type.String({\n description: 'Private key (for env source). WARNING: Will be stored in config file.',\n })),\n // Chain restrictions\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional list of chains this wallet can use',\n })),\n});\n/**\n * Schema for amped_rename_wallet\n */\nconst RenameWalletSchema = Type.Object({\n currentNickname: Type.String({\n description: 'Current wallet nickname',\n }),\n newNickname: Type.String({\n description: 'New wallet nickname',\n }),\n});\n/**\n * Schema for amped_remove_wallet\n */\nconst RemoveWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to remove',\n }),\n confirm: Type.Optional(Type.Boolean({\n description: 'Set to true to confirm removal',\n default: false,\n })),\n});\n/**\n * Schema for amped_set_default_wallet\n */\nconst SetDefaultWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to set as default',\n }),\n});\n// ============================================================================\n// Handlers\n// ============================================================================\n/**\n * Add a new wallet with a nickname\n */\nasync function handleAddWallet(params) {\n const { nickname, source, path, apiKey, apiUrl, address, privateKey, chains } = params;\n console.log('[walletManagement:addWallet] Adding wallet', { nickname, source });\n // Validate required fields based on source\n if (source === 'bankr' && !apiKey) {\n throw new Error('Bankr wallet requires apiKey');\n }\n if (source === 'env' && (!address || !privateKey)) {\n throw new Error('Env wallet requires both address and privateKey');\n }\n // Build config\n const config = {\n source: source,\n chains,\n };\n if (source === 'evm-wallet-skill') {\n if (path)\n config.path = path;\n }\n else if (source === 'bankr') {\n config.apiKey = apiKey;\n if (apiUrl)\n config.apiUrl = apiUrl;\n }\n else if (source === 'env') {\n config.address = address;\n config.privateKey = privateKey;\n }\n // Add wallet\n const walletManager = getWalletManager();\n await walletManager.addWallet(nickname, config);\n // Get the new wallet info\n const wallet = await walletManager.resolve(nickname);\n const walletAddress = await wallet.getAddress();\n return {\n success: true,\n message: `Wallet \"${nickname}\" added successfully`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: source,\n address: walletAddress,\n chains: wallet.supportedChains,\n },\n hint: `You can now use: \"swap 100 USDC to ETH using ${nickname}\"`,\n };\n}\n/**\n * Rename a wallet\n */\nasync function handleRenameWallet(params) {\n const { currentNickname, newNickname } = params;\n console.log('[walletManagement:renameWallet] Renaming wallet', {\n from: currentNickname,\n to: newNickname\n });\n const walletManager = getWalletManager();\n // Get current info before rename\n const wallet = await walletManager.resolve(currentNickname);\n const address = await wallet.getAddress();\n // Rename\n await walletManager.renameWallet(currentNickname, newNickname);\n return {\n success: true,\n message: `Wallet renamed from \"${currentNickname}\" to \"${newNickname}\"`,\n wallet: {\n nickname: newNickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: `Now use: \"swap 100 USDC using ${newNickname}\"`,\n };\n}\n/**\n * Remove a wallet\n */\nasync function handleRemoveWallet(params) {\n const { nickname, confirm } = params;\n console.log('[walletManagement:removeWallet] Removing wallet', { nickname, confirm });\n const walletManager = getWalletManager();\n // Get wallet info before removal\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n if (!confirm) {\n return {\n success: false,\n requiresConfirmation: true,\n message: `Are you sure you want to remove wallet \"${nickname}\" (${address})?`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: 'Call again with confirm: true to proceed',\n };\n }\n // Remove\n await walletManager.removeWallet(nickname);\n // List remaining wallets\n const remainingWallets = await walletManager.listWallets();\n return {\n success: true,\n message: `Wallet \"${nickname}\" removed`,\n remainingWallets: remainingWallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n isDefault: w.isDefault,\n })),\n };\n}\n/**\n * Set default wallet\n */\nasync function handleSetDefaultWallet(params) {\n const { nickname } = params;\n console.log('[walletManagement:setDefaultWallet] Setting default', { nickname });\n const walletManager = getWalletManager();\n // Validate wallet exists\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n // Set default\n await walletManager.setDefaultWallet(nickname);\n return {\n success: true,\n message: `Default wallet set to \"${nickname}\"`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n chains: [...wallet.supportedChains],\n },\n hint: 'Operations without a wallet specified will now use this wallet',\n };\n}\n// ============================================================================\n// Error Wrapper\n// ============================================================================\nfunction wrapHandler(handler) {\n return async (params) => {\n try {\n return await handler(params);\n }\n catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[walletManagement] Error:', message);\n return {\n success: false,\n error: message,\n };\n }\n };\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register wallet management tools\n */\nexport function registerWalletManagementTools(agentTools) {\n // 1. Add wallet\n agentTools.register({\n name: 'amped_add_wallet',\n summary: 'Add a new wallet with a nickname for easy reference',\n description: 'Add a wallet from evm-wallet-skill, Bankr, or environment variables. ' +\n 'Give it a memorable nickname like \"trading\", \"savings\", or \"degen\". ' +\n 'The wallet will be saved to config and available across sessions.',\n schema: AddWalletSchema,\n handler: wrapHandler(handleAddWallet),\n });\n // 2. Rename wallet\n agentTools.register({\n name: 'amped_rename_wallet',\n summary: 'Rename a wallet to a new nickname',\n description: 'Change the nickname of an existing wallet. ' +\n 'The wallet address and configuration remain the same, only the nickname changes.',\n schema: RenameWalletSchema,\n handler: wrapHandler(handleRenameWallet),\n });\n // 3. Remove wallet\n agentTools.register({\n name: 'amped_remove_wallet',\n summary: 'Remove a wallet from configuration',\n description: 'Remove a wallet by nickname. This only removes it from the config file, ' +\n 'it does NOT delete the actual wallet or funds. Requires confirmation.',\n schema: RemoveWalletSchema,\n handler: wrapHandler(handleRemoveWallet),\n });\n // 4. Set default wallet\n agentTools.register({\n name: 'amped_set_default_wallet',\n summary: 'Set which wallet to use by default',\n description: 'Set the default wallet for operations. When you don\\'t specify a wallet, ' +\n 'this one will be used automatically.',\n schema: SetDefaultWalletSchema,\n handler: wrapHandler(handleSetDefaultWallet),\n });\n}\n// Export schemas and handlers for testing\nexport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet, };\n//# sourceMappingURL=walletManagement.js.map",
785 "inputSchema": {},
786 "outputSchema": null,
787 "icons": null,
788 "annotations": null,
789 "meta": null,
790 "execution": null
791 },
792 {
793 "name": "portfolio.d.ts",
794 "title": null,
795 "description": "Script: portfolio.d.ts. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_portfolio_summary\n */\nexport declare const PortfolioSummarySchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n chains: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n includeZeroBalances: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ntype PortfolioSummaryParams = Static<typeof PortfolioSummarySchema>;\n/**\n * Handle portfolio summary request\n */\nexport declare function handlePortfolioSummary(params: PortfolioSummaryParams): Promise<unknown>;\n/**\n * Get all Solana balances for a wallet\n */\nexport declare function getSolanaWalletBalances(address: string, includeZeroBalances?: boolean): Promise<{\n chainId: string;\n chainName: string;\n native: {\n symbol: string;\n balance: string;\n usdValue?: string;\n };\n tokens: Array<{\n symbol: string;\n balance: string;\n address: string;\n usdValue?: string;\n }>;\n chainTotalUsd?: string;\n} | null>;\nexport {};\n//# sourceMappingURL=portfolio.d.ts.map",
796 "inputSchema": {},
797 "outputSchema": null,
798 "icons": null,
799 "annotations": null,
800 "meta": null,
801 "execution": null
802 },
803 {
804 "name": "types.js",
805 "title": null,
806 "description": "Script: types.js. Code:\n/**\n * Common types for Amped DeFi plugin\n */\nexport {};\n//# sourceMappingURL=types.js.map",
807 "inputSchema": {},
808 "outputSchema": null,
809 "icons": null,
810 "annotations": null,
811 "meta": null,
812 "execution": null
813 },
814 {
815 "name": "spokeProviderFactory.js",
816 "title": null,
817 "description": "Script: spokeProviderFactory.js. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n *\n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\n// Official SDK wallet provider\nimport { EvmWalletProvider } from '@sodax/wallet-sdk-core';\n// Import spoke providers and chain config from SDK\nimport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\n// Import chain configuration from types\nimport { spokeChainConfig } from '@sodax/types';\n// Import wallet management\nimport { getWalletManager, createBankrWalletProvider } from '../wallet';\nimport { getWalletAdapter } from '../wallet/skillWalletAdapter';\nimport { normalizeChainId, getBankrChainId } from '../wallet/types';\n// Cache for providers: Map<cacheKey, SpokeProvider>\nconst providerCache = new Map();\n// Sonic hub chain identifier\nconst SONIC_CHAIN_ID = 'sonic';\n// Chain ID mapping for SDK (some chains need specific format)\nconst CHAIN_ID_MAP = {\n 'sonic': 'sonic',\n 'ethereum': 'ethereum',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n/**\n * Get RPC URL for a chain\n */\nasync function getRpcUrl(chainId) {\n const skillAdapter = getWalletAdapter();\n return skillAdapter.getRpcUrl(chainId);\n}\n/**\n * Get the SDK chain ID for a given chain\n */\nfunction getSdkChainId(chainId) {\n return (CHAIN_ID_MAP[chainId] || chainId);\n}\n/**\n * Validate that wallet supports the requested chain\n */\nfunction validateChainSupport(wallet, chainId) {\n const normalizedForWallet = normalizeChainId(chainId);\n if (!wallet.supportsChain(normalizedForWallet)) {\n throw new Error(`Wallet \"${wallet.nickname}\" doesn't support chain \"${chainId}\". ` +\n `Supported chains: ${wallet.supportedChains.join(', ')}. ` +\n `Try a different wallet.`);\n }\n}\n/**\n * Create a spoke provider for local key signing\n */\nasync function createLocalSpokeProvider(wallet, chainId, rpcUrl) {\n if (!wallet.getPrivateKey) {\n throw new Error(`Wallet \"${wallet.nickname}\" does not support local signing`);\n }\n const privateKey = await wallet.getPrivateKey();\n const sdkChainId = getSdkChainId(chainId);\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}. Available: ${Object.keys(spokeChainConfig).join(', ')}`);\n }\n // Create wallet provider using official SDK\n const walletProvider = new EvmWalletProvider({\n privateKey,\n chainId: sdkChainId,\n rpcUrl: rpcUrl,\n });\n // Use SonicSpokeProvider for Sonic hub chain, EvmSpokeProvider for others\n if (chainId === SONIC_CHAIN_ID) {\n console.log('[spokeProviderFactory] Creating SonicSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n });\n return new SonicSpokeProvider(walletProvider, chainConfig, rpcUrl);\n }\n else {\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n });\n return new EvmSpokeProvider(walletProvider, chainConfig, rpcUrl);\n }\n}\n/**\n * Create a spoke provider for Bankr wallet\n * Uses BankrWalletProvider which submits transactions to Bankr API\n */\nasync function createBankrSpokeProvider(wallet, chainId, rpcUrl) {\n const sdkChainId = getSdkChainId(chainId);\n // Normalize chain ID for Bankr lookup (0x2105.base -> base)\n const normalizedChainId = normalizeChainId(chainId);\n const numericChainId = getBankrChainId(normalizedChainId);\n console.log('[spokeProviderFactory] Bankr chain resolution', {\n input: chainId,\n normalized: normalizedChainId,\n numeric: numericChainId,\n });\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}`);\n }\n // Get Bankr API key from environment\n const apiKey = process.env.BANKR_API_KEY;\n if (!apiKey) {\n throw new Error('BANKR_API_KEY environment variable not set');\n }\n // Get the Bankr wallet address (cached after first call)\n const walletAddress = await wallet.getAddress();\n // Create BankrWalletProvider which implements IEvmWalletProvider\n const walletProvider = createBankrWalletProvider({\n apiKey,\n apiUrl: process.env.BANKR_API_URL,\n chainId: numericChainId,\n rpcUrl,\n cachedAddress: walletAddress,\n });\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider with Bankr backend', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n address: walletAddress?.slice(0, 10) + '...',\n });\n // Use standard EvmSpokeProvider with our BankrWalletProvider\n // The SDK doesn't care how transactions are signed - it just calls the interface methods\n return new EvmSpokeProvider(walletProvider, // BankrWalletProvider implements IEvmWalletProvider\n chainConfig, rpcUrl);\n}\n/**\n * Create a spoke provider for the given wallet and chain\n *\n * @param walletId - Wallet nickname (e.g., \"main\", \"bankr\", \"trading\")\n * @param chainId - Chain identifier (e.g., \"ethereum\", \"base\")\n */\nasync function createSpokeProvider(walletId, chainId) {\n // Get wallet from unified manager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n // Validate chain support\n validateChainSupport(wallet, chainId);\n const rpcUrl = await getRpcUrl(chainId);\n // Route based on wallet type\n if (wallet.type === 'bankr') {\n // Use BankrWalletProvider for Bankr wallets\n return createBankrSpokeProvider(wallet, chainId, rpcUrl);\n }\n // Local key signing (evm-wallet-skill or env)\n return createLocalSpokeProvider(wallet, chainId, rpcUrl);\n}\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport async function getSpokeProvider(walletId, chainId, raw = false) {\n const cacheKey = `${walletId}:${chainId}`;\n // Check cache\n const cached = providerCache.get(cacheKey);\n if (cached) {\n console.log('[spokeProviderFactory] Using cached provider', {\n walletId,\n chainId,\n });\n return cached;\n }\n // Create new provider\n const provider = await createSpokeProvider(walletId, chainId);\n // Cache the provider\n providerCache.set(cacheKey, provider);\n return provider;\n}\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport function clearProviderCache() {\n providerCache.clear();\n console.log('[spokeProviderFactory] Provider cache cleared');\n}\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport function getCacheStats() {\n return {\n size: providerCache.size,\n keys: Array.from(providerCache.keys()),\n };\n}\n//# sourceMappingURL=spokeProviderFactory.js.map",
818 "inputSchema": {},
819 "outputSchema": null,
820 "icons": null,
821 "annotations": null,
822 "meta": null,
823 "execution": null
824 },
825 {
826 "name": "spokeProviderFactory.d.ts",
827 "title": null,
828 "description": "Script: spokeProviderFactory.d.ts. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n *\n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\nimport { type SpokeProvider } from '@sodax/sdk';\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport declare function getSpokeProvider(walletId: string, chainId: string, raw?: boolean): Promise<SpokeProvider>;\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport declare function clearProviderCache(): void;\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport declare function getCacheStats(): {\n size: number;\n keys: string[];\n};\nexport type { SpokeProvider };\n//# sourceMappingURL=spokeProviderFactory.d.ts.map",
829 "inputSchema": {},
830 "outputSchema": null,
831 "icons": null,
832 "annotations": null,
833 "meta": null,
834 "execution": null
835 },
836 {
837 "name": "policyEngine.d.ts",
838 "title": null,
839 "description": "Script: policyEngine.d.ts. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\nimport { BridgeOperation, PolicyConfig } from '../types';\n/**\n * Policy check result\n */\nexport interface PolicyCheckResult {\n allowed: boolean;\n reason?: string;\n details?: Record<string, unknown>;\n}\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport declare class PolicyEngine {\n private config;\n constructor(policyId?: string);\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n private loadPolicyConfig;\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n private checkChainAllowed;\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n private checkTokenAllowed;\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n private checkRecipientNotBlocked;\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n private checkBridgeAmount;\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n checkBridge(operation: BridgeOperation): Promise<PolicyCheckResult>;\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig(): PolicyConfig;\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies(): string[];\n /**\n * Check swap input amount against USD limits\n */\n private checkSwapAmount;\n /**\n * Check slippage against maximum allowed\n */\n private checkSlippage;\n /**\n * Check a swap operation against all policies\n */\n checkSwap(params: {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n inputAmount: string;\n slippageBps: number;\n policyId?: string;\n }): Promise<PolicyCheckResult>;\n /**\n * Check borrow amount against limits\n */\n private checkBorrowAmount;\n /**\n * Check a money market operation against all policies\n */\n checkMoneyMarket(params: {\n walletId: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n amountUsd?: number;\n operation: 'supply' | 'withdraw' | 'borrow' | 'repay';\n policyId?: string;\n }): Promise<PolicyCheckResult>;\n}\n//# sourceMappingURL=policyEngine.d.ts.map",
840 "inputSchema": {},
841 "outputSchema": null,
842 "icons": null,
843 "annotations": null,
844 "meta": null,
845 "execution": null
846 },
847 {
848 "name": "policyEngine.js",
849 "title": null,
850 "description": "Script: policyEngine.js. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\nimport { normalizeChainId } from '../wallet/types';\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport class PolicyEngine {\n config;\n constructor(policyId) {\n this.config = this.loadPolicyConfig(policyId);\n }\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n loadPolicyConfig(policyId) {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson) {\n return {};\n }\n try {\n const allConfigs = JSON.parse(limitsJson);\n // If policyId is specified, use that config; otherwise use 'default' or empty\n const config = policyId\n ? allConfigs[policyId]\n : allConfigs['default'] || allConfigs;\n if (policyId && !config) {\n return allConfigs['default'] || {};\n }\n return config || {};\n }\n catch (_error) {\n return {};\n }\n }\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n checkChainAllowed(chainId) {\n const { allowedChains } = this.config;\n if (allowedChains && allowedChains.length > 0) {\n const normalizedChain = normalizeChainId(chainId);\n if (!allowedChains.includes(normalizedChain)) {\n return {\n allowed: false,\n reason: `Chain not allowed: ${chainId}. Allowed chains: ${allowedChains.join(', ')}`,\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n checkTokenAllowed(chainId, token) {\n const { allowedTokensByChain } = this.config;\n if (allowedTokensByChain) {\n const normalizedChainForTokens = normalizeChainId(chainId);\n const allowedTokens = allowedTokensByChain[normalizedChainForTokens];\n if (allowedTokens && allowedTokens.length > 0) {\n if (!allowedTokens.includes(token)) {\n return {\n allowed: false,\n reason: `Token not allowed on ${chainId}: ${token}. Allowed tokens: ${allowedTokens.join(', ')}`,\n };\n }\n }\n }\n return { allowed: true };\n }\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n checkRecipientNotBlocked(recipient) {\n const { blockedRecipients } = this.config;\n if (blockedRecipients && blockedRecipients.length > 0) {\n if (blockedRecipients.includes(recipient.toLowerCase())) {\n return {\n allowed: false,\n reason: `Recipient is blocked: ${recipient}`,\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n checkBridgeAmount(token, amount) {\n const { maxBridgeAmountToken } = this.config;\n if (maxBridgeAmountToken) {\n const maxAmount = maxBridgeAmountToken[token];\n if (maxAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxAmount) {\n return {\n allowed: false,\n reason: `Bridge amount ${amount} exceeds maximum ${maxAmount} for token ${token}`,\n details: { maxAllowed: maxAmount, requested: amountNum },\n };\n }\n }\n }\n return { allowed: true };\n }\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n async checkBridge(operation) {\n const { srcChainId, dstChainId, srcToken, dstToken, amount, recipient } = operation;\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed)\n return srcChainCheck;\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed)\n return srcTokenCheck;\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed)\n return dstTokenCheck;\n // Check amount limits\n const amountCheck = this.checkBridgeAmount(srcToken, amount);\n if (!amountCheck.allowed)\n return amountCheck;\n // Check recipient if specified\n if (recipient) {\n const recipientCheck = this.checkRecipientNotBlocked(recipient);\n if (!recipientCheck.allowed)\n return recipientCheck;\n }\n return { allowed: true };\n }\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig() {\n return { ...this.config };\n }\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies() {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson)\n return [];\n try {\n const allConfigs = JSON.parse(limitsJson);\n return Object.keys(allConfigs);\n }\n catch {\n return [];\n }\n }\n // ============================================================================\n // Swap Policy Checks\n // ============================================================================\n /**\n * Check swap input amount against USD limits\n */\n checkSwapAmount(inputAmount, srcToken) {\n const { maxSwapInputUsd, maxSwapInputToken } = this.config;\n // Check per-token limit if configured\n if (maxSwapInputToken) {\n const maxTokenAmount = maxSwapInputToken[srcToken];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(inputAmount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Swap input amount ${inputAmount} exceeds maximum ${maxTokenAmount} for token ${srcToken}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n // Note: USD limit check would require price oracle integration\n // For now, we skip enforcement without prices\n return { allowed: true };\n }\n /**\n * Check slippage against maximum allowed\n */\n checkSlippage(slippageBps) {\n const { maxSlippageBps } = this.config;\n if (maxSlippageBps !== undefined && slippageBps > maxSlippageBps) {\n return {\n allowed: false,\n reason: `Slippage ${slippageBps} bps exceeds maximum allowed ${maxSlippageBps} bps`,\n details: { maxAllowed: maxSlippageBps, requested: slippageBps },\n };\n }\n return { allowed: true };\n }\n /**\n * Check a swap operation against all policies\n */\n async checkSwap(params) {\n const { srcChainId, dstChainId, srcToken, dstToken, inputAmount, slippageBps } = params;\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed)\n return srcChainCheck;\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed)\n return srcTokenCheck;\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed)\n return dstTokenCheck;\n // Check swap amount limits\n const amountCheck = this.checkSwapAmount(inputAmount, srcToken);\n if (!amountCheck.allowed)\n return amountCheck;\n // Check slippage\n const slippageCheck = this.checkSlippage(slippageBps);\n if (!slippageCheck.allowed)\n return slippageCheck;\n return { allowed: true };\n }\n // ============================================================================\n // Money Market Policy Checks\n // ============================================================================\n /**\n * Check borrow amount against limits\n */\n checkBorrowAmount(token, amount, amountUsd) {\n const { maxBorrowUsd, maxBorrowToken } = this.config;\n // Check per-token limit if configured\n if (maxBorrowToken) {\n const maxTokenAmount = maxBorrowToken[token];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Borrow amount ${amount} exceeds maximum ${maxTokenAmount} for token ${token}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n // Check USD limit if amountUsd is provided\n if (maxBorrowUsd !== undefined && amountUsd !== undefined) {\n if (amountUsd > maxBorrowUsd) {\n return {\n allowed: false,\n reason: `Borrow amount $${amountUsd} exceeds maximum $${maxBorrowUsd}`,\n details: { maxAllowed: maxBorrowUsd, requested: amountUsd },\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check a money market operation against all policies\n */\n async checkMoneyMarket(params) {\n const { chainId, dstChainId, token, amount, amountUsd, operation } = params;\n // Check source chain\n const chainCheck = this.checkChainAllowed(chainId);\n if (!chainCheck.allowed)\n return chainCheck;\n // Check destination chain if cross-chain operation\n if (dstChainId) {\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n }\n // Check token\n const tokenCheck = this.checkTokenAllowed(chainId, token);\n if (!tokenCheck.allowed)\n return tokenCheck;\n // Operation-specific checks\n if (operation === 'borrow') {\n const borrowCheck = this.checkBorrowAmount(token, amount, amountUsd);\n if (!borrowCheck.allowed)\n return borrowCheck;\n }\n return { allowed: true };\n }\n}\n//# sourceMappingURL=policyEngine.js.map",
851 "inputSchema": {},
852 "outputSchema": null,
853 "icons": null,
854 "annotations": null,
855 "meta": null,
856 "execution": null
857 },
858 {
859 "name": "index.js",
860 "title": null,
861 "description": "Script: index.js. Code:\n/**\n * Amped DeFi Plugin\n *\n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nimport { getCacheStats } from './providers/spokeProviderFactory';\nimport { PolicyEngine } from './policy/policyEngine';\nimport { getWalletManager } from './wallet/walletManager';\n// Tool schemas and handlers\nimport { SwapQuoteSchema, SwapExecuteSchema, SwapStatusSchema, SwapCancelSchema, handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel } from './tools/swap';\nimport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema, handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute } from './tools/bridge';\nimport { MmSupplySchema, MmWithdrawSchema, MmBorrowSchema, MmRepaySchema, handleMmSupply, handleMmWithdraw, handleMmBorrow, handleMmRepay } from './tools/moneyMarket';\nimport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketReservesSchema, MoneyMarketPositionsSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketReserves, handleMoneyMarketPositions, handleCrossChainPositions, handleUserIntents, handleListWallets } from './tools/discovery';\nimport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet } from './tools/walletManagement';\nimport { PortfolioSummarySchema, handlePortfolioSummary } from './tools/portfolio';\n/**\n * Plugin configuration schema (matches openclaw.plugin.json)\n */\nconst configSchema = Type.Object({\n walletsJson: Type.Optional(Type.String()),\n rpcUrlsJson: Type.Optional(Type.String()),\n mode: Type.Optional(Type.Union([Type.Literal('execute'), Type.Literal('simulate')])),\n dynamicConfig: Type.Optional(Type.Boolean()),\n});\n/**\n * Apply plugin config to environment\n */\nfunction applyConfig(config) {\n if (config.walletsJson && typeof config.walletsJson === 'string') {\n process.env.AMPED_OC_WALLETS_JSON = config.walletsJson;\n }\n if (config.rpcUrlsJson && typeof config.rpcUrlsJson === 'string') {\n process.env.AMPED_OC_RPC_URLS_JSON = config.rpcUrlsJson;\n }\n if (config.mode && typeof config.mode === 'string') {\n process.env.AMPED_OC_MODE = config.mode;\n }\n if (config.dynamicConfig !== undefined) {\n process.env.AMPED_OC_SODAX_DYNAMIC_CONFIG = config.dynamicConfig ? 'true' : 'false';\n }\n}\n/**\n * Validate required environment variables\n */\nfunction validateEnvironment() {\n const missing = [];\n if (!process.env.AMPED_OC_WALLETS_JSON) {\n missing.push('AMPED_OC_WALLETS_JSON');\n }\n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (mode === 'execute' && !process.env.AMPED_OC_RPC_URLS_JSON) {\n missing.push('AMPED_OC_RPC_URLS_JSON');\n }\n return missing;\n}\n/**\n * Deep-clone an object while converting BigInt values to strings\n * Prevents serialization errors when OpenClaw framework handles the details field\n */\nfunction sanitizeBigInt(obj) {\n return JSON.parse(JSON.stringify(obj, (_, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n/**\n * Helper to wrap a handler for OpenClaw's tool format\n */\nfunction wrapHandler(handler) {\n return async (_toolCallId, params) => {\n const result = await handler(params);\n // Sanitize BigInt values in details to prevent framework serialization errors\n const sanitizedResult = sanitizeBigInt(result);\n return {\n content: [{ type: 'text', text: JSON.stringify(result, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }],\n details: sanitizedResult,\n };\n };\n}\n/**\n * OpenClaw Plugin Definition\n */\nexport default {\n id: 'amped-defi',\n name: 'Amped DeFi',\n description: 'DeFi operations plugin for swaps, bridging, and money market via SODAX SDK',\n kind: 'tools',\n configSchema,\n register(api) {\n // Apply config from OpenClaw\n const config = api.pluginConfig || {};\n applyConfig(config);\n // Check for missing env vars (silent)\n validateEnvironment();\n // Initialize core components (async, non-blocking, silent)\n (async () => {\n try {\n await getSodaxClientAsync();\n getCacheStats();\n new PolicyEngine();\n getWalletManager();\n }\n catch (_error) {\n // Silent initialization - errors will surface when tools are used\n }\n })();\n // Register Discovery Tools\n api.registerTool({\n name: 'amped_supported_chains',\n description: 'List all blockchain networks supported by the Amped DeFi plugin',\n parameters: SupportedChainsSchema,\n execute: wrapHandler(handleSupportedChains),\n });\n api.registerTool({\n name: 'amped_supported_tokens',\n description: 'List tokens supported on a specific chain for swaps and bridging',\n parameters: SupportedTokensSchema,\n execute: wrapHandler(handleSupportedTokens),\n });\n api.registerTool({\n name: 'amped_wallet_address',\n description: 'Get the wallet address for a specific wallet ID',\n parameters: WalletAddressSchema,\n execute: wrapHandler(handleWalletAddress),\n });\n api.registerTool({\n name: 'amped_money_market_reserves',\n description: 'Get money market reserve info (APY, utilization, liquidity)',\n parameters: MoneyMarketReservesSchema,\n execute: wrapHandler(handleMoneyMarketReserves),\n });\n api.registerTool({\n name: 'amped_money_market_positions',\n description: 'Get user positions in money market on a single chain',\n parameters: MoneyMarketPositionsSchema,\n execute: wrapHandler(handleMoneyMarketPositions),\n });\n api.registerTool({\n name: 'amped_cross_chain_positions',\n description: 'Get aggregated money market positions across all chains',\n parameters: CrossChainPositionsSchema,\n execute: wrapHandler(handleCrossChainPositions),\n });\n api.registerTool({\n name: 'amped_user_intents',\n description: 'Query user intent history from SODAX API',\n parameters: UserIntentsSchema,\n execute: wrapHandler(handleUserIntents),\n });\n api.registerTool({\n name: 'amped_list_wallets',\n description: 'List ALL configured wallets including evm-wallet-skill, Bankr, and env wallets. Shows nicknames, addresses, types, and supported chains. Use this when user asks \"what wallets do I have\" or \"show my wallets\".',\n parameters: ListWalletsSchema,\n execute: wrapHandler(handleListWallets),\n });\n api.registerTool({\n name: 'amped_portfolio_summary',\n description: 'Get a comprehensive portfolio summary including wallet balances (native + major tokens) across chains and money market positions. Use when user asks for \"portfolio\", \"balances\", or \"summary of positions\".',\n parameters: PortfolioSummarySchema,\n execute: wrapHandler(handlePortfolioSummary),\n });\n // Register Wallet Management Tools\n api.registerTool({\n name: 'amped_add_wallet',\n description: 'Add a new wallet with a nickname (evm-wallet-skill, bankr, or env)',\n parameters: AddWalletSchema,\n execute: wrapHandler(handleAddWallet),\n });\n api.registerTool({\n name: 'amped_rename_wallet',\n description: 'Rename a wallet to a new nickname',\n parameters: RenameWalletSchema,\n execute: wrapHandler(handleRenameWallet),\n });\n api.registerTool({\n name: 'amped_remove_wallet',\n description: 'Remove a wallet from configuration (does not delete funds)',\n parameters: RemoveWalletSchema,\n execute: wrapHandler(handleRemoveWallet),\n });\n api.registerTool({\n name: 'amped_set_default_wallet',\n description: 'Set which wallet to use by default for operations',\n parameters: SetDefaultWalletSchema,\n execute: wrapHandler(handleSetDefaultWallet),\n });\n // Register Swap Tools\n api.registerTool({\n name: 'amped_swap_quote',\n description: 'Get a quote for swapping tokens (same chain or cross-chain)',\n parameters: SwapQuoteSchema,\n execute: wrapHandler(handleSwapQuote),\n });\n api.registerTool({\n name: 'amped_swap_execute',\n description: 'Execute a token swap using a previously obtained quote',\n parameters: SwapExecuteSchema,\n execute: wrapHandler(handleSwapExecute),\n });\n api.registerTool({\n name: 'amped_swap_status',\n description: 'Check the status of a swap/bridge operation by intent ID',\n parameters: SwapStatusSchema,\n execute: wrapHandler(handleSwapStatus),\n });\n api.registerTool({\n name: 'amped_swap_cancel',\n description: 'Cancel a pending swap/bridge operation',\n parameters: SwapCancelSchema,\n execute: wrapHandler(handleSwapCancel),\n });\n // Register Bridge Tools\n api.registerTool({\n name: 'amped_bridge_discover',\n description: 'Discover available bridge routes between chains',\n parameters: BridgeDiscoverSchema,\n execute: wrapHandler(handleBridgeDiscover),\n });\n api.registerTool({\n name: 'amped_bridge_quote',\n description: 'Get a quote for bridging tokens between chains',\n parameters: BridgeQuoteSchema,\n execute: wrapHandler(handleBridgeQuote),\n });\n api.registerTool({\n name: 'amped_bridge_execute',\n description: 'Execute a bridge transfer using a previously obtained quote',\n parameters: BridgeExecuteSchema,\n execute: wrapHandler(handleBridgeExecute),\n });\n // Register Money Market Tools\n api.registerTool({\n name: 'amped_mm_supply',\n description: 'Supply (deposit) tokens to money market to earn interest',\n parameters: MmSupplySchema,\n execute: wrapHandler(handleMmSupply),\n });\n api.registerTool({\n name: 'amped_mm_withdraw',\n description: 'Withdraw supplied tokens from money market',\n parameters: MmWithdrawSchema,\n execute: wrapHandler(handleMmWithdraw),\n });\n api.registerTool({\n name: 'amped_mm_borrow',\n description: 'Borrow tokens from money market (cross-chain capable)',\n parameters: MmBorrowSchema,\n execute: wrapHandler(handleMmBorrow),\n });\n api.registerTool({\n name: 'amped_mm_repay',\n description: 'Repay borrowed tokens to money market',\n parameters: MmRepaySchema,\n execute: wrapHandler(handleMmRepay),\n });\n // Register cleanup service\n api.registerService({\n id: 'amped-defi',\n start: () => { },\n stop: async () => {\n resetSodaxClient();\n },\n });\n },\n};\n// Re-export types and utilities for external use\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\n// Legacy exports for backward compatibility\nexport async function activate() {\n // Deprecated - use default export\n}\nexport async function deactivate() {\n resetSodaxClient();\n}\n//# sourceMappingURL=index.js.map",
862 "inputSchema": {},
863 "outputSchema": null,
864 "icons": null,
865 "annotations": null,
866 "meta": null,
867 "execution": null
868 },
869 {
870 "name": "setup.js",
871 "title": null,
872 "description": "Script: setup.js. Code:\n/**\n * Jest Test Setup\n */\n// Mock console methods to reduce noise during tests\nglobal.console = {\n ...console,\n log: jest.fn(),\n debug: jest.fn(),\n info: jest.fn(),\n warn: jest.fn(),\n error: jest.fn(),\n};\n// Set default test environment\nprocess.env.AMPED_OC_MODE = 'execute';\nprocess.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n test: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123def456',\n },\n});\nprocess.env.AMPED_OC_RPC_URLS_JSON = JSON.stringify({\n ethereum: 'https://eth-mainnet.example.com',\n arbitrum: 'https://arb-mainnet.example.com',\n sonic: 'https://rpc.sonic.example.com',\n});\n// Global test timeout\njest.setTimeout(10000);\n// Clean up after each test\nafterEach(() => {\n jest.clearAllMocks();\n});\n//# sourceMappingURL=setup.js.map",
873 "inputSchema": {},
874 "outputSchema": null,
875 "icons": null,
876 "annotations": null,
877 "meta": null,
878 "execution": null
879 },
880 {
881 "name": "setup.d.ts",
882 "title": null,
883 "description": "Script: setup.d.ts. Code:\n/**\n * Jest Test Setup\n */\n//# sourceMappingURL=setup.d.ts.map",
884 "inputSchema": {},
885 "outputSchema": null,
886 "icons": null,
887 "annotations": null,
888 "meta": null,
889 "execution": null
890 },
891 {
892 "name": "basic-usage.ts",
893 "title": null,
894 "description": "Script: basic-usage.ts. Code:\n/**\n * Basic Usage Examples for Amped DeFi Plugin\n */\n\nimport { activate, deactivate } from '../src/index';\n\n// Mock agent tools for demonstration\nconst mockAgentTools = {\n register: (tool: { name: string; summary: string; schema: unknown; handler: Function }) => {\n console.log(`Registered: ${tool.name}`);\n },\n};\n\nasync function main() {\n console.log('=== Amped DeFi Plugin Examples ===\\n');\n\n // Initialize plugin\n console.log('1. Initializing plugin...');\n await activate(mockAgentTools as any);\n console.log();\n\n // Example workflows\n await demonstrateCrossChainPositionView();\n await demonstrateSwapWorkflow();\n await demonstrateCrossChainBorrow();\n\n // Cleanup\n console.log('\\n5. Deactivating plugin...');\n await deactivate();\n}\n\nasync function demonstrateCrossChainPositionView() {\n console.log('2. Cross-Chain Position View Example');\n console.log(' Tool: amped_cross_chain_positions');\n console.log(' Purpose: Get unified portfolio view across all chains');\n console.log(' Parameters: { walletId: \"main\" }');\n console.log(' Returns:');\n console.log(' - Total supply/borrow across all chains');\n console.log(' - Health factor and liquidation risk');\n console.log(' - Available borrowing power');\n console.log(' - Weighted APYs and net yield');\n console.log(' - Per-chain breakdowns');\n console.log(' - Risk metrics and recommendations');\n console.log();\n}\n\nasync function demonstrateSwapWorkflow() {\n console.log('3. Swap Workflow Example');\n console.log(' Step 1: Get Quote');\n console.log(' Tool: amped_swap_quote');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' srcChainId: \"ethereum\",');\n console.log(' dstChainId: \"arbitrum\",');\n console.log(' srcToken: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", // USDC');\n console.log(' dstToken: \"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT');\n console.log(' amount: \"1000\",');\n console.log(' type: \"exact_input\",');\n console.log(' slippageBps: 100');\n console.log(' }');\n console.log();\n console.log(' Step 2: Execute Swap');\n console.log(' Tool: amped_swap_execute');\n console.log(' Parameters: { walletId, quote, maxSlippageBps: 100 }');\n console.log();\n}\n\nasync function demonstrateCrossChainBorrow() {\n console.log('4. Cross-Chain Money Market Borrow Example');\n console.log(' Feature: Supply on Chain A, Borrow to Chain B');\n console.log();\n console.log(' Step 1: Supply on Ethereum');\n console.log(' Tool: amped_mm_supply');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' chainId: \"ethereum\",');\n console.log(' token: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", // USDC');\n console.log(' amount: \"50000\",');\n console.log(' useAsCollateral: true');\n console.log(' }');\n console.log();\n console.log(' Step 2: Borrow to Arbitrum (Cross-Chain!)');\n console.log(' Tool: amped_mm_borrow');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' chainId: \"ethereum\", // Collateral source');\n console.log(' dstChainId: \"arbitrum\", // Borrowed tokens destination');\n console.log(' token: \"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT');\n console.log(' amount: \"20000\",');\n console.log(' interestRateMode: 2 // Variable rate');\n console.log(' }');\n console.log();\n console.log(' Result: User receives 20k USDT on Arbitrum while collateral stays on Ethereum!');\n console.log();\n}\n\n// Run examples\nmain().catch(console.error);\n",
895 "inputSchema": {},
896 "outputSchema": null,
897 "icons": null,
898 "annotations": null,
899 "meta": null,
900 "execution": null
901 },
902 {
903 "name": "tokenResolver.ts",
904 "title": null,
905 "description": "Script: tokenResolver.ts. Code:\n/**\n * Token Resolution Utility\n * \n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\n\nimport type { Token } from '@sodax/types';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\n\n// Cache tokens per chain to avoid repeated lookups\nconst tokenCache = new Map<string, Token[]>();\n\n// Native token addresses\nconst EVM_NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';\nconst SOLANA_NATIVE_ADDRESS = '11111111111111111111111111111111';\n\n// Native token configs per chain (18 decimals for all EVM chains, 9 for Solana)\nconst NATIVE_TOKENS: Record<string, { symbol: string; name: string; decimals: number; address: string }> = {\n sonic: { symbol: 'S', name: 'Sonic', decimals: 18, address: EVM_NATIVE_ADDRESS },\n ethereum: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa4b1.arbitrum': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x2105.base': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa.optimism': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x38.bsc': { symbol: 'BNB', name: 'BNB', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x89.polygon': { symbol: 'POL', name: 'POL', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa86a.avax': { symbol: 'AVAX', name: 'Avalanche', decimals: 18, address: EVM_NATIVE_ADDRESS },\n hyper: { symbol: 'HYPE', name: 'Hyperliquid', decimals: 18, address: EVM_NATIVE_ADDRESS },\n lightlink: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n solana: { symbol: 'SOL', name: 'Solana', decimals: 9, address: SOLANA_NATIVE_ADDRESS },\n};\n\n// Fallback token list for common chains when SDK config is unavailable\nconst FALLBACK_TOKENS: Record<string, { address: string; symbol: string; name: string; decimals: number }[]> = {\n '0x2105.base': [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'ethereum': [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', symbol: 'DAI', name: 'Dai Stablecoin', decimals: 18 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n '0xa4b1.arbitrum': [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'sonic': [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x6047828dc181963ba44974801FF68e538dA5eaF9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'S', name: 'Sonic', decimals: 18 },\n ],\n 'solana': [\n { address: SOLANA_NATIVE_ADDRESS, symbol: 'SOL', name: 'Solana', decimals: 9 },\n { address: '3rSPCLNEF7Quw4wX8S1NyKivELoyij8eYA2gJwBgt4V5', symbol: 'bnUSD', name: 'bnUSD', decimals: 9 },\n { address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n ],\n};\n\n// Chain type detection\nconst SOLANA_CHAINS = new Set(['solana']);\n\nfunction isSolanaChain(chainId: string): boolean {\n return SOLANA_CHAINS.has(chainId.toLowerCase());\n}\n\n/**\n * Check if an address is a native token for the given chain\n */\nfunction isNativeToken(address: string, chainId?: string): boolean {\n const addrLower = address.toLowerCase();\n if (chainId && isSolanaChain(chainId)) {\n return addrLower === SOLANA_NATIVE_ADDRESS.toLowerCase();\n }\n return addrLower === EVM_NATIVE_ADDRESS;\n}\n\n/**\n * Get native token info for a chain\n */\nfunction getNativeTokenInfo(chainId: string): { address: string; symbol: string; name: string; decimals: number } | null {\n const native = NATIVE_TOKENS[chainId];\n if (!native) return null;\n return { ...native };\n}\n\n/**\n * Check if a string is a valid EVM address (0x format)\n */\nfunction isEvmAddress(value: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/i.test(value);\n}\n\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\nfunction isSolanaAddress(value: string): boolean {\n // Base58 charset: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\n // Excludes: 0, O, I, l\n return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);\n}\n\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\nfunction isValidTokenAddress(value: string, chainId?: string): boolean {\n if (chainId && isSolanaChain(chainId)) {\n return isSolanaAddress(value);\n }\n // For EVM chains or unknown chains, check both formats\n return isEvmAddress(value) || isSolanaAddress(value);\n}\n\n/**\n * Populate the token cache for a chain from SDK config service\n * This is the canonical way to get tokens - used by both resolveToken and getTokenInfo\n */\nfunction populateTokenCache(chainId: string): Token[] {\n // Convert to SDK chain ID format (e.g., \"base\" -> \"0x2105.base\")\n const sdkChainId = toSodaxChainId(chainId);\n let tokens = tokenCache.get(chainId);\n if (tokens) return tokens;\n \n try {\n const client = getSodaxClient();\n const configService = (client as any).config;\n \n if (configService?.getSupportedSwapTokensByChainId) {\n // Preferred method - returns readonly Token[]\n tokens = [...configService.getSupportedSwapTokensByChainId(sdkChainId)] as Token[];\n } else if (configService?.getSwapTokensByChainId) {\n tokens = configService.getSwapTokensByChainId(sdkChainId) as Token[];\n } else if (configService?.getSwapTokens) {\n const allTokens = configService.getSwapTokens();\n tokens = allTokens[sdkChainId] || [];\n } else {\n console.warn(`[tokenResolver] configService not available for chain ${chainId}`);\n tokens = [];\n }\n \n // Log what we got from SDK\n if (tokens && tokens.length > 0) {\n console.log(`[tokenResolver] Loaded ${tokens.length} tokens from SDK for ${chainId}`);\n }\n } catch (err) {\n console.error(`[tokenResolver] Failed to fetch tokens for chain ${chainId}:`, err);\n tokens = [];\n }\n \n // Use fallback tokens if SDK returned empty list\n if ((!tokens || tokens.length === 0) && FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId]) {\n console.log(`[tokenResolver] Using fallback token list for ${chainId}`);\n tokens = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId] as unknown as Token[];\n }\n \n tokenCache.set(chainId, tokens || []);\n return tokens || [];\n}\n\n/**\n * Resolve a token symbol or address to a normalized address\n * \n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport async function resolveToken(\n chainId: string,\n tokenInput: string\n): Promise<string> {\n // If already a valid address, normalize and return\n if (isValidTokenAddress(tokenInput, chainId)) {\n // EVM addresses are lowercased, Solana addresses preserve case\n return isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n }\n\n // Get tokens from cache or SDK\n const tokens = populateTokenCache(chainId);\n\n // Find by symbol (case-insensitive)\n const symbolUpper = tokenInput.toUpperCase();\n const token = tokens.find(t => t.symbol.toUpperCase() === symbolUpper);\n \n if (!token) {\n // Build helpful error message with available tokens\n const available = tokens.length > 0 \n ? tokens.map(t => t.symbol).join(', ')\n : 'No tokens loaded';\n throw new Error(\n `Unknown token \"${tokenInput}\" on chain ${chainId}. ` +\n `Available: ${available}. ` +\n `Alternatively, provide the token address directly.`\n );\n }\n\n return isEvmAddress(token.address) ? token.address.toLowerCase() : token.address;\n}\n\n/**\n * Resolve multiple tokens at once (for efficiency)\n * \n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport async function resolveTokens(\n chainId: string,\n tokenInputs: string[]\n): Promise<string[]> {\n return Promise.all(tokenInputs.map(t => resolveToken(chainId, t)));\n}\n\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport async function getTokenInfo(\n chainId: string,\n tokenInput: string\n): Promise<Token | null> {\n const sdkChainId = toSodaxChainId(chainId);\n // Handle native tokens first\n if (isValidTokenAddress(tokenInput, chainId) && isNativeToken(tokenInput, chainId)) {\n const nativeInfo = getNativeTokenInfo(chainId);\n if (nativeInfo) {\n return nativeInfo as unknown as Token;\n }\n }\n\n // Get tokens from cache or SDK (same path as resolveToken)\n const tokens = populateTokenCache(chainId);\n\n // Find by address or symbol\n if (isValidTokenAddress(tokenInput, chainId)) {\n // Normalize address for comparison\n const addrNorm = isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n const found = tokens.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (found) {\n return found;\n }\n // Check fallback tokens even if SDK tokens were loaded\n const fallback = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n if (fallback) {\n const fallbackToken = fallback.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (fallbackToken) {\n console.log(`[tokenResolver] Found ${fallbackToken.symbol} in fallback for ${chainId}`);\n return fallbackToken as unknown as Token;\n }\n }\n return null;\n } else {\n const symbolUpper = tokenInput.toUpperCase();\n return tokens.find(t => t.symbol.toUpperCase() === symbolUpper) || null;\n }\n}\n\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport function clearTokenCache(): void {\n tokenCache.clear();\n}\n\n/**\n * Get all cached tokens for a chain\n */\nexport function getCachedTokens(chainId: string): Token[] | undefined {\n return tokenCache.get(chainId);\n}\n\n// Export address validation utilities for use by other modules\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n",
906 "inputSchema": {},
907 "outputSchema": null,
908 "icons": null,
909 "annotations": null,
910 "meta": null,
911 "execution": null
912 },
913 {
914 "name": "priceService.ts",
915 "title": null,
916 "description": "Script: priceService.ts. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\n\nimport { getSodaxClient } from '../sodax/client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TokenPrice {\n symbol: string;\n priceUsd: number;\n underlyingAsset: string;\n}\n\nexport interface PriceMap {\n /** Map of symbol (lowercase) to USD price */\n bySymbol: Map<string, number>;\n /** Map of address (lowercase) to USD price */\n byAddress: Map<string, number>;\n /** Last update timestamp */\n timestamp: number;\n}\n\n// ============================================================================\n// Cache\n// ============================================================================\n\nlet cachedPrices: PriceMap | null = null;\nconst CACHE_TTL_MS = 60_000; // 1 minute cache\n\n// ============================================================================\n// Price Fetching\n// ============================================================================\n\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport async function fetchTokenPrices(): Promise<PriceMap> {\n // Return cached if fresh\n if (cachedPrices && Date.now() - cachedPrices.timestamp < CACHE_TTL_MS) {\n return cachedPrices;\n }\n\n console.log('[priceService] Fetching token prices from SODAX reserves');\n\n const sodax = await getSodaxClient();\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n\n const bySymbol = new Map<string, number>();\n const byAddress = new Map<string, number>();\n\n // Market reference currency decimals (typically 8)\n const PRICE_DECIMALS = 8;\n\n for (const reserve of reserves.reservesData) {\n // priceInMarketReferenceCurrency is a string representing the raw value\n const priceRaw = BigInt(reserve.priceInMarketReferenceCurrency);\n const priceUsd = Number(priceRaw) / Math.pow(10, PRICE_DECIMALS);\n\n // Use symbol for matching (e.g., \"sodaUSDC\" -> \"USDC\")\n const symbol = reserve.symbol.toLowerCase();\n const normalizedSymbol = normalizeSymbol(reserve.symbol);\n const address = reserve.underlyingAsset.toLowerCase();\n\n bySymbol.set(symbol, priceUsd);\n bySymbol.set(normalizedSymbol, priceUsd);\n byAddress.set(address, priceUsd);\n\n console.log(`[priceService] ${reserve.symbol}: $${priceUsd.toFixed(4)}`);\n }\n\n cachedPrices = {\n bySymbol,\n byAddress,\n timestamp: Date.now(),\n };\n\n console.log(`[priceService] Cached ${bySymbol.size} token prices`);\n\n return cachedPrices;\n}\n\n/**\n * Normalize SODAX symbol to standard symbol\n * e.g., \"sodaUSDC\" -> \"usdc\", \"sodaETH\" -> \"eth\"\n */\nfunction normalizeSymbol(symbol: string): string {\n const lower = symbol.toLowerCase();\n if (lower.startsWith('soda')) {\n return lower.slice(4); // Remove 'soda' prefix\n }\n return lower;\n}\n\n/**\n * Get USD price for a token by symbol\n */\nexport async function getTokenPriceBySymbol(symbol: string): Promise<number | null> {\n const prices = await fetchTokenPrices();\n const normalizedSymbol = symbol.toLowerCase();\n\n // Try exact match first\n if (prices.bySymbol.has(normalizedSymbol)) {\n return prices.bySymbol.get(normalizedSymbol)!;\n }\n\n // Try with 'soda' prefix\n const sodaSymbol = 'soda' + normalizedSymbol;\n if (prices.bySymbol.has(sodaSymbol)) {\n return prices.bySymbol.get(sodaSymbol)!;\n }\n\n return null;\n}\n\n/**\n * Get USD price for a token by address\n */\nexport async function getTokenPriceByAddress(address: string): Promise<number | null> {\n const prices = await fetchTokenPrices();\n return prices.byAddress.get(address.toLowerCase()) ?? null;\n}\n\n/**\n * Calculate USD value for a token amount\n */\nexport async function calculateUsdValue(\n symbol: string,\n amount: string | number\n): Promise<number | null> {\n const price = await getTokenPriceBySymbol(symbol);\n if (price === null) return null;\n\n const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;\n return amountNum * price;\n}\n\n/**\n * Clear the price cache (useful for testing)\n */\nexport function clearPriceCache(): void {\n cachedPrices = null;\n}\n",
917 "inputSchema": {},
918 "outputSchema": null,
919 "icons": null,
920 "annotations": null,
921 "meta": null,
922 "execution": null
923 },
924 {
925 "name": "sodaxApi.ts",
926 "title": null,
927 "description": "Script: sodaxApi.ts. Code:\n/**\n * SODAX API Client\n * \n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\n\nimport { ErrorCode, AmpedDefiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://canary-api.sodax.com';\nconst API_VERSION = 'v1';\n\nexport interface SodaxApiConfig {\n baseUrl?: string;\n apiKey?: string;\n timeoutMs?: number;\n}\n\nexport interface PaginationParams {\n offset?: number;\n limit?: number;\n}\n\nexport interface PaginatedResponse<T> {\n items: T[];\n total: number;\n offset: number;\n limit: number;\n}\n\nexport interface IntentState {\n exists: boolean;\n remainingInput: string;\n receivedOutput: string;\n pendingPayment: boolean;\n}\n\nexport interface IntentEvent {\n eventType: string;\n txHash: string;\n logIndex: number;\n blockNumber: number;\n intentState: IntentState;\n}\n\nexport interface IntentDetails {\n intentId: string;\n creator: string;\n inputToken: string;\n outputToken: string;\n inputAmount: string;\n minOutputAmount: string;\n deadline: string;\n allowPartialFill: boolean;\n srcChain: number;\n dstChain: number;\n srcAddress: string;\n dstAddress: string;\n solver: string;\n data: string;\n}\n\nexport interface UserIntent {\n intentHash: string;\n txHash: string;\n logIndex: number;\n chainId: number;\n blockNumber: number;\n open: boolean;\n intent: IntentDetails;\n events: IntentEvent[];\n createdAt: string;\n}\n\nexport interface UserIntentFilters {\n open?: boolean;\n srcChain?: number;\n dstChain?: number;\n inputToken?: string;\n outputToken?: string;\n}\n\nexport class SodaxApiClient {\n private baseUrl: string;\n private apiKey?: string;\n private timeoutMs: number;\n\n constructor(config: SodaxApiConfig = {}) {\n this.baseUrl = config.baseUrl || process.env.SODAX_API_URL || DEFAULT_BASE_URL;\n this.apiKey = config.apiKey || process.env.SODAX_API_KEY;\n this.timeoutMs = config.timeoutMs || 30000;\n }\n\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n async getIntentByHash(intentHash: string): Promise<UserIntent | null> {\n const normalizedHash = intentHash.startsWith('0x') ? intentHash : `0x${intentHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/${normalizedHash}`;\n\n console.log('[sodaxApi] Fetching intent by hash:', { intentHash: normalizedHash });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as UserIntent;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch intent by hash: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n async getIntentByTxHash(txHash: string): Promise<UserIntent | null> {\n const normalizedHash = txHash.startsWith('0x') ? txHash : `0x${txHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/tx/${normalizedHash}`;\n\n console.log('[sodaxApi] Fetching intent by txHash:', { txHash: normalizedHash });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as UserIntent;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch intent by txHash: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getUserIntents(\n userAddress: string,\n pagination: PaginationParams = {},\n filters?: UserIntentFilters\n ): Promise<PaginatedResponse<UserIntent>> {\n if (!this.isValidAddress(userAddress)) {\n throw new AmpedDefiError(\n ErrorCode.WALLET_INVALID_ADDRESS,\n `Invalid user address: ${userAddress}`\n );\n }\n\n const normalizedAddress = userAddress.toLowerCase();\n const queryParams = new URLSearchParams();\n \n if (pagination.offset !== undefined) {\n queryParams.set('offset', pagination.offset.toString());\n }\n if (pagination.limit !== undefined) {\n queryParams.set('limit', pagination.limit.toString());\n }\n\n if (filters) {\n if (filters.open !== undefined) queryParams.set('open', filters.open.toString());\n if (filters.srcChain !== undefined) queryParams.set('srcChain', filters.srcChain.toString());\n if (filters.dstChain !== undefined) queryParams.set('dstChain', filters.dstChain.toString());\n if (filters.inputToken) queryParams.set('inputToken', filters.inputToken.toLowerCase());\n if (filters.outputToken) queryParams.set('outputToken', filters.outputToken.toLowerCase());\n }\n\n const queryString = queryParams.toString();\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/user/${normalizedAddress}${queryString ? `?${queryString}` : ''}`;\n\n console.log('[sodaxApi] Fetching user intents:', { userAddress: normalizedAddress });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as PaginatedResponse<UserIntent>;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch user intents: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getOpenIntents(\n userAddress: string,\n pagination: PaginationParams = {}\n ): Promise<PaginatedResponse<UserIntent>> {\n return this.getUserIntents(userAddress, pagination, { open: true });\n }\n\n async getIntentHistory(\n userAddress: string,\n pagination: PaginationParams = {}\n ): Promise<PaginatedResponse<UserIntent>> {\n return this.getUserIntents(userAddress, pagination, { open: false });\n }\n\n private async fetchWithTimeout(url: string): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const headers: Record<string, string> = { 'Accept': 'application/json' };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n return await fetch(url, { signal: controller.signal, headers });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n }\n}\n\nlet apiClient: SodaxApiClient | null = null;\n\nexport function getSodaxApiClient(config?: SodaxApiConfig): SodaxApiClient {\n if (!apiClient) {\n apiClient = new SodaxApiClient(config);\n }\n return apiClient;\n}\n\nexport function resetSodaxApiClient(): void {\n apiClient = null;\n}\n",
928 "inputSchema": {},
929 "outputSchema": null,
930 "icons": null,
931 "annotations": null,
932 "meta": null,
933 "execution": null
934 },
935 {
936 "name": "errors.ts",
937 "title": null,
938 "description": "Script: errors.ts. Code:\n/**\n * Error Handling Utilities\n * \n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport enum ErrorCode {\n // Policy errors\n POLICY_SLIPPAGE_EXCEEDED = 'POLICY_SLIPPAGE_EXCEEDED',\n POLICY_SPEND_LIMIT_EXCEEDED = 'POLICY_SPEND_LIMIT_EXCEEDED',\n POLICY_CHAIN_NOT_ALLOWED = 'POLICY_CHAIN_NOT_ALLOWED',\n POLICY_TOKEN_NOT_ALLOWED = 'POLICY_TOKEN_NOT_ALLOWED',\n POLICY_RECIPIENT_BLOCKED = 'POLICY_RECIPIENT_BLOCKED',\n\n // Wallet errors\n WALLET_NOT_FOUND = 'WALLET_NOT_FOUND',\n WALLET_INVALID_ADDRESS = 'WALLET_INVALID_ADDRESS',\n WALLET_MISSING_PRIVATE_KEY = 'WALLET_MISSING_PRIVATE_KEY',\n WALLET_RESOLUTION_FAILED = 'WALLET_RESOLUTION_FAILED',\n\n // Chain/Provider errors\n CHAIN_NOT_SUPPORTED = 'CHAIN_NOT_SUPPORTED',\n RPC_URL_NOT_CONFIGURED = 'RPC_URL_NOT_CONFIGURED',\n PROVIDER_CREATION_FAILED = 'PROVIDER_CREATION_FAILED',\n SONIC_PROVIDER_REQUIRED = 'SONIC_PROVIDER_REQUIRED',\n\n // Token errors\n INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',\n INSUFFICIENT_ALLOWANCE = 'INSUFFICIENT_ALLOWANCE',\n TOKEN_NOT_SUPPORTED = 'TOKEN_NOT_SUPPORTED',\n TOKEN_DECIMALS_NOT_FOUND = 'TOKEN_DECIMALS_NOT_FOUND',\n\n // Operation errors\n QUOTE_EXPIRED = 'QUOTE_EXPIRED',\n QUOTE_NOT_FOUND = 'QUOTE_NOT_FOUND',\n BRIDGE_NOT_AVAILABLE = 'BRIDGE_NOT_AVAILABLE',\n SWAP_EXECUTION_FAILED = 'SWAP_EXECUTION_FAILED',\n BRIDGE_EXECUTION_FAILED = 'BRIDGE_EXECUTION_FAILED',\n MM_HEALTH_FACTOR_LOW = 'MM_HEALTH_FACTOR_LOW',\n MM_CROSS_CHAIN_NOT_SUPPORTED = 'MM_CROSS_CHAIN_NOT_SUPPORTED',\n MM_INSUFFICIENT_COLLATERAL = 'MM_INSUFFICIENT_COLLATERAL',\n MM_POSITION_NOT_FOUND = 'MM_POSITION_NOT_FOUND',\n\n // Transaction errors\n TRANSACTION_FAILED = 'TRANSACTION_FAILED',\n TRANSACTION_TIMEOUT = 'TRANSACTION_TIMEOUT',\n TRANSACTION_REJECTED = 'TRANSACTION_REJECTED',\n TRANSACTION_SIMULATION_FAILED = 'TRANSACTION_SIMULATION_FAILED',\n\n // SDK/Configuration errors\n SDK_NOT_INITIALIZED = 'SDK_NOT_INITIALIZED',\n SDK_INITIALIZATION_FAILED = 'SDK_INITIALIZATION_FAILED',\n INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',\n CONFIG_PARSE_ERROR = 'CONFIG_PARSE_ERROR',\n\n // Unknown/Generic\n UNKNOWN_ERROR = 'UNKNOWN_ERROR',\n OPERATION_CANCELLED = 'OPERATION_CANCELLED',\n}\n\n/**\n * Error severity levels\n */\nexport enum ErrorSeverity {\n INFO = 'info',\n WARNING = 'warning',\n ERROR = 'error',\n CRITICAL = 'critical',\n}\n\n/**\n * Structured error information\n */\nexport interface AmpedError {\n code: ErrorCode;\n message: string;\n severity: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n}\n\n/**\n * Error context for logging\n */\nexport interface ErrorContext {\n operation?: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokens?: string[];\n amount?: string;\n requestId?: string;\n txHash?: string;\n [key: string]: unknown;\n}\n\n/**\n * Amped DeFi Error class\n */\nexport class AmpedDefiError extends Error {\n public readonly code: ErrorCode;\n public readonly severity: ErrorSeverity;\n public readonly remediation?: string;\n public readonly details?: Record<string, unknown>;\n public readonly context?: ErrorContext;\n\n constructor(\n code: ErrorCode,\n message: string,\n options?: {\n severity?: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n context?: ErrorContext;\n cause?: Error;\n }\n ) {\n super(message, { cause: options?.cause });\n this.name = 'AmpedDefiError';\n this.code = code;\n this.severity = options?.severity || ErrorSeverity.ERROR;\n this.remediation = options?.remediation;\n this.details = options?.details;\n this.context = options?.context;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, AmpedDefiError);\n }\n }\n\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): AmpedError {\n return {\n code: this.code,\n message: this.message,\n severity: this.severity,\n remediation: this.remediation,\n details: this.details,\n };\n }\n\n /**\n * Get user-friendly error message\n */\n toUserMessage(): string {\n let msg = `[${this.code}] ${this.message}`;\n if (this.remediation) {\n msg += `\\n\\nSuggestion: ${this.remediation}`;\n }\n return msg;\n }\n}\n\n// ============================================================================\n// Error Factory Functions\n// ============================================================================\n\n/**\n * Create a policy error\n */\nexport function createPolicyError(\n code: ErrorCode,\n message: string,\n details?: { current?: unknown; limit?: unknown; [key: string]: unknown },\n context?: ErrorContext\n): AmpedDefiError {\n const remediation = getPolicyRemediation(code, details);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.WARNING,\n remediation,\n details,\n context,\n });\n}\n\n/**\n * Create a wallet error\n */\nexport function createWalletError(\n code: ErrorCode,\n walletId: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n const message = getWalletErrorMessage(code, walletId);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getWalletRemediation(code),\n context: { ...context, walletId },\n cause,\n });\n}\n\n/**\n * Create a transaction error\n */\nexport function createTransactionError(\n code: ErrorCode,\n message: string,\n txHash?: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getTransactionRemediation(code),\n details: txHash ? { txHash } : undefined,\n context: txHash ? { ...context, txHash } : context,\n cause,\n });\n}\n\n/**\n * Create an SDK error\n */\nexport function createSDKError(\n code: ErrorCode,\n message: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.CRITICAL,\n remediation: 'Please check your configuration and try again. If the issue persists, contact support.',\n context,\n cause,\n });\n}\n\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport function wrapError(\n error: unknown,\n fallbackCode: ErrorCode = ErrorCode.UNKNOWN_ERROR,\n context?: ErrorContext\n): AmpedDefiError {\n if (error instanceof AmpedDefiError) {\n return error;\n }\n\n if (error instanceof Error) {\n // Try to infer error code from message\n const code = inferErrorCode(error.message) || fallbackCode;\n return new AmpedDefiError(code, error.message, {\n severity: ErrorSeverity.ERROR,\n context,\n cause: error,\n });\n }\n\n return new AmpedDefiError(fallbackCode, String(error), {\n severity: ErrorSeverity.ERROR,\n context,\n });\n}\n\n// ============================================================================\n// Remediation Helpers\n// ============================================================================\n\nfunction getPolicyRemediation(code: ErrorCode, details?: Record<string, unknown>): string {\n switch (code) {\n case ErrorCode.POLICY_SLIPPAGE_EXCEEDED:\n return `Slippage ${details?.current} bps exceeds limit of ${details?.limit} bps. Increase maxSlippageBps in your policy configuration or wait for better market conditions.`;\n case ErrorCode.POLICY_SPEND_LIMIT_EXCEEDED:\n return `Reduce the operation amount or request a policy limit increase. Current limit: ${details?.limit}`;\n case ErrorCode.POLICY_CHAIN_NOT_ALLOWED:\n return `Add the chain to your allowedChains policy configuration or use a different chain.`;\n case ErrorCode.POLICY_TOKEN_NOT_ALLOWED:\n return `Add the token to your allowedTokensByChain policy configuration or use a different token.`;\n case ErrorCode.POLICY_RECIPIENT_BLOCKED:\n return `Use a different recipient address. This address has been blocked by policy.`;\n default:\n return 'Review your policy configuration or contact your administrator.';\n }\n}\n\nfunction getWalletRemediation(code: ErrorCode): string {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return 'Check your AMPED_OC_WALLETS_JSON configuration and ensure the walletId is correct.';\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return 'Verify the wallet address format (should be 0x-prefixed Ethereum address).';\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return 'Add the private key to your wallet configuration for execute mode, or switch to prepare mode.';\n default:\n return 'Check your wallet configuration and try again.';\n }\n}\n\nfunction getTransactionRemediation(code: ErrorCode): string {\n switch (code) {\n case ErrorCode.TRANSACTION_FAILED:\n return 'Check the transaction on a block explorer for revert reasons. You may need to adjust parameters or try again later.';\n case ErrorCode.TRANSACTION_TIMEOUT:\n return 'The operation timed out. You can check the status later using the transaction hash.';\n case ErrorCode.TRANSACTION_REJECTED:\n return 'The transaction was rejected. This may be due to network congestion or insufficient gas.';\n case ErrorCode.TRANSACTION_SIMULATION_FAILED:\n return 'The transaction would fail if executed. Check your balances, allowances, and parameters.';\n default:\n return 'Try again or contact support if the issue persists.';\n }\n}\n\nfunction getWalletErrorMessage(code: ErrorCode, walletId: string): string {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return `Wallet not found: ${walletId}`;\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return `Wallet ${walletId} has an invalid address`;\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return `Wallet ${walletId} is missing private key (required in execute mode)`;\n default:\n return `Wallet error for ${walletId}`;\n }\n}\n\nfunction inferErrorCode(message: string): ErrorCode | null {\n const lowerMsg = message.toLowerCase();\n \n if (lowerMsg.includes('insufficient balance')) return ErrorCode.INSUFFICIENT_BALANCE;\n if (lowerMsg.includes('allowance')) return ErrorCode.INSUFFICIENT_ALLOWANCE;\n if (lowerMsg.includes('slippage')) return ErrorCode.POLICY_SLIPPAGE_EXCEEDED;\n if (lowerMsg.includes('health factor')) return ErrorCode.MM_HEALTH_FACTOR_LOW;\n if (lowerMsg.includes('timeout')) return ErrorCode.TRANSACTION_TIMEOUT;\n if (lowerMsg.includes('rejected')) return ErrorCode.TRANSACTION_REJECTED;\n if (lowerMsg.includes('simulation')) return ErrorCode.TRANSACTION_SIMULATION_FAILED;\n if (lowerMsg.includes('not initialized')) return ErrorCode.SDK_NOT_INITIALIZED;\n if (lowerMsg.includes('bridge') && lowerMsg.includes('not')) return ErrorCode.BRIDGE_NOT_AVAILABLE;\n if (lowerMsg.includes('quote') && lowerMsg.includes('expir')) return ErrorCode.QUOTE_EXPIRED;\n \n return null;\n}\n\n// ============================================================================\n// Logging and Observability\n// ============================================================================\n\n/**\n * Log an error with structured context\n */\nexport function logError(error: AmpedDefiError | Error, context?: ErrorContext): void {\n const structuredLog = {\n timestamp: new Date().toISOString(),\n component: 'amped-defi',\n level: error instanceof AmpedDefiError ? error.severity : 'error',\n code: error instanceof AmpedDefiError ? error.code : ErrorCode.UNKNOWN_ERROR,\n message: error.message,\n context,\n stack: error.stack,\n cause: error.cause,\n };\n\n // Log as JSON for structured logging systems\n console.error(JSON.stringify(structuredLog, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n\n/**\n * Check if an error is retryable\n */\nexport function isRetryableError(error: AmpedDefiError | Error): boolean {\n if (error instanceof AmpedDefiError) {\n const retryableCodes = [\n ErrorCode.TRANSACTION_TIMEOUT,\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n ErrorCode.SDK_NOT_INITIALIZED,\n ErrorCode.UNKNOWN_ERROR,\n ];\n return retryableCodes.includes(error.code);\n }\n \n // For generic errors, check message patterns\n const lowerMsg = error.message.toLowerCase();\n return lowerMsg.includes('timeout') || \n lowerMsg.includes('network') || \n lowerMsg.includes('connection') ||\n lowerMsg.includes('rate limit');\n}\n\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport function getRetryDelay(attempt: number, baseDelay = 1000): number {\n return Math.min(baseDelay * Math.pow(2, attempt), 30000); // Cap at 30 seconds\n}\n",
939 "inputSchema": {},
940 "outputSchema": null,
941 "icons": null,
942 "annotations": null,
943 "meta": null,
944 "execution": null
945 },
946 {
947 "name": "errorUtils.ts",
948 "title": null,
949 "description": "Script: errorUtils.ts. Code:\n/**\n * Serialize SDK error objects for readable error messages\n */\nfunction bigintReplacer(key: string, value: any): any {\n return typeof value === 'bigint' ? value.toString() : value;\n}\n\nexport function serializeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error, bigintReplacer, 2);\n } catch {\n return String(error);\n }\n}\n",
950 "inputSchema": {},
951 "outputSchema": null,
952 "icons": null,
953 "annotations": null,
954 "meta": null,
955 "execution": null
956 },
957 {
958 "name": "positionAggregator.ts",
959 "title": null,
960 "description": "Script: positionAggregator.ts. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n * \n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\n\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { normalizeChainId } from '../wallet/types';\n\n/**\n * Position data for a single token on a single chain\n */\nexport interface TokenPosition {\n chainId: string;\n token: {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n };\n supply: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n isCollateral: boolean;\n };\n borrow: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n };\n loanToValue: number;\n liquidationThreshold: number;\n}\n\n/**\n * Aggregated position summary across all chains\n */\nexport interface AggregatedPositionSummary {\n totalSupplyUsd: number;\n totalBorrowUsd: number;\n netWorthUsd: number;\n availableBorrowUsd: number;\n healthFactor: number | null;\n liquidationRisk: 'none' | 'low' | 'medium' | 'high';\n weightedSupplyApy: number;\n weightedBorrowApy: number;\n netApy: number;\n}\n\n/**\n * Chain-specific position summary\n */\nexport interface ChainPositionSummary {\n chainId: string;\n supplyUsd: number;\n borrowUsd: number;\n netWorthUsd: number;\n healthFactor: number | null;\n positionCount: number;\n}\n\n/**\n * Complete cross-chain position view\n */\nexport interface CrossChainPositionView {\n walletId: string;\n address: string;\n timestamp: string;\n summary: AggregatedPositionSummary;\n chainSummaries: ChainPositionSummary[];\n positions: TokenPosition[];\n collateralUtilization: {\n totalCollateralUsd: number;\n usedCollateralUsd: number;\n availableCollateralUsd: number;\n utilizationRate: number;\n };\n riskMetrics: {\n maxLtv: number;\n currentLtv: number;\n bufferUntilLiquidation: number;\n safeMaxBorrowUsd: number;\n };\n}\n\n/**\n * Options for position aggregation\n */\nexport interface AggregationOptions {\n /** Specific chains to query (defaults to all supported chains) */\n chainIds?: string[];\n /** Include zero-balance positions */\n includeZeroBalances?: boolean;\n /** Minimum USD value to include (positions below this are filtered out unless includeZeroBalances is true) */\n minUsdValue?: number;\n}\n\n// ============================================================================\n// Position Aggregation Functions\n// ============================================================================\n\n/**\n * Aggregate money market positions across all supported chains\n * \n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport async function aggregateCrossChainPositions(\n walletId: string,\n options: AggregationOptions = {}\n): Promise<CrossChainPositionView> {\n const startTime = Date.now();\n \n // Get wallet\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Get supported chains from SODAX\n const sodax = getSodaxClient();\n const sodaxChains = sodax.config.getSupportedSpokeChains();\n \n // Map SDK chains to string IDs\n const allSodaxChains = sodaxChains.map((c: any) => \n typeof c === 'string' ? c : c.id\n );\n \n // Filter chains by what the wallet supports\n // This is important for Bankr which only supports ethereum/polygon/base\n const walletSupportedChains = wallet.supportedChains;\n const filteredChains = allSodaxChains.filter((chainId: string) => \n wallet.supportsChain(normalizeChainId(chainId))\n );\n \n // Determine which chains to query\n const chainsToQuery = options.chainIds || filteredChains;\n \n console.log('[positionAggregator] Wallet chain filter', {\n walletType: wallet.type,\n walletSupports: walletSupportedChains,\n sodaxChains: allSodaxChains,\n filteredChains: filteredChains,\n normalizedFiltered: filteredChains.map(normalizeChainId),\n });\n \n console.log('[positionAggregator] Querying positions across chains', {\n walletId,\n address: walletAddress,\n chains: chainsToQuery,\n });\n\n // Query positions from all chains in parallel\n const chainResults = await Promise.allSettled(\n chainsToQuery.map(chainId => queryChainPositions(walletId, walletAddress, chainId))\n );\n\n // Collect all positions\n const allPositions: TokenPosition[] = [];\n const chainSummaries: ChainPositionSummary[] = [];\n\n chainResults.forEach((result, index) => {\n const chainId = chainsToQuery[index];\n \n if (result.status === 'fulfilled') {\n const { positions, summary } = result.value;\n \n if (positions.length > 0 || options.includeZeroBalances) {\n allPositions.push(...positions);\n chainSummaries.push(summary);\n }\n } else {\n console.warn(`[positionAggregator] Failed to query chain ${chainId}:`, result.reason);\n }\n });\n\n // Calculate aggregated summary\n const summary = calculateAggregatedSummary(allPositions);\n \n // Calculate collateral utilization\n const collateralUtilization = calculateCollateralUtilization(allPositions, summary);\n \n // Calculate risk metrics\n const riskMetrics = calculateRiskMetrics(allPositions, summary);\n \n const view: CrossChainPositionView = {\n walletId,\n address: walletAddress,\n timestamp: new Date().toISOString(),\n summary,\n chainSummaries: chainSummaries.sort((a, b) => b.netWorthUsd - a.netWorthUsd),\n positions: allPositions.sort((a, b) => \n (parseFloat(b.supply.balanceUsd) + parseFloat(b.borrow.balanceUsd)) -\n (parseFloat(a.supply.balanceUsd) + parseFloat(a.borrow.balanceUsd))\n ),\n collateralUtilization,\n riskMetrics,\n };\n\n console.log('[positionAggregator] Aggregation complete', {\n durationMs: Date.now() - startTime,\n totalPositions: allPositions.length,\n totalSupplyUsd: summary.totalSupplyUsd,\n totalBorrowUsd: summary.totalBorrowUsd,\n healthFactor: summary.healthFactor,\n });\n\n return view;\n}\n\n/**\n * Query positions for a single chain\n * \n * IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n * To get token symbols/names, we must:\n * 1. Fetch getReservesHumanized() for token metadata\n * 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n * 3. Join with formatUserSummary(buildUserSummaryRequest())\n * \n * Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n */\nasync function queryChainPositions(\n walletId: string,\n address: string,\n chainId: string\n): Promise<{ positions: TokenPosition[]; summary: ChainPositionSummary }> {\n try {\n // Use address for spoke provider lookup\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n // This is the key fix - getUserReservesHumanized alone doesn't include token metadata\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n \n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(\n sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)\n );\n \n // Step 3: Fetch user-specific balances\n const userReserves = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n \n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(\n sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReserves)\n );\n\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = (userSummary as any).userReservesData || [];\n\n // Convert to TokenPosition format\n const positions: TokenPosition[] = userReservesData.map((reserve: any) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n \n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n \n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n \n return {\n chainId,\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n logoURI: reserve.reserve?.iconSymbol || undefined,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n balanceRaw: reserve.scaledATokenBalance || '0',\n apy: supplyApy,\n isCollateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n balanceRaw: reserve.scaledVariableDebt || '0',\n apy: borrowApy,\n },\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n\n // Filter out positions with zero balance (unless explicitly requested)\n const activePositions = positions.filter(p => \n parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0\n );\n\n // Calculate chain summary\n const supplyUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const borrowUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'), 0);\n \n // Calculate health factor for this chain\n const healthFactor = calculateChainHealthFactor(activePositions);\n\n const summary: ChainPositionSummary = {\n chainId,\n supplyUsd,\n borrowUsd,\n netWorthUsd: supplyUsd - borrowUsd,\n healthFactor,\n positionCount: activePositions.length,\n };\n\n console.log(`[positionAggregator] Chain ${chainId}: ${activePositions.length} positions, supply=$${supplyUsd.toFixed(2)}, borrow=$${borrowUsd.toFixed(2)}`);\n\n return { positions: activePositions, summary };\n } catch (error) {\n console.error(`[positionAggregator] Error querying ${chainId}:`, error);\n throw error;\n }\n}\n\n// ============================================================================\n// Calculation Helpers\n// ============================================================================\n\n/**\n * Calculate aggregated summary across all positions\n */\nfunction calculateAggregatedSummary(positions: TokenPosition[]): AggregatedPositionSummary {\n let totalSupplyUsd = 0;\n let totalBorrowUsd = 0;\n let weightedSupplyApy = 0;\n let weightedBorrowApy = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n\n totalSupplyUsd += supplyUsd;\n totalBorrowUsd += borrowUsd;\n weightedSupplyApy += supplyUsd * pos.supply.apy;\n weightedBorrowApy += borrowUsd * pos.borrow.apy;\n });\n\n // Calculate weighted average APYs\n const avgSupplyApy = totalSupplyUsd > 0 ? weightedSupplyApy / totalSupplyUsd : 0;\n const avgBorrowApy = totalBorrowUsd > 0 ? weightedBorrowApy / totalBorrowUsd : 0;\n\n // Calculate health factor\n const healthFactor = calculateHealthFactor(positions);\n\n // Determine liquidation risk\n let liquidationRisk: AggregatedPositionSummary['liquidationRisk'] = 'none';\n if (healthFactor !== null) {\n if (healthFactor < 1.1) liquidationRisk = 'high';\n else if (healthFactor < 1.5) liquidationRisk = 'medium';\n else if (healthFactor < 2) liquidationRisk = 'low';\n }\n\n // Calculate available borrow (simplified - would need proper oracle prices)\n // This is a conservative estimate based on average LTV\n const avgLtv = positions.length > 0\n ? positions.reduce((sum, p) => sum + p.loanToValue, 0) / positions.length\n : 0;\n const availableBorrowUsd = totalSupplyUsd * avgLtv - totalBorrowUsd;\n\n return {\n totalSupplyUsd,\n totalBorrowUsd,\n netWorthUsd: totalSupplyUsd - totalBorrowUsd,\n availableBorrowUsd: Math.max(0, availableBorrowUsd),\n healthFactor,\n liquidationRisk,\n weightedSupplyApy: avgSupplyApy,\n weightedBorrowApy: avgBorrowApy,\n netApy: totalSupplyUsd > 0 \n ? (avgSupplyApy * totalSupplyUsd - avgBorrowApy * totalBorrowUsd) / totalSupplyUsd \n : 0,\n };\n}\n\n/**\n * Calculate collateral utilization metrics\n */\nfunction calculateCollateralUtilization(\n positions: TokenPosition[],\n summary: AggregatedPositionSummary\n): CrossChainPositionView['collateralUtilization'] {\n // Only count collateral-enabled supplies\n const totalCollateralUsd = positions\n .filter(p => p.supply.isCollateral)\n .reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n\n const usedCollateralUsd = summary.totalBorrowUsd;\n const availableCollateralUsd = Math.max(0, totalCollateralUsd - usedCollateralUsd);\n const utilizationRate = totalCollateralUsd > 0 ? (usedCollateralUsd / totalCollateralUsd) * 100 : 0;\n\n return {\n totalCollateralUsd,\n usedCollateralUsd,\n availableCollateralUsd,\n utilizationRate,\n };\n}\n\n/**\n * Calculate risk metrics\n */\nfunction calculateRiskMetrics(\n positions: TokenPosition[],\n summary: AggregatedPositionSummary\n): CrossChainPositionView['riskMetrics'] {\n // Calculate max LTV across all positions (weighted by supply)\n let totalSupply = 0;\n let weightedLtvSum = 0;\n let liquidationThresholdSum = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n totalSupply += supplyUsd;\n weightedLtvSum += supplyUsd * pos.loanToValue;\n liquidationThresholdSum += supplyUsd * pos.liquidationThreshold;\n });\n\n const maxLtv = totalSupply > 0 ? weightedLtvSum / totalSupply : 0;\n const avgLiquidationThreshold = totalSupply > 0 ? liquidationThresholdSum / totalSupply : 0;\n\n // Current LTV\n const currentLtv = summary.totalSupplyUsd > 0 \n ? summary.totalBorrowUsd / summary.totalSupplyUsd \n : 0;\n\n // Buffer until liquidation (percentage points)\n const bufferUntilLiquidation = Math.max(0, avgLiquidationThreshold - currentLtv) * 100;\n\n // Safe max borrow (at 80% of liquidation threshold for safety)\n const safeMaxBorrowUsd = summary.totalSupplyUsd * avgLiquidationThreshold * 0.8;\n\n return {\n maxLtv,\n currentLtv,\n bufferUntilLiquidation,\n safeMaxBorrowUsd,\n };\n}\n\n/**\n * Calculate health factor for a set of positions\n * Health Factor = (Total Collateral in ETH * Liquidation Threshold) / Total Borrow in ETH\n */\nfunction calculateHealthFactor(positions: TokenPosition[]): number | null {\n let totalCollateralEth = 0;\n let totalBorrowEth = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n\n // Only count collateral-enabled supplies\n if (pos.supply.isCollateral) {\n totalCollateralEth += supplyUsd * pos.liquidationThreshold;\n }\n totalBorrowEth += borrowUsd;\n });\n\n if (totalBorrowEth === 0) {\n return totalCollateralEth > 0 ? Infinity : null;\n }\n\n return totalCollateralEth / totalBorrowEth;\n}\n\n/**\n * Calculate health factor for a single chain\n */\nfunction calculateChainHealthFactor(positions: TokenPosition[]): number | null {\n return calculateHealthFactor(positions);\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Format health factor for display\n */\nexport function formatHealthFactor(hf: number | null): string {\n if (hf === null) return 'N/A';\n if (hf === Infinity) return '\u221e';\n return hf.toFixed(2);\n}\n\n/**\n * Get health factor color/styling indicator\n */\nexport function getHealthFactorStatus(hf: number | null): {\n status: 'healthy' | 'caution' | 'danger' | 'critical';\n color: 'green' | 'yellow' | 'orange' | 'red';\n} {\n if (hf === null) return { status: 'healthy', color: 'green' };\n if (hf === Infinity) return { status: 'healthy', color: 'green' };\n if (hf < 1.1) return { status: 'critical', color: 'red' };\n if (hf < 1.5) return { status: 'danger', color: 'orange' };\n if (hf < 2) return { status: 'caution', color: 'yellow' };\n return { status: 'healthy', color: 'green' };\n}\n\n/**\n * Get recommendation based on position health\n */\nexport function getPositionRecommendation(view: CrossChainPositionView): string[] {\n const recommendations: string[] = [];\n const { summary } = view;\n\n // Health factor recommendations\n if (summary.healthFactor !== null && summary.healthFactor < 1.5) {\n recommendations.push('\u26a0\ufe0f Health factor is low. Consider repaying debt or adding collateral.');\n }\n\n // Borrowing capacity recommendations\n if (summary.availableBorrowUsd > 1000 && summary.healthFactor !== null && summary.healthFactor > 2) {\n recommendations.push(`\ud83d\udca1 You have $${summary.availableBorrowUsd.toFixed(2)} in available borrowing power.`);\n }\n\n // Collateral utilization\n if (view.collateralUtilization.utilizationRate > 80) {\n recommendations.push('\u26a0\ufe0f High collateral utilization. Avoid borrowing more to maintain safety margin.');\n }\n\n // Net APY optimization\n if (summary.netApy < 0) {\n recommendations.push('\ud83d\udcc9 Your borrowing costs exceed supply earnings. Consider reducing debt or finding higher APY supply opportunities.');\n }\n\n // Cross-chain opportunities\n const highApyChains = view.chainSummaries\n .filter(cs => cs.supplyUsd > 100)\n .sort((a, b) => (b.healthFactor || Infinity) - (a.healthFactor || Infinity));\n \n if (highApyChains.length > 1) {\n recommendations.push(`\ud83c\udf10 You have positions across ${highApyChains.length} chains. Monitor each chain's health factor independently.`);\n }\n\n return recommendations;\n}\n",
961 "inputSchema": {},
962 "outputSchema": null,
963 "icons": null,
964 "annotations": null,
965 "meta": null,
966 "execution": null
967 },
968 {
969 "name": "index.ts",
970 "title": null,
971 "description": "Script: index.ts. Code:\n/**\n * Wallet Module\n * \n * Multi-source wallet management with nicknames\n */\n\n// Types\nexport * from './types';\n\n// Backends\nexport * from './backends';\n\n// Manager\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n",
972 "inputSchema": {},
973 "outputSchema": null,
974 "icons": null,
975 "annotations": null,
976 "meta": null,
977 "execution": null
978 },
979 {
980 "name": "walletRegistry.ts",
981 "title": null,
982 "description": "Script: walletRegistry.ts. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n * \n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\n\nimport { WalletConfig } from '../types';\nimport { EvmWalletSkillAdapter, getWalletAdapter } from './skillWalletAdapter';\n\n/**\n * Wallet registry entry\n */\ninterface WalletEntry extends WalletConfig {\n mode: 'execute' | 'prepare';\n}\n\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport class WalletRegistry {\n private wallets: Map<string, WalletEntry>;\n private skillAdapter: EvmWalletSkillAdapter;\n\n constructor() {\n this.skillAdapter = getWalletAdapter();\n this.wallets = this.loadWallets();\n \n // Log skill adapter status\n if (this.skillAdapter.isUsingSkillWallets()) {\n console.log('[walletRegistry] evm-wallet-skill integration active');\n }\n }\n\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n private loadWallets(): Map<string, WalletEntry> {\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n const mode = (process.env.AMPED_OC_MODE as 'execute' | 'prepare') || 'execute';\n\n if (!walletsJson) {\n console.warn('[walletRegistry] AMPED_OC_WALLETS_JSON not set');\n return new Map();\n }\n\n try {\n const walletConfigs = JSON.parse(walletsJson) as Record<string, WalletConfig>;\n const wallets = new Map<string, WalletEntry>();\n\n for (const [walletId, config] of Object.entries(walletConfigs)) {\n wallets.set(walletId, {\n ...config,\n mode,\n });\n }\n\n console.log(`[walletRegistry] Loaded ${wallets.size} wallet(s) in ${mode} mode`);\n return wallets;\n } catch (error) {\n console.error('[walletRegistry] Failed to parse AMPED_OC_WALLETS_JSON', error);\n return new Map();\n }\n }\n\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId: string): WalletEntry | null {\n const wallet = this.wallets.get(walletId);\n if (wallet) {\n return this.validateWallet(wallet, walletId);\n }\n\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n async resolveWallet(walletId: string): Promise<WalletEntry | null> {\n // Try local registry first (synchronous)\n const wallet = this.getWallet(walletId);\n if (wallet) {\n return wallet;\n }\n\n // Try skill adapter (includes ~/.evm-wallet.json)\n if (this.skillAdapter.isUsingSkillWallets()) {\n try {\n const config = await this.skillAdapter.getWalletConfig(walletId);\n const mode = this.getMode();\n \n return {\n address: config.address,\n privateKey: config.privateKey,\n mode,\n };\n } catch (error) {\n console.error(`[walletRegistry] Skill wallet resolution failed: ${error}`);\n }\n }\n\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n\n /**\n * Validate a wallet entry\n */\n private validateWallet(wallet: WalletEntry, walletId: string): WalletEntry | null {\n // In execute mode, validate that private key is present\n if (wallet.mode === 'execute' && !wallet.privateKey) {\n console.error(`[walletRegistry] Wallet ${walletId} missing privateKey in execute mode`);\n return null;\n }\n\n // Validate address format (basic check)\n if (!wallet.address || !wallet.address.startsWith('0x')) {\n console.error(`[walletRegistry] Wallet ${walletId} has invalid address: ${wallet.address}`);\n return null;\n }\n\n return wallet;\n }\n\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode(): 'execute' | 'prepare' {\n return (process.env.AMPED_OC_MODE as 'execute' | 'prepare') || 'execute';\n }\n\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode(): boolean {\n return this.getMode() === 'execute';\n }\n\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode(): boolean {\n return this.getMode() === 'prepare';\n }\n\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds(): string[] {\n const localIds = Array.from(this.wallets.keys());\n const skillIds = this.skillAdapter.getWalletIds();\n // Merge unique IDs\n return [...new Set([...localIds, ...skillIds])];\n }\n\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount(): number {\n return this.getWalletIds().length;\n }\n\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload(): void {\n this.wallets = this.loadWallets();\n }\n}\n\n// Singleton instance\nlet walletRegistryInstance: WalletRegistry | null = null;\n\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport function getWalletRegistry(): WalletRegistry {\n if (!walletRegistryInstance) {\n walletRegistryInstance = new WalletRegistry();\n }\n return walletRegistryInstance;\n}\n\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport function resetWalletRegistry(): void {\n walletRegistryInstance = null;\n}\n",
983 "inputSchema": {},
984 "outputSchema": null,
985 "icons": null,
986 "annotations": null,
987 "meta": null,
988 "execution": null
989 },
990 {
991 "name": "index.ts",
992 "title": null,
993 "description": "Script: index.ts. Code:\n/**\n * Wallet Backends\n * \n * Export all wallet backend implementations\n */\n\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend, type BankrBackendConfig } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv, type EnvBackendConfig } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider, type BankrWalletProviderConfig } from './BankrWalletProvider';\n",
994 "inputSchema": {},
995 "outputSchema": null,
996 "icons": null,
997 "annotations": null,
998 "meta": null,
999 "execution": null
1000 },
1001 {
1002 "name": "EvmWalletSkillBackend.ts",
1003 "title": null,
1004 "description": "Script: EvmWalletSkillBackend.ts. Code:\n/**\n * EVM Wallet Skill Backend\n * \n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n\n/**\n * Default path to evm-wallet-skill wallet file\n */\nconst DEFAULT_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n\n/**\n * EVM Wallet Skill wallet file structure\n */\ninterface EvmWalletFile {\n address: string;\n privateKey: string;\n}\n\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport class EvmWalletSkillBackend implements IWalletBackend {\n readonly type = 'evm-wallet-skill' as const;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n private walletPath: string;\n private cachedWallet: EvmWalletFile | null = null;\n\n constructor(options: {\n nickname: string;\n path?: string;\n chains?: string[];\n }) {\n this.nickname = options.nickname;\n this.walletPath = options.path || DEFAULT_WALLET_PATH;\n this.supportedChains = options.chains || [...SODAX_SUPPORTED_CHAINS];\n }\n\n /**\n * Load wallet from file (cached)\n */\n private loadWallet(): EvmWalletFile {\n if (this.cachedWallet) return this.cachedWallet;\n \n if (!existsSync(this.walletPath)) {\n throw new Error(\n `Wallet file not found: ${this.walletPath}\\n` +\n `Run: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n` +\n ` cd ~/.openclaw/skills/evm-wallet-skill && npm install && node src/setup.js`\n );\n }\n \n try {\n const content = readFileSync(this.walletPath, 'utf-8');\n this.cachedWallet = JSON.parse(content) as EvmWalletFile;\n return this.cachedWallet;\n } catch (error) {\n throw new Error(`Failed to load wallet from ${this.walletPath}: ${error}`);\n }\n }\n\n async getAddress(): Promise<Address> {\n const wallet = this.loadWallet();\n return wallet.address as Address;\n }\n\n supportsChain(chainId: string): boolean {\n return this.supportedChains.includes(chainId);\n }\n\n async getPrivateKey(): Promise<`0x${string}`> {\n const wallet = this.loadWallet();\n const key = wallet.privateKey;\n return key.startsWith('0x') ? key as `0x${string}` : `0x${key}` as `0x${string}`;\n }\n\n async isReady(): Promise<boolean> {\n try {\n this.loadWallet();\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Create an evm-wallet-skill backend\n */\nexport function createEvmWalletSkillBackend(options: {\n nickname?: string;\n path?: string;\n chains?: string[];\n} = {}): EvmWalletSkillBackend {\n return new EvmWalletSkillBackend({\n nickname: options.nickname || 'main',\n path: options.path,\n chains: options.chains,\n });\n}\n",
1005 "inputSchema": {},
1006 "outputSchema": null,
1007 "icons": null,
1008 "annotations": null,
1009 "meta": null,
1010 "execution": null
1011 },
1012 {
1013 "name": "BankrWalletProvider.ts",
1014 "title": null,
1015 "description": "Script: BankrWalletProvider.ts. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n * \n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n * \n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n * \n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\n\nimport type { Address, Hash, PublicClient } from 'viem';\nimport { createPublicClient, http } from 'viem';\nimport { mainnet, polygon, base } from 'viem/chains';\nimport type { \n IEvmWalletProvider, \n EvmRawTransaction, \n EvmRawTransactionReceipt \n} from '@sodax/types';\n\n/**\n * Chain configurations for Bankr\n */\nconst BANKR_CHAINS: Record<number, { chain: any; name: string }> = {\n 1: { chain: mainnet, name: 'ethereum' },\n 137: { chain: polygon, name: 'polygon' },\n 8453: { chain: base, name: 'base' },\n};\n\n/**\n * Bankr API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message?: string;\n error?: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ \n type?: string;\n transactionHash?: string;\n txHash?: string;\n hash?: string;\n [key: string]: unknown;\n }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n}\n\n/**\n * Configuration for BankrWalletProvider\n */\nexport interface BankrWalletProviderConfig {\n apiKey: string;\n apiUrl?: string;\n chainId: number;\n rpcUrl?: string;\n /** Pre-cached address (avoids initial API call) */\n cachedAddress?: Address;\n}\n\n/**\n * Bankr Wallet Provider\n * \n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport class BankrWalletProvider implements IEvmWalletProvider {\n readonly publicClient: PublicClient;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private readonly chainId: number;\n private cachedAddress: Address | null;\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrWalletProviderConfig) {\n this.apiKey = config.apiKey;\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.chainId = config.chainId;\n this.cachedAddress = config.cachedAddress || null;\n \n // Validate chain support\n const chainConfig = BANKR_CHAINS[config.chainId];\n if (!chainConfig) {\n throw new Error(\n `Bankr does not support chainId ${config.chainId}. ` +\n `Supported: Ethereum (1), Polygon (137), Base (8453)`\n );\n }\n \n // Create public client for read operations\n this.publicClient = createPublicClient({\n chain: chainConfig.chain,\n transport: http(config.rpcUrl),\n }) as PublicClient;\n \n console.log(`[BankrWalletProvider] Initialized for ${chainConfig.name} (${config.chainId})`);\n }\n\n /**\n * Get the Bankr wallet address\n */\n async getWalletAddress(): Promise<Address> {\n if (this.cachedAddress) return this.cachedAddress;\n \n console.log('[BankrWalletProvider] Fetching wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n \n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n throw new Error('Could not parse wallet address from Bankr response');\n }\n \n this.cachedAddress = addressMatch[0] as Address;\n console.log(`[BankrWalletProvider] Wallet address: ${this.cachedAddress}`);\n \n return this.cachedAddress;\n } catch (error) {\n console.error('[BankrWalletProvider] Failed to get address:', error);\n throw error;\n }\n }\n\n /**\n * Send a transaction via Bankr\n * \n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n async sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash> {\n console.log('[BankrWalletProvider] Sending transaction via Bankr');\n console.log(`[BankrWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[BankrWalletProvider] Value: ${evmRawTx.value}`);\n console.log(`[BankrWalletProvider] Data: ${evmRawTx.data.slice(0, 20)}...`);\n\n // Format transaction for Bankr's arbitrary transaction endpoint\n const txJson = JSON.stringify({\n to: evmRawTx.to,\n data: evmRawTx.data,\n value: evmRawTx.value.toString(),\n chainId: this.chainId,\n }, null, 2);\n\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n\n console.log('[BankrWalletProvider] Submitting to Bankr API...');\n \n const result = await this.submitAndWaitForJob(prompt);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`Transaction failed: ${errorMsg}`);\n }\n\n console.log(`[BankrWalletProvider] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Wait for transaction receipt\n * \n * Uses the public client to query the blockchain directly.\n */\n async waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt> {\n console.log(`[BankrWalletProvider] Waiting for receipt: ${txHash}`);\n \n const receipt = await this.publicClient.waitForTransactionReceipt({\n hash: txHash,\n timeout: 120_000, // 2 minutes\n });\n\n // Convert viem receipt to SODAX format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: `0x${receipt.transactionIndex.toString(16)}`,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: `0x${receipt.cumulativeGasUsed.toString(16)}`,\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: receipt.contractAddress,\n logs: receipt.logs.map(log => ({\n address: log.address as Address,\n topics: (log as any).topics || [],\n data: log.data,\n blockHash: log.blockHash,\n blockNumber: log.blockNumber ? `0x${log.blockNumber.toString(16)}` : null,\n logIndex: log.logIndex !== null ? `0x${log.logIndex.toString(16)}` : null,\n transactionHash: log.transactionHash,\n transactionIndex: log.transactionIndex !== null ? `0x${log.transactionIndex.toString(16)}` : null,\n removed: log.removed,\n })) as any,\n logsBloom: receipt.logsBloom,\n status: receipt.status === 'success' ? '0x1' : '0x0',\n type: receipt.type,\n effectiveGasPrice: receipt.effectiveGasPrice ? `0x${receipt.effectiveGasPrice.toString(16)}` : undefined,\n };\n }\n\n /**\n * Submit prompt and wait for text response\n */\n private async submitAndWait(prompt: string): Promise<string> {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n\n /**\n * Submit prompt and wait for job completion\n */\n private async submitAndWaitForJob(prompt: string): Promise<BankrJobStatusResponse> {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`Failed to submit job: ${submitResponse.status} ${error}`);\n }\n\n const submitData = await submitResponse.json() as BankrJobSubmitResponse;\n \n if (!submitData.success || !submitData.jobId) {\n throw new Error(`Invalid job response: ${JSON.stringify(submitData)}`);\n }\n\n const jobId = submitData.jobId;\n console.log(`[BankrWalletProvider] Job submitted: ${jobId}`);\n\n // Poll for completion\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`Failed to get job status: ${statusResponse.status} ${error}`);\n }\n\n const result = await statusResponse.json() as BankrJobStatusResponse;\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrWalletProvider] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`Job was cancelled`);\n }\n }\n\n throw new Error(`Job ${jobId} timed out`);\n }\n\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) return item.transactionHash as Hash;\n if (item.txHash) return item.txHash as Hash;\n if (item.hash) return item.hash as Hash;\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) return hashMatch[0] as Hash;\n }\n\n return null;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a BankrWalletProvider\n */\nexport function createBankrWalletProvider(config: BankrWalletProviderConfig): BankrWalletProvider {\n return new BankrWalletProvider(config);\n}\n",
1016 "inputSchema": {},
1017 "outputSchema": null,
1018 "icons": null,
1019 "annotations": null,
1020 "meta": null,
1021 "execution": null
1022 },
1023 {
1024 "name": "BankrBackend.ts",
1025 "title": null,
1026 "description": "Script: BankrBackend.ts. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\n\nimport type { Address, Hash } from 'viem';\nimport type { IWalletBackend, RawTransaction } from '../types';\nimport { BANKR_SUPPORTED_CHAINS, isBankrSupportedChain } from '../types';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\n\n/**\n * Disk cache path for bankr address\n */\nconst BANKR_CACHE_DIR = join(homedir(), '.openclaw', 'cache');\nconst getBankrCachePath = (nickname: string) => join(BANKR_CACHE_DIR, `bankr-${nickname}-address.json`);\n\n/**\n * Bankr API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message?: string;\n error?: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ \n type?: string;\n transactionHash?: string;\n txHash?: string;\n hash?: string;\n [key: string]: unknown;\n }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n createdAt: string;\n completedAt?: string;\n}\n\n/**\n * Bankr backend configuration\n */\nexport interface BankrBackendConfig {\n nickname?: string;\n apiKey: string;\n apiUrl?: string;\n}\n\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport class BankrBackend implements IWalletBackend {\n readonly type = 'bankr' as const;\n readonly nickname: string;\n readonly supportedChains = BANKR_SUPPORTED_CHAINS;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private cachedAddress: Address | null = null;\n private cachedSolanaAddress: string | null = null;\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrBackendConfig) {\n this.nickname = config.nickname || 'bankr';\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.apiKey = config.apiKey;\n \n // Try to load cached address from disk\n this.loadCachedAddress();\n \n console.log(`[BankrBackend] Initialized as \"${this.nickname}\"`);\n console.log(`[BankrBackend] Supported chains: ${this.supportedChains.join(', ')}`);\n if (this.cachedAddress) {\n console.log(`[BankrBackend] Loaded cached address: ${this.cachedAddress}`);\n }\n }\n\n /**\n * Load cached address from disk\n */\n private loadCachedAddress(): void {\n const cachePath = getBankrCachePath(this.nickname);\n if (existsSync(cachePath)) {\n try {\n const data = JSON.parse(readFileSync(cachePath, 'utf-8'));\n if (data.address && data.address.match(/^0x[a-fA-F0-9]{40}$/)) {\n this.cachedAddress = data.address as Address;\n }\n } catch (e) {\n console.warn(`[BankrBackend] Failed to load cached address: ${e}`);\n }\n }\n }\n\n /**\n * Save address to disk cache\n */\n private saveCachedAddress(address: Address): void {\n const cachePath = getBankrCachePath(this.nickname);\n try {\n if (!existsSync(BANKR_CACHE_DIR)) {\n mkdirSync(BANKR_CACHE_DIR, { recursive: true });\n }\n writeFileSync(cachePath, JSON.stringify({ address, timestamp: Date.now() }));\n console.log(`[BankrBackend] Cached address to ${cachePath}`);\n } catch (e) {\n console.warn(`[BankrBackend] Failed to cache address: ${e}`);\n }\n }\n\n async getAddress(): Promise<Address> {\n if (this.cachedAddress) return this.cachedAddress;\n \n // Query Bankr for the wallet address\n console.log('[BankrBackend] Fetching wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n \n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n console.warn('[BankrBackend] Could not parse address from response:', response.slice(0, 100));\n throw new Error('[BankrBackend] Could not determine wallet address from Bankr');\n }\n \n this.cachedAddress = addressMatch[0] as Address;\n \n // Save to disk for next time\n this.saveCachedAddress(this.cachedAddress);\n \n console.log(`[BankrBackend] Wallet address: ${this.cachedAddress}`);\n \n return this.cachedAddress;\n } catch (error) {\n console.error('[BankrBackend] Failed to get address:', error);\n throw error;\n }\n }\n\n /**\n * Get the Solana wallet address from Bankr\n */\n async getSolanaAddress(): Promise<string | null> {\n if (this.cachedSolanaAddress) return this.cachedSolanaAddress;\n \n // Check for cached address on disk\n const cachePath = `${process.env.HOME}/.openclaw/cache/bankr-${this.nickname}-solana-address.json`;\n try {\n const fs = await import('fs');\n if (fs.existsSync(cachePath)) {\n const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));\n if (cached.address && Date.now() - cached.timestamp < 86400000) {\n this.cachedSolanaAddress = cached.address;\n console.log(`[BankrBackend] Loaded cached Solana address: ${this.cachedSolanaAddress}`);\n return this.cachedSolanaAddress;\n }\n }\n } catch (e) {\n // Cache miss, continue to query\n }\n \n console.log('[BankrBackend] Fetching Solana wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my Solana wallet address?');\n \n // Solana addresses are base58, typically 32-44 chars, no 0x prefix\n const solanaMatch = response.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/);\n if (!solanaMatch) {\n console.warn('[BankrBackend] Could not parse Solana address from response');\n return null;\n }\n \n this.cachedSolanaAddress = solanaMatch[0];\n console.log(`[BankrBackend] Solana address: ${this.cachedSolanaAddress}`);\n \n // Cache to disk\n try {\n const fs = await import('fs');\n const path = await import('path');\n const cacheDir = path.dirname(cachePath);\n if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify({ address: this.cachedSolanaAddress, timestamp: Date.now() }));\n } catch (e) {\n console.warn('[BankrBackend] Failed to cache Solana address:', e);\n }\n \n return this.cachedSolanaAddress;\n } catch (error) {\n console.error('[BankrBackend] Failed to get Solana address:', error);\n return null;\n }\n }\n\n supportsChain(chainId: string): boolean {\n // Normalize chain ID to handle SODAX format (0x2105.base -> base)\n return isBankrSupportedChain(chainId);\n }\n\n async isReady(): Promise<boolean> {\n if (!this.apiKey) return false;\n \n try {\n // Test API connectivity\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n \n return response.status !== 503 && response.status !== 502;\n } catch {\n return false;\n }\n }\n\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n async sendRawTransaction(tx: RawTransaction): Promise<Hash> {\n console.log(`[BankrBackend] Sending raw transaction`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Chain: ${tx.chainId}`);\n console.log(`[BankrBackend] Value: ${tx.value}`);\n console.log(`[BankrBackend] Data: ${tx.data.slice(0, 20)}...`);\n\n // Format as documented in arbitrary-transaction.md\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data,\n value: tx.value,\n chainId: tx.chainId,\n }, null, 2);\n\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n\n console.log(`[BankrBackend] Submitting to Bankr API...`);\n \n const result = await this.submitAndWaitForJob(prompt);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`[BankrBackend] Transaction failed: ${errorMsg}`);\n }\n\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Submit prompt and wait for text response\n */\n private async submitAndWait(prompt: string): Promise<string> {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n\n /**\n * Submit prompt and wait for job completion\n */\n private async submitAndWaitForJob(prompt: string): Promise<BankrJobStatusResponse> {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${submitResponse.status} ${error}`);\n }\n\n const submitData = await submitResponse.json() as BankrJobSubmitResponse;\n \n if (!submitData.success || !submitData.jobId) {\n throw new Error(`[BankrBackend] Invalid job response: ${JSON.stringify(submitData)}`);\n }\n\n const jobId = submitData.jobId;\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n\n // Poll for completion\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${statusResponse.status} ${error}`);\n }\n\n const result = await statusResponse.json() as BankrJobStatusResponse;\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n \n // Log progress updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n }\n }\n\n throw new Error(`[BankrBackend] Job ${jobId} timed out`);\n }\n\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) return item.transactionHash as Hash;\n if (item.txHash) return item.txHash as Hash;\n if (item.hash) return item.hash as Hash;\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) return hashMatch[0] as Hash;\n \n // Check for failure indicators\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n\n return null;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a Bankr backend from API key\n */\nexport function createBankrBackend(config: BankrBackendConfig): BankrBackend {\n return new BankrBackend(config);\n}\n",
1027 "inputSchema": {},
1028 "outputSchema": null,
1029 "icons": null,
1030 "annotations": null,
1031 "meta": null,
1032 "execution": null
1033 },
1034 {
1035 "name": "EnvBackend.ts",
1036 "title": null,
1037 "description": "Script: EnvBackend.ts. Code:\n/**\n * Environment Variable Backend\n * \n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\n\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n\n/**\n * Wallet entry from AMPED_OC_WALLETS_JSON\n */\ninterface EnvWalletEntry {\n address: string;\n privateKey: string;\n}\n\n/**\n * Environment variable backend configuration\n */\nexport interface EnvBackendConfig {\n nickname: string;\n address?: Address;\n privateKey?: `0x${string}`;\n envVar?: string; // Name of env var containing JSON\n chains?: string[];\n}\n\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport class EnvBackend implements IWalletBackend {\n readonly type = 'env' as const;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n private address: Address | null = null;\n private privateKey: `0x${string}` | null = null;\n private envVar: string | null = null;\n\n constructor(config: EnvBackendConfig) {\n this.nickname = config.nickname;\n this.supportedChains = config.chains || [...SODAX_SUPPORTED_CHAINS];\n \n if (config.address && config.privateKey) {\n // Direct address/key provided\n this.address = config.address;\n this.privateKey = config.privateKey;\n } else if (config.envVar) {\n // Will load from env var\n this.envVar = config.envVar;\n }\n }\n\n /**\n * Load wallet from environment variable if needed\n */\n private loadFromEnv(): { address: Address; privateKey: `0x${string}` } {\n if (this.address && this.privateKey) {\n return { address: this.address, privateKey: this.privateKey };\n }\n\n if (this.envVar) {\n const envValue = process.env[this.envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${this.envVar} not set`);\n }\n \n try {\n const data = JSON.parse(envValue) as EnvWalletEntry;\n this.address = data.address as Address;\n this.privateKey = (data.privateKey.startsWith('0x') \n ? data.privateKey \n : `0x${data.privateKey}`) as `0x${string}`;\n return { address: this.address, privateKey: this.privateKey };\n } catch (error) {\n throw new Error(`Failed to parse ${this.envVar}: ${error}`);\n }\n }\n\n throw new Error(`No wallet configuration for \"${this.nickname}\"`);\n }\n\n async getAddress(): Promise<Address> {\n const { address } = this.loadFromEnv();\n return address;\n }\n\n supportsChain(chainId: string): boolean {\n return this.supportedChains.includes(chainId);\n }\n\n async getPrivateKey(): Promise<`0x${string}`> {\n const { privateKey } = this.loadFromEnv();\n return privateKey;\n }\n\n async isReady(): Promise<boolean> {\n try {\n this.loadFromEnv();\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Create env backend from direct config\n */\nexport function createEnvBackend(config: EnvBackendConfig): EnvBackend {\n return new EnvBackend(config);\n}\n\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport function loadWalletsFromEnv(): Map<string, EnvBackend> {\n const wallets = new Map<string, EnvBackend>();\n \n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n if (!walletsJson) return wallets;\n \n try {\n const parsed = JSON.parse(walletsJson) as Record<string, EnvWalletEntry>;\n \n for (const [name, wallet] of Object.entries(parsed)) {\n const backend = new EnvBackend({\n nickname: name,\n address: wallet.address as Address,\n privateKey: (wallet.privateKey.startsWith('0x') \n ? wallet.privateKey \n : `0x${wallet.privateKey}`) as `0x${string}`,\n });\n wallets.set(name.toLowerCase(), backend);\n }\n \n console.log(`[EnvBackend] Loaded ${wallets.size} wallet(s) from AMPED_OC_WALLETS_JSON`);\n } catch (error) {\n console.warn(`[EnvBackend] Failed to parse AMPED_OC_WALLETS_JSON: ${error}`);\n }\n \n return wallets;\n}\n",
1038 "inputSchema": {},
1039 "outputSchema": null,
1040 "icons": null,
1041 "annotations": null,
1042 "meta": null,
1043 "execution": null
1044 },
1045 {
1046 "name": "walletManager.ts",
1047 "title": null,
1048 "description": "Script: walletManager.ts. Code:\n/**\n * Unified Wallet Manager\n * \n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n * \n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport type { Address } from 'viem';\nimport type { IWalletBackend, WalletInfo, WalletsConfigFile, WalletConfig } from './types';\nimport { \n createEvmWalletSkillBackend, \n createBankrBackend, \n createEnvBackend,\n loadWalletsFromEnv \n} from './backends';\n\n/**\n * Config file path\n */\nconst CONFIG_PATH = join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'wallets.json');\nconst EVM_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n\n/**\n * Singleton WalletManager instance\n */\nlet instance: WalletManager | null = null;\n\n/**\n * Unified wallet manager\n */\nexport class WalletManager {\n private wallets = new Map<string, IWalletBackend>();\n private defaultWallet: string | null = null;\n private initialized = false;\n\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n \n console.log('[WalletManager] Initializing...');\n \n // 1. Load from config file if exists\n await this.loadConfigFile();\n \n // 2. Auto-discover from environment\n await this.autoDiscover();\n \n // 3. Set default\n this.determineDefault();\n \n this.initialized = true;\n \n console.log(`[WalletManager] Initialized with ${this.wallets.size} wallet(s)`);\n if (this.defaultWallet) {\n console.log(`[WalletManager] Default wallet: ${this.defaultWallet}`);\n }\n }\n\n /**\n * Load wallets from config file\n */\n private async loadConfigFile(): Promise<void> {\n if (!existsSync(CONFIG_PATH)) return;\n \n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const config = JSON.parse(content) as WalletsConfigFile;\n \n for (const [name, walletConfig] of Object.entries(config.wallets)) {\n const backend = this.createBackendFromConfig(name, walletConfig);\n if (backend) {\n this.wallets.set(name.toLowerCase(), backend);\n console.log(`[WalletManager] Loaded wallet \"${name}\" from config`);\n }\n }\n \n if (config.default) {\n this.defaultWallet = config.default.toLowerCase();\n }\n } catch (error) {\n console.warn(`[WalletManager] Failed to load config: ${error}`);\n }\n }\n\n /**\n * Create backend from config entry\n */\n private createBackendFromConfig(name: string, config: WalletConfig): IWalletBackend | null {\n try {\n switch (config.source) {\n case 'evm-wallet-skill':\n return createEvmWalletSkillBackend({\n nickname: name,\n path: config.path,\n chains: config.chains,\n });\n \n case 'bankr':\n if (!config.apiKey) {\n console.warn(`[WalletManager] Bankr wallet \"${name}\" missing apiKey`);\n return null;\n }\n return createBankrBackend({\n nickname: name,\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n });\n \n case 'env':\n return createEnvBackend({\n nickname: name,\n address: config.address,\n privateKey: config.privateKey,\n envVar: config.envVar,\n chains: config.chains,\n });\n \n default:\n console.warn(`[WalletManager] Unknown wallet source: ${config.source}`);\n return null;\n }\n } catch (error) {\n console.warn(`[WalletManager] Failed to create backend for \"${name}\": ${error}`);\n return null;\n }\n }\n\n /**\n * Auto-discover wallets from environment\n */\n private async autoDiscover(): Promise<void> {\n // evm-wallet-skill (if not already configured)\n if (!this.wallets.has('main') && existsSync(EVM_WALLET_PATH)) {\n try {\n const backend = createEvmWalletSkillBackend({ nickname: 'main' });\n if (await backend.isReady()) {\n this.wallets.set('main', backend);\n console.log('[WalletManager] Auto-discovered: evm-wallet-skill \u2192 \"main\"');\n }\n } catch (error) {\n console.debug(`[WalletManager] evm-wallet-skill not available: ${error}`);\n }\n }\n \n // Bankr (if API key present and not already configured)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (!this.wallets.has('bankr') && bankrApiKey) {\n console.log('[WalletManager] Found BANKR_API_KEY, attempting to add Bankr wallet...');\n try {\n const backend = createBankrBackend({\n nickname: 'bankr',\n apiKey: bankrApiKey,\n apiUrl: process.env.BANKR_API_URL,\n });\n const ready = await backend.isReady();\n if (ready) {\n this.wallets.set('bankr', backend);\n console.log('[WalletManager] Auto-discovered: BANKR_API_KEY \u2192 \"bankr\"');\n } else {\n console.warn('[WalletManager] Bankr API key present but connectivity check failed');\n }\n } catch (error) {\n console.warn(`[WalletManager] Bankr auto-discovery failed: ${error}`);\n }\n }\n \n // Environment variable wallets\n const envWallets = loadWalletsFromEnv();\n for (const [name, backend] of envWallets) {\n if (!this.wallets.has(name)) {\n this.wallets.set(name, backend);\n console.log(`[WalletManager] Auto-discovered: AMPED_OC_WALLETS_JSON \u2192 \"${name}\"`);\n }\n }\n }\n\n /**\n * Determine default wallet\n */\n private determineDefault(): void {\n // If already set from config, verify it exists\n if (this.defaultWallet && this.wallets.has(this.defaultWallet)) {\n return;\n }\n \n // Priority: main > first available\n if (this.wallets.has('main')) {\n this.defaultWallet = 'main';\n } else if (this.wallets.size > 0) {\n this.defaultWallet = Array.from(this.wallets.keys())[0];\n } else {\n this.defaultWallet = null;\n }\n }\n\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n async resolve(nickname?: string): Promise<IWalletBackend> {\n await this.initialize();\n \n const name = (nickname || this.defaultWallet)?.toLowerCase();\n \n if (!name) {\n throw new Error(\n 'No wallet configured.\\n\\n' +\n 'To set up a wallet, install evm-wallet-skill:\\n' +\n ' git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n' +\n ' cd ~/.openclaw/skills/evm-wallet-skill && npm install\\n' +\n ' node src/setup.js'\n );\n }\n \n const wallet = this.wallets.get(name);\n if (!wallet) {\n const available = Array.from(this.wallets.keys()).join(', ') || '(none)';\n throw new Error(`Wallet \"${name}\" not found. Available wallets: ${available}`);\n }\n \n return wallet;\n }\n\n /**\n * Check if a wallet exists\n */\n async has(nickname: string): Promise<boolean> {\n await this.initialize();\n return this.wallets.has(nickname.toLowerCase());\n }\n\n /**\n * List all available wallets\n */\n async listWallets(): Promise<WalletInfo[]> {\n await this.initialize();\n \n const wallets: WalletInfo[] = [];\n \n for (const [name, backend] of this.wallets) {\n try {\n // Add timeout for slow backends (like Bankr)\n const addressPromise = backend.getAddress();\n const timeoutPromise = new Promise<never>((_, reject) => \n setTimeout(() => reject(new Error('Timeout')), 30000)\n );\n \n const address = await Promise.race([addressPromise, timeoutPromise]);\n \n // Get Solana address for Bankr wallets\n let solanaAddress: string | undefined;\n if (backend.type === 'bankr' && (backend as any).getSolanaAddress) {\n try {\n solanaAddress = await (backend as any).getSolanaAddress() || undefined;\n } catch (e) {\n console.warn(`[WalletManager] Failed to get Solana address for ${name}`);\n }\n }\n \n wallets.push({\n nickname: name,\n type: backend.type,\n address,\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n solanaAddress,\n });\n } catch (error) {\n // Include wallet with placeholder address if we can't get it\n console.warn(`[WalletManager] Failed to get address for \"${name}\": ${error}`);\n wallets.push({\n nickname: name,\n type: backend.type,\n address: '0x...' as Address, // Placeholder\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n });\n }\n }\n \n return wallets;\n }\n\n /**\n * Get the default wallet nickname\n */\n async getDefaultWalletName(): Promise<string | null> {\n await this.initialize();\n return this.defaultWallet;\n }\n\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname: string, backend: IWalletBackend): void {\n this.wallets.set(nickname.toLowerCase(), backend);\n console.log(`[WalletManager] Registered wallet: ${nickname}`);\n }\n\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds(): string[] {\n return Array.from(this.wallets.keys());\n }\n\n /**\n * Add a new wallet to the config file\n */\n async addWallet(nickname: string, config: WalletConfig): Promise<void> {\n await this.initialize();\n \n const normalizedName = nickname.toLowerCase();\n \n // Check if wallet already exists\n if (this.wallets.has(normalizedName)) {\n throw new Error(`Wallet \"${nickname}\" already exists. Use rename to change it.`);\n }\n \n // Create the backend to validate config\n const backend = this.createBackendFromConfig(normalizedName, config);\n if (!backend) {\n throw new Error(`Failed to create wallet backend for \"${nickname}\"`);\n }\n \n // Validate the backend works\n const ready = await backend.isReady();\n if (!ready) {\n throw new Error(`Wallet \"${nickname}\" configuration is invalid or not accessible`);\n }\n \n // Load existing config\n const fileConfig = this.loadConfigFromFile();\n \n // Add new wallet\n fileConfig.wallets[normalizedName] = config;\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Register in memory\n this.wallets.set(normalizedName, backend);\n \n console.log(`[WalletManager] Added wallet \"${nickname}\"`);\n }\n\n /**\n * Rename a wallet\n */\n async renameWallet(currentNickname: string, newNickname: string): Promise<void> {\n await this.initialize();\n \n const currentName = currentNickname.toLowerCase();\n const newName = newNickname.toLowerCase();\n \n // Check source exists\n if (!this.wallets.has(currentName)) {\n throw new Error(`Wallet \"${currentNickname}\" not found`);\n }\n \n // Check target doesn't exist\n if (this.wallets.has(newName)) {\n throw new Error(`Wallet \"${newNickname}\" already exists`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Move wallet config\n if (fileConfig.wallets[currentName]) {\n fileConfig.wallets[newName] = fileConfig.wallets[currentName];\n delete fileConfig.wallets[currentName];\n } else {\n // Wallet was auto-discovered, need to add it to config\n const backend = this.wallets.get(currentName)!;\n const config = await this.backendToConfig(backend);\n fileConfig.wallets[newName] = config;\n }\n \n // Update default if needed\n if (fileConfig.default === currentName) {\n fileConfig.default = newName;\n }\n if (this.defaultWallet === currentName) {\n this.defaultWallet = newName;\n }\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Update in-memory\n const backend = this.wallets.get(currentName)!;\n this.wallets.delete(currentName);\n this.wallets.set(newName, backend);\n \n console.log(`[WalletManager] Renamed wallet \"${currentNickname}\" to \"${newNickname}\"`);\n }\n\n /**\n * Remove a wallet from config\n */\n async removeWallet(nickname: string): Promise<void> {\n await this.initialize();\n \n const name = nickname.toLowerCase();\n \n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Remove from config\n delete fileConfig.wallets[name];\n \n // Update default if needed\n if (fileConfig.default === name) {\n delete fileConfig.default;\n }\n if (this.defaultWallet === name) {\n this.defaultWallet = null;\n this.determineDefault();\n }\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Remove from memory\n this.wallets.delete(name);\n \n console.log(`[WalletManager] Removed wallet \"${nickname}\"`);\n }\n\n /**\n * Set the default wallet\n */\n async setDefaultWallet(nickname: string): Promise<void> {\n await this.initialize();\n \n const name = nickname.toLowerCase();\n \n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Update default\n fileConfig.default = name;\n this.defaultWallet = name;\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n console.log(`[WalletManager] Set default wallet to \"${nickname}\"`);\n }\n\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n private loadConfigFromFile(): WalletsConfigFile {\n if (!existsSync(CONFIG_PATH)) {\n return { wallets: {} };\n }\n \n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n return JSON.parse(content) as WalletsConfigFile;\n } catch {\n return { wallets: {} };\n }\n }\n\n /**\n * Save config to file\n */\n private saveConfigToFile(config: WalletsConfigFile): void {\n // Ensure directory exists\n const dir = dirname(CONFIG_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n \n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n console.log(`[WalletManager] Config saved to ${CONFIG_PATH}`);\n }\n\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n private async backendToConfig(backend: IWalletBackend): Promise<WalletConfig> {\n const config: WalletConfig = {\n source: backend.type,\n chains: [...backend.supportedChains],\n };\n \n // For evm-wallet-skill, just reference the default path\n if (backend.type === 'evm-wallet-skill') {\n config.path = EVM_WALLET_PATH;\n }\n \n // For env backends, we need address (privateKey should NOT be saved)\n if (backend.type === 'env') {\n config.address = await backend.getAddress();\n // Note: We don't save privateKey to config for security\n }\n \n // For bankr, we need the API key\n if (backend.type === 'bankr') {\n config.apiKey = process.env.BANKR_API_KEY;\n }\n \n return config;\n }\n\n /**\n * Reset the manager (for testing)\n */\n reset(): void {\n this.wallets.clear();\n this.defaultWallet = null;\n this.initialized = false;\n }\n}\n\n/**\n * Get the singleton WalletManager instance\n */\nexport function getWalletManager(): WalletManager {\n if (!instance) {\n instance = new WalletManager();\n }\n return instance;\n}\n\n/**\n * Reset the singleton (for testing)\n */\nexport function resetWalletManager(): void {\n if (instance) {\n instance.reset();\n instance = null;\n }\n}\n",
1049 "inputSchema": {},
1050 "outputSchema": null,
1051 "icons": null,
1052 "annotations": null,
1053 "meta": null,
1054 "execution": null
1055 },
1056 {
1057 "name": "LocalKeyBackend.ts",
1058 "title": null,
1059 "description": "Script: LocalKeyBackend.ts. Code:\n/**\n * Local Key Backend\n * \n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n * \n * Uses viem for transaction signing and submission.\n */\n\nimport {\n createPublicClient,\n createWalletClient,\n http,\n type Hash,\n type Address,\n type Chain,\n} from 'viem';\nimport { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport type { \n IWalletBackend, \n LocalKeyBackendConfig, \n TransactionRequest, \n TransactionReceipt \n} from './types';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n\n/**\n * Local private key wallet backend\n * \n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport class LocalKeyBackend implements IWalletBackend {\n readonly type = 'localKey' as const;\n \n private readonly account: PrivateKeyAccount;\n private readonly walletClient: ReturnType<typeof createWalletClient>;\n private readonly _publicClient: ReturnType<typeof createPublicClient>;\n private readonly chainId: number;\n private readonly chain: Chain;\n\n constructor(config: LocalKeyBackendConfig) {\n // Resolve chain configuration\n this.chainId = resolveChainId(config.chainId);\n this.chain = getViemChain(this.chainId);\n \n // Get RPC URL (custom or default)\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(this.chainId);\n \n // Create account from private key\n this.account = privateKeyToAccount(config.privateKey);\n \n // Create viem clients\n this._publicClient = createPublicClient({\n chain: this.chain,\n transport: http(rpcUrl),\n });\n \n this.walletClient = createWalletClient({\n account: this.account,\n chain: this.chain,\n transport: http(rpcUrl),\n });\n \n console.log(`[LocalKeyBackend] Initialized for chain ${this.chain.name} (${this.chainId})`);\n console.log(`[LocalKeyBackend] Address: ${this.account.address}`);\n }\n\n /**\n * Get the wallet address\n */\n async getAddress(): Promise<Address> {\n return this.account.address;\n }\n\n /**\n * Send a transaction\n * \n * Signs locally and submits via RPC.\n */\n async sendTransaction(tx: TransactionRequest): Promise<Hash> {\n console.log(`[LocalKeyBackend] Sending transaction to ${tx.to}`);\n \n // Build transaction params\n const txParams: any = {\n account: this.account,\n chain: this.chain,\n to: tx.to,\n value: tx.value || 0n,\n data: tx.data,\n };\n \n // Add optional gas parameters\n if (tx.gasLimit) txParams.gas = tx.gasLimit;\n if (tx.gasPrice) txParams.gasPrice = tx.gasPrice;\n if (tx.maxFeePerGas) txParams.maxFeePerGas = tx.maxFeePerGas;\n if (tx.maxPriorityFeePerGas) txParams.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;\n if (tx.nonce !== undefined) txParams.nonce = tx.nonce;\n \n const hash = await this.walletClient.sendTransaction(txParams);\n \n console.log(`[LocalKeyBackend] Transaction sent: ${hash}`);\n return hash;\n }\n\n /**\n * Wait for transaction confirmation\n */\n async waitForTransaction(txHash: Hash): Promise<TransactionReceipt> {\n console.log(`[LocalKeyBackend] Waiting for transaction: ${txHash}`);\n \n const receipt = await this._publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n \n console.log(`[LocalKeyBackend] Transaction confirmed in block ${receipt.blockNumber}`);\n \n return {\n transactionHash: receipt.transactionHash,\n blockNumber: receipt.blockNumber,\n blockHash: receipt.blockHash,\n from: receipt.from,\n to: receipt.to,\n gasUsed: receipt.gasUsed,\n status: receipt.status === 'success' ? 'success' : 'reverted',\n logs: receipt.logs.map((log: any) => ({\n address: log.address,\n topics: [...(log.topics || [])] as `0x${string}`[],\n data: log.data,\n })),\n };\n }\n\n /**\n * Check if backend is ready\n * \n * For local key backend, we verify RPC connectivity.\n */\n async isReady(): Promise<boolean> {\n try {\n await this._publicClient.getChainId();\n return true;\n } catch (error) {\n console.error('[LocalKeyBackend] RPC connectivity check failed:', error);\n return false;\n }\n }\n\n /**\n * Get the chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n\n /**\n * Get the public client (for external use)\n */\n getPublicClient(): ReturnType<typeof createPublicClient> {\n return this._publicClient;\n }\n\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient(): ReturnType<typeof createWalletClient> {\n return this.walletClient;\n }\n}\n\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport async function createLocalKeyBackend(config: LocalKeyBackendConfig): Promise<LocalKeyBackend> {\n const backend = new LocalKeyBackend(config);\n \n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[LocalKeyBackend] Backend created but RPC connectivity check failed');\n }\n \n return backend;\n}\n",
1060 "inputSchema": {},
1061 "outputSchema": null,
1062 "icons": null,
1063 "annotations": null,
1064 "meta": null,
1065 "execution": null
1066 },
1067 {
1068 "name": "index.ts",
1069 "title": null,
1070 "description": "Script: index.ts. Code:\n/**\n * Wallet Providers\n * \n * Pluggable wallet backend architecture for Amped DeFi plugin.\n * \n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n * \n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n * \n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\n\n// Main provider\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\n\n// Backends\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\n\n// Chain configuration\nexport {\n CHAIN_IDS,\n SDK_CHAIN_ID_MAP,\n DEFAULT_RPC_URLS,\n hyper,\n resolveChainId,\n getViemChain,\n getDefaultRpcUrl,\n isChainSupported,\n getSupportedChainIds,\n getChainName,\n} from './chainConfig';\n\n// Types\nexport type {\n WalletBackendType,\n WalletBackendBaseConfig,\n LocalKeyBackendConfig,\n BankrBackendConfig,\n PrivyBackendConfig,\n SmartWalletBackendConfig,\n WalletBackendConfig,\n TransactionRequest,\n TransactionReceipt,\n IWalletBackend,\n WalletBackendFactory,\n AmpedWalletProviderConfig,\n IAmpedWalletProvider,\n} from './types';\n",
1071 "inputSchema": {},
1072 "outputSchema": null,
1073 "icons": null,
1074 "annotations": null,
1075 "meta": null,
1076 "execution": null
1077 },
1078 {
1079 "name": "AmpedWalletProvider.ts",
1080 "title": null,
1081 "description": "Script: AmpedWalletProvider.ts. Code:\n/**\n * Amped Wallet Provider\n * \n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n * \n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n * \n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\n\nimport {\n createPublicClient,\n http,\n type Hash,\n type Address,\n} from 'viem';\nimport type { \n IEvmWalletProvider, \n EvmRawTransaction, \n EvmRawTransactionReceipt \n} from '@sodax/types';\nimport type { \n IWalletBackend, \n WalletBackendConfig, \n WalletBackendType,\n IAmpedWalletProvider,\n LocalKeyBackendConfig,\n BankrBackendConfig,\n} from './types';\nimport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nimport { BankrBackend, createBankrBackend } from './BankrBackend';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n\n/**\n * Amped Wallet Provider\n * \n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport class AmpedWalletProvider implements IAmpedWalletProvider {\n readonly publicClient: ReturnType<typeof createPublicClient>;\n \n private readonly backend: IWalletBackend;\n private readonly chainId: number;\n\n private constructor(backend: IWalletBackend, publicClient: ReturnType<typeof createPublicClient>) {\n this.backend = backend;\n this.publicClient = publicClient;\n this.chainId = backend.getChainId();\n \n console.log(`[AmpedWalletProvider] Initialized with ${backend.type} backend`);\n console.log(`[AmpedWalletProvider] Chain ID: ${this.chainId}`);\n }\n\n /**\n * Create an AmpedWalletProvider with a local key backend\n * \n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static async fromPrivateKey(config: {\n privateKey: `0x${string}`;\n chainId: string | number;\n rpcUrl?: string;\n }): Promise<AmpedWalletProvider> {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n\n // Create backend\n const backend = await createLocalKeyBackend({\n type: 'localKey',\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl,\n });\n\n // Use the backend's public client\n const publicClient = backend.getPublicClient() as any;\n\n return new AmpedWalletProvider(backend, publicClient);\n }\n\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n * \n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static async fromBankr(config: {\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n chainId: string | number;\n rpcUrl?: string;\n policy?: BankrBackendConfig['policy'];\n }): Promise<AmpedWalletProvider> {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n\n // Create backend\n const backend = await createBankrBackend({\n type: 'bankr',\n bankrApiUrl: config.bankrApiUrl,\n bankrApiKey: config.bankrApiKey,\n userAddress: config.userAddress,\n chainId: config.chainId,\n rpcUrl,\n policy: config.policy,\n });\n\n // Create public client (for read operations)\n // Bankr backend doesn't have its own public client\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n }) as any; // Type cast needed due to viem's strict typing\n\n return new AmpedWalletProvider(backend, publicClient);\n }\n\n /**\n * Create from generic backend configuration\n */\n static async fromConfig(config: WalletBackendConfig): Promise<AmpedWalletProvider> {\n switch (config.type) {\n case 'localKey':\n return AmpedWalletProvider.fromPrivateKey({\n privateKey: (config as LocalKeyBackendConfig).privateKey,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n });\n \n case 'bankr':\n const bankrConfig = config as BankrBackendConfig;\n return AmpedWalletProvider.fromBankr({\n bankrApiUrl: bankrConfig.bankrApiUrl,\n bankrApiKey: bankrConfig.bankrApiKey,\n userAddress: bankrConfig.userAddress,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n policy: bankrConfig.policy,\n });\n \n default:\n throw new Error(`Unsupported backend type: ${(config as any).type}`);\n }\n }\n\n // ===== IEvmWalletProvider Implementation =====\n\n /**\n * Get the wallet address\n */\n async getWalletAddress(): Promise<Address> {\n return this.backend.getAddress();\n }\n\n /**\n * Send a transaction\n * \n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n async sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash> {\n console.log(`[AmpedWalletProvider] sendTransaction`);\n console.log(`[AmpedWalletProvider] From: ${evmRawTx.from}`);\n console.log(`[AmpedWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[AmpedWalletProvider] Value: ${evmRawTx.value}`);\n\n return this.backend.sendTransaction({\n to: evmRawTx.to,\n value: evmRawTx.value,\n data: evmRawTx.data,\n });\n }\n\n /**\n * Wait for transaction receipt\n * \n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n async waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt> {\n console.log(`[AmpedWalletProvider] waitForTransactionReceipt: ${txHash}`);\n \n const receipt = await this.backend.waitForTransaction(txHash);\n \n // Convert to SDK format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0', // Not tracked in our simplified receipt\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: '0x0', // Not tracked\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: null, // Would need to check if this was a deployment\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics as [`0x${string}`, ...`0x${string}`[]] | [],\n data: log.data,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n logIndex: '0x0',\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0',\n removed: false,\n })),\n logsBloom: '0x',\n status: receipt.status === 'success' ? '0x1' : '0x0',\n };\n }\n\n // ===== IAmpedWalletProvider Extensions =====\n\n /**\n * Get the underlying backend\n */\n getBackend(): IWalletBackend {\n return this.backend;\n }\n\n /**\n * Get the backend type\n */\n getBackendType(): WalletBackendType {\n return this.backend.type;\n }\n\n /**\n * Check if ready for transactions\n */\n async isReady(): Promise<boolean> {\n return this.backend.isReady();\n }\n\n /**\n * Get chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n}\n\n// Re-export for convenience\nexport type { IAmpedWalletProvider };\n",
1082 "inputSchema": {},
1083 "outputSchema": null,
1084 "icons": null,
1085 "annotations": null,
1086 "meta": null,
1087 "execution": null
1088 },
1089 {
1090 "name": "chainConfig.ts",
1091 "title": null,
1092 "description": "Script: chainConfig.ts. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n * \n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\n\nimport { defineChain, type Chain } from 'viem';\nimport {\n mainnet,\n arbitrum,\n optimism,\n base,\n polygon,\n bsc,\n avalanche,\n sonic,\n lightlinkPhoenix,\n} from 'viem/chains';\n\n/**\n * Chain ID constants matching @sodax/types\n */\nexport const CHAIN_IDS = {\n ETHEREUM: 1,\n ARBITRUM: 42161,\n OPTIMISM: 10,\n BASE: 8453,\n POLYGON: 137,\n BSC: 56,\n AVALANCHE: 43114,\n SONIC: 146,\n LIGHTLINK: 1890,\n HYPEREVM: 999,\n KAIA: 8217,\n} as const;\n\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport const SDK_CHAIN_ID_MAP: Record<string, number> = {\n 'ethereum': CHAIN_IDS.ETHEREUM,\n 'arbitrum': CHAIN_IDS.ARBITRUM,\n '0xa4b1.arbitrum': CHAIN_IDS.ARBITRUM,\n 'optimism': CHAIN_IDS.OPTIMISM,\n '0xa.optimism': CHAIN_IDS.OPTIMISM,\n 'base': CHAIN_IDS.BASE,\n '0x2105.base': CHAIN_IDS.BASE,\n 'polygon': CHAIN_IDS.POLYGON,\n '0x89.polygon': CHAIN_IDS.POLYGON,\n 'bsc': CHAIN_IDS.BSC,\n '0x38.bsc': CHAIN_IDS.BSC,\n 'avalanche': CHAIN_IDS.AVALANCHE,\n 'avax': CHAIN_IDS.AVALANCHE,\n '0xa86a.avax': CHAIN_IDS.AVALANCHE,\n 'sonic': CHAIN_IDS.SONIC,\n 'lightlink': CHAIN_IDS.LIGHTLINK,\n 'hyperevm': CHAIN_IDS.HYPEREVM,\n 'hyper': CHAIN_IDS.HYPEREVM,\n 'kaia': CHAIN_IDS.KAIA,\n '0x2019.kaia': CHAIN_IDS.KAIA,\n};\n\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport const hyper = defineChain({\n id: CHAIN_IDS.HYPEREVM,\n name: 'HyperEVM',\n nativeCurrency: { decimals: 18, name: 'HYPE', symbol: 'HYPE' },\n rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },\n blockExplorers: { default: { name: 'HyperEVMScan', url: 'https://hyperevmscan.io/' } },\n contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 13051 } },\n});\n\n/**\n * Kaia chain definition\n */\nexport const kaia = defineChain({\n id: CHAIN_IDS.KAIA,\n name: 'Kaia',\n nativeCurrency: { decimals: 18, name: 'KAIA', symbol: 'KAIA' },\n rpcUrls: { default: { http: ['https://public-en.node.kaia.io'] } },\n blockExplorers: { default: { name: 'KaiaScan', url: 'https://kaiascan.io/' } },\n});\n\n/**\n * Chain configuration by numeric ID\n */\nconst CHAIN_CONFIG: Record<number, Chain> = {\n [CHAIN_IDS.ETHEREUM]: mainnet,\n [CHAIN_IDS.ARBITRUM]: arbitrum,\n [CHAIN_IDS.OPTIMISM]: optimism,\n [CHAIN_IDS.BASE]: base,\n [CHAIN_IDS.POLYGON]: polygon,\n [CHAIN_IDS.BSC]: bsc,\n [CHAIN_IDS.AVALANCHE]: avalanche,\n [CHAIN_IDS.SONIC]: sonic,\n [CHAIN_IDS.LIGHTLINK]: lightlinkPhoenix,\n [CHAIN_IDS.HYPEREVM]: hyper,\n [CHAIN_IDS.KAIA]: kaia,\n};\n\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport const DEFAULT_RPC_URLS: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'https://ethereum.publicnode.com',\n [CHAIN_IDS.ARBITRUM]: 'https://arb1.arbitrum.io/rpc',\n [CHAIN_IDS.OPTIMISM]: 'https://mainnet.optimism.io',\n [CHAIN_IDS.BASE]: 'https://mainnet.base.org',\n [CHAIN_IDS.POLYGON]: 'https://polygon-bor-rpc.publicnode.com',\n [CHAIN_IDS.BSC]: 'https://bsc-dataseed.binance.org',\n [CHAIN_IDS.AVALANCHE]: 'https://api.avax.network/ext/bc/C/rpc',\n [CHAIN_IDS.SONIC]: 'https://rpc.soniclabs.com',\n [CHAIN_IDS.LIGHTLINK]: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n [CHAIN_IDS.HYPEREVM]: 'https://rpc.hyperliquid.xyz/evm',\n [CHAIN_IDS.KAIA]: 'https://public-en.node.kaia.io',\n};\n\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport function resolveChainId(sdkChainId: string | number): number {\n if (typeof sdkChainId === 'number') return sdkChainId;\n\n const lower = sdkChainId.toLowerCase();\n if (SDK_CHAIN_ID_MAP[lower] !== undefined) return SDK_CHAIN_ID_MAP[lower];\n\n if (lower.startsWith('0x')) {\n const parsed = parseInt(lower, 16);\n if (!isNaN(parsed)) return parsed;\n }\n\n const parsed = parseInt(sdkChainId, 10);\n if (!isNaN(parsed)) return parsed;\n\n throw new Error(`Unable to resolve chain ID: ${sdkChainId}`);\n}\n\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport function getViemChain(chainId: string | number): Chain {\n const numericId = resolveChainId(chainId);\n const chain = CHAIN_CONFIG[numericId];\n if (!chain) throw new Error(`Unsupported chain ID: ${chainId} (resolved to ${numericId})`);\n return chain;\n}\n\n/**\n * Get default RPC URL for a chain\n */\nexport function getDefaultRpcUrl(chainId: string | number): string {\n const numericId = resolveChainId(chainId);\n const rpcUrl = DEFAULT_RPC_URLS[numericId];\n if (!rpcUrl) throw new Error(`No default RPC URL for chain ID: ${chainId}`);\n return rpcUrl;\n}\n\n/**\n * Check if a chain is supported\n */\nexport function isChainSupported(chainId: string | number): boolean {\n try {\n getViemChain(chainId);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get all supported chain IDs\n */\nexport function getSupportedChainIds(): number[] {\n return Object.keys(CHAIN_CONFIG).map(Number);\n}\n\n/**\n * Get chain name\n */\nexport function getChainName(chainId: string | number): string {\n return getViemChain(chainId).name;\n}\n",
1093 "inputSchema": {},
1094 "outputSchema": null,
1095 "icons": null,
1096 "annotations": null,
1097 "meta": null,
1098 "execution": null
1099 },
1100 {
1101 "name": "BankrBackend.ts",
1102 "title": null,
1103 "description": "Script: BankrBackend.ts. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\n\nimport type { Hash, Address } from 'viem';\nimport type { \n IWalletBackend, \n BankrBackendConfig, \n TransactionRequest, \n TransactionReceipt \n} from './types';\nimport { resolveChainId } from './chainConfig';\n\n/**\n * Serialize error objects for readable error messages\n */\nfunction serializeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Bankr Agent API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ type?: string; [key: string]: unknown }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n createdAt: string;\n completedAt?: string;\n processingTime?: number;\n}\n\n/**\n * Chain ID to chain name mapping for Bankr prompts\n */\nconst CHAIN_NAMES: Record<number, string> = {\n 1: 'ethereum',\n 8453: 'base',\n 137: 'polygon',\n 42161: 'arbitrum',\n 10: 'optimism',\n 1890: 'lightlink',\n 146: 'sonic',\n};\n\n/**\n * Bankr execution backend\n * \n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport class BankrBackend implements IWalletBackend {\n readonly type = 'bankr' as const;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private readonly userAddress: Address;\n private readonly chainId: number;\n private readonly policy?: BankrBackendConfig['policy'];\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrBackendConfig) {\n this.apiUrl = config.bankrApiUrl || 'https://api.bankr.bot';\n this.apiKey = config.bankrApiKey;\n this.userAddress = config.userAddress;\n this.chainId = resolveChainId(config.chainId);\n this.policy = config.policy;\n \n console.log(`[BankrBackend] Initialized for chain ${this.chainId}`);\n console.log(`[BankrBackend] User address: ${this.userAddress}`);\n console.log(`[BankrBackend] API URL: ${this.apiUrl}`);\n }\n\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n async getAddress(): Promise<Address> {\n return this.userAddress;\n }\n\n /**\n * Send a transaction via Bankr Agent API\n * \n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n async sendTransaction(tx: TransactionRequest): Promise<Hash> {\n console.log(`[BankrBackend] Sending transaction via Bankr API`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Value: ${tx.value || 0n}`);\n console.log(`[BankrBackend] Data: ${tx.data ? tx.data.slice(0, 20) + '...' : '0x'}`);\n \n // Validate against policy\n if (this.policy) {\n await this.validatePolicy(tx);\n }\n\n // Format transaction as JSON for Bankr prompt\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data || '0x',\n value: (tx.value || 0n).toString(),\n chainId: this.chainId,\n });\n\n // Create natural language prompt for Bankr\n const prompt = `Submit this transaction: ${txJson}`;\n \n console.log(`[BankrBackend] Submitting prompt to Bankr API`);\n\n // Submit job to Bankr\n const jobId = await this.submitJob(prompt);\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n\n // Poll for completion\n const result = await this.pollJobUntilComplete(jobId);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n throw new Error(`[BankrBackend] Transaction failed: ${serializeError(result.response || result.error) || 'Unknown error'}`);\n }\n\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Wait for transaction confirmation\n * \n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n async waitForTransaction(txHash: Hash): Promise<TransactionReceipt> {\n console.log(`[BankrBackend] waitForTransaction called for: ${txHash}`);\n \n // Bankr transactions are confirmed when the job completes\n // We return a minimal receipt since we don't have full details\n return {\n transactionHash: txHash,\n blockNumber: 0n, // Unknown - would need to query chain\n blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000' as Hash,\n from: this.userAddress,\n to: null,\n gasUsed: 0n,\n status: 'success',\n logs: [],\n };\n }\n\n /**\n * Check if backend is ready\n * \n * Verifies Bankr API connectivity with a simple balance query.\n */\n async isReady(): Promise<boolean> {\n if (!this.apiUrl || !this.apiKey || !this.userAddress) {\n return false;\n }\n\n try {\n // Test API connectivity with a simple query\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n \n // Even a 4xx error means API is reachable\n return response.status !== 503 && response.status !== 502;\n } catch (error) {\n console.error('[BankrBackend] Connectivity check failed:', error);\n return false;\n }\n }\n\n /**\n * Get the chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n\n /**\n * Submit a job to Bankr Agent API\n */\n private async submitJob(prompt: string): Promise<string> {\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${response.status} ${error}`);\n }\n\n const data = await response.json() as BankrJobSubmitResponse;\n \n if (!data.success || !data.jobId) {\n throw new Error(`[BankrBackend] Invalid job submission response: ${JSON.stringify(data)}`);\n }\n\n return data.jobId;\n }\n\n /**\n * Poll for job completion\n */\n private async pollJobUntilComplete(jobId: string): Promise<BankrJobStatusResponse> {\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const result = await this.getJobStatus(jobId);\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId} status: ${result.status}`);\n lastStatus = result.status;\n }\n \n // Log status updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n\n // Check for terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${serializeError(result.error) || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n case 'pending':\n case 'processing':\n // Continue polling\n break;\n default:\n console.warn(`[BankrBackend] Unknown status: ${result.status}`);\n }\n }\n\n throw new Error(`[BankrBackend] Job ${jobId} timed out after ${this.maxPollAttempts * this.pollIntervalMs / 1000} seconds`);\n }\n\n /**\n * Get job status from Bankr API\n */\n private async getJobStatus(jobId: string): Promise<BankrJobStatusResponse> {\n const response = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${response.status} ${error}`);\n }\n\n return await response.json() as BankrJobStatusResponse;\n }\n\n /**\n * Extract transaction hash from Bankr response\n * \n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) {\n return item.transactionHash as Hash;\n }\n if (item.txHash) {\n return item.txHash as Hash;\n }\n if (item.hash) {\n return item.hash as Hash;\n }\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for hex transaction hash pattern (0x followed by 64 hex chars)\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) {\n return hashMatch[0] as Hash;\n }\n \n // Check if response indicates failure\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Validate transaction against policy\n */\n private async validatePolicy(tx: TransactionRequest): Promise<void> {\n if (!this.policy) return;\n\n // Check max value per transaction\n if (this.policy.maxValuePerTx && tx.value && tx.value > this.policy.maxValuePerTx) {\n throw new Error(\n `Transaction value ${tx.value} exceeds max allowed ${this.policy.maxValuePerTx}`\n );\n }\n\n // Check allowed contracts\n if (this.policy.allowedContracts && this.policy.allowedContracts.length > 0) {\n if (!this.policy.allowedContracts.includes(tx.to)) {\n throw new Error(\n `Contract ${tx.to} is not in the allowed contracts list`\n );\n }\n }\n }\n\n /**\n * Sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a BankrBackend from configuration\n */\nexport async function createBankrBackend(config: BankrBackendConfig): Promise<BankrBackend> {\n const backend = new BankrBackend(config);\n \n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[BankrBackend] Backend created but connectivity check failed');\n }\n \n return backend;\n}\n",
1104 "inputSchema": {},
1105 "outputSchema": null,
1106 "icons": null,
1107 "annotations": null,
1108 "meta": null,
1109 "execution": null
1110 },
1111 {
1112 "name": "types.ts",
1113 "title": null,
1114 "description": "Script: types.ts. Code:\n/**\n * Wallet Provider Types\n * \n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\n\nimport type { Hash, Address } from 'viem';\nimport { createPublicClient } from 'viem';\nimport type { \n EvmRawTransaction, \n EvmRawTransactionReceipt,\n IEvmWalletProvider \n} from '@sodax/types';\n\n/**\n * Wallet backend type identifiers\n */\nexport type WalletBackendType = 'localKey' | 'bankr' | 'privy' | 'smartWallet';\n\n/**\n * Base configuration for all backends\n */\nexport interface WalletBackendBaseConfig {\n type: WalletBackendType;\n chainId: string | number;\n rpcUrl?: string;\n}\n\n/**\n * Configuration for local private key backend\n */\nexport interface LocalKeyBackendConfig extends WalletBackendBaseConfig {\n type: 'localKey';\n privateKey: `0x${string}`;\n}\n\n/**\n * Configuration for Bankr execution backend\n */\nexport interface BankrBackendConfig extends WalletBackendBaseConfig {\n type: 'bankr';\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n /** Optional: policy limits for transactions */\n policy?: {\n maxValuePerTx?: bigint;\n maxDailyVolume?: bigint;\n allowedContracts?: Address[];\n };\n}\n\n/**\n * Configuration for Privy server wallet backend (future)\n */\nexport interface PrivyBackendConfig extends WalletBackendBaseConfig {\n type: 'privy';\n appId: string;\n appSecret: string;\n walletId: string;\n}\n\n/**\n * Configuration for smart contract wallet backend (future)\n */\nexport interface SmartWalletBackendConfig extends WalletBackendBaseConfig {\n type: 'smartWallet';\n walletAddress: Address;\n sessionKey: `0x${string}`;\n entryPointAddress: Address;\n}\n\n/**\n * Union of all backend configurations\n */\nexport type WalletBackendConfig = \n | LocalKeyBackendConfig \n | BankrBackendConfig\n | PrivyBackendConfig\n | SmartWalletBackendConfig;\n\n/**\n * Transaction request (simplified)\n */\nexport interface TransactionRequest {\n to: Address;\n value?: bigint;\n data?: `0x${string}`;\n gasLimit?: bigint;\n gasPrice?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n nonce?: number;\n}\n\n/**\n * Transaction receipt (simplified)\n */\nexport interface TransactionReceipt {\n transactionHash: Hash;\n blockNumber: bigint;\n blockHash: Hash;\n from: Address;\n to: Address | null;\n gasUsed: bigint;\n status: 'success' | 'reverted';\n logs: Array<{\n address: Address;\n topics: `0x${string}`[];\n data: `0x${string}`;\n }>;\n}\n\n/**\n * Wallet backend interface\n * \n * All wallet backends must implement this interface.\n * This allows AmpedWalletProvider to delegate to different backends\n * without changing its own implementation.\n */\nexport interface IWalletBackend {\n /** Backend type identifier */\n readonly type: WalletBackendType;\n \n /** Get the wallet address */\n getAddress(): Promise<Address>;\n \n /** \n * Send a transaction and return the transaction hash\n * The backend is responsible for signing and submitting the transaction.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n \n /**\n * Wait for a transaction to be confirmed\n * Returns when the transaction is included in a block.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n \n /**\n * Check if the backend can execute transactions\n * (e.g., Bankr backend may need API connectivity)\n */\n isReady(): Promise<boolean>;\n \n /**\n * Get the numeric chain ID this backend is configured for\n */\n getChainId(): number;\n}\n\n/**\n * Factory function type for creating backends\n */\nexport type WalletBackendFactory = (config: WalletBackendConfig) => Promise<IWalletBackend>;\n\n/**\n * Amped Wallet Provider configuration\n */\nexport interface AmpedWalletProviderConfig {\n /** Backend configuration */\n backend: WalletBackendConfig;\n /** Optional custom RPC URL (overrides chain default) */\n rpcUrl?: string;\n}\n\n/**\n * Extended IEvmWalletProvider with Amped-specific methods\n */\nexport interface IAmpedWalletProvider extends IEvmWalletProvider {\n /** Get the underlying backend */\n getBackend(): IWalletBackend;\n \n /** Get the backend type */\n getBackendType(): WalletBackendType;\n \n /** Check if ready for transactions */\n isReady(): Promise<boolean>;\n \n /** Get chain ID */\n getChainId(): number;\n}\n",
1115 "inputSchema": {},
1116 "outputSchema": null,
1117 "icons": null,
1118 "annotations": null,
1119 "meta": null,
1120 "execution": null
1121 },
1122 {
1123 "name": "skillWalletAdapter.ts",
1124 "title": null,
1125 "description": "Script: skillWalletAdapter.ts. Code:\n/**\n * EVM Wallet Skill Adapter\n * \n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n * \n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n * \n * @see https://github.com/surfer77/evm-wallet-skill\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ErrorCode, AmpedDefiError } from '../utils/errors';\nimport { normalizeChainId } from './types';\n\n// Try to import viem for address derivation\nlet privateKeyToAccount: ((key: `0x${string}`) => { address: string }) | null = null;\ntry {\n const viem = require('viem/accounts');\n privateKeyToAccount = viem.privateKeyToAccount;\n} catch {\n // viem not available, will use address from config\n}\n\n/**\n * FALLBACK RPC URLs - primary RPCs come from evm-wallet-skill\n * These are only used when evm-wallet-skill does not provide an RPC\n */\nconst FALLBACK_RPCS: Record<string, string> = {\n // SODAX supported spoke chains\n ethereum: 'https://ethereum.publicnode.com',\n arbitrum: 'https://arb1.arbitrum.io/rpc',\n base: 'https://mainnet.base.org',\n optimism: 'https://mainnet.optimism.io',\n avalanche: 'https://api.avax.network/ext/bc/C/rpc',\n bsc: 'https://bsc-dataseed.binance.org',\n polygon: 'https://polygon-bor-rpc.publicnode.com',\n // Sonic hub chain\n sonic: 'https://rpc.soniclabs.com',\n // Additional chains (may not be SODAX-supported but useful)\n lightlink: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n};\n\n/**\n * Wallet information from evm-wallet-skill\n */\nexport interface EvmWalletInfo {\n id: string;\n address: string;\n chainId?: number;\n provider?: 'privateKey' | 'kms' | 'hardware' | 'web3Auth';\n}\n\n/**\n * Wallet adapter options\n */\nexport interface WalletAdapterOptions {\n preferSkill?: boolean;\n walletId?: string;\n}\n\n/**\n * EVM Wallet Skill Adapter\n */\nexport class EvmWalletSkillAdapter {\n private skillWallets: Map<string, EvmWalletInfo> = new Map();\n private skillRpcs: Map<string, string> = new Map();\n private useSkill: boolean;\n\n constructor(options: WalletAdapterOptions = {}) {\n this.useSkill = options.preferSkill !== false;\n if (this.useSkill) {\n this.loadSkillConfig();\n }\n }\n\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n private loadSkillConfig(): void {\n // 1. Try ~/.evm-wallet.json first (evm-wallet-skill default location)\n this.loadEvmWalletFile();\n \n // 2. Try environment variables\n this.loadEnvWallets();\n \n // 3. Load RPC URLs from environment\n this.loadEnvRpcs();\n }\n\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n private loadEvmWalletFile(): void {\n try {\n const walletPath = path.join(os.homedir(), '.evm-wallet.json');\n \n if (!fs.existsSync(walletPath)) {\n return;\n }\n\n const content = fs.readFileSync(walletPath, 'utf-8');\n const walletData = JSON.parse(content);\n\n // evm-wallet-skill stores: { privateKey: \"0x...\" } or { privateKey: \"0x...\", address: \"0x...\" }\n if (walletData.privateKey) {\n let address = walletData.address;\n \n // Derive address from private key if not provided\n if (!address && privateKeyToAccount) {\n try {\n const account = privateKeyToAccount(walletData.privateKey as `0x${string}`);\n address = account.address;\n } catch (e) {\n console.warn('[walletAdapter] Failed to derive address from private key');\n }\n }\n\n if (address) {\n this.skillWallets.set('default', {\n id: 'default',\n address,\n provider: 'privateKey',\n });\n // Store private key for later use\n (this.skillWallets.get('default') as any).privateKey = walletData.privateKey;\n console.log(`[walletAdapter] Loaded wallet from ~/.evm-wallet.json (${address.slice(0, 8)}...)`);\n }\n }\n } catch (error) {\n // Silently ignore - file may not exist\n }\n }\n\n /**\n * Load wallets from environment variables\n */\n private loadEnvWallets(): void {\n try {\n const skillWalletsJson = process.env.EVM_WALLETS_JSON || process.env.WALLET_CONFIG_JSON;\n\n if (skillWalletsJson) {\n const wallets = JSON.parse(skillWalletsJson);\n \n if (Array.isArray(wallets)) {\n wallets.forEach(w => {\n this.skillWallets.set(w.id || w.name || 'default', {\n id: w.id || w.name || 'default',\n address: w.address,\n chainId: w.chainId,\n provider: w.provider || w.type,\n });\n });\n } else if (typeof wallets === 'object') {\n Object.entries(wallets).forEach(([id, config]: [string, any]) => {\n this.skillWallets.set(id, {\n id,\n address: config.address,\n chainId: config.chainId,\n provider: config.provider || 'privateKey',\n });\n });\n }\n\n console.log(`[walletAdapter] Loaded ${this.skillWallets.size} wallets from environment`);\n }\n } catch (error) {\n console.warn('[walletAdapter] Failed to parse wallet environment variables:', error);\n }\n }\n\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n private loadEnvRpcs(): void {\n // Start with default RPCs\n Object.entries(FALLBACK_RPCS).forEach(([chain, url]) => {\n this.skillRpcs.set(chain.toLowerCase(), url);\n });\n\n // Override with environment variables if provided\n try {\n const skillRpcsJson = process.env.AMPED_OC_RPC_URLS_JSON || \n process.env.EVM_RPC_URLS_JSON || \n process.env.RPC_URLS_JSON;\n\n if (skillRpcsJson) {\n const rpcs = JSON.parse(skillRpcsJson);\n Object.entries(rpcs).forEach(([chain, url]) => {\n this.skillRpcs.set(String(chain).toLowerCase(), url as string);\n });\n console.log(`[walletAdapter] Custom RPC URLs configured for: ${Object.keys(rpcs).join(', ')}`);\n }\n } catch (error) {\n console.warn('[walletAdapter] Failed to parse RPC environment variables:', error);\n }\n\n console.log(`[walletAdapter] ${this.skillRpcs.size} RPC URLs available (includes defaults)`);\n }\n\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n async getWalletAddress(walletId?: string): Promise<string> {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') || \n Array.from(this.skillWallets.values())[0];\n if (wallet) return wallet.address;\n }\n\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.address) return wallet.address;\n }\n\n throw new AmpedDefiError(\n ErrorCode.WALLET_NOT_FOUND,\n `Wallet not found: ${walletId || 'default'}`,\n { remediation: 'Configure ~/.evm-wallet.json, EVM_WALLETS_JSON, or AMPED_OC_WALLETS_JSON' }\n );\n }\n\n /**\n * Get wallet private key - for signing transactions\n */\n async getPrivateKey(walletId?: string): Promise<string | null> {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') || \n Array.from(this.skillWallets.values())[0];\n if (wallet && (wallet as any).privateKey) {\n return (wallet as any).privateKey;\n }\n }\n\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.privateKey) return wallet.privateKey;\n }\n\n return null;\n }\n\n /**\n * Get full wallet config (address + privateKey if available)\n */\n async getWalletConfig(walletId?: string): Promise<{ address: string; privateKey?: string }> {\n const address = await this.getWalletAddress(walletId);\n const privateKey = await this.getPrivateKey(walletId);\n return { address, privateKey: privateKey || undefined };\n }\n\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n async getRpcUrl(chainId: string | number): Promise<string> {\n const key = normalizeChainId(String(chainId)).toLowerCase();\n\n // Try skill RPCs\n if (this.skillRpcs.has(key)) {\n return this.skillRpcs.get(key)!;\n }\n\n // Fallback to AMPED_OC_RPC_URLS_JSON\n const legacy = process.env.AMPED_OC_RPC_URLS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n if (config[key] || config[chainId]) return config[key] || config[chainId];\n }\n\n throw new AmpedDefiError(\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n `RPC URL not configured for chain: ${chainId}`,\n { remediation: 'Configure EVM_RPC_URLS_JSON or AMPED_OC_RPC_URLS_JSON' }\n );\n }\n\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets(): boolean {\n return this.skillWallets.size > 0;\n }\n\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs(): boolean {\n return this.skillRpcs.size > 0;\n }\n\n /**\n * Get all skill wallet IDs\n */\n getWalletIds(): string[] {\n return Array.from(this.skillWallets.keys());\n }\n\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds(): string[] {\n return Array.from(this.skillRpcs.keys());\n }\n}\n\n// Singleton\nlet adapter: EvmWalletSkillAdapter | null = null;\n\nexport function getWalletAdapter(options?: WalletAdapterOptions): EvmWalletSkillAdapter {\n if (!adapter) {\n adapter = new EvmWalletSkillAdapter(options);\n }\n return adapter;\n}\n\nexport function resetWalletAdapter(): void {\n adapter = null;\n}\n",
1126 "inputSchema": {},
1127 "outputSchema": null,
1128 "icons": null,
1129 "annotations": null,
1130 "meta": null,
1131 "execution": null
1132 },
1133 {
1134 "name": "types.ts",
1135 "title": null,
1136 "description": "Script: types.ts. Code:\n/**\n * Wallet Types - Multi-source wallet management\n * \n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\n\nimport type { Address, Hash } from 'viem';\n\n/**\n * Supported wallet backend types\n */\nexport type WalletBackendType = 'evm-wallet-skill' | 'bankr' | 'env';\n\n/**\n * Raw transaction for Bankr submission\n */\nexport interface RawTransaction {\n to: Address;\n data: `0x${string}`;\n value: string; // Wei as string\n chainId: number;\n}\n\n/**\n * Wallet info returned by list operations\n */\nexport interface WalletInfo {\n nickname: string;\n type: WalletBackendType;\n address: Address;\n chains: string[];\n isDefault: boolean;\n /** Solana address (if wallet has one, e.g., Bankr) */\n solanaAddress?: string;\n}\n\n/**\n * Wallet backend interface\n * Different implementations for different sources\n */\nexport interface IWalletBackend {\n readonly type: WalletBackendType;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n \n /**\n * Check if this wallet supports a specific chain\n */\n supportsChain(chainId: string): boolean;\n \n /**\n * Get private key (for local/env wallets)\n * Returns undefined for Bankr (no local key access)\n */\n getPrivateKey?(): Promise<`0x${string}`>;\n \n /**\n * Send raw transaction via Bankr API\n * Only available for Bankr backend\n */\n sendRawTransaction?(tx: RawTransaction): Promise<Hash>;\n \n /**\n * Check if backend is ready/configured\n */\n isReady(): Promise<boolean>;\n}\n\n/**\n * Wallet configuration from wallets.json\n */\nexport interface WalletConfig {\n source: WalletBackendType;\n \n // For evm-wallet-skill\n path?: string;\n \n // For Bankr\n apiKey?: string;\n apiUrl?: string;\n \n // For env\n envVar?: string;\n address?: Address;\n privateKey?: `0x${string}`;\n \n // Chain restrictions (optional)\n chains?: string[];\n}\n\n/**\n * Wallets config file structure\n */\nexport interface WalletsConfigFile {\n wallets: Record<string, WalletConfig>;\n default?: string;\n}\n\n/**\n * Chain IDs for Bankr submission\n */\nexport const BANKR_CHAIN_IDS: Record<string, number> = {\n ethereum: 1,\n polygon: 137,\n base: 8453,\n unichain: 130,\n};\n\n/**\n * Chains supported by Bankr\n */\nexport const BANKR_SUPPORTED_CHAINS = ['ethereum', 'polygon', 'base'] as const;\n\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport const SODAX_SUPPORTED_CHAINS = [\n 'ethereum',\n 'base', \n 'polygon',\n 'arbitrum',\n 'optimism',\n 'sonic',\n 'avalanche',\n 'bsc',\n 'lightlink',\n 'hyper',\n 'kaia',\n] as const;\n\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport const SODAX_TO_SIMPLE_CHAIN: Record<string, string> = {\n // SODAX format -> simple\n '0x2105.base': 'base',\n '0x89.polygon': 'polygon',\n '0xa4b1.arbitrum': 'arbitrum',\n '0xa.optimism': 'optimism',\n '0x38.bsc': 'bsc',\n '0xa86a.avax': 'avalanche',\n '0x2019.kaia': 'kaia',\n // These don't have prefixes in SODAX\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyper': 'hyperevm',\n 'kaia': 'kaia',\n};\n\n/**\n * Simple to SODAX chain ID mapping\n */\nexport const SIMPLE_TO_SODAX_CHAIN: Record<string, string> = {\n // Simple -> SODAX format\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'kaia': '0x2019.kaia',\n // No prefix needed\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport function normalizeChainId(chainId: string): string {\n // Already in mapping\n if (SODAX_TO_SIMPLE_CHAIN[chainId]) {\n return SODAX_TO_SIMPLE_CHAIN[chainId];\n }\n \n // Check if it's already simple format\n if (SIMPLE_TO_SODAX_CHAIN[chainId]) {\n return chainId;\n }\n \n // Try to extract from prefixed format (0xNNN.name -> name)\n const match = chainId.match(/^0x[a-fA-F0-9]+\\.(.+)$/);\n if (match) {\n return match[1];\n }\n \n // Return as-is\n return chainId;\n}\n\n/**\n * Convert simple chain ID to SODAX format\n */\nexport function toSodaxChainId(chainId: string): string {\n // Already in SODAX format\n if (chainId.startsWith('0x') && chainId.includes('.')) {\n return chainId;\n }\n \n return SIMPLE_TO_SODAX_CHAIN[chainId] || chainId;\n}\n\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport function isSameChain(chainId1: string, chainId2: string): boolean {\n return normalizeChainId(chainId1) === normalizeChainId(chainId2);\n}\n\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function isBankrSupportedChain(chainId: string): boolean {\n const normalized = normalizeChainId(chainId);\n return BANKR_SUPPORTED_CHAINS.includes(normalized as any);\n}\n\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function getBankrChainId(chainId: string): number {\n const normalized = normalizeChainId(chainId);\n const id = BANKR_CHAIN_IDS[normalized];\n if (!id) {\n throw new Error(`Chain ${chainId} (normalized: ${normalized}) not supported by Bankr. Supported: ${BANKR_SUPPORTED_CHAINS.join(', ')}`);\n }\n return id;\n}\n",
1137 "inputSchema": {},
1138 "outputSchema": null,
1139 "icons": null,
1140 "annotations": null,
1141 "meta": null,
1142 "execution": null
1143 },
1144 {
1145 "name": "backendConfig.ts",
1146 "title": null,
1147 "description": "Script: backendConfig.ts. Code:\n/**\n * Wallet Backend Configuration\n * \n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n * \n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { WalletBackendType } from './providers';\n\nexport interface BackendConfig {\n backend: WalletBackendType;\n bankrApiKey?: string;\n bankrApiUrl?: string;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: BackendConfig = {\n backend: 'localKey',\n};\n\n/**\n * Path to plugin config file\n */\nfunction getConfigPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'config.json');\n}\n\n/**\n * Load configuration from file\n */\nfunction loadConfigFile(): Partial<BackendConfig> | null {\n const configPath = getConfigPath();\n \n if (!existsSync(configPath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(content);\n return {\n backend: config.walletBackend as WalletBackendType,\n bankrApiKey: config.bankrApiKey,\n bankrApiUrl: config.bankrApiUrl,\n };\n } catch (error) {\n console.warn('[backendConfig] Failed to load config file:', error);\n return null;\n }\n}\n\n/**\n * Load configuration from environment variables\n */\nfunction loadEnvConfig(): Partial<BackendConfig> {\n const config: Partial<BackendConfig> = {};\n\n // Check for explicit backend selection\n const backendEnv = process.env.AMPED_OC_WALLET_BACKEND;\n if (backendEnv === 'bankr' || backendEnv === 'localKey') {\n config.backend = backendEnv;\n }\n\n // Check for Bankr API key (implies bankr backend)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (bankrApiKey) {\n config.bankrApiKey = bankrApiKey;\n // Auto-select bankr backend if API key is present\n if (!config.backend) {\n config.backend = 'bankr';\n }\n }\n\n // Optional Bankr API URL override\n const bankrApiUrl = process.env.BANKR_API_URL;\n if (bankrApiUrl) {\n config.bankrApiUrl = bankrApiUrl;\n }\n\n return config;\n}\n\n/**\n * Get the resolved backend configuration\n * \n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport function getBackendConfig(): BackendConfig {\n const fileConfig = loadConfigFile() || {};\n const envConfig = loadEnvConfig();\n\n // Merge with priority: env > file > default\n const config: BackendConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n ...envConfig,\n };\n\n // Validate bankr configuration\n if (config.backend === 'bankr' && !config.bankrApiKey) {\n console.warn('[backendConfig] Bankr backend selected but no API key provided');\n console.warn('[backendConfig] Set BANKR_API_KEY environment variable or add bankrApiKey to config.json');\n console.warn('[backendConfig] Falling back to localKey backend');\n config.backend = 'localKey';\n }\n\n // Set default Bankr API URL if not specified\n if (config.backend === 'bankr' && !config.bankrApiUrl) {\n config.bankrApiUrl = 'https://api.bankr.bot';\n }\n\n console.log(`[backendConfig] Using wallet backend: ${config.backend}`);\n\n return config;\n}\n\n/**\n * Check if Bankr backend is configured and available\n */\nexport function isBankrConfigured(): boolean {\n const config = getBackendConfig();\n return config.backend === 'bankr' && !!config.bankrApiKey;\n}\n\n/**\n * Get Bankr configuration if available\n */\nexport function getBankrConfig(): { apiKey: string; apiUrl: string } | null {\n const config = getBackendConfig();\n \n if (config.backend !== 'bankr' || !config.bankrApiKey) {\n return null;\n }\n\n return {\n apiKey: config.bankrApiKey,\n apiUrl: config.bankrApiUrl || 'https://api.bankr.bot',\n };\n}\n",
1148 "inputSchema": {},
1149 "outputSchema": null,
1150 "icons": null,
1151 "annotations": null,
1152 "meta": null,
1153 "execution": null
1154 },
1155 {
1156 "name": "index.ts",
1157 "title": null,
1158 "description": "Script: index.ts. Code:\n/**\n * Amped DeFi Plugin\n * \n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\n\nimport { Type, TSchema } from '@sinclair/typebox';\n\n/**\n * OpenClaw Plugin API (defined locally to avoid SDK dependency)\n */\ninterface OpenClawPluginApi {\n pluginConfig: Record<string, unknown>;\n logger: {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n debug: (msg: string) => void;\n };\n registerTool: (tool: {\n name: string;\n description: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: unknown) => Promise<{\n content: Array<{ type: 'text'; text: string }>;\n details?: unknown;\n }>;\n }) => void;\n registerService: (service: {\n id: string;\n start: () => void;\n stop: () => Promise<void> | void;\n }) => void;\n on: (event: string, handler: (event: unknown, ctx: unknown) => unknown) => void;\n}\nimport { getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nimport { getCacheStats } from './providers/spokeProviderFactory';\nimport { PolicyEngine } from './policy/policyEngine';\nimport { getWalletManager } from './wallet/walletManager';\nimport { getWalletRegistry } from './wallet/walletRegistry';\n\n// Tool schemas and handlers\nimport { \n SwapQuoteSchema, SwapExecuteSchema, SwapStatusSchema, SwapCancelSchema,\n handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel\n} from './tools/swap';\nimport {\n BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema,\n handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute\n} from './tools/bridge';\nimport {\n MmSupplySchema, MmWithdrawSchema, MmBorrowSchema, MmRepaySchema,\n handleMmSupply, handleMmWithdraw, handleMmBorrow, handleMmRepay\n} from './tools/moneyMarket';\nimport {\n SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema,\n MoneyMarketReservesSchema, MoneyMarketPositionsSchema, CrossChainPositionsSchema,\n UserIntentsSchema, ListWalletsSchema,\n handleSupportedChains, handleSupportedTokens, handleWalletAddress,\n handleMoneyMarketReserves, handleMoneyMarketPositions, handleCrossChainPositions,\n handleUserIntents, handleListWallets\n} from './tools/discovery';\nimport {\n AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema,\n handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet\n} from './tools/walletManagement';\nimport {\n PortfolioSummarySchema,\n handlePortfolioSummary\n} from './tools/portfolio';\n\n/**\n * Plugin configuration schema (matches openclaw.plugin.json)\n */\nconst configSchema = Type.Object({\n walletsJson: Type.Optional(Type.String()),\n rpcUrlsJson: Type.Optional(Type.String()),\n mode: Type.Optional(Type.Union([Type.Literal('execute'), Type.Literal('simulate')])),\n dynamicConfig: Type.Optional(Type.Boolean()),\n});\n\n/**\n * Apply plugin config to environment\n */\nfunction applyConfig(config: Record<string, unknown>): void {\n if (config.walletsJson && typeof config.walletsJson === 'string') {\n process.env.AMPED_OC_WALLETS_JSON = config.walletsJson;\n }\n if (config.rpcUrlsJson && typeof config.rpcUrlsJson === 'string') {\n process.env.AMPED_OC_RPC_URLS_JSON = config.rpcUrlsJson;\n }\n if (config.mode && typeof config.mode === 'string') {\n process.env.AMPED_OC_MODE = config.mode;\n }\n if (config.dynamicConfig !== undefined) {\n process.env.AMPED_OC_SODAX_DYNAMIC_CONFIG = config.dynamicConfig ? 'true' : 'false';\n }\n}\n\n/**\n * Validate required environment variables\n */\nfunction validateEnvironment(): string[] {\n const missing: string[] = [];\n \n if (!process.env.AMPED_OC_WALLETS_JSON) {\n missing.push('AMPED_OC_WALLETS_JSON');\n }\n \n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (mode === 'execute' && !process.env.AMPED_OC_RPC_URLS_JSON) {\n missing.push('AMPED_OC_RPC_URLS_JSON');\n }\n \n return missing;\n}\n\n\n/**\n * Deep-clone an object while converting BigInt values to strings\n * Prevents serialization errors when OpenClaw framework handles the details field\n */\nfunction sanitizeBigInt(obj: unknown): unknown {\n return JSON.parse(JSON.stringify(obj, (_, v) => \n typeof v === 'bigint' ? v.toString() : v\n ));\n}\n\n/**\n * Helper to wrap a handler for OpenClaw's tool format\n */\nfunction wrapHandler(handler: (params: unknown) => Promise<unknown>) {\n return async (_toolCallId: string, params: unknown) => {\n const result = await handler(params);\n // Sanitize BigInt values in details to prevent framework serialization errors\n const sanitizedResult = sanitizeBigInt(result);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }],\n details: sanitizedResult,\n };\n };\n}\n\n/**\n * OpenClaw Plugin Definition\n */\nexport default {\n id: 'amped-defi',\n name: 'Amped DeFi',\n description: 'DeFi operations plugin for swaps, bridging, and money market via SODAX SDK',\n kind: 'tools' as const,\n configSchema,\n\n register(api: OpenClawPluginApi) {\n // Apply config from OpenClaw\n const config = api.pluginConfig as Record<string, unknown> || {};\n applyConfig(config);\n \n // Check for missing env vars (silent)\n validateEnvironment();\n\n // Initialize core components (async, non-blocking, silent)\n (async () => {\n try {\n await getSodaxClientAsync();\n getCacheStats();\n new PolicyEngine();\n getWalletManager();\n } catch (_error) {\n // Silent initialization - errors will surface when tools are used\n }\n })();\n\n // Register Discovery Tools\n api.registerTool({\n name: 'amped_supported_chains',\n description: 'List all blockchain networks supported by the Amped DeFi plugin',\n parameters: SupportedChainsSchema,\n execute: wrapHandler(handleSupportedChains),\n });\n\n api.registerTool({\n name: 'amped_supported_tokens',\n description: 'List tokens supported on a specific chain for swaps and bridging',\n parameters: SupportedTokensSchema,\n execute: wrapHandler(handleSupportedTokens),\n });\n\n api.registerTool({\n name: 'amped_wallet_address',\n description: 'Get the wallet address for a specific wallet ID',\n parameters: WalletAddressSchema,\n execute: wrapHandler(handleWalletAddress),\n });\n\n api.registerTool({\n name: 'amped_money_market_reserves',\n description: 'Get money market reserve info (APY, utilization, liquidity)',\n parameters: MoneyMarketReservesSchema,\n execute: wrapHandler(handleMoneyMarketReserves),\n });\n\n api.registerTool({\n name: 'amped_money_market_positions',\n description: 'Get user positions in money market on a single chain',\n parameters: MoneyMarketPositionsSchema,\n execute: wrapHandler(handleMoneyMarketPositions),\n });\n\n api.registerTool({\n name: 'amped_cross_chain_positions',\n description: 'Get aggregated money market positions across all chains',\n parameters: CrossChainPositionsSchema,\n execute: wrapHandler(handleCrossChainPositions),\n });\n\n api.registerTool({\n name: 'amped_user_intents',\n description: 'Query user intent history from SODAX API',\n parameters: UserIntentsSchema,\n execute: wrapHandler(handleUserIntents),\n });\n\n api.registerTool({\n name: 'amped_list_wallets',\n description: 'List ALL configured wallets including evm-wallet-skill, Bankr, and env wallets. Shows nicknames, addresses, types, and supported chains. Use this when user asks \"what wallets do I have\" or \"show my wallets\".',\n parameters: ListWalletsSchema,\n execute: wrapHandler(handleListWallets),\n });\n\n api.registerTool({\n name: 'amped_portfolio_summary',\n description: 'Get a comprehensive portfolio summary including wallet balances (native + major tokens) across chains and money market positions. Use when user asks for \"portfolio\", \"balances\", or \"summary of positions\".',\n parameters: PortfolioSummarySchema,\n execute: wrapHandler(handlePortfolioSummary),\n });\n\n // Register Wallet Management Tools\n api.registerTool({\n name: 'amped_add_wallet',\n description: 'Add a new wallet with a nickname (evm-wallet-skill, bankr, or env)',\n parameters: AddWalletSchema,\n execute: wrapHandler(handleAddWallet),\n });\n\n api.registerTool({\n name: 'amped_rename_wallet',\n description: 'Rename a wallet to a new nickname',\n parameters: RenameWalletSchema,\n execute: wrapHandler(handleRenameWallet),\n });\n\n api.registerTool({\n name: 'amped_remove_wallet',\n description: 'Remove a wallet from configuration (does not delete funds)',\n parameters: RemoveWalletSchema,\n execute: wrapHandler(handleRemoveWallet),\n });\n\n api.registerTool({\n name: 'amped_set_default_wallet',\n description: 'Set which wallet to use by default for operations',\n parameters: SetDefaultWalletSchema,\n execute: wrapHandler(handleSetDefaultWallet),\n });\n\n // Register Swap Tools\n api.registerTool({\n name: 'amped_swap_quote',\n description: 'Get a quote for swapping tokens (same chain or cross-chain)',\n parameters: SwapQuoteSchema,\n execute: wrapHandler(handleSwapQuote),\n });\n\n api.registerTool({\n name: 'amped_swap_execute',\n description: 'Execute a token swap using a previously obtained quote',\n parameters: SwapExecuteSchema,\n execute: wrapHandler(handleSwapExecute),\n });\n\n api.registerTool({\n name: 'amped_swap_status',\n description: 'Check the status of a swap/bridge operation by intent ID',\n parameters: SwapStatusSchema,\n execute: wrapHandler(handleSwapStatus),\n });\n\n api.registerTool({\n name: 'amped_swap_cancel',\n description: 'Cancel a pending swap/bridge operation',\n parameters: SwapCancelSchema,\n execute: wrapHandler(handleSwapCancel),\n });\n\n // Register Bridge Tools\n api.registerTool({\n name: 'amped_bridge_discover',\n description: 'Discover available bridge routes between chains',\n parameters: BridgeDiscoverSchema,\n execute: wrapHandler(handleBridgeDiscover),\n });\n\n api.registerTool({\n name: 'amped_bridge_quote',\n description: 'Get a quote for bridging tokens between chains',\n parameters: BridgeQuoteSchema,\n execute: wrapHandler(handleBridgeQuote),\n });\n\n api.registerTool({\n name: 'amped_bridge_execute',\n description: 'Execute a bridge transfer using a previously obtained quote',\n parameters: BridgeExecuteSchema,\n execute: wrapHandler(handleBridgeExecute),\n });\n\n // Register Money Market Tools\n api.registerTool({\n name: 'amped_mm_supply',\n description: 'Supply (deposit) tokens to money market to earn interest',\n parameters: MmSupplySchema,\n execute: wrapHandler(handleMmSupply),\n });\n\n api.registerTool({\n name: 'amped_mm_withdraw',\n description: 'Withdraw supplied tokens from money market',\n parameters: MmWithdrawSchema,\n execute: wrapHandler(handleMmWithdraw),\n });\n\n api.registerTool({\n name: 'amped_mm_borrow',\n description: 'Borrow tokens from money market (cross-chain capable)',\n parameters: MmBorrowSchema,\n execute: wrapHandler(handleMmBorrow),\n });\n\n api.registerTool({\n name: 'amped_mm_repay',\n description: 'Repay borrowed tokens to money market',\n parameters: MmRepaySchema,\n execute: wrapHandler(handleMmRepay),\n });\n\n // Register cleanup service\n api.registerService({\n id: 'amped-defi',\n start: () => {},\n stop: async () => {\n resetSodaxClient();\n },\n });\n },\n};\n\n// Re-export types and utilities for external use\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport type { SpokeProvider } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\nexport type { IWalletBackend, WalletInfo, WalletBackendType } from './wallet/types';\n\n// Legacy exports for backward compatibility\nexport async function activate() {\n // Deprecated - use default export\n}\nexport async function deactivate() {\n resetSodaxClient();\n}\n",
1159 "inputSchema": {},
1160 "outputSchema": null,
1161 "icons": null,
1162 "annotations": null,
1163 "meta": null,
1164 "execution": null
1165 },
1166 {
1167 "name": "client.ts",
1168 "title": null,
1169 "description": "Script: client.ts. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\n\nimport { Sodax } from \"@sodax/sdk\";\n\n// Singleton instance\nlet sodaxClient: Sodax | null = null;\n\n/**\n * HARDCODED PARTNER CONFIGURATION\n * These values are baked in and cannot be overridden.\n * \n * Fee is 0.2% (20 basis points)\n * SDK expects: percentage in bps where 100 = 1%, so 20 = 0.2%\n */\nconst PARTNER_FEE = {\n address: \"0xd99C871c8130B03C8BB597A74fb5EAA7a46864Bb\" as `0x${string}`,\n percentage: 20, // 20 bps = 0.2%\n};\n\n/**\n * Initialize the SODAX SDK client\n * Always uses dynamic config to fetch live token lists and routes\n */\nasync function initializeSodax(): Promise<Sodax> {\n // Initialize SODAX with hardcoded partner fee on ALL services\n const sodax = new Sodax({\n swaps: { partnerFee: PARTNER_FEE },\n moneyMarket: { partnerFee: PARTNER_FEE },\n bridge: { partnerFee: PARTNER_FEE },\n } as any);\n\n // Suppress SDK console output during initialization\n const originalWarn = console.warn;\n const originalLog = console.log;\n console.warn = () => {};\n console.log = () => {};\n \n try {\n // Initialize with dynamic config\n await sodax.initialize();\n } finally {\n // Restore console\n console.warn = originalWarn;\n console.log = originalLog;\n }\n\n return sodax;\n}\n\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport async function getSodaxClientAsync(): Promise<Sodax> {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n return sodaxClient;\n}\n\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport function getSodaxClient(): Sodax {\n if (!sodaxClient) {\n throw new Error(\n \"SODAX client not initialized. Call getSodaxClientAsync() first.\",\n );\n }\n return sodaxClient;\n}\n\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport async function preInitializeSodax(): Promise<void> {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n}\n\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport function resetSodaxClient(): void {\n sodaxClient = null;\n}\n\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport class SodaxClient {\n private static instance: Sodax | null = null;\n\n static async getClient(): Promise<Sodax> {\n if (!SodaxClient.instance) {\n SodaxClient.instance = await initializeSodax();\n sodaxClient = SodaxClient.instance;\n }\n return SodaxClient.instance;\n }\n\n static reset(): void {\n SodaxClient.instance = null;\n sodaxClient = null;\n }\n}\n",
1170 "inputSchema": {},
1171 "outputSchema": null,
1172 "icons": null,
1173 "annotations": null,
1174 "meta": null,
1175 "execution": null
1176 },
1177 {
1178 "name": "sdk.ts",
1179 "title": null,
1180 "description": "Script: sdk.ts. Code:\n/**\n * Mock for @sodax/sdk\n */\n\nexport class Sodax {\n static initialize = jest.fn().mockResolvedValue(undefined);\n \n swaps = {\n getQuote: jest.fn(),\n executeSwap: jest.fn(),\n cancelIntent: jest.fn(),\n };\n \n bridge = {\n getBridgeableTokens: jest.fn(),\n bridge: jest.fn(),\n };\n \n moneyMarket = {\n supply: jest.fn(),\n withdraw: jest.fn(),\n borrow: jest.fn(),\n repay: jest.fn(),\n getUserAccountDataOnSpoke: jest.fn(),\n };\n}\n\nexport const Intent = {};\n",
1181 "inputSchema": {},
1182 "outputSchema": null,
1183 "icons": null,
1184 "annotations": null,
1185 "meta": null,
1186 "execution": null
1187 },
1188 {
1189 "name": "portfolio.ts",
1190 "title": null,
1191 "description": "Script: portfolio.ts. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { createPublicClient, http, formatUnits, type PublicClient, type Address } from 'viem';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus } from '../utils/positionAggregator';\nimport { fetchTokenPrices, getTokenPriceBySymbol } from '../utils/priceService';\nimport { \n getViemChain, \n getDefaultRpcUrl, \n resolveChainId,\n CHAIN_IDS,\n} from '../wallet/providers/chainConfig';\n\n// ============================================================================\n// TypeBox Schema\n// ============================================================================\n\n/**\n * Schema for amped_portfolio_summary\n */\nexport const PortfolioSummarySchema = Type.Object({\n walletId: Type.Optional(Type.String({\n description: 'Specific wallet to query (defaults to all wallets)',\n })),\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Specific chains to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include tokens with zero balance',\n default: false,\n })),\n});\n\ntype PortfolioSummaryParams = Static<typeof PortfolioSummarySchema>;\n\n// ============================================================================\n// Token Configuration\n// ============================================================================\n\ninterface TokenConfig {\n address: string;\n symbol: string;\n decimals: number;\n}\n\n/**\n * Major tokens to check on each chain\n */\nconst MAJOR_TOKENS: Record<number, TokenConfig[]> = {\n [CHAIN_IDS.ETHEREUM]: [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 },\n { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BASE]: [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.ARBITRUM]: [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', decimals: 6 },\n { address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.OPTIMISM]: [\n { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', symbol: 'USDC', decimals: 6 },\n { address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', symbol: 'USDT', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.POLYGON]: [\n { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', decimals: 6 },\n { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 },\n { address: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BSC]: [\n { address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', symbol: 'USDC', decimals: 18 },\n { address: '0x55d398326f99059fF775485246999027B3197955', symbol: 'USDT', decimals: 18 },\n { address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.AVALANCHE]: [\n { address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', symbol: 'USDC', decimals: 6 },\n { address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', symbol: 'USDT', decimals: 6 },\n { address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.SONIC]: [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', decimals: 6 },\n ],\n [CHAIN_IDS.LIGHTLINK]: [\n { address: '0xbCF8C1B03bBDDA88D579330BDF236B58F8bb2cFd', symbol: 'USDC', decimals: 6 },\n ],\n};\n\n/**\n * Native token symbols by chain\n */\nconst NATIVE_SYMBOLS: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'ETH',\n [CHAIN_IDS.ARBITRUM]: 'ETH',\n [CHAIN_IDS.OPTIMISM]: 'ETH',\n [CHAIN_IDS.BASE]: 'ETH',\n [CHAIN_IDS.POLYGON]: 'POL',\n [CHAIN_IDS.BSC]: 'BNB',\n [CHAIN_IDS.AVALANCHE]: 'AVAX',\n [CHAIN_IDS.SONIC]: 'S',\n [CHAIN_IDS.LIGHTLINK]: 'ETH',\n [CHAIN_IDS.HYPEREVM]: 'HYPE',\n [CHAIN_IDS.KAIA]: 'KAIA',\n};\n\n/**\n * Chain ID to name mapping\n */\nconst CHAIN_NAMES: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'Ethereum',\n [CHAIN_IDS.ARBITRUM]: 'Arbitrum',\n [CHAIN_IDS.OPTIMISM]: 'Optimism',\n [CHAIN_IDS.BASE]: 'Base',\n [CHAIN_IDS.POLYGON]: 'Polygon',\n [CHAIN_IDS.BSC]: 'BSC',\n [CHAIN_IDS.AVALANCHE]: 'Avalanche',\n [CHAIN_IDS.SONIC]: 'Sonic',\n [CHAIN_IDS.LIGHTLINK]: 'LightLink',\n [CHAIN_IDS.HYPEREVM]: 'HyperEVM',\n [CHAIN_IDS.KAIA]: 'Kaia',\n};\n\n/**\n * Chain name strings for wallet support check\n */\nconst CHAIN_NAME_STRINGS: Record<number, string[]> = {\n [CHAIN_IDS.ETHEREUM]: ['ethereum'],\n [CHAIN_IDS.BASE]: ['base'],\n [CHAIN_IDS.ARBITRUM]: ['arbitrum'],\n [CHAIN_IDS.OPTIMISM]: ['optimism'],\n [CHAIN_IDS.POLYGON]: ['polygon'],\n [CHAIN_IDS.BSC]: ['bsc'],\n [CHAIN_IDS.AVALANCHE]: ['avalanche', 'avax'],\n [CHAIN_IDS.SONIC]: ['sonic'],\n [CHAIN_IDS.LIGHTLINK]: ['lightlink'],\n};\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Create a viem public client for a chain\n */\nfunction createClient(chainId: number): PublicClient {\n const chain = getViemChain(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n return createPublicClient({\n chain,\n transport: http(rpcUrl, { timeout: 10000 }),\n }) as PublicClient;\n}\n\n/**\n * Get native balance for a wallet on a chain\n */\nasync function getNativeBalance(\n client: PublicClient,\n address: Address,\n chainId: number\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint }> {\n try {\n const balance = await client.getBalance({ address });\n return {\n symbol: NATIVE_SYMBOLS[chainId] || 'ETH',\n balance: formatUnits(balance, 18),\n balanceRaw: balance,\n };\n } catch (error) {\n console.error(`[portfolio] Failed to get native balance on chain ${chainId}:`, error);\n return { symbol: NATIVE_SYMBOLS[chainId] || 'ETH', balance: '0', balanceRaw: 0n };\n }\n}\n\n/**\n * Get ERC20 token balance using eth_call directly (avoids viem type issues)\n */\nasync function getTokenBalance(\n rpcUrl: string,\n walletAddress: string,\n tokenAddress: string,\n decimals: number,\n symbol: string\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint; address: string }> {\n try {\n // balanceOf(address) selector: 0x70a08231\n const paddedAddress = walletAddress.slice(2).toLowerCase().padStart(64, '0');\n const callData = `0x70a08231${paddedAddress}`;\n\n const response = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to: tokenAddress, data: callData }, 'latest'],\n id: 1,\n }),\n });\n\n const json = await response.json() as { result?: string };\n const result = json.result;\n\n if (!result || result === '0x' || result === '0x0') {\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n\n const balanceRaw = BigInt(result);\n const balance = formatUnits(balanceRaw, decimals);\n\n return { symbol, balance, balanceRaw, address: tokenAddress };\n } catch (error) {\n console.error(`[portfolio] Failed to get ${symbol} balance:`, error);\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n}\n\n/**\n * Query all balances for a wallet on a specific chain\n */\nasync function getChainBalances(\n address: Address,\n chainId: number,\n includeZeroBalances: boolean\n): Promise<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>; chainTotalUsd?: string;\n}> {\n const client = createClient(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n const chainName = CHAIN_NAMES[chainId] || `Chain ${chainId}`;\n\n // Get native balance\n const native = await getNativeBalance(client, address, chainId);\n\n // Get token balances\n const tokenConfigs = MAJOR_TOKENS[chainId] || [];\n const tokenPromises = tokenConfigs.map((t) =>\n getTokenBalance(rpcUrl, address, t.address, t.decimals, t.symbol)\n );\n const tokenResults = await Promise.all(tokenPromises);\n\n // Filter zero balances if requested\n const tokens = includeZeroBalances\n ? tokenResults.map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }))\n : tokenResults\n .filter((t) => t.balanceRaw > 0n)\n .map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }));\n\n return {\n chainId: chainId.toString(),\n chainName,\n native: {\n symbol: native.symbol,\n balance: parseFloat(native.balance).toFixed(6),\n },\n tokens,\n };\n}\n\n// ============================================================================\n// Main Handler\n// ============================================================================\n\ninterface WalletBalanceResult {\n wallet: {\n nickname: string;\n address: string;\n type: string;\n };\n balances: Array<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>;\n chainTotalUsd?: string;\n }>;\n moneyMarket?: {\n totalSupplyUsd: string;\n totalBorrowUsd: string;\n netWorthUsd: string;\n healthFactor: string;\n healthStatus: { status: string; color: string };\n /** Per-chain breakdown - CRITICAL: each chain has independent health factor */\n chainBreakdown: Array<{\n chainId: string;\n supplyUsd: string;\n borrowUsd: string;\n healthFactor: string;\n healthStatus: { status: string; color: string };\n }>;\n };\n walletTotalUsd?: string;\n}\n\n/**\n * Handle portfolio summary request\n */\nexport async function handlePortfolioSummary(\n params: PortfolioSummaryParams\n): Promise<unknown> {\n const { walletId, chains, includeZeroBalances = false } = params;\n\n console.log('[portfolio:summary] Fetching portfolio summary', {\n walletId: walletId || 'all',\n chains: chains || 'all',\n includeZeroBalances,\n });\n\n // Fetch token prices from SODAX (cached, 1 min TTL)\n let priceMap: Awaited<ReturnType<typeof fetchTokenPrices>> | null = null;\n try {\n priceMap = await fetchTokenPrices();\n } catch (err) {\n console.warn('[portfolio] Failed to fetch prices, USD values will be unavailable:', err);\n }\n\n const walletManager = getWalletManager();\n const allWallets = await walletManager.listWallets();\n\n // Filter to specific wallet if requested\n const walletsToQuery = walletId\n ? allWallets.filter((w) => w.nickname === walletId)\n : allWallets;\n\n if (walletsToQuery.length === 0) {\n return {\n success: false,\n error: walletId ? `Wallet not found: ${walletId}` : 'No wallets configured',\n };\n }\n\n // Determine chains to query\n // Query all chains with configured tokens by default\n const defaultChains = [\n CHAIN_IDS.BASE,\n CHAIN_IDS.ETHEREUM,\n CHAIN_IDS.ARBITRUM,\n CHAIN_IDS.OPTIMISM,\n CHAIN_IDS.POLYGON,\n CHAIN_IDS.SONIC,\n CHAIN_IDS.BSC,\n CHAIN_IDS.AVALANCHE,\n CHAIN_IDS.LIGHTLINK,\n ];\n const chainIdsToQuery = chains\n ? chains.map((c) => resolveChainId(c))\n : defaultChains;\n\n const results: WalletBalanceResult[] = [];\n let totalValueUsd = 0;\n\n // Helper to get USD price for a symbol\n const getPrice = (symbol: string): number | null => {\n if (!priceMap) return null;\n const lower = symbol.toLowerCase();\n return priceMap.bySymbol.get(lower) ?? priceMap.bySymbol.get('soda' + lower) ?? null;\n };\n\n for (const wallet of walletsToQuery) {\n // Skip wallets without known addresses\n if (wallet.address === '0x...') {\n console.log(`[portfolio] Skipping wallet ${wallet.nickname} - address not resolved`);\n continue;\n }\n\n const address = wallet.address as Address;\n\n // Filter chains to those the wallet supports\n const supportedChains = wallet.chains || [];\n const chainsForWallet = chainIdsToQuery.filter((cid) => {\n const names = CHAIN_NAME_STRINGS[cid] || [];\n return supportedChains.length === 0 || names.some((n) => supportedChains.includes(n));\n });\n\n // Query balances for each chain (in parallel)\n const balancePromises = chainsForWallet.map((cid) =>\n getChainBalances(address, cid, includeZeroBalances).catch((err) => {\n console.error(`[portfolio] Failed to query chain ${cid}:`, err);\n return null;\n })\n );\n const balanceResults = (await Promise.all(balancePromises)).filter(\n (b): b is NonNullable<typeof b> => b !== null\n );\n\n // Filter out chains with no balances if not including zeros\n const filteredBalances = includeZeroBalances\n ? balanceResults\n : balanceResults.filter(\n (b) => parseFloat(b.native.balance) > 0 || b.tokens.length > 0\n );\n\n // Add USD values to balances\n let walletBalanceUsd = 0;\n const balancesWithUsd = filteredBalances.map((chainBalance) => {\n let chainTotalUsd = 0;\n\n // Native token USD value\n const nativePrice = getPrice(chainBalance.native.symbol);\n const nativeBalance = parseFloat(chainBalance.native.balance);\n const nativeUsdValue = nativePrice ? nativeBalance * nativePrice : null;\n if (nativeUsdValue) chainTotalUsd += nativeUsdValue;\n\n // Token USD values\n const tokensWithUsd = chainBalance.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue) chainTotalUsd += usdValue;\n return {\n ...token,\n usdValue: usdValue ? `$${usdValue.toFixed(2)}` : undefined,\n };\n });\n\n walletBalanceUsd += chainTotalUsd;\n\n return {\n chainId: chainBalance.chainId,\n chainName: chainBalance.chainName,\n native: {\n symbol: chainBalance.native.symbol,\n balance: chainBalance.native.balance,\n usdValue: nativeUsdValue ? `$${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: chainTotalUsd > 0 ? `$${chainTotalUsd.toFixed(2)}` : undefined,\n };\n });\n\n // Query Solana balances if wallet has a Solana address\n // Bankr wallets have a separate Solana address that can be cached\n const solanaAddress = (wallet as any).solanaAddress;\n if (solanaAddress) {\n try {\n const solanaBalances = await getSolanaWalletBalances(solanaAddress, includeZeroBalances);\n if (solanaBalances) {\n // Add USD values for Solana\n let solanaTotalUsd = 0;\n const solPrice = getPrice('SOL');\n const nativeBalance = parseFloat(solanaBalances.native.balance);\n const nativeUsdValue = solPrice ? nativeBalance * solPrice : null;\n if (nativeUsdValue) solanaTotalUsd += nativeUsdValue;\n \n const tokensWithUsd = solanaBalances.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue) solanaTotalUsd += usdValue;\n return { ...token, usdValue: usdValue ? `${usdValue.toFixed(2)}` : undefined };\n });\n \n walletBalanceUsd += solanaTotalUsd;\n balancesWithUsd.push({\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: solanaBalances.native.symbol,\n balance: solanaBalances.native.balance,\n usdValue: nativeUsdValue ? `${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: solanaTotalUsd > 0 ? `${solanaTotalUsd.toFixed(2)}` : undefined,\n });\n }\n } catch (err) {\n console.error(`[portfolio] Failed to get Solana balances for ${wallet.nickname}:`, err);\n }\n }\n\n // Get money market positions (aggregate)\n let mmSummary: WalletBalanceResult['moneyMarket'] | undefined;\n try {\n const positions = await aggregateCrossChainPositions(wallet.nickname);\n if (positions && (positions.summary.totalSupplyUsd > 0 || positions.summary.totalBorrowUsd > 0)) {\n const hfStatus = getHealthFactorStatus(positions.summary.healthFactor);\n // Build per-chain breakdown with individual health factors\n const chainBreakdown = positions.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n healthStatus: getHealthFactorStatus(cs.healthFactor),\n }));\n mmSummary = {\n totalSupplyUsd: positions.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: positions.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: positions.summary.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(positions.summary.healthFactor),\n healthStatus: hfStatus,\n chainBreakdown,\n };\n // MM net worth is already USD - add to wallet total\n walletBalanceUsd += positions.summary.netWorthUsd;\n }\n } catch (err) {\n console.error(`[portfolio] Failed to get MM positions for ${wallet.nickname}:`, err);\n }\n\n totalValueUsd += walletBalanceUsd;\n\n results.push({\n wallet: {\n nickname: wallet.nickname,\n address: wallet.address,\n type: wallet.type,\n },\n balances: balancesWithUsd as WalletBalanceResult['balances'],\n moneyMarket: mmSummary,\n walletTotalUsd: walletBalanceUsd > 0 ? `$${walletBalanceUsd.toFixed(2)}` : undefined,\n } as WalletBalanceResult);\n }\n\n // Build summary\n const summary = {\n walletCount: results.length,\n chainsQueried: chainIdsToQuery.length,\n timestamp: new Date().toISOString(),\n estimatedTotalUsd: totalValueUsd > 0 ? `$${totalValueUsd.toFixed(2)}` : 'No positions',\n priceSource: priceMap ? 'SODAX' : 'unavailable',\n };\n\n return {\n success: true,\n summary,\n wallets: results,\n };\n}\n\n// ============================================================================\n// Solana Balance Functions\n// ============================================================================\n\nconst SOLANA_RPC_URL = 'https://api.mainnet-beta.solana.com';\n\n/**\n * Major SPL tokens to check on Solana\n */\nconst SOLANA_TOKENS: Array<{ mint: string; symbol: string; decimals: number }> = [\n { mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', decimals: 6 },\n { mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', symbol: 'USDT', decimals: 6 },\n];\n\n/**\n * Get native SOL balance for a Solana wallet\n */\nasync function getSolanaBalance(\n address: string\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint }> {\n try {\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getBalance',\n params: [address],\n }),\n });\n const json = await response.json() as { result?: { value: number } };\n const lamports = BigInt(json.result?.value || 0);\n // SOL has 9 decimals\n const balance = Number(lamports) / 1e9;\n return {\n symbol: 'SOL',\n balance: balance.toFixed(6),\n balanceRaw: lamports,\n };\n } catch (error) {\n console.error('[portfolio] Failed to get SOL balance:', error);\n return { symbol: 'SOL', balance: '0', balanceRaw: 0n };\n }\n}\n\n/**\n * Get SPL token balances for a Solana wallet\n */\nasync function getSolanaTokenBalances(\n address: string\n): Promise<Array<{ symbol: string; balance: string; address: string }>> {\n const results: Array<{ symbol: string; balance: string; address: string }> = [];\n \n try {\n // Query all token accounts owned by this wallet\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [\n address,\n { programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' },\n { encoding: 'jsonParsed' },\n ],\n }),\n });\n \n const json = await response.json() as {\n result?: {\n value: Array<{\n account: {\n data: {\n parsed: {\n info: {\n mint: string;\n tokenAmount: { uiAmount: number; decimals: number };\n };\n };\n };\n };\n }>;\n };\n };\n \n const accounts = json.result?.value || [];\n \n // Match against known tokens\n for (const tokenConfig of SOLANA_TOKENS) {\n const account = accounts.find(\n (a) => a.account.data.parsed.info.mint === tokenConfig.mint\n );\n if (account) {\n const amount = account.account.data.parsed.info.tokenAmount.uiAmount || 0;\n if (amount > 0) {\n results.push({\n symbol: tokenConfig.symbol,\n balance: amount.toFixed(6),\n address: tokenConfig.mint,\n });\n }\n }\n }\n } catch (error) {\n console.error('[portfolio] Failed to get Solana token balances:', error);\n }\n \n return results;\n}\n\n/**\n * Get all Solana balances for a wallet\n */\nexport async function getSolanaWalletBalances(\n address: string,\n includeZeroBalances: boolean = false\n): Promise<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>;\n chainTotalUsd?: string;\n} | null> {\n // Validate Solana address format (base58, 32-44 chars)\n if (!address || address.startsWith('0x') || address.length < 32 || address.length > 44) {\n return null;\n }\n \n try {\n const native = await getSolanaBalance(address);\n const tokens = await getSolanaTokenBalances(address);\n \n // Skip if no balances and not including zeros\n if (!includeZeroBalances && native.balanceRaw === 0n && tokens.length === 0) {\n return null;\n }\n \n return {\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: native.symbol,\n balance: native.balance,\n },\n tokens,\n };\n } catch (error) {\n console.error('[portfolio] Failed to get Solana balances:', error);\n return null;\n }\n}\n",
1192 "inputSchema": {},
1193 "outputSchema": null,
1194 "icons": null,
1195 "annotations": null,
1196 "meta": null,
1197 "execution": null
1198 },
1199 {
1200 "name": "walletManagement.ts",
1201 "title": null,
1202 "description": "Script: walletManagement.ts. Code:\n/**\n * Wallet Management Tools\n * \n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n * \n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { getWalletManager } from '../wallet';\nimport type { WalletConfig, WalletBackendType } from '../wallet/types';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_add_wallet\n */\nconst AddWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Nickname for the wallet (e.g., \"trading\", \"savings\", \"degen\")',\n }),\n source: Type.Union([\n Type.Literal('evm-wallet-skill'),\n Type.Literal('bankr'),\n Type.Literal('env'),\n ], {\n description: 'Wallet source type',\n }),\n // For evm-wallet-skill\n path: Type.Optional(Type.String({\n description: 'Path to wallet JSON file (for evm-wallet-skill). Defaults to ~/.evm-wallet.json',\n })),\n // For bankr\n apiKey: Type.Optional(Type.String({\n description: 'Bankr API key (for bankr source)',\n })),\n apiUrl: Type.Optional(Type.String({\n description: 'Bankr API URL (optional, defaults to https://api.bankr.bot)',\n })),\n // For env\n address: Type.Optional(Type.String({\n description: 'Wallet address (for env source)',\n })),\n privateKey: Type.Optional(Type.String({\n description: 'Private key (for env source). WARNING: Will be stored in config file.',\n })),\n // Chain restrictions\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional list of chains this wallet can use',\n })),\n});\n\n/**\n * Schema for amped_rename_wallet\n */\nconst RenameWalletSchema = Type.Object({\n currentNickname: Type.String({\n description: 'Current wallet nickname',\n }),\n newNickname: Type.String({\n description: 'New wallet nickname',\n }),\n});\n\n/**\n * Schema for amped_remove_wallet\n */\nconst RemoveWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to remove',\n }),\n confirm: Type.Optional(Type.Boolean({\n description: 'Set to true to confirm removal',\n default: false,\n })),\n});\n\n/**\n * Schema for amped_set_default_wallet\n */\nconst SetDefaultWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to set as default',\n }),\n});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype AddWalletParams = Static<typeof AddWalletSchema>;\ntype RenameWalletParams = Static<typeof RenameWalletSchema>;\ntype RemoveWalletParams = Static<typeof RemoveWalletSchema>;\ntype SetDefaultWalletParams = Static<typeof SetDefaultWalletSchema>;\n\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n\n// ============================================================================\n// Handlers\n// ============================================================================\n\n/**\n * Add a new wallet with a nickname\n */\nasync function handleAddWallet(params: AddWalletParams): Promise<unknown> {\n const { nickname, source, path, apiKey, apiUrl, address, privateKey, chains } = params;\n\n console.log('[walletManagement:addWallet] Adding wallet', { nickname, source });\n\n // Validate required fields based on source\n if (source === 'bankr' && !apiKey) {\n throw new Error('Bankr wallet requires apiKey');\n }\n if (source === 'env' && (!address || !privateKey)) {\n throw new Error('Env wallet requires both address and privateKey');\n }\n\n // Build config\n const config: WalletConfig = {\n source: source as WalletBackendType,\n chains,\n };\n\n if (source === 'evm-wallet-skill') {\n if (path) config.path = path;\n } else if (source === 'bankr') {\n config.apiKey = apiKey;\n if (apiUrl) config.apiUrl = apiUrl;\n } else if (source === 'env') {\n config.address = address as `0x${string}`;\n config.privateKey = privateKey as `0x${string}`;\n }\n\n // Add wallet\n const walletManager = getWalletManager();\n await walletManager.addWallet(nickname, config);\n\n // Get the new wallet info\n const wallet = await walletManager.resolve(nickname);\n const walletAddress = await wallet.getAddress();\n\n return {\n success: true,\n message: `Wallet \"${nickname}\" added successfully`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: source,\n address: walletAddress,\n chains: wallet.supportedChains,\n },\n hint: `You can now use: \"swap 100 USDC to ETH using ${nickname}\"`,\n };\n}\n\n/**\n * Rename a wallet\n */\nasync function handleRenameWallet(params: RenameWalletParams): Promise<unknown> {\n const { currentNickname, newNickname } = params;\n\n console.log('[walletManagement:renameWallet] Renaming wallet', { \n from: currentNickname, \n to: newNickname \n });\n\n const walletManager = getWalletManager();\n \n // Get current info before rename\n const wallet = await walletManager.resolve(currentNickname);\n const address = await wallet.getAddress();\n\n // Rename\n await walletManager.renameWallet(currentNickname, newNickname);\n\n return {\n success: true,\n message: `Wallet renamed from \"${currentNickname}\" to \"${newNickname}\"`,\n wallet: {\n nickname: newNickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: `Now use: \"swap 100 USDC using ${newNickname}\"`,\n };\n}\n\n/**\n * Remove a wallet\n */\nasync function handleRemoveWallet(params: RemoveWalletParams): Promise<unknown> {\n const { nickname, confirm } = params;\n\n console.log('[walletManagement:removeWallet] Removing wallet', { nickname, confirm });\n\n const walletManager = getWalletManager();\n \n // Get wallet info before removal\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n\n if (!confirm) {\n return {\n success: false,\n requiresConfirmation: true,\n message: `Are you sure you want to remove wallet \"${nickname}\" (${address})?`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: 'Call again with confirm: true to proceed',\n };\n }\n\n // Remove\n await walletManager.removeWallet(nickname);\n\n // List remaining wallets\n const remainingWallets = await walletManager.listWallets();\n\n return {\n success: true,\n message: `Wallet \"${nickname}\" removed`,\n remainingWallets: remainingWallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n isDefault: w.isDefault,\n })),\n };\n}\n\n/**\n * Set default wallet\n */\nasync function handleSetDefaultWallet(params: SetDefaultWalletParams): Promise<unknown> {\n const { nickname } = params;\n\n console.log('[walletManagement:setDefaultWallet] Setting default', { nickname });\n\n const walletManager = getWalletManager();\n \n // Validate wallet exists\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n\n // Set default\n await walletManager.setDefaultWallet(nickname);\n\n return {\n success: true,\n message: `Default wallet set to \"${nickname}\"`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n chains: [...wallet.supportedChains],\n },\n hint: 'Operations without a wallet specified will now use this wallet',\n };\n}\n\n// ============================================================================\n// Error Wrapper\n// ============================================================================\n\nfunction wrapHandler<T>(handler: (params: T) => Promise<unknown>) {\n return async (params: unknown): Promise<unknown> => {\n try {\n return await handler(params as T);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[walletManagement] Error:', message);\n return {\n success: false,\n error: message,\n };\n }\n };\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register wallet management tools\n */\nexport function registerWalletManagementTools(agentTools: AgentTools): void {\n // 1. Add wallet\n agentTools.register({\n name: 'amped_add_wallet',\n summary: 'Add a new wallet with a nickname for easy reference',\n description:\n 'Add a wallet from evm-wallet-skill, Bankr, or environment variables. ' +\n 'Give it a memorable nickname like \"trading\", \"savings\", or \"degen\". ' +\n 'The wallet will be saved to config and available across sessions.',\n schema: AddWalletSchema,\n handler: wrapHandler(handleAddWallet),\n });\n\n // 2. Rename wallet\n agentTools.register({\n name: 'amped_rename_wallet',\n summary: 'Rename a wallet to a new nickname',\n description:\n 'Change the nickname of an existing wallet. ' +\n 'The wallet address and configuration remain the same, only the nickname changes.',\n schema: RenameWalletSchema,\n handler: wrapHandler(handleRenameWallet),\n });\n\n // 3. Remove wallet\n agentTools.register({\n name: 'amped_remove_wallet',\n summary: 'Remove a wallet from configuration',\n description:\n 'Remove a wallet by nickname. This only removes it from the config file, ' +\n 'it does NOT delete the actual wallet or funds. Requires confirmation.',\n schema: RemoveWalletSchema,\n handler: wrapHandler(handleRemoveWallet),\n });\n\n // 4. Set default wallet\n agentTools.register({\n name: 'amped_set_default_wallet',\n summary: 'Set which wallet to use by default',\n description:\n 'Set the default wallet for operations. When you don\\'t specify a wallet, ' +\n 'this one will be used automatically.',\n schema: SetDefaultWalletSchema,\n handler: wrapHandler(handleSetDefaultWallet),\n });\n}\n\n// Export schemas and handlers for testing\nexport {\n AddWalletSchema,\n RenameWalletSchema,\n RemoveWalletSchema,\n SetDefaultWalletSchema,\n handleAddWallet,\n handleRenameWallet,\n handleRemoveWallet,\n handleSetDefaultWallet,\n};\n",
1203 "inputSchema": {},
1204 "outputSchema": null,
1205 "icons": null,
1206 "annotations": null,
1207 "meta": null,
1208 "execution": null
1209 },
1210 {
1211 "name": "discovery.ts",
1212 "title": null,
1213 "description": "Script: discovery.ts. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager, type IWalletBackend, type WalletInfo } from '../wallet';\nimport { \n aggregateCrossChainPositions, \n formatHealthFactor,\n getHealthFactorStatus,\n getPositionRecommendation \n} from '../utils/positionAggregator';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_supported_chains - no parameters required\n */\nconst SupportedChainsSchema = Type.Object({});\n\n/**\n * Schema for amped_supported_tokens\n */\nconst SupportedTokensSchema = Type.Object({\n module: Type.Union([\n Type.Literal('swaps'),\n Type.Literal('bridge'),\n Type.Literal('moneyMarket'),\n ]),\n chainId: Type.String({\n description: 'Spoke chain ID (e.g., \"ethereum\", \"arbitrum\", \"sonic\")',\n }),\n});\n\n/**\n * Schema for amped_wallet_address\n */\nconst WalletAddressSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n});\n\n/**\n * Schema for amped_money_market_positions\n */\nconst MoneyMarketPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainId: Type.String({\n description: 'Spoke chain ID to query positions on',\n }),\n});\n\n/**\n * Schema for amped_money_market_reserves\n */\nconst MoneyMarketReservesSchema = Type.Object({\n chainId: Type.Optional(\n Type.String({\n description:\n 'Optional chain ID. Money market is hub-centric, so this filters results for a specific spoke chain if needed',\n })\n ),\n});\n\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\nconst CrossChainPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainIds: Type.Optional(\n Type.Array(Type.String(), {\n description: 'Optional array of specific chain IDs to query (defaults to all supported chains)',\n })\n ),\n includeZeroBalances: Type.Optional(\n Type.Boolean({\n description: 'Include positions with zero balance',\n default: false,\n })\n ),\n minUsdValue: Type.Optional(\n Type.Number({\n description: 'Minimum USD value threshold for including positions',\n default: 0.01,\n })\n ),\n});\n\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\nconst UserIntentsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n status: Type.Optional(\n Type.Union([\n Type.Literal('all', { description: 'All intents (open and closed)' }),\n Type.Literal('open', { description: 'Only open/pending intents' }),\n Type.Literal('closed', { description: 'Only filled/cancelled/expired intents' }),\n ], {\n description: 'Filter by intent status',\n default: 'all',\n })\n ),\n limit: Type.Optional(\n Type.Number({\n description: 'Number of items to return (default: 50, max: 100)',\n default: 50,\n minimum: 1,\n maximum: 100,\n })\n ),\n offset: Type.Optional(\n Type.Number({\n description: 'Number of items to skip (for pagination)',\n default: 0,\n minimum: 0,\n })\n ),\n});\n\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\nconst ListWalletsSchema = Type.Object({});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype SupportedChainsParams = Static<typeof SupportedChainsSchema>;\ntype SupportedTokensParams = Static<typeof SupportedTokensSchema>;\ntype WalletAddressParams = Static<typeof WalletAddressSchema>;\ntype MoneyMarketPositionsParams = Static<typeof MoneyMarketPositionsSchema>;\ntype ListWalletsParams = Static<typeof ListWalletsSchema>;\ntype MoneyMarketReservesParams = Static<typeof MoneyMarketReservesSchema>;\ntype CrossChainPositionsParams = Static<typeof CrossChainPositionsSchema>;\ntype UserIntentsParams = Static<typeof UserIntentsSchema>;\n\n/**\n * AgentTools interface for registering tools with the OpenClaw framework\n */\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n\n// Helper to wrap typed handlers for AgentTools registration\nfunction wrapHandler<T>(handler: (params: T) => Promise<unknown>): (params: unknown) => Promise<unknown> {\n return (params: unknown) => handler(params as T);\n}\n\n// ============================================================================\n// Tool Implementations\n// ============================================================================\n\n/**\n * Get supported spoke chains from SODAX configuration\n */\nasync function handleSupportedChains(\n _params: SupportedChainsParams\n): Promise<unknown> {\n const sodax = getSodaxClient();\n const chains = sodax.config.getSupportedSpokeChains();\n\n // SDK may return chain IDs as strings or chain objects\n return {\n success: true,\n chains: chains.map((chain: any) => {\n // Handle both string IDs and chain objects\n if (typeof chain === 'string') {\n return {\n id: chain,\n name: chain,\n type: 'evm',\n isHub: chain === 'sonic',\n nativeCurrency: undefined,\n };\n }\n return {\n id: chain.id || chain,\n name: chain.name || chain.id || chain,\n type: chain.type || 'evm',\n isHub: (chain.id || chain) === 'sonic',\n nativeCurrency: chain.nativeCurrency,\n };\n }),\n };\n}\n\n/**\n * Get supported tokens for a specific module and chain\n */\nasync function handleSupportedTokens(\n params: SupportedTokensParams\n): Promise<unknown> {\n const sodax = getSodaxClient();\n const { module, chainId: rawChainId } = params;\n const chainId = toSodaxChainId(rawChainId);\n\n let tokens: Array<{\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n }> = [];\n\n // Helper to normalize token data\n const normalizeToken = (token: any) => ({\n address: token.address || '',\n symbol: token.symbol || '',\n name: token.name || token.symbol || '',\n decimals: token.decimals || 18,\n logoURI: token.logoURI || token.logoUri,\n });\n\n switch (module) {\n case 'swaps': {\n // Get supported swap tokens by chain ID\n // SDK may require chainId to be cast to specific type\n try {\n const swapTokens = sodax.config.getSupportedSwapTokensByChainId(chainId as any);\n tokens = (swapTokens || []).map(normalizeToken);\n } catch (e) {\n console.warn('[discovery] Failed to get swap tokens:', e);\n tokens = [];\n }\n break;\n }\n\n\n case 'bridge': {\n // Get bridgeable tokens via hub assets\n // Hub assets represent tokens that can be bridged between chains\n // Reference: sodax-frontend uses getHubAssets() for bridge token discovery\n try {\n const hubAssets = sodax.config.getHubAssets();\n \n // Check if this is the hub chain (Sonic)\n const isHubChain = rawChainId === 'sonic' || chainId === 'sonic';\n \n if (isHubChain) {\n // For Sonic (hub), show all bridgeable assets from all spoke chains\n // These are the assets that can be bridged FROM Sonic to other chains\n const allTokens: typeof tokens = [];\n const seenAddresses = new Set<string>();\n \n for (const spokeChainId of Object.keys(hubAssets)) {\n const chainAssets = hubAssets[spokeChainId as keyof typeof hubAssets] || {};\n for (const asset of Object.values(chainAssets)) {\n // Add the hub asset (on Sonic) - dedupe by hub address\n const hubAddress = (asset as any).asset || (asset as any).hubAddress || (asset as any).address;\n if (hubAddress && !seenAddresses.has(hubAddress.toLowerCase())) {\n seenAddresses.add(hubAddress.toLowerCase());\n allTokens.push(normalizeToken({\n address: hubAddress,\n symbol: (asset as any).symbol || '',\n name: (asset as any).name || (asset as any).symbol || '',\n decimals: (asset as any).decimals || 18,\n logoURI: (asset as any).logoURI || (asset as any).logoUri,\n }));\n }\n }\n }\n tokens = allTokens;\n } else {\n // For spoke chains, get assets bridgeable from that specific chain\n const chainAssets = hubAssets[chainId as keyof typeof hubAssets] || {};\n tokens = Object.values(chainAssets).map((asset: any) => normalizeToken({\n address: (asset as any).asset || (asset as any).address || (asset as any).originalAddress || '',\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: (asset as any).decimal || (asset as any).decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n } catch (e) {\n console.warn('[discovery] Failed to get bridge tokens:', e);\n tokens = [];\n }\n break;\n }\n\n case 'moneyMarket': {\n // Get money market supported tokens from config\n // Reference: sodax-frontend ConfigService.getSupportedMoneyMarketTokensByChainId\n try {\n const mmTokens = sodax.config.getSupportedMoneyMarketTokensByChainId?.(chainId as any);\n if (mmTokens && Array.isArray(mmTokens)) {\n tokens = mmTokens.map(normalizeToken);\n } else {\n // Fallback: try supportedMoneyMarketTokens directly from config\n const allMmTokens = (sodax.config as any).sodaxConfig?.supportedMoneyMarketTokens;\n if (allMmTokens && allMmTokens[chainId]) {\n tokens = allMmTokens[chainId].map(normalizeToken);\n } else {\n console.warn('[discovery] No money market tokens found for chain', chainId);\n tokens = [];\n }\n }\n } catch (e) {\n console.warn('[discovery] Failed to get money market tokens:', e);\n tokens = [];\n }\n break;\n }\n\n default:\n throw new Error(`Unknown module: ${module}`);\n }\n\n return {\n success: true,\n module,\n chainId,\n tokens,\n count: tokens.length,\n };\n}\n\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\nasync function handleWalletAddress(\n params: WalletAddressParams\n): Promise<unknown> {\n const { walletId } = params;\n\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n\n const address = await wallet.getAddress();\n\n return {\n success: true,\n walletId: wallet.nickname,\n address,\n type: wallet.type,\n chains: [...wallet.supportedChains],\n };\n}\n\n/**\n * Get user money market positions (humanized format)\n */\nasync function handleMoneyMarketPositions(\n params: MoneyMarketPositionsParams\n): Promise<unknown> {\n const { walletId, chainId } = params;\n\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Get spoke provider for this wallet and chain\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n\n const sodax = getSodaxClient();\n\n // IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n // To get token symbols/names, we must:\n // 1. Fetch getReservesHumanized() for token metadata\n // 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n // 3. Join with formatUserSummary(buildUserSummaryRequest())\n // Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(\n sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)\n );\n\n // Step 3: Fetch user-specific balances\n const userReservesResult = await sodax.moneyMarket.data.getUserReservesHumanized(\n spokeProvider\n );\n\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(\n sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReservesResult)\n );\n\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = (userSummary as any).userReservesData || [];\n\n // Format positions for readability\n const positions = userReservesData.map((reserve: any) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n\n return {\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n apy: supplyApy,\n collateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n apy: borrowApy,\n },\n // Health indicators\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n\n // Filter to only positions with activity\n const activePositions = positions.filter((p: any) =>\n parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0\n );\n\n // Calculate summary metrics\n const totalSupplyUsd = activePositions.reduce(\n (sum: number, p: any) => sum + (parseFloat(p.supply.balanceUsd) || 0),\n 0\n );\n const totalBorrowUsd = activePositions.reduce(\n (sum: number, p: any) => sum + (parseFloat(p.borrow.balanceUsd) || 0),\n 0\n );\n const netWorthUsd = totalSupplyUsd - totalBorrowUsd;\n const healthFactor =\n totalBorrowUsd > 0 ? totalSupplyUsd / totalBorrowUsd : Infinity;\n\n return {\n success: true,\n walletId,\n address: walletAddress,\n chainId,\n positions: activePositions,\n summary: {\n totalSupplyUsd: totalSupplyUsd.toFixed(2),\n totalBorrowUsd: totalBorrowUsd.toFixed(2),\n netWorthUsd: netWorthUsd.toFixed(2),\n healthFactor: healthFactor === Infinity ? '\u221e' : healthFactor.toFixed(2),\n positionCount: activePositions.length,\n },\n };\n}\n\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\nasync function handleMoneyMarketReserves(\n params: MoneyMarketReservesParams\n): Promise<unknown> {\n const { chainId } = params;\n\n const sodax = getSodaxClient();\n\n // Get reserves in humanized format (hub-centric)\n const reservesResult = await sodax.moneyMarket.data.getReservesHumanized();\n\n // SDK may return ReservesDataHumanized object with .reservesData array or just array\n const reservesArray = Array.isArray(reservesResult) \n ? reservesResult \n : (reservesResult as any).reservesData || [];\n\n // Filter by chainId if provided\n let filteredReserves = reservesArray;\n if (chainId) {\n filteredReserves = reservesArray.filter(\n (r: any) => r.token?.chainId === chainId || r.hubChainId === chainId || r.chainId === chainId\n );\n }\n\n // Format reserves for readability\n const formattedReserves = filteredReserves.map((reserve: any) => ({\n token: {\n address: reserve.token?.address || reserve.underlyingAsset || '',\n symbol: reserve.token?.symbol || reserve.symbol || '',\n name: reserve.token?.name || reserve.name || '',\n decimals: reserve.token?.decimals || reserve.decimals || 18,\n chainId: reserve.token?.chainId || reserve.chainId || '',\n },\n liquidity: {\n totalSupply: reserve.liquidity?.totalSupply || reserve.totalScaledVariableDebt || '0',\n availableLiquidity: reserve.liquidity?.availableLiquidity || reserve.availableLiquidity || '0',\n totalBorrow: reserve.liquidity?.totalBorrow || reserve.totalVariableDebt || '0',\n utilizationRate: reserve.liquidity?.utilizationRate || reserve.utilizationRate || '0',\n },\n rates: {\n supplyApy: reserve.rates?.supplyApy || reserve.supplyAPY || '0',\n borrowApy: reserve.rates?.borrowApy || reserve.variableBorrowAPY || '0',\n },\n parameters: {\n loanToValue: reserve.parameters?.loanToValue || reserve.baseLTVasCollateral || '0',\n liquidationThreshold: reserve.parameters?.liquidationThreshold || reserve.reserveLiquidationThreshold || '0',\n liquidationBonus: reserve.parameters?.liquidationBonus || reserve.reserveLiquidationBonus || '0',\n },\n hubChainId: reserve.hubChainId || 'sonic',\n }));\n\n // Calculate aggregate metrics\n const totalAvailableLiquidity = formattedReserves.reduce(\n (sum: number, r: any) => sum + (parseFloat(r.liquidity.availableLiquidity) || 0),\n 0\n );\n const totalBorrowed = formattedReserves.reduce(\n (sum: number, r: any) => sum + (parseFloat(r.liquidity.totalBorrow) || 0),\n 0\n );\n\n return {\n success: true,\n chainId: chainId || 'all',\n reserves: formattedReserves,\n summary: {\n reserveCount: formattedReserves.length,\n totalAvailableLiquidity: totalAvailableLiquidity.toFixed(2),\n totalBorrowed: totalBorrowed.toFixed(2),\n globalUtilizationRate:\n totalAvailableLiquidity + totalBorrowed > 0\n ? (\n (totalBorrowed / (totalAvailableLiquidity + totalBorrowed)) *\n 100\n ).toFixed(2) + '%'\n : '0%',\n },\n };\n}\n\n// ============================================================================\n// Cross-Chain Positions Tool\n// ============================================================================\n\n/**\n * Get aggregated money market positions across all chains\n * \n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\nasync function handleCrossChainPositions(\n params: CrossChainPositionsParams\n): Promise<unknown> {\n const { walletId, chainIds, includeZeroBalances, minUsdValue } = params;\n\n console.log('[discovery:crossChainPositions] Aggregating positions', {\n walletId,\n chainIds: chainIds || 'all',\n includeZeroBalances,\n minUsdValue,\n });\n\n try {\n const view = await aggregateCrossChainPositions(walletId, {\n chainIds,\n includeZeroBalances,\n minUsdValue,\n });\n\n // Get recommendations\n const recommendations = getPositionRecommendation(view);\n\n // Format response\n const response = {\n success: true,\n walletId: view.walletId,\n address: view.address,\n timestamp: view.timestamp,\n summary: {\n totalSupplyUsd: view.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: view.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: view.summary.netWorthUsd.toFixed(2),\n availableBorrowUsd: view.summary.availableBorrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(view.summary.healthFactor),\n healthFactorStatus: getHealthFactorStatus(view.summary.healthFactor),\n liquidationRisk: view.summary.liquidationRisk,\n weightedSupplyApy: `${(view.summary.weightedSupplyApy * 100).toFixed(2)}%`,\n weightedBorrowApy: `${(view.summary.weightedBorrowApy * 100).toFixed(2)}%`,\n netApy: `${(view.summary.netApy * 100).toFixed(2)}%`,\n },\n chainBreakdown: view.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n netWorthUsd: cs.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n positionCount: cs.positionCount,\n })),\n collateralUtilization: {\n totalCollateralUsd: view.collateralUtilization.totalCollateralUsd.toFixed(2),\n usedCollateralUsd: view.collateralUtilization.usedCollateralUsd.toFixed(2),\n availableCollateralUsd: view.collateralUtilization.availableCollateralUsd.toFixed(2),\n utilizationRate: `${view.collateralUtilization.utilizationRate.toFixed(2)}%`,\n },\n riskMetrics: {\n maxLtv: `${(view.riskMetrics.maxLtv * 100).toFixed(2)}%`,\n currentLtv: `${(view.riskMetrics.currentLtv * 100).toFixed(2)}%`,\n bufferUntilLiquidation: `${view.riskMetrics.bufferUntilLiquidation.toFixed(2)}%`,\n safeMaxBorrowUsd: view.riskMetrics.safeMaxBorrowUsd.toFixed(2),\n },\n positions: view.positions.map(pos => ({\n chainId: pos.chainId,\n token: pos.token,\n supply: {\n balance: pos.supply.balance,\n balanceUsd: pos.supply.balanceUsd,\n apy: `${(pos.supply.apy * 100).toFixed(2)}%`,\n isCollateral: pos.supply.isCollateral,\n },\n borrow: {\n balance: pos.borrow.balance,\n balanceUsd: pos.borrow.balanceUsd,\n apy: `${(pos.borrow.apy * 100).toFixed(2)}%`,\n },\n loanToValue: `${(pos.loanToValue * 100).toFixed(2)}%`,\n liquidationThreshold: `${(pos.liquidationThreshold * 100).toFixed(2)}%`,\n })),\n recommendations,\n };\n\n console.log('[discovery:crossChainPositions] Aggregation complete', {\n totalPositions: view.positions.length,\n totalSupplyUsd: view.summary.totalSupplyUsd,\n totalBorrowUsd: view.summary.totalBorrowUsd,\n healthFactor: view.summary.healthFactor,\n });\n\n return response;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:crossChainPositions] Failed to aggregate positions', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to aggregate cross-chain positions: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// User Intents Tool (SODAX API)\n// ============================================================================\n\n/**\n * Get user intents from SODAX API\n * \n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\nasync function handleUserIntents(\n params: UserIntentsParams\n): Promise<unknown> {\n const { walletId, status = 'all', limit = 50, offset = 0 } = params;\n\n console.log('[discovery:userIntents] Fetching user intents', {\n walletId,\n status,\n limit,\n offset,\n });\n\n try {\n // Get wallet address from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Initialize API client\n const apiClient = getSodaxApiClient();\n\n // Determine filters based on status\n let filters;\n if (status === 'open') {\n filters = { open: true };\n } else if (status === 'closed') {\n filters = { open: false };\n }\n\n // Fetch intents\n const response = await apiClient.getUserIntents(\n walletAddress,\n { limit, offset },\n filters\n );\n\n // Format response\n const formattedIntents = response.items.map(intent => ({\n intentHash: intent.intentHash,\n txHash: intent.txHash,\n chainId: intent.chainId,\n blockNumber: intent.blockNumber,\n status: intent.open ? 'open' : 'closed',\n createdAt: intent.createdAt,\n input: {\n token: intent.intent.inputToken,\n amount: intent.intent.inputAmount,\n chainId: intent.intent.srcChain,\n },\n output: {\n token: intent.intent.outputToken,\n minAmount: intent.intent.minOutputAmount,\n chainId: intent.intent.dstChain,\n },\n deadline: new Date(parseInt(intent.intent.deadline) * 1000).toISOString(),\n allowPartialFill: intent.intent.allowPartialFill,\n events: intent.events\n .filter((event): event is typeof event & { intentState: NonNullable<typeof event.intentState> } => \n event.intentState != null\n )\n .map(event => ({\n type: event.eventType,\n txHash: event.txHash,\n blockNumber: event.blockNumber,\n state: {\n remainingInput: event.intentState.remainingInput,\n receivedOutput: event.intentState.receivedOutput,\n pendingPayment: event.intentState.pendingPayment,\n },\n })),\n }));\n\n const result = {\n success: true,\n walletId,\n address: walletAddress,\n pagination: {\n total: response.total,\n offset: response.offset,\n limit: response.limit,\n hasMore: response.offset + response.items.length < response.total,\n },\n intents: formattedIntents,\n summary: {\n totalIntents: response.total,\n returned: formattedIntents.length,\n openIntents: formattedIntents.filter((i: { status: string }) => i.status === 'open').length,\n closedIntents: formattedIntents.filter((i: { status: string }) => i.status === 'closed').length,\n },\n };\n\n console.log('[discovery:userIntents] User intents fetched', {\n total: response.total,\n returned: formattedIntents.length,\n });\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:userIntents] Failed to fetch user intents', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to fetch user intents: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// List Wallets Tool\n// ============================================================================\n\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\nasync function handleListWallets(\n _params: ListWalletsParams\n): Promise<unknown> {\n console.log('[discovery:listWallets] Listing configured wallets');\n\n const walletManager = getWalletManager();\n const wallets = await walletManager.listWallets();\n\n const formattedWallets = wallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n address: w.address,\n addressKnown: w.address !== '0x...',\n supportedChains: w.chains,\n isDefault: w.isDefault,\n note: w.address === '0x...' && w.type === 'bankr' \n ? 'Address pending - will be fetched on first use' \n : undefined,\n }));\n\n const defaultWallet = await walletManager.getDefaultWalletName();\n\n // Group by type for summary\n const byType = {\n 'evm-wallet-skill': formattedWallets.filter(w => w.type === 'evm-wallet-skill'),\n 'bankr': formattedWallets.filter(w => w.type === 'bankr'),\n 'env': formattedWallets.filter(w => w.type === 'env'),\n };\n\n // Check if Bankr is configured but wallet not found\n const bankrKeyPresent = !!process.env.BANKR_API_KEY;\n const bankrWalletFound = byType.bankr.length > 0;\n\n return {\n success: true,\n wallets: formattedWallets,\n defaultWallet,\n count: formattedWallets.length,\n summary: {\n selfCustody: byType['evm-wallet-skill'].length + byType.env.length,\n bankrManaged: byType.bankr.length,\n },\n sources: {\n evmWalletSkill: byType['evm-wallet-skill'].length > 0,\n bankr: bankrWalletFound,\n bankrKeyConfigured: bankrKeyPresent,\n env: byType.env.length > 0,\n },\n hint: wallets.length === 0\n ? 'No wallets configured. Install evm-wallet-skill: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill'\n : bankrKeyPresent && !bankrWalletFound\n ? 'Bankr API key found but wallet not loaded. Try \"Add my bankr wallet\" to register it.'\n : 'Use wallet nickname in operations, e.g., \"swap 100 USDC to ETH using main\"',\n };\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport function registerDiscoveryTools(agentTools: AgentTools): void {\n // 1. amped_supported_chains - Get supported spoke chains\n agentTools.register({\n name: 'amped_supported_chains',\n summary:\n 'Get a list of all supported spoke chains for swaps, bridging, and money market operations',\n schema: SupportedChainsSchema,\n handler: wrapHandler(handleSupportedChains),\n });\n\n // 2. amped_supported_tokens - Get supported tokens by module\n agentTools.register({\n name: 'amped_supported_tokens',\n summary:\n 'Get supported tokens for a specific module (swaps, bridge, or moneyMarket) on a given chain',\n schema: SupportedTokensSchema,\n handler: wrapHandler(handleSupportedTokens),\n });\n\n // 3. amped_wallet_address - Get wallet address\n agentTools.register({\n name: 'amped_wallet_address',\n summary:\n 'Get the resolved wallet address for a given walletId. Validates private key matches in execute mode.',\n schema: WalletAddressSchema,\n handler: wrapHandler(handleWalletAddress),\n });\n\n // 4. amped_money_market_positions - Get user positions (humanized)\n agentTools.register({\n name: 'amped_money_market_positions',\n summary:\n 'Get humanized money market positions for a wallet on a specific chain, including supply/borrow balances and health metrics',\n schema: MoneyMarketPositionsSchema,\n handler: wrapHandler(handleMoneyMarketPositions),\n });\n\n // 5. amped_money_market_reserves - Get market reserves (humanized)\n agentTools.register({\n name: 'amped_money_market_reserves',\n summary:\n 'Get humanized money market reserves data including liquidity, rates, and parameters. Hub-centric with optional chain filtering.',\n schema: MoneyMarketReservesSchema,\n handler: wrapHandler(handleMoneyMarketReserves),\n });\n\n // 6. amped_cross_chain_positions - Get aggregated positions across all chains\n agentTools.register({\n name: 'amped_cross_chain_positions',\n summary:\n 'Get a unified view of money market positions across ALL chains. Shows total supply/borrow, health factor, borrowing power, net APY, and risk metrics.',\n description:\n 'Aggregates money market positions across all supported chains to provide a comprehensive portfolio view. ' +\n 'Includes: total supply/borrow in USD, health factor with risk status, available borrowing power, ' +\n 'weighted APYs, collateral utilization, and personalized recommendations. ' +\n 'This is the recommended tool for getting a complete picture of money market positions.',\n schema: CrossChainPositionsSchema,\n handler: wrapHandler(handleCrossChainPositions),\n });\n\n // 7. amped_user_intents - Query user intent history from SODAX API\n agentTools.register({\n name: 'amped_user_intents',\n summary:\n 'Query user swap intent history from SODAX backend API. Shows open, filled, and cancelled intents with event details.',\n description:\n 'Retrieves the complete intent history for a wallet from the SODAX backend API. ' +\n 'Includes open intents (pending), filled intents (completed), and cancelled/expired intents. ' +\n 'Each intent includes input/output tokens, amounts, chain IDs, and event history. ' +\n 'Use this to track the status of past swaps and bridge operations.',\n schema: UserIntentsSchema,\n handler: wrapHandler(handleUserIntents),\n });\n\n // 8. amped_list_wallets - List all configured wallets\n agentTools.register({\n name: 'amped_list_wallets',\n summary:\n 'List all configured wallets with their nicknames, types, addresses, and supported chains.',\n description:\n 'Shows all available wallets from evm-wallet-skill (~/.evm-wallet.json), Bankr API, ' +\n 'and environment variables (AMPED_OC_WALLETS_JSON). Each wallet has a nickname that can be ' +\n 'used in operations like \"swap 100 USDC using bankr\" or \"check balance on main\". ' +\n 'Also shows which chains each wallet supports.',\n schema: ListWalletsSchema,\n handler: wrapHandler(handleListWallets),\n });\n}\n\n// Export schemas for testing and reuse\nexport {\n SupportedChainsSchema,\n SupportedTokensSchema,\n WalletAddressSchema,\n MoneyMarketPositionsSchema,\n MoneyMarketReservesSchema,\n CrossChainPositionsSchema,\n UserIntentsSchema,\n ListWalletsSchema,\n};\n\n// Export handlers for testing\nexport {\n handleSupportedChains,\n handleSupportedTokens,\n handleWalletAddress,\n handleMoneyMarketPositions,\n handleMoneyMarketReserves,\n handleCrossChainPositions,\n handleUserIntents,\n handleListWallets,\n};\n",
1214 "inputSchema": {},
1215 "outputSchema": null,
1216 "icons": null,
1217 "annotations": null,
1218 "meta": null,
1219 "execution": null
1220 },
1221 {
1222 "name": "moneyMarket.ts",
1223 "title": null,
1224 "description": "Script: moneyMarket.ts. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n * \n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n * \n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n * \n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\n\nimport { Type, Static } from \"@sinclair/typebox\";\nimport { getSodaxClient } from \"../sodax/client\";\nimport { getSpokeProvider } from \"../providers/spokeProviderFactory\";\nimport { PolicyEngine } from \"../policy/policyEngine\";\nimport { getWalletManager } from '../wallet/walletManager';\nimport { AgentTools } from \"../types\";\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Base schema for money market operations\n */\nconst MoneyMarketBaseSchema = Type.Object({\n walletId: Type.String({ \n description: \"Unique identifier for the wallet\" \n }),\n chainId: Type.String({ \n description: \"Source SODAX spoke chain ID where the operation originates (e.g., 'ethereum', 'arbitrum', 'sonic')\" \n }),\n token: Type.String({\n description: \"Token address or symbol to supply/borrow/withdraw/repay\",\n }),\n amount: Type.String({\n description: \"Amount in human-readable units (e.g., '100.5' for 100.5 USDC). Use '-1' for max repay (repay full debt).\",\n }),\n timeoutMs: Type.Optional(\n Type.Number({\n description: \"Operation timeout in milliseconds\",\n default: 180000,\n })\n ),\n policyId: Type.Optional(\n Type.String({ description: \"Optional policy profile identifier for custom limits\" })\n ),\n skipSimulation: Type.Optional(\n Type.Boolean({\n description: \"Skip transaction simulation (not recommended)\",\n default: false,\n })\n ),\n});\n\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\nconst MoneyMarketSupplySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n useAsCollateral: Type.Optional(\n Type.Boolean({\n description: \"Whether to use the supplied tokens as collateral for borrowing (default: true)\",\n default: true,\n })\n ),\n // Cross-chain supply options\n dstChainId: Type.Optional(\n Type.String({\n description: \"Optional destination chain for the supply operation. If different from chainId, performs cross-chain supply.\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address for the supplied position (defaults to wallet address)\",\n })\n ),\n }),\n]);\n\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\nconst MoneyMarketWithdrawSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n withdrawType: Type.Optional(\n Type.Union([\n Type.Literal('default'),\n Type.Literal('collateral'),\n Type.Literal('all'),\n ], {\n description: \"Withdraw type: 'default' (standard), 'collateral' (withdraw collateral only), 'all' (withdraw maximum)\",\n default: 'default',\n })\n ),\n // Cross-chain withdraw options\n dstChainId: Type.Optional(\n Type.String({\n description: \"Optional destination chain to receive withdrawn tokens. If different from chainId, performs cross-chain withdraw.\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address to receive withdrawn tokens (defaults to wallet address)\",\n })\n ),\n }),\n]);\n\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n * \n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\nconst MoneyMarketBorrowSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(\n Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate (recommended)\" }),\n ], {\n description: \"Interest rate mode: 1 = Stable, 2 = Variable\",\n default: 2,\n })\n ),\n referralCode: Type.Optional(\n Type.String({\n description: \"Optional referral code for the borrow operation\",\n })\n ),\n // Cross-chain borrow options (key feature!)\n dstChainId: Type.Optional(\n Type.String({\n description: \"Destination chain to receive borrowed tokens. If different from chainId, performs cross-chain borrow (supply on chainId, receive borrowed tokens on dstChainId).\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address to receive borrowed tokens (defaults to wallet address on dstChainId or chainId)\",\n })\n ),\n }),\n]);\n\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\nconst MoneyMarketRepaySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(\n Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate\" }),\n ], {\n description: \"Interest rate mode of the debt to repay: 1 = Stable, 2 = Variable\",\n default: 2,\n })\n ),\n repayAll: Type.Optional(\n Type.Boolean({\n description: \"If true, repays the full debt amount (useful for closing position)\",\n default: false,\n })\n ),\n // Cross-chain repay options\n collateralChainId: Type.Optional(\n Type.String({\n description: \"Optional chain ID where collateral is held (for cross-chain repay scenarios)\",\n })\n ),\n }),\n]);\n\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\nconst CreateSupplyIntentSchema = Type.Composite([\n MoneyMarketSupplySchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateBorrowIntentSchema = Type.Composite([\n MoneyMarketBorrowSchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateWithdrawIntentSchema = Type.Composite([\n MoneyMarketWithdrawSchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateRepayIntentSchema = Type.Composite([\n MoneyMarketRepaySchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\n// ============================================================================\n// Output Types\n// ============================================================================\n\ninterface MoneyMarketOperationResult {\n success: boolean;\n txHash?: string;\n status: \"success\" | \"pending\" | \"failed\";\n spokeTxHash?: string;\n hubTxHash?: string;\n intentHash?: string;\n operation: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n message?: string;\n warnings?: string[];\n // Cross-chain specific\n isCrossChain?: boolean;\n srcSpokeTxHash?: string;\n dstSpokeTxHash?: string;\n // Raw intent data (for createIntent operations)\n rawIntent?: unknown;\n}\n\ninterface IntentResult extends MoneyMarketOperationResult {\n intentData: unknown;\n requiresSubmission: boolean;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Converts human-readable amount to token units (wei)\n */\nfunction parseTokenAmount(amount: string, decimals: number = 18): bigint {\n // Handle special case for max repay\n if (amount === '-1') {\n return BigInt(-1);\n }\n \n const parsed = parseFloat(amount);\n if (isNaN(parsed)) {\n throw new Error(`Invalid amount: ${amount}`);\n }\n \n const multiplier = Math.pow(10, decimals);\n return BigInt(Math.floor(parsed * multiplier));\n}\n\n/**\n * Resolves wallet and creates spoke provider for the operation\n */\nasync function resolveWalletAndProvider(\n walletId: string,\n chainId: string\n): Promise<{\n walletAddress: string;\n spokeProvider: any;\n}> {\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n\n return { walletAddress, spokeProvider };\n}\n\n/**\n * Common pre-operation checks and allowance handling\n */\nasync function prepareMoneyMarketOperation(\n walletId: string,\n chainId: string,\n token: string,\n amount: string,\n operation: \"supply\" | \"withdraw\" | \"borrow\" | \"repay\",\n policyId?: string\n): Promise<{ walletAddress: string; spokeProvider: any; policyResult: any; tokenAddr: string }> {\n // ============================================================================\n // Hub Chain Validation\n // ============================================================================\n // SODAX architecture: Money market operations must be initiated from spoke chains,\n // not the hub chain (Sonic). The hub chain is the settlement layer where contracts\n // live, but users interact via spoke chains that relay operations to the hub.\n // Reference: sodax-tests/tests/crossChainSdk.test.ts explicitly omits SONIC_MAINNET_CHAIN_ID\n const isHubChainSource = chainId.toLowerCase() === 'sonic' || chainId === '146';\n if (isHubChainSource) {\n throw new Error(\n `Money market operations cannot be initiated from the hub chain (Sonic). ` +\n `Please use a spoke chain (base, arbitrum, ethereum, optimism, avalanche, bsc, polygon) as the source chain.`\n );\n }\n // Ensure sodax client is initialized\n const _sodaxClient = getSodaxClient(); // Just verify it's ready\n void _sodaxClient;\n\n // Normalize chain ID to SDK format for token resolution\n const sdkChainId = toSodaxChainId(chainId);\n\n // Resolve token symbol to address\n const tokenAddr = await resolveToken(sdkChainId, token);\n\n // Resolve wallet and create spoke provider\n const { walletAddress, spokeProvider } = await resolveWalletAndProvider(walletId, chainId);\n\n // Policy check\n const policyEngine = new PolicyEngine();\n const policyResult = await policyEngine.checkMoneyMarket({\n walletId,\n chainId,\n token,\n amount, // Add required amount parameter\n amountUsd: parseFloat(amount), // Simplified - would need actual price lookup\n operation,\n policyId,\n });\n\n if (!policyResult.allowed) {\n throw new Error(\n `Policy check failed: ${policyResult.reason || \"Operation not permitted\"}.`\n );\n }\n\n return { walletAddress, spokeProvider, policyResult, tokenAddr };\n}\n/**\n * Resolves token and returns its decimals\n * Falls back to 18 decimals if token info not found\n */\nasync function getTokenDecimals(\n chainId: string,\n token: string\n): Promise<number> {\n try {\n const sdkChainId = toSodaxChainId(chainId);\n const tokenInfo = await getTokenInfo(sdkChainId, token);\n return tokenInfo?.decimals ?? 18;\n } catch {\n // If token info lookup fails, fall back to 18 decimals\n return 18;\n }\n}\n\n\n/**\n * Checks and handles token approval if needed\n */\nasync function ensureAllowance(\n params: {\n token: string;\n amount: bigint;\n action: 'supply' | 'repay';\n },\n spokeProvider: any,\n raw: boolean = false\n): Promise<{ approvalTxHash?: string; rawApproval?: unknown }> {\n const sodaxClient = await getSodaxClient();\n\n // Check if allowance is sufficient\n const isAllowanceValid = await sodaxClient.moneyMarket.isAllowanceValid(\n params,\n spokeProvider\n );\n\n if (!isAllowanceValid.ok || !isAllowanceValid.value) {\n if (raw) {\n // Return raw approval transaction\n const rawApproval = await sodaxClient.moneyMarket.approve(\n params,\n spokeProvider,\n true // raw mode\n );\n return { rawApproval };\n } else {\n // Execute approval transaction\n const approvalResult = await sodaxClient.moneyMarket.approve(\n params,\n spokeProvider,\n false\n );\n // Handle Result type from SDK\n const txHash = (approvalResult as any).ok \n ? (approvalResult as any).value \n : (approvalResult as any).txHash || approvalResult;\n return { approvalTxHash: String(txHash) };\n }\n }\n\n return {};\n}\n\n/**\n * Determine if operation is cross-chain\n */\nfunction isCrossChainOperation(srcChainId: string, dstChainId?: string): boolean {\n return !!dstChainId && dstChainId !== srcChainId;\n}\n\n// ============================================================================\n// Tool Handlers\n// ============================================================================\n\n/**\n * Supply tokens to the money market\n * \n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\nasync function handleSupply(\n params: Static<typeof MoneyMarketSupplySchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n useAsCollateral = true,\n dstChainId,\n recipient,\n skipSimulation = false \n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"supply\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n // Check allowance for supply\n const { approvalTxHash } = await ensureAllowance(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n\n const sodaxClient = await getSodaxClient();\n\n // Build supply parameters\n const supplyParams: any = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain supply: tokens supplied on ${chainId}, collateral recorded on ${dstChainId}`);\n }\n\n // Check and handle allowance (required for ALL supply operations)\n // Reference: sodax-frontend moneymarket-ops.ts - supply ALWAYS checks allowance\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:supply] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:supply] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:supply] Allowance check failed, proceeding anyway:', allowanceError);\n }\n\n // Execute supply\n const supplyResult = await (sodaxClient as any).moneyMarket.supply(\n supplyParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (supplyResult.ok === false) {\n throw new Error(`Supply failed: ${serializeError(supplyResult.error)}`);\n }\n \n const value = supplyResult.ok ? supplyResult.value : supplyResult;\n // SDK may return [spokeTxHash, hubTxHash] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain \n ? `Successfully supplied ${amount} ${token} on ${chainId}. Collateral available on ${dstChainId || chainId}.`\n : `Successfully supplied ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during supply\";\n return {\n success: false,\n status: \"failed\",\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market supply failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Withdraw tokens from the money market\n * \n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\nasync function handleWithdraw(\n params: Static<typeof MoneyMarketWithdrawSchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n withdrawType = 'default',\n dstChainId,\n recipient,\n skipSimulation = false\n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"withdraw\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n const sodaxClient = await getSodaxClient();\n\n // Build withdraw parameters\n const withdrawParams: any = {\n action: 'withdraw',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n withdrawParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain withdraw: withdrawing from ${chainId}, receiving tokens on ${dstChainId}`);\n }\n\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'withdraw' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:withdraw] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'withdraw' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:withdraw] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:withdraw] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n\n // Execute withdraw\n const withdrawResult = await (sodaxClient as any).moneyMarket.withdraw(\n withdrawParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (withdrawResult.ok === false) {\n throw new Error(`Withdraw failed: ${serializeError(withdrawResult.error)}`);\n }\n \n const value = withdrawResult.ok ? withdrawResult.value : withdrawResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully withdrew ${amount} ${token} from ${chainId} to ${dstChainId}`\n : `Successfully withdrew ${amount} ${token} from money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during withdraw\";\n return {\n success: false,\n status: \"failed\",\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market withdraw failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Borrow tokens from the money market\n * \n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n * \n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\nasync function handleBorrow(\n params: Static<typeof MoneyMarketBorrowSchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n interestRateMode = 2,\n referralCode,\n dstChainId, // KEY: This can be different from chainId for cross-chain borrow!\n recipient,\n skipSimulation = false\n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"borrow\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n // Get user's positions to check health factor (best practice)\n const sodaxClient = await getSodaxClient();\n \n // For cross-chain borrow, resolve token on DESTINATION chain\n // SDK expects: getMoneyMarketToken(toChainId, params.token)\n // So params.token must be the destination chain's token address\n let borrowTokenAddr = tokenAddr;\n if (crossChain && dstChainId) {\n borrowTokenAddr = await resolveToken(dstChainId, token);\n console.log('[mm:borrow] Cross-chain: resolved token on destination chain', {\n srcChain: chainId,\n dstChain: dstChainId,\n srcTokenAddr: tokenAddr,\n dstTokenAddr: borrowTokenAddr,\n });\n }\n\n // Build borrow parameters\n const borrowParams: any = {\n action: 'borrow',\n token: borrowTokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // KEY CROSS-CHAIN FEATURE:\n // If dstChainId is provided and different from chainId, the borrowed tokens\n // will be delivered to dstChainId instead of chainId where the borrow is initiated\n if (crossChain && dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain borrow: Using collateral on ${chainId}, receiving borrowed tokens on ${dstChainId}`);\n warnings.push(`Ensure you have sufficient collateral on ${chainId} to support this borrow`);\n }\n\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:borrow] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:borrow] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:borrow] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n\n // Execute borrow\n const borrowResult = await (sodaxClient as any).moneyMarket.borrow(\n borrowParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (borrowResult.ok === false) {\n throw new Error(`Borrow failed: ${serializeError(borrowResult.error)}`);\n }\n \n const value = borrowResult.ok ? borrowResult.value : borrowResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully borrowed ${amount} ${token} on ${dstChainId} using collateral from ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`\n : `Successfully borrowed ${amount} ${token} from money market on ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during borrow\";\n return {\n success: false,\n status: \"failed\",\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market borrow failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Repay borrowed tokens to the money market\n * \n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\nasync function handleRepay(\n params: Static<typeof MoneyMarketRepaySchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n interestRateMode = 2,\n repayAll = false,\n collateralChainId,\n skipSimulation = false\n } = params;\n\n const crossChain = !!collateralChainId && collateralChainId !== chainId;\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"repay\",\n policyId\n );\n\n // Parse amount with actual token decimals (use -1 for max repay if repayAll is true)\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = repayAll ? BigInt(-1) : parseTokenAmount(amount, decimals);\n\n // Check allowance for repay\n const { approvalTxHash } = await ensureAllowance(\n { token: tokenAddr, amount: amountBigInt === BigInt(-1) ? BigInt(0) : amountBigInt, action: 'repay' },\n spokeProvider\n );\n\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n\n const sodaxClient = await getSodaxClient();\n\n // Build repay parameters\n const repayParams: any = {\n action: 'repay',\n token: tokenAddr,\n amount: amountBigInt,\n \n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && collateralChainId) {\n repayParams.toChainId = collateralChainId;\n warnings.push(`Cross-chain repay: Repaying debt on ${collateralChainId} using tokens from ${chainId}`);\n }\n\n // Check and handle allowance (required for ALL repay operations)\n // Reference: sodax-frontend moneymarket-ops.ts - repay ALWAYS checks allowance\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'repay' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:repay] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'repay' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:repay] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:repay] Allowance check failed, proceeding anyway:', allowanceError);\n }\n\n // Execute repay\n const repayResult = await (sodaxClient as any).moneyMarket.repay(\n repayParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (repayResult.ok === false) {\n throw new Error(`Repay failed: ${serializeError(repayResult.error)}`);\n }\n \n const value = repayResult.ok ? repayResult.value : repayResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: repayAll\n ? `Successfully repaid full debt for ${token} on ${chainId}`\n : `Successfully repaid ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during repay\";\n return {\n success: false,\n status: \"failed\",\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: `Money market repay failed: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Intent Creation Handlers (Advanced)\n// ============================================================================\n\n/**\n * Create a supply intent without executing (for custom flows)\n */\nasync function handleCreateSupplyIntent(\n params: Static<typeof CreateSupplyIntentSchema>\n): Promise<IntentResult> {\n const { walletId, chainId, token, amount, useAsCollateral = true, dstChainId, recipient, raw = true } = params;\n\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId, chainId, token, amount, \"supply\"\n );\n\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n\n const supplyParams: any = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n if (dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n }\n\n const intentData = await sodaxClient.moneyMarket.createSupplyIntent(\n supplyParams,\n spokeProvider,\n raw\n );\n\n return {\n success: true,\n status: \"pending\",\n operation: \"createSupplyIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: \"Supply intent created. Submit this intent to execute the supply operation.\",\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create supply intent failed: ${errorMessage}`);\n }\n}\n\n/**\n * Create a borrow intent without executing (for custom flows)\n */\nasync function handleCreateBorrowIntent(\n params: Static<typeof CreateBorrowIntentSchema>\n): Promise<IntentResult> {\n const { walletId, chainId, token, amount, interestRateMode = 2, dstChainId, recipient, raw = true } = params;\n\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId, chainId, token, amount, \"borrow\"\n );\n\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n\n const borrowParams: any = {\n action: 'borrow',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n if (dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n }\n\n const intentData = await sodaxClient.moneyMarket.createBorrowIntent(\n borrowParams,\n spokeProvider,\n raw\n );\n\n return {\n success: true,\n status: \"pending\",\n operation: \"createBorrowIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: dstChainId && dstChainId !== chainId\n ? `Cross-chain borrow intent created. Collateral on ${chainId}, borrowed tokens to ${dstChainId}. Submit this intent to execute.`\n : \"Borrow intent created. Submit this intent to execute the borrow operation.\",\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create borrow intent failed: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport function registerMoneyMarketTools(agentTools: AgentTools): void {\n // Supply\n agentTools.register({\n name: \"amped_mm_supply\",\n summary: \"Supply tokens as collateral to the SODAX money market. Supports same-chain and cross-chain supply (supply on chain A, collateral available on chain B).\",\n schema: MoneyMarketSupplySchema,\n handler: handleSupply,\n });\n\n // Withdraw\n agentTools.register({\n name: \"amped_mm_withdraw\",\n summary: \"Withdraw supplied tokens from the SODAX money market. Supports cross-chain withdraw (withdraw from chain A, receive tokens on chain B).\",\n schema: MoneyMarketWithdrawSchema,\n handler: handleWithdraw,\n });\n\n // Borrow\n agentTools.register({\n name: \"amped_mm_borrow\",\n summary: \"Borrow tokens from the SODAX money market. KEY FEATURE: Can borrow to a different chain than collateral! Example: Supply on Ethereum, borrow to Arbitrum using dstChainId parameter.\",\n schema: MoneyMarketBorrowSchema,\n handler: handleBorrow,\n });\n\n // Repay\n agentTools.register({\n name: \"amped_mm_repay\",\n summary: \"Repay borrowed tokens to the SODAX money market. Use amount='-1' or repayAll=true to repay full debt. Supports cross-chain repay.\",\n schema: MoneyMarketRepaySchema,\n handler: handleRepay,\n });\n\n // Advanced: Create Intent variants for custom flows\n agentTools.register({\n name: \"amped_mm_create_supply_intent\",\n summary: \"[Advanced] Create a supply intent without executing. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateSupplyIntentSchema,\n handler: handleCreateSupplyIntent,\n });\n\n agentTools.register({\n name: \"amped_mm_create_borrow_intent\",\n summary: \"[Advanced] Create a borrow intent without executing. Supports cross-chain borrow intents. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateBorrowIntentSchema,\n handler: handleCreateBorrowIntent,\n });\n}\n\n// ============================================================================\n// Re-exports for testing and direct usage\n// ============================================================================\n\nexport {\n MoneyMarketBaseSchema,\n MoneyMarketSupplySchema,\n MoneyMarketWithdrawSchema,\n MoneyMarketBorrowSchema,\n MoneyMarketRepaySchema,\n CreateSupplyIntentSchema,\n CreateWithdrawIntentSchema,\n CreateBorrowIntentSchema,\n CreateRepayIntentSchema,\n handleSupply,\n handleWithdraw,\n handleBorrow,\n handleRepay,\n handleCreateSupplyIntent,\n handleCreateBorrowIntent,\n};\n\n// Aliases for index.ts compatibility\nexport {\n MoneyMarketSupplySchema as MmSupplySchema,\n MoneyMarketWithdrawSchema as MmWithdrawSchema,\n MoneyMarketBorrowSchema as MmBorrowSchema,\n MoneyMarketRepaySchema as MmRepaySchema,\n handleSupply as handleMmSupply,\n handleWithdraw as handleMmWithdraw,\n handleBorrow as handleMmBorrow,\n handleRepay as handleMmRepay,\n};\n\nexport type { MoneyMarketOperationResult, IntentResult };\n",
1225 "inputSchema": {},
1226 "outputSchema": null,
1227 "icons": null,
1228 "annotations": null,
1229 "meta": null,
1230 "execution": null
1231 },
1232 {
1233 "name": "swap.ts",
1234 "title": null,
1235 "description": "Script: swap.ts. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n * \n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\n\nimport { Static, Type } from '@sinclair/typebox';\nimport { serializeError } from '../utils/errorUtils';\n// SDK types - using any for now due to beta API changes\nimport { Intent } from '@sodax/sdk';\ntype QuoteRequest = any;\ntype SwapQuote = any;\ntype IntentStatus = any;\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport type { AgentTools } from '../types';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n\n// ============================================================================\n// SODAX API & Explorer Links\n// ============================================================================\n\nconst SODAX_CANARY_API = 'https://canary-api.sodax.com/v1/be';\n\n// Chain ID to block explorer mapping\nconst CHAIN_EXPLORERS: Record<string, string> = {\n 'ethereum': 'https://etherscan.io/tx/',\n 'base': 'https://basescan.org/tx/',\n 'arbitrum': 'https://arbiscan.io/tx/',\n 'optimism': 'https://optimistic.etherscan.io/tx/',\n 'polygon': 'https://polygonscan.com/tx/',\n 'sonic': 'https://sonicscan.org/tx/',\n 'avalanche': 'https://snowtrace.io/tx/',\n 'bsc': 'https://bscscan.com/tx/',\n 'solana': 'https://solscan.io/tx/',\n};\n\nfunction getExplorerLink(chainId: string, txHash: string): string | undefined {\n // Normalize chain ID (remove 0x prefix and suffix if present)\n const normalizedChainId = chainId.replace(/^0x[\\\\da-f]+\\\\./, '').toLowerCase();\n const explorer = CHAIN_EXPLORERS[normalizedChainId];\n return explorer ? `${explorer}${txHash}` : undefined;\n}\n\nasync function fetchIntentFromSodax(intentHash: string): Promise<any> {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) return null;\n return await response.json();\n } catch {\n return null;\n }\n}\n\n/**\n * Ensure intent hash is in hex format (0x prefixed)\n */\nfunction toHexIntentHash(hash: unknown): string | undefined {\n if (!hash) return undefined;\n const str = String(hash);\n // Already hex format\n if (str.startsWith('0x')) return str;\n // Convert decimal BigInt string to hex\n try {\n return '0x' + BigInt(str).toString(16);\n } catch {\n return str; // Return as-is if conversion fails\n }\n}\n\nfunction getSodaxScanUrl(txHash: string): string {\n return `https://sodaxscan.com/messages/search?value=${txHash}`;\n}\n\nfunction getSodaxIntentApiUrl(intentHash: string): string {\n return `${SODAX_CANARY_API}/intent/${intentHash}`;\n}\n\n// SODAX internal chain ID to block explorer mapping\nconst SODAX_CHAIN_EXPLORERS: Record<number, string> = {\n 1: 'https://solscan.io/tx/', // Solana\n 30: 'https://basescan.org/tx/', // Base\n 146: 'https://sonicscan.org/tx/', // Sonic (hub)\n 42161: 'https://arbiscan.io/tx/', // Arbitrum\n 10: 'https://optimistic.etherscan.io/tx/', // Optimism\n 137: 'https://polygonscan.com/tx/', // Polygon\n 56: 'https://bscscan.com/tx/', // BSC\n 43114: 'https://snowtrace.io/tx/', // Avalanche\n};\n\n/**\n * Poll SODAX API until intent is delivered, then return delivery tx explorer link\n */\nasync function pollForDelivery(\n intentHash: string,\n timeoutMs: number = 60000,\n pollIntervalMs: number = 3000\n): Promise<{ delivered: boolean; deliveryTxHash?: string; deliveryExplorer?: string; dstChainId?: number }> {\n const startTime = Date.now();\n \n while (Date.now() - startTime < timeoutMs) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n continue;\n }\n \n const data: any = await response.json();\n \n // Check if intent is filled (closed)\n if (data.open === false && data.events?.length > 0) {\n const fillEvent = data.events.find((e: any) => e.eventType === 'intent-filled');\n if (fillEvent) {\n const dstChainId = data.intent?.dstChain;\n const explorer = SODAX_CHAIN_EXPLORERS[dstChainId] || '';\n \n return {\n delivered: true,\n deliveryTxHash: fillEvent.txHash,\n deliveryExplorer: explorer ? `${explorer}${fillEvent.txHash}` : undefined,\n dstChainId\n };\n }\n }\n \n await new Promise(r => setTimeout(r, pollIntervalMs));\n } catch {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n }\n \n return { delivered: false };\n}\n\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\nconst SwapTypeSchema = Type.Union([\n Type.Literal('exact_input'),\n Type.Literal('exact_output')\n]);\n\nconst SwapQuoteRequestSchema = Type.Object({\n walletId: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n type: SwapTypeSchema,\n slippageBps: Type.Number({ default: 50, minimum: 0, maximum: 10000 }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain. For cross-chain swaps to Solana, provide a Solana base58 address. Defaults to wallet address if omitted.'\n }))\n});\n\n// Result schema for documentation (not used at runtime)\nconst _SwapQuoteResultSchema = Type.Object({\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n fees: Type.Object({\n solverFee: Type.String(),\n protocolFee: Type.Optional(Type.String()),\n partnerFee: Type.Optional(Type.String())\n }),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n});\nvoid _SwapQuoteResultSchema; // Suppress unused warning\n\nconst SwapExecuteParamsSchema = Type.Object({\n walletId: Type.String(),\n quote: Type.Object({\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n }),\n maxSlippageBps: Type.Optional(Type.Number({ minimum: 0, maximum: 10000 })),\n policyId: Type.Optional(Type.String()),\n skipSimulation: Type.Optional(Type.Boolean({ default: false })),\n timeoutMs: Type.Optional(Type.Number({ default: 120000 }))\n});\n\nconst SwapExecuteResultSchema = Type.Object({\n spokeTxHash: Type.String(),\n hubTxHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String()),\n status: Type.String(),\n message: Type.Optional(Type.String())\n});\n\nconst SwapStatusParamsSchema = Type.Object({\n txHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String())\n});\n\nconst SwapStatusResultSchema = Type.Object({\n status: Type.String(),\n intentHash: Type.Optional(Type.String()),\n spokeTxHash: Type.Optional(Type.String()),\n hubTxHash: Type.Optional(Type.String()),\n filledAmount: Type.Optional(Type.String()),\n error: Type.Optional(Type.String()),\n createdAt: Type.Optional(Type.Number()),\n expiresAt: Type.Optional(Type.Number())\n});\n\nconst SwapCancelParamsSchema = Type.Object({\n walletId: Type.String(),\n intent: Type.Object({\n id: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n deadline: Type.Number()\n }),\n srcChainId: Type.String()\n});\n\nconst SwapCancelResultSchema = Type.Object({\n success: Type.Boolean(),\n txHash: Type.Optional(Type.String()),\n message: Type.String()\n});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype SwapQuoteRequest = Static<typeof SwapQuoteRequestSchema>;\ntype SwapExecuteParams = Static<typeof SwapExecuteParamsSchema>;\ntype SwapStatusParams = Static<typeof SwapStatusParamsSchema>;\ntype SwapCancelParams = Static<typeof SwapCancelParamsSchema>;\n\n// ============================================================================\n// Swap Quote Tool\n// ============================================================================\n\nasync function handleSwapQuote(params: SwapQuoteRequest): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.srcChainId, params.srcToken);\n const dstTokenAddr = await resolveToken(params.dstChainId, params.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.dstChainId, dstTokenAddr);\n \n // Get token config to determine decimals for amount conversion\n const configService = (sodaxClient as any).configService;\n const decimals = srcTokenInfo?.decimals ?? 18; // Default to 18 (most EVM tokens) if not found\n \n // Convert human-readable amount to raw amount (bigint)\n const amountFloat = parseFloat(params.amount);\n const rawAmount = BigInt(Math.floor(amountFloat * Math.pow(10, decimals)));\n \n // Build SDK-compatible request with snake_case parameters\n const quoteRequest = {\n token_src: srcTokenAddr,\n token_src_blockchain_id: toSodaxChainId(params.srcChainId),\n token_dst: dstTokenAddr,\n token_dst_blockchain_id: toSodaxChainId(params.dstChainId),\n amount: rawAmount,\n quote_type: params.type\n };\n \n console.log('[swap_quote] SDK request:', JSON.stringify(quoteRequest, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n\n const quoteResult = await (sodaxClient as any).swaps.getQuote(quoteRequest);\n \n // Handle Result type from SDK\n if (quoteResult.ok === false) {\n const errorMsg = quoteResult.error instanceof Error \n ? quoteResult.error.message \n : typeof quoteResult.error === 'string' \n ? quoteResult.error \n : JSON.stringify(quoteResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Quote failed: ${errorMsg}`);\n }\n \n const quote = quoteResult.ok ? quoteResult.value : quoteResult;\n \n console.log('[swap_quote] SDK response:', JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n \n // Get output token decimals with multiple fallbacks\n // SDK returns quoted_amount as bigint - convert to human-readable string\n let dstDecimals = (quote as any).token_dst_decimals || (quote as any).tokenDstDecimals;\n \n if (!dstDecimals && dstTokenInfo) {\n dstDecimals = dstTokenInfo.decimals;\n }\n \n if (!dstDecimals) {\n // Hardcoded decimals for common stablecoins\n const KNOWN_DECIMALS: Record<string, number> = {\n usdc: 6, USDC: 6, usdt: 6, USDT: 6, sol: 9, SOL: 9,\n dai: 18, DAI: 18, bnusd: 18, bnUSD: 18\n };\n const tokenSymbol = params.dstToken.toUpperCase();\n dstDecimals = KNOWN_DECIMALS[tokenSymbol] || 18;\n console.warn(`[swap_quote] Using fallback decimals (${dstDecimals}) for token ${params.dstToken}`);\n }\n const quotedAmount = quote.quoted_amount || quote.quotedAmount || quote.outputAmount;\n const outputAmountStr = quotedAmount \n ? (Number(quotedAmount) / Math.pow(10, dstDecimals)).toString()\n : '0';\n \n // Normalize and return quote (SDK uses snake_case, we return camelCase)\n const result = {\n inputAmount: params.amount,\n outputAmount: outputAmountStr,\n srcToken: srcTokenAddr,\n dstToken: dstTokenAddr,\n srcChainId: params.srcChainId,\n dstChainId: params.dstChainId,\n slippageBps: params.slippageBps,\n deadline: quote.deadline || calculateDeadline(300), // 5 min default\n fees: {\n solverFee: quote.solver_fee || quote.fees?.solverFee || '0',\n protocolFee: quote.protocol_fee || quote.fees?.protocolFee,\n partnerFee: quote.partner_fee || quote.fees?.partnerFee\n },\n minOutputAmount: quote.min_output_amount || quote.minOutputAmount,\n maxInputAmount: quote.max_input_amount || quote.maxInputAmount,\n recipient: params.recipient, // Pass through for execute\n // Include raw SDK response for debugging\n _raw: JSON.parse(JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v))\n };\n \n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n tokenAddresses: [params.srcToken, params.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to get swap quote: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Execute Tool\n// ============================================================================\n\nasync function handleSwapExecute(params: SwapExecuteParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n // 1. Initialize dependencies\n const policyEngine = new PolicyEngine();\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.quote.srcChainId, params.quote.srcToken);\n const dstTokenAddr = await resolveToken(params.quote.dstChainId, params.quote.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.quote.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.quote.dstChainId, dstTokenAddr);\n \n // 2. Resolve wallet\n const wallet = await walletManager.resolve(params.walletId);\n const walletAddress = await wallet.getAddress();\n \n // 3. Policy check\n const policyCheck = await policyEngine.checkSwap({\n walletId: params.walletId,\n srcChainId: params.quote.srcChainId,\n dstChainId: params.quote.dstChainId,\n srcToken: params.quote.srcToken,\n dstToken: params.quote.dstToken,\n inputAmount: params.quote.inputAmount,\n slippageBps: params.maxSlippageBps || params.quote.slippageBps,\n policyId: params.policyId\n });\n \n if (!policyCheck.allowed) {\n throw new Error(`Policy check failed: ${policyCheck.reason}`);\n }\n \n // 4. Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(\n params.walletId,\n params.quote.srcChainId\n );\n \n // 5. Convert amounts to bigint FIRST (needed for intentParams)\n const srcDecimals = srcTokenInfo?.decimals ?? 18;\n const dstDecimals = dstTokenInfo?.decimals ?? 18;\n const inputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.inputAmount) * Math.pow(10, srcDecimals)));\n const outputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.outputAmount) * Math.pow(10, dstDecimals)));\n \n // Calculate minOutputAmount with slippage\n const slippageBps = params.maxSlippageBps || params.quote.slippageBps || 100;\n const minOutputAmountRaw = outputAmountRaw - (outputAmountRaw * BigInt(slippageBps) / 10000n);\n \n console.log(\"[swap_execute] Amount conversion:\", {\n inputAmount: params.quote.inputAmount,\n inputAmountRaw: inputAmountRaw.toString(),\n outputAmount: params.quote.outputAmount,\n outputAmountRaw: outputAmountRaw.toString(),\n minOutputAmountRaw: minOutputAmountRaw.toString(),\n srcDecimals,\n dstDecimals\n });\n \n // 6. Build intentParams (used for allowance check, approval, and swap)\n const intentParams = {\n srcAddress: walletAddress,\n dstAddress: params.quote.recipient || walletAddress,\n srcChain: toSodaxChainId(params.quote.srcChainId),\n dstChain: toSodaxChainId(params.quote.dstChainId),\n inputToken: srcTokenAddr,\n outputToken: dstTokenAddr,\n inputAmount: inputAmountRaw,\n minOutputAmount: minOutputAmountRaw,\n deadline: BigInt(params.quote.deadline),\n allowPartialFill: false,\n solver: \"0x0000000000000000000000000000000000000000\" as `0x${string}`,\n data: \"0x\" as `0x${string}`\n };\n \n // 7. Check allowance using SDK's expected API\n let allowanceValid = false;\n try {\n const allowanceResult = await (sodaxClient as any).swaps.isAllowanceValid({\n intentParams,\n spokeProvider\n });\n allowanceValid = allowanceResult?.ok ? allowanceResult.value : !!allowanceResult;\n } catch (e) {\n console.warn('[swap_execute] Allowance check failed, assuming approval needed:', e);\n allowanceValid = false;\n }\n \n // 8. Approve if needed using SDK's expected API\n if (!allowanceValid) {\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n message: 'Token approval required'\n });\n \n const approvalResult = await (sodaxClient as any).swaps.approve({\n intentParams,\n spokeProvider\n });\n \n const approvalTx = approvalResult?.ok ? approvalResult.value : approvalResult;\n \n // Wait for approval confirmation if possible\n if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt && approvalTx) {\n await (spokeProvider as any).walletProvider.waitForTransactionReceipt(approvalTx);\n }\n \n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n approvalTx: String(approvalTx),\n success: true\n });\n }\n \n // 9. Execute swap\n const swapResult = await (sodaxClient as any).swaps.swap({\n intentParams,\n spokeProvider,\n skipSimulation: params.skipSimulation || false,\n timeout: params.timeoutMs || 120000\n });\n \n // Handle Result type from SDK\n if (swapResult.ok === false) {\n const errorMsg = swapResult.error instanceof Error \n ? swapResult.error.message \n : typeof swapResult.error === 'string' \n ? swapResult.error \n : JSON.stringify(swapResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Swap failed: ${errorMsg}`);\n }\n \n const value = swapResult.ok ? swapResult.value : swapResult;\n \n // SDK may return [response, intent, deliveryInfo] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract internal tracking info\n const srcTxHash = deliveryInfo?.srcTxHash;\n const intentHash = toHexIntentHash((solverResponse as any)?.intent_hash) || toHexIntentHash(intent?.intentId);\n \n // Poll for delivery confirmation (wait up to 60s)\n let deliveryResult: { delivered: boolean; deliveryExplorer?: string } = { delivered: false };\n if (intentHash) {\n console.log('[swap_execute] Waiting for delivery confirmation...');\n deliveryResult = await pollForDelivery(intentHash, 60000, 3000);\n }\n \n // Build user-friendly result\n const result = {\n status: deliveryResult.delivered ? 'delivered' : 'submitted',\n message: deliveryResult.delivered \n ? 'Swap completed! Funds delivered to destination.' \n : 'Swap submitted, awaiting cross-chain delivery...',\n // User-friendly tracking link\n sodaxScanUrl: srcTxHash ? getSodaxScanUrl(srcTxHash) : undefined,\n // Source chain: where user initiated the swap\n // Destination chain: where user RECEIVED funds\n initiationTx: srcTxHash ? getExplorerLink(params.quote.srcChainId, srcTxHash) : undefined,\n receiptTx: deliveryResult.deliveryExplorer,\n };\n \n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n tokenAddresses: [params.quote.srcToken, params.quote.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Swap execution failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Status Tool\n// ============================================================================\n\n\nasync function handleSwapStatus(params: SwapStatusParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n if (!params.txHash && !params.intentHash) {\n throw new Error('Either txHash or intentHash must be provided');\n }\n \n // Use our SodaxApiClient for Backend API access (not SDK)\n const sodaxApi = getSodaxApiClient();\n \n let intentData: any = null;\n \n // Try intentHash first (most reliable)\n if (params.intentHash) {\n try {\n intentData = await sodaxApi.getIntentByHash(params.intentHash);\n } catch (err) {\n console.warn(`[swap_status] getIntentByHash failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n \n // If intentHash lookup failed or wasn't provided, try txHash\n // Note: txHash should be from the HUB chain (Sonic), not spoke chain\n if (!intentData && params.txHash) {\n try {\n intentData = await sodaxApi.getIntentByTxHash(params.txHash);\n } catch (err) {\n // txHash lookup failed - provide helpful error message\n const errorMsg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Unable to find intent for txHash: ${params.txHash}. ` +\n `Note: The txHash must be from the HUB chain (Sonic), not the spoke chain. ` +\n `If you have a spoke chain txHash (Base, Arbitrum, etc.), use amped_user_intents to find the intent first. ` +\n `Error: ${errorMsg}`\n );\n }\n }\n \n if (!intentData) {\n throw new Error('Unable to retrieve swap status. Provide a valid intentHash or hub chain txHash.');\n }\n \n // Extract intent details\n const intentHash = intentData.intentHash;\n const hubTxHash = intentData.txHash; // This is the hub chain tx that created the intent\n const isOpen = intentData.open;\n const intent = intentData.intent;\n \n // Determine status from intent state\n let status = 'unknown';\n if (intentData.open === true) {\n status = 'pending';\n } else if (intentData.open === false) {\n // Check events to determine if filled or cancelled/expired\n const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');\n const cancelledEvent = intentData.events?.find((e: any) => \n e.eventType === 'intent-cancelled' || e.eventType === 'intent-expired'\n );\n if (filledEvent) {\n status = 'filled';\n } else if (cancelledEvent) {\n status = cancelledEvent.eventType === 'intent-cancelled' ? 'cancelled' : 'expired';\n } else {\n status = 'closed';\n }\n }\n \n // Extract fulfillment details if available\n const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');\n const fulfillmentTxHash = filledEvent?.txHash;\n const receivedOutput = filledEvent?.intentState?.receivedOutput;\n \n // Build result\n const result: Record<string, unknown> = {\n status,\n intentHash,\n hubTxHash, // Hub chain tx that created the intent\n spokeTxHash: params.txHash !== hubTxHash ? params.txHash : undefined, // Original spoke tx if different\n open: isOpen,\n // Intent details\n srcChain: intent?.srcChain,\n dstChain: intent?.dstChain,\n inputToken: intent?.inputToken,\n outputToken: intent?.outputToken,\n inputAmount: intent?.inputAmount,\n minOutputAmount: intent?.minOutputAmount,\n receivedOutput: receivedOutput,\n deadline: intent?.deadline ? new Date(parseInt(intent.deadline) * 1000).toISOString() : undefined,\n createdAt: intentData.createdAt,\n // Fulfillment details\n fulfillmentTxHash,\n fulfillmentChain: intent?.dstChain,\n // Tracking links\n sodaxScanUrl: `https://sodaxscan.com/intents/${intentHash}`,\n };\n \n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash,\n txHash: params.txHash,\n status,\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash: params.intentHash,\n txHash: params.txHash,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to get swap status: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Cancel Tool\n// ============================================================================\n\nasync function handleSwapCancel(params: SwapCancelParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.intent.srcChainId, params.intent.srcToken);\n const dstTokenAddr = await resolveToken(params.intent.dstChainId, params.intent.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.intent.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.intent.dstChainId, dstTokenAddr);\n \n // Resolve wallet (validates it exists)\n await walletManager.resolve(params.walletId);\n \n // Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(\n params.walletId,\n params.srcChainId\n );\n \n // Construct intent object for cancellation\n const intent = {\n id: params.intent.id,\n srcChainId: params.intent.srcChainId,\n dstChainId: params.intent.dstChainId,\n srcToken: params.intent.srcToken,\n dstToken: params.intent.dstToken,\n amount: params.intent.amount,\n deadline: BigInt(params.intent.deadline),\n createdAt: Date.now(),\n status: 'pending'\n } as unknown as Intent;\n \n // Cancel the intent - SDK expects (intent, spokeProvider)\n const cancelResult = await (sodaxClient as any).swaps.cancelIntent(intent, spokeProvider);\n \n // Handle Result type\n if (cancelResult.ok === false) {\n throw new Error(`Cancel failed: ${serializeError(cancelResult.error)}`);\n }\n \n const cancelTx = cancelResult.ok ? cancelResult.value : cancelResult;\n const cancelTxHash = typeof cancelTx === 'string' ? cancelTx : String(cancelTx);\n \n // Wait for cancellation confirmation if possible\n // SDK may expose waitForTransactionReceipt on the underlying wallet provider\n if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt) {\n await (spokeProvider as any).walletProvider.waitForTransactionReceipt(cancelTxHash);\n }\n \n const result = {\n success: true,\n txHash: cancelTxHash,\n message: 'Intent cancelled successfully'\n };\n \n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n txHash: cancelTxHash,\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to cancel swap: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\nfunction generateRequestId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\n\nfunction calculateDeadline(secondsFromNow: number): number {\n return Math.floor(Date.now() / 1000) + secondsFromNow;\n}\n\ninterface LogEntry {\n requestId: string;\n opType: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokenAddresses?: string[];\n intentHash?: string;\n txHash?: string;\n spokeTxHash?: string;\n hubTxHash?: string;\n approvalTx?: string;\n status?: string;\n durationMs?: number;\n success?: boolean;\n error?: string;\n message?: string;\n intentId?: string;\n}\n\nfunction logStructured(entry: LogEntry): void {\n // Structured JSON logging for observability\n // Use replacer to handle BigInt serialization\n console.log(JSON.stringify({\n ...entry,\n timestamp: new Date().toISOString(),\n component: 'amped-defi-swap'\n }, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\nexport function registerSwapTools(agentTools: AgentTools): void {\n // Register swap quote tool\n agentTools.register({\n name: 'amped_swap_quote',\n summary: 'Get a swap quote for exact-in or exact-out swaps across chains',\n description: 'Retrieves a quote for swapping tokens across chains using the SODAX swap protocol. ' +\n 'Supports both exact input (specify input amount, get output estimate) and ' +\n 'exact output (specify desired output, get required input) modes.',\n schema: SwapQuoteRequestSchema,\n handler: handleSwapQuote\n });\n \n // Register swap execute tool\n agentTools.register({\n name: 'amped_swap_execute',\n summary: 'Execute a swap with policy enforcement and allowance handling',\n description: 'Executes a swap using a previously obtained quote. ' +\n 'Performs policy checks, validates allowances, approves tokens if needed, ' +\n 'and executes the swap transaction. Returns transaction hashes and intent status.',\n schema: SwapExecuteParamsSchema,\n handler: handleSwapExecute\n });\n \n // Register swap status tool\n agentTools.register({\n name: 'amped_swap_status',\n summary: 'Check the status of a swap intent or transaction',\n description: 'Polls the status of a swap by intent hash or transaction hash. ' +\n 'Returns current status, fill amount, error details if failed, and timing information.',\n schema: SwapStatusParamsSchema,\n handler: handleSwapStatus\n });\n \n // Register swap cancel tool\n agentTools.register({\n name: 'amped_swap_cancel',\n summary: 'Cancel an active swap intent',\n description: 'Cancels a pending swap intent on the source chain. ' +\n 'Requires the intent details and source chain ID. Returns cancellation transaction hash.',\n schema: SwapCancelParamsSchema,\n handler: handleSwapCancel\n });\n}\n\n// Silence unused variable warnings for result schemas (used for documentation)\nvoid SwapExecuteResultSchema;\nvoid SwapStatusResultSchema;\nvoid SwapCancelResultSchema;\n\n// Export schemas with consistent naming\nexport {\n SwapQuoteRequestSchema as SwapQuoteSchema,\n SwapExecuteParamsSchema as SwapExecuteSchema,\n SwapStatusParamsSchema as SwapStatusSchema,\n SwapCancelParamsSchema as SwapCancelSchema,\n};\n\n// Export handlers\nexport {\n handleSwapQuote,\n handleSwapExecute,\n handleSwapStatus,\n handleSwapCancel,\n};\n",
1236 "inputSchema": {},
1237 "outputSchema": null,
1238 "icons": null,
1239 "annotations": null,
1240 "meta": null,
1241 "execution": null
1242 },
1243 {
1244 "name": "bridge.ts",
1245 "title": null,
1246 "description": "Script: bridge.ts. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts \n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\n\nimport { Static, Type } from '@sinclair/typebox';\nimport { AgentTools, BridgeOperation } from '../types';\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\nimport { handleSwapQuote, handleSwapExecute } from './swap';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\nconst BridgeDiscoverSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID (e.g., \"ethereum\", \"arbitrum\")',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID (e.g., \"sonic\", \"optimism\")',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n});\n\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\nconst BridgeQuoteSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol',\n }),\n});\n\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\nconst BridgeExecuteSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet to use',\n }),\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol to bridge from',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol to bridge to',\n }),\n amount: Type.String({\n description: 'Amount to bridge in human-readable units (e.g., \"100.5\")',\n }),\n recipient: Type.Optional(\n Type.String({\n description: 'Recipient address on destination chain (defaults to wallet address)',\n })\n ),\n timeoutMs: Type.Optional(\n Type.Number({\n description: 'Timeout for bridge operation in milliseconds',\n default: 300000, // 5 minutes\n })\n ),\n policyId: Type.Optional(\n Type.String({\n description: 'Optional policy profile ID for custom limits',\n })\n ),\n});\n\n// Type inference from schemas\ntype BridgeDiscoverParams = Static<typeof BridgeDiscoverSchema>;\ntype BridgeQuoteParams = Static<typeof BridgeQuoteSchema>;\ntype BridgeExecuteParams = Static<typeof BridgeExecuteSchema>;\n\n// ============================================================================\n// Bridge Discover Tool\n// ============================================================================\n\n/**\n * Transaction result type for bridge execute\n */\ninterface TransactionResult {\n spokeTxHash: string;\n hubTxHash?: string;\n}\n\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\nasync function handleBridgeDiscover(\n params: BridgeDiscoverParams\n): Promise<{ bridgeableTokens: string[] }> {\n const { srcChainId, dstChainId, srcToken } = params;\n\n // Resolve token symbol to address\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n\n console.log('[bridge:discover] Discovering bridgeable tokens', {\n srcChainId,\n dstChainId,\n srcToken,\n });\n\n try {\n const sodax = getSodaxClient();\n\n // Get bridgeable tokens from SODAX SDK\n // SDK API: getBridgeableTokens(from: SpokeChainId, to: SpokeChainId, token: string)\n const result = sodax.bridge.getBridgeableTokens(\n toSodaxChainId(srcChainId) as any,\n toSodaxChainId(dstChainId) as any,\n srcTokenAddr\n );\n\n // Handle Result type - SDK returns Result<XToken[], unknown>\n if (!result.ok) {\n throw new Error(`Failed to get bridgeable tokens: ${serializeError((result as any).error) || 'Unknown error'}`);\n }\n\n const tokens = result.value;\n const bridgeableTokens = tokens.map((t: any) => t.address || t.symbol || String(t));\n\n console.log('[bridge:discover] Found bridgeable tokens', {\n count: bridgeableTokens.length,\n tokens: bridgeableTokens,\n });\n\n return { bridgeableTokens };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:discover] Failed to discover bridgeable tokens', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n });\n throw new Error(`Failed to discover bridgeable tokens: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Bridge Quote Tool\n// ============================================================================\n\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\nasync function handleBridgeQuote(\n params: BridgeQuoteParams\n): Promise<{ isBridgeable: boolean; maxBridgeableAmount: string }> {\n const { srcChainId, dstChainId, srcToken, dstToken } = params;\n\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n const dstTokenAddr = await resolveToken(dstChainId, dstToken);\n\n console.log('[bridge:quote] Checking bridge quote', {\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n\n try {\n const sodax = getSodaxClient();\n\n // Create XToken objects for the SDK\n const fromToken = { chainId: toSodaxChainId(srcChainId), address: srcTokenAddr } as any;\n const toToken = { chainId: toSodaxChainId(dstChainId), address: dstTokenAddr } as any;\n\n // Check if the route is bridgeable using isBridgeable\n // SDK may have different signature - adapting based on available methods\n let isBridgeable = false;\n try {\n // Try to get bridgeable tokens to check if route exists\n const result = sodax.bridge.getBridgeableTokens(\n toSodaxChainId(srcChainId) as any,\n toSodaxChainId(dstChainId) as any,\n srcTokenAddr\n );\n if (result.ok && result.value.length > 0) {\n isBridgeable = result.value.some((t: any) => \n t.address?.toLowerCase() === dstTokenAddr.toLowerCase() ||\n t === dstTokenAddr\n );\n }\n } catch {\n isBridgeable = false;\n }\n\n // Get maximum bridgeable amount\n let maxBridgeableAmount = '0';\n if (isBridgeable) {\n try {\n // SDK API: getBridgeableAmount(from: XToken, to: XToken)\n const maxAmountResult = await sodax.bridge.getBridgeableAmount(fromToken, toToken);\n if (maxAmountResult.ok) {\n const val = maxAmountResult.value as any;\n // BridgeLimit may have different property names depending on SDK version\n maxBridgeableAmount = val?.max?.toString() || \n val?.maxAmount?.toString() ||\n val?.limit?.toString() ||\n val?.toString() || '0';\n }\n } catch (e) {\n console.warn('[bridge:quote] Could not get max bridgeable amount:', e);\n }\n }\n\n console.log('[bridge:quote] Bridge quote result', {\n isBridgeable,\n maxBridgeableAmount,\n });\n\n return { isBridgeable, maxBridgeableAmount };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:quote] Failed to get bridge quote', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n throw new Error(`Failed to get bridge quote: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Bridge Execute Tool (Delegates to Swap)\n// ============================================================================\n\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\nasync function handleBridgeExecute(\n params: BridgeExecuteParams\n): Promise<TransactionResult> {\n const {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n recipient,\n timeoutMs = 300000,\n policyId,\n } = params;\n\n console.log('[bridge:execute] Delegating to swap infrastructure', {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n });\n\n try {\n // Step 1: Get a swap quote for this bridge route\n const quoteResult = await handleSwapQuote({\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n type: 'exact_input',\n slippageBps: 100, // 1% slippage for bridges\n recipient,\n });\n\n console.log('[bridge:execute] Got swap quote', quoteResult);\n\n // Step 2: Execute the swap\n const swapResult = await handleSwapExecute({\n walletId,\n quote: {\n srcChainId,\n dstChainId,\n srcToken: String(quoteResult.srcToken),\n dstToken: String(quoteResult.dstToken),\n inputAmount: String(quoteResult.inputAmount),\n outputAmount: String(quoteResult.outputAmount),\n slippageBps: Number(quoteResult.slippageBps),\n deadline: Number(quoteResult.deadline),\n recipient,\n },\n policyId,\n timeoutMs,\n });\n\n console.log('[bridge:execute] Swap executed', swapResult);\n\n // Map swap result to bridge result format\n return {\n spokeTxHash: String(swapResult.initiationTx || swapResult.spokeTxHash || ''),\n hubTxHash: swapResult.hubTxHash ? String(swapResult.hubTxHash) : undefined,\n status: String(swapResult.status),\n message: swapResult.message ? String(swapResult.message) : 'Bridge executed via swap infrastructure',\n sodaxScanUrl: swapResult.sodaxScanUrl ? String(swapResult.sodaxScanUrl) : undefined,\n } as TransactionResult;\n } catch (error) {\n const errorMessage = serializeError(error);\n console.error('[bridge:execute] Bridge via swap failed:', errorMessage);\n throw new Error(`Bridge execution failed: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport function registerBridgeTools(agentTools: AgentTools): void {\n // Register bridge discover tool\n agentTools.register({\n name: 'amped_bridge_discover',\n summary: 'Discover bridgeable tokens for a given source chain and token',\n description:\n 'Retrieves a list of tokens that can be bridged from the specified source chain ' +\n 'to the destination chain, starting from a specific source token. ' +\n 'Use this to find valid bridge routes before requesting a quote.',\n schema: BridgeDiscoverSchema,\n handler: handleBridgeDiscover,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_discover');\n\n // Register bridge quote tool\n agentTools.register({\n name: 'amped_bridge_quote',\n summary: 'Check bridgeability and get maximum bridgeable amount',\n description:\n 'Validates whether a specific bridge route (source chain/token \u2192 destination chain/token) ' +\n 'is supported and returns the maximum amount that can be bridged. ' +\n 'Always call this before executing a bridge to verify the route is valid.',\n schema: BridgeQuoteSchema,\n handler: handleBridgeQuote,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_quote');\n\n // Register bridge execute tool\n agentTools.register({\n name: 'amped_bridge_execute',\n summary: 'Execute a cross-chain bridge operation',\n description:\n 'Executes a bridge operation that moves tokens from a source chain to a destination chain. ' +\n 'This tool handles the complete flow: policy validation, allowance checking, ' +\n 'token approval (if needed), and bridge execution. ' +\n 'Returns transaction hashes for both the spoke chain and hub chain.',\n schema: BridgeExecuteSchema,\n handler: handleBridgeExecute,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_execute');\n}\n\n// Export schemas for testing and reuse\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\n\n// Export handlers\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n",
1247 "inputSchema": {},
1248 "outputSchema": null,
1249 "icons": null,
1250 "annotations": null,
1251 "meta": null,
1252 "execution": null
1253 },
1254 {
1255 "name": "spokeProviderFactory.ts",
1256 "title": null,
1257 "description": "Script: spokeProviderFactory.ts. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n * \n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\n\n// Official SDK wallet provider\nimport { EvmWalletProvider } from '@sodax/wallet-sdk-core';\n\n// Import spoke providers and chain config from SDK\nimport { \n EvmSpokeProvider, \n SonicSpokeProvider,\n type SpokeProvider \n} from '@sodax/sdk';\n\n// Import chain configuration from types\nimport { spokeChainConfig, type SpokeChainId } from '@sodax/types';\n\n// Import wallet management\nimport { getWalletManager, type IWalletBackend, createBankrWalletProvider } from '../wallet';\nimport { getWalletAdapter } from '../wallet/skillWalletAdapter';\nimport { BANKR_CHAIN_IDS, normalizeChainId, getBankrChainId } from '../wallet/types';\n\n// Cache for providers: Map<cacheKey, SpokeProvider>\nconst providerCache = new Map<string, SpokeProvider>();\n\n// Sonic hub chain identifier\nconst SONIC_CHAIN_ID = 'sonic';\n\n// Chain ID mapping for SDK (some chains need specific format)\nconst CHAIN_ID_MAP: Record<string, SpokeChainId> = {\n 'sonic': 'sonic',\n 'ethereum': 'ethereum',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n} as Record<string, SpokeChainId>;\n\n/**\n * Get RPC URL for a chain\n */\nasync function getRpcUrl(chainId: string): Promise<string> {\n const skillAdapter = getWalletAdapter();\n return skillAdapter.getRpcUrl(chainId);\n}\n\n/**\n * Get the SDK chain ID for a given chain\n */\nfunction getSdkChainId(chainId: string): SpokeChainId {\n return (CHAIN_ID_MAP[chainId] || chainId) as SpokeChainId;\n}\n\n/**\n * Validate that wallet supports the requested chain\n */\nfunction validateChainSupport(wallet: IWalletBackend, chainId: string): void {\n const normalizedForWallet = normalizeChainId(chainId);\n if (!wallet.supportsChain(normalizedForWallet)) {\n throw new Error(\n `Wallet \"${wallet.nickname}\" doesn't support chain \"${chainId}\". ` +\n `Supported chains: ${wallet.supportedChains.join(', ')}. ` +\n `Try a different wallet.`\n );\n }\n}\n\n/**\n * Create a spoke provider for local key signing\n */\nasync function createLocalSpokeProvider(\n wallet: IWalletBackend,\n chainId: string,\n rpcUrl: string\n): Promise<SpokeProvider> {\n if (!wallet.getPrivateKey) {\n throw new Error(`Wallet \"${wallet.nickname}\" does not support local signing`);\n }\n\n const privateKey = await wallet.getPrivateKey();\n const sdkChainId = getSdkChainId(chainId);\n\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}. Available: ${Object.keys(spokeChainConfig).join(', ')}`);\n }\n\n // Create wallet provider using official SDK\n const walletProvider = new EvmWalletProvider({\n privateKey,\n chainId: sdkChainId,\n rpcUrl: rpcUrl as `http${string}`,\n });\n\n // Use SonicSpokeProvider for Sonic hub chain, EvmSpokeProvider for others\n if (chainId === SONIC_CHAIN_ID) {\n console.log('[spokeProviderFactory] Creating SonicSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n });\n\n return new SonicSpokeProvider(\n walletProvider,\n chainConfig as any,\n rpcUrl\n );\n } else {\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n });\n\n return new EvmSpokeProvider(\n walletProvider,\n chainConfig as any,\n rpcUrl\n );\n }\n}\n\n/**\n * Create a spoke provider for Bankr wallet\n * Uses BankrWalletProvider which submits transactions to Bankr API\n */\nasync function createBankrSpokeProvider(\n wallet: IWalletBackend,\n chainId: string,\n rpcUrl: string\n): Promise<SpokeProvider> {\n const sdkChainId = getSdkChainId(chainId);\n \n // Normalize chain ID for Bankr lookup (0x2105.base -> base)\n const normalizedChainId = normalizeChainId(chainId);\n const numericChainId = getBankrChainId(normalizedChainId);\n \n console.log('[spokeProviderFactory] Bankr chain resolution', {\n input: chainId,\n normalized: normalizedChainId,\n numeric: numericChainId,\n });\n\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}`);\n }\n\n // Get Bankr API key from environment\n const apiKey = process.env.BANKR_API_KEY;\n if (!apiKey) {\n throw new Error('BANKR_API_KEY environment variable not set');\n }\n\n // Get the Bankr wallet address (cached after first call)\n const walletAddress = await wallet.getAddress();\n\n // Create BankrWalletProvider which implements IEvmWalletProvider\n const walletProvider = createBankrWalletProvider({\n apiKey,\n apiUrl: process.env.BANKR_API_URL,\n chainId: numericChainId,\n rpcUrl,\n cachedAddress: walletAddress,\n });\n\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider with Bankr backend', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n address: walletAddress?.slice(0, 10) + '...',\n });\n\n // Use standard EvmSpokeProvider with our BankrWalletProvider\n // The SDK doesn't care how transactions are signed - it just calls the interface methods\n return new EvmSpokeProvider(\n walletProvider as any, // BankrWalletProvider implements IEvmWalletProvider\n chainConfig as any,\n rpcUrl\n );\n}\n\n/**\n * Create a spoke provider for the given wallet and chain\n * \n * @param walletId - Wallet nickname (e.g., \"main\", \"bankr\", \"trading\")\n * @param chainId - Chain identifier (e.g., \"ethereum\", \"base\")\n */\nasync function createSpokeProvider(\n walletId: string,\n chainId: string\n): Promise<SpokeProvider> {\n // Get wallet from unified manager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n \n // Validate chain support\n validateChainSupport(wallet, chainId);\n\n const rpcUrl = await getRpcUrl(chainId);\n\n // Route based on wallet type\n if (wallet.type === 'bankr') {\n // Use BankrWalletProvider for Bankr wallets\n return createBankrSpokeProvider(wallet, chainId, rpcUrl);\n }\n\n // Local key signing (evm-wallet-skill or env)\n return createLocalSpokeProvider(wallet, chainId, rpcUrl);\n}\n\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport async function getSpokeProvider(\n walletId: string,\n chainId: string,\n raw = false\n): Promise<SpokeProvider> {\n const cacheKey = `${walletId}:${chainId}`;\n\n // Check cache\n const cached = providerCache.get(cacheKey);\n if (cached) {\n console.log('[spokeProviderFactory] Using cached provider', {\n walletId,\n chainId,\n });\n return cached;\n }\n\n // Create new provider\n const provider = await createSpokeProvider(walletId, chainId);\n\n // Cache the provider\n providerCache.set(cacheKey, provider);\n\n return provider;\n}\n\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport function clearProviderCache(): void {\n providerCache.clear();\n console.log('[spokeProviderFactory] Provider cache cleared');\n}\n\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport function getCacheStats(): { size: number; keys: string[] } {\n return {\n size: providerCache.size,\n keys: Array.from(providerCache.keys()),\n };\n}\n\n// Export the type for use in other modules\nexport type { SpokeProvider };\n",
1258 "inputSchema": {},
1259 "outputSchema": null,
1260 "icons": null,
1261 "annotations": null,
1262 "meta": null,
1263 "execution": null
1264 },
1265 {
1266 "name": "policyEngine.ts",
1267 "title": null,
1268 "description": "Script: policyEngine.ts. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\n\nimport { BridgeOperation, PolicyConfig } from '../types';\nimport { normalizeChainId } from '../wallet/types';\n\n/**\n * Policy check result\n */\nexport interface PolicyCheckResult {\n allowed: boolean;\n reason?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport class PolicyEngine {\n private config: PolicyConfig;\n\n constructor(policyId?: string) {\n this.config = this.loadPolicyConfig(policyId);\n }\n\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n private loadPolicyConfig(policyId?: string): PolicyConfig {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n\n if (!limitsJson) {\n return {};\n }\n\n try {\n const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;\n\n // If policyId is specified, use that config; otherwise use 'default' or empty\n const config = policyId\n ? allConfigs[policyId]\n : allConfigs['default'] || allConfigs;\n\n if (policyId && !config) {\n return allConfigs['default'] || {};\n }\n\n return config || {};\n } catch (_error) {\n return {};\n }\n }\n\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n private checkChainAllowed(chainId: string): PolicyCheckResult {\n const { allowedChains } = this.config;\n\n if (allowedChains && allowedChains.length > 0) {\n const normalizedChain = normalizeChainId(chainId);\n if (!allowedChains.includes(normalizedChain)) {\n return {\n allowed: false,\n reason: `Chain not allowed: ${chainId}. Allowed chains: ${allowedChains.join(', ')}`,\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n private checkTokenAllowed(chainId: string, token: string): PolicyCheckResult {\n const { allowedTokensByChain } = this.config;\n\n if (allowedTokensByChain) {\n const normalizedChainForTokens = normalizeChainId(chainId);\n const allowedTokens = allowedTokensByChain[normalizedChainForTokens];\n if (allowedTokens && allowedTokens.length > 0) {\n if (!allowedTokens.includes(token)) {\n return {\n allowed: false,\n reason: `Token not allowed on ${chainId}: ${token}. Allowed tokens: ${allowedTokens.join(', ')}`,\n };\n }\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n private checkRecipientNotBlocked(recipient: string): PolicyCheckResult {\n const { blockedRecipients } = this.config;\n\n if (blockedRecipients && blockedRecipients.length > 0) {\n if (blockedRecipients.includes(recipient.toLowerCase())) {\n return {\n allowed: false,\n reason: `Recipient is blocked: ${recipient}`,\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n private checkBridgeAmount(token: string, amount: string): PolicyCheckResult {\n const { maxBridgeAmountToken } = this.config;\n\n if (maxBridgeAmountToken) {\n const maxAmount = maxBridgeAmountToken[token];\n if (maxAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxAmount) {\n return {\n allowed: false,\n reason: `Bridge amount ${amount} exceeds maximum ${maxAmount} for token ${token}`,\n details: { maxAllowed: maxAmount, requested: amountNum },\n };\n }\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n async checkBridge(operation: BridgeOperation): Promise<PolicyCheckResult> {\n const { srcChainId, dstChainId, srcToken, dstToken, amount, recipient } = operation;\n\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed) return srcChainCheck;\n\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed) return srcTokenCheck;\n\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed) return dstTokenCheck;\n\n // Check amount limits\n const amountCheck = this.checkBridgeAmount(srcToken, amount);\n if (!amountCheck.allowed) return amountCheck;\n\n // Check recipient if specified\n if (recipient) {\n const recipientCheck = this.checkRecipientNotBlocked(recipient);\n if (!recipientCheck.allowed) return recipientCheck;\n }\n\n return { allowed: true };\n }\n\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig(): PolicyConfig {\n return { ...this.config };\n }\n\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies(): string[] {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson) return [];\n \n try {\n const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;\n return Object.keys(allConfigs);\n } catch {\n return [];\n }\n }\n\n // ============================================================================\n // Swap Policy Checks\n // ============================================================================\n\n /**\n * Check swap input amount against USD limits\n */\n private checkSwapAmount(inputAmount: string, srcToken: string): PolicyCheckResult {\n const { maxSwapInputUsd, maxSwapInputToken } = this.config;\n\n // Check per-token limit if configured\n if (maxSwapInputToken) {\n const maxTokenAmount = maxSwapInputToken[srcToken];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(inputAmount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Swap input amount ${inputAmount} exceeds maximum ${maxTokenAmount} for token ${srcToken}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n\n // Note: USD limit check would require price oracle integration\n // For now, we skip enforcement without prices\n\n return { allowed: true };\n }\n\n /**\n * Check slippage against maximum allowed\n */\n private checkSlippage(slippageBps: number): PolicyCheckResult {\n const { maxSlippageBps } = this.config;\n\n if (maxSlippageBps !== undefined && slippageBps > maxSlippageBps) {\n return {\n allowed: false,\n reason: `Slippage ${slippageBps} bps exceeds maximum allowed ${maxSlippageBps} bps`,\n details: { maxAllowed: maxSlippageBps, requested: slippageBps },\n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a swap operation against all policies\n */\n async checkSwap(params: {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n inputAmount: string;\n slippageBps: number;\n policyId?: string;\n }): Promise<PolicyCheckResult> {\n const { srcChainId, dstChainId, srcToken, dstToken, inputAmount, slippageBps } = params;\n\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed) return srcChainCheck;\n\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed) return srcTokenCheck;\n\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed) return dstTokenCheck;\n\n // Check swap amount limits\n const amountCheck = this.checkSwapAmount(inputAmount, srcToken);\n if (!amountCheck.allowed) return amountCheck;\n\n // Check slippage\n const slippageCheck = this.checkSlippage(slippageBps);\n if (!slippageCheck.allowed) return slippageCheck;\n\n return { allowed: true };\n }\n\n // ============================================================================\n // Money Market Policy Checks\n // ============================================================================\n\n /**\n * Check borrow amount against limits\n */\n private checkBorrowAmount(token: string, amount: string, amountUsd?: number): PolicyCheckResult {\n const { maxBorrowUsd, maxBorrowToken } = this.config;\n\n // Check per-token limit if configured\n if (maxBorrowToken) {\n const maxTokenAmount = maxBorrowToken[token];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Borrow amount ${amount} exceeds maximum ${maxTokenAmount} for token ${token}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n\n // Check USD limit if amountUsd is provided\n if (maxBorrowUsd !== undefined && amountUsd !== undefined) {\n if (amountUsd > maxBorrowUsd) {\n return {\n allowed: false,\n reason: `Borrow amount $${amountUsd} exceeds maximum $${maxBorrowUsd}`,\n details: { maxAllowed: maxBorrowUsd, requested: amountUsd },\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a money market operation against all policies\n */\n async checkMoneyMarket(params: {\n walletId: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n amountUsd?: number;\n operation: 'supply' | 'withdraw' | 'borrow' | 'repay';\n policyId?: string;\n }): Promise<PolicyCheckResult> {\n const { chainId, dstChainId, token, amount, amountUsd, operation } = params;\n\n // Check source chain\n const chainCheck = this.checkChainAllowed(chainId);\n if (!chainCheck.allowed) return chainCheck;\n\n // Check destination chain if cross-chain operation\n if (dstChainId) {\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n }\n\n // Check token\n const tokenCheck = this.checkTokenAllowed(chainId, token);\n if (!tokenCheck.allowed) return tokenCheck;\n\n // Operation-specific checks\n if (operation === 'borrow') {\n const borrowCheck = this.checkBorrowAmount(token, amount, amountUsd);\n if (!borrowCheck.allowed) return borrowCheck;\n }\n\n return { allowed: true };\n }\n}\n",
1269 "inputSchema": {},
1270 "outputSchema": null,
1271 "icons": null,
1272 "annotations": null,
1273 "meta": null,
1274 "execution": null
1275 },
1276 {
1277 "name": "types.ts",
1278 "title": null,
1279 "description": "Script: types.ts. Code:\n/**\n * Common types for Amped DeFi plugin\n */\n\nimport { Static, TSchema } from '@sinclair/typebox';\n\n/**\n * Tool handler function type\n */\nexport type ToolHandler<T extends TSchema> = (params: Static<T>) => Promise<unknown>;\n\n/**\n * Agent tools registry interface\n */\nexport interface AgentTools {\n register: <T extends TSchema>(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: T;\n handler: ToolHandler<T>;\n }) => void;\n}\n\n/**\n * Alternative registration interface with input/output schemas\n */\nexport interface AgentToolsTyped {\n register<T extends TSchema, R extends TSchema>(config: {\n name: string;\n summary: string;\n description?: string;\n schema: {\n input: T;\n output?: R;\n };\n handler: (input: Static<T>) => Promise<Static<R>>;\n }): void;\n}\n\n/**\n * Standard tool result wrapper\n */\nexport interface ToolResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\n/**\n * Transaction status\n */\nexport type TransactionStatus = \n | 'pending'\n | 'submitted'\n | 'confirmed'\n | 'failed'\n | 'cancelled'\n | 'unknown';\n\n/**\n * Intent status from SODAX\n */\nexport interface IntentStatus {\n intentHash: string;\n status: 'pending' | 'filled' | 'cancelled' | 'expired' | 'failed';\n spokeTxHash?: string;\n hubTxHash?: string;\n filledAmount?: string;\n error?: string;\n createdAt?: number;\n updatedAt?: number;\n}\n\n/**\n * Quote result\n */\nexport interface QuoteResult {\n quoteId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n srcAmount: string;\n dstAmount: string;\n minDstAmount?: string;\n slippageBps: number;\n deadline: number;\n fees: {\n solverFee?: string;\n partnerFee?: string;\n gasFee?: string;\n };\n route?: unknown;\n}\n\n/**\n * Bridge result\n */\nexport interface BridgeResult {\n spokeTxHash: string;\n hubTxHash?: string;\n status: TransactionStatus;\n}\n\n/**\n * Money market position\n */\nexport interface MoneyMarketPosition {\n token: string;\n supplied: string;\n borrowed: string;\n supplyApy: number;\n borrowApy: number;\n collateralFactor: number;\n}\n\n/**\n * Money market reserve\n */\nexport interface MoneyMarketReserve {\n token: string;\n totalSupplied: string;\n totalBorrowed: string;\n supplyApy: number;\n borrowApy: number;\n utilizationRate: number;\n collateralFactor: number;\n liquidationThreshold: number;\n}\n\n/**\n * Chain configuration\n */\nexport interface ChainConfig {\n chainId: string;\n name: string;\n isHub: boolean;\n nativeCurrency: {\n symbol: string;\n decimals: number;\n };\n rpcUrl?: string;\n}\n\n/**\n * Token configuration\n */\nexport interface TokenConfig {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n chainId: string;\n}\n\n/**\n * Wallet information (safe to log)\n */\nexport interface WalletInfo {\n walletId: string;\n address: string;\n mode: 'execute' | 'prepare';\n chains: string[];\n}\n\n/**\n * Operation context for logging\n */\nexport interface OperationContext {\n requestId: string;\n agentId?: string;\n walletId: string;\n operation: string;\n chainIds: string[];\n tokenAddresses?: string[];\n timestamp: number;\n}\n\n/**\n * Bridge operation parameters\n */\nexport interface BridgeOperation {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n amount: string;\n recipient?: string;\n timeoutMs?: number;\n policyId?: string;\n}\n\n/**\n * Wallet configuration\n */\nexport interface WalletConfig {\n address: string;\n privateKey?: string;\n}\n\n/**\n * Policy configuration\n */\nexport interface PolicyConfig {\n /** Maximum USD value for swap inputs */\n maxSwapInputUsd?: number;\n /** Maximum per-token amount for swap inputs */\n maxSwapInputToken?: Record<string, number>;\n /** Maximum per-token amount for bridge operations */\n maxBridgeAmountToken?: Record<string, number>;\n /** Maximum USD value for borrows */\n maxBorrowUsd?: number;\n /** Maximum per-token amount for borrows */\n maxBorrowToken?: Record<string, number>;\n /** Allowed chain IDs for operations */\n allowedChains?: string[];\n /** Allowed tokens per chain */\n allowedTokensByChain?: Record<string, string[]>;\n /** Blocked recipient addresses */\n blockedRecipients?: string[];\n /** Maximum slippage in basis points (100 = 1%) */\n maxSlippageBps?: number;\n /** Whether to require transaction simulation */\n requireSimulation?: boolean;\n}\n",
1280 "inputSchema": {},
1281 "outputSchema": null,
1282 "icons": null,
1283 "annotations": null,
1284 "meta": null,
1285 "execution": null
1286 },
1287 {
1288 "name": "positionAggregator.test.ts",
1289 "title": null,
1290 "description": "Script: positionAggregator.test.ts. Code:\n/**\n * Position Aggregator Tests\n */\n\nimport {\n formatHealthFactor,\n getHealthFactorStatus,\n getPositionRecommendation,\n TokenPosition,\n CrossChainPositionView,\n} from '../utils/positionAggregator';\n\n// Mock dependencies\njest.mock('../sodax/client');\njest.mock('../providers/spokeProviderFactory');\njest.mock('../wallet/walletRegistry');\n\ndescribe('Position Aggregator Utilities', () => {\n describe('formatHealthFactor', () => {\n it('should format finite health factors', () => {\n expect(formatHealthFactor(1.5)).toBe('1.50');\n expect(formatHealthFactor(2.345)).toBe('2.35');\n expect(formatHealthFactor(0.95)).toBe('0.95');\n });\n\n it('should format infinity', () => {\n expect(formatHealthFactor(Infinity)).toBe('\u221e');\n });\n\n it('should handle null', () => {\n expect(formatHealthFactor(null)).toBe('N/A');\n });\n });\n\n describe('getHealthFactorStatus', () => {\n it('should return critical for HF < 1.1', () => {\n expect(getHealthFactorStatus(1.0)).toEqual({ status: 'critical', color: 'red' });\n expect(getHealthFactorStatus(1.05)).toEqual({ status: 'critical', color: 'red' });\n });\n\n it('should return danger for HF 1.1-1.5', () => {\n expect(getHealthFactorStatus(1.2)).toEqual({ status: 'danger', color: 'orange' });\n expect(getHealthFactorStatus(1.49)).toEqual({ status: 'danger', color: 'orange' });\n });\n\n it('should return caution for HF 1.5-2', () => {\n expect(getHealthFactorStatus(1.6)).toEqual({ status: 'caution', color: 'yellow' });\n expect(getHealthFactorStatus(1.9)).toEqual({ status: 'caution', color: 'yellow' });\n });\n\n it('should return healthy for HF >= 2', () => {\n expect(getHealthFactorStatus(2.0)).toEqual({ status: 'healthy', color: 'green' });\n expect(getHealthFactorStatus(5.0)).toEqual({ status: 'healthy', color: 'green' });\n expect(getHealthFactorStatus(Infinity)).toEqual({ status: 'healthy', color: 'green' });\n });\n\n it('should return healthy for null', () => {\n expect(getHealthFactorStatus(null)).toEqual({ status: 'healthy', color: 'green' });\n });\n });\n\n describe('getPositionRecommendation', () => {\n it('should warn about low health factor', () => {\n const view = createMockView({\n healthFactor: 1.3,\n availableBorrowUsd: 0,\n utilizationRate: 60,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('Health factor is low'))).toBe(true);\n });\n\n it('should suggest available borrowing power', () => {\n const view = createMockView({\n healthFactor: 2.5,\n availableBorrowUsd: 5000,\n utilizationRate: 40,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('borrowing power'))).toBe(true);\n });\n\n it('should warn about high utilization', () => {\n const view = createMockView({\n healthFactor: 1.8,\n availableBorrowUsd: 100,\n utilizationRate: 85,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('High collateral utilization'))).toBe(true);\n });\n\n it('should warn about negative net APY', () => {\n const view = createMockView({\n healthFactor: 2.0,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: -0.01,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('borrowing costs exceed'))).toBe(true);\n });\n\n it('should mention cross-chain positions', () => {\n const view = createMockView({\n healthFactor: 2.0,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: 0.02,\n chainCount: 3,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('chains'))).toBe(true);\n });\n\n it('should return empty array for healthy position', () => {\n const view = createMockView({\n healthFactor: 2.5,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: 0.05,\n chainCount: 1,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.length).toBe(0);\n });\n });\n});\n\n// Helper function to create mock views\nfunction createMockView(params: {\n healthFactor: number;\n availableBorrowUsd: number;\n utilizationRate: number;\n netApy: number;\n chainCount?: number;\n}): CrossChainPositionView {\n const chainSummaries = Array(params.chainCount || 1).fill(null).map((_, i) => ({\n chainId: `chain${i}`,\n supplyUsd: 10000,\n borrowUsd: 5000,\n netWorthUsd: 5000,\n healthFactor: params.healthFactor,\n positionCount: 2,\n }));\n\n return {\n walletId: 'test',\n address: '0x123',\n timestamp: new Date().toISOString(),\n summary: {\n totalSupplyUsd: chainSummaries.reduce((s, c) => s + c.supplyUsd, 0),\n totalBorrowUsd: chainSummaries.reduce((s, c) => s + c.borrowUsd, 0),\n netWorthUsd: chainSummaries.reduce((s, c) => s + c.netWorthUsd, 0),\n availableBorrowUsd: params.availableBorrowUsd,\n healthFactor: params.healthFactor,\n liquidationRisk: params.healthFactor < 1.5 ? 'medium' : 'none',\n weightedSupplyApy: 0.05,\n weightedBorrowApy: 0.03,\n netApy: params.netApy,\n },\n chainSummaries,\n positions: [],\n collateralUtilization: {\n totalCollateralUsd: 10000,\n usedCollateralUsd: params.utilizationRate * 100,\n availableCollateralUsd: 10000 - params.utilizationRate * 100,\n utilizationRate: params.utilizationRate,\n },\n riskMetrics: {\n maxLtv: 0.8,\n currentLtv: 0.5,\n bufferUntilLiquidation: 30,\n safeMaxBorrowUsd: 8000,\n },\n };\n}\n\ndescribe('Position Calculations', () => {\n const mockPositions: TokenPosition[] = [\n {\n chainId: 'ethereum',\n token: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n symbol: 'USDC',\n name: 'USD Coin',\n decimals: 6,\n },\n supply: {\n balance: '10000',\n balanceUsd: '10000',\n balanceRaw: '10000000000',\n apy: 0.05,\n isCollateral: true,\n },\n borrow: {\n balance: '0',\n balanceUsd: '0',\n balanceRaw: '0',\n apy: 0,\n },\n loanToValue: 0.8,\n liquidationThreshold: 0.85,\n },\n {\n chainId: 'ethereum',\n token: {\n address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',\n symbol: 'WETH',\n name: 'Wrapped Ether',\n decimals: 18,\n },\n supply: {\n balance: '0',\n balanceUsd: '0',\n balanceRaw: '0',\n apy: 0,\n isCollateral: false,\n },\n borrow: {\n balance: '2',\n balanceUsd: '5000',\n balanceRaw: '2000000000000000000',\n apy: 0.03,\n },\n loanToValue: 0.75,\n liquidationThreshold: 0.8,\n },\n ];\n\n it('should calculate total supply correctly', () => {\n const totalSupply = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'),\n 0\n );\n expect(totalSupply).toBe(10000);\n });\n\n it('should calculate total borrow correctly', () => {\n const totalBorrow = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'),\n 0\n );\n expect(totalBorrow).toBe(5000);\n });\n\n it('should identify collateral assets', () => {\n const collateralPositions = mockPositions.filter(p => p.supply.isCollateral);\n expect(collateralPositions).toHaveLength(1);\n expect(collateralPositions[0].token.symbol).toBe('USDC');\n });\n\n it('should calculate weighted APY correctly', () => {\n const totalSupply = 10000;\n const weightedSupplyApy = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.supply.balanceUsd || '0') * p.supply.apy,\n 0\n ) / totalSupply;\n \n expect(weightedSupplyApy).toBe(0.05);\n });\n});\n",
1291 "inputSchema": {},
1292 "outputSchema": null,
1293 "icons": null,
1294 "annotations": null,
1295 "meta": null,
1296 "execution": null
1297 },
1298 {
1299 "name": "sodaxApi.test.ts",
1300 "title": null,
1301 "description": "Script: sodaxApi.test.ts. Code:\n/**\n * SODAX API Client Tests\n */\n\nimport { SodaxApiClient, getSodaxApiClient, resetSodaxApiClient } from '../utils/sodaxApi';\nimport { AmpedDefiError, ErrorCode } from '../utils/errors';\n\n// Mock fetch\nglobal.fetch = jest.fn();\n\ndescribe('SodaxApiClient', () => {\n beforeEach(() => {\n jest.resetAllMocks();\n resetSodaxApiClient();\n });\n\n describe('Configuration', () => {\n it('should use default base URL', () => {\n const client = new SodaxApiClient();\n expect(client).toBeDefined();\n });\n\n it('should use custom base URL', () => {\n const client = new SodaxApiClient({ baseUrl: 'https://custom.api.com' });\n expect(client).toBeDefined();\n });\n\n it('should use environment variable for base URL', () => {\n process.env.SODAX_API_URL = 'https://env.api.com';\n const client = new SodaxApiClient();\n expect(client).toBeDefined();\n delete process.env.SODAX_API_URL;\n });\n });\n\n describe('Address Validation', () => {\n it('should reject invalid addresses', async () => {\n const client = new SodaxApiClient();\n \n await expect(client.getUserIntents('invalid-address'))\n .rejects\n .toThrow(AmpedDefiError);\n });\n\n it('should accept valid addresses', async () => {\n const mockResponse = {\n items: [],\n total: 0,\n offset: 0,\n limit: 50,\n };\n\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => mockResponse,\n });\n\n const client = new SodaxApiClient();\n const result = await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n \n expect(result).toEqual(mockResponse);\n });\n });\n\n describe('API Requests', () => {\n it('should fetch user intents with default pagination', async () => {\n const mockResponse = {\n items: [\n {\n intentHash: '0xabc',\n txHash: '0xdef',\n chainId: 146,\n open: true,\n intent: {\n inputToken: '0xinput',\n outputToken: '0xoutput',\n inputAmount: '1000',\n },\n events: [],\n createdAt: '2025-01-01T00:00:00Z',\n },\n ],\n total: 1,\n offset: 0,\n limit: 50,\n };\n\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => mockResponse,\n });\n\n const client = new SodaxApiClient();\n const result = await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n expect(result.items).toHaveLength(1);\n expect(result.total).toBe(1);\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('/v1/be/intent/user/0xf48cd107faaa95de81afc2436e0a044196e21825'),\n expect.any(Object)\n );\n });\n\n it('should apply pagination parameters', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 100, offset: 10, limit: 20 }),\n });\n\n const client = new SodaxApiClient();\n await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825', {\n offset: 10,\n limit: 20,\n });\n\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('offset=10'),\n expect.any(Object)\n );\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('limit=20'),\n expect.any(Object)\n );\n });\n\n it('should apply filters', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825', {}, {\n open: true,\n srcChain: 1,\n inputToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n });\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=true');\n expect(callUrl).toContain('srcChain=1');\n expect(callUrl).toContain('inputToken=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48');\n });\n\n it('should handle API errors', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: false,\n status: 500,\n statusText: 'Internal Server Error',\n text: async () => 'Server error',\n });\n\n const client = new SodaxApiClient();\n \n await expect(client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825'))\n .rejects\n .toThrow(AmpedDefiError);\n });\n });\n\n describe('Convenience Methods', () => {\n it('should get open intents', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getOpenIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=true');\n });\n\n it('should get intent history (closed)', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getIntentHistory('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=false');\n });\n });\n\n describe('Singleton', () => {\n it('should return same instance', () => {\n const client1 = getSodaxApiClient();\n const client2 = getSodaxApiClient();\n expect(client1).toBe(client2);\n });\n\n it('should create new instance after reset', () => {\n const client1 = getSodaxApiClient();\n resetSodaxApiClient();\n const client2 = getSodaxApiClient();\n expect(client1).not.toBe(client2);\n });\n });\n});\n",
1302 "inputSchema": {},
1303 "outputSchema": null,
1304 "icons": null,
1305 "annotations": null,
1306 "meta": null,
1307 "execution": null
1308 },
1309 {
1310 "name": "policyEngine.test.ts",
1311 "title": null,
1312 "description": "Script: policyEngine.test.ts. Code:\n/**\n * Policy Engine Tests\n */\n\nimport { PolicyEngine, PolicyCheckResult } from '../policy/policyEngine';\n\n// Mock environment variables\nconst originalEnv = process.env;\n\ndescribe('PolicyEngine', () => {\n beforeEach(() => {\n jest.resetModules();\n process.env = { ...originalEnv };\n delete process.env.AMPED_OC_LIMITS_JSON;\n });\n\n afterAll(() => {\n process.env = originalEnv;\n });\n\n describe('Configuration Loading', () => {\n it('should load empty config when env not set', () => {\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config).toEqual({});\n });\n\n it('should load default policy config', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n maxSlippageBps: 100,\n allowedChains: ['ethereum', 'arbitrum'],\n },\n });\n\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(100);\n expect(config.allowedChains).toEqual(['ethereum', 'arbitrum']);\n });\n\n it('should load specific policy by ID', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: { maxSlippageBps: 100 },\n aggressive: { maxSlippageBps: 300 },\n conservative: { maxSlippageBps: 50 },\n });\n\n const engine = new PolicyEngine('aggressive');\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(300);\n });\n\n it('should fallback to default when policy ID not found', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: { maxSlippageBps: 100 },\n });\n\n const engine = new PolicyEngine('nonexistent');\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(100);\n });\n\n it('should handle invalid JSON gracefully', () => {\n process.env.AMPED_OC_LIMITS_JSON = 'invalid json';\n\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config).toEqual({});\n });\n });\n\n describe('Bridge Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'arbitrum', 'sonic'],\n allowedTokensByChain: {\n ethereum: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],\n },\n maxBridgeAmountToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 10000,\n },\n blockedRecipients: ['0x0000000000000000000000000000000000000000'],\n },\n });\n });\n\n it('should allow valid bridge operation', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject disallowed source chain', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'polygon',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n\n it('should reject disallowed destination chain', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'polygon',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n\n it('should reject disallowed token', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xInvalidToken',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Token not allowed');\n });\n\n it('should reject amount exceeding limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '15000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n expect(result.details).toEqual({ maxAllowed: 10000, requested: 15000 });\n });\n\n it('should reject blocked recipient', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n recipient: '0x0000000000000000000000000000000000000000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Recipient is blocked');\n });\n });\n\n describe('Swap Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'arbitrum'],\n maxSlippageBps: 100,\n maxSwapInputToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 50000,\n },\n },\n });\n });\n\n it('should allow valid swap', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '1000',\n slippageBps: 50,\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject excessive slippage', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '1000',\n slippageBps: 150,\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Slippage');\n expect(result.details).toEqual({ maxAllowed: 100, requested: 150 });\n });\n\n it('should reject swap exceeding token limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '60000',\n slippageBps: 50,\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n });\n\n describe('Money Market Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'sonic'],\n allowedTokensByChain: {\n ethereum: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],\n sonic: ['0x29219dd400f2bf60e5a23d13be72b486d4038894'],\n },\n maxBorrowUsd: 50000,\n maxBorrowToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 30000,\n },\n },\n });\n });\n\n it('should allow valid supply', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '10000',\n operation: 'supply',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should allow valid borrow within limits', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '20000',\n amountUsd: 20000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject borrow exceeding USD limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Use allowed token\n amount: '60000',\n amountUsd: 60000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n\n it('should reject borrow exceeding token limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '35000',\n amountUsd: 35000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n\n it('should check cross-chain destination', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n dstChainId: 'polygon',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '10000',\n operation: 'supply',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n });\n\n describe('getAvailablePolicies', () => {\n it('should return empty array when no config', () => {\n const engine = new PolicyEngine();\n expect(engine.getAvailablePolicies()).toEqual([]);\n });\n\n it('should return policy IDs', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {},\n aggressive: {},\n conservative: {},\n });\n\n const engine = new PolicyEngine();\n const policies = engine.getAvailablePolicies();\n expect(policies).toContain('default');\n expect(policies).toContain('aggressive');\n expect(policies).toContain('conservative');\n expect(policies).toHaveLength(3);\n });\n });\n});\n",
1313 "inputSchema": {},
1314 "outputSchema": null,
1315 "icons": null,
1316 "annotations": null,
1317 "meta": null,
1318 "execution": null
1319 },
1320 {
1321 "name": "walletRegistry.test.ts",
1322 "title": null,
1323 "description": "Script: walletRegistry.test.ts. Code:\n/**\n * Wallet Registry Tests\n */\n\nimport { WalletRegistry } from '../wallet/walletRegistry';\n\n// Mock environment\nconst originalEnv = process.env;\n\ndescribe('WalletRegistry', () => {\n beforeEach(() => {\n jest.resetModules();\n process.env = { ...originalEnv };\n delete process.env.AMPED_OC_WALLETS_JSON;\n delete process.env.AMPED_OC_MODE;\n });\n\n afterAll(() => {\n process.env = originalEnv;\n });\n\n describe('Configuration Loading', () => {\n it('should load empty wallets when env not set', () => {\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(0);\n expect(registry.getWalletIds()).toEqual([]);\n });\n\n it('should load wallets in execute mode', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123',\n },\n trading: {\n address: '0x0987654321098765432109876543210987654321',\n privateKey: '0xdef456',\n },\n });\n process.env.AMPED_OC_MODE = 'execute';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(2);\n expect(registry.getWalletIds()).toContain('main');\n expect(registry.getWalletIds()).toContain('trading');\n expect(registry.isExecuteMode()).toBe(true);\n expect(registry.isPrepareMode()).toBe(false);\n });\n\n it('should load wallets in prepare mode', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n },\n });\n process.env.AMPED_OC_MODE = 'prepare';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(1);\n expect(registry.isPrepareMode()).toBe(true);\n expect(registry.isExecuteMode()).toBe(false);\n });\n\n it('should handle invalid JSON gracefully', () => {\n process.env.AMPED_OC_WALLETS_JSON = 'invalid json';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(0);\n });\n });\n\n describe('Wallet Resolution', () => {\n beforeEach(() => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123',\n },\n nopk: {\n address: '0x0987654321098765432109876543210987654321',\n },\n invalid: {\n address: 'not-an-address',\n },\n });\n process.env.AMPED_OC_MODE = 'execute';\n });\n\n it('should resolve existing wallet', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('main');\n\n expect(wallet).not.toBeNull();\n expect(wallet?.address).toBe('0x1234567890123456789012345678901234567890');\n expect(wallet?.privateKey).toBe('0xabc123');\n expect(wallet?.mode).toBe('execute');\n });\n\n it('should return null for non-existent wallet', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('nonexistent');\n\n expect(wallet).toBeNull();\n });\n\n it('should reject wallet without private key in execute mode', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('nopk');\n\n expect(wallet).toBeNull();\n });\n\n it('should reject wallet with invalid address', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('invalid');\n\n expect(wallet).toBeNull();\n });\n });\n\n describe('Mode Detection', () => {\n it('should default to execute mode', () => {\n delete process.env.AMPED_OC_MODE;\n const registry = new WalletRegistry();\n\n expect(registry.getMode()).toBe('execute');\n });\n\n it('should detect prepare mode', () => {\n process.env.AMPED_OC_MODE = 'prepare';\n const registry = new WalletRegistry();\n\n expect(registry.getMode()).toBe('prepare');\n });\n });\n\n describe('Hot Reload', () => {\n it('should reload wallets from environment', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n wallet1: { address: '0x1234567890123456789012345678901234567890' },\n });\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(1);\n\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n wallet1: { address: '0x1234567890123456789012345678901234567890' },\n wallet2: { address: '0x0987654321098765432109876543210987654321' },\n });\n\n registry.reload();\n expect(registry.getWalletCount()).toBe(2);\n });\n });\n});\n",
1324 "inputSchema": {},
1325 "outputSchema": null,
1326 "icons": null,
1327 "annotations": null,
1328 "meta": null,
1329 "execution": null
1330 },
1331 {
1332 "name": "setup.ts",
1333 "title": null,
1334 "description": "Script: setup.ts. Code:\n/**\n * Jest Test Setup\n */\n\n// Mock console methods to reduce noise during tests\nglobal.console = {\n ...console,\n log: jest.fn(),\n debug: jest.fn(),\n info: jest.fn(),\n warn: jest.fn(),\n error: jest.fn(),\n};\n\n// Set default test environment\nprocess.env.AMPED_OC_MODE = 'execute';\nprocess.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n test: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123def456',\n },\n});\nprocess.env.AMPED_OC_RPC_URLS_JSON = JSON.stringify({\n ethereum: 'https://eth-mainnet.example.com',\n arbitrum: 'https://arb-mainnet.example.com',\n sonic: 'https://rpc.sonic.example.com',\n});\n\n// Global test timeout\njest.setTimeout(10000);\n\n// Clean up after each test\nafterEach(() => {\n jest.clearAllMocks();\n});\n",
1335 "inputSchema": {},
1336 "outputSchema": null,
1337 "icons": null,
1338 "annotations": null,
1339 "meta": null,
1340 "execution": null
1341 },
1342 {
1343 "name": "errors.test.ts",
1344 "title": null,
1345 "description": "Script: errors.test.ts. Code:\n/**\n * Error Handling Utilities Tests\n */\n\nimport {\n ErrorCode,\n ErrorSeverity,\n AmpedDefiError,\n createPolicyError,\n createWalletError,\n createTransactionError,\n wrapError,\n isRetryableError,\n getRetryDelay,\n logError,\n} from '../utils/errors';\n\ndescribe('Error Utilities', () => {\n describe('AmpedDefiError', () => {\n it('should create error with all properties', () => {\n const error = new AmpedDefiError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage too high',\n {\n severity: ErrorSeverity.WARNING,\n remediation: 'Increase slippage tolerance',\n details: { current: 100, limit: 50 },\n context: { operation: 'swap', walletId: 'test' },\n }\n );\n\n expect(error.code).toBe(ErrorCode.POLICY_SLIPPAGE_EXCEEDED);\n expect(error.message).toBe('Slippage too high');\n expect(error.severity).toBe(ErrorSeverity.WARNING);\n expect(error.remediation).toBe('Increase slippage tolerance');\n expect(error.details).toEqual({ current: 100, limit: 50 });\n expect(error.context).toEqual({ operation: 'swap', walletId: 'test' });\n expect(error.name).toBe('AmpedDefiError');\n });\n\n it('should default severity to ERROR', () => {\n const error = new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n 'Something went wrong'\n );\n\n expect(error.severity).toBe(ErrorSeverity.ERROR);\n });\n\n it('should serialize to JSON correctly', () => {\n const error = new AmpedDefiError(\n ErrorCode.WALLET_NOT_FOUND,\n 'Wallet not found',\n { remediation: 'Check configuration' }\n );\n\n const json = error.toJSON();\n expect(json.code).toBe(ErrorCode.WALLET_NOT_FOUND);\n expect(json.message).toBe('Wallet not found');\n expect(json.remediation).toBe('Check configuration');\n });\n\n it('should generate user-friendly message', () => {\n const error = new AmpedDefiError(\n ErrorCode.TRANSACTION_FAILED,\n 'Transaction failed',\n { remediation: 'Try again later' }\n );\n\n const userMsg = error.toUserMessage();\n expect(userMsg).toContain('[TRANSACTION_FAILED] Transaction failed');\n expect(userMsg).toContain('Suggestion: Try again later');\n });\n });\n\n describe('Error Factory Functions', () => {\n it('should create policy error with remediation', () => {\n const error = createPolicyError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage exceeds limit',\n { current: 150, limit: 100 }\n );\n\n expect(error.code).toBe(ErrorCode.POLICY_SLIPPAGE_EXCEEDED);\n expect(error.severity).toBe(ErrorSeverity.WARNING);\n expect(error.remediation).toContain('150');\n expect(error.remediation).toContain('100');\n expect(error.remediation).toContain('exceeds limit');\n });\n\n it('should create wallet error with context', () => {\n const cause = new Error('Original error');\n const error = createWalletError(\n ErrorCode.WALLET_NOT_FOUND,\n 'my-wallet',\n cause,\n { operation: 'borrow' }\n );\n\n expect(error.code).toBe(ErrorCode.WALLET_NOT_FOUND);\n expect(error.message).toContain('my-wallet');\n expect(error.context).toEqual({ operation: 'borrow', walletId: 'my-wallet' });\n expect(error.cause).toBe(cause);\n });\n\n it('should create transaction error with txHash', () => {\n const error = createTransactionError(\n ErrorCode.TRANSACTION_TIMEOUT,\n 'Transaction timed out',\n '0xabc123'\n );\n\n expect(error.code).toBe(ErrorCode.TRANSACTION_TIMEOUT);\n expect(error.details).toEqual({ txHash: '0xabc123' });\n expect(error.context).toEqual({ txHash: '0xabc123' });\n });\n });\n\n describe('wrapError', () => {\n it('should return AmpedDefiError as-is', () => {\n const original = new AmpedDefiError(\n ErrorCode.SDK_NOT_INITIALIZED,\n 'SDK not ready'\n );\n const wrapped = wrapError(original);\n\n expect(wrapped).toBe(original);\n });\n\n it('should wrap standard Error and infer code', () => {\n const original = new Error('Insufficient balance for transaction');\n const wrapped = wrapError(original);\n\n expect(wrapped).toBeInstanceOf(AmpedDefiError);\n expect(wrapped.code).toBe(ErrorCode.INSUFFICIENT_BALANCE);\n expect(wrapped.message).toBe('Insufficient balance for transaction');\n });\n\n it('should use fallback code when unable to infer', () => {\n const original = new Error('Some random error');\n const wrapped = wrapError(original, ErrorCode.UNKNOWN_ERROR);\n\n expect(wrapped.code).toBe(ErrorCode.UNKNOWN_ERROR);\n });\n\n it('should wrap non-error values', () => {\n const wrapped = wrapError('string error');\n\n expect(wrapped).toBeInstanceOf(AmpedDefiError);\n expect(wrapped.message).toBe('string error');\n });\n });\n\n describe('isRetryableError', () => {\n it('should identify retryable error codes', () => {\n const timeoutError = new AmpedDefiError(\n ErrorCode.TRANSACTION_TIMEOUT,\n 'Timeout'\n );\n expect(isRetryableError(timeoutError)).toBe(true);\n\n const rpcError = new AmpedDefiError(\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n 'RPC error'\n );\n expect(isRetryableError(rpcError)).toBe(true);\n });\n\n it('should identify non-retryable error codes', () => {\n const policyError = new AmpedDefiError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage'\n );\n expect(isRetryableError(policyError)).toBe(false);\n });\n\n it('should check message patterns for generic errors', () => {\n const networkError = new Error('Network connection failed');\n expect(isRetryableError(networkError)).toBe(true);\n\n const randomError = new Error('Something broke');\n expect(isRetryableError(randomError)).toBe(false);\n });\n });\n\n describe('getRetryDelay', () => {\n it('should calculate exponential backoff', () => {\n expect(getRetryDelay(0)).toBe(1000);\n expect(getRetryDelay(1)).toBe(2000);\n expect(getRetryDelay(2)).toBe(4000);\n expect(getRetryDelay(3)).toBe(8000);\n });\n\n it('should cap at 30 seconds', () => {\n expect(getRetryDelay(10)).toBe(30000);\n expect(getRetryDelay(20)).toBe(30000);\n });\n\n it('should use custom base delay', () => {\n expect(getRetryDelay(0, 500)).toBe(500);\n expect(getRetryDelay(1, 500)).toBe(1000);\n });\n });\n\n describe('logError', () => {\n it('should log structured error', () => {\n const consoleSpy = jest.spyOn(console, 'error').mockImplementation();\n\n const error = new AmpedDefiError(\n ErrorCode.TRANSACTION_FAILED,\n 'Transaction failed'\n );\n\n logError(error, { operation: 'swap', walletId: 'test' });\n\n expect(consoleSpy).toHaveBeenCalled();\n const logged = JSON.parse(consoleSpy.mock.calls[0][0]);\n expect(logged.component).toBe('amped-defi');\n expect(logged.code).toBe(ErrorCode.TRANSACTION_FAILED);\n expect(logged.level).toBe('error');\n\n consoleSpy.mockRestore();\n });\n\n it('should handle standard errors', () => {\n const consoleSpy = jest.spyOn(console, 'error').mockImplementation();\n\n const error = new Error('Standard error');\n logError(error);\n\n expect(consoleSpy).toHaveBeenCalled();\n const logged = JSON.parse(consoleSpy.mock.calls[0][0]);\n expect(logged.code).toBe(ErrorCode.UNKNOWN_ERROR);\n\n consoleSpy.mockRestore();\n });\n });\n});\n",
1346 "inputSchema": {},
1347 "outputSchema": null,
1348 "icons": null,
1349 "annotations": null,
1350 "meta": null,
1351 "execution": null
1352 },
1353 {
1354 "name": "index.js",
1355 "title": null,
1356 "description": "Script: index.js. Code:\n// Re-export from compiled dist\nmodule.exports = require('./dist/index.js');\n",
1357 "inputSchema": {},
1358 "outputSchema": null,
1359 "icons": null,
1360 "annotations": null,
1361 "meta": null,
1362 "execution": null
1363 }
1364 ]
1365 },
1366 "error": null
1367 }
1368 ],
1369 "issues": [
1370 {
1371 "code": "W004",
1372 "message": "The MCP server is not in our registry.",
1373 "reference": [
1374 0,
1375 null
1376 ],
1377 "extra_data": null
1378 },
1379 {
1380 "code": "W011",
1381 "message": "Third-party content exposure detected (high risk: 0.70). The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.",
1382 "reference": [
1383 0,
1384 null
1385 ],
1386 "extra_data": {
1387 "risk_score": 0.7,
1388 "reason": "The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.",
1389 "thought_process": "<reason>The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.</reason>\n<answer>0.7</answer>",
1390 "severity": "high"
1391 }
1392 },
1393 {
1394 "code": "W009",
1395 "message": "Direct money access detected (high risk: 1.00). The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.",
1396 "reference": [
1397 0,
1398 null
1399 ],
1400 "extra_data": {
1401 "risk_score": 1.0,
1402 "reason": "The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.",
1403 "thought_process": "<reason>The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.</reason>\n<answer>1</answer>",
1404 "severity": "high"
1405 }
1406 }
1407 ],
1408 "labels": [
1409 [
1410 {
1411 "is_public_sink": 0,
1412 "destructive": 0,
1413 "untrusted_content": 0,
1414 "private_data": 0
1415 },
1416 {
1417 "is_public_sink": 0,
1418 "destructive": 0,
1419 "untrusted_content": 0,
1420 "private_data": 0
1421 },
1422 {
1423 "is_public_sink": 0,
1424 "destructive": 0,
1425 "untrusted_content": 0,
1426 "private_data": 0
1427 },
1428 {
1429 "is_public_sink": 0,
1430 "destructive": 0,
1431 "untrusted_content": 0,
1432 "private_data": 0
1433 },
1434 {
1435 "is_public_sink": 0,
1436 "destructive": 0,
1437 "untrusted_content": 0,
1438 "private_data": 0
1439 },
1440 {
1441 "is_public_sink": 0,
1442 "destructive": 0,
1443 "untrusted_content": 0,
1444 "private_data": 0
1445 },
1446 {
1447 "is_public_sink": 0,
1448 "destructive": 0,
1449 "untrusted_content": 0,
1450 "private_data": 0
1451 },
1452 {
1453 "is_public_sink": 0,
1454 "destructive": 0,
1455 "untrusted_content": 0,
1456 "private_data": 0
1457 },
1458 {
1459 "is_public_sink": 0,
1460 "destructive": 0,
1461 "untrusted_content": 0,
1462 "private_data": 0
1463 },
1464 {
1465 "is_public_sink": 0,
1466 "destructive": 0,
1467 "untrusted_content": 0,
1468 "private_data": 0
1469 },
1470 {
1471 "is_public_sink": 0,
1472 "destructive": 0,
1473 "untrusted_content": 0,
1474 "private_data": 0
1475 },
1476 {
1477 "is_public_sink": 0,
1478 "destructive": 0,
1479 "untrusted_content": 0,
1480 "private_data": 0
1481 },
1482 {
1483 "is_public_sink": 0,
1484 "destructive": 0,
1485 "untrusted_content": 0,
1486 "private_data": 0
1487 },
1488 {
1489 "is_public_sink": 0,
1490 "destructive": 0,
1491 "untrusted_content": 0,
1492 "private_data": 0
1493 },
1494 {
1495 "is_public_sink": 0,
1496 "destructive": 0,
1497 "untrusted_content": 0,
1498 "private_data": 0
1499 },
1500 {
1501 "is_public_sink": 0,
1502 "destructive": 0,
1503 "untrusted_content": 0,
1504 "private_data": 0
1505 },
1506 {
1507 "is_public_sink": 0,
1508 "destructive": 0,
1509 "untrusted_content": 0,
1510 "private_data": 0
1511 },
1512 {
1513 "is_public_sink": 0,
1514 "destructive": 0,
1515 "untrusted_content": 0,
1516 "private_data": 0
1517 },
1518 {
1519 "is_public_sink": 0,
1520 "destructive": 0,
1521 "untrusted_content": 0,
1522 "private_data": 0
1523 },
1524 {
1525 "is_public_sink": 0,
1526 "destructive": 0,
1527 "untrusted_content": 0,
1528 "private_data": 0
1529 },
1530 {
1531 "is_public_sink": 0,
1532 "destructive": 0,
1533 "untrusted_content": 0,
1534 "private_data": 0
1535 },
1536 {
1537 "is_public_sink": 0,
1538 "destructive": 0,
1539 "untrusted_content": 0,
1540 "private_data": 0
1541 },
1542 {
1543 "is_public_sink": 0,
1544 "destructive": 0,
1545 "untrusted_content": 0,
1546 "private_data": 0
1547 },
1548 {
1549 "is_public_sink": 0,
1550 "destructive": 0,
1551 "untrusted_content": 0,
1552 "private_data": 0
1553 },
1554 {
1555 "is_public_sink": 0,
1556 "destructive": 0,
1557 "untrusted_content": 0,
1558 "private_data": 0
1559 },
1560 {
1561 "is_public_sink": 0,
1562 "destructive": 0,
1563 "untrusted_content": 0,
1564 "private_data": 0
1565 },
1566 {
1567 "is_public_sink": 0,
1568 "destructive": 0,
1569 "untrusted_content": 0,
1570 "private_data": 0
1571 },
1572 {
1573 "is_public_sink": 0,
1574 "destructive": 0,
1575 "untrusted_content": 0,
1576 "private_data": 0
1577 },
1578 {
1579 "is_public_sink": 0,
1580 "destructive": 0,
1581 "untrusted_content": 0,
1582 "private_data": 0
1583 },
1584 {
1585 "is_public_sink": 0,
1586 "destructive": 0,
1587 "untrusted_content": 0,
1588 "private_data": 0
1589 },
1590 {
1591 "is_public_sink": 0,
1592 "destructive": 0,
1593 "untrusted_content": 0,
1594 "private_data": 0
1595 },
1596 {
1597 "is_public_sink": 0,
1598 "destructive": 0,
1599 "untrusted_content": 0,
1600 "private_data": 0
1601 },
1602 {
1603 "is_public_sink": 0,
1604 "destructive": 0,
1605 "untrusted_content": 0,
1606 "private_data": 0
1607 },
1608 {
1609 "is_public_sink": 0,
1610 "destructive": 0,
1611 "untrusted_content": 0,
1612 "private_data": 0
1613 },
1614 {
1615 "is_public_sink": 0,
1616 "destructive": 0,
1617 "untrusted_content": 0,
1618 "private_data": 0
1619 },
1620 {
1621 "is_public_sink": 0,
1622 "destructive": 0,
1623 "untrusted_content": 0,
1624 "private_data": 0
1625 },
1626 {
1627 "is_public_sink": 0,
1628 "destructive": 0,
1629 "untrusted_content": 0,
1630 "private_data": 0
1631 },
1632 {
1633 "is_public_sink": 0,
1634 "destructive": 0,
1635 "untrusted_content": 0,
1636 "private_data": 0
1637 },
1638 {
1639 "is_public_sink": 0,
1640 "destructive": 0,
1641 "untrusted_content": 0,
1642 "private_data": 0
1643 },
1644 {
1645 "is_public_sink": 0,
1646 "destructive": 0,
1647 "untrusted_content": 0,
1648 "private_data": 0
1649 },
1650 {
1651 "is_public_sink": 0,
1652 "destructive": 0,
1653 "untrusted_content": 0,
1654 "private_data": 0
1655 },
1656 {
1657 "is_public_sink": 0,
1658 "destructive": 0,
1659 "untrusted_content": 0,
1660 "private_data": 0
1661 },
1662 {
1663 "is_public_sink": 0,
1664 "destructive": 0,
1665 "untrusted_content": 0,
1666 "private_data": 0
1667 },
1668 {
1669 "is_public_sink": 0,
1670 "destructive": 0,
1671 "untrusted_content": 0,
1672 "private_data": 0
1673 },
1674 {
1675 "is_public_sink": 0,
1676 "destructive": 0,
1677 "untrusted_content": 0,
1678 "private_data": 0
1679 },
1680 {
1681 "is_public_sink": 0,
1682 "destructive": 0,
1683 "untrusted_content": 0,
1684 "private_data": 0
1685 },
1686 {
1687 "is_public_sink": 0,
1688 "destructive": 0,
1689 "untrusted_content": 0,
1690 "private_data": 0
1691 },
1692 {
1693 "is_public_sink": 0,
1694 "destructive": 0,
1695 "untrusted_content": 0,
1696 "private_data": 0
1697 },
1698 {
1699 "is_public_sink": 0,
1700 "destructive": 0,
1701 "untrusted_content": 0,
1702 "private_data": 0
1703 },
1704 {
1705 "is_public_sink": 0,
1706 "destructive": 0,
1707 "untrusted_content": 0,
1708 "private_data": 0
1709 },
1710 {
1711 "is_public_sink": 0,
1712 "destructive": 0,
1713 "untrusted_content": 0,
1714 "private_data": 0
1715 },
1716 {
1717 "is_public_sink": 0,
1718 "destructive": 0,
1719 "untrusted_content": 0,
1720 "private_data": 0
1721 },
1722 {
1723 "is_public_sink": 0,
1724 "destructive": 0,
1725 "untrusted_content": 0,
1726 "private_data": 0
1727 },
1728 {
1729 "is_public_sink": 0,
1730 "destructive": 0,
1731 "untrusted_content": 0,
1732 "private_data": 0
1733 },
1734 {
1735 "is_public_sink": 0,
1736 "destructive": 0,
1737 "untrusted_content": 0,
1738 "private_data": 0
1739 },
1740 {
1741 "is_public_sink": 0,
1742 "destructive": 0,
1743 "untrusted_content": 0,
1744 "private_data": 0
1745 },
1746 {
1747 "is_public_sink": 0,
1748 "destructive": 0,
1749 "untrusted_content": 0,
1750 "private_data": 0
1751 },
1752 {
1753 "is_public_sink": 0,
1754 "destructive": 0,
1755 "untrusted_content": 0,
1756 "private_data": 0
1757 },
1758 {
1759 "is_public_sink": 0,
1760 "destructive": 0,
1761 "untrusted_content": 0,
1762 "private_data": 0
1763 },
1764 {
1765 "is_public_sink": 0,
1766 "destructive": 0,
1767 "untrusted_content": 0,
1768 "private_data": 0
1769 },
1770 {
1771 "is_public_sink": 0,
1772 "destructive": 0,
1773 "untrusted_content": 0,
1774 "private_data": 0
1775 },
1776 {
1777 "is_public_sink": 0,
1778 "destructive": 0,
1779 "untrusted_content": 0,
1780 "private_data": 0
1781 },
1782 {
1783 "is_public_sink": 0,
1784 "destructive": 0,
1785 "untrusted_content": 0,
1786 "private_data": 0
1787 },
1788 {
1789 "is_public_sink": 0,
1790 "destructive": 0,
1791 "untrusted_content": 0,
1792 "private_data": 0
1793 },
1794 {
1795 "is_public_sink": 0,
1796 "destructive": 0,
1797 "untrusted_content": 0,
1798 "private_data": 0
1799 },
1800 {
1801 "is_public_sink": 0,
1802 "destructive": 0,
1803 "untrusted_content": 0,
1804 "private_data": 0
1805 },
1806 {
1807 "is_public_sink": 0,
1808 "destructive": 0,
1809 "untrusted_content": 0,
1810 "private_data": 0
1811 },
1812 {
1813 "is_public_sink": 0,
1814 "destructive": 0,
1815 "untrusted_content": 0,
1816 "private_data": 0
1817 },
1818 {
1819 "is_public_sink": 0,
1820 "destructive": 0,
1821 "untrusted_content": 0,
1822 "private_data": 0
1823 },
1824 {
1825 "is_public_sink": 0,
1826 "destructive": 0,
1827 "untrusted_content": 0,
1828 "private_data": 0
1829 },
1830 {
1831 "is_public_sink": 0,
1832 "destructive": 0,
1833 "untrusted_content": 0,
1834 "private_data": 0
1835 },
1836 {
1837 "is_public_sink": 0,
1838 "destructive": 0,
1839 "untrusted_content": 0,
1840 "private_data": 0
1841 },
1842 {
1843 "is_public_sink": 0,
1844 "destructive": 0,
1845 "untrusted_content": 0,
1846 "private_data": 0
1847 },
1848 {
1849 "is_public_sink": 0,
1850 "destructive": 0,
1851 "untrusted_content": 0,
1852 "private_data": 0
1853 },
1854 {
1855 "is_public_sink": 0,
1856 "destructive": 0,
1857 "untrusted_content": 0,
1858 "private_data": 0
1859 },
1860 {
1861 "is_public_sink": 0,
1862 "destructive": 0,
1863 "untrusted_content": 0,
1864 "private_data": 0
1865 },
1866 {
1867 "is_public_sink": 0,
1868 "destructive": 0,
1869 "untrusted_content": 0,
1870 "private_data": 0
1871 },
1872 {
1873 "is_public_sink": 0,
1874 "destructive": 0,
1875 "untrusted_content": 0,
1876 "private_data": 0
1877 },
1878 {
1879 "is_public_sink": 0,
1880 "destructive": 0,
1881 "untrusted_content": 0,
1882 "private_data": 0
1883 },
1884 {
1885 "is_public_sink": 0,
1886 "destructive": 0,
1887 "untrusted_content": 0,
1888 "private_data": 0
1889 },
1890 {
1891 "is_public_sink": 0,
1892 "destructive": 0,
1893 "untrusted_content": 0,
1894 "private_data": 0
1895 },
1896 {
1897 "is_public_sink": 0,
1898 "destructive": 0,
1899 "untrusted_content": 0,
1900 "private_data": 0
1901 },
1902 {
1903 "is_public_sink": 0,
1904 "destructive": 0,
1905 "untrusted_content": 0,
1906 "private_data": 0
1907 },
1908 {
1909 "is_public_sink": 0,
1910 "destructive": 0,
1911 "untrusted_content": 0,
1912 "private_data": 0
1913 },
1914 {
1915 "is_public_sink": 0,
1916 "destructive": 0,
1917 "untrusted_content": 0,
1918 "private_data": 0
1919 },
1920 {
1921 "is_public_sink": 0,
1922 "destructive": 0,
1923 "untrusted_content": 0,
1924 "private_data": 0
1925 },
1926 {
1927 "is_public_sink": 0,
1928 "destructive": 0,
1929 "untrusted_content": 0,
1930 "private_data": 0
1931 },
1932 {
1933 "is_public_sink": 0,
1934 "destructive": 0,
1935 "untrusted_content": 0,
1936 "private_data": 0
1937 },
1938 {
1939 "is_public_sink": 0,
1940 "destructive": 0,
1941 "untrusted_content": 0,
1942 "private_data": 0
1943 },
1944 {
1945 "is_public_sink": 0,
1946 "destructive": 0,
1947 "untrusted_content": 0,
1948 "private_data": 0
1949 },
1950 {
1951 "is_public_sink": 0,
1952 "destructive": 0,
1953 "untrusted_content": 0,
1954 "private_data": 0
1955 },
1956 {
1957 "is_public_sink": 0,
1958 "destructive": 0,
1959 "untrusted_content": 0,
1960 "private_data": 0
1961 },
1962 {
1963 "is_public_sink": 0,
1964 "destructive": 0,
1965 "untrusted_content": 0,
1966 "private_data": 0
1967 },
1968 {
1969 "is_public_sink": 0,
1970 "destructive": 0,
1971 "untrusted_content": 0,
1972 "private_data": 0
1973 },
1974 {
1975 "is_public_sink": 0,
1976 "destructive": 0,
1977 "untrusted_content": 0,
1978 "private_data": 0
1979 },
1980 {
1981 "is_public_sink": 0,
1982 "destructive": 0,
1983 "untrusted_content": 0,
1984 "private_data": 0
1985 },
1986 {
1987 "is_public_sink": 0,
1988 "destructive": 0,
1989 "untrusted_content": 0,
1990 "private_data": 0
1991 },
1992 {
1993 "is_public_sink": 0,
1994 "destructive": 0,
1995 "untrusted_content": 0,
1996 "private_data": 0
1997 },
1998 {
1999 "is_public_sink": 0,
2000 "destructive": 0,
2001 "untrusted_content": 0,
2002 "private_data": 0
2003 },
2004 {
2005 "is_public_sink": 0,
2006 "destructive": 0,
2007 "untrusted_content": 0,
2008 "private_data": 0
2009 },
2010 {
2011 "is_public_sink": 0,
2012 "destructive": 0,
2013 "untrusted_content": 0,
2014 "private_data": 0
2015 },
2016 {
2017 "is_public_sink": 0,
2018 "destructive": 0,
2019 "untrusted_content": 0,
2020 "private_data": 0
2021 },
2022 {
2023 "is_public_sink": 0,
2024 "destructive": 0,
2025 "untrusted_content": 0,
2026 "private_data": 0
2027 },
2028 {
2029 "is_public_sink": 0,
2030 "destructive": 0,
2031 "untrusted_content": 0,
2032 "private_data": 0
2033 },
2034 {
2035 "is_public_sink": 0,
2036 "destructive": 0,
2037 "untrusted_content": 0,
2038 "private_data": 0
2039 },
2040 {
2041 "is_public_sink": 0,
2042 "destructive": 0,
2043 "untrusted_content": 0,
2044 "private_data": 0
2045 },
2046 {
2047 "is_public_sink": 0,
2048 "destructive": 0,
2049 "untrusted_content": 0,
2050 "private_data": 0
2051 },
2052 {
2053 "is_public_sink": 0,
2054 "destructive": 0,
2055 "untrusted_content": 0,
2056 "private_data": 0
2057 },
2058 {
2059 "is_public_sink": 0,
2060 "destructive": 0,
2061 "untrusted_content": 0,
2062 "private_data": 0
2063 },
2064 {
2065 "is_public_sink": 0,
2066 "destructive": 0,
2067 "untrusted_content": 0,
2068 "private_data": 0
2069 },
2070 {
2071 "is_public_sink": 0,
2072 "destructive": 0,
2073 "untrusted_content": 0,
2074 "private_data": 0
2075 },
2076 {
2077 "is_public_sink": 0,
2078 "destructive": 0,
2079 "untrusted_content": 0,
2080 "private_data": 0
2081 },
2082 {
2083 "is_public_sink": 0,
2084 "destructive": 0,
2085 "untrusted_content": 0,
2086 "private_data": 0
2087 },
2088 {
2089 "is_public_sink": 0,
2090 "destructive": 0,
2091 "untrusted_content": 0,
2092 "private_data": 0
2093 },
2094 {
2095 "is_public_sink": 0,
2096 "destructive": 0,
2097 "untrusted_content": 0,
2098 "private_data": 0
2099 },
2100 {
2101 "is_public_sink": 0,
2102 "destructive": 0,
2103 "untrusted_content": 0,
2104 "private_data": 0
2105 },
2106 {
2107 "is_public_sink": 0,
2108 "destructive": 0,
2109 "untrusted_content": 0,
2110 "private_data": 0
2111 },
2112 {
2113 "is_public_sink": 0,
2114 "destructive": 0,
2115 "untrusted_content": 0,
2116 "private_data": 0
2117 },
2118 {
2119 "is_public_sink": 0,
2120 "destructive": 0,
2121 "untrusted_content": 0,
2122 "private_data": 0
2123 },
2124 {
2125 "is_public_sink": 0,
2126 "destructive": 0,
2127 "untrusted_content": 0,
2128 "private_data": 0
2129 }
2130 ]
2131 ],
2132 "error": null
2133 }
2134}
2135
2136Process exited with code 0
2137✓ Completed in 218643ms
npm-audit
0 findings154249ms
No findings — all checks passed.
View logs
npm-audit154249ms
1[2026-02-11T18:38:52.512Z] $ npm audit --json --prefix /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish
2{
3 "error": {
4 "code": "ENOLOCK",
5 "summary": "This command requires an existing lockfile.",
6 "detail": "Try creating one first with: npm i --package-lock-only\nOriginal error: loadVirtual requires existing shrinkwrap file"
7 }
8}
9
10⚠ stderr output:
11npm error code ENOLOCK
12npm error audit This command requires an existing lockfile.
13npm error audit Try creating one first with: npm i --package-lock-only
14npm error audit Original error: loadVirtual requires existing shrinkwrap file
15npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-02-11T18_38_35_265Z-debug-0.log
16
17Process exited with code 1
18✓ Completed in 154249ms

Files analyzed

SKILL.mdsdk.d.tssdk.jssetup.d.tssetup.jsindex.d.tsindex.jspolicyEngine.d.tspolicyEngine.jsspokeProviderFactory.d.tsspokeProviderFactory.jsclient.d.tsclient.jsbridge.d.tsbridge.jsdiscovery.d.tsdiscovery.jsmoneyMarket.d.tsmoneyMarket.jsportfolio.d.tsportfolio.jsswap.d.tsswap.jswalletManagement.d.tswalletManagement.jstypes.d.tstypes.jserrorUtils.d.tserrorUtils.jserrors.d.tserrors.jspositionAggregator.d.tspositionAggregator.jspriceService.d.tspriceService.jssodaxApi.d.tssodaxApi.jstokenResolver.d.tstokenResolver.jsbackendConfig.d.tsbackendConfig.jsBankrBackend.d.tsBankrBackend.jsBankrWalletProvider.d.tsBankrWalletProvider.jsEnvBackend.d.tsEnvBackend.jsEvmWalletSkillBackend.d.tsEvmWalletSkillBackend.jsindex.d.tsindex.jsindex.d.tsindex.jsAmpedWalletProvider.d.tsAmpedWalletProvider.jsBankrBackend.d.tsBankrBackend.jsLocalKeyBackend.d.tsLocalKeyBackend.jschainConfig.d.tschainConfig.jsindex.d.tsindex.jstypes.d.tstypes.jsskillWalletAdapter.d.tsskillWalletAdapter.jstypes.d.tstypes.jswalletManager.d.tswalletManager.jswalletRegistry.d.tswalletRegistry.jsbasic-usage.tsindex.jssdk.tserrors.test.tspolicyEngine.test.tspositionAggregator.test.tssetup.tssodaxApi.test.tswalletRegistry.test.tsindex.tspolicyEngine.tsspokeProviderFactory.tsclient.tsbridge.tsdiscovery.tsmoneyMarket.tsportfolio.tsswap.tswalletManagement.tstypes.tserrorUtils.tserrors.tspositionAggregator.tspriceService.tssodaxApi.tstokenResolver.tsbackendConfig.tsBankrBackend.tsBankrWalletProvider.tsEnvBackend.tsEvmWalletSkillBackend.tsindex.tsindex.tsAmpedWalletProvider.tsBankrBackend.tsLocalKeyBackend.tschainConfig.tsindex.tstypes.tsskillWalletAdapter.tstypes.tswalletManager.tswalletRegistry.ts

Rules coverage147 patterns

58
prompt injection
15
secrets
53
malware
21
permissions

Security Findings

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:871

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439

Evidence: token="0x29219dd400f2bf60e5a23d13be72b486d4038894"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533

Evidence: token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564

Evidence: token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651

Evidence: token="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661

Evidence: token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900

Evidence: token="0x29219dd400f2bf60e5a23d13be72b486d4038894"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924

Evidence: token="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"

HighSEC-004clawguard-rulessecrets

Potential secret in key-value assignment

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934

Evidence: token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:86

Evidence: 0x5b18d04a545f089e6de59106fa79498cfc0b0274

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:87

Evidence: 0x1c4a8ded456b97ba9fa2b95ee954ed7e92a40365

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:302

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:303

Evidence: 0x4200000000000000000000000000000000000006

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:331

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:332

Evidence: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:370

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:378

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:379

Evidence: 0x29219dd400f2bf60e5a23d13be72b486d4038894

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:393

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:394

Evidence: 0x29219dd400f2bf60e5a23d13be72b486d4038894

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439

Evidence: 0x29219dd400f2bf60e5a23d13be72b486d4038894

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533

Evidence: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564

Evidence: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661

Evidence: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:721

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:727

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:872

Evidence: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:888

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:889

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:890

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900

Evidence: 0x29219dd400f2bf60e5a23d13be72b486d4038894

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924

Evidence: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

LowPI-041clawguard-rulesprompt-injection

Possible base64-encoded payload

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934

Evidence: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:181

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:224

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:392

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:481

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:511

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:185

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:331

Lowjavascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringsemgrepsecurity

Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts:153

Mediumjavascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversalsemgrepsecurity

Detected possible user input going into a `path.join` or `path.resolve` function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.

/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts:22

LowMCP-W004mcp-scanmcp

The MCP server is not in our registry.

HighMCP-W011mcp-scanmcp

Third-party content exposure detected (high risk: 0.70). The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.

Evidence: The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.

HighMCP-W009mcp-scanmcp

Direct money access detected (high risk: 1.00). The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets — i.e., direct financial execution (crypto/blockchain). Therefore it meets the "Direct Financial Execution" criteria.

Evidence: The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets — i.e., direct financial execution (crypto/blockchain). Therefore it meets the "Direct Financial Execution" criteria.

Scan History1 scan

Failedv1.0.04d2cf2a
46 findings
0
critical
10
high
1
medium
35
low
0
info

Scanners5/5 ran

clawguard-rules
34 findings9ms
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924)
SEC-004Potential secret in key-value assignment(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:86)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:87)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:302)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:303)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:331)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:332)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:370)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:378)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:379)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:393)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:394)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:439)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:533)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:564)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:651)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:661)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:721)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:727)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:871)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:872)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:888)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:889)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:890)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:900)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:924)
PI-041Possible base64-encoded payload(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md:934)
View logs
clawguard-rules9ms
1[2026-02-11T18:36:18.254Z] Running @yourclaw/clawguard-rules pattern matcher
2Scanning: /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md
3Content length: 37724 chars
4Patterns matched: 34
5 [high] SEC-004: Potential secret in key-value assignment
6 [high] SEC-004: Potential secret in key-value assignment
7 [high] SEC-004: Potential secret in key-value assignment
8 [high] SEC-004: Potential secret in key-value assignment
9 [high] SEC-004: Potential secret in key-value assignment
10 [high] SEC-004: Potential secret in key-value assignment
11 [high] SEC-004: Potential secret in key-value assignment
12 [high] SEC-004: Potential secret in key-value assignment
13 [low] PI-041: Possible base64-encoded payload
14 [low] PI-041: Possible base64-encoded payload
15 [low] PI-041: Possible base64-encoded payload
16 [low] PI-041: Possible base64-encoded payload
17 [low] PI-041: Possible base64-encoded payload
18 [low] PI-041: Possible base64-encoded payload
19 [low] PI-041: Possible base64-encoded payload
20 [low] PI-041: Possible base64-encoded payload
21 [low] PI-041: Possible base64-encoded payload
22 [low] PI-041: Possible base64-encoded payload
23 [low] PI-041: Possible base64-encoded payload
24 [low] PI-041: Possible base64-encoded payload
25 [low] PI-041: Possible base64-encoded payload
26 [low] PI-041: Possible base64-encoded payload
27 [low] PI-041: Possible base64-encoded payload
28 [low] PI-041: Possible base64-encoded payload
29 [low] PI-041: Possible base64-encoded payload
30 [low] PI-041: Possible base64-encoded payload
31 [low] PI-041: Possible base64-encoded payload
32 [low] PI-041: Possible base64-encoded payload
33 [low] PI-041: Possible base64-encoded payload
34 [low] PI-041: Possible base64-encoded payload
35 [low] PI-041: Possible base64-encoded payload
36 [low] PI-041: Possible base64-encoded payload
37 [low] PI-041: Possible base64-encoded payload
38 [low] PI-041: Possible base64-encoded payload
39✓ Completed in 9ms
gitleaks
0 findings163533ms
No findings — all checks passed.
View logs
gitleaks163533ms
1[2026-02-11T18:39:01.787Z] $ gitleaks detect --source /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish --report-format json --report-path /dev/stdout --no-git
2
3⚠ stderr output:
4
5 │╲
6 │ ○
7 ○ ░
8 ░ gitleaks
9
106:39PM FTL Report path is not writable: /dev/stdout error="open /dev/stdout: no such device or address"
11
12Process exited with code 1
13✓ Completed in 163533ms
semgrep
9 findings292443ms
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:181)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:224)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:392)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:481)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts:511)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:185)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts:331)
javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstringDetected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts:153)
javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversalDetected possible user input going into a `path.join` or `path.resolve` function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.(/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts:22)
View logs
semgrep292443ms
1[2026-02-11T18:41:10.700Z] $ semgrep scan --json --quiet --config auto /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish
2{"version":"1.151.0","results":[{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":181,"col":19,"offset":6407},"end":{"line":181,"col":81,"offset":6469},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":224,"col":19,"offset":7811},"end":{"line":224,"col":64,"offset":7856},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":392,"col":23,"offset":13041},"end":{"line":392,"col":65,"offset":13083},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":481,"col":23,"offset":16555},"end":{"line":481,"col":89,"offset":16621},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","start":{"line":511,"col":21,"offset":17972},"end":{"line":511,"col":84,"offset":18035},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","start":{"line":185,"col":20,"offset":5216},"end":{"line":185,"col":75,"offset":5271},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","start":{"line":331,"col":19,"offset":11090},"end":{"line":331,"col":67,"offset":11138},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts","start":{"line":153,"col":19,"offset":6839},"end":{"line":153,"col":80,"offset":6900},"extra":{"message":"Detected string concatenation with a non-literal variable in a util.format / console.log function. If an attacker injects a format specifier in the string, it will forge the log message. Try to use constant values for the format string.","metadata":{"cwe":["CWE-134: Use of Externally-Controlled Format String"],"owasp":["A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"category":"security","technology":["javascript"],"subcategory":["audit"],"likelihood":"MEDIUM","impact":"LOW","confidence":"LOW","references":["https://cwe.mitre.org/data/definitions/134.html"],"license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Improper Validation"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring","shortlink":"https://sg.run/7Y5R"},"severity":"INFO","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}},{"check_id":"javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal","path":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts","start":{"line":22,"col":71,"offset":838},"end":{"line":22,"col":102,"offset":869},"extra":{"message":"Detected possible user input going into a `path.join` or `path.resolve` function. This could possibly lead to a path traversal vulnerability, where the attacker can access arbitrary files stored in the file system. Instead, be sure to sanitize or validate user input first.","metadata":{"owasp":["A05:2017 - Broken Access Control","A01:2021 - Broken Access Control","A01:2025 - Broken Access Control"],"cwe":["CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"],"category":"security","references":["https://owasp.org/www-community/attacks/Path_Traversal"],"technology":["javascript","node.js"],"cwe2022-top25":true,"cwe2021-top25":true,"subcategory":["vuln"],"likelihood":"HIGH","impact":"MEDIUM","confidence":"LOW","license":"Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license","vulnerability_class":["Path Traversal"],"source":"https://semgrep.dev/r/javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal","shortlink":"https://sg.run/OPqk"},"severity":"WARNING","fingerprint":"requires login","lines":"requires login","validation_state":"NO_VALIDATOR","engine_kind":"OSS"}}],"errors":[],"paths":{"scanned":["/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/README.md","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/SKILL.md","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/_meta.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/examples/basic-usage.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/index.js","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/openclaw.plugin.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/package.json","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__mocks__/@sodax/sdk.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/errors.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/policyEngine.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/positionAggregator.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/setup.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/sodaxApi.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/__tests__/walletRegistry.test.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/policy/policyEngine.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/providers/spokeProviderFactory.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/sodax/client.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/bridge.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/discovery.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/moneyMarket.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/swap.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/walletManagement.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/errorUtils.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/errors.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/priceService.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/sodaxApi.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/tokenResolver.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backendConfig.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/BankrWalletProvider.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/EnvBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/EvmWalletSkillBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/backends/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/AmpedWalletProvider.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/BankrBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/LocalKeyBackend.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/chainConfig.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/index.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/providers/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/skillWalletAdapter.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/types.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/walletManager.ts","/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/wallet/walletRegistry.ts"]},"time":{"rules":[],"rules_parse_time":29.759514808654785,"profiling_times":{"config_time":39.555999517440796,"core_time":59.95686173439026,"ignores_time":0.0030536651611328125,"total_time":99.5768826007843},"parsing_time":{"total_time":0.0,"per_file_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_files":[]},"scanning_time":{"total_time":31.35837483406067,"per_file_time":{"mean":0.22083362559197658,"std_dev":0.2457130224013178},"very_slow_stats":{"time_ratio":0.44541932318657135,"count_ratio":0.04225352112676056},"very_slow_files":[{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/portfolio.ts","ftime":1.7254259586334229},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/policy/policyEngine.ts","ftime":1.980241060256958},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/utils/positionAggregator.ts","ftime":2.2785351276397705},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/walletManagement.ts","ftime":2.3493080139160156},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/swap.ts","ftime":2.6788549423217773},{"fpath":"/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish/src/tools/moneyMarket.ts","ftime":2.955260992050171}]},"matching_time":{"total_time":0.0,"per_file_and_rule_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_rules_on_files":[]},"tainting_time":{"total_time":0.0,"per_def_and_rule_time":{"mean":0.0,"std_dev":0.0},"very_slow_stats":{"time_ratio":0.0,"count_ratio":0.0},"very_slow_rules_on_defs":[]},"fixpoint_timeouts":[],"prefiltering":{"project_level_time":0.0,"file_level_time":0.0,"rules_with_project_prefilters_ratio":0.0,"rules_with_file_prefilters_ratio":0.9854888253362991,"rules_selected_ratio":0.039190763690287046,"rules_matched_ratio":0.039190763690287046},"targets":[],"total_bytes":0,"max_memory_bytes":1379013312},"engine_requested":"OSS","skipped_rules":[],"profiling_results":[]}
3
4Process exited with code 0
5✓ Completed in 292443ms
mcp-scan
3 findings218643ms
MCP-W004The MCP server is not in our registry.
MCP-W011Third-party content exposure detected (high risk: 0.70). The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.
MCP-W009Direct money access detected (high risk: 1.00). The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets — i.e., direct financial execution (crypto/blockchain). Therefore it meets the "Direct Financial Execution" criteria.
View logs
mcp-scan218643ms
1[2026-02-11T18:39:56.903Z] $ mcp-scan --skills /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish --json
2{
3 "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance": {
4 "client": "not-available",
5 "path": "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance",
6 "servers": [
7 {
8 "name": "amped-defi-publish",
9 "server": {
10 "path": "/tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish",
11 "type": "skill"
12 },
13 "signature": {
14 "metadata": {
15 "meta": null,
16 "protocolVersion": "built-in",
17 "capabilities": {
18 "experimental": null,
19 "logging": null,
20 "prompts": null,
21 "resources": null,
22 "tools": {
23 "listChanged": false
24 },
25 "completions": null,
26 "tasks": null
27 },
28 "serverInfo": {
29 "name": "amped-defi",
30 "title": null,
31 "version": "skills",
32 "websiteUrl": null,
33 "icons": null
34 },
35 "instructions": "25 DeFi tools for cross-chain swaps, bridging, and money market operations via SODAX SDK. Supply on Chain A, borrow to Chain B. Supports Ethereum, Arbitrum, Base, Optimism, Avalanche, BSC, Polygon, Sonic, LightLink, HyperEVM, Kaia.",
36 "prompts": {
37 "listChanged": false
38 },
39 "resources": {
40 "subscribe": null,
41 "listChanged": false
42 }
43 },
44 "prompts": [
45 {
46 "name": "SKILL.md",
47 "title": null,
48 "description": "\n\n# Amped DeFi Skill\n\n## Overview\n\nThe **Amped DeFi** skill provides on-chain DeFi operations capabilities for agents, enabling seamless **swaps**, **bridging**, and **money market** (supply/borrow/repay/withdraw) actions across multiple chains using the SODAX SDK. This skill abstracts the complexity of cross-chain intent flows, allowance handling, and policy enforcement, allowing agents to execute DeFi operations safely and efficiently.\n\n**Key capabilities:**\n- Cross-chain and same-chain token swaps via solver network\n- Token bridging between spoke chains and the Sonic hub chain\n- **Cross-chain money market operations** - supply on one chain, borrow to another!\n- Money market operations (supply, withdraw, borrow, repay) with position tracking\n- Policy enforcement (spend limits, slippage caps, allowlists)\n- Support for both execution mode (agent signs) and prepare mode (unsigned txs for external signing)\n\n## Tool Categories\n\n### Discovery Tools\n\nUse these tools to explore supported chains, tokens, and wallet state before executing operations.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_supported_chains` | List all supported spoke chains (e.g., ethereum, arbitrum, sonic) |\n| `amped_supported_tokens` | Get supported tokens for a specific module (swaps/bridge/moneyMarket) on a chain |\n| `amped_wallet_address` | Resolve wallet address by walletId (validates private key \u2194 address match in execute mode) |\n| `amped_money_market_reserves` | View available money market reserves (collateral/borrow markets) |\n| `amped_money_market_positions` | View user's money market positions on a SINGLE chain |\n| `amped_cross_chain_positions` | **RECOMMENDED**: View aggregated positions across ALL chains with total supply/borrow, health factor, borrowing power, net APY, and risk metrics |\n| `amped_user_intents` | Query user's swap/bridge intent history from SODAX backend API. Shows open, filled, cancelled intents with full event details. |\n\n**When to use:** Always start with discovery tools to verify chain/token support before attempting any operation.\n\n### User Intent History (SODAX API)\n\nQuery the SODAX backend API to retrieve complete intent history for a wallet:\n\n```\n\u2192 amped_user_intents(\n walletId=\"main\",\n status=\"all\", // \"all\", \"open\", or \"closed\"\n limit=10, // Number of results (max 100)\n offset=0 // For pagination\n )\n\u2190 Returns: {\n pagination: { total: 1545, offset: 0, limit: 10, hasMore: true },\n intents: [\n {\n intentHash: \"0x5b18d04a545f089e6de59106fa79498cfc0b0274...\",\n txHash: \"0x1c4a8ded456b97ba9fa2b95ee954ed7e92a40365...\",\n chainId: 146,\n blockNumber: 57622027,\n status: \"closed\",\n createdAt: \"2025-12-10T19:44:00.380Z\",\n input: { token: \"0x654D...\", amount: \"10000000000000000000\", chainId: 1768124270 },\n output: { token: \"0x9Ee1...\", minAmount: \"78684607057391028830\", chainId: 5 },\n deadline: \"2026-12-10T19:48:32.000Z\",\n events: [\n { type: \"intent-filled\", txHash: \"0x7981...\", blockNumber: 57622086, ... }\n ]\n }\n ],\n summary: { totalIntents: 1545, returned: 10, openIntents: 3, closedIntents: 1537 }\n }\n```\n\n**When to use:**\n- Track status of pending swap/bridge operations\n- View historical intent execution history\n- Debug failed or cancelled intents\n- Monitor solver performance and fill rates\n\n### Swap Tools\n\nCross-chain and same-chain token swaps via SODAX's intent-based solver network.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_swap_quote` | Get an exact-in or exact-out swap quote with slippage and fee estimates |\n| `amped_swap_execute` | Execute a swap (handles allowance, approval, and execution automatically) |\n| `amped_swap_status` | Check the status of a swap transaction or intent |\n| `amped_swap_cancel` | Cancel an active swap intent (where supported) |\n\n**When to use swaps:**\n- Exchanging one token for another on the same chain\n- Cross-chain swaps (e.g., USDC on Ethereum \u2192 USDT on Arbitrum)\n- When you need competitive pricing via solver competition\n\n**When NOT to use swaps:**\n- Moving the same token across chains (use bridge tools instead)\n- Borrowing/lending operations (use money market tools instead)\n\n### Bridge Tools\n\nBridge tokens between chains via the swap infrastructure.\n\n> **Note:** In SODAX, bridges and cross-chain swaps use the same underlying intent-based messaging system. The `amped_bridge_execute` tool internally delegates to the swap infrastructure, which provides better routing and reliability.\n>\n> **Recommendation:** Use cross-chain swaps (`amped_swap_quote` + `amped_swap_execute`) directly for bridging. You can swap USDC on one chain directly to native tokens (ETH, AVAX, POL, etc.) on another chain in a single operation.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_bridge_discover` | Discover bridgeable tokens between two chains |\n| `amped_bridge_quote` | Check bridgeability, limits, and max bridgeable amount |\n| `amped_bridge_execute` | Execute bridge (delegates to swap infrastructure) |\n\n**When to use bridging/cross-chain swaps:**\n- Moving tokens from one chain to another (e.g., USDC on Base \u2192 ETH on Arbitrum)\n- Getting native gas tokens on a new chain (e.g., USDC \u2192 POL on Polygon)\n- Transferring assets to/from the Sonic hub chain\n\n**Preferred approach for gas distribution:**\n```\n// Get gas tokens on multiple chains from a single source\n\u2192 amped_swap_quote(srcChainId=\"base\", dstChainId=\"polygon\", srcToken=\"USDC\", dstToken=\"POL\", amount=\"0.5\", ...)\n\u2192 amped_swap_execute(quote)\n// Result: 0.5 USDC on Base \u2192 ~4 POL on Polygon\n```\n\n### Money Market Tools\n\nSupply, borrow, repay, and withdraw assets from the SODAX money market with **cross-chain capabilities**.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_mm_supply` | Supply tokens as collateral to the money market. Supports cross-chain supply. |\n| `amped_mm_withdraw` | Withdraw supplied tokens from the money market. Supports cross-chain withdraw. |\n| `amped_mm_borrow` | Borrow tokens against supplied collateral. **KEY FEATURE: Can borrow to a different chain!** |\n| `amped_mm_repay` | Repay borrowed tokens. Use amount='-1' or repayAll=true for full repay. |\n| `amped_mm_create_supply_intent` | [Advanced] Create a supply intent without executing (for custom flows) |\n| `amped_mm_create_borrow_intent` | [Advanced] Create a borrow intent without executing (supports cross-chain) |\n\n**Cross-Chain Money Market Capabilities:**\n\nThe SODAX money market supports powerful cross-chain operations:\n\n1. **Cross-Chain Borrow** (Most powerful feature)\n - Supply collateral on Chain A (e.g., Ethereum)\n - Borrow tokens to Chain B (e.g., Arbitrum)\n - Your collateral stays on Chain A, but you receive borrowed tokens on Chain B\n - Use `dstChainId` parameter to specify the destination chain\n\n2. **Cross-Chain Supply**\n - Supply tokens on Chain A\n - Collateral is recorded on Chain B (if different)\n - Use `dstChainId` parameter\n\n3. **Cross-Chain Withdraw**\n - Withdraw collateral from Chain A\n - Receive tokens on Chain B\n - Use `dstChainId` parameter\n\n**When to use money market:**\n- Earning yield by supplying assets\n- Borrowing against existing collateral\n- **Accessing liquidity on Chain B without moving collateral from Chain A**\n- Arbitraging interest rates across chains\n- Managing leveraged positions\n- Repaying debt to improve health factor\n\n**When NOT to use money market:**\n- Simple token exchanges (use swap tools)\n- Moving assets across chains without borrowing (use bridge tools)\n\n### Wallet Management Tools\n\nManage multiple wallets with nicknames for easy identification.\n\n| Tool | Purpose |\n|------|---------|\n| `amped_list_wallets` | List all configured wallets with their nicknames and addresses |\n| `amped_add_wallet` | Add a new wallet with a nickname (supports private key or Bankr wallets) |\n| `amped_rename_wallet` | Rename an existing wallet's nickname |\n| `amped_remove_wallet` | Remove a wallet from configuration |\n| `amped_set_default_wallet` | Set which wallet is used by default for operations |\n\n**When to use wallet management:**\n- Setting up multiple wallets for different purposes (trading, holding, testing)\n- Organizing wallets with memorable nicknames instead of addresses\n- Switching between wallets for different operations\n- Managing a portfolio across multiple addresses\n\n**Example workflow:**\n```\n1. amped_add_wallet(nickname=\"trading\", address=\"0x...\", privateKey=\"0x...\")\n2. amped_add_wallet(nickname=\"vault\", address=\"0x...\")\n3. amped_set_default_wallet(nickname=\"trading\")\n4. amped_list_wallets() // Shows all wallets with default indicator\n5. amped_swap_execute(walletId=\"trading\", ...) // Uses trading wallet\n```\n\n## Safety Rules\n\n\u26a0\ufe0f **MUST FOLLOW \u2014 These rules are enforced by the Policy Engine:**\n\n1. **Always get a quote before executing**\n - Never execute a swap without first calling `amped_swap_quote`\n - Never execute a bridge without first calling `amped_bridge_quote`\n - Review the quote output for acceptable slippage and output amounts\n\n2. **Verify chain and token are supported**\n - Call `amped_supported_chains` and `amped_supported_tokens` before operations\n - Unsupported chains/tokens will return clear errors\n\n3. **Check slippage is within acceptable bounds**\n - Slippage is specified in basis points (bps): 100 bps = 1%\n - Default max slippage: 100 bps (1%)\n - Quotes with slippage exceeding configured caps will be rejected\n - Policy violations return structured errors with remediation guidance\n\n4. **Never attempt to drain entire wallet**\n - Leave sufficient balance for gas fees\n - Spend limits are enforced per-transaction and per-day\n - Policy caps: `maxSwapInputUsd`, `maxBridgeAmountToken`, `maxBorrowUsd`\n\n5. **Always verify transaction status after execution**\n - Use `amped_swap_status` to track swap completion\n - Check `amped_money_market_positions` to verify position updates\n - Never assume success based on transaction hash alone\n\n6. **Enforce allowlist compliance**\n - Only operate on `allowedChains` and `allowedTokensByChain` per policy\n - Blocked recipients are rejected\n - Policy failures return structured errors with remediation text\n\n7. **Simulation is enabled by default**\n - `skipSimulation=false` unless operator override\n - Simulations catch revert conditions before broadcast\n\n8. **Monitor health factor for money market positions**\n - Health factor < 1.0 = liquidation risk\n - Keep health factor > 1.5 for safety margin\n - Use `amped_money_market_positions` to monitor\n\n## Parameter Conventions\n\n### Amount Units\n- **Amounts are in human-readable units** (e.g., `\"100\"` for 100 USDC, `\"0.5\"` for 0.5 ETH)\n- The SDK internally converts to raw units using token decimals from SODAX config\n- Examples:\n - `\"1000\"` USDC (USDC has 6 decimals) \u2192 1000000000 raw units\n - `\"1.5\"` ETH (ETH has 18 decimals) \u2192 1500000000000000000 raw units\n\n### Slippage (Basis Points)\n- Slippage is specified in **basis points (bps)** where 100 bps = 1%\n- Common values:\n - `50` = 0.5% (tight, for stable pairs)\n - `100` = 1% (standard)\n - `300` = 3% (volatile pairs or cross-chain)\n- Quotes exceeding configured `maxSlippageBps` will be rejected\n\n### Chain Identifiers\n- Chain IDs are **string identifiers**, not numeric chain IDs:\n - `\"ethereum\"` (Ethereum mainnet)\n - `\"arbitrum\"` (Arbitrum One)\n - `\"sonic\"` (Sonic hub chain)\n - `\"base\"` (Base)\n - `\"optimism\"` (Optimism)\n - `\"avalanche\"` (Avalanche)\n - `\"bsc\"` (BNB Smart Chain)\n\n### Token Addresses\n- Token addresses should be **checksum addresses** (mixed-case per EIP-55)\n- Examples:\n - `\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"` (USDC on Ethereum)\n - `\"0x4200000000000000000000000000000000000006\"` (WETH on Base)\n\n### Wallet Identification\n- All execute tools require a `walletId` string\n- Wallet resolution is by ID; private keys are never exposed in tool parameters\n\n### Optional Parameters\n- `recipient`: Optional destination address (defaults to wallet address)\n- `timeoutMs`: Optional operation timeout in milliseconds\n- `policyId`: Optional policy profile selector for custom limits\n- `dstChainId`: **For cross-chain money market** - destination chain for the operation\n\n## Workflows\n\n### Swap Workflow\n\nComplete workflow for executing a token swap:\n\n```\nStep 1: Discovery (if needed)\n \u2192 amped_supported_chains\n \u2192 amped_supported_tokens(module=\"swaps\", chainId=\"ethereum\")\n\nStep 2: Get Quote\n \u2192 amped_swap_quote(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"arbitrum\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\",\n amount=\"1000\",\n type=\"exact_input\",\n slippageBps=100\n )\n \u2190 Returns: { quoteId, expectedOutput, slippageBps, fees, deadline }\n\nStep 3: Review Quote\n \u2713 Check slippageBps \u2264 maxSlippageBps (configurable, default 100)\n \u2713 Verify expectedOutput meets requirements\n \u2713 Confirm fees are acceptable\n\nStep 4: Execute Swap\n \u2192 amped_swap_execute(\n walletId=\"main\",\n quote=<quote from step 2>,\n maxSlippageBps=100,\n skipSimulation=false\n )\n \u2190 Returns: { spokeTxHash, hubTxHash, intentHash, status }\n\nStep 5: Verify Status\n \u2192 amped_swap_status(txHash=spokeTxHash)\n \u2190 Returns: { status, confirmations, filledAmount, remainingAmount }\n\nStep 6: Handle Failures (if needed)\n \u2192 amped_swap_cancel(walletId=\"main\", intent=<intent>, srcChainId=\"ethereum\")\n```\n\n### Bridge Workflow\n\nComplete workflow for bridging tokens between chains:\n\n```\nStep 1: Discover Routes\n \u2192 amped_bridge_discover(\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"\n )\n \u2190 Returns: { bridgeableTokens: [...] }\n\nStep 2: Get Bridge Quote\n \u2192 amped_bridge_quote(\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\"\n )\n \u2190 Returns: { isBridgeable: true, maxBridgeableAmount: \"1000000\" }\n\nStep 3: Review Limits\n \u2713 Verify isBridgeable === true\n \u2713 Check amount \u2264 maxBridgeableAmount\n \u2713 Confirm amount within policy limits\n\nStep 4: Execute Bridge\n \u2192 amped_bridge_execute(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"sonic\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\",\n amount=\"5000\",\n recipient=\"0x...\" // optional, defaults to wallet\n )\n \u2190 Returns: { spokeTxHash, hubTxHash }\n```\n\n### Money Market Supply Workflow\n\nComplete workflow for supplying to and monitoring money market positions:\n\n```\nStep 1: View Available Markets\n \u2192 amped_money_market_reserves(chainId=\"sonic\")\n \u2190 Returns: { reserves: [\n { token: \"USDC\", supplyAPY: \"4.5%\", totalSupplied: \"...\" },\n { token: \"WETH\", supplyAPY: \"2.1%\", totalSupplied: \"...\" }\n ]}\n\nStep 2: Check Current Positions (RECOMMENDED: use cross-chain view)\n \u2192 amped_cross_chain_positions(walletId=\"main\")\n \u2190 Returns: { \n summary: {\n totalSupplyUsd: \"15000.00\",\n totalBorrowUsd: \"5000.00\",\n netWorthUsd: \"10000.00\",\n availableBorrowUsd: \"7000.00\",\n healthFactor: \"2.55\",\n healthFactorStatus: { status: \"healthy\", color: \"green\" },\n liquidationRisk: \"none\",\n weightedSupplyApy: \"4.25%\",\n weightedBorrowApy: \"3.50%\",\n netApy: \"1.08%\"\n },\n chainBreakdown: [...],\n collateralUtilization: {...},\n riskMetrics: {...},\n positions: [...],\n recommendations: [\"\ud83d\udca1 You have $7000.00 in available borrowing power.\"]\n }\n\nStep 3: Supply Tokens\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\",\n amount=\"1000\",\n useAsCollateral=true // Use as collateral for borrowing\n )\n \u2190 Returns: { txHash, spokeTxHash, hubTxHash }\n\nStep 4: Verify Position Update (cross-chain view)\n \u2192 amped_cross_chain_positions(walletId=\"main\")\n \u2190 Returns: Updated positions reflecting the new supply across all chains\n```\n\n### Cross-Chain Positions View (Recommended)\n\nThe `amped_cross_chain_positions` tool provides a **unified portfolio view** across all chains. This is the recommended way to check money market positions.\n\n**What it shows:**\n- **Total Portfolio Summary**: Supply, borrow, net worth across ALL chains\n- **Health Metrics**: Health factor with status indicator, liquidation risk level\n- **Borrowing Power**: Available borrow amount based on collateral\n- **Yield Metrics**: Weighted supply/borrow APY, net APY\n- **Chain Breakdown**: Per-chain position summaries\n- **Collateral Utilization**: How much of your collateral is being used\n- **Risk Metrics**: Current LTV, buffer until liquidation, safe max borrow\n- **Personalized Recommendations**: AI-generated suggestions based on your position\n\n**Example Response:**\n```json\n{\n \"success\": true,\n \"walletId\": \"main\",\n \"address\": \"0x...\",\n \"timestamp\": \"2026-02-02T12:58:27.999Z\",\n \"summary\": {\n \"totalSupplyUsd\": \"25000.00\",\n \"totalBorrowUsd\": \"8000.00\",\n \"netWorthUsd\": \"17000.00\",\n \"availableBorrowUsd\": \"12000.00\",\n \"healthFactor\": \"2.65\",\n \"healthFactorStatus\": { \"status\": \"healthy\", \"color\": \"green\" },\n \"liquidationRisk\": \"none\",\n \"weightedSupplyApy\": \"4.52%\",\n \"weightedBorrowApy\": \"3.21%\",\n \"netApy\": \"2.89%\"\n },\n \"chainBreakdown\": [\n { \"chainId\": \"ethereum\", \"supplyUsd\": \"15000.00\", \"borrowUsd\": \"5000.00\", \"healthFactor\": \"2.80\" },\n { \"chainId\": \"arbitrum\", \"supplyUsd\": \"5000.00\", \"borrowUsd\": \"2000.00\", \"healthFactor\": \"2.50\" },\n { \"chainId\": \"sonic\", \"supplyUsd\": \"5000.00\", \"borrowUsd\": \"1000.00\", \"healthFactor\": \"5.00\" }\n ],\n \"collateralUtilization\": {\n \"totalCollateralUsd\": \"20000.00\",\n \"usedCollateralUsd\": \"8000.00\",\n \"availableCollateralUsd\": \"12000.00\",\n \"utilizationRate\": \"40.00%\"\n },\n \"riskMetrics\": {\n \"maxLtv\": \"80.00%\",\n \"currentLtv\": \"32.00%\",\n \"bufferUntilLiquidation\": \"53.00%\",\n \"safeMaxBorrowUsd\": \"13600.00\"\n },\n \"recommendations\": [\n \"\ud83d\udca1 You have $12000.00 in available borrowing power.\",\n \"\ud83c\udf10 You have positions across 3 chains. Monitor each chain's health factor independently.\"\n ]\n}\n```\n\n**When to use:**\n- Always start here to get a complete picture of money market positions\n- Before any borrow/withdraw operation to check health factor\n- To monitor portfolio performance across all chains\n- To identify opportunities (available borrowing power, low utilization)\n\n### Cross-Chain Money Market Borrow Workflow (Advanced)\n\n**Key Feature:** Borrow tokens to a different chain than where your collateral is supplied!\n\n```\nScenario: Supply USDC on Ethereum, borrow USDT to Arbitrum\n\nStep 1: Verify Collateral Position on Source Chain\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2190 Returns: { positions: [...], totalCollateralUSD, availableBorrowUSD, healthFactor }\n\nStep 2: Check Borrow Capacity\n \u2713 Verify availableBorrowUSD > desired borrow amount\n \u2713 Check healthFactor will remain safe after borrow\n\nStep 3: Cross-Chain Borrow\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Source chain (where collateral is)\n dstChainId=\"arbitrum\", // Destination chain (where you receive borrowed tokens)\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT on Arbitrum\n amount=\"500\",\n interestRateMode=2 // Variable rate\n )\n \u2190 Returns: { \n txHash, \n spokeTxHash, // On Ethereum (source)\n hubTxHash, \n dstSpokeTxHash, // On Arbitrum (destination)\n isCrossChain: true \n }\n\nStep 4: Verify Position\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2190 Returns: Updated positions with new borrow recorded\n\nStep 5: Verify Received Tokens on Destination Chain\n \u2192 amped_wallet_address(walletId=\"main\")\n \u2190 Check USDT balance on Arbitrum via external means or position query\n```\n\n### Cross-Chain Money Market Supply Workflow\n\n```\nScenario: Supply tokens on Arbitrum, collateral recorded on Sonic\n\nStep 1: Supply with Cross-Chain Flag\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"arbitrum\", // Source chain (where tokens are)\n dstChainId=\"sonic\", // Destination chain (where collateral is recorded)\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\",\n amount=\"1000\",\n useAsCollateral=true\n )\n \u2190 Returns: {\n txHash,\n isCrossChain: true,\n message: \"Tokens supplied on arbitrum. Collateral available on sonic.\"\n }\n\nStep 2: Verify on Destination Chain\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Collateral should appear on Sonic\n```\n\n### Money Market Repay Workflow\n\nComplete workflow for repaying borrowed tokens:\n\n```\nStep 1: Check Borrow Position\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: { positions: [...], totalBorrowUSD, healthFactor }\n\nStep 2: Repay (Full or Partial)\n Option A - Partial Repay:\n \u2192 amped_mm_repay(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"500\"\n )\n \n Option B - Full Repay:\n \u2192 amped_mm_repay(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"-1\", // Special value for max\n repayAll=true\n )\n\nStep 3: Verify Repayment\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Updated positions with reduced borrow, improved healthFactor\n```\n\n### Money Market Withdraw Workflow\n\nComplete workflow for withdrawing supplied tokens:\n\n```\nStep 1: Check Position and Available Liquidity\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Verify: withdrawal won't cause healthFactor to drop below safe level\n \u2190 Verify: sufficient available liquidity in reserve\n\nStep 2: Withdraw\n \u2192 amped_mm_withdraw(\n walletId=\"main\",\n chainId=\"sonic\",\n token=\"0x...\",\n amount=\"500\",\n withdrawType=\"default\" // Options: default, collateral, all\n )\n \u2190 Returns: { txHash, spokeTxHash, hubTxHash }\n\nStep 3: Verify Withdrawal\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n \u2190 Returns: Updated positions with reduced supply\n```\n\n## Cross-Chain Money Market Examples\n\n### Example 1: Supply on Ethereum, Borrow to Base\n\n```\nUser: \"I have USDC on Ethereum. I want to borrow USDC on Base without moving my collateral.\"\n\nAgent actions:\n1. Check positions on Ethereum\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n\n2. Supply USDC on Ethereum\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"ethereum\",\n token=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n amount=\"10000\",\n useAsCollateral=true\n )\n\n3. Cross-chain borrow to Base\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Collateral is here\n dstChainId=\"base\", // Receive borrowed tokens here\n token=\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\", // USDC on Base\n amount=\"5000\",\n interestRateMode=2\n )\n\n4. Verify positions\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"base\")\n```\n\n### Example 2: Cross-Chain Withdraw\n\n```\nUser: \"I have collateral on Sonic but I want to withdraw to Arbitrum.\"\n\nAgent actions:\n\u2192 amped_mm_withdraw(\n walletId=\"main\",\n chainId=\"sonic\", // Collateral source\n dstChainId=\"arbitrum\", // Token destination\n token=\"0x...\",\n amount=\"1000\"\n )\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `AMPED_OC_MODE` | Operation mode: `'execute'` (agent signs) or `'prepare'` (returns unsigned txs) | `execute` |\n| `AMPED_OC_WALLETS_JSON` | JSON map of wallet configurations keyed by walletId | `{}` |\n| `AMPED_OC_RPC_URLS_JSON` | JSON map of RPC URLs by chainId | `{}` |\n| `AMPED_OC_LIMITS_JSON` | Policy limits configuration | `{}` |\n| `AMPED_OC_SODAX_DYNAMIC_CONFIG` | Enable dynamic config via `sodax.initialize()` | `false` |\n\n### Wallet Configuration (`AMPED_OC_WALLETS_JSON`)\n\n```json\n{\n \"main\": {\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\" // Required for execute mode\n },\n \"trading\": {\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\"\n }\n}\n```\n\n**Security:** Private keys are never logged. In prepare mode, only address is required.\n\n### Policy Limits (`AMPED_OC_LIMITS_JSON`)\n\n```json\n{\n \"maxSwapInputUsd\": 10000,\n \"maxBridgeAmountToken\": {\n \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\": 50000\n },\n \"maxBorrowUsd\": 5000,\n \"maxSlippageBps\": 100,\n \"allowedChains\": [\"ethereum\", \"arbitrum\", \"sonic\", \"base\"],\n \"allowedTokensByChain\": {\n \"ethereum\": [\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", \"0x...\"]\n },\n \"blockedRecipients\": [\"0x...\"]\n}\n```\n\n### RPC Configuration (`AMPED_OC_RPC_URLS_JSON`)\n\n```json\n{\n \"ethereum\": \"https://eth-mainnet.g.alchemy.com/v2/...\",\n \"arbitrum\": \"https://arb-mainnet.g.alchemy.com/v2/...\",\n \"base\": \"https://base-mainnet.g.alchemy.com/v2/...\",\n \"sonic\": \"https://rpc.soniclabs.com\"\n}\n```\n\n## Error Handling\n\n### Policy Violations\n\nPolicy violations return structured errors with:\n- `code`: Error code (e.g., `POLICY_SLIPPAGE_EXCEEDED`)\n- `message`: Human-readable description\n- `remediation`: Suggested action to resolve\n- `current`: Current value that violated policy\n- `limit`: Configured limit\n\n### Common Error Codes\n\n| Code | Description | Remediation |\n|------|-------------|-------------|\n| `POLICY_SLIPPAGE_EXCEEDED` | Quote slippage exceeds maxSlippageBps | Increase maxSlippageBps or wait for better conditions |\n| `POLICY_SPEND_LIMIT_EXCEEDED` | Amount exceeds per-transaction or daily limit | Reduce amount or request limit increase |\n| `POLICY_CHAIN_NOT_ALLOWED` | Chain not in allowedChains | Add chain to allowedChains or use different chain |\n| `POLICY_TOKEN_NOT_ALLOWED` | Token not in allowedTokensByChain | Add token to allowlist or use different token |\n| `INSUFFICIENT_BALANCE` | Wallet balance < requested amount | Reduce amount or fund wallet |\n| `INSUFFICIENT_ALLOWANCE` | Token allowance < requested amount | Tool will auto-approve, or approve manually |\n| `QUOTE_EXPIRED` | Quote deadline has passed | Get fresh quote |\n| `BRIDGE_NOT_AVAILABLE` | Token pair not bridgeable | Use swap for different tokens or different route |\n| `MM_HEALTH_FACTOR_LOW` | Operation would cause liquidation risk | Repay debt or add collateral first |\n| `MM_CROSS_CHAIN_NOT_SUPPORTED` | Cross-chain operation not supported for this pair | Use same-chain operation or different token/chain |\n\n## Idempotency and Retries\n\n### Client Operation ID\n\nExecute tools accept an optional `clientOperationId` parameter for idempotency:\n- Duplicate operations with the same ID within the cache window return the cached result\n- Prevents duplicate broadcasts on retries\n- Recommended for automated workflows\n\n### Retry Guidance\n\n- **Read operations** (quotes, status, positions): Safe to retry with exponential backoff\n- **Write operations** (execute, supply, borrow): Use `clientOperationId` to prevent duplicates\n- **Timeout handling**: Bridge and money market operations specify timeouts; respect SDK defaults\n\n## Security Model\n\n### Key Segregation\n\n- Each agent workspace has distinct wallet configurations\n- Spoke providers are cached per `walletId` and never shared across agents\n- Private keys are resolved by `walletId` only; never passed as parameters\n\n### Execution vs Prepare Mode\n\n| Mode | Signing | Use Case |\n|------|---------|----------|\n| `execute` | Agent signs with private key | Automated agents, server-side operations |\n| `prepare` | Returns unsigned tx for external signing | Hardware wallets, air-gapped signing, multi-sig |\n\n### Logging\n\nStructured logs include:\n- `requestId`: Unique request identifier\n- `agentId`: Agent identifier (if available)\n- `walletId`: Wallet identifier\n- `opType`: Operation type (swap, bridge, supply, etc.)\n- `chainIds`, `tokenAddresses`: Operation context\n- `txHashes`: Transaction hashes (for tracing)\n\n**Never logged:** Private keys, full wallet JSON, sensitive configuration\n\n## Chain-Specific Notes\n\n### Sonic Hub Chain\n\n- Sonic is the **hub chain** for SODAX operations\n- Uses `SonicSpokeProvider` instead of `EvmSpokeProvider`\n- Special handling required for hub chain operations\n- Money market operations are hub-centric but support cross-chain interactions\n\n### EVM Spoke Chains\n\n- Use `EvmSpokeProvider` for standard EVM chains (Ethereum, Arbitrum, Base, etc.)\n- Standard allowance/approval flow applies\n- Bridge operations go: Spoke \u2192 Hub \u2192 Destination Spoke\n- Cross-chain money market operations leverage the hub for state management\n\n## Cross-Chain Money Market Architecture\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 SODAX Money Market Flow \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502\n\u2502 Cross-Chain Borrow Example: \u2502\n\u2502 Supply USDC on Ethereum \u2192 Borrow USDT on Arbitrum \u2502\n\u2502 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 Ethereum \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Sonic \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Arbitrum \u2502 \u2502\n\u2502 \u2502 (Supply) \u2502 \u2502 (Hub) \u2502 \u2502 (Borrow) \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2502 1. Supply USDC \u2502 \u2502 \u2502\n\u2502 \u2502 2. Record collateral\u2502 \u2502 \u2502\n\u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502 \u2502\n\u2502 \u2502 \u2502 3. Verify collateral\u2502 \u2502\n\u2502 \u2502 \u2502 4. Process borrow \u2502 \u2502\n\u2502 \u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502\n\u2502 \u2502 \u2502 \u2502 5. Deliver\u2502\n\u2502 \u2502 \u2502 \u2502 USDT \u2502\n\u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2502\n\u2502 Key Insight: Your collateral stays on the source chain, \u2502\n\u2502 but you receive borrowed tokens on the destination chain! \u2502\n\u2502 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n## Examples\n\n### Example 1: Simple Same-Chain Swap\n\n```\nUser: \"Swap 100 USDC for ETH on Ethereum\"\n\nAgent actions:\n1. amped_swap_quote(\n walletId=\"main\",\n srcChainId=\"ethereum\",\n dstChainId=\"ethereum\",\n srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n dstToken=\"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\",\n amount=\"100\",\n type=\"exact_input\",\n slippageBps=100\n )\n2. Review quote (slippage 0.8%, expected output 0.042 ETH)\n3. amped_swap_execute(walletId=\"main\", quote=<quote>, maxSlippageBps=100)\n4. amped_swap_status(txHash=<spokeTxHash>)\n```\n\n### Example 2: Cross-Chain Bridge\n\n```\nUser: \"Bridge 1000 USDC from Ethereum to Sonic\"\n\nAgent actions:\n1. amped_bridge_discover(srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\")\n2. amped_bridge_quote(srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\")\n3. amped_bridge_execute(walletId=\"main\", srcChainId=\"ethereum\", dstChainId=\"sonic\", srcToken=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", dstToken=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\", amount=\"1000\")\n```\n\n### Example 3: Supply and Borrow Loop (Same Chain)\n\n```\nUser: \"Supply 5000 USDC and borrow 2000 USDT on Sonic\"\n\nAgent actions:\n1. amped_money_market_reserves(chainId=\"sonic\")\n2. amped_mm_supply(walletId=\"main\", chainId=\"sonic\", token=\"0x29219dd400f2bf60e5a23d13be72b486d4038894\", amount=\"5000\")\n3. amped_money_market_positions(walletId=\"main\", chainId=\"sonic\")\n4. amped_mm_borrow(walletId=\"main\", chainId=\"sonic\", token=\"0x...usdt...\", amount=\"2000\")\n5. amped_money_market_positions(walletId=\"main\", chainId=\"sonic\") // Verify new health factor\n```\n\n### Example 4: Cross-Chain Money Market (Advanced)\n\n```\nUser: \"I want to use my USDC on Ethereum as collateral to borrow USDC on Arbitrum for a trading opportunity\"\n\nAgent actions:\n1. Verify supported chains and tokens\n \u2192 amped_supported_tokens(module=\"moneyMarket\", chainId=\"ethereum\")\n \u2192 amped_supported_tokens(module=\"moneyMarket\", chainId=\"arbitrum\")\n\n2. Check current positions\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"ethereum\")\n \u2192 amped_money_market_positions(walletId=\"main\", chainId=\"arbitrum\")\n\n3. Supply USDC on Ethereum\n \u2192 amped_mm_supply(\n walletId=\"main\",\n chainId=\"ethereum\",\n token=\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n amount=\"50000\",\n useAsCollateral=true\n )\n\n4. Cross-chain borrow to Arbitrum\n \u2192 amped_mm_borrow(\n walletId=\"main\",\n chainId=\"ethereum\", // Source: collateral is here\n dstChainId=\"arbitrum\", // Destination: receive tokens here\n token=\"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDC on Arbitrum\n amount=\"20000\",\n interestRateMode=2\n )\n\n5. Verify the cross-chain borrow worked\n \u2192 Check positions on Ethereum (collateral + debt recorded)\n \u2192 Check USDC balance on Arbitrum (borrowed tokens received)\n\nResult: User has 20k USDC on Arbitrum to trade with, while their 50k USDC collateral remains on Ethereum!\n```\n\n## Transaction Execution Architecture\n\n### SODAX-First Routing (Mandatory)\n\n**Critical:** ALL DeFi operations MUST route through the SODAX SDK first. External wallet backends (like Bankr) are used ONLY for transaction execution\u2014never for routing decisions.\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 SODAX SDK (Routing Layer) \u2502\n\u2502 \u2713 Calculates optimal swap routes \u2502\n\u2502 \u2713 Determines bridge paths \u2502\n\u2502 \u2713 Manages money market intents \u2502\n\u2502 \u2713 Handles slippage, fees, deadlines \u2502\n\u2502 \u2713 Prepares transaction data (to, data, value) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25bc raw transaction data\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Wallet Backend (Execution Layer ONLY) \u2502\n\u2502 \u2713 Signs the pre-computed transaction \u2502\n\u2502 \u2713 Submits to blockchain \u2502\n\u2502 \u2713 Returns transaction hash \u2502\n\u2502 \u2717 NO routing decisions \u2502\n\u2502 \u2717 NO token swapping logic \u2502\n\u2502 \u2717 NO DeFi protocol selection \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### Supported Backends\n\n| Backend | Description | Use Case |\n|---------|-------------|----------|\n| `localKey` | Direct signing via private key from `~/.evm-wallet.json` or config | Default, self-custody |\n| `bankr` | Bankr API for transaction submission | Managed wallets via Bankr |\n\n### Backend Selection\n\nThe wallet backend is selected via:\n1. `config.json` \u2192 `walletBackend: \"bankr\" | \"localKey\"`\n2. Environment: `AMPED_OC_WALLET_BACKEND`\n3. Default: `localKey`\n\n### Bankr Integration\n\nWhen `walletBackend: \"bankr\"` is configured:\n\n1. **SODAX SDK prepares the transaction** - All routing, calculation, and intent creation happens in SODAX\n2. **Transaction data is passed to Bankr** - Only the raw `{to, data, value, chainId}` is sent\n3. **Bankr signs and submits** - Bankr executes exactly what SODAX prepared\n4. **No Bankr routing** - Bankr does NOT interpret or re-route the transaction\n\nThis ensures:\n- Consistent behavior across all backends\n- SODAX optimizations always apply\n- Audit trail shows SODAX as routing authority\n- Backend is a pure execution layer\n\n### Configuration Example (Bankr)\n\n```json\n// ~/.openclaw/extensions/amped-defi/config.json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"bk_...\",\n \"bankrApiUrl\": \"https://api.bankr.bot\",\n \"bankrWalletAddress\": \"0x...\"\n}\n```\n\n**Security Note:** The Bankr API key is stored locally and never exposed in tool parameters or logs.\n",
49 "arguments": [],
50 "icons": null,
51 "meta": null
52 },
53 {
54 "name": "README.md",
55 "title": null,
56 "description": "# Amped DeFi Plugin\n\n[![npm version](https://img.shields.io/npm/v/amped-defi.svg)](https://www.npmjs.com/package/amped-defi)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)\n\nDeFi operations plugin for [OpenClaw](https://openclaw.ai) enabling seamless cross-chain swaps, bridging, and money market operations via the [SODAX SDK](https://docs.sodax.com).\n\n## Features\n\n- **\ud83d\udd01 Cross-Chain Swaps** - Execute token swaps across multiple chains via SODAX's intent-based solver network\n- **\ud83c\udf09 Token Bridging** - Bridge assets between spoke chains and the Sonic hub chain\n- **\ud83c\udfe6 Cross-Chain Money Market** - Supply on Chain A, borrow to Chain B - your collateral stays put!\n- **\ud83d\udcca Unified Portfolio View** - Cross-chain position aggregator with health metrics, risk analysis & recommendations\n- **\ud83d\udcdc Intent History** - Query complete swap/bridge history via SODAX API\n- **\ud83d\udd10 Security First** - Policy engine with spend limits, slippage caps, allowlists\n- **\u26a1 Dual Mode** - Execute mode (agent signs) or prepare mode (unsigned txs for external signing)\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Quick Start](#quick-start)\n- [Available Tools](#available-tools)\n- [Usage Examples](#usage-examples)\n- [Cross-Chain Money Market](#cross-chain-money-market)\n- [API Integration](#api-integration)\n- [Error Handling](#error-handling)\n- [Testing](#testing)\n- [Project Structure](#project-structure)\n- [License](#license)\n\n## Installation\n\n### Prerequisites\n\n- Node.js >= 18.0.0\n- [OpenClaw](https://openclaw.ai) installed and configured\n- **[evm-wallet-skill](https://github.com/surfer77/evm-wallet-skill)** (recommended) - For wallet and RPC configuration\n\n### Quick Install\n\n```bash\nopenclaw plugins install amped-defi\n```\n\nVerify with `openclaw plugins list`.\n\n### Install from Source\n\nIf you prefer installing from source:\n\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n#### 4. Verify Installation\n\n```bash\n# List loaded plugins\nopenclaw plugins list\n\n# Check for amped tools (should see 24 tools)\nopenclaw tools list | grep amped_\n```\n\nYou should see tools like:\n- `amped_supported_chains`\n- `amped_swap_quote`\n- `amped_mm_supply`\n- `amped_cross_chain_positions`\n- etc.\n\n### Updating the Plugin\n\n```bash\nopenclaw plugins uninstall amped-defi\nopenclaw plugins install amped-defi\n```\n\n### Uninstalling\n\n```bash\nopenclaw plugins uninstall amped-defi\n```\n\n### Troubleshooting Installation\n\n**\"Cannot find module 'viem'\" or similar errors:**\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n**\"plugin not found\" after uninstall:**\nEdit `~/.openclaw/openclaw.json` and remove the `amped-defi` entry from `plugins.entries`.\n\n**Plugin not loading:**\nCheck OpenClaw logs for errors:\n```bash\ntail -f ~/.openclaw/logs/openclaw.log\n```\n\n## Configuration\n\n### Wallet Setup (Optional)\n\nThe plugin works without a wallet for **read-only operations** (quotes, balances, discovery). To execute transactions, configure a wallet using one of the options below.\n\n> **No wallet configured?** The agent will prompt you to install [evm-wallet-skill](https://github.com/amped-finance/evm-wallet-skill) when you try to execute a transaction.\n\n### \ud83d\udd0c evm-wallet-skill Integration (Recommended)\n\nInstall [evm-wallet-skill](https://github.com/amped-finance/evm-wallet-skill) for self-sovereign wallet management:\n\n```bash\ngit clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\ncd ~/.openclaw/skills/evm-wallet-skill && npm install\nnode src/setup.js # Generate a new wallet\n```\n\nThe plugin automatically detects wallets from `~/.evm-wallet.json`.\n\n**Supported chains:** Ethereum, Base, Arbitrum, Optimism, Polygon, Sonic, LightLink, HyperEVM, Avalanche, BSC, MegaETH, and more.\n\n**Add custom chains via natural language:**\n> \"Add Berachain with chain ID 80094 and RPC https://rpc.berachain.com\"\n\nOr directly:\n```bash\nnode src/add-chain.js berachain 80094 https://rpc.berachain.com --native-token BERA\n```\n\nThe plugin will automatically detect and use:\n- `EVM_WALLETS_JSON` or `WALLET_CONFIG_JSON`\n- `EVM_RPC_URLS_JSON` or `RPC_URLS_JSON`\n\n### \ud83e\udd16 Bankr Integration\n\nFor users with [Bankr](https://bankr.bot) wallets, the plugin supports the Bankr Agent API for transaction execution. This allows agents to execute transactions through Bankr's managed infrastructure.\n\n**Option 1: Environment Variable**\n```bash\nexport BANKR_API_KEY=your-bankr-api-key\n```\n\n**Option 2: Config File**\n\nCreate `~/.openclaw/extensions/amped-defi/config.json`:\n```json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"your-bankr-api-key\"\n}\n```\n\n> \u26a0\ufe0f **Important:** Your Bankr API key must have **\"Agent API\" access enabled** in your Bankr dashboard. A standard bot key won't work.\n\nWhen configured, the plugin automatically uses Bankr for transaction execution instead of local key signing.\n\n### Manual Configuration\n\nIf you're not using evm-wallet-skill, set these environment variables:\n\n#### Required\n\n```bash\n# Wallet configuration (JSON map of walletId -> {address, privateKey})\nexport AMPED_OC_WALLETS_JSON='{\n \"main\": {\n \"address\": \"0xYourWalletAddress\",\n \"privateKey\": \"0xYourPrivateKey\"\n },\n \"trading\": {\n \"address\": \"0xAnotherAddress\",\n \"privateKey\": \"0xAnotherPrivateKey\"\n }\n}'\n\n# RPC URLs for all supported chains\nexport AMPED_OC_RPC_URLS_JSON='{\n \"ethereum\": \"https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"arbitrum\": \"https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"base\": \"https://base-mainnet.g.alchemy.com/v2/YOUR_KEY\",\n \"sonic\": \"https://rpc.soniclabs.com\"\n}'\n```\n\n#### Optional\n\n```bash\n# Operation mode: \"execute\" (default) or \"prepare\"\nexport AMPED_OC_MODE=execute\n\n# Enable dynamic SODAX config (fetches from API)\nexport AMPED_OC_SODAX_DYNAMIC_CONFIG=true\n\n# Policy limits (JSON)\nexport AMPED_OC_LIMITS_JSON='{\n \"default\": {\n \"maxSlippageBps\": 100,\n \"maxSwapInputUsd\": 10000,\n \"maxBorrowUsd\": 50000,\n \"allowedChains\": [\"ethereum\", \"arbitrum\", \"base\", \"sonic\"],\n \"allowedTokensByChain\": {\n \"ethereum\": [\"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\"]\n }\n }\n}'\n\n# SODAX API configuration\nexport SODAX_API_URL=https://canary-api.sodax.com # or https://api.sodax.com\nexport SODAX_API_KEY=your-api-key # if required\n```\n\n### \ud83c\udfe6 Bankr Integration (Alternative Wallet Backend)\n\nInstead of using local private keys, you can use [Bankr](https://bankr.bot) as your wallet backend. Bankr manages keys securely and executes transactions on your behalf via their AI Agent API.\n\n#### Why Bankr?\n\n- **No private key exposure** - Keys stay with Bankr, never in your config\n- **Multi-chain support** - Base, Ethereum, Polygon, Solana, and more\n- **Built-in trading features** - DCA, limit orders, stop-loss\n- **Social trading** - Send to ENS, Twitter handles, Farcaster\n\n#### Setup\n\n1. **Create a Bankr account** at [bankr.bot](https://bankr.bot)\n\n2. **Generate an API key** at [bankr.bot/api](https://bankr.bot/api)\n - Enable **\"Agent API\"** access on the key\n\n3. **Configure the plugin:**\n\n```bash\n# Set your Bankr API key\nexport BANKR_API_KEY=bk_your_api_key_here\n\n# Optionally specify the backend explicitly\nexport AMPED_OC_WALLET_BACKEND=bankr\n```\n\nOr via config file (`~/.openclaw/extensions/amped-defi/config.json`):\n\n```json\n{\n \"walletBackend\": \"bankr\",\n \"bankrApiKey\": \"bk_your_api_key_here\",\n \"bankrApiUrl\": \"https://api.bankr.bot\"\n}\n```\n\n#### How It Works\n\nWhen using Bankr backend:\n1. Plugin prepares transaction calldata (approvals, swaps, etc.)\n2. Submits to Bankr Agent API as an execution request\n3. Bankr signs and broadcasts the transaction\n4. Plugin receives transaction hash on completion\n\n#### Important Notes\n\n- **Separate wallet**: Your Bankr wallet address is different from your personal wallets\n- **Check your address**: Run `\"What is my wallet address?\"` in Bankr terminal\n- **Fund the wallet**: Send ETH/gas tokens to your Bankr wallet before trading\n- **Rate limits**: Agent API may have rate limits depending on your plan\n\n#### Bankr vs Local Keys\n\n| Feature | Local Keys | Bankr |\n|---------|-----------|-------|\n| Key storage | Your machine | Bankr servers |\n| Setup | Configure private key | API key only |\n| Security | You manage keys | Bankr manages keys |\n| Execution | Direct RPC calls | Via Bankr API |\n| Speed | Instant | ~2-45 seconds |\n| Features | Basic tx signing | Full trading suite |\n\n\n## Quick Start\n\n```typescript\nimport { activate, deactivate } from 'amped-defi';\n\n// In your OpenClaw agent setup\nasync function setupAgent(agentTools) {\n // Activate the plugin\n await activate(agentTools);\n \n // Plugin is now ready with all tools registered\n // Tools can be called via the agent\n}\n\n// Cleanup when done\nawait deactivate();\n```\n\n## Available Tools\n\n### Wallet Management Tools (5)\n\n| Tool | Description |\n|------|-------------|\n| `amped_list_wallets` | List all configured wallets with nicknames and addresses |\n| `amped_add_wallet` | Add a new wallet with a nickname |\n| `amped_rename_wallet` | Rename an existing wallet |\n| `amped_remove_wallet` | Remove a wallet from configuration |\n| `amped_set_default_wallet` | Set which wallet to use by default |\n\n### Discovery Tools (8)\n\n| Tool | Description |\n|------|-------------|\n| `amped_supported_chains` | List all supported spoke chains |\n| `amped_supported_tokens` | Get supported tokens by module and chain |\n| `amped_wallet_address` | Resolve wallet address by walletId |\n| `amped_money_market_reserves` | View market reserves and liquidity |\n| `amped_money_market_positions` | View positions on a single chain |\n| `amped_cross_chain_positions` | \u2b50 **Unified portfolio view across ALL chains** |\n| `amped_user_intents` | Query intent history via SODAX API |\n\n### Swap Tools (4)\n\n| Tool | Description |\n|------|-------------|\n| `amped_swap_quote` | Get exact-in/exact-out swap quote |\n| `amped_swap_execute` | Execute swap with policy enforcement |\n| `amped_swap_status` | Check swap status by txHash/intentHash |\n| `amped_swap_cancel` | Cancel pending swap intent |\n\n### Bridge Tools (3)\n\n| Tool | Description |\n|------|-------------|\n| `amped_bridge_discover` | Discover bridgeable tokens for a route |\n| `amped_bridge_quote` | Check bridgeability and max amount |\n| `amped_bridge_execute` | Execute bridge operation |\n\n### Money Market Tools (4)\n\n| Tool | Description |\n|------|-------------|\n| `amped_mm_supply` | Supply tokens as collateral |\n| `amped_mm_withdraw` | Withdraw supplied tokens |\n| `amped_mm_borrow` | Borrow tokens (cross-chain capable!) |\n| `amped_mm_repay` | Repay borrowed tokens |\n\n## Wallet Management\n\nManage wallets through natural language or tool calls:\n\n### Natural Language Examples\n\n```\n\"What wallets do I have?\"\n\"Add a wallet called trading with address 0x... and private key 0x...\"\n\"Rename main to savings\"\n\"Make bankr my default wallet\"\n\"Remove the trading wallet\"\n```\n\n### Multiple Wallet Support\n\n| Source | Default Nickname | Supported Chains |\n|--------|-----------------|------------------|\n| evm-wallet-skill | `main` | All SODAX chains |\n| Bankr | `bankr` | Ethereum, Base, Polygon |\n| Environment | Custom | All SODAX chains |\n\n### Using Wallets in Operations\n\nSpecify a wallet nickname in any operation:\n\n```typescript\n// Swap using a specific wallet\nawait agent.call('amped_swap_execute', {\n walletId: 'trading', // Use the \"trading\" wallet\n quote: quoteResult,\n maxSlippageBps: 50\n});\n```\n\nOr in natural language:\n```\n\"Swap 100 USDC to ETH using trading\"\n\"Check balance on bankr\"\n```\n\n### Wallet Config File\n\nConfigurations persist to `~/.openclaw/extensions/amped-defi/wallets.json`:\n\n```json\n{\n \"wallets\": {\n \"trading\": {\n \"source\": \"env\",\n \"address\": \"0x...\",\n \"privateKey\": \"0x...\"\n }\n },\n \"default\": \"main\"\n}\n```\n\n## Usage Examples\n\n### 1. Cross-Chain Position View (Recommended)\n\nGet a complete portfolio overview across all chains:\n\n```typescript\nconst positions = await agentTools.call('amped_cross_chain_positions', {\n walletId: 'main'\n});\n\n// Response:\n{\n summary: {\n totalSupplyUsd: \"25000.00\",\n totalBorrowUsd: \"8000.00\",\n netWorthUsd: \"17000.00\",\n availableBorrowUsd: \"12000.00\",\n healthFactor: \"2.65\",\n healthFactorStatus: { status: \"healthy\", color: \"green\" },\n liquidationRisk: \"none\",\n weightedSupplyApy: \"4.52%\",\n weightedBorrowApy: \"3.21%\",\n netApy: \"2.89%\"\n },\n chainBreakdown: [\n { chainId: \"ethereum\", supplyUsd: \"15000.00\", borrowUsd: \"5000.00\", healthFactor: \"2.80\" },\n { chainId: \"arbitrum\", supplyUsd: \"5000.00\", borrowUsd: \"2000.00\", healthFactor: \"2.50\" },\n { chainId: \"sonic\", supplyUsd: \"5000.00\", borrowUsd: \"1000.00\", healthFactor: \"5.00\" }\n ],\n collateralUtilization: {\n totalCollateralUsd: \"20000.00\",\n usedCollateralUsd: \"8000.00\",\n utilizationRate: \"40.00%\"\n },\n riskMetrics: {\n maxLtv: \"80.00%\",\n currentLtv: \"32.00%\",\n bufferUntilLiquidation: \"53.00%\",\n safeMaxBorrowUsd: \"13600.00\"\n },\n recommendations: [\n \"\ud83d\udca1 You have $12000.00 in available borrowing power.\",\n \"\ud83c\udf10 You have positions across 3 chains.\"\n ]\n}\n```\n\n### 2. Cross-Chain Swap\n\n```typescript\n// Step 1: Get quote\nconst quote = await agentTools.call('amped_swap_quote', {\n walletId: 'main',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDT\n amount: '1000',\n type: 'exact_input',\n slippageBps: 100\n});\n\n// Step 2: Execute\nconst result = await agentTools.call('amped_swap_execute', {\n walletId: 'main',\n quote: quote,\n maxSlippageBps: 100\n});\n\n// Returns: { spokeTxHash, hubTxHash, intentHash, status }\n```\n\n### 3. Query Intent History\n\n```typescript\nconst history = await agentTools.call('amped_user_intents', {\n walletId: 'main',\n status: 'all', // 'all', 'open', or 'closed'\n limit: 50,\n offset: 0\n});\n\n// Returns paginated intent history with event details\n```\n\n## Cross-Chain Money Market\n\nThe plugin's standout feature is **cross-chain money market operations**:\n\n```\nSupply USDC on Ethereum \u2192 Borrow USDT on Arbitrum\n```\n\nYour collateral stays on the source chain, but you receive borrowed tokens on the destination chain.\n\n### Example: Cross-Chain Borrow\n\n```typescript\n// Step 1: Supply on Ethereum\nawait agentTools.call('amped_mm_supply', {\n walletId: 'main',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC\n amount: '50000',\n useAsCollateral: true\n});\n\n// Step 2: Borrow to Arbitrum (different chain!)\nawait agentTools.call('amped_mm_borrow', {\n walletId: 'main',\n chainId: 'ethereum', // Collateral source\n dstChainId: 'arbitrum', // Borrowed tokens destination\n token: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDT\n amount: '20000',\n interestRateMode: 2 // Variable rate\n});\n\n// Result: User has 20k USDT on Arbitrum, 50k USDC collateral remains on Ethereum\n```\n\n## API Integration\n\nThe plugin integrates with the SODAX backend API for enhanced querying:\n\n| Environment | Base URL |\n|-------------|----------|\n| Canary (Pre-release) | `https://canary-api.sodax.com` |\n| Production | `https://api.sodax.com` |\n\n### Features:\n- **Paginated Intent History** - Query all intents with offset/limit\n- **Status Filtering** - Filter by open/closed status\n- **Chain/Token Filtering** - Filter by source/destination chain or token\n- **Event History** - Full event log for each intent (fills, cancels, etc.)\n\n### API Configuration:\n\n```bash\nexport SODAX_API_URL=https://canary-api.sodax.com\nexport SODAX_API_KEY=your-api-key # if required\n```\n\n## Partner Fee Configuration\n\nTo earn partner fees from swaps and bridges, modify the hardcoded values in `src/sodax/client.ts`:\n\n```typescript\n// src/sodax/client.ts\n\n// HARDCODED PARTNER CONFIGURATION\nconst PARTNER_ADDRESS: string | undefined = '0xYourPartnerWalletAddress';\nconst PARTNER_FEE_BPS: number | undefined = 10; // 0.1%\n```\n\nPartner fees are automatically collected from swap and bridge operations and sent to the specified address. These values are hardcoded to ensure consistent fee collection across all plugin instances.\n\n## Error Handling\n\nThe plugin provides structured error codes for better debugging:\n\n```typescript\nimport { ErrorCode, wrapError } from '@amped/openclaw-plugin';\n\ntry {\n await agentTools.call('amped_swap_execute', params);\n} catch (error) {\n const ampedError = wrapError(error);\n \n console.log(ampedError.code); // POLICY_SLIPPAGE_EXCEEDED\n console.log(ampedError.message); // Human-readable message\n console.log(ampedError.remediation); // Suggestion to fix\n}\n```\n\n### Error Categories:\n- **Policy Errors** - Slippage exceeded, spend limits, chain/token restrictions\n- **Wallet Errors** - Not found, invalid address, missing private key\n- **Transaction Errors** - Failed, timeout, rejected, simulation failed\n- **SDK Errors** - Not initialized, configuration errors\n\n## Troubleshooting\n\n### Plugin Not Loading in OpenClaw\n\n**Issue:** Tools not showing up in `openclaw tools list`\n\n**Solutions:**\n1. Ensure dependencies are installed in the extension directory:\n ```bash\n cd ~/.openclaw/extensions/amped-defi\n npm install\n ```\n\n2. Check OpenClaw config has the plugin enabled:\n ```yaml\n plugins:\n entries:\n amped-defi:\n enabled: true\n ```\n\n3. Check OpenClaw logs for errors:\n ```bash\n tail -100 ~/.openclaw/logs/openclaw.log\n ```\n\n### \"Cannot find module\" Errors\n\nThis means dependencies weren't installed in the extension directory:\n```bash\ncd ~/.openclaw/extensions/amped-defi\nnpm install\n```\n\n### \"plugin not found\" After Uninstall\n\nEdit `~/.openclaw/openclaw.json` and remove the stale entry:\n```bash\n# Remove the amped-defi entry from plugins.entries\nnano ~/.openclaw/openclaw.json\n```\n\n### Wallet Not Found Errors\n\nEnsure you have either:\n- `EVM_WALLETS_JSON` set (if using evm-wallet-skill)\n- `AMPED_OC_WALLETS_JSON` set (plugin-specific)\n- Or configure via OpenClaw plugin settings\n\n### RPC URL Not Configured\n\nEnsure you have either:\n- `EVM_RPC_URLS_JSON` set (if using evm-wallet-skill) \n- `AMPED_OC_RPC_URLS_JSON` set (plugin-specific)\n- Or configure via OpenClaw plugin settings\n\n## Testing\n\n```bash\n# Install dependencies\nnpm install\n\n# Run tests\nnpm test\n\n# Run tests with coverage\nnpm run test:coverage\n\n# Run tests in watch mode\nnpm run test:watch\n\n# Type check\nnpm run typecheck\n\n# Lint\nnpm run lint\n```\n\n### Test Coverage\n\n- \u2705 Error handling utilities\n- \u2705 Policy engine\n- \u2705 Position aggregator\n- \u2705 Wallet registry\n- \u2705 SODAX API client\n\n## Project Structure\n\n```\nsrc/\n\u251c\u2500\u2500 index.ts # Plugin entry point\n\u251c\u2500\u2500 types.ts # Shared TypeScript types\n\u251c\u2500\u2500 sodax/\n\u2502 \u2514\u2500\u2500 client.ts # SODAX SDK singleton client\n\u251c\u2500\u2500 providers/\n\u2502 \u2514\u2500\u2500 spokeProviderFactory.ts # Evm/Sonic spoke provider factory\n\u251c\u2500\u2500 wallet/\n\u2502 \u2514\u2500\u2500 walletRegistry.ts # Wallet resolution by walletId\n\u251c\u2500\u2500 policy/\n\u2502 \u2514\u2500\u2500 policyEngine.ts # Security policy enforcement\n\u251c\u2500\u2500 tools/\n\u2502 \u251c\u2500\u2500 discovery.ts # Discovery/read tools\n\u2502 \u251c\u2500\u2500 swap.ts # Swap operations\n\u2502 \u251c\u2500\u2500 bridge.ts # Bridge operations\n\u2502 \u2514\u2500\u2500 moneyMarket.ts # Money market operations\n\u251c\u2500\u2500 utils/\n\u2502 \u251c\u2500\u2500 errors.ts # Error handling utilities\n\u2502 \u251c\u2500\u2500 positionAggregator.ts # Cross-chain position aggregation\n\u2502 \u2514\u2500\u2500 sodaxApi.ts # SODAX backend API client\n\u2514\u2500\u2500 __tests__/ # Test suite\n```\n\n## Architecture\n\n### Cross-Chain Money Market Flow\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Ethereum \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Sonic \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 Arbitrum \u2502\n\u2502 (Supply) \u2502 \u2502 (Hub) \u2502 \u2502 (Borrow) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502 \u2502\n \u2502 1. Supply USDC \u2502 \u2502\n \u2502 2. Record collateral\u2502 \u2502\n \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502 \u2502\n \u2502 \u2502 3. Verify collateral\n \u2502 \u2502 4. Process borrow \u2502\n \u2502 \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502\n \u2502 \u2502 \u2502 5. Deliver USDT\n```\n\n### Security Model\n\n- **Key Segregation** - Each agent workspace has distinct wallet configurations\n- **Spoke Provider Caching** - Cached per `walletId`, never shared across agents\n- **Policy Enforcement** - Spend limits, slippage caps, chain/token allowlists\n- **Simulation** - Transactions simulated before execution by default\n- **No Key Logging** - Private keys never logged or exposed\n\n## Supported Chains\n\n| Chain | Chain ID | Type |\n|-------|----------|------|\n| Ethereum | `ethereum` | EVM Spoke |\n| Arbitrum | `arbitrum` | EVM Spoke |\n| Base | `base` | EVM Spoke |\n| Optimism | `optimism` | EVM Spoke |\n| Polygon | `polygon` | EVM Spoke |\n| Avalanche | `avalanche` | EVM Spoke |\n| BSC | `bsc` | EVM Spoke |\n| LightLink | `lightlink` | EVM Spoke |\n| HyperEVM | `hyperevm` | EVM Spoke |\n| Kaia | `kaia` | EVM Spoke |\n| Sonic | `sonic` | Hub |\n| Solana | `solana` | Solana (receive only) |\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- **Documentation**: [https://docs.sodax.com](https://docs.sodax.com)\n- **Issues**: [GitHub Issues](https://github.com/amped-finance/amped-openclaw/issues)\n- **Discord**: [Amped Finance Community](https://discord.gg/amped)\n",
57 "arguments": null,
58 "icons": null,
59 "meta": null
60 }
61 ],
62 "resources": [
63 {
64 "name": "openclaw.plugin.json",
65 "title": null,
66 "uri": "skill://openclaw.plugin.json",
67 "description": "{\n \"id\": \"amped-defi\",\n \"kind\": \"tools\",\n \"uiHints\": {\n \"walletsJson\": {\n \"label\": \"Wallets JSON\",\n \"sensitive\": true,\n \"placeholder\": \"{\\\"default\\\": {\\\"privateKey\\\": \\\"0x...\\\"}}\",\n \"help\": \"JSON object mapping wallet IDs to configs. Or use ${AMPED_OC_WALLETS_JSON}\"\n },\n \"rpcUrlsJson\": {\n \"label\": \"RPC URLs JSON\",\n \"placeholder\": \"{\\\"sonic\\\": \\\"https://rpc.sonic.fantom.network\\\"}\",\n \"help\": \"JSON object mapping chain IDs to RPC URLs. Or use ${AMPED_OC_RPC_URLS_JSON}\"\n },\n \"mode\": {\n \"label\": \"Execution Mode\",\n \"help\": \"'execute' (default) = execute transactions, 'simulate' = dry-run only\"\n },\n \"dynamicConfig\": {\n \"label\": \"Dynamic SDK Config\",\n \"help\": \"Fetch fresh configuration from SODAX API on startup\",\n \"advanced\": true\n }\n },\n \"configSchema\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"walletsJson\": { \"type\": \"string\" },\n \"rpcUrlsJson\": { \"type\": \"string\" },\n \"mode\": { \"type\": \"string\", \"enum\": [\"execute\", \"simulate\"] },\n \"dynamicConfig\": { \"type\": \"boolean\" }\n },\n \"required\": []\n }\n}\n",
68 "mimeType": null,
69 "size": null,
70 "icons": null,
71 "annotations": null,
72 "meta": null
73 },
74 {
75 "name": "package.json",
76 "title": null,
77 "uri": "skill://package.json",
78 "description": "{\n \"name\": \"amped-defi\",\n \"version\": \"1.0.0\",\n \"description\": \"Amped DeFi plugin for cross-chain swaps, bridging, and money markets via SODAX SDK\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"src\",\n \"index.js\",\n \"README.md\",\n \"openclaw.plugin.json\"\n ],\n \"scripts\": {\n \"build\": \"tsc\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src --ext .ts\",\n \"test\": \"jest\",\n \"test:watch\": \"jest --watch\",\n \"test:coverage\": \"jest --coverage\"\n },\n \"openclaw\": {\n \"extensions\": [\n \"./index.js\"\n ]\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/amped-finance/amped-openclaw.git\"\n },\n \"homepage\": \"https://github.com/amped-finance/amped-openclaw#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/amped-finance/amped-openclaw/issues\"\n },\n \"author\": \"Amped Finance\",\n \"dependencies\": {\n \"@sinclair/typebox\": \"^0.32.0\",\n \"@sodax/sdk\": \"1.1.0-beta-rc2\",\n \"@sodax/wallet-sdk-core\": \"1.1.0-beta-rc2\",\n \"@sodax/types\": \"1.1.0-beta-rc2\",\n \"viem\": \"^2.0.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.3.0\",\n \"eslint\": \"^8.0.0\",\n \"@typescript-eslint/eslint-plugin\": \"^6.0.0\",\n \"@typescript-eslint/parser\": \"^6.0.0\",\n \"jest\": \"^29.0.0\",\n \"@types/jest\": \"^29.0.0\",\n \"ts-jest\": \"^29.0.0\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n },\n \"keywords\": [\n \"openclaw\",\n \"plugin\",\n \"defi\",\n \"sodax\",\n \"bridge\",\n \"swap\",\n \"money-market\",\n \"cross-chain\",\n \"intent\"\n ],\n \"license\": \"MIT\"\n}\n",
79 "mimeType": null,
80 "size": null,
81 "icons": null,
82 "annotations": null,
83 "meta": null
84 },
85 {
86 "name": "_meta.json",
87 "title": null,
88 "uri": "skill://_meta.json",
89 "description": "{\n \"owner\": \"ampedfinance\",\n \"slug\": \"amped-defi-publish\",\n \"displayName\": \"Amped Defi Publish\",\n \"latest\": {\n \"version\": \"1.0.0\",\n \"publishedAt\": 1770262924877,\n \"commit\": \"https://github.com/clawdbot/skills/commit/87437abcc55c98d55f40b83567fa5446b7392d10\"\n },\n \"history\": []\n}\n",
90 "mimeType": null,
91 "size": null,
92 "icons": null,
93 "annotations": null,
94 "meta": null
95 }
96 ],
97 "resource_templates": [],
98 "tools": [
99 {
100 "name": "sodaxApi.js",
101 "title": null,
102 "description": "Script: sodaxApi.js. Code:\n/**\n * SODAX API Client\n *\n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\nimport { ErrorCode, AmpedDefiError } from './errors';\nconst DEFAULT_BASE_URL = 'https://canary-api.sodax.com';\nconst API_VERSION = 'v1';\nexport class SodaxApiClient {\n baseUrl;\n apiKey;\n timeoutMs;\n constructor(config = {}) {\n this.baseUrl = config.baseUrl || process.env.SODAX_API_URL || DEFAULT_BASE_URL;\n this.apiKey = config.apiKey || process.env.SODAX_API_KEY;\n this.timeoutMs = config.timeoutMs || 30000;\n }\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n async getIntentByHash(intentHash) {\n const normalizedHash = intentHash.startsWith('0x') ? intentHash : `0x${intentHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/${normalizedHash}`;\n console.log('[sodaxApi] Fetching intent by hash:', { intentHash: normalizedHash });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch intent by hash: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n async getIntentByTxHash(txHash) {\n const normalizedHash = txHash.startsWith('0x') ? txHash : `0x${txHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/tx/${normalizedHash}`;\n console.log('[sodaxApi] Fetching intent by txHash:', { txHash: normalizedHash });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch intent by txHash: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n async getUserIntents(userAddress, pagination = {}, filters) {\n if (!this.isValidAddress(userAddress)) {\n throw new AmpedDefiError(ErrorCode.WALLET_INVALID_ADDRESS, `Invalid user address: ${userAddress}`);\n }\n const normalizedAddress = userAddress.toLowerCase();\n const queryParams = new URLSearchParams();\n if (pagination.offset !== undefined) {\n queryParams.set('offset', pagination.offset.toString());\n }\n if (pagination.limit !== undefined) {\n queryParams.set('limit', pagination.limit.toString());\n }\n if (filters) {\n if (filters.open !== undefined)\n queryParams.set('open', filters.open.toString());\n if (filters.srcChain !== undefined)\n queryParams.set('srcChain', filters.srcChain.toString());\n if (filters.dstChain !== undefined)\n queryParams.set('dstChain', filters.dstChain.toString());\n if (filters.inputToken)\n queryParams.set('inputToken', filters.inputToken.toLowerCase());\n if (filters.outputToken)\n queryParams.set('outputToken', filters.outputToken.toLowerCase());\n }\n const queryString = queryParams.toString();\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/user/${normalizedAddress}${queryString ? `?${queryString}` : ''}`;\n console.log('[sodaxApi] Fetching user intents:', { userAddress: normalizedAddress });\n try {\n const response = await this.fetchWithTimeout(url);\n if (!response.ok) {\n const errorText = await response.text();\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `SODAX API error: ${response.status} ${errorText}`);\n }\n return await response.json();\n }\n catch (error) {\n if (error instanceof AmpedDefiError)\n throw error;\n throw new AmpedDefiError(ErrorCode.UNKNOWN_ERROR, `Failed to fetch user intents: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n async getOpenIntents(userAddress, pagination = {}) {\n return this.getUserIntents(userAddress, pagination, { open: true });\n }\n async getIntentHistory(userAddress, pagination = {}) {\n return this.getUserIntents(userAddress, pagination, { open: false });\n }\n async fetchWithTimeout(url) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const headers = { 'Accept': 'application/json' };\n if (this.apiKey)\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n return await fetch(url, { signal: controller.signal, headers });\n }\n finally {\n clearTimeout(timeoutId);\n }\n }\n isValidAddress(address) {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n }\n}\nlet apiClient = null;\nexport function getSodaxApiClient(config) {\n if (!apiClient) {\n apiClient = new SodaxApiClient(config);\n }\n return apiClient;\n}\nexport function resetSodaxApiClient() {\n apiClient = null;\n}\n//# sourceMappingURL=sodaxApi.js.map",
103 "inputSchema": {},
104 "outputSchema": null,
105 "icons": null,
106 "annotations": null,
107 "meta": null,
108 "execution": null
109 },
110 {
111 "name": "positionAggregator.d.ts",
112 "title": null,
113 "description": "Script: positionAggregator.d.ts. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n *\n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\n/**\n * Position data for a single token on a single chain\n */\nexport interface TokenPosition {\n chainId: string;\n token: {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n };\n supply: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n isCollateral: boolean;\n };\n borrow: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n };\n loanToValue: number;\n liquidationThreshold: number;\n}\n/**\n * Aggregated position summary across all chains\n */\nexport interface AggregatedPositionSummary {\n totalSupplyUsd: number;\n totalBorrowUsd: number;\n netWorthUsd: number;\n availableBorrowUsd: number;\n healthFactor: number | null;\n liquidationRisk: 'none' | 'low' | 'medium' | 'high';\n weightedSupplyApy: number;\n weightedBorrowApy: number;\n netApy: number;\n}\n/**\n * Chain-specific position summary\n */\nexport interface ChainPositionSummary {\n chainId: string;\n supplyUsd: number;\n borrowUsd: number;\n netWorthUsd: number;\n healthFactor: number | null;\n positionCount: number;\n}\n/**\n * Complete cross-chain position view\n */\nexport interface CrossChainPositionView {\n walletId: string;\n address: string;\n timestamp: string;\n summary: AggregatedPositionSummary;\n chainSummaries: ChainPositionSummary[];\n positions: TokenPosition[];\n collateralUtilization: {\n totalCollateralUsd: number;\n usedCollateralUsd: number;\n availableCollateralUsd: number;\n utilizationRate: number;\n };\n riskMetrics: {\n maxLtv: number;\n currentLtv: number;\n bufferUntilLiquidation: number;\n safeMaxBorrowUsd: number;\n };\n}\n/**\n * Options for position aggregation\n */\nexport interface AggregationOptions {\n /** Specific chains to query (defaults to all supported chains) */\n chainIds?: string[];\n /** Include zero-balance positions */\n includeZeroBalances?: boolean;\n /** Minimum USD value to include (positions below this are filtered out unless includeZeroBalances is true) */\n minUsdValue?: number;\n}\n/**\n * Aggregate money market positions across all supported chains\n *\n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport declare function aggregateCrossChainPositions(walletId: string, options?: AggregationOptions): Promise<CrossChainPositionView>;\n/**\n * Format health factor for display\n */\nexport declare function formatHealthFactor(hf: number | null): string;\n/**\n * Get health factor color/styling indicator\n */\nexport declare function getHealthFactorStatus(hf: number | null): {\n status: 'healthy' | 'caution' | 'danger' | 'critical';\n color: 'green' | 'yellow' | 'orange' | 'red';\n};\n/**\n * Get recommendation based on position health\n */\nexport declare function getPositionRecommendation(view: CrossChainPositionView): string[];\n//# sourceMappingURL=positionAggregator.d.ts.map",
114 "inputSchema": {},
115 "outputSchema": null,
116 "icons": null,
117 "annotations": null,
118 "meta": null,
119 "execution": null
120 },
121 {
122 "name": "errors.d.ts",
123 "title": null,
124 "description": "Script: errors.d.ts. Code:\n/**\n * Error Handling Utilities\n *\n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport declare enum ErrorCode {\n POLICY_SLIPPAGE_EXCEEDED = \"POLICY_SLIPPAGE_EXCEEDED\",\n POLICY_SPEND_LIMIT_EXCEEDED = \"POLICY_SPEND_LIMIT_EXCEEDED\",\n POLICY_CHAIN_NOT_ALLOWED = \"POLICY_CHAIN_NOT_ALLOWED\",\n POLICY_TOKEN_NOT_ALLOWED = \"POLICY_TOKEN_NOT_ALLOWED\",\n POLICY_RECIPIENT_BLOCKED = \"POLICY_RECIPIENT_BLOCKED\",\n WALLET_NOT_FOUND = \"WALLET_NOT_FOUND\",\n WALLET_INVALID_ADDRESS = \"WALLET_INVALID_ADDRESS\",\n WALLET_MISSING_PRIVATE_KEY = \"WALLET_MISSING_PRIVATE_KEY\",\n WALLET_RESOLUTION_FAILED = \"WALLET_RESOLUTION_FAILED\",\n CHAIN_NOT_SUPPORTED = \"CHAIN_NOT_SUPPORTED\",\n RPC_URL_NOT_CONFIGURED = \"RPC_URL_NOT_CONFIGURED\",\n PROVIDER_CREATION_FAILED = \"PROVIDER_CREATION_FAILED\",\n SONIC_PROVIDER_REQUIRED = \"SONIC_PROVIDER_REQUIRED\",\n INSUFFICIENT_BALANCE = \"INSUFFICIENT_BALANCE\",\n INSUFFICIENT_ALLOWANCE = \"INSUFFICIENT_ALLOWANCE\",\n TOKEN_NOT_SUPPORTED = \"TOKEN_NOT_SUPPORTED\",\n TOKEN_DECIMALS_NOT_FOUND = \"TOKEN_DECIMALS_NOT_FOUND\",\n QUOTE_EXPIRED = \"QUOTE_EXPIRED\",\n QUOTE_NOT_FOUND = \"QUOTE_NOT_FOUND\",\n BRIDGE_NOT_AVAILABLE = \"BRIDGE_NOT_AVAILABLE\",\n SWAP_EXECUTION_FAILED = \"SWAP_EXECUTION_FAILED\",\n BRIDGE_EXECUTION_FAILED = \"BRIDGE_EXECUTION_FAILED\",\n MM_HEALTH_FACTOR_LOW = \"MM_HEALTH_FACTOR_LOW\",\n MM_CROSS_CHAIN_NOT_SUPPORTED = \"MM_CROSS_CHAIN_NOT_SUPPORTED\",\n MM_INSUFFICIENT_COLLATERAL = \"MM_INSUFFICIENT_COLLATERAL\",\n MM_POSITION_NOT_FOUND = \"MM_POSITION_NOT_FOUND\",\n TRANSACTION_FAILED = \"TRANSACTION_FAILED\",\n TRANSACTION_TIMEOUT = \"TRANSACTION_TIMEOUT\",\n TRANSACTION_REJECTED = \"TRANSACTION_REJECTED\",\n TRANSACTION_SIMULATION_FAILED = \"TRANSACTION_SIMULATION_FAILED\",\n SDK_NOT_INITIALIZED = \"SDK_NOT_INITIALIZED\",\n SDK_INITIALIZATION_FAILED = \"SDK_INITIALIZATION_FAILED\",\n INVALID_CONFIGURATION = \"INVALID_CONFIGURATION\",\n CONFIG_PARSE_ERROR = \"CONFIG_PARSE_ERROR\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n OPERATION_CANCELLED = \"OPERATION_CANCELLED\"\n}\n/**\n * Error severity levels\n */\nexport declare enum ErrorSeverity {\n INFO = \"info\",\n WARNING = \"warning\",\n ERROR = \"error\",\n CRITICAL = \"critical\"\n}\n/**\n * Structured error information\n */\nexport interface AmpedError {\n code: ErrorCode;\n message: string;\n severity: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n}\n/**\n * Error context for logging\n */\nexport interface ErrorContext {\n operation?: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokens?: string[];\n amount?: string;\n requestId?: string;\n txHash?: string;\n [key: string]: unknown;\n}\n/**\n * Amped DeFi Error class\n */\nexport declare class AmpedDefiError extends Error {\n readonly code: ErrorCode;\n readonly severity: ErrorSeverity;\n readonly remediation?: string;\n readonly details?: Record<string, unknown>;\n readonly context?: ErrorContext;\n constructor(code: ErrorCode, message: string, options?: {\n severity?: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n context?: ErrorContext;\n cause?: Error;\n });\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): AmpedError;\n /**\n * Get user-friendly error message\n */\n toUserMessage(): string;\n}\n/**\n * Create a policy error\n */\nexport declare function createPolicyError(code: ErrorCode, message: string, details?: {\n current?: unknown;\n limit?: unknown;\n [key: string]: unknown;\n}, context?: ErrorContext): AmpedDefiError;\n/**\n * Create a wallet error\n */\nexport declare function createWalletError(code: ErrorCode, walletId: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Create a transaction error\n */\nexport declare function createTransactionError(code: ErrorCode, message: string, txHash?: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Create an SDK error\n */\nexport declare function createSDKError(code: ErrorCode, message: string, cause?: Error, context?: ErrorContext): AmpedDefiError;\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport declare function wrapError(error: unknown, fallbackCode?: ErrorCode, context?: ErrorContext): AmpedDefiError;\n/**\n * Log an error with structured context\n */\nexport declare function logError(error: AmpedDefiError | Error, context?: ErrorContext): void;\n/**\n * Check if an error is retryable\n */\nexport declare function isRetryableError(error: AmpedDefiError | Error): boolean;\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport declare function getRetryDelay(attempt: number, baseDelay?: number): number;\n//# sourceMappingURL=errors.d.ts.map",
125 "inputSchema": {},
126 "outputSchema": null,
127 "icons": null,
128 "annotations": null,
129 "meta": null,
130 "execution": null
131 },
132 {
133 "name": "errorUtils.js",
134 "title": null,
135 "description": "Script: errorUtils.js. Code:\n/**\n * Serialize SDK error objects for readable error messages\n */\nfunction bigintReplacer(key, value) {\n return typeof value === 'bigint' ? value.toString() : value;\n}\nexport function serializeError(error) {\n if (error instanceof Error)\n return error.message;\n if (typeof error === 'string')\n return error;\n try {\n return JSON.stringify(error, bigintReplacer, 2);\n }\n catch {\n return String(error);\n }\n}\n//# sourceMappingURL=errorUtils.js.map",
136 "inputSchema": {},
137 "outputSchema": null,
138 "icons": null,
139 "annotations": null,
140 "meta": null,
141 "execution": null
142 },
143 {
144 "name": "errors.js",
145 "title": null,
146 "description": "Script: errors.js. Code:\n/**\n * Error Handling Utilities\n *\n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport var ErrorCode;\n(function (ErrorCode) {\n // Policy errors\n ErrorCode[\"POLICY_SLIPPAGE_EXCEEDED\"] = \"POLICY_SLIPPAGE_EXCEEDED\";\n ErrorCode[\"POLICY_SPEND_LIMIT_EXCEEDED\"] = \"POLICY_SPEND_LIMIT_EXCEEDED\";\n ErrorCode[\"POLICY_CHAIN_NOT_ALLOWED\"] = \"POLICY_CHAIN_NOT_ALLOWED\";\n ErrorCode[\"POLICY_TOKEN_NOT_ALLOWED\"] = \"POLICY_TOKEN_NOT_ALLOWED\";\n ErrorCode[\"POLICY_RECIPIENT_BLOCKED\"] = \"POLICY_RECIPIENT_BLOCKED\";\n // Wallet errors\n ErrorCode[\"WALLET_NOT_FOUND\"] = \"WALLET_NOT_FOUND\";\n ErrorCode[\"WALLET_INVALID_ADDRESS\"] = \"WALLET_INVALID_ADDRESS\";\n ErrorCode[\"WALLET_MISSING_PRIVATE_KEY\"] = \"WALLET_MISSING_PRIVATE_KEY\";\n ErrorCode[\"WALLET_RESOLUTION_FAILED\"] = \"WALLET_RESOLUTION_FAILED\";\n // Chain/Provider errors\n ErrorCode[\"CHAIN_NOT_SUPPORTED\"] = \"CHAIN_NOT_SUPPORTED\";\n ErrorCode[\"RPC_URL_NOT_CONFIGURED\"] = \"RPC_URL_NOT_CONFIGURED\";\n ErrorCode[\"PROVIDER_CREATION_FAILED\"] = \"PROVIDER_CREATION_FAILED\";\n ErrorCode[\"SONIC_PROVIDER_REQUIRED\"] = \"SONIC_PROVIDER_REQUIRED\";\n // Token errors\n ErrorCode[\"INSUFFICIENT_BALANCE\"] = \"INSUFFICIENT_BALANCE\";\n ErrorCode[\"INSUFFICIENT_ALLOWANCE\"] = \"INSUFFICIENT_ALLOWANCE\";\n ErrorCode[\"TOKEN_NOT_SUPPORTED\"] = \"TOKEN_NOT_SUPPORTED\";\n ErrorCode[\"TOKEN_DECIMALS_NOT_FOUND\"] = \"TOKEN_DECIMALS_NOT_FOUND\";\n // Operation errors\n ErrorCode[\"QUOTE_EXPIRED\"] = \"QUOTE_EXPIRED\";\n ErrorCode[\"QUOTE_NOT_FOUND\"] = \"QUOTE_NOT_FOUND\";\n ErrorCode[\"BRIDGE_NOT_AVAILABLE\"] = \"BRIDGE_NOT_AVAILABLE\";\n ErrorCode[\"SWAP_EXECUTION_FAILED\"] = \"SWAP_EXECUTION_FAILED\";\n ErrorCode[\"BRIDGE_EXECUTION_FAILED\"] = \"BRIDGE_EXECUTION_FAILED\";\n ErrorCode[\"MM_HEALTH_FACTOR_LOW\"] = \"MM_HEALTH_FACTOR_LOW\";\n ErrorCode[\"MM_CROSS_CHAIN_NOT_SUPPORTED\"] = \"MM_CROSS_CHAIN_NOT_SUPPORTED\";\n ErrorCode[\"MM_INSUFFICIENT_COLLATERAL\"] = \"MM_INSUFFICIENT_COLLATERAL\";\n ErrorCode[\"MM_POSITION_NOT_FOUND\"] = \"MM_POSITION_NOT_FOUND\";\n // Transaction errors\n ErrorCode[\"TRANSACTION_FAILED\"] = \"TRANSACTION_FAILED\";\n ErrorCode[\"TRANSACTION_TIMEOUT\"] = \"TRANSACTION_TIMEOUT\";\n ErrorCode[\"TRANSACTION_REJECTED\"] = \"TRANSACTION_REJECTED\";\n ErrorCode[\"TRANSACTION_SIMULATION_FAILED\"] = \"TRANSACTION_SIMULATION_FAILED\";\n // SDK/Configuration errors\n ErrorCode[\"SDK_NOT_INITIALIZED\"] = \"SDK_NOT_INITIALIZED\";\n ErrorCode[\"SDK_INITIALIZATION_FAILED\"] = \"SDK_INITIALIZATION_FAILED\";\n ErrorCode[\"INVALID_CONFIGURATION\"] = \"INVALID_CONFIGURATION\";\n ErrorCode[\"CONFIG_PARSE_ERROR\"] = \"CONFIG_PARSE_ERROR\";\n // Unknown/Generic\n ErrorCode[\"UNKNOWN_ERROR\"] = \"UNKNOWN_ERROR\";\n ErrorCode[\"OPERATION_CANCELLED\"] = \"OPERATION_CANCELLED\";\n})(ErrorCode || (ErrorCode = {}));\n/**\n * Error severity levels\n */\nexport var ErrorSeverity;\n(function (ErrorSeverity) {\n ErrorSeverity[\"INFO\"] = \"info\";\n ErrorSeverity[\"WARNING\"] = \"warning\";\n ErrorSeverity[\"ERROR\"] = \"error\";\n ErrorSeverity[\"CRITICAL\"] = \"critical\";\n})(ErrorSeverity || (ErrorSeverity = {}));\n/**\n * Amped DeFi Error class\n */\nexport class AmpedDefiError extends Error {\n code;\n severity;\n remediation;\n details;\n context;\n constructor(code, message, options) {\n super(message, { cause: options?.cause });\n this.name = 'AmpedDefiError';\n this.code = code;\n this.severity = options?.severity || ErrorSeverity.ERROR;\n this.remediation = options?.remediation;\n this.details = options?.details;\n this.context = options?.context;\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, AmpedDefiError);\n }\n }\n /**\n * Convert to JSON-serializable object\n */\n toJSON() {\n return {\n code: this.code,\n message: this.message,\n severity: this.severity,\n remediation: this.remediation,\n details: this.details,\n };\n }\n /**\n * Get user-friendly error message\n */\n toUserMessage() {\n let msg = `[${this.code}] ${this.message}`;\n if (this.remediation) {\n msg += `\\n\\nSuggestion: ${this.remediation}`;\n }\n return msg;\n }\n}\n// ============================================================================\n// Error Factory Functions\n// ============================================================================\n/**\n * Create a policy error\n */\nexport function createPolicyError(code, message, details, context) {\n const remediation = getPolicyRemediation(code, details);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.WARNING,\n remediation,\n details,\n context,\n });\n}\n/**\n * Create a wallet error\n */\nexport function createWalletError(code, walletId, cause, context) {\n const message = getWalletErrorMessage(code, walletId);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getWalletRemediation(code),\n context: { ...context, walletId },\n cause,\n });\n}\n/**\n * Create a transaction error\n */\nexport function createTransactionError(code, message, txHash, cause, context) {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getTransactionRemediation(code),\n details: txHash ? { txHash } : undefined,\n context: txHash ? { ...context, txHash } : context,\n cause,\n });\n}\n/**\n * Create an SDK error\n */\nexport function createSDKError(code, message, cause, context) {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.CRITICAL,\n remediation: 'Please check your configuration and try again. If the issue persists, contact support.',\n context,\n cause,\n });\n}\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport function wrapError(error, fallbackCode = ErrorCode.UNKNOWN_ERROR, context) {\n if (error instanceof AmpedDefiError) {\n return error;\n }\n if (error instanceof Error) {\n // Try to infer error code from message\n const code = inferErrorCode(error.message) || fallbackCode;\n return new AmpedDefiError(code, error.message, {\n severity: ErrorSeverity.ERROR,\n context,\n cause: error,\n });\n }\n return new AmpedDefiError(fallbackCode, String(error), {\n severity: ErrorSeverity.ERROR,\n context,\n });\n}\n// ============================================================================\n// Remediation Helpers\n// ============================================================================\nfunction getPolicyRemediation(code, details) {\n switch (code) {\n case ErrorCode.POLICY_SLIPPAGE_EXCEEDED:\n return `Slippage ${details?.current} bps exceeds limit of ${details?.limit} bps. Increase maxSlippageBps in your policy configuration or wait for better market conditions.`;\n case ErrorCode.POLICY_SPEND_LIMIT_EXCEEDED:\n return `Reduce the operation amount or request a policy limit increase. Current limit: ${details?.limit}`;\n case ErrorCode.POLICY_CHAIN_NOT_ALLOWED:\n return `Add the chain to your allowedChains policy configuration or use a different chain.`;\n case ErrorCode.POLICY_TOKEN_NOT_ALLOWED:\n return `Add the token to your allowedTokensByChain policy configuration or use a different token.`;\n case ErrorCode.POLICY_RECIPIENT_BLOCKED:\n return `Use a different recipient address. This address has been blocked by policy.`;\n default:\n return 'Review your policy configuration or contact your administrator.';\n }\n}\nfunction getWalletRemediation(code) {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return 'Check your AMPED_OC_WALLETS_JSON configuration and ensure the walletId is correct.';\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return 'Verify the wallet address format (should be 0x-prefixed Ethereum address).';\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return 'Add the private key to your wallet configuration for execute mode, or switch to prepare mode.';\n default:\n return 'Check your wallet configuration and try again.';\n }\n}\nfunction getTransactionRemediation(code) {\n switch (code) {\n case ErrorCode.TRANSACTION_FAILED:\n return 'Check the transaction on a block explorer for revert reasons. You may need to adjust parameters or try again later.';\n case ErrorCode.TRANSACTION_TIMEOUT:\n return 'The operation timed out. You can check the status later using the transaction hash.';\n case ErrorCode.TRANSACTION_REJECTED:\n return 'The transaction was rejected. This may be due to network congestion or insufficient gas.';\n case ErrorCode.TRANSACTION_SIMULATION_FAILED:\n return 'The transaction would fail if executed. Check your balances, allowances, and parameters.';\n default:\n return 'Try again or contact support if the issue persists.';\n }\n}\nfunction getWalletErrorMessage(code, walletId) {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return `Wallet not found: ${walletId}`;\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return `Wallet ${walletId} has an invalid address`;\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return `Wallet ${walletId} is missing private key (required in execute mode)`;\n default:\n return `Wallet error for ${walletId}`;\n }\n}\nfunction inferErrorCode(message) {\n const lowerMsg = message.toLowerCase();\n if (lowerMsg.includes('insufficient balance'))\n return ErrorCode.INSUFFICIENT_BALANCE;\n if (lowerMsg.includes('allowance'))\n return ErrorCode.INSUFFICIENT_ALLOWANCE;\n if (lowerMsg.includes('slippage'))\n return ErrorCode.POLICY_SLIPPAGE_EXCEEDED;\n if (lowerMsg.includes('health factor'))\n return ErrorCode.MM_HEALTH_FACTOR_LOW;\n if (lowerMsg.includes('timeout'))\n return ErrorCode.TRANSACTION_TIMEOUT;\n if (lowerMsg.includes('rejected'))\n return ErrorCode.TRANSACTION_REJECTED;\n if (lowerMsg.includes('simulation'))\n return ErrorCode.TRANSACTION_SIMULATION_FAILED;\n if (lowerMsg.includes('not initialized'))\n return ErrorCode.SDK_NOT_INITIALIZED;\n if (lowerMsg.includes('bridge') && lowerMsg.includes('not'))\n return ErrorCode.BRIDGE_NOT_AVAILABLE;\n if (lowerMsg.includes('quote') && lowerMsg.includes('expir'))\n return ErrorCode.QUOTE_EXPIRED;\n return null;\n}\n// ============================================================================\n// Logging and Observability\n// ============================================================================\n/**\n * Log an error with structured context\n */\nexport function logError(error, context) {\n const structuredLog = {\n timestamp: new Date().toISOString(),\n component: 'amped-defi',\n level: error instanceof AmpedDefiError ? error.severity : 'error',\n code: error instanceof AmpedDefiError ? error.code : ErrorCode.UNKNOWN_ERROR,\n message: error.message,\n context,\n stack: error.stack,\n cause: error.cause,\n };\n // Log as JSON for structured logging systems\n console.error(JSON.stringify(structuredLog, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n/**\n * Check if an error is retryable\n */\nexport function isRetryableError(error) {\n if (error instanceof AmpedDefiError) {\n const retryableCodes = [\n ErrorCode.TRANSACTION_TIMEOUT,\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n ErrorCode.SDK_NOT_INITIALIZED,\n ErrorCode.UNKNOWN_ERROR,\n ];\n return retryableCodes.includes(error.code);\n }\n // For generic errors, check message patterns\n const lowerMsg = error.message.toLowerCase();\n return lowerMsg.includes('timeout') ||\n lowerMsg.includes('network') ||\n lowerMsg.includes('connection') ||\n lowerMsg.includes('rate limit');\n}\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport function getRetryDelay(attempt, baseDelay = 1000) {\n return Math.min(baseDelay * Math.pow(2, attempt), 30000); // Cap at 30 seconds\n}\n//# sourceMappingURL=errors.js.map",
147 "inputSchema": {},
148 "outputSchema": null,
149 "icons": null,
150 "annotations": null,
151 "meta": null,
152 "execution": null
153 },
154 {
155 "name": "errorUtils.d.ts",
156 "title": null,
157 "description": "Script: errorUtils.d.ts. Code:\nexport declare function serializeError(error: unknown): string;\n//# sourceMappingURL=errorUtils.d.ts.map",
158 "inputSchema": {},
159 "outputSchema": null,
160 "icons": null,
161 "annotations": null,
162 "meta": null,
163 "execution": null
164 },
165 {
166 "name": "sodaxApi.d.ts",
167 "title": null,
168 "description": "Script: sodaxApi.d.ts. Code:\n/**\n * SODAX API Client\n *\n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\nexport interface SodaxApiConfig {\n baseUrl?: string;\n apiKey?: string;\n timeoutMs?: number;\n}\nexport interface PaginationParams {\n offset?: number;\n limit?: number;\n}\nexport interface PaginatedResponse<T> {\n items: T[];\n total: number;\n offset: number;\n limit: number;\n}\nexport interface IntentState {\n exists: boolean;\n remainingInput: string;\n receivedOutput: string;\n pendingPayment: boolean;\n}\nexport interface IntentEvent {\n eventType: string;\n txHash: string;\n logIndex: number;\n blockNumber: number;\n intentState: IntentState;\n}\nexport interface IntentDetails {\n intentId: string;\n creator: string;\n inputToken: string;\n outputToken: string;\n inputAmount: string;\n minOutputAmount: string;\n deadline: string;\n allowPartialFill: boolean;\n srcChain: number;\n dstChain: number;\n srcAddress: string;\n dstAddress: string;\n solver: string;\n data: string;\n}\nexport interface UserIntent {\n intentHash: string;\n txHash: string;\n logIndex: number;\n chainId: number;\n blockNumber: number;\n open: boolean;\n intent: IntentDetails;\n events: IntentEvent[];\n createdAt: string;\n}\nexport interface UserIntentFilters {\n open?: boolean;\n srcChain?: number;\n dstChain?: number;\n inputToken?: string;\n outputToken?: string;\n}\nexport declare class SodaxApiClient {\n private baseUrl;\n private apiKey?;\n private timeoutMs;\n constructor(config?: SodaxApiConfig);\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n getIntentByHash(intentHash: string): Promise<UserIntent | null>;\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n getIntentByTxHash(txHash: string): Promise<UserIntent | null>;\n getUserIntents(userAddress: string, pagination?: PaginationParams, filters?: UserIntentFilters): Promise<PaginatedResponse<UserIntent>>;\n getOpenIntents(userAddress: string, pagination?: PaginationParams): Promise<PaginatedResponse<UserIntent>>;\n getIntentHistory(userAddress: string, pagination?: PaginationParams): Promise<PaginatedResponse<UserIntent>>;\n private fetchWithTimeout;\n private isValidAddress;\n}\nexport declare function getSodaxApiClient(config?: SodaxApiConfig): SodaxApiClient;\nexport declare function resetSodaxApiClient(): void;\n//# sourceMappingURL=sodaxApi.d.ts.map",
169 "inputSchema": {},
170 "outputSchema": null,
171 "icons": null,
172 "annotations": null,
173 "meta": null,
174 "execution": null
175 },
176 {
177 "name": "tokenResolver.d.ts",
178 "title": null,
179 "description": "Script: tokenResolver.d.ts. Code:\n/**\n * Token Resolution Utility\n *\n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\nimport type { Token } from '@sodax/types';\ndeclare function isSolanaChain(chainId: string): boolean;\n/**\n * Check if a string is a valid EVM address (0x format)\n */\ndeclare function isEvmAddress(value: string): boolean;\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\ndeclare function isSolanaAddress(value: string): boolean;\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\ndeclare function isValidTokenAddress(value: string, chainId?: string): boolean;\n/**\n * Resolve a token symbol or address to a normalized address\n *\n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport declare function resolveToken(chainId: string, tokenInput: string): Promise<string>;\n/**\n * Resolve multiple tokens at once (for efficiency)\n *\n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport declare function resolveTokens(chainId: string, tokenInputs: string[]): Promise<string[]>;\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport declare function getTokenInfo(chainId: string, tokenInput: string): Promise<Token | null>;\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport declare function clearTokenCache(): void;\n/**\n * Get all cached tokens for a chain\n */\nexport declare function getCachedTokens(chainId: string): Token[] | undefined;\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n//# sourceMappingURL=tokenResolver.d.ts.map",
180 "inputSchema": {},
181 "outputSchema": null,
182 "icons": null,
183 "annotations": null,
184 "meta": null,
185 "execution": null
186 },
187 {
188 "name": "priceService.d.ts",
189 "title": null,
190 "description": "Script: priceService.d.ts. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\nexport interface TokenPrice {\n symbol: string;\n priceUsd: number;\n underlyingAsset: string;\n}\nexport interface PriceMap {\n /** Map of symbol (lowercase) to USD price */\n bySymbol: Map<string, number>;\n /** Map of address (lowercase) to USD price */\n byAddress: Map<string, number>;\n /** Last update timestamp */\n timestamp: number;\n}\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport declare function fetchTokenPrices(): Promise<PriceMap>;\n/**\n * Get USD price for a token by symbol\n */\nexport declare function getTokenPriceBySymbol(symbol: string): Promise<number | null>;\n/**\n * Get USD price for a token by address\n */\nexport declare function getTokenPriceByAddress(address: string): Promise<number | null>;\n/**\n * Calculate USD value for a token amount\n */\nexport declare function calculateUsdValue(symbol: string, amount: string | number): Promise<number | null>;\n/**\n * Clear the price cache (useful for testing)\n */\nexport declare function clearPriceCache(): void;\n//# sourceMappingURL=priceService.d.ts.map",
191 "inputSchema": {},
192 "outputSchema": null,
193 "icons": null,
194 "annotations": null,
195 "meta": null,
196 "execution": null
197 },
198 {
199 "name": "priceService.js",
200 "title": null,
201 "description": "Script: priceService.js. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\nimport { getSodaxClient } from '../sodax/client';\n// ============================================================================\n// Cache\n// ============================================================================\nlet cachedPrices = null;\nconst CACHE_TTL_MS = 60_000; // 1 minute cache\n// ============================================================================\n// Price Fetching\n// ============================================================================\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport async function fetchTokenPrices() {\n // Return cached if fresh\n if (cachedPrices && Date.now() - cachedPrices.timestamp < CACHE_TTL_MS) {\n return cachedPrices;\n }\n console.log('[priceService] Fetching token prices from SODAX reserves');\n const sodax = await getSodaxClient();\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n const bySymbol = new Map();\n const byAddress = new Map();\n // Market reference currency decimals (typically 8)\n const PRICE_DECIMALS = 8;\n for (const reserve of reserves.reservesData) {\n // priceInMarketReferenceCurrency is a string representing the raw value\n const priceRaw = BigInt(reserve.priceInMarketReferenceCurrency);\n const priceUsd = Number(priceRaw) / Math.pow(10, PRICE_DECIMALS);\n // Use symbol for matching (e.g., \"sodaUSDC\" -> \"USDC\")\n const symbol = reserve.symbol.toLowerCase();\n const normalizedSymbol = normalizeSymbol(reserve.symbol);\n const address = reserve.underlyingAsset.toLowerCase();\n bySymbol.set(symbol, priceUsd);\n bySymbol.set(normalizedSymbol, priceUsd);\n byAddress.set(address, priceUsd);\n console.log(`[priceService] ${reserve.symbol}: $${priceUsd.toFixed(4)}`);\n }\n cachedPrices = {\n bySymbol,\n byAddress,\n timestamp: Date.now(),\n };\n console.log(`[priceService] Cached ${bySymbol.size} token prices`);\n return cachedPrices;\n}\n/**\n * Normalize SODAX symbol to standard symbol\n * e.g., \"sodaUSDC\" -> \"usdc\", \"sodaETH\" -> \"eth\"\n */\nfunction normalizeSymbol(symbol) {\n const lower = symbol.toLowerCase();\n if (lower.startsWith('soda')) {\n return lower.slice(4); // Remove 'soda' prefix\n }\n return lower;\n}\n/**\n * Get USD price for a token by symbol\n */\nexport async function getTokenPriceBySymbol(symbol) {\n const prices = await fetchTokenPrices();\n const normalizedSymbol = symbol.toLowerCase();\n // Try exact match first\n if (prices.bySymbol.has(normalizedSymbol)) {\n return prices.bySymbol.get(normalizedSymbol);\n }\n // Try with 'soda' prefix\n const sodaSymbol = 'soda' + normalizedSymbol;\n if (prices.bySymbol.has(sodaSymbol)) {\n return prices.bySymbol.get(sodaSymbol);\n }\n return null;\n}\n/**\n * Get USD price for a token by address\n */\nexport async function getTokenPriceByAddress(address) {\n const prices = await fetchTokenPrices();\n return prices.byAddress.get(address.toLowerCase()) ?? null;\n}\n/**\n * Calculate USD value for a token amount\n */\nexport async function calculateUsdValue(symbol, amount) {\n const price = await getTokenPriceBySymbol(symbol);\n if (price === null)\n return null;\n const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;\n return amountNum * price;\n}\n/**\n * Clear the price cache (useful for testing)\n */\nexport function clearPriceCache() {\n cachedPrices = null;\n}\n//# sourceMappingURL=priceService.js.map",
202 "inputSchema": {},
203 "outputSchema": null,
204 "icons": null,
205 "annotations": null,
206 "meta": null,
207 "execution": null
208 },
209 {
210 "name": "tokenResolver.js",
211 "title": null,
212 "description": "Script: tokenResolver.js. Code:\n/**\n * Token Resolution Utility\n *\n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\n// Cache tokens per chain to avoid repeated lookups\nconst tokenCache = new Map();\n// Native token addresses\nconst EVM_NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';\nconst SOLANA_NATIVE_ADDRESS = '11111111111111111111111111111111';\n// Native token configs per chain (18 decimals for all EVM chains, 9 for Solana)\nconst NATIVE_TOKENS = {\n sonic: { symbol: 'S', name: 'Sonic', decimals: 18, address: EVM_NATIVE_ADDRESS },\n ethereum: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa4b1.arbitrum': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x2105.base': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa.optimism': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x38.bsc': { symbol: 'BNB', name: 'BNB', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x89.polygon': { symbol: 'POL', name: 'POL', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa86a.avax': { symbol: 'AVAX', name: 'Avalanche', decimals: 18, address: EVM_NATIVE_ADDRESS },\n hyper: { symbol: 'HYPE', name: 'Hyperliquid', decimals: 18, address: EVM_NATIVE_ADDRESS },\n lightlink: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n solana: { symbol: 'SOL', name: 'Solana', decimals: 9, address: SOLANA_NATIVE_ADDRESS },\n};\n// Fallback token list for common chains when SDK config is unavailable\nconst FALLBACK_TOKENS = {\n '0x2105.base': [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'ethereum': [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', symbol: 'DAI', name: 'Dai Stablecoin', decimals: 18 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n '0xa4b1.arbitrum': [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'sonic': [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x6047828dc181963ba44974801FF68e538dA5eaF9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'S', name: 'Sonic', decimals: 18 },\n ],\n 'solana': [\n { address: SOLANA_NATIVE_ADDRESS, symbol: 'SOL', name: 'Solana', decimals: 9 },\n { address: '3rSPCLNEF7Quw4wX8S1NyKivELoyij8eYA2gJwBgt4V5', symbol: 'bnUSD', name: 'bnUSD', decimals: 9 },\n { address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n ],\n};\n// Chain type detection\nconst SOLANA_CHAINS = new Set(['solana']);\nfunction isSolanaChain(chainId) {\n return SOLANA_CHAINS.has(chainId.toLowerCase());\n}\n/**\n * Check if an address is a native token for the given chain\n */\nfunction isNativeToken(address, chainId) {\n const addrLower = address.toLowerCase();\n if (chainId && isSolanaChain(chainId)) {\n return addrLower === SOLANA_NATIVE_ADDRESS.toLowerCase();\n }\n return addrLower === EVM_NATIVE_ADDRESS;\n}\n/**\n * Get native token info for a chain\n */\nfunction getNativeTokenInfo(chainId) {\n const native = NATIVE_TOKENS[chainId];\n if (!native)\n return null;\n return { ...native };\n}\n/**\n * Check if a string is a valid EVM address (0x format)\n */\nfunction isEvmAddress(value) {\n return /^0x[a-fA-F0-9]{40}$/i.test(value);\n}\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\nfunction isSolanaAddress(value) {\n // Base58 charset: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\n // Excludes: 0, O, I, l\n return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);\n}\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\nfunction isValidTokenAddress(value, chainId) {\n if (chainId && isSolanaChain(chainId)) {\n return isSolanaAddress(value);\n }\n // For EVM chains or unknown chains, check both formats\n return isEvmAddress(value) || isSolanaAddress(value);\n}\n/**\n * Populate the token cache for a chain from SDK config service\n * This is the canonical way to get tokens - used by both resolveToken and getTokenInfo\n */\nfunction populateTokenCache(chainId) {\n // Convert to SDK chain ID format (e.g., \"base\" -> \"0x2105.base\")\n const sdkChainId = toSodaxChainId(chainId);\n let tokens = tokenCache.get(chainId);\n if (tokens)\n return tokens;\n try {\n const client = getSodaxClient();\n const configService = client.config;\n if (configService?.getSupportedSwapTokensByChainId) {\n // Preferred method - returns readonly Token[]\n tokens = [...configService.getSupportedSwapTokensByChainId(sdkChainId)];\n }\n else if (configService?.getSwapTokensByChainId) {\n tokens = configService.getSwapTokensByChainId(sdkChainId);\n }\n else if (configService?.getSwapTokens) {\n const allTokens = configService.getSwapTokens();\n tokens = allTokens[sdkChainId] || [];\n }\n else {\n console.warn(`[tokenResolver] configService not available for chain ${chainId}`);\n tokens = [];\n }\n // Log what we got from SDK\n if (tokens && tokens.length > 0) {\n console.log(`[tokenResolver] Loaded ${tokens.length} tokens from SDK for ${chainId}`);\n }\n }\n catch (err) {\n console.error(`[tokenResolver] Failed to fetch tokens for chain ${chainId}:`, err);\n tokens = [];\n }\n // Use fallback tokens if SDK returned empty list\n if ((!tokens || tokens.length === 0) && FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId]) {\n console.log(`[tokenResolver] Using fallback token list for ${chainId}`);\n tokens = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n }\n tokenCache.set(chainId, tokens || []);\n return tokens || [];\n}\n/**\n * Resolve a token symbol or address to a normalized address\n *\n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport async function resolveToken(chainId, tokenInput) {\n // If already a valid address, normalize and return\n if (isValidTokenAddress(tokenInput, chainId)) {\n // EVM addresses are lowercased, Solana addresses preserve case\n return isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n }\n // Get tokens from cache or SDK\n const tokens = populateTokenCache(chainId);\n // Find by symbol (case-insensitive)\n const symbolUpper = tokenInput.toUpperCase();\n const token = tokens.find(t => t.symbol.toUpperCase() === symbolUpper);\n if (!token) {\n // Build helpful error message with available tokens\n const available = tokens.length > 0\n ? tokens.map(t => t.symbol).join(', ')\n : 'No tokens loaded';\n throw new Error(`Unknown token \"${tokenInput}\" on chain ${chainId}. ` +\n `Available: ${available}. ` +\n `Alternatively, provide the token address directly.`);\n }\n return isEvmAddress(token.address) ? token.address.toLowerCase() : token.address;\n}\n/**\n * Resolve multiple tokens at once (for efficiency)\n *\n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport async function resolveTokens(chainId, tokenInputs) {\n return Promise.all(tokenInputs.map(t => resolveToken(chainId, t)));\n}\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport async function getTokenInfo(chainId, tokenInput) {\n const sdkChainId = toSodaxChainId(chainId);\n // Handle native tokens first\n if (isValidTokenAddress(tokenInput, chainId) && isNativeToken(tokenInput, chainId)) {\n const nativeInfo = getNativeTokenInfo(chainId);\n if (nativeInfo) {\n return nativeInfo;\n }\n }\n // Get tokens from cache or SDK (same path as resolveToken)\n const tokens = populateTokenCache(chainId);\n // Find by address or symbol\n if (isValidTokenAddress(tokenInput, chainId)) {\n // Normalize address for comparison\n const addrNorm = isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n const found = tokens.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (found) {\n return found;\n }\n // Check fallback tokens even if SDK tokens were loaded\n const fallback = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n if (fallback) {\n const fallbackToken = fallback.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (fallbackToken) {\n console.log(`[tokenResolver] Found ${fallbackToken.symbol} in fallback for ${chainId}`);\n return fallbackToken;\n }\n }\n return null;\n }\n else {\n const symbolUpper = tokenInput.toUpperCase();\n return tokens.find(t => t.symbol.toUpperCase() === symbolUpper) || null;\n }\n}\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport function clearTokenCache() {\n tokenCache.clear();\n}\n/**\n * Get all cached tokens for a chain\n */\nexport function getCachedTokens(chainId) {\n return tokenCache.get(chainId);\n}\n// Export address validation utilities for use by other modules\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n//# sourceMappingURL=tokenResolver.js.map",
213 "inputSchema": {},
214 "outputSchema": null,
215 "icons": null,
216 "annotations": null,
217 "meta": null,
218 "execution": null
219 },
220 {
221 "name": "positionAggregator.js",
222 "title": null,
223 "description": "Script: positionAggregator.js. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n *\n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { normalizeChainId } from '../wallet/types';\n// ============================================================================\n// Position Aggregation Functions\n// ============================================================================\n/**\n * Aggregate money market positions across all supported chains\n *\n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport async function aggregateCrossChainPositions(walletId, options = {}) {\n const startTime = Date.now();\n // Get wallet\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Get supported chains from SODAX\n const sodax = getSodaxClient();\n const sodaxChains = sodax.config.getSupportedSpokeChains();\n // Map SDK chains to string IDs\n const allSodaxChains = sodaxChains.map((c) => typeof c === 'string' ? c : c.id);\n // Filter chains by what the wallet supports\n // This is important for Bankr which only supports ethereum/polygon/base\n const walletSupportedChains = wallet.supportedChains;\n const filteredChains = allSodaxChains.filter((chainId) => wallet.supportsChain(normalizeChainId(chainId)));\n // Determine which chains to query\n const chainsToQuery = options.chainIds || filteredChains;\n console.log('[positionAggregator] Wallet chain filter', {\n walletType: wallet.type,\n walletSupports: walletSupportedChains,\n sodaxChains: allSodaxChains,\n filteredChains: filteredChains,\n normalizedFiltered: filteredChains.map(normalizeChainId),\n });\n console.log('[positionAggregator] Querying positions across chains', {\n walletId,\n address: walletAddress,\n chains: chainsToQuery,\n });\n // Query positions from all chains in parallel\n const chainResults = await Promise.allSettled(chainsToQuery.map(chainId => queryChainPositions(walletId, walletAddress, chainId)));\n // Collect all positions\n const allPositions = [];\n const chainSummaries = [];\n chainResults.forEach((result, index) => {\n const chainId = chainsToQuery[index];\n if (result.status === 'fulfilled') {\n const { positions, summary } = result.value;\n if (positions.length > 0 || options.includeZeroBalances) {\n allPositions.push(...positions);\n chainSummaries.push(summary);\n }\n }\n else {\n console.warn(`[positionAggregator] Failed to query chain ${chainId}:`, result.reason);\n }\n });\n // Calculate aggregated summary\n const summary = calculateAggregatedSummary(allPositions);\n // Calculate collateral utilization\n const collateralUtilization = calculateCollateralUtilization(allPositions, summary);\n // Calculate risk metrics\n const riskMetrics = calculateRiskMetrics(allPositions, summary);\n const view = {\n walletId,\n address: walletAddress,\n timestamp: new Date().toISOString(),\n summary,\n chainSummaries: chainSummaries.sort((a, b) => b.netWorthUsd - a.netWorthUsd),\n positions: allPositions.sort((a, b) => (parseFloat(b.supply.balanceUsd) + parseFloat(b.borrow.balanceUsd)) -\n (parseFloat(a.supply.balanceUsd) + parseFloat(a.borrow.balanceUsd))),\n collateralUtilization,\n riskMetrics,\n };\n console.log('[positionAggregator] Aggregation complete', {\n durationMs: Date.now() - startTime,\n totalPositions: allPositions.length,\n totalSupplyUsd: summary.totalSupplyUsd,\n totalBorrowUsd: summary.totalBorrowUsd,\n healthFactor: summary.healthFactor,\n });\n return view;\n}\n/**\n * Query positions for a single chain\n *\n * IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n * To get token symbols/names, we must:\n * 1. Fetch getReservesHumanized() for token metadata\n * 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n * 3. Join with formatUserSummary(buildUserSummaryRequest())\n *\n * Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n */\nasync function queryChainPositions(walletId, address, chainId) {\n try {\n // Use address for spoke provider lookup\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n // This is the key fix - getUserReservesHumanized alone doesn't include token metadata\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(sodax.moneyMarket.data.buildReserveDataWithPrice(reserves));\n // Step 3: Fetch user-specific balances\n const userReserves = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReserves));\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = userSummary.userReservesData || [];\n // Convert to TokenPosition format\n const positions = userReservesData.map((reserve) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n return {\n chainId,\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n logoURI: reserve.reserve?.iconSymbol || undefined,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n balanceRaw: reserve.scaledATokenBalance || '0',\n apy: supplyApy,\n isCollateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n balanceRaw: reserve.scaledVariableDebt || '0',\n apy: borrowApy,\n },\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n // Filter out positions with zero balance (unless explicitly requested)\n const activePositions = positions.filter(p => parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0);\n // Calculate chain summary\n const supplyUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const borrowUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'), 0);\n // Calculate health factor for this chain\n const healthFactor = calculateChainHealthFactor(activePositions);\n const summary = {\n chainId,\n supplyUsd,\n borrowUsd,\n netWorthUsd: supplyUsd - borrowUsd,\n healthFactor,\n positionCount: activePositions.length,\n };\n console.log(`[positionAggregator] Chain ${chainId}: ${activePositions.length} positions, supply=$${supplyUsd.toFixed(2)}, borrow=$${borrowUsd.toFixed(2)}`);\n return { positions: activePositions, summary };\n }\n catch (error) {\n console.error(`[positionAggregator] Error querying ${chainId}:`, error);\n throw error;\n }\n}\n// ============================================================================\n// Calculation Helpers\n// ============================================================================\n/**\n * Calculate aggregated summary across all positions\n */\nfunction calculateAggregatedSummary(positions) {\n let totalSupplyUsd = 0;\n let totalBorrowUsd = 0;\n let weightedSupplyApy = 0;\n let weightedBorrowApy = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n totalSupplyUsd += supplyUsd;\n totalBorrowUsd += borrowUsd;\n weightedSupplyApy += supplyUsd * pos.supply.apy;\n weightedBorrowApy += borrowUsd * pos.borrow.apy;\n });\n // Calculate weighted average APYs\n const avgSupplyApy = totalSupplyUsd > 0 ? weightedSupplyApy / totalSupplyUsd : 0;\n const avgBorrowApy = totalBorrowUsd > 0 ? weightedBorrowApy / totalBorrowUsd : 0;\n // Calculate health factor\n const healthFactor = calculateHealthFactor(positions);\n // Determine liquidation risk\n let liquidationRisk = 'none';\n if (healthFactor !== null) {\n if (healthFactor < 1.1)\n liquidationRisk = 'high';\n else if (healthFactor < 1.5)\n liquidationRisk = 'medium';\n else if (healthFactor < 2)\n liquidationRisk = 'low';\n }\n // Calculate available borrow (simplified - would need proper oracle prices)\n // This is a conservative estimate based on average LTV\n const avgLtv = positions.length > 0\n ? positions.reduce((sum, p) => sum + p.loanToValue, 0) / positions.length\n : 0;\n const availableBorrowUsd = totalSupplyUsd * avgLtv - totalBorrowUsd;\n return {\n totalSupplyUsd,\n totalBorrowUsd,\n netWorthUsd: totalSupplyUsd - totalBorrowUsd,\n availableBorrowUsd: Math.max(0, availableBorrowUsd),\n healthFactor,\n liquidationRisk,\n weightedSupplyApy: avgSupplyApy,\n weightedBorrowApy: avgBorrowApy,\n netApy: totalSupplyUsd > 0\n ? (avgSupplyApy * totalSupplyUsd - avgBorrowApy * totalBorrowUsd) / totalSupplyUsd\n : 0,\n };\n}\n/**\n * Calculate collateral utilization metrics\n */\nfunction calculateCollateralUtilization(positions, summary) {\n // Only count collateral-enabled supplies\n const totalCollateralUsd = positions\n .filter(p => p.supply.isCollateral)\n .reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const usedCollateralUsd = summary.totalBorrowUsd;\n const availableCollateralUsd = Math.max(0, totalCollateralUsd - usedCollateralUsd);\n const utilizationRate = totalCollateralUsd > 0 ? (usedCollateralUsd / totalCollateralUsd) * 100 : 0;\n return {\n totalCollateralUsd,\n usedCollateralUsd,\n availableCollateralUsd,\n utilizationRate,\n };\n}\n/**\n * Calculate risk metrics\n */\nfunction calculateRiskMetrics(positions, summary) {\n // Calculate max LTV across all positions (weighted by supply)\n let totalSupply = 0;\n let weightedLtvSum = 0;\n let liquidationThresholdSum = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n totalSupply += supplyUsd;\n weightedLtvSum += supplyUsd * pos.loanToValue;\n liquidationThresholdSum += supplyUsd * pos.liquidationThreshold;\n });\n const maxLtv = totalSupply > 0 ? weightedLtvSum / totalSupply : 0;\n const avgLiquidationThreshold = totalSupply > 0 ? liquidationThresholdSum / totalSupply : 0;\n // Current LTV\n const currentLtv = summary.totalSupplyUsd > 0\n ? summary.totalBorrowUsd / summary.totalSupplyUsd\n : 0;\n // Buffer until liquidation (percentage points)\n const bufferUntilLiquidation = Math.max(0, avgLiquidationThreshold - currentLtv) * 100;\n // Safe max borrow (at 80% of liquidation threshold for safety)\n const safeMaxBorrowUsd = summary.totalSupplyUsd * avgLiquidationThreshold * 0.8;\n return {\n maxLtv,\n currentLtv,\n bufferUntilLiquidation,\n safeMaxBorrowUsd,\n };\n}\n/**\n * Calculate health factor for a set of positions\n * Health Factor = (Total Collateral in ETH * Liquidation Threshold) / Total Borrow in ETH\n */\nfunction calculateHealthFactor(positions) {\n let totalCollateralEth = 0;\n let totalBorrowEth = 0;\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n // Only count collateral-enabled supplies\n if (pos.supply.isCollateral) {\n totalCollateralEth += supplyUsd * pos.liquidationThreshold;\n }\n totalBorrowEth += borrowUsd;\n });\n if (totalBorrowEth === 0) {\n return totalCollateralEth > 0 ? Infinity : null;\n }\n return totalCollateralEth / totalBorrowEth;\n}\n/**\n * Calculate health factor for a single chain\n */\nfunction calculateChainHealthFactor(positions) {\n return calculateHealthFactor(positions);\n}\n// ============================================================================\n// Utility Functions\n// ============================================================================\n/**\n * Format health factor for display\n */\nexport function formatHealthFactor(hf) {\n if (hf === null)\n return 'N/A';\n if (hf === Infinity)\n return '\u221e';\n return hf.toFixed(2);\n}\n/**\n * Get health factor color/styling indicator\n */\nexport function getHealthFactorStatus(hf) {\n if (hf === null)\n return { status: 'healthy', color: 'green' };\n if (hf === Infinity)\n return { status: 'healthy', color: 'green' };\n if (hf < 1.1)\n return { status: 'critical', color: 'red' };\n if (hf < 1.5)\n return { status: 'danger', color: 'orange' };\n if (hf < 2)\n return { status: 'caution', color: 'yellow' };\n return { status: 'healthy', color: 'green' };\n}\n/**\n * Get recommendation based on position health\n */\nexport function getPositionRecommendation(view) {\n const recommendations = [];\n const { summary } = view;\n // Health factor recommendations\n if (summary.healthFactor !== null && summary.healthFactor < 1.5) {\n recommendations.push('\u26a0\ufe0f Health factor is low. Consider repaying debt or adding collateral.');\n }\n // Borrowing capacity recommendations\n if (summary.availableBorrowUsd > 1000 && summary.healthFactor !== null && summary.healthFactor > 2) {\n recommendations.push(`\ud83d\udca1 You have $${summary.availableBorrowUsd.toFixed(2)} in available borrowing power.`);\n }\n // Collateral utilization\n if (view.collateralUtilization.utilizationRate > 80) {\n recommendations.push('\u26a0\ufe0f High collateral utilization. Avoid borrowing more to maintain safety margin.');\n }\n // Net APY optimization\n if (summary.netApy < 0) {\n recommendations.push('\ud83d\udcc9 Your borrowing costs exceed supply earnings. Consider reducing debt or finding higher APY supply opportunities.');\n }\n // Cross-chain opportunities\n const highApyChains = view.chainSummaries\n .filter(cs => cs.supplyUsd > 100)\n .sort((a, b) => (b.healthFactor || Infinity) - (a.healthFactor || Infinity));\n if (highApyChains.length > 1) {\n recommendations.push(`\ud83c\udf10 You have positions across ${highApyChains.length} chains. Monitor each chain's health factor independently.`);\n }\n return recommendations;\n}\n//# sourceMappingURL=positionAggregator.js.map",
224 "inputSchema": {},
225 "outputSchema": null,
226 "icons": null,
227 "annotations": null,
228 "meta": null,
229 "execution": null
230 },
231 {
232 "name": "walletRegistry.d.ts",
233 "title": null,
234 "description": "Script: walletRegistry.d.ts. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n *\n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport { WalletConfig } from '../types';\n/**\n * Wallet registry entry\n */\ninterface WalletEntry extends WalletConfig {\n mode: 'execute' | 'prepare';\n}\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport declare class WalletRegistry {\n private wallets;\n private skillAdapter;\n constructor();\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n private loadWallets;\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId: string): WalletEntry | null;\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n resolveWallet(walletId: string): Promise<WalletEntry | null>;\n /**\n * Validate a wallet entry\n */\n private validateWallet;\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode(): 'execute' | 'prepare';\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode(): boolean;\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode(): boolean;\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds(): string[];\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount(): number;\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload(): void;\n}\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport declare function getWalletRegistry(): WalletRegistry;\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport declare function resetWalletRegistry(): void;\nexport {};\n//# sourceMappingURL=walletRegistry.d.ts.map",
235 "inputSchema": {},
236 "outputSchema": null,
237 "icons": null,
238 "annotations": null,
239 "meta": null,
240 "execution": null
241 },
242 {
243 "name": "walletRegistry.js",
244 "title": null,
245 "description": "Script: walletRegistry.js. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n *\n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport { getWalletAdapter } from './skillWalletAdapter';\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport class WalletRegistry {\n wallets;\n skillAdapter;\n constructor() {\n this.skillAdapter = getWalletAdapter();\n this.wallets = this.loadWallets();\n // Log skill adapter status\n if (this.skillAdapter.isUsingSkillWallets()) {\n console.log('[walletRegistry] evm-wallet-skill integration active');\n }\n }\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n loadWallets() {\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (!walletsJson) {\n console.warn('[walletRegistry] AMPED_OC_WALLETS_JSON not set');\n return new Map();\n }\n try {\n const walletConfigs = JSON.parse(walletsJson);\n const wallets = new Map();\n for (const [walletId, config] of Object.entries(walletConfigs)) {\n wallets.set(walletId, {\n ...config,\n mode,\n });\n }\n console.log(`[walletRegistry] Loaded ${wallets.size} wallet(s) in ${mode} mode`);\n return wallets;\n }\n catch (error) {\n console.error('[walletRegistry] Failed to parse AMPED_OC_WALLETS_JSON', error);\n return new Map();\n }\n }\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId) {\n const wallet = this.wallets.get(walletId);\n if (wallet) {\n return this.validateWallet(wallet, walletId);\n }\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n async resolveWallet(walletId) {\n // Try local registry first (synchronous)\n const wallet = this.getWallet(walletId);\n if (wallet) {\n return wallet;\n }\n // Try skill adapter (includes ~/.evm-wallet.json)\n if (this.skillAdapter.isUsingSkillWallets()) {\n try {\n const config = await this.skillAdapter.getWalletConfig(walletId);\n const mode = this.getMode();\n return {\n address: config.address,\n privateKey: config.privateKey,\n mode,\n };\n }\n catch (error) {\n console.error(`[walletRegistry] Skill wallet resolution failed: ${error}`);\n }\n }\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n /**\n * Validate a wallet entry\n */\n validateWallet(wallet, walletId) {\n // In execute mode, validate that private key is present\n if (wallet.mode === 'execute' && !wallet.privateKey) {\n console.error(`[walletRegistry] Wallet ${walletId} missing privateKey in execute mode`);\n return null;\n }\n // Validate address format (basic check)\n if (!wallet.address || !wallet.address.startsWith('0x')) {\n console.error(`[walletRegistry] Wallet ${walletId} has invalid address: ${wallet.address}`);\n return null;\n }\n return wallet;\n }\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode() {\n return process.env.AMPED_OC_MODE || 'execute';\n }\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode() {\n return this.getMode() === 'execute';\n }\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode() {\n return this.getMode() === 'prepare';\n }\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds() {\n const localIds = Array.from(this.wallets.keys());\n const skillIds = this.skillAdapter.getWalletIds();\n // Merge unique IDs\n return [...new Set([...localIds, ...skillIds])];\n }\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount() {\n return this.getWalletIds().length;\n }\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload() {\n this.wallets = this.loadWallets();\n }\n}\n// Singleton instance\nlet walletRegistryInstance = null;\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport function getWalletRegistry() {\n if (!walletRegistryInstance) {\n walletRegistryInstance = new WalletRegistry();\n }\n return walletRegistryInstance;\n}\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport function resetWalletRegistry() {\n walletRegistryInstance = null;\n}\n//# sourceMappingURL=walletRegistry.js.map",
246 "inputSchema": {},
247 "outputSchema": null,
248 "icons": null,
249 "annotations": null,
250 "meta": null,
251 "execution": null
252 },
253 {
254 "name": "walletManager.js",
255 "title": null,
256 "description": "Script: walletManager.js. Code:\n/**\n * Unified Wallet Manager\n *\n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n *\n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport { createEvmWalletSkillBackend, createBankrBackend, createEnvBackend, loadWalletsFromEnv } from './backends';\n/**\n * Config file path\n */\nconst CONFIG_PATH = join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'wallets.json');\nconst EVM_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n/**\n * Singleton WalletManager instance\n */\nlet instance = null;\n/**\n * Unified wallet manager\n */\nexport class WalletManager {\n wallets = new Map();\n defaultWallet = null;\n initialized = false;\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n async initialize() {\n if (this.initialized)\n return;\n console.log('[WalletManager] Initializing...');\n // 1. Load from config file if exists\n await this.loadConfigFile();\n // 2. Auto-discover from environment\n await this.autoDiscover();\n // 3. Set default\n this.determineDefault();\n this.initialized = true;\n console.log(`[WalletManager] Initialized with ${this.wallets.size} wallet(s)`);\n if (this.defaultWallet) {\n console.log(`[WalletManager] Default wallet: ${this.defaultWallet}`);\n }\n }\n /**\n * Load wallets from config file\n */\n async loadConfigFile() {\n if (!existsSync(CONFIG_PATH))\n return;\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const config = JSON.parse(content);\n for (const [name, walletConfig] of Object.entries(config.wallets)) {\n const backend = this.createBackendFromConfig(name, walletConfig);\n if (backend) {\n this.wallets.set(name.toLowerCase(), backend);\n console.log(`[WalletManager] Loaded wallet \"${name}\" from config`);\n }\n }\n if (config.default) {\n this.defaultWallet = config.default.toLowerCase();\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Failed to load config: ${error}`);\n }\n }\n /**\n * Create backend from config entry\n */\n createBackendFromConfig(name, config) {\n try {\n switch (config.source) {\n case 'evm-wallet-skill':\n return createEvmWalletSkillBackend({\n nickname: name,\n path: config.path,\n chains: config.chains,\n });\n case 'bankr':\n if (!config.apiKey) {\n console.warn(`[WalletManager] Bankr wallet \"${name}\" missing apiKey`);\n return null;\n }\n return createBankrBackend({\n nickname: name,\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n });\n case 'env':\n return createEnvBackend({\n nickname: name,\n address: config.address,\n privateKey: config.privateKey,\n envVar: config.envVar,\n chains: config.chains,\n });\n default:\n console.warn(`[WalletManager] Unknown wallet source: ${config.source}`);\n return null;\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Failed to create backend for \"${name}\": ${error}`);\n return null;\n }\n }\n /**\n * Auto-discover wallets from environment\n */\n async autoDiscover() {\n // evm-wallet-skill (if not already configured)\n if (!this.wallets.has('main') && existsSync(EVM_WALLET_PATH)) {\n try {\n const backend = createEvmWalletSkillBackend({ nickname: 'main' });\n if (await backend.isReady()) {\n this.wallets.set('main', backend);\n console.log('[WalletManager] Auto-discovered: evm-wallet-skill \u2192 \"main\"');\n }\n }\n catch (error) {\n console.debug(`[WalletManager] evm-wallet-skill not available: ${error}`);\n }\n }\n // Bankr (if API key present and not already configured)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (!this.wallets.has('bankr') && bankrApiKey) {\n console.log('[WalletManager] Found BANKR_API_KEY, attempting to add Bankr wallet...');\n try {\n const backend = createBankrBackend({\n nickname: 'bankr',\n apiKey: bankrApiKey,\n apiUrl: process.env.BANKR_API_URL,\n });\n const ready = await backend.isReady();\n if (ready) {\n this.wallets.set('bankr', backend);\n console.log('[WalletManager] Auto-discovered: BANKR_API_KEY \u2192 \"bankr\"');\n }\n else {\n console.warn('[WalletManager] Bankr API key present but connectivity check failed');\n }\n }\n catch (error) {\n console.warn(`[WalletManager] Bankr auto-discovery failed: ${error}`);\n }\n }\n // Environment variable wallets\n const envWallets = loadWalletsFromEnv();\n for (const [name, backend] of envWallets) {\n if (!this.wallets.has(name)) {\n this.wallets.set(name, backend);\n console.log(`[WalletManager] Auto-discovered: AMPED_OC_WALLETS_JSON \u2192 \"${name}\"`);\n }\n }\n }\n /**\n * Determine default wallet\n */\n determineDefault() {\n // If already set from config, verify it exists\n if (this.defaultWallet && this.wallets.has(this.defaultWallet)) {\n return;\n }\n // Priority: main > first available\n if (this.wallets.has('main')) {\n this.defaultWallet = 'main';\n }\n else if (this.wallets.size > 0) {\n this.defaultWallet = Array.from(this.wallets.keys())[0];\n }\n else {\n this.defaultWallet = null;\n }\n }\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n async resolve(nickname) {\n await this.initialize();\n const name = (nickname || this.defaultWallet)?.toLowerCase();\n if (!name) {\n throw new Error('No wallet configured.\\n\\n' +\n 'To set up a wallet, install evm-wallet-skill:\\n' +\n ' git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n' +\n ' cd ~/.openclaw/skills/evm-wallet-skill && npm install\\n' +\n ' node src/setup.js');\n }\n const wallet = this.wallets.get(name);\n if (!wallet) {\n const available = Array.from(this.wallets.keys()).join(', ') || '(none)';\n throw new Error(`Wallet \"${name}\" not found. Available wallets: ${available}`);\n }\n return wallet;\n }\n /**\n * Check if a wallet exists\n */\n async has(nickname) {\n await this.initialize();\n return this.wallets.has(nickname.toLowerCase());\n }\n /**\n * List all available wallets\n */\n async listWallets() {\n await this.initialize();\n const wallets = [];\n for (const [name, backend] of this.wallets) {\n try {\n // Add timeout for slow backends (like Bankr)\n const addressPromise = backend.getAddress();\n const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 30000));\n const address = await Promise.race([addressPromise, timeoutPromise]);\n // Get Solana address for Bankr wallets\n let solanaAddress;\n if (backend.type === 'bankr' && backend.getSolanaAddress) {\n try {\n solanaAddress = await backend.getSolanaAddress() || undefined;\n }\n catch (e) {\n console.warn(`[WalletManager] Failed to get Solana address for ${name}`);\n }\n }\n wallets.push({\n nickname: name,\n type: backend.type,\n address,\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n solanaAddress,\n });\n }\n catch (error) {\n // Include wallet with placeholder address if we can't get it\n console.warn(`[WalletManager] Failed to get address for \"${name}\": ${error}`);\n wallets.push({\n nickname: name,\n type: backend.type,\n address: '0x...', // Placeholder\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n });\n }\n }\n return wallets;\n }\n /**\n * Get the default wallet nickname\n */\n async getDefaultWalletName() {\n await this.initialize();\n return this.defaultWallet;\n }\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname, backend) {\n this.wallets.set(nickname.toLowerCase(), backend);\n console.log(`[WalletManager] Registered wallet: ${nickname}`);\n }\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds() {\n return Array.from(this.wallets.keys());\n }\n /**\n * Add a new wallet to the config file\n */\n async addWallet(nickname, config) {\n await this.initialize();\n const normalizedName = nickname.toLowerCase();\n // Check if wallet already exists\n if (this.wallets.has(normalizedName)) {\n throw new Error(`Wallet \"${nickname}\" already exists. Use rename to change it.`);\n }\n // Create the backend to validate config\n const backend = this.createBackendFromConfig(normalizedName, config);\n if (!backend) {\n throw new Error(`Failed to create wallet backend for \"${nickname}\"`);\n }\n // Validate the backend works\n const ready = await backend.isReady();\n if (!ready) {\n throw new Error(`Wallet \"${nickname}\" configuration is invalid or not accessible`);\n }\n // Load existing config\n const fileConfig = this.loadConfigFromFile();\n // Add new wallet\n fileConfig.wallets[normalizedName] = config;\n // Save config\n this.saveConfigToFile(fileConfig);\n // Register in memory\n this.wallets.set(normalizedName, backend);\n console.log(`[WalletManager] Added wallet \"${nickname}\"`);\n }\n /**\n * Rename a wallet\n */\n async renameWallet(currentNickname, newNickname) {\n await this.initialize();\n const currentName = currentNickname.toLowerCase();\n const newName = newNickname.toLowerCase();\n // Check source exists\n if (!this.wallets.has(currentName)) {\n throw new Error(`Wallet \"${currentNickname}\" not found`);\n }\n // Check target doesn't exist\n if (this.wallets.has(newName)) {\n throw new Error(`Wallet \"${newNickname}\" already exists`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Move wallet config\n if (fileConfig.wallets[currentName]) {\n fileConfig.wallets[newName] = fileConfig.wallets[currentName];\n delete fileConfig.wallets[currentName];\n }\n else {\n // Wallet was auto-discovered, need to add it to config\n const backend = this.wallets.get(currentName);\n const config = await this.backendToConfig(backend);\n fileConfig.wallets[newName] = config;\n }\n // Update default if needed\n if (fileConfig.default === currentName) {\n fileConfig.default = newName;\n }\n if (this.defaultWallet === currentName) {\n this.defaultWallet = newName;\n }\n // Save config\n this.saveConfigToFile(fileConfig);\n // Update in-memory\n const backend = this.wallets.get(currentName);\n this.wallets.delete(currentName);\n this.wallets.set(newName, backend);\n console.log(`[WalletManager] Renamed wallet \"${currentNickname}\" to \"${newNickname}\"`);\n }\n /**\n * Remove a wallet from config\n */\n async removeWallet(nickname) {\n await this.initialize();\n const name = nickname.toLowerCase();\n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Remove from config\n delete fileConfig.wallets[name];\n // Update default if needed\n if (fileConfig.default === name) {\n delete fileConfig.default;\n }\n if (this.defaultWallet === name) {\n this.defaultWallet = null;\n this.determineDefault();\n }\n // Save config\n this.saveConfigToFile(fileConfig);\n // Remove from memory\n this.wallets.delete(name);\n console.log(`[WalletManager] Removed wallet \"${nickname}\"`);\n }\n /**\n * Set the default wallet\n */\n async setDefaultWallet(nickname) {\n await this.initialize();\n const name = nickname.toLowerCase();\n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n // Load config\n const fileConfig = this.loadConfigFromFile();\n // Update default\n fileConfig.default = name;\n this.defaultWallet = name;\n // Save config\n this.saveConfigToFile(fileConfig);\n console.log(`[WalletManager] Set default wallet to \"${nickname}\"`);\n }\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n loadConfigFromFile() {\n if (!existsSync(CONFIG_PATH)) {\n return { wallets: {} };\n }\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n return JSON.parse(content);\n }\n catch {\n return { wallets: {} };\n }\n }\n /**\n * Save config to file\n */\n saveConfigToFile(config) {\n // Ensure directory exists\n const dir = dirname(CONFIG_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n console.log(`[WalletManager] Config saved to ${CONFIG_PATH}`);\n }\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n async backendToConfig(backend) {\n const config = {\n source: backend.type,\n chains: [...backend.supportedChains],\n };\n // For evm-wallet-skill, just reference the default path\n if (backend.type === 'evm-wallet-skill') {\n config.path = EVM_WALLET_PATH;\n }\n // For env backends, we need address (privateKey should NOT be saved)\n if (backend.type === 'env') {\n config.address = await backend.getAddress();\n // Note: We don't save privateKey to config for security\n }\n // For bankr, we need the API key\n if (backend.type === 'bankr') {\n config.apiKey = process.env.BANKR_API_KEY;\n }\n return config;\n }\n /**\n * Reset the manager (for testing)\n */\n reset() {\n this.wallets.clear();\n this.defaultWallet = null;\n this.initialized = false;\n }\n}\n/**\n * Get the singleton WalletManager instance\n */\nexport function getWalletManager() {\n if (!instance) {\n instance = new WalletManager();\n }\n return instance;\n}\n/**\n * Reset the singleton (for testing)\n */\nexport function resetWalletManager() {\n if (instance) {\n instance.reset();\n instance = null;\n }\n}\n//# sourceMappingURL=walletManager.js.map",
257 "inputSchema": {},
258 "outputSchema": null,
259 "icons": null,
260 "annotations": null,
261 "meta": null,
262 "execution": null
263 },
264 {
265 "name": "skillWalletAdapter.js",
266 "title": null,
267 "description": "Script: skillWalletAdapter.js. Code:\n/**\n * EVM Wallet Skill Adapter\n *\n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n *\n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n *\n * @see https://github.com/surfer77/evm-wallet-skill\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ErrorCode, AmpedDefiError } from '../utils/errors';\nimport { normalizeChainId } from './types';\n// Try to import viem for address derivation\nlet privateKeyToAccount = null;\ntry {\n const viem = require('viem/accounts');\n privateKeyToAccount = viem.privateKeyToAccount;\n}\ncatch {\n // viem not available, will use address from config\n}\n/**\n * FALLBACK RPC URLs - primary RPCs come from evm-wallet-skill\n * These are only used when evm-wallet-skill does not provide an RPC\n */\nconst FALLBACK_RPCS = {\n // SODAX supported spoke chains\n ethereum: 'https://ethereum.publicnode.com',\n arbitrum: 'https://arb1.arbitrum.io/rpc',\n base: 'https://mainnet.base.org',\n optimism: 'https://mainnet.optimism.io',\n avalanche: 'https://api.avax.network/ext/bc/C/rpc',\n bsc: 'https://bsc-dataseed.binance.org',\n polygon: 'https://polygon-bor-rpc.publicnode.com',\n // Sonic hub chain\n sonic: 'https://rpc.soniclabs.com',\n // Additional chains (may not be SODAX-supported but useful)\n lightlink: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n};\n/**\n * EVM Wallet Skill Adapter\n */\nexport class EvmWalletSkillAdapter {\n skillWallets = new Map();\n skillRpcs = new Map();\n useSkill;\n constructor(options = {}) {\n this.useSkill = options.preferSkill !== false;\n if (this.useSkill) {\n this.loadSkillConfig();\n }\n }\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n loadSkillConfig() {\n // 1. Try ~/.evm-wallet.json first (evm-wallet-skill default location)\n this.loadEvmWalletFile();\n // 2. Try environment variables\n this.loadEnvWallets();\n // 3. Load RPC URLs from environment\n this.loadEnvRpcs();\n }\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n loadEvmWalletFile() {\n try {\n const walletPath = path.join(os.homedir(), '.evm-wallet.json');\n if (!fs.existsSync(walletPath)) {\n return;\n }\n const content = fs.readFileSync(walletPath, 'utf-8');\n const walletData = JSON.parse(content);\n // evm-wallet-skill stores: { privateKey: \"0x...\" } or { privateKey: \"0x...\", address: \"0x...\" }\n if (walletData.privateKey) {\n let address = walletData.address;\n // Derive address from private key if not provided\n if (!address && privateKeyToAccount) {\n try {\n const account = privateKeyToAccount(walletData.privateKey);\n address = account.address;\n }\n catch (e) {\n console.warn('[walletAdapter] Failed to derive address from private key');\n }\n }\n if (address) {\n this.skillWallets.set('default', {\n id: 'default',\n address,\n provider: 'privateKey',\n });\n // Store private key for later use\n this.skillWallets.get('default').privateKey = walletData.privateKey;\n console.log(`[walletAdapter] Loaded wallet from ~/.evm-wallet.json (${address.slice(0, 8)}...)`);\n }\n }\n }\n catch (error) {\n // Silently ignore - file may not exist\n }\n }\n /**\n * Load wallets from environment variables\n */\n loadEnvWallets() {\n try {\n const skillWalletsJson = process.env.EVM_WALLETS_JSON || process.env.WALLET_CONFIG_JSON;\n if (skillWalletsJson) {\n const wallets = JSON.parse(skillWalletsJson);\n if (Array.isArray(wallets)) {\n wallets.forEach(w => {\n this.skillWallets.set(w.id || w.name || 'default', {\n id: w.id || w.name || 'default',\n address: w.address,\n chainId: w.chainId,\n provider: w.provider || w.type,\n });\n });\n }\n else if (typeof wallets === 'object') {\n Object.entries(wallets).forEach(([id, config]) => {\n this.skillWallets.set(id, {\n id,\n address: config.address,\n chainId: config.chainId,\n provider: config.provider || 'privateKey',\n });\n });\n }\n console.log(`[walletAdapter] Loaded ${this.skillWallets.size} wallets from environment`);\n }\n }\n catch (error) {\n console.warn('[walletAdapter] Failed to parse wallet environment variables:', error);\n }\n }\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n loadEnvRpcs() {\n // Start with default RPCs\n Object.entries(FALLBACK_RPCS).forEach(([chain, url]) => {\n this.skillRpcs.set(chain.toLowerCase(), url);\n });\n // Override with environment variables if provided\n try {\n const skillRpcsJson = process.env.AMPED_OC_RPC_URLS_JSON ||\n process.env.EVM_RPC_URLS_JSON ||\n process.env.RPC_URLS_JSON;\n if (skillRpcsJson) {\n const rpcs = JSON.parse(skillRpcsJson);\n Object.entries(rpcs).forEach(([chain, url]) => {\n this.skillRpcs.set(String(chain).toLowerCase(), url);\n });\n console.log(`[walletAdapter] Custom RPC URLs configured for: ${Object.keys(rpcs).join(', ')}`);\n }\n }\n catch (error) {\n console.warn('[walletAdapter] Failed to parse RPC environment variables:', error);\n }\n console.log(`[walletAdapter] ${this.skillRpcs.size} RPC URLs available (includes defaults)`);\n }\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n async getWalletAddress(walletId) {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') ||\n Array.from(this.skillWallets.values())[0];\n if (wallet)\n return wallet.address;\n }\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.address)\n return wallet.address;\n }\n throw new AmpedDefiError(ErrorCode.WALLET_NOT_FOUND, `Wallet not found: ${walletId || 'default'}`, { remediation: 'Configure ~/.evm-wallet.json, EVM_WALLETS_JSON, or AMPED_OC_WALLETS_JSON' });\n }\n /**\n * Get wallet private key - for signing transactions\n */\n async getPrivateKey(walletId) {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') ||\n Array.from(this.skillWallets.values())[0];\n if (wallet && wallet.privateKey) {\n return wallet.privateKey;\n }\n }\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.privateKey)\n return wallet.privateKey;\n }\n return null;\n }\n /**\n * Get full wallet config (address + privateKey if available)\n */\n async getWalletConfig(walletId) {\n const address = await this.getWalletAddress(walletId);\n const privateKey = await this.getPrivateKey(walletId);\n return { address, privateKey: privateKey || undefined };\n }\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n async getRpcUrl(chainId) {\n const key = normalizeChainId(String(chainId)).toLowerCase();\n // Try skill RPCs\n if (this.skillRpcs.has(key)) {\n return this.skillRpcs.get(key);\n }\n // Fallback to AMPED_OC_RPC_URLS_JSON\n const legacy = process.env.AMPED_OC_RPC_URLS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n if (config[key] || config[chainId])\n return config[key] || config[chainId];\n }\n throw new AmpedDefiError(ErrorCode.RPC_URL_NOT_CONFIGURED, `RPC URL not configured for chain: ${chainId}`, { remediation: 'Configure EVM_RPC_URLS_JSON or AMPED_OC_RPC_URLS_JSON' });\n }\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets() {\n return this.skillWallets.size > 0;\n }\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs() {\n return this.skillRpcs.size > 0;\n }\n /**\n * Get all skill wallet IDs\n */\n getWalletIds() {\n return Array.from(this.skillWallets.keys());\n }\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds() {\n return Array.from(this.skillRpcs.keys());\n }\n}\n// Singleton\nlet adapter = null;\nexport function getWalletAdapter(options) {\n if (!adapter) {\n adapter = new EvmWalletSkillAdapter(options);\n }\n return adapter;\n}\nexport function resetWalletAdapter() {\n adapter = null;\n}\n//# sourceMappingURL=skillWalletAdapter.js.map",
268 "inputSchema": {},
269 "outputSchema": null,
270 "icons": null,
271 "annotations": null,
272 "meta": null,
273 "execution": null
274 },
275 {
276 "name": "types.d.ts",
277 "title": null,
278 "description": "Script: types.d.ts. Code:\n/**\n * Wallet Types - Multi-source wallet management\n *\n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\nimport type { Address, Hash } from 'viem';\n/**\n * Supported wallet backend types\n */\nexport type WalletBackendType = 'evm-wallet-skill' | 'bankr' | 'env';\n/**\n * Raw transaction for Bankr submission\n */\nexport interface RawTransaction {\n to: Address;\n data: `0x${string}`;\n value: string;\n chainId: number;\n}\n/**\n * Wallet info returned by list operations\n */\nexport interface WalletInfo {\n nickname: string;\n type: WalletBackendType;\n address: Address;\n chains: string[];\n isDefault: boolean;\n /** Solana address (if wallet has one, e.g., Bankr) */\n solanaAddress?: string;\n}\n/**\n * Wallet backend interface\n * Different implementations for different sources\n */\nexport interface IWalletBackend {\n readonly type: WalletBackendType;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n /**\n * Check if this wallet supports a specific chain\n */\n supportsChain(chainId: string): boolean;\n /**\n * Get private key (for local/env wallets)\n * Returns undefined for Bankr (no local key access)\n */\n getPrivateKey?(): Promise<`0x${string}`>;\n /**\n * Send raw transaction via Bankr API\n * Only available for Bankr backend\n */\n sendRawTransaction?(tx: RawTransaction): Promise<Hash>;\n /**\n * Check if backend is ready/configured\n */\n isReady(): Promise<boolean>;\n}\n/**\n * Wallet configuration from wallets.json\n */\nexport interface WalletConfig {\n source: WalletBackendType;\n path?: string;\n apiKey?: string;\n apiUrl?: string;\n envVar?: string;\n address?: Address;\n privateKey?: `0x${string}`;\n chains?: string[];\n}\n/**\n * Wallets config file structure\n */\nexport interface WalletsConfigFile {\n wallets: Record<string, WalletConfig>;\n default?: string;\n}\n/**\n * Chain IDs for Bankr submission\n */\nexport declare const BANKR_CHAIN_IDS: Record<string, number>;\n/**\n * Chains supported by Bankr\n */\nexport declare const BANKR_SUPPORTED_CHAINS: readonly [\"ethereum\", \"polygon\", \"base\"];\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport declare const SODAX_SUPPORTED_CHAINS: readonly [\"ethereum\", \"base\", \"polygon\", \"arbitrum\", \"optimism\", \"sonic\", \"avalanche\", \"bsc\", \"lightlink\", \"hyper\", \"kaia\"];\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport declare const SODAX_TO_SIMPLE_CHAIN: Record<string, string>;\n/**\n * Simple to SODAX chain ID mapping\n */\nexport declare const SIMPLE_TO_SODAX_CHAIN: Record<string, string>;\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport declare function normalizeChainId(chainId: string): string;\n/**\n * Convert simple chain ID to SODAX format\n */\nexport declare function toSodaxChainId(chainId: string): string;\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport declare function isSameChain(chainId1: string, chainId2: string): boolean;\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport declare function isBankrSupportedChain(chainId: string): boolean;\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport declare function getBankrChainId(chainId: string): number;\n//# sourceMappingURL=types.d.ts.map",
279 "inputSchema": {},
280 "outputSchema": null,
281 "icons": null,
282 "annotations": null,
283 "meta": null,
284 "execution": null
285 },
286 {
287 "name": "index.d.ts",
288 "title": null,
289 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Module\n *\n * Multi-source wallet management with nicknames\n */\nexport * from './types';\nexport * from './backends';\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n//# sourceMappingURL=index.d.ts.map",
290 "inputSchema": {},
291 "outputSchema": null,
292 "icons": null,
293 "annotations": null,
294 "meta": null,
295 "execution": null
296 },
297 {
298 "name": "types.js",
299 "title": null,
300 "description": "Script: types.js. Code:\n/**\n * Wallet Types - Multi-source wallet management\n *\n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\n/**\n * Chain IDs for Bankr submission\n */\nexport const BANKR_CHAIN_IDS = {\n ethereum: 1,\n polygon: 137,\n base: 8453,\n unichain: 130,\n};\n/**\n * Chains supported by Bankr\n */\nexport const BANKR_SUPPORTED_CHAINS = ['ethereum', 'polygon', 'base'];\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport const SODAX_SUPPORTED_CHAINS = [\n 'ethereum',\n 'base',\n 'polygon',\n 'arbitrum',\n 'optimism',\n 'sonic',\n 'avalanche',\n 'bsc',\n 'lightlink',\n 'hyper',\n 'kaia',\n];\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport const SODAX_TO_SIMPLE_CHAIN = {\n // SODAX format -> simple\n '0x2105.base': 'base',\n '0x89.polygon': 'polygon',\n '0xa4b1.arbitrum': 'arbitrum',\n '0xa.optimism': 'optimism',\n '0x38.bsc': 'bsc',\n '0xa86a.avax': 'avalanche',\n '0x2019.kaia': 'kaia',\n // These don't have prefixes in SODAX\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyper': 'hyperevm',\n 'kaia': 'kaia',\n};\n/**\n * Simple to SODAX chain ID mapping\n */\nexport const SIMPLE_TO_SODAX_CHAIN = {\n // Simple -> SODAX format\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'kaia': '0x2019.kaia',\n // No prefix needed\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport function normalizeChainId(chainId) {\n // Already in mapping\n if (SODAX_TO_SIMPLE_CHAIN[chainId]) {\n return SODAX_TO_SIMPLE_CHAIN[chainId];\n }\n // Check if it's already simple format\n if (SIMPLE_TO_SODAX_CHAIN[chainId]) {\n return chainId;\n }\n // Try to extract from prefixed format (0xNNN.name -> name)\n const match = chainId.match(/^0x[a-fA-F0-9]+\\.(.+)$/);\n if (match) {\n return match[1];\n }\n // Return as-is\n return chainId;\n}\n/**\n * Convert simple chain ID to SODAX format\n */\nexport function toSodaxChainId(chainId) {\n // Already in SODAX format\n if (chainId.startsWith('0x') && chainId.includes('.')) {\n return chainId;\n }\n return SIMPLE_TO_SODAX_CHAIN[chainId] || chainId;\n}\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport function isSameChain(chainId1, chainId2) {\n return normalizeChainId(chainId1) === normalizeChainId(chainId2);\n}\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function isBankrSupportedChain(chainId) {\n const normalized = normalizeChainId(chainId);\n return BANKR_SUPPORTED_CHAINS.includes(normalized);\n}\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function getBankrChainId(chainId) {\n const normalized = normalizeChainId(chainId);\n const id = BANKR_CHAIN_IDS[normalized];\n if (!id) {\n throw new Error(`Chain ${chainId} (normalized: ${normalized}) not supported by Bankr. Supported: ${BANKR_SUPPORTED_CHAINS.join(', ')}`);\n }\n return id;\n}\n//# sourceMappingURL=types.js.map",
301 "inputSchema": {},
302 "outputSchema": null,
303 "icons": null,
304 "annotations": null,
305 "meta": null,
306 "execution": null
307 },
308 {
309 "name": "EvmWalletSkillBackend.d.ts",
310 "title": null,
311 "description": "Script: EvmWalletSkillBackend.d.ts. Code:\n/**\n * EVM Wallet Skill Backend\n *\n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport declare class EvmWalletSkillBackend implements IWalletBackend {\n readonly type: \"evm-wallet-skill\";\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n private walletPath;\n private cachedWallet;\n constructor(options: {\n nickname: string;\n path?: string;\n chains?: string[];\n });\n /**\n * Load wallet from file (cached)\n */\n private loadWallet;\n getAddress(): Promise<Address>;\n supportsChain(chainId: string): boolean;\n getPrivateKey(): Promise<`0x${string}`>;\n isReady(): Promise<boolean>;\n}\n/**\n * Create an evm-wallet-skill backend\n */\nexport declare function createEvmWalletSkillBackend(options?: {\n nickname?: string;\n path?: string;\n chains?: string[];\n}): EvmWalletSkillBackend;\n//# sourceMappingURL=EvmWalletSkillBackend.d.ts.map",
312 "inputSchema": {},
313 "outputSchema": null,
314 "icons": null,
315 "annotations": null,
316 "meta": null,
317 "execution": null
318 },
319 {
320 "name": "EnvBackend.d.ts",
321 "title": null,
322 "description": "Script: EnvBackend.d.ts. Code:\n/**\n * Environment Variable Backend\n *\n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\n/**\n * Environment variable backend configuration\n */\nexport interface EnvBackendConfig {\n nickname: string;\n address?: Address;\n privateKey?: `0x${string}`;\n envVar?: string;\n chains?: string[];\n}\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport declare class EnvBackend implements IWalletBackend {\n readonly type: \"env\";\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n private address;\n private privateKey;\n private envVar;\n constructor(config: EnvBackendConfig);\n /**\n * Load wallet from environment variable if needed\n */\n private loadFromEnv;\n getAddress(): Promise<Address>;\n supportsChain(chainId: string): boolean;\n getPrivateKey(): Promise<`0x${string}`>;\n isReady(): Promise<boolean>;\n}\n/**\n * Create env backend from direct config\n */\nexport declare function createEnvBackend(config: EnvBackendConfig): EnvBackend;\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport declare function loadWalletsFromEnv(): Map<string, EnvBackend>;\n//# sourceMappingURL=EnvBackend.d.ts.map",
323 "inputSchema": {},
324 "outputSchema": null,
325 "icons": null,
326 "annotations": null,
327 "meta": null,
328 "execution": null
329 },
330 {
331 "name": "BankrBackend.js",
332 "title": null,
333 "description": "Script: BankrBackend.js. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport { BANKR_SUPPORTED_CHAINS, isBankrSupportedChain } from '../types';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n/**\n * Disk cache path for bankr address\n */\nconst BANKR_CACHE_DIR = join(homedir(), '.openclaw', 'cache');\nconst getBankrCachePath = (nickname) => join(BANKR_CACHE_DIR, `bankr-${nickname}-address.json`);\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport class BankrBackend {\n type = 'bankr';\n nickname;\n supportedChains = BANKR_SUPPORTED_CHAINS;\n apiUrl;\n apiKey;\n cachedAddress = null;\n cachedSolanaAddress = null;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.nickname = config.nickname || 'bankr';\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.apiKey = config.apiKey;\n // Try to load cached address from disk\n this.loadCachedAddress();\n console.log(`[BankrBackend] Initialized as \"${this.nickname}\"`);\n console.log(`[BankrBackend] Supported chains: ${this.supportedChains.join(', ')}`);\n if (this.cachedAddress) {\n console.log(`[BankrBackend] Loaded cached address: ${this.cachedAddress}`);\n }\n }\n /**\n * Load cached address from disk\n */\n loadCachedAddress() {\n const cachePath = getBankrCachePath(this.nickname);\n if (existsSync(cachePath)) {\n try {\n const data = JSON.parse(readFileSync(cachePath, 'utf-8'));\n if (data.address && data.address.match(/^0x[a-fA-F0-9]{40}$/)) {\n this.cachedAddress = data.address;\n }\n }\n catch (e) {\n console.warn(`[BankrBackend] Failed to load cached address: ${e}`);\n }\n }\n }\n /**\n * Save address to disk cache\n */\n saveCachedAddress(address) {\n const cachePath = getBankrCachePath(this.nickname);\n try {\n if (!existsSync(BANKR_CACHE_DIR)) {\n mkdirSync(BANKR_CACHE_DIR, { recursive: true });\n }\n writeFileSync(cachePath, JSON.stringify({ address, timestamp: Date.now() }));\n console.log(`[BankrBackend] Cached address to ${cachePath}`);\n }\n catch (e) {\n console.warn(`[BankrBackend] Failed to cache address: ${e}`);\n }\n }\n async getAddress() {\n if (this.cachedAddress)\n return this.cachedAddress;\n // Query Bankr for the wallet address\n console.log('[BankrBackend] Fetching wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n console.warn('[BankrBackend] Could not parse address from response:', response.slice(0, 100));\n throw new Error('[BankrBackend] Could not determine wallet address from Bankr');\n }\n this.cachedAddress = addressMatch[0];\n // Save to disk for next time\n this.saveCachedAddress(this.cachedAddress);\n console.log(`[BankrBackend] Wallet address: ${this.cachedAddress}`);\n return this.cachedAddress;\n }\n catch (error) {\n console.error('[BankrBackend] Failed to get address:', error);\n throw error;\n }\n }\n /**\n * Get the Solana wallet address from Bankr\n */\n async getSolanaAddress() {\n if (this.cachedSolanaAddress)\n return this.cachedSolanaAddress;\n // Check for cached address on disk\n const cachePath = `${process.env.HOME}/.openclaw/cache/bankr-${this.nickname}-solana-address.json`;\n try {\n const fs = await import('fs');\n if (fs.existsSync(cachePath)) {\n const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));\n if (cached.address && Date.now() - cached.timestamp < 86400000) {\n this.cachedSolanaAddress = cached.address;\n console.log(`[BankrBackend] Loaded cached Solana address: ${this.cachedSolanaAddress}`);\n return this.cachedSolanaAddress;\n }\n }\n }\n catch (e) {\n // Cache miss, continue to query\n }\n console.log('[BankrBackend] Fetching Solana wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my Solana wallet address?');\n // Solana addresses are base58, typically 32-44 chars, no 0x prefix\n const solanaMatch = response.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/);\n if (!solanaMatch) {\n console.warn('[BankrBackend] Could not parse Solana address from response');\n return null;\n }\n this.cachedSolanaAddress = solanaMatch[0];\n console.log(`[BankrBackend] Solana address: ${this.cachedSolanaAddress}`);\n // Cache to disk\n try {\n const fs = await import('fs');\n const path = await import('path');\n const cacheDir = path.dirname(cachePath);\n if (!fs.existsSync(cacheDir))\n fs.mkdirSync(cacheDir, { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify({ address: this.cachedSolanaAddress, timestamp: Date.now() }));\n }\n catch (e) {\n console.warn('[BankrBackend] Failed to cache Solana address:', e);\n }\n return this.cachedSolanaAddress;\n }\n catch (error) {\n console.error('[BankrBackend] Failed to get Solana address:', error);\n return null;\n }\n }\n supportsChain(chainId) {\n // Normalize chain ID to handle SODAX format (0x2105.base -> base)\n return isBankrSupportedChain(chainId);\n }\n async isReady() {\n if (!this.apiKey)\n return false;\n try {\n // Test API connectivity\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n return response.status !== 503 && response.status !== 502;\n }\n catch {\n return false;\n }\n }\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n async sendRawTransaction(tx) {\n console.log(`[BankrBackend] Sending raw transaction`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Chain: ${tx.chainId}`);\n console.log(`[BankrBackend] Value: ${tx.value}`);\n console.log(`[BankrBackend] Data: ${tx.data.slice(0, 20)}...`);\n // Format as documented in arbitrary-transaction.md\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data,\n value: tx.value,\n chainId: tx.chainId,\n }, null, 2);\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n console.log(`[BankrBackend] Submitting to Bankr API...`);\n const result = await this.submitAndWaitForJob(prompt);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`[BankrBackend] Transaction failed: ${errorMsg}`);\n }\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Submit prompt and wait for text response\n */\n async submitAndWait(prompt) {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n /**\n * Submit prompt and wait for job completion\n */\n async submitAndWaitForJob(prompt) {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${submitResponse.status} ${error}`);\n }\n const submitData = await submitResponse.json();\n if (!submitData.success || !submitData.jobId) {\n throw new Error(`[BankrBackend] Invalid job response: ${JSON.stringify(submitData)}`);\n }\n const jobId = submitData.jobId;\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n // Poll for completion\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${statusResponse.status} ${error}`);\n }\n const result = await statusResponse.json();\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n // Log progress updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n }\n }\n throw new Error(`[BankrBackend] Job ${jobId} timed out`);\n }\n /**\n * Extract transaction hash from Bankr response\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash)\n return item.transactionHash;\n if (item.txHash)\n return item.txHash;\n if (item.hash)\n return item.hash;\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch)\n return hashMatch[0];\n // Check for failure indicators\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n return null;\n }\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a Bankr backend from API key\n */\nexport function createBankrBackend(config) {\n return new BankrBackend(config);\n}\n//# sourceMappingURL=BankrBackend.js.map",
334 "inputSchema": {},
335 "outputSchema": null,
336 "icons": null,
337 "annotations": null,
338 "meta": null,
339 "execution": null
340 },
341 {
342 "name": "EvmWalletSkillBackend.js",
343 "title": null,
344 "description": "Script: EvmWalletSkillBackend.js. Code:\n/**\n * EVM Wallet Skill Backend\n *\n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n/**\n * Default path to evm-wallet-skill wallet file\n */\nconst DEFAULT_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport class EvmWalletSkillBackend {\n type = 'evm-wallet-skill';\n nickname;\n supportedChains;\n walletPath;\n cachedWallet = null;\n constructor(options) {\n this.nickname = options.nickname;\n this.walletPath = options.path || DEFAULT_WALLET_PATH;\n this.supportedChains = options.chains || [...SODAX_SUPPORTED_CHAINS];\n }\n /**\n * Load wallet from file (cached)\n */\n loadWallet() {\n if (this.cachedWallet)\n return this.cachedWallet;\n if (!existsSync(this.walletPath)) {\n throw new Error(`Wallet file not found: ${this.walletPath}\\n` +\n `Run: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n` +\n ` cd ~/.openclaw/skills/evm-wallet-skill && npm install && node src/setup.js`);\n }\n try {\n const content = readFileSync(this.walletPath, 'utf-8');\n this.cachedWallet = JSON.parse(content);\n return this.cachedWallet;\n }\n catch (error) {\n throw new Error(`Failed to load wallet from ${this.walletPath}: ${error}`);\n }\n }\n async getAddress() {\n const wallet = this.loadWallet();\n return wallet.address;\n }\n supportsChain(chainId) {\n return this.supportedChains.includes(chainId);\n }\n async getPrivateKey() {\n const wallet = this.loadWallet();\n const key = wallet.privateKey;\n return key.startsWith('0x') ? key : `0x${key}`;\n }\n async isReady() {\n try {\n this.loadWallet();\n return true;\n }\n catch {\n return false;\n }\n }\n}\n/**\n * Create an evm-wallet-skill backend\n */\nexport function createEvmWalletSkillBackend(options = {}) {\n return new EvmWalletSkillBackend({\n nickname: options.nickname || 'main',\n path: options.path,\n chains: options.chains,\n });\n}\n//# sourceMappingURL=EvmWalletSkillBackend.js.map",
345 "inputSchema": {},
346 "outputSchema": null,
347 "icons": null,
348 "annotations": null,
349 "meta": null,
350 "execution": null
351 },
352 {
353 "name": "index.d.ts",
354 "title": null,
355 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Backends\n *\n * Export all wallet backend implementations\n */\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend, type BankrBackendConfig } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv, type EnvBackendConfig } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider, type BankrWalletProviderConfig } from './BankrWalletProvider';\n//# sourceMappingURL=index.d.ts.map",
356 "inputSchema": {},
357 "outputSchema": null,
358 "icons": null,
359 "annotations": null,
360 "meta": null,
361 "execution": null
362 },
363 {
364 "name": "BankrBackend.d.ts",
365 "title": null,
366 "description": "Script: BankrBackend.d.ts. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport type { Address, Hash } from 'viem';\nimport type { IWalletBackend, RawTransaction } from '../types';\n/**\n * Bankr backend configuration\n */\nexport interface BankrBackendConfig {\n nickname?: string;\n apiKey: string;\n apiUrl?: string;\n}\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport declare class BankrBackend implements IWalletBackend {\n readonly type: \"bankr\";\n readonly nickname: string;\n readonly supportedChains: readonly [\"ethereum\", \"polygon\", \"base\"];\n private readonly apiUrl;\n private readonly apiKey;\n private cachedAddress;\n private cachedSolanaAddress;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrBackendConfig);\n /**\n * Load cached address from disk\n */\n private loadCachedAddress;\n /**\n * Save address to disk cache\n */\n private saveCachedAddress;\n getAddress(): Promise<Address>;\n /**\n * Get the Solana wallet address from Bankr\n */\n getSolanaAddress(): Promise<string | null>;\n supportsChain(chainId: string): boolean;\n isReady(): Promise<boolean>;\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n sendRawTransaction(tx: RawTransaction): Promise<Hash>;\n /**\n * Submit prompt and wait for text response\n */\n private submitAndWait;\n /**\n * Submit prompt and wait for job completion\n */\n private submitAndWaitForJob;\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash;\n private sleep;\n}\n/**\n * Create a Bankr backend from API key\n */\nexport declare function createBankrBackend(config: BankrBackendConfig): BankrBackend;\n//# sourceMappingURL=BankrBackend.d.ts.map",
367 "inputSchema": {},
368 "outputSchema": null,
369 "icons": null,
370 "annotations": null,
371 "meta": null,
372 "execution": null
373 },
374 {
375 "name": "BankrWalletProvider.js",
376 "title": null,
377 "description": "Script: BankrWalletProvider.js. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n *\n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n *\n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport { createPublicClient, http } from 'viem';\nimport { mainnet, polygon, base } from 'viem/chains';\n/**\n * Chain configurations for Bankr\n */\nconst BANKR_CHAINS = {\n 1: { chain: mainnet, name: 'ethereum' },\n 137: { chain: polygon, name: 'polygon' },\n 8453: { chain: base, name: 'base' },\n};\n/**\n * Bankr Wallet Provider\n *\n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport class BankrWalletProvider {\n publicClient;\n apiUrl;\n apiKey;\n chainId;\n cachedAddress;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.apiKey = config.apiKey;\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.chainId = config.chainId;\n this.cachedAddress = config.cachedAddress || null;\n // Validate chain support\n const chainConfig = BANKR_CHAINS[config.chainId];\n if (!chainConfig) {\n throw new Error(`Bankr does not support chainId ${config.chainId}. ` +\n `Supported: Ethereum (1), Polygon (137), Base (8453)`);\n }\n // Create public client for read operations\n this.publicClient = createPublicClient({\n chain: chainConfig.chain,\n transport: http(config.rpcUrl),\n });\n console.log(`[BankrWalletProvider] Initialized for ${chainConfig.name} (${config.chainId})`);\n }\n /**\n * Get the Bankr wallet address\n */\n async getWalletAddress() {\n if (this.cachedAddress)\n return this.cachedAddress;\n console.log('[BankrWalletProvider] Fetching wallet address from Bankr...');\n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n throw new Error('Could not parse wallet address from Bankr response');\n }\n this.cachedAddress = addressMatch[0];\n console.log(`[BankrWalletProvider] Wallet address: ${this.cachedAddress}`);\n return this.cachedAddress;\n }\n catch (error) {\n console.error('[BankrWalletProvider] Failed to get address:', error);\n throw error;\n }\n }\n /**\n * Send a transaction via Bankr\n *\n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n async sendTransaction(evmRawTx) {\n console.log('[BankrWalletProvider] Sending transaction via Bankr');\n console.log(`[BankrWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[BankrWalletProvider] Value: ${evmRawTx.value}`);\n console.log(`[BankrWalletProvider] Data: ${evmRawTx.data.slice(0, 20)}...`);\n // Format transaction for Bankr's arbitrary transaction endpoint\n const txJson = JSON.stringify({\n to: evmRawTx.to,\n data: evmRawTx.data,\n value: evmRawTx.value.toString(),\n chainId: this.chainId,\n }, null, 2);\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n console.log('[BankrWalletProvider] Submitting to Bankr API...');\n const result = await this.submitAndWaitForJob(prompt);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`Transaction failed: ${errorMsg}`);\n }\n console.log(`[BankrWalletProvider] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Wait for transaction receipt\n *\n * Uses the public client to query the blockchain directly.\n */\n async waitForTransactionReceipt(txHash) {\n console.log(`[BankrWalletProvider] Waiting for receipt: ${txHash}`);\n const receipt = await this.publicClient.waitForTransactionReceipt({\n hash: txHash,\n timeout: 120_000, // 2 minutes\n });\n // Convert viem receipt to SODAX format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: `0x${receipt.transactionIndex.toString(16)}`,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: `0x${receipt.cumulativeGasUsed.toString(16)}`,\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: receipt.contractAddress,\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics || [],\n data: log.data,\n blockHash: log.blockHash,\n blockNumber: log.blockNumber ? `0x${log.blockNumber.toString(16)}` : null,\n logIndex: log.logIndex !== null ? `0x${log.logIndex.toString(16)}` : null,\n transactionHash: log.transactionHash,\n transactionIndex: log.transactionIndex !== null ? `0x${log.transactionIndex.toString(16)}` : null,\n removed: log.removed,\n })),\n logsBloom: receipt.logsBloom,\n status: receipt.status === 'success' ? '0x1' : '0x0',\n type: receipt.type,\n effectiveGasPrice: receipt.effectiveGasPrice ? `0x${receipt.effectiveGasPrice.toString(16)}` : undefined,\n };\n }\n /**\n * Submit prompt and wait for text response\n */\n async submitAndWait(prompt) {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n /**\n * Submit prompt and wait for job completion\n */\n async submitAndWaitForJob(prompt) {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`Failed to submit job: ${submitResponse.status} ${error}`);\n }\n const submitData = await submitResponse.json();\n if (!submitData.success || !submitData.jobId) {\n throw new Error(`Invalid job response: ${JSON.stringify(submitData)}`);\n }\n const jobId = submitData.jobId;\n console.log(`[BankrWalletProvider] Job submitted: ${jobId}`);\n // Poll for completion\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`Failed to get job status: ${statusResponse.status} ${error}`);\n }\n const result = await statusResponse.json();\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrWalletProvider] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`Job was cancelled`);\n }\n }\n throw new Error(`Job ${jobId} timed out`);\n }\n /**\n * Extract transaction hash from Bankr response\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash)\n return item.transactionHash;\n if (item.txHash)\n return item.txHash;\n if (item.hash)\n return item.hash;\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch)\n return hashMatch[0];\n }\n return null;\n }\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a BankrWalletProvider\n */\nexport function createBankrWalletProvider(config) {\n return new BankrWalletProvider(config);\n}\n//# sourceMappingURL=BankrWalletProvider.js.map",
378 "inputSchema": {},
379 "outputSchema": null,
380 "icons": null,
381 "annotations": null,
382 "meta": null,
383 "execution": null
384 },
385 {
386 "name": "BankrWalletProvider.d.ts",
387 "title": null,
388 "description": "Script: BankrWalletProvider.d.ts. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n *\n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n *\n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\nimport type { Address, Hash, PublicClient } from 'viem';\nimport type { IEvmWalletProvider, EvmRawTransaction, EvmRawTransactionReceipt } from '@sodax/types';\n/**\n * Configuration for BankrWalletProvider\n */\nexport interface BankrWalletProviderConfig {\n apiKey: string;\n apiUrl?: string;\n chainId: number;\n rpcUrl?: string;\n /** Pre-cached address (avoids initial API call) */\n cachedAddress?: Address;\n}\n/**\n * Bankr Wallet Provider\n *\n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport declare class BankrWalletProvider implements IEvmWalletProvider {\n readonly publicClient: PublicClient;\n private readonly apiUrl;\n private readonly apiKey;\n private readonly chainId;\n private cachedAddress;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrWalletProviderConfig);\n /**\n * Get the Bankr wallet address\n */\n getWalletAddress(): Promise<Address>;\n /**\n * Send a transaction via Bankr\n *\n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash>;\n /**\n * Wait for transaction receipt\n *\n * Uses the public client to query the blockchain directly.\n */\n waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt>;\n /**\n * Submit prompt and wait for text response\n */\n private submitAndWait;\n /**\n * Submit prompt and wait for job completion\n */\n private submitAndWaitForJob;\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash;\n private sleep;\n}\n/**\n * Create a BankrWalletProvider\n */\nexport declare function createBankrWalletProvider(config: BankrWalletProviderConfig): BankrWalletProvider;\n//# sourceMappingURL=BankrWalletProvider.d.ts.map",
389 "inputSchema": {},
390 "outputSchema": null,
391 "icons": null,
392 "annotations": null,
393 "meta": null,
394 "execution": null
395 },
396 {
397 "name": "index.js",
398 "title": null,
399 "description": "Script: index.js. Code:\n/**\n * Wallet Backends\n *\n * Export all wallet backend implementations\n */\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider } from './BankrWalletProvider';\n//# sourceMappingURL=index.js.map",
400 "inputSchema": {},
401 "outputSchema": null,
402 "icons": null,
403 "annotations": null,
404 "meta": null,
405 "execution": null
406 },
407 {
408 "name": "EnvBackend.js",
409 "title": null,
410 "description": "Script: EnvBackend.js. Code:\n/**\n * Environment Variable Backend\n *\n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport class EnvBackend {\n type = 'env';\n nickname;\n supportedChains;\n address = null;\n privateKey = null;\n envVar = null;\n constructor(config) {\n this.nickname = config.nickname;\n this.supportedChains = config.chains || [...SODAX_SUPPORTED_CHAINS];\n if (config.address && config.privateKey) {\n // Direct address/key provided\n this.address = config.address;\n this.privateKey = config.privateKey;\n }\n else if (config.envVar) {\n // Will load from env var\n this.envVar = config.envVar;\n }\n }\n /**\n * Load wallet from environment variable if needed\n */\n loadFromEnv() {\n if (this.address && this.privateKey) {\n return { address: this.address, privateKey: this.privateKey };\n }\n if (this.envVar) {\n const envValue = process.env[this.envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${this.envVar} not set`);\n }\n try {\n const data = JSON.parse(envValue);\n this.address = data.address;\n this.privateKey = (data.privateKey.startsWith('0x')\n ? data.privateKey\n : `0x${data.privateKey}`);\n return { address: this.address, privateKey: this.privateKey };\n }\n catch (error) {\n throw new Error(`Failed to parse ${this.envVar}: ${error}`);\n }\n }\n throw new Error(`No wallet configuration for \"${this.nickname}\"`);\n }\n async getAddress() {\n const { address } = this.loadFromEnv();\n return address;\n }\n supportsChain(chainId) {\n return this.supportedChains.includes(chainId);\n }\n async getPrivateKey() {\n const { privateKey } = this.loadFromEnv();\n return privateKey;\n }\n async isReady() {\n try {\n this.loadFromEnv();\n return true;\n }\n catch {\n return false;\n }\n }\n}\n/**\n * Create env backend from direct config\n */\nexport function createEnvBackend(config) {\n return new EnvBackend(config);\n}\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport function loadWalletsFromEnv() {\n const wallets = new Map();\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n if (!walletsJson)\n return wallets;\n try {\n const parsed = JSON.parse(walletsJson);\n for (const [name, wallet] of Object.entries(parsed)) {\n const backend = new EnvBackend({\n nickname: name,\n address: wallet.address,\n privateKey: (wallet.privateKey.startsWith('0x')\n ? wallet.privateKey\n : `0x${wallet.privateKey}`),\n });\n wallets.set(name.toLowerCase(), backend);\n }\n console.log(`[EnvBackend] Loaded ${wallets.size} wallet(s) from AMPED_OC_WALLETS_JSON`);\n }\n catch (error) {\n console.warn(`[EnvBackend] Failed to parse AMPED_OC_WALLETS_JSON: ${error}`);\n }\n return wallets;\n}\n//# sourceMappingURL=EnvBackend.js.map",
411 "inputSchema": {},
412 "outputSchema": null,
413 "icons": null,
414 "annotations": null,
415 "meta": null,
416 "execution": null
417 },
418 {
419 "name": "backendConfig.d.ts",
420 "title": null,
421 "description": "Script: backendConfig.d.ts. Code:\n/**\n * Wallet Backend Configuration\n *\n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n *\n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\nimport type { WalletBackendType } from './providers';\nexport interface BackendConfig {\n backend: WalletBackendType;\n bankrApiKey?: string;\n bankrApiUrl?: string;\n}\n/**\n * Get the resolved backend configuration\n *\n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport declare function getBackendConfig(): BackendConfig;\n/**\n * Check if Bankr backend is configured and available\n */\nexport declare function isBankrConfigured(): boolean;\n/**\n * Get Bankr configuration if available\n */\nexport declare function getBankrConfig(): {\n apiKey: string;\n apiUrl: string;\n} | null;\n//# sourceMappingURL=backendConfig.d.ts.map",
422 "inputSchema": {},
423 "outputSchema": null,
424 "icons": null,
425 "annotations": null,
426 "meta": null,
427 "execution": null
428 },
429 {
430 "name": "walletManager.d.ts",
431 "title": null,
432 "description": "Script: walletManager.d.ts. Code:\n/**\n * Unified Wallet Manager\n *\n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n *\n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\nimport type { IWalletBackend, WalletInfo, WalletConfig } from './types';\n/**\n * Unified wallet manager\n */\nexport declare class WalletManager {\n private wallets;\n private defaultWallet;\n private initialized;\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n initialize(): Promise<void>;\n /**\n * Load wallets from config file\n */\n private loadConfigFile;\n /**\n * Create backend from config entry\n */\n private createBackendFromConfig;\n /**\n * Auto-discover wallets from environment\n */\n private autoDiscover;\n /**\n * Determine default wallet\n */\n private determineDefault;\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n resolve(nickname?: string): Promise<IWalletBackend>;\n /**\n * Check if a wallet exists\n */\n has(nickname: string): Promise<boolean>;\n /**\n * List all available wallets\n */\n listWallets(): Promise<WalletInfo[]>;\n /**\n * Get the default wallet nickname\n */\n getDefaultWalletName(): Promise<string | null>;\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname: string, backend: IWalletBackend): void;\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds(): string[];\n /**\n * Add a new wallet to the config file\n */\n addWallet(nickname: string, config: WalletConfig): Promise<void>;\n /**\n * Rename a wallet\n */\n renameWallet(currentNickname: string, newNickname: string): Promise<void>;\n /**\n * Remove a wallet from config\n */\n removeWallet(nickname: string): Promise<void>;\n /**\n * Set the default wallet\n */\n setDefaultWallet(nickname: string): Promise<void>;\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n private loadConfigFromFile;\n /**\n * Save config to file\n */\n private saveConfigToFile;\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n private backendToConfig;\n /**\n * Reset the manager (for testing)\n */\n reset(): void;\n}\n/**\n * Get the singleton WalletManager instance\n */\nexport declare function getWalletManager(): WalletManager;\n/**\n * Reset the singleton (for testing)\n */\nexport declare function resetWalletManager(): void;\n//# sourceMappingURL=walletManager.d.ts.map",
433 "inputSchema": {},
434 "outputSchema": null,
435 "icons": null,
436 "annotations": null,
437 "meta": null,
438 "execution": null
439 },
440 {
441 "name": "AmpedWalletProvider.d.ts",
442 "title": null,
443 "description": "Script: AmpedWalletProvider.d.ts. Code:\n/**\n * Amped Wallet Provider\n *\n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n *\n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n *\n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\nimport { createPublicClient, type Hash, type Address } from 'viem';\nimport type { EvmRawTransaction, EvmRawTransactionReceipt } from '@sodax/types';\nimport type { IWalletBackend, WalletBackendConfig, WalletBackendType, IAmpedWalletProvider, BankrBackendConfig } from './types';\n/**\n * Amped Wallet Provider\n *\n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport declare class AmpedWalletProvider implements IAmpedWalletProvider {\n readonly publicClient: ReturnType<typeof createPublicClient>;\n private readonly backend;\n private readonly chainId;\n private constructor();\n /**\n * Create an AmpedWalletProvider with a local key backend\n *\n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static fromPrivateKey(config: {\n privateKey: `0x${string}`;\n chainId: string | number;\n rpcUrl?: string;\n }): Promise<AmpedWalletProvider>;\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n *\n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static fromBankr(config: {\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n chainId: string | number;\n rpcUrl?: string;\n policy?: BankrBackendConfig['policy'];\n }): Promise<AmpedWalletProvider>;\n /**\n * Create from generic backend configuration\n */\n static fromConfig(config: WalletBackendConfig): Promise<AmpedWalletProvider>;\n /**\n * Get the wallet address\n */\n getWalletAddress(): Promise<Address>;\n /**\n * Send a transaction\n *\n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash>;\n /**\n * Wait for transaction receipt\n *\n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt>;\n /**\n * Get the underlying backend\n */\n getBackend(): IWalletBackend;\n /**\n * Get the backend type\n */\n getBackendType(): WalletBackendType;\n /**\n * Check if ready for transactions\n */\n isReady(): Promise<boolean>;\n /**\n * Get chain ID\n */\n getChainId(): number;\n}\nexport type { IAmpedWalletProvider };\n//# sourceMappingURL=AmpedWalletProvider.d.ts.map",
444 "inputSchema": {},
445 "outputSchema": null,
446 "icons": null,
447 "annotations": null,
448 "meta": null,
449 "execution": null
450 },
451 {
452 "name": "BankrBackend.js",
453 "title": null,
454 "description": "Script: BankrBackend.js. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\nimport { resolveChainId } from './chainConfig';\n/**\n * Serialize error objects for readable error messages\n */\nfunction serializeError(error) {\n if (error instanceof Error)\n return error.message;\n if (typeof error === 'string')\n return error;\n try {\n return JSON.stringify(error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n }\n catch {\n return String(error);\n }\n}\n/**\n * Chain ID to chain name mapping for Bankr prompts\n */\nconst CHAIN_NAMES = {\n 1: 'ethereum',\n 8453: 'base',\n 137: 'polygon',\n 42161: 'arbitrum',\n 10: 'optimism',\n 1890: 'lightlink',\n 146: 'sonic',\n};\n/**\n * Bankr execution backend\n *\n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport class BankrBackend {\n type = 'bankr';\n apiUrl;\n apiKey;\n userAddress;\n chainId;\n policy;\n // Polling configuration\n pollIntervalMs = 2000;\n maxPollAttempts = 150; // 5 minutes max\n constructor(config) {\n this.apiUrl = config.bankrApiUrl || 'https://api.bankr.bot';\n this.apiKey = config.bankrApiKey;\n this.userAddress = config.userAddress;\n this.chainId = resolveChainId(config.chainId);\n this.policy = config.policy;\n console.log(`[BankrBackend] Initialized for chain ${this.chainId}`);\n console.log(`[BankrBackend] User address: ${this.userAddress}`);\n console.log(`[BankrBackend] API URL: ${this.apiUrl}`);\n }\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n async getAddress() {\n return this.userAddress;\n }\n /**\n * Send a transaction via Bankr Agent API\n *\n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n async sendTransaction(tx) {\n console.log(`[BankrBackend] Sending transaction via Bankr API`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Value: ${tx.value || 0n}`);\n console.log(`[BankrBackend] Data: ${tx.data ? tx.data.slice(0, 20) + '...' : '0x'}`);\n // Validate against policy\n if (this.policy) {\n await this.validatePolicy(tx);\n }\n // Format transaction as JSON for Bankr prompt\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data || '0x',\n value: (tx.value || 0n).toString(),\n chainId: this.chainId,\n });\n // Create natural language prompt for Bankr\n const prompt = `Submit this transaction: ${txJson}`;\n console.log(`[BankrBackend] Submitting prompt to Bankr API`);\n // Submit job to Bankr\n const jobId = await this.submitJob(prompt);\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n // Poll for completion\n const result = await this.pollJobUntilComplete(jobId);\n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n if (!txHash) {\n throw new Error(`[BankrBackend] Transaction failed: ${serializeError(result.response || result.error) || 'Unknown error'}`);\n }\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n /**\n * Wait for transaction confirmation\n *\n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n async waitForTransaction(txHash) {\n console.log(`[BankrBackend] waitForTransaction called for: ${txHash}`);\n // Bankr transactions are confirmed when the job completes\n // We return a minimal receipt since we don't have full details\n return {\n transactionHash: txHash,\n blockNumber: 0n, // Unknown - would need to query chain\n blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000',\n from: this.userAddress,\n to: null,\n gasUsed: 0n,\n status: 'success',\n logs: [],\n };\n }\n /**\n * Check if backend is ready\n *\n * Verifies Bankr API connectivity with a simple balance query.\n */\n async isReady() {\n if (!this.apiUrl || !this.apiKey || !this.userAddress) {\n return false;\n }\n try {\n // Test API connectivity with a simple query\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n // Even a 4xx error means API is reachable\n return response.status !== 503 && response.status !== 502;\n }\n catch (error) {\n console.error('[BankrBackend] Connectivity check failed:', error);\n return false;\n }\n }\n /**\n * Get the chain ID\n */\n getChainId() {\n return this.chainId;\n }\n /**\n * Submit a job to Bankr Agent API\n */\n async submitJob(prompt) {\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${response.status} ${error}`);\n }\n const data = await response.json();\n if (!data.success || !data.jobId) {\n throw new Error(`[BankrBackend] Invalid job submission response: ${JSON.stringify(data)}`);\n }\n return data.jobId;\n }\n /**\n * Poll for job completion\n */\n async pollJobUntilComplete(jobId) {\n let lastStatus = '';\n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n const result = await this.getJobStatus(jobId);\n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId} status: ${result.status}`);\n lastStatus = result.status;\n }\n // Log status updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n // Check for terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${serializeError(result.error) || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n case 'pending':\n case 'processing':\n // Continue polling\n break;\n default:\n console.warn(`[BankrBackend] Unknown status: ${result.status}`);\n }\n }\n throw new Error(`[BankrBackend] Job ${jobId} timed out after ${this.maxPollAttempts * this.pollIntervalMs / 1000} seconds`);\n }\n /**\n * Get job status from Bankr API\n */\n async getJobStatus(jobId) {\n const response = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${response.status} ${error}`);\n }\n return await response.json();\n }\n /**\n * Extract transaction hash from Bankr response\n *\n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n extractTransactionHash(result) {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) {\n return item.transactionHash;\n }\n if (item.txHash) {\n return item.txHash;\n }\n if (item.hash) {\n return item.hash;\n }\n }\n }\n // Try to extract from response text\n if (result.response) {\n // Look for hex transaction hash pattern (0x followed by 64 hex chars)\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) {\n return hashMatch[0];\n }\n // Check if response indicates failure\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n return null;\n }\n /**\n * Validate transaction against policy\n */\n async validatePolicy(tx) {\n if (!this.policy)\n return;\n // Check max value per transaction\n if (this.policy.maxValuePerTx && tx.value && tx.value > this.policy.maxValuePerTx) {\n throw new Error(`Transaction value ${tx.value} exceeds max allowed ${this.policy.maxValuePerTx}`);\n }\n // Check allowed contracts\n if (this.policy.allowedContracts && this.policy.allowedContracts.length > 0) {\n if (!this.policy.allowedContracts.includes(tx.to)) {\n throw new Error(`Contract ${tx.to} is not in the allowed contracts list`);\n }\n }\n }\n /**\n * Sleep helper\n */\n sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n/**\n * Create a BankrBackend from configuration\n */\nexport async function createBankrBackend(config) {\n const backend = new BankrBackend(config);\n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[BankrBackend] Backend created but connectivity check failed');\n }\n return backend;\n}\n//# sourceMappingURL=BankrBackend.js.map",
455 "inputSchema": {},
456 "outputSchema": null,
457 "icons": null,
458 "annotations": null,
459 "meta": null,
460 "execution": null
461 },
462 {
463 "name": "types.d.ts",
464 "title": null,
465 "description": "Script: types.d.ts. Code:\n/**\n * Wallet Provider Types\n *\n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\nimport type { Hash, Address } from 'viem';\nimport type { IEvmWalletProvider } from '@sodax/types';\n/**\n * Wallet backend type identifiers\n */\nexport type WalletBackendType = 'localKey' | 'bankr' | 'privy' | 'smartWallet';\n/**\n * Base configuration for all backends\n */\nexport interface WalletBackendBaseConfig {\n type: WalletBackendType;\n chainId: string | number;\n rpcUrl?: string;\n}\n/**\n * Configuration for local private key backend\n */\nexport interface LocalKeyBackendConfig extends WalletBackendBaseConfig {\n type: 'localKey';\n privateKey: `0x${string}`;\n}\n/**\n * Configuration for Bankr execution backend\n */\nexport interface BankrBackendConfig extends WalletBackendBaseConfig {\n type: 'bankr';\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n /** Optional: policy limits for transactions */\n policy?: {\n maxValuePerTx?: bigint;\n maxDailyVolume?: bigint;\n allowedContracts?: Address[];\n };\n}\n/**\n * Configuration for Privy server wallet backend (future)\n */\nexport interface PrivyBackendConfig extends WalletBackendBaseConfig {\n type: 'privy';\n appId: string;\n appSecret: string;\n walletId: string;\n}\n/**\n * Configuration for smart contract wallet backend (future)\n */\nexport interface SmartWalletBackendConfig extends WalletBackendBaseConfig {\n type: 'smartWallet';\n walletAddress: Address;\n sessionKey: `0x${string}`;\n entryPointAddress: Address;\n}\n/**\n * Union of all backend configurations\n */\nexport type WalletBackendConfig = LocalKeyBackendConfig | BankrBackendConfig | PrivyBackendConfig | SmartWalletBackendConfig;\n/**\n * Transaction request (simplified)\n */\nexport interface TransactionRequest {\n to: Address;\n value?: bigint;\n data?: `0x${string}`;\n gasLimit?: bigint;\n gasPrice?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n nonce?: number;\n}\n/**\n * Transaction receipt (simplified)\n */\nexport interface TransactionReceipt {\n transactionHash: Hash;\n blockNumber: bigint;\n blockHash: Hash;\n from: Address;\n to: Address | null;\n gasUsed: bigint;\n status: 'success' | 'reverted';\n logs: Array<{\n address: Address;\n topics: `0x${string}`[];\n data: `0x${string}`;\n }>;\n}\n/**\n * Wallet backend interface\n *\n * All wallet backends must implement this interface.\n * This allows AmpedWalletProvider to delegate to different backends\n * without changing its own implementation.\n */\nexport interface IWalletBackend {\n /** Backend type identifier */\n readonly type: WalletBackendType;\n /** Get the wallet address */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction and return the transaction hash\n * The backend is responsible for signing and submitting the transaction.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for a transaction to be confirmed\n * Returns when the transaction is included in a block.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if the backend can execute transactions\n * (e.g., Bankr backend may need API connectivity)\n */\n isReady(): Promise<boolean>;\n /**\n * Get the numeric chain ID this backend is configured for\n */\n getChainId(): number;\n}\n/**\n * Factory function type for creating backends\n */\nexport type WalletBackendFactory = (config: WalletBackendConfig) => Promise<IWalletBackend>;\n/**\n * Amped Wallet Provider configuration\n */\nexport interface AmpedWalletProviderConfig {\n /** Backend configuration */\n backend: WalletBackendConfig;\n /** Optional custom RPC URL (overrides chain default) */\n rpcUrl?: string;\n}\n/**\n * Extended IEvmWalletProvider with Amped-specific methods\n */\nexport interface IAmpedWalletProvider extends IEvmWalletProvider {\n /** Get the underlying backend */\n getBackend(): IWalletBackend;\n /** Get the backend type */\n getBackendType(): WalletBackendType;\n /** Check if ready for transactions */\n isReady(): Promise<boolean>;\n /** Get chain ID */\n getChainId(): number;\n}\n//# sourceMappingURL=types.d.ts.map",
466 "inputSchema": {},
467 "outputSchema": null,
468 "icons": null,
469 "annotations": null,
470 "meta": null,
471 "execution": null
472 },
473 {
474 "name": "chainConfig.d.ts",
475 "title": null,
476 "description": "Script: chainConfig.d.ts. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n *\n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\nimport { type Chain } from 'viem';\n/**\n * Chain ID constants matching @sodax/types\n */\nexport declare const CHAIN_IDS: {\n readonly ETHEREUM: 1;\n readonly ARBITRUM: 42161;\n readonly OPTIMISM: 10;\n readonly BASE: 8453;\n readonly POLYGON: 137;\n readonly BSC: 56;\n readonly AVALANCHE: 43114;\n readonly SONIC: 146;\n readonly LIGHTLINK: 1890;\n readonly HYPEREVM: 999;\n readonly KAIA: 8217;\n};\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport declare const SDK_CHAIN_ID_MAP: Record<string, number>;\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport declare const hyper: {\n blockExplorers: {\n readonly default: {\n readonly name: \"HyperEVMScan\";\n readonly url: \"https://hyperevmscan.io/\";\n };\n };\n blockTime?: number | undefined;\n contracts: {\n readonly multicall3: {\n readonly address: \"0xcA11bde05977b3631167028862bE2a173976CA11\";\n readonly blockCreated: 13051;\n };\n };\n ensTlds?: readonly string[] | undefined;\n id: 999;\n name: \"HyperEVM\";\n nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"HYPE\";\n readonly symbol: \"HYPE\";\n };\n experimental_preconfirmationTime?: number | undefined;\n rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://rpc.hyperliquid.xyz/evm\"];\n };\n };\n sourceId?: number | undefined;\n testnet?: boolean | undefined;\n custom?: Record<string, unknown>;\n extendSchema?: Record<string, unknown>;\n fees?: import(\"viem\").ChainFees<undefined>;\n formatters?: undefined;\n prepareTransactionRequest?: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | [fn: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | undefined, options: {\n runAt: readonly (\"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\")[];\n }] | undefined;\n serializers?: import(\"viem\").ChainSerializers<undefined, import(\"viem\").TransactionSerializable>;\n verifyHash?: ((client: import(\"viem\").Client, parameters: import(\"viem\").VerifyHashActionParameters) => Promise<import(\"viem\").VerifyHashActionReturnType>) | undefined;\n extend: <const extended_1 extends Record<string, unknown>>(extended: extended_1) => import(\"viem\").Assign<import(\"viem\").Assign<Chain<undefined>, {\n readonly id: 999;\n readonly name: \"HyperEVM\";\n readonly nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"HYPE\";\n readonly symbol: \"HYPE\";\n };\n readonly rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://rpc.hyperliquid.xyz/evm\"];\n };\n };\n readonly blockExplorers: {\n readonly default: {\n readonly name: \"HyperEVMScan\";\n readonly url: \"https://hyperevmscan.io/\";\n };\n };\n readonly contracts: {\n readonly multicall3: {\n readonly address: \"0xcA11bde05977b3631167028862bE2a173976CA11\";\n readonly blockCreated: 13051;\n };\n };\n }>, extended_1>;\n};\n/**\n * Kaia chain definition\n */\nexport declare const kaia: {\n blockExplorers: {\n readonly default: {\n readonly name: \"KaiaScan\";\n readonly url: \"https://kaiascan.io/\";\n };\n };\n blockTime?: number | undefined;\n contracts?: import(\"viem\").Prettify<{\n [key: string]: import(\"viem\").ChainContract | {\n [sourceId: number]: import(\"viem\").ChainContract | undefined;\n } | undefined;\n } & {\n ensRegistry?: import(\"viem\").ChainContract | undefined;\n ensUniversalResolver?: import(\"viem\").ChainContract | undefined;\n multicall3?: import(\"viem\").ChainContract | undefined;\n erc6492Verifier?: import(\"viem\").ChainContract | undefined;\n }> | undefined;\n ensTlds?: readonly string[] | undefined;\n id: 8217;\n name: \"Kaia\";\n nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"KAIA\";\n readonly symbol: \"KAIA\";\n };\n experimental_preconfirmationTime?: number | undefined;\n rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://public-en.node.kaia.io\"];\n };\n };\n sourceId?: number | undefined;\n testnet?: boolean | undefined;\n custom?: Record<string, unknown>;\n extendSchema?: Record<string, unknown>;\n fees?: import(\"viem\").ChainFees<undefined>;\n formatters?: undefined;\n prepareTransactionRequest?: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | [fn: ((args: import(\"viem\").PrepareTransactionRequestParameters, options: {\n phase: \"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\";\n }) => Promise<import(\"viem\").PrepareTransactionRequestParameters>) | undefined, options: {\n runAt: readonly (\"beforeFillTransaction\" | \"beforeFillParameters\" | \"afterFillParameters\")[];\n }] | undefined;\n serializers?: import(\"viem\").ChainSerializers<undefined, import(\"viem\").TransactionSerializable>;\n verifyHash?: ((client: import(\"viem\").Client, parameters: import(\"viem\").VerifyHashActionParameters) => Promise<import(\"viem\").VerifyHashActionReturnType>) | undefined;\n extend: <const extended_1 extends Record<string, unknown>>(extended: extended_1) => import(\"viem\").Assign<import(\"viem\").Assign<Chain<undefined>, {\n readonly id: 8217;\n readonly name: \"Kaia\";\n readonly nativeCurrency: {\n readonly decimals: 18;\n readonly name: \"KAIA\";\n readonly symbol: \"KAIA\";\n };\n readonly rpcUrls: {\n readonly default: {\n readonly http: readonly [\"https://public-en.node.kaia.io\"];\n };\n };\n readonly blockExplorers: {\n readonly default: {\n readonly name: \"KaiaScan\";\n readonly url: \"https://kaiascan.io/\";\n };\n };\n }>, extended_1>;\n};\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport declare const DEFAULT_RPC_URLS: Record<number, string>;\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport declare function resolveChainId(sdkChainId: string | number): number;\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport declare function getViemChain(chainId: string | number): Chain;\n/**\n * Get default RPC URL for a chain\n */\nexport declare function getDefaultRpcUrl(chainId: string | number): string;\n/**\n * Check if a chain is supported\n */\nexport declare function isChainSupported(chainId: string | number): boolean;\n/**\n * Get all supported chain IDs\n */\nexport declare function getSupportedChainIds(): number[];\n/**\n * Get chain name\n */\nexport declare function getChainName(chainId: string | number): string;\n//# sourceMappingURL=chainConfig.d.ts.map",
477 "inputSchema": {},
478 "outputSchema": null,
479 "icons": null,
480 "annotations": null,
481 "meta": null,
482 "execution": null
483 },
484 {
485 "name": "AmpedWalletProvider.js",
486 "title": null,
487 "description": "Script: AmpedWalletProvider.js. Code:\n/**\n * Amped Wallet Provider\n *\n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n *\n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n *\n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\nimport { createPublicClient, http, } from 'viem';\nimport { createLocalKeyBackend } from './LocalKeyBackend';\nimport { createBankrBackend } from './BankrBackend';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n/**\n * Amped Wallet Provider\n *\n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport class AmpedWalletProvider {\n publicClient;\n backend;\n chainId;\n constructor(backend, publicClient) {\n this.backend = backend;\n this.publicClient = publicClient;\n this.chainId = backend.getChainId();\n console.log(`[AmpedWalletProvider] Initialized with ${backend.type} backend`);\n console.log(`[AmpedWalletProvider] Chain ID: ${this.chainId}`);\n }\n /**\n * Create an AmpedWalletProvider with a local key backend\n *\n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static async fromPrivateKey(config) {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n // Create backend\n const backend = await createLocalKeyBackend({\n type: 'localKey',\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl,\n });\n // Use the backend's public client\n const publicClient = backend.getPublicClient();\n return new AmpedWalletProvider(backend, publicClient);\n }\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n *\n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static async fromBankr(config) {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n // Create backend\n const backend = await createBankrBackend({\n type: 'bankr',\n bankrApiUrl: config.bankrApiUrl,\n bankrApiKey: config.bankrApiKey,\n userAddress: config.userAddress,\n chainId: config.chainId,\n rpcUrl,\n policy: config.policy,\n });\n // Create public client (for read operations)\n // Bankr backend doesn't have its own public client\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n }); // Type cast needed due to viem's strict typing\n return new AmpedWalletProvider(backend, publicClient);\n }\n /**\n * Create from generic backend configuration\n */\n static async fromConfig(config) {\n switch (config.type) {\n case 'localKey':\n return AmpedWalletProvider.fromPrivateKey({\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n });\n case 'bankr':\n const bankrConfig = config;\n return AmpedWalletProvider.fromBankr({\n bankrApiUrl: bankrConfig.bankrApiUrl,\n bankrApiKey: bankrConfig.bankrApiKey,\n userAddress: bankrConfig.userAddress,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n policy: bankrConfig.policy,\n });\n default:\n throw new Error(`Unsupported backend type: ${config.type}`);\n }\n }\n // ===== IEvmWalletProvider Implementation =====\n /**\n * Get the wallet address\n */\n async getWalletAddress() {\n return this.backend.getAddress();\n }\n /**\n * Send a transaction\n *\n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n async sendTransaction(evmRawTx) {\n console.log(`[AmpedWalletProvider] sendTransaction`);\n console.log(`[AmpedWalletProvider] From: ${evmRawTx.from}`);\n console.log(`[AmpedWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[AmpedWalletProvider] Value: ${evmRawTx.value}`);\n return this.backend.sendTransaction({\n to: evmRawTx.to,\n value: evmRawTx.value,\n data: evmRawTx.data,\n });\n }\n /**\n * Wait for transaction receipt\n *\n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n async waitForTransactionReceipt(txHash) {\n console.log(`[AmpedWalletProvider] waitForTransactionReceipt: ${txHash}`);\n const receipt = await this.backend.waitForTransaction(txHash);\n // Convert to SDK format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0', // Not tracked in our simplified receipt\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: '0x0', // Not tracked\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: null, // Would need to check if this was a deployment\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics,\n data: log.data,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n logIndex: '0x0',\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0',\n removed: false,\n })),\n logsBloom: '0x',\n status: receipt.status === 'success' ? '0x1' : '0x0',\n };\n }\n // ===== IAmpedWalletProvider Extensions =====\n /**\n * Get the underlying backend\n */\n getBackend() {\n return this.backend;\n }\n /**\n * Get the backend type\n */\n getBackendType() {\n return this.backend.type;\n }\n /**\n * Check if ready for transactions\n */\n async isReady() {\n return this.backend.isReady();\n }\n /**\n * Get chain ID\n */\n getChainId() {\n return this.chainId;\n }\n}\n//# sourceMappingURL=AmpedWalletProvider.js.map",
488 "inputSchema": {},
489 "outputSchema": null,
490 "icons": null,
491 "annotations": null,
492 "meta": null,
493 "execution": null
494 },
495 {
496 "name": "LocalKeyBackend.d.ts",
497 "title": null,
498 "description": "Script: LocalKeyBackend.d.ts. Code:\n/**\n * Local Key Backend\n *\n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n *\n * Uses viem for transaction signing and submission.\n */\nimport { createPublicClient, createWalletClient, type Hash, type Address } from 'viem';\nimport type { IWalletBackend, LocalKeyBackendConfig, TransactionRequest, TransactionReceipt } from './types';\n/**\n * Local private key wallet backend\n *\n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport declare class LocalKeyBackend implements IWalletBackend {\n readonly type: \"localKey\";\n private readonly account;\n private readonly walletClient;\n private readonly _publicClient;\n private readonly chainId;\n private readonly chain;\n constructor(config: LocalKeyBackendConfig);\n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction\n *\n * Signs locally and submits via RPC.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for transaction confirmation\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if backend is ready\n *\n * For local key backend, we verify RPC connectivity.\n */\n isReady(): Promise<boolean>;\n /**\n * Get the chain ID\n */\n getChainId(): number;\n /**\n * Get the public client (for external use)\n */\n getPublicClient(): ReturnType<typeof createPublicClient>;\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient(): ReturnType<typeof createWalletClient>;\n}\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport declare function createLocalKeyBackend(config: LocalKeyBackendConfig): Promise<LocalKeyBackend>;\n//# sourceMappingURL=LocalKeyBackend.d.ts.map",
499 "inputSchema": {},
500 "outputSchema": null,
501 "icons": null,
502 "annotations": null,
503 "meta": null,
504 "execution": null
505 },
506 {
507 "name": "index.d.ts",
508 "title": null,
509 "description": "Script: index.d.ts. Code:\n/**\n * Wallet Providers\n *\n * Pluggable wallet backend architecture for Amped DeFi plugin.\n *\n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n *\n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n *\n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\nexport { CHAIN_IDS, SDK_CHAIN_ID_MAP, DEFAULT_RPC_URLS, hyper, resolveChainId, getViemChain, getDefaultRpcUrl, isChainSupported, getSupportedChainIds, getChainName, } from './chainConfig';\nexport type { WalletBackendType, WalletBackendBaseConfig, LocalKeyBackendConfig, BankrBackendConfig, PrivyBackendConfig, SmartWalletBackendConfig, WalletBackendConfig, TransactionRequest, TransactionReceipt, IWalletBackend, WalletBackendFactory, AmpedWalletProviderConfig, IAmpedWalletProvider, } from './types';\n//# sourceMappingURL=index.d.ts.map",
510 "inputSchema": {},
511 "outputSchema": null,
512 "icons": null,
513 "annotations": null,
514 "meta": null,
515 "execution": null
516 },
517 {
518 "name": "LocalKeyBackend.js",
519 "title": null,
520 "description": "Script: LocalKeyBackend.js. Code:\n/**\n * Local Key Backend\n *\n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n *\n * Uses viem for transaction signing and submission.\n */\nimport { createPublicClient, createWalletClient, http, } from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n/**\n * Local private key wallet backend\n *\n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport class LocalKeyBackend {\n type = 'localKey';\n account;\n walletClient;\n _publicClient;\n chainId;\n chain;\n constructor(config) {\n // Resolve chain configuration\n this.chainId = resolveChainId(config.chainId);\n this.chain = getViemChain(this.chainId);\n // Get RPC URL (custom or default)\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(this.chainId);\n // Create account from private key\n this.account = privateKeyToAccount(config.privateKey);\n // Create viem clients\n this._publicClient = createPublicClient({\n chain: this.chain,\n transport: http(rpcUrl),\n });\n this.walletClient = createWalletClient({\n account: this.account,\n chain: this.chain,\n transport: http(rpcUrl),\n });\n console.log(`[LocalKeyBackend] Initialized for chain ${this.chain.name} (${this.chainId})`);\n console.log(`[LocalKeyBackend] Address: ${this.account.address}`);\n }\n /**\n * Get the wallet address\n */\n async getAddress() {\n return this.account.address;\n }\n /**\n * Send a transaction\n *\n * Signs locally and submits via RPC.\n */\n async sendTransaction(tx) {\n console.log(`[LocalKeyBackend] Sending transaction to ${tx.to}`);\n // Build transaction params\n const txParams = {\n account: this.account,\n chain: this.chain,\n to: tx.to,\n value: tx.value || 0n,\n data: tx.data,\n };\n // Add optional gas parameters\n if (tx.gasLimit)\n txParams.gas = tx.gasLimit;\n if (tx.gasPrice)\n txParams.gasPrice = tx.gasPrice;\n if (tx.maxFeePerGas)\n txParams.maxFeePerGas = tx.maxFeePerGas;\n if (tx.maxPriorityFeePerGas)\n txParams.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;\n if (tx.nonce !== undefined)\n txParams.nonce = tx.nonce;\n const hash = await this.walletClient.sendTransaction(txParams);\n console.log(`[LocalKeyBackend] Transaction sent: ${hash}`);\n return hash;\n }\n /**\n * Wait for transaction confirmation\n */\n async waitForTransaction(txHash) {\n console.log(`[LocalKeyBackend] Waiting for transaction: ${txHash}`);\n const receipt = await this._publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n console.log(`[LocalKeyBackend] Transaction confirmed in block ${receipt.blockNumber}`);\n return {\n transactionHash: receipt.transactionHash,\n blockNumber: receipt.blockNumber,\n blockHash: receipt.blockHash,\n from: receipt.from,\n to: receipt.to,\n gasUsed: receipt.gasUsed,\n status: receipt.status === 'success' ? 'success' : 'reverted',\n logs: receipt.logs.map((log) => ({\n address: log.address,\n topics: [...(log.topics || [])],\n data: log.data,\n })),\n };\n }\n /**\n * Check if backend is ready\n *\n * For local key backend, we verify RPC connectivity.\n */\n async isReady() {\n try {\n await this._publicClient.getChainId();\n return true;\n }\n catch (error) {\n console.error('[LocalKeyBackend] RPC connectivity check failed:', error);\n return false;\n }\n }\n /**\n * Get the chain ID\n */\n getChainId() {\n return this.chainId;\n }\n /**\n * Get the public client (for external use)\n */\n getPublicClient() {\n return this._publicClient;\n }\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient() {\n return this.walletClient;\n }\n}\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport async function createLocalKeyBackend(config) {\n const backend = new LocalKeyBackend(config);\n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[LocalKeyBackend] Backend created but RPC connectivity check failed');\n }\n return backend;\n}\n//# sourceMappingURL=LocalKeyBackend.js.map",
521 "inputSchema": {},
522 "outputSchema": null,
523 "icons": null,
524 "annotations": null,
525 "meta": null,
526 "execution": null
527 },
528 {
529 "name": "chainConfig.js",
530 "title": null,
531 "description": "Script: chainConfig.js. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n *\n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\nimport { defineChain } from 'viem';\nimport { mainnet, arbitrum, optimism, base, polygon, bsc, avalanche, sonic, lightlinkPhoenix, } from 'viem/chains';\n/**\n * Chain ID constants matching @sodax/types\n */\nexport const CHAIN_IDS = {\n ETHEREUM: 1,\n ARBITRUM: 42161,\n OPTIMISM: 10,\n BASE: 8453,\n POLYGON: 137,\n BSC: 56,\n AVALANCHE: 43114,\n SONIC: 146,\n LIGHTLINK: 1890,\n HYPEREVM: 999,\n KAIA: 8217,\n};\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport const SDK_CHAIN_ID_MAP = {\n 'ethereum': CHAIN_IDS.ETHEREUM,\n 'arbitrum': CHAIN_IDS.ARBITRUM,\n '0xa4b1.arbitrum': CHAIN_IDS.ARBITRUM,\n 'optimism': CHAIN_IDS.OPTIMISM,\n '0xa.optimism': CHAIN_IDS.OPTIMISM,\n 'base': CHAIN_IDS.BASE,\n '0x2105.base': CHAIN_IDS.BASE,\n 'polygon': CHAIN_IDS.POLYGON,\n '0x89.polygon': CHAIN_IDS.POLYGON,\n 'bsc': CHAIN_IDS.BSC,\n '0x38.bsc': CHAIN_IDS.BSC,\n 'avalanche': CHAIN_IDS.AVALANCHE,\n 'avax': CHAIN_IDS.AVALANCHE,\n '0xa86a.avax': CHAIN_IDS.AVALANCHE,\n 'sonic': CHAIN_IDS.SONIC,\n 'lightlink': CHAIN_IDS.LIGHTLINK,\n 'hyperevm': CHAIN_IDS.HYPEREVM,\n 'hyper': CHAIN_IDS.HYPEREVM,\n 'kaia': CHAIN_IDS.KAIA,\n '0x2019.kaia': CHAIN_IDS.KAIA,\n};\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport const hyper = defineChain({\n id: CHAIN_IDS.HYPEREVM,\n name: 'HyperEVM',\n nativeCurrency: { decimals: 18, name: 'HYPE', symbol: 'HYPE' },\n rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },\n blockExplorers: { default: { name: 'HyperEVMScan', url: 'https://hyperevmscan.io/' } },\n contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 13051 } },\n});\n/**\n * Kaia chain definition\n */\nexport const kaia = defineChain({\n id: CHAIN_IDS.KAIA,\n name: 'Kaia',\n nativeCurrency: { decimals: 18, name: 'KAIA', symbol: 'KAIA' },\n rpcUrls: { default: { http: ['https://public-en.node.kaia.io'] } },\n blockExplorers: { default: { name: 'KaiaScan', url: 'https://kaiascan.io/' } },\n});\n/**\n * Chain configuration by numeric ID\n */\nconst CHAIN_CONFIG = {\n [CHAIN_IDS.ETHEREUM]: mainnet,\n [CHAIN_IDS.ARBITRUM]: arbitrum,\n [CHAIN_IDS.OPTIMISM]: optimism,\n [CHAIN_IDS.BASE]: base,\n [CHAIN_IDS.POLYGON]: polygon,\n [CHAIN_IDS.BSC]: bsc,\n [CHAIN_IDS.AVALANCHE]: avalanche,\n [CHAIN_IDS.SONIC]: sonic,\n [CHAIN_IDS.LIGHTLINK]: lightlinkPhoenix,\n [CHAIN_IDS.HYPEREVM]: hyper,\n [CHAIN_IDS.KAIA]: kaia,\n};\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport const DEFAULT_RPC_URLS = {\n [CHAIN_IDS.ETHEREUM]: 'https://ethereum.publicnode.com',\n [CHAIN_IDS.ARBITRUM]: 'https://arb1.arbitrum.io/rpc',\n [CHAIN_IDS.OPTIMISM]: 'https://mainnet.optimism.io',\n [CHAIN_IDS.BASE]: 'https://mainnet.base.org',\n [CHAIN_IDS.POLYGON]: 'https://polygon-bor-rpc.publicnode.com',\n [CHAIN_IDS.BSC]: 'https://bsc-dataseed.binance.org',\n [CHAIN_IDS.AVALANCHE]: 'https://api.avax.network/ext/bc/C/rpc',\n [CHAIN_IDS.SONIC]: 'https://rpc.soniclabs.com',\n [CHAIN_IDS.LIGHTLINK]: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n [CHAIN_IDS.HYPEREVM]: 'https://rpc.hyperliquid.xyz/evm',\n [CHAIN_IDS.KAIA]: 'https://public-en.node.kaia.io',\n};\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport function resolveChainId(sdkChainId) {\n if (typeof sdkChainId === 'number')\n return sdkChainId;\n const lower = sdkChainId.toLowerCase();\n if (SDK_CHAIN_ID_MAP[lower] !== undefined)\n return SDK_CHAIN_ID_MAP[lower];\n if (lower.startsWith('0x')) {\n const parsed = parseInt(lower, 16);\n if (!isNaN(parsed))\n return parsed;\n }\n const parsed = parseInt(sdkChainId, 10);\n if (!isNaN(parsed))\n return parsed;\n throw new Error(`Unable to resolve chain ID: ${sdkChainId}`);\n}\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport function getViemChain(chainId) {\n const numericId = resolveChainId(chainId);\n const chain = CHAIN_CONFIG[numericId];\n if (!chain)\n throw new Error(`Unsupported chain ID: ${chainId} (resolved to ${numericId})`);\n return chain;\n}\n/**\n * Get default RPC URL for a chain\n */\nexport function getDefaultRpcUrl(chainId) {\n const numericId = resolveChainId(chainId);\n const rpcUrl = DEFAULT_RPC_URLS[numericId];\n if (!rpcUrl)\n throw new Error(`No default RPC URL for chain ID: ${chainId}`);\n return rpcUrl;\n}\n/**\n * Check if a chain is supported\n */\nexport function isChainSupported(chainId) {\n try {\n getViemChain(chainId);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Get all supported chain IDs\n */\nexport function getSupportedChainIds() {\n return Object.keys(CHAIN_CONFIG).map(Number);\n}\n/**\n * Get chain name\n */\nexport function getChainName(chainId) {\n return getViemChain(chainId).name;\n}\n//# sourceMappingURL=chainConfig.js.map",
532 "inputSchema": {},
533 "outputSchema": null,
534 "icons": null,
535 "annotations": null,
536 "meta": null,
537 "execution": null
538 },
539 {
540 "name": "BankrBackend.d.ts",
541 "title": null,
542 "description": "Script: BankrBackend.d.ts. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\nimport type { Hash, Address } from 'viem';\nimport type { IWalletBackend, BankrBackendConfig, TransactionRequest, TransactionReceipt } from './types';\n/**\n * Bankr execution backend\n *\n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport declare class BankrBackend implements IWalletBackend {\n readonly type: \"bankr\";\n private readonly apiUrl;\n private readonly apiKey;\n private readonly userAddress;\n private readonly chainId;\n private readonly policy?;\n private readonly pollIntervalMs;\n private readonly maxPollAttempts;\n constructor(config: BankrBackendConfig);\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n getAddress(): Promise<Address>;\n /**\n * Send a transaction via Bankr Agent API\n *\n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n /**\n * Wait for transaction confirmation\n *\n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n /**\n * Check if backend is ready\n *\n * Verifies Bankr API connectivity with a simple balance query.\n */\n isReady(): Promise<boolean>;\n /**\n * Get the chain ID\n */\n getChainId(): number;\n /**\n * Submit a job to Bankr Agent API\n */\n private submitJob;\n /**\n * Poll for job completion\n */\n private pollJobUntilComplete;\n /**\n * Get job status from Bankr API\n */\n private getJobStatus;\n /**\n * Extract transaction hash from Bankr response\n *\n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n private extractTransactionHash;\n /**\n * Validate transaction against policy\n */\n private validatePolicy;\n /**\n * Sleep helper\n */\n private sleep;\n}\n/**\n * Create a BankrBackend from configuration\n */\nexport declare function createBankrBackend(config: BankrBackendConfig): Promise<BankrBackend>;\n//# sourceMappingURL=BankrBackend.d.ts.map",
543 "inputSchema": {},
544 "outputSchema": null,
545 "icons": null,
546 "annotations": null,
547 "meta": null,
548 "execution": null
549 },
550 {
551 "name": "types.js",
552 "title": null,
553 "description": "Script: types.js. Code:\n/**\n * Wallet Provider Types\n *\n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\nexport {};\n//# sourceMappingURL=types.js.map",
554 "inputSchema": {},
555 "outputSchema": null,
556 "icons": null,
557 "annotations": null,
558 "meta": null,
559 "execution": null
560 },
561 {
562 "name": "index.js",
563 "title": null,
564 "description": "Script: index.js. Code:\n/**\n * Wallet Providers\n *\n * Pluggable wallet backend architecture for Amped DeFi plugin.\n *\n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n *\n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n *\n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\n// Main provider\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\n// Backends\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\n// Chain configuration\nexport { CHAIN_IDS, SDK_CHAIN_ID_MAP, DEFAULT_RPC_URLS, hyper, resolveChainId, getViemChain, getDefaultRpcUrl, isChainSupported, getSupportedChainIds, getChainName, } from './chainConfig';\n//# sourceMappingURL=index.js.map",
565 "inputSchema": {},
566 "outputSchema": null,
567 "icons": null,
568 "annotations": null,
569 "meta": null,
570 "execution": null
571 },
572 {
573 "name": "index.js",
574 "title": null,
575 "description": "Script: index.js. Code:\n/**\n * Wallet Module\n *\n * Multi-source wallet management with nicknames\n */\n// Types\nexport * from './types';\n// Backends\nexport * from './backends';\n// Manager\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n//# sourceMappingURL=index.js.map",
576 "inputSchema": {},
577 "outputSchema": null,
578 "icons": null,
579 "annotations": null,
580 "meta": null,
581 "execution": null
582 },
583 {
584 "name": "backendConfig.js",
585 "title": null,
586 "description": "Script: backendConfig.js. Code:\n/**\n * Wallet Backend Configuration\n *\n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n *\n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG = {\n backend: 'localKey',\n};\n/**\n * Path to plugin config file\n */\nfunction getConfigPath() {\n return join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'config.json');\n}\n/**\n * Load configuration from file\n */\nfunction loadConfigFile() {\n const configPath = getConfigPath();\n if (!existsSync(configPath)) {\n return null;\n }\n try {\n const content = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(content);\n return {\n backend: config.walletBackend,\n bankrApiKey: config.bankrApiKey,\n bankrApiUrl: config.bankrApiUrl,\n };\n }\n catch (error) {\n console.warn('[backendConfig] Failed to load config file:', error);\n return null;\n }\n}\n/**\n * Load configuration from environment variables\n */\nfunction loadEnvConfig() {\n const config = {};\n // Check for explicit backend selection\n const backendEnv = process.env.AMPED_OC_WALLET_BACKEND;\n if (backendEnv === 'bankr' || backendEnv === 'localKey') {\n config.backend = backendEnv;\n }\n // Check for Bankr API key (implies bankr backend)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (bankrApiKey) {\n config.bankrApiKey = bankrApiKey;\n // Auto-select bankr backend if API key is present\n if (!config.backend) {\n config.backend = 'bankr';\n }\n }\n // Optional Bankr API URL override\n const bankrApiUrl = process.env.BANKR_API_URL;\n if (bankrApiUrl) {\n config.bankrApiUrl = bankrApiUrl;\n }\n return config;\n}\n/**\n * Get the resolved backend configuration\n *\n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport function getBackendConfig() {\n const fileConfig = loadConfigFile() || {};\n const envConfig = loadEnvConfig();\n // Merge with priority: env > file > default\n const config = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n ...envConfig,\n };\n // Validate bankr configuration\n if (config.backend === 'bankr' && !config.bankrApiKey) {\n console.warn('[backendConfig] Bankr backend selected but no API key provided');\n console.warn('[backendConfig] Set BANKR_API_KEY environment variable or add bankrApiKey to config.json');\n console.warn('[backendConfig] Falling back to localKey backend');\n config.backend = 'localKey';\n }\n // Set default Bankr API URL if not specified\n if (config.backend === 'bankr' && !config.bankrApiUrl) {\n config.bankrApiUrl = 'https://api.bankr.bot';\n }\n console.log(`[backendConfig] Using wallet backend: ${config.backend}`);\n return config;\n}\n/**\n * Check if Bankr backend is configured and available\n */\nexport function isBankrConfigured() {\n const config = getBackendConfig();\n return config.backend === 'bankr' && !!config.bankrApiKey;\n}\n/**\n * Get Bankr configuration if available\n */\nexport function getBankrConfig() {\n const config = getBackendConfig();\n if (config.backend !== 'bankr' || !config.bankrApiKey) {\n return null;\n }\n return {\n apiKey: config.bankrApiKey,\n apiUrl: config.bankrApiUrl || 'https://api.bankr.bot',\n };\n}\n//# sourceMappingURL=backendConfig.js.map",
587 "inputSchema": {},
588 "outputSchema": null,
589 "icons": null,
590 "annotations": null,
591 "meta": null,
592 "execution": null
593 },
594 {
595 "name": "skillWalletAdapter.d.ts",
596 "title": null,
597 "description": "Script: skillWalletAdapter.d.ts. Code:\n/**\n * EVM Wallet Skill Adapter\n *\n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n *\n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n *\n * @see https://github.com/surfer77/evm-wallet-skill\n */\n/**\n * Wallet information from evm-wallet-skill\n */\nexport interface EvmWalletInfo {\n id: string;\n address: string;\n chainId?: number;\n provider?: 'privateKey' | 'kms' | 'hardware' | 'web3Auth';\n}\n/**\n * Wallet adapter options\n */\nexport interface WalletAdapterOptions {\n preferSkill?: boolean;\n walletId?: string;\n}\n/**\n * EVM Wallet Skill Adapter\n */\nexport declare class EvmWalletSkillAdapter {\n private skillWallets;\n private skillRpcs;\n private useSkill;\n constructor(options?: WalletAdapterOptions);\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n private loadSkillConfig;\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n private loadEvmWalletFile;\n /**\n * Load wallets from environment variables\n */\n private loadEnvWallets;\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n private loadEnvRpcs;\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n getWalletAddress(walletId?: string): Promise<string>;\n /**\n * Get wallet private key - for signing transactions\n */\n getPrivateKey(walletId?: string): Promise<string | null>;\n /**\n * Get full wallet config (address + privateKey if available)\n */\n getWalletConfig(walletId?: string): Promise<{\n address: string;\n privateKey?: string;\n }>;\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n getRpcUrl(chainId: string | number): Promise<string>;\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets(): boolean;\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs(): boolean;\n /**\n * Get all skill wallet IDs\n */\n getWalletIds(): string[];\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds(): string[];\n}\nexport declare function getWalletAdapter(options?: WalletAdapterOptions): EvmWalletSkillAdapter;\nexport declare function resetWalletAdapter(): void;\n//# sourceMappingURL=skillWalletAdapter.d.ts.map",
598 "inputSchema": {},
599 "outputSchema": null,
600 "icons": null,
601 "annotations": null,
602 "meta": null,
603 "execution": null
604 },
605 {
606 "name": "types.d.ts",
607 "title": null,
608 "description": "Script: types.d.ts. Code:\n/**\n * Common types for Amped DeFi plugin\n */\nimport { Static, TSchema } from '@sinclair/typebox';\n/**\n * Tool handler function type\n */\nexport type ToolHandler<T extends TSchema> = (params: Static<T>) => Promise<unknown>;\n/**\n * Agent tools registry interface\n */\nexport interface AgentTools {\n register: <T extends TSchema>(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: T;\n handler: ToolHandler<T>;\n }) => void;\n}\n/**\n * Alternative registration interface with input/output schemas\n */\nexport interface AgentToolsTyped {\n register<T extends TSchema, R extends TSchema>(config: {\n name: string;\n summary: string;\n description?: string;\n schema: {\n input: T;\n output?: R;\n };\n handler: (input: Static<T>) => Promise<Static<R>>;\n }): void;\n}\n/**\n * Standard tool result wrapper\n */\nexport interface ToolResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n/**\n * Transaction status\n */\nexport type TransactionStatus = 'pending' | 'submitted' | 'confirmed' | 'failed' | 'cancelled' | 'unknown';\n/**\n * Intent status from SODAX\n */\nexport interface IntentStatus {\n intentHash: string;\n status: 'pending' | 'filled' | 'cancelled' | 'expired' | 'failed';\n spokeTxHash?: string;\n hubTxHash?: string;\n filledAmount?: string;\n error?: string;\n createdAt?: number;\n updatedAt?: number;\n}\n/**\n * Quote result\n */\nexport interface QuoteResult {\n quoteId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n srcAmount: string;\n dstAmount: string;\n minDstAmount?: string;\n slippageBps: number;\n deadline: number;\n fees: {\n solverFee?: string;\n partnerFee?: string;\n gasFee?: string;\n };\n route?: unknown;\n}\n/**\n * Bridge result\n */\nexport interface BridgeResult {\n spokeTxHash: string;\n hubTxHash?: string;\n status: TransactionStatus;\n}\n/**\n * Money market position\n */\nexport interface MoneyMarketPosition {\n token: string;\n supplied: string;\n borrowed: string;\n supplyApy: number;\n borrowApy: number;\n collateralFactor: number;\n}\n/**\n * Money market reserve\n */\nexport interface MoneyMarketReserve {\n token: string;\n totalSupplied: string;\n totalBorrowed: string;\n supplyApy: number;\n borrowApy: number;\n utilizationRate: number;\n collateralFactor: number;\n liquidationThreshold: number;\n}\n/**\n * Chain configuration\n */\nexport interface ChainConfig {\n chainId: string;\n name: string;\n isHub: boolean;\n nativeCurrency: {\n symbol: string;\n decimals: number;\n };\n rpcUrl?: string;\n}\n/**\n * Token configuration\n */\nexport interface TokenConfig {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n chainId: string;\n}\n/**\n * Wallet information (safe to log)\n */\nexport interface WalletInfo {\n walletId: string;\n address: string;\n mode: 'execute' | 'prepare';\n chains: string[];\n}\n/**\n * Operation context for logging\n */\nexport interface OperationContext {\n requestId: string;\n agentId?: string;\n walletId: string;\n operation: string;\n chainIds: string[];\n tokenAddresses?: string[];\n timestamp: number;\n}\n/**\n * Bridge operation parameters\n */\nexport interface BridgeOperation {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n amount: string;\n recipient?: string;\n timeoutMs?: number;\n policyId?: string;\n}\n/**\n * Wallet configuration\n */\nexport interface WalletConfig {\n address: string;\n privateKey?: string;\n}\n/**\n * Policy configuration\n */\nexport interface PolicyConfig {\n /** Maximum USD value for swap inputs */\n maxSwapInputUsd?: number;\n /** Maximum per-token amount for swap inputs */\n maxSwapInputToken?: Record<string, number>;\n /** Maximum per-token amount for bridge operations */\n maxBridgeAmountToken?: Record<string, number>;\n /** Maximum USD value for borrows */\n maxBorrowUsd?: number;\n /** Maximum per-token amount for borrows */\n maxBorrowToken?: Record<string, number>;\n /** Allowed chain IDs for operations */\n allowedChains?: string[];\n /** Allowed tokens per chain */\n allowedTokensByChain?: Record<string, string[]>;\n /** Blocked recipient addresses */\n blockedRecipients?: string[];\n /** Maximum slippage in basis points (100 = 1%) */\n maxSlippageBps?: number;\n /** Whether to require transaction simulation */\n requireSimulation?: boolean;\n}\n//# sourceMappingURL=types.d.ts.map",
609 "inputSchema": {},
610 "outputSchema": null,
611 "icons": null,
612 "annotations": null,
613 "meta": null,
614 "execution": null
615 },
616 {
617 "name": "client.js",
618 "title": null,
619 "description": "Script: client.js. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\nimport { Sodax } from \"@sodax/sdk\";\n// Singleton instance\nlet sodaxClient = null;\n/**\n * HARDCODED PARTNER CONFIGURATION\n * These values are baked in and cannot be overridden.\n *\n * Fee is 0.2% (20 basis points)\n * SDK expects: percentage in bps where 100 = 1%, so 20 = 0.2%\n */\nconst PARTNER_FEE = {\n address: \"0xd99C871c8130B03C8BB597A74fb5EAA7a46864Bb\",\n percentage: 20, // 20 bps = 0.2%\n};\n/**\n * Initialize the SODAX SDK client\n * Always uses dynamic config to fetch live token lists and routes\n */\nasync function initializeSodax() {\n // Initialize SODAX with hardcoded partner fee on ALL services\n const sodax = new Sodax({\n swaps: { partnerFee: PARTNER_FEE },\n moneyMarket: { partnerFee: PARTNER_FEE },\n bridge: { partnerFee: PARTNER_FEE },\n });\n // Suppress SDK console output during initialization\n const originalWarn = console.warn;\n const originalLog = console.log;\n console.warn = () => { };\n console.log = () => { };\n try {\n // Initialize with dynamic config\n await sodax.initialize();\n }\n finally {\n // Restore console\n console.warn = originalWarn;\n console.log = originalLog;\n }\n return sodax;\n}\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport async function getSodaxClientAsync() {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n return sodaxClient;\n}\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport function getSodaxClient() {\n if (!sodaxClient) {\n throw new Error(\"SODAX client not initialized. Call getSodaxClientAsync() first.\");\n }\n return sodaxClient;\n}\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport async function preInitializeSodax() {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n}\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport function resetSodaxClient() {\n sodaxClient = null;\n}\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport class SodaxClient {\n static instance = null;\n static async getClient() {\n if (!SodaxClient.instance) {\n SodaxClient.instance = await initializeSodax();\n sodaxClient = SodaxClient.instance;\n }\n return SodaxClient.instance;\n }\n static reset() {\n SodaxClient.instance = null;\n sodaxClient = null;\n }\n}\n//# sourceMappingURL=client.js.map",
620 "inputSchema": {},
621 "outputSchema": null,
622 "icons": null,
623 "annotations": null,
624 "meta": null,
625 "execution": null
626 },
627 {
628 "name": "client.d.ts",
629 "title": null,
630 "description": "Script: client.d.ts. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\nimport { Sodax } from \"@sodax/sdk\";\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport declare function getSodaxClientAsync(): Promise<Sodax>;\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport declare function getSodaxClient(): Sodax;\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport declare function preInitializeSodax(): Promise<void>;\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport declare function resetSodaxClient(): void;\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport declare class SodaxClient {\n private static instance;\n static getClient(): Promise<Sodax>;\n static reset(): void;\n}\n//# sourceMappingURL=client.d.ts.map",
631 "inputSchema": {},
632 "outputSchema": null,
633 "icons": null,
634 "annotations": null,
635 "meta": null,
636 "execution": null
637 },
638 {
639 "name": "sdk.d.ts",
640 "title": null,
641 "description": "Script: sdk.d.ts. Code:\n/**\n * Mock for @sodax/sdk\n */\nexport declare class Sodax {\n static initialize: jest.Mock<any, any, any>;\n swaps: {\n getQuote: jest.Mock<any, any, any>;\n executeSwap: jest.Mock<any, any, any>;\n cancelIntent: jest.Mock<any, any, any>;\n };\n bridge: {\n getBridgeableTokens: jest.Mock<any, any, any>;\n bridge: jest.Mock<any, any, any>;\n };\n moneyMarket: {\n supply: jest.Mock<any, any, any>;\n withdraw: jest.Mock<any, any, any>;\n borrow: jest.Mock<any, any, any>;\n repay: jest.Mock<any, any, any>;\n getUserAccountDataOnSpoke: jest.Mock<any, any, any>;\n };\n}\nexport declare const Intent: {};\n//# sourceMappingURL=sdk.d.ts.map",
642 "inputSchema": {},
643 "outputSchema": null,
644 "icons": null,
645 "annotations": null,
646 "meta": null,
647 "execution": null
648 },
649 {
650 "name": "sdk.js",
651 "title": null,
652 "description": "Script: sdk.js. Code:\n/**\n * Mock for @sodax/sdk\n */\nexport class Sodax {\n static initialize = jest.fn().mockResolvedValue(undefined);\n swaps = {\n getQuote: jest.fn(),\n executeSwap: jest.fn(),\n cancelIntent: jest.fn(),\n };\n bridge = {\n getBridgeableTokens: jest.fn(),\n bridge: jest.fn(),\n };\n moneyMarket = {\n supply: jest.fn(),\n withdraw: jest.fn(),\n borrow: jest.fn(),\n repay: jest.fn(),\n getUserAccountDataOnSpoke: jest.fn(),\n };\n}\nexport const Intent = {};\n//# sourceMappingURL=sdk.js.map",
653 "inputSchema": {},
654 "outputSchema": null,
655 "icons": null,
656 "annotations": null,
657 "meta": null,
658 "execution": null
659 },
660 {
661 "name": "index.d.ts",
662 "title": null,
663 "description": "Script: index.d.ts. Code:\n/**\n * Amped DeFi Plugin\n *\n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\nimport { TSchema } from '@sinclair/typebox';\n/**\n * OpenClaw Plugin API (defined locally to avoid SDK dependency)\n */\ninterface OpenClawPluginApi {\n pluginConfig: Record<string, unknown>;\n logger: {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n debug: (msg: string) => void;\n };\n registerTool: (tool: {\n name: string;\n description: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: unknown) => Promise<{\n content: Array<{\n type: 'text';\n text: string;\n }>;\n details?: unknown;\n }>;\n }) => void;\n registerService: (service: {\n id: string;\n start: () => void;\n stop: () => Promise<void> | void;\n }) => void;\n on: (event: string, handler: (event: unknown, ctx: unknown) => unknown) => void;\n}\n/**\n * OpenClaw Plugin Definition\n */\ndeclare const _default: {\n id: string;\n name: string;\n description: string;\n kind: \"tools\";\n configSchema: import(\"@sinclair/typebox\").TObject<{\n walletsJson: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n rpcUrlsJson: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n mode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"execute\">, import(\"@sinclair/typebox\").TLiteral<\"simulate\">]>>;\n dynamicConfig: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n }>;\n register(api: OpenClawPluginApi): void;\n};\nexport default _default;\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport type { SpokeProvider } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\nexport type { IWalletBackend, WalletInfo, WalletBackendType } from './wallet/types';\nexport declare function activate(): Promise<void>;\nexport declare function deactivate(): Promise<void>;\n//# sourceMappingURL=index.d.ts.map",
664 "inputSchema": {},
665 "outputSchema": null,
666 "icons": null,
667 "annotations": null,
668 "meta": null,
669 "execution": null
670 },
671 {
672 "name": "moneyMarket.d.ts",
673 "title": null,
674 "description": "Script: moneyMarket.d.ts. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n *\n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n *\n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n *\n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\nimport { Static } from \"@sinclair/typebox\";\nimport { AgentTools } from \"../types\";\n/**\n * Base schema for money market operations\n */\ndeclare const MoneyMarketBaseSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\ndeclare const MoneyMarketSupplySchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n useAsCollateral: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\ndeclare const MoneyMarketWithdrawSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n withdrawType: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"default\">, import(\"@sinclair/typebox\").TLiteral<\"collateral\">, import(\"@sinclair/typebox\").TLiteral<\"all\">]>>;\n}>;\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n *\n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\ndeclare const MoneyMarketBorrowSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n referralCode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\ndeclare const MoneyMarketRepaySchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n repayAll: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n collateralChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\ndeclare const CreateSupplyIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n useAsCollateral: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateBorrowIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n referralCode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateWithdrawIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n dstChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n withdrawType: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"default\">, import(\"@sinclair/typebox\").TLiteral<\"collateral\">, import(\"@sinclair/typebox\").TLiteral<\"all\">]>>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ndeclare const CreateRepayIntentSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TString;\n walletId: import(\"@sinclair/typebox\").TString;\n token: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n interestRateMode: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<1>, import(\"@sinclair/typebox\").TLiteral<2>]>>;\n repayAll: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n collateralChainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n raw: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ninterface MoneyMarketOperationResult {\n success: boolean;\n txHash?: string;\n status: \"success\" | \"pending\" | \"failed\";\n spokeTxHash?: string;\n hubTxHash?: string;\n intentHash?: string;\n operation: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n message?: string;\n warnings?: string[];\n isCrossChain?: boolean;\n srcSpokeTxHash?: string;\n dstSpokeTxHash?: string;\n rawIntent?: unknown;\n}\ninterface IntentResult extends MoneyMarketOperationResult {\n intentData: unknown;\n requiresSubmission: boolean;\n}\n/**\n * Supply tokens to the money market\n *\n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\ndeclare function handleSupply(params: Static<typeof MoneyMarketSupplySchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Withdraw tokens from the money market\n *\n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\ndeclare function handleWithdraw(params: Static<typeof MoneyMarketWithdrawSchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Borrow tokens from the money market\n *\n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n *\n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\ndeclare function handleBorrow(params: Static<typeof MoneyMarketBorrowSchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Repay borrowed tokens to the money market\n *\n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\ndeclare function handleRepay(params: Static<typeof MoneyMarketRepaySchema>): Promise<MoneyMarketOperationResult>;\n/**\n * Create a supply intent without executing (for custom flows)\n */\ndeclare function handleCreateSupplyIntent(params: Static<typeof CreateSupplyIntentSchema>): Promise<IntentResult>;\n/**\n * Create a borrow intent without executing (for custom flows)\n */\ndeclare function handleCreateBorrowIntent(params: Static<typeof CreateBorrowIntentSchema>): Promise<IntentResult>;\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport declare function registerMoneyMarketTools(agentTools: AgentTools): void;\nexport { MoneyMarketBaseSchema, MoneyMarketSupplySchema, MoneyMarketWithdrawSchema, MoneyMarketBorrowSchema, MoneyMarketRepaySchema, CreateSupplyIntentSchema, CreateWithdrawIntentSchema, CreateBorrowIntentSchema, CreateRepayIntentSchema, handleSupply, handleWithdraw, handleBorrow, handleRepay, handleCreateSupplyIntent, handleCreateBorrowIntent, };\nexport { MoneyMarketSupplySchema as MmSupplySchema, MoneyMarketWithdrawSchema as MmWithdrawSchema, MoneyMarketBorrowSchema as MmBorrowSchema, MoneyMarketRepaySchema as MmRepaySchema, handleSupply as handleMmSupply, handleWithdraw as handleMmWithdraw, handleBorrow as handleMmBorrow, handleRepay as handleMmRepay, };\nexport type { MoneyMarketOperationResult, IntentResult };\n//# sourceMappingURL=moneyMarket.d.ts.map",
675 "inputSchema": {},
676 "outputSchema": null,
677 "icons": null,
678 "annotations": null,
679 "meta": null,
680 "execution": null
681 },
682 {
683 "name": "bridge.d.ts",
684 "title": null,
685 "description": "Script: bridge.d.ts. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts\n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\nimport { Static } from '@sinclair/typebox';\nimport { AgentTools } from '../types';\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\ndeclare const BridgeDiscoverSchema: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\ndeclare const BridgeQuoteSchema: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\ndeclare const BridgeExecuteSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ntype BridgeDiscoverParams = Static<typeof BridgeDiscoverSchema>;\ntype BridgeQuoteParams = Static<typeof BridgeQuoteSchema>;\ntype BridgeExecuteParams = Static<typeof BridgeExecuteSchema>;\n/**\n * Transaction result type for bridge execute\n */\ninterface TransactionResult {\n spokeTxHash: string;\n hubTxHash?: string;\n}\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\ndeclare function handleBridgeDiscover(params: BridgeDiscoverParams): Promise<{\n bridgeableTokens: string[];\n}>;\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\ndeclare function handleBridgeQuote(params: BridgeQuoteParams): Promise<{\n isBridgeable: boolean;\n maxBridgeableAmount: string;\n}>;\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\ndeclare function handleBridgeExecute(params: BridgeExecuteParams): Promise<TransactionResult>;\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport declare function registerBridgeTools(agentTools: AgentTools): void;\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n//# sourceMappingURL=bridge.d.ts.map",
686 "inputSchema": {},
687 "outputSchema": null,
688 "icons": null,
689 "annotations": null,
690 "meta": null,
691 "execution": null
692 },
693 {
694 "name": "discovery.js",
695 "title": null,
696 "description": "Script: discovery.js. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus, getPositionRecommendation } from '../utils/positionAggregator';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_supported_chains - no parameters required\n */\nconst SupportedChainsSchema = Type.Object({});\n/**\n * Schema for amped_supported_tokens\n */\nconst SupportedTokensSchema = Type.Object({\n module: Type.Union([\n Type.Literal('swaps'),\n Type.Literal('bridge'),\n Type.Literal('moneyMarket'),\n ]),\n chainId: Type.String({\n description: 'Spoke chain ID (e.g., \"ethereum\", \"arbitrum\", \"sonic\")',\n }),\n});\n/**\n * Schema for amped_wallet_address\n */\nconst WalletAddressSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n});\n/**\n * Schema for amped_money_market_positions\n */\nconst MoneyMarketPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainId: Type.String({\n description: 'Spoke chain ID to query positions on',\n }),\n});\n/**\n * Schema for amped_money_market_reserves\n */\nconst MoneyMarketReservesSchema = Type.Object({\n chainId: Type.Optional(Type.String({\n description: 'Optional chain ID. Money market is hub-centric, so this filters results for a specific spoke chain if needed',\n })),\n});\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\nconst CrossChainPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainIds: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional array of specific chain IDs to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include positions with zero balance',\n default: false,\n })),\n minUsdValue: Type.Optional(Type.Number({\n description: 'Minimum USD value threshold for including positions',\n default: 0.01,\n })),\n});\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\nconst UserIntentsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n status: Type.Optional(Type.Union([\n Type.Literal('all', { description: 'All intents (open and closed)' }),\n Type.Literal('open', { description: 'Only open/pending intents' }),\n Type.Literal('closed', { description: 'Only filled/cancelled/expired intents' }),\n ], {\n description: 'Filter by intent status',\n default: 'all',\n })),\n limit: Type.Optional(Type.Number({\n description: 'Number of items to return (default: 50, max: 100)',\n default: 50,\n minimum: 1,\n maximum: 100,\n })),\n offset: Type.Optional(Type.Number({\n description: 'Number of items to skip (for pagination)',\n default: 0,\n minimum: 0,\n })),\n});\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\nconst ListWalletsSchema = Type.Object({});\n// Helper to wrap typed handlers for AgentTools registration\nfunction wrapHandler(handler) {\n return (params) => handler(params);\n}\n// ============================================================================\n// Tool Implementations\n// ============================================================================\n/**\n * Get supported spoke chains from SODAX configuration\n */\nasync function handleSupportedChains(_params) {\n const sodax = getSodaxClient();\n const chains = sodax.config.getSupportedSpokeChains();\n // SDK may return chain IDs as strings or chain objects\n return {\n success: true,\n chains: chains.map((chain) => {\n // Handle both string IDs and chain objects\n if (typeof chain === 'string') {\n return {\n id: chain,\n name: chain,\n type: 'evm',\n isHub: chain === 'sonic',\n nativeCurrency: undefined,\n };\n }\n return {\n id: chain.id || chain,\n name: chain.name || chain.id || chain,\n type: chain.type || 'evm',\n isHub: (chain.id || chain) === 'sonic',\n nativeCurrency: chain.nativeCurrency,\n };\n }),\n };\n}\n/**\n * Get supported tokens for a specific module and chain\n */\nasync function handleSupportedTokens(params) {\n const sodax = getSodaxClient();\n const { module, chainId: rawChainId } = params;\n const chainId = toSodaxChainId(rawChainId);\n let tokens = [];\n // Helper to normalize token data\n const normalizeToken = (token) => ({\n address: token.address || '',\n symbol: token.symbol || '',\n name: token.name || token.symbol || '',\n decimals: token.decimals || 18,\n logoURI: token.logoURI || token.logoUri,\n });\n switch (module) {\n case 'swaps': {\n // Get supported swap tokens by chain ID\n // SDK may require chainId to be cast to specific type\n try {\n const swapTokens = sodax.config.getSupportedSwapTokensByChainId(chainId);\n tokens = (swapTokens || []).map(normalizeToken);\n }\n catch (e) {\n console.warn('[discovery] Failed to get swap tokens:', e);\n tokens = [];\n }\n break;\n }\n case 'bridge': {\n // Get bridgeable tokens via hub assets\n // Hub assets represent tokens that can be bridged between chains\n // Reference: sodax-frontend uses getHubAssets() for bridge token discovery\n try {\n const hubAssets = sodax.config.getHubAssets();\n // Check if this is the hub chain (Sonic)\n const isHubChain = rawChainId === 'sonic' || chainId === 'sonic';\n if (isHubChain) {\n // For Sonic (hub), show all bridgeable assets from all spoke chains\n // These are the assets that can be bridged FROM Sonic to other chains\n const allTokens = [];\n const seenAddresses = new Set();\n for (const spokeChainId of Object.keys(hubAssets)) {\n const chainAssets = hubAssets[spokeChainId] || {};\n for (const asset of Object.values(chainAssets)) {\n // Add the hub asset (on Sonic) - dedupe by hub address\n const hubAddress = asset.asset || asset.hubAddress || asset.address;\n if (hubAddress && !seenAddresses.has(hubAddress.toLowerCase())) {\n seenAddresses.add(hubAddress.toLowerCase());\n allTokens.push(normalizeToken({\n address: hubAddress,\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: asset.decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n }\n }\n tokens = allTokens;\n }\n else {\n // For spoke chains, get assets bridgeable from that specific chain\n const chainAssets = hubAssets[chainId] || {};\n tokens = Object.values(chainAssets).map((asset) => normalizeToken({\n address: asset.asset || asset.address || asset.originalAddress || '',\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: asset.decimal || asset.decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n }\n catch (e) {\n console.warn('[discovery] Failed to get bridge tokens:', e);\n tokens = [];\n }\n break;\n }\n case 'moneyMarket': {\n // Get money market supported tokens from config\n // Reference: sodax-frontend ConfigService.getSupportedMoneyMarketTokensByChainId\n try {\n const mmTokens = sodax.config.getSupportedMoneyMarketTokensByChainId?.(chainId);\n if (mmTokens && Array.isArray(mmTokens)) {\n tokens = mmTokens.map(normalizeToken);\n }\n else {\n // Fallback: try supportedMoneyMarketTokens directly from config\n const allMmTokens = sodax.config.sodaxConfig?.supportedMoneyMarketTokens;\n if (allMmTokens && allMmTokens[chainId]) {\n tokens = allMmTokens[chainId].map(normalizeToken);\n }\n else {\n console.warn('[discovery] No money market tokens found for chain', chainId);\n tokens = [];\n }\n }\n }\n catch (e) {\n console.warn('[discovery] Failed to get money market tokens:', e);\n tokens = [];\n }\n break;\n }\n default:\n throw new Error(`Unknown module: ${module}`);\n }\n return {\n success: true,\n module,\n chainId,\n tokens,\n count: tokens.length,\n };\n}\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\nasync function handleWalletAddress(params) {\n const { walletId } = params;\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const address = await wallet.getAddress();\n return {\n success: true,\n walletId: wallet.nickname,\n address,\n type: wallet.type,\n chains: [...wallet.supportedChains],\n };\n}\n/**\n * Get user money market positions (humanized format)\n */\nasync function handleMoneyMarketPositions(params) {\n const { walletId, chainId } = params;\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Get spoke provider for this wallet and chain\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n // IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n // To get token symbols/names, we must:\n // 1. Fetch getReservesHumanized() for token metadata\n // 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n // 3. Join with formatUserSummary(buildUserSummaryRequest())\n // Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(sodax.moneyMarket.data.buildReserveDataWithPrice(reserves));\n // Step 3: Fetch user-specific balances\n const userReservesResult = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReservesResult));\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = userSummary.userReservesData || [];\n // Format positions for readability\n const positions = userReservesData.map((reserve) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n return {\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n apy: supplyApy,\n collateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n apy: borrowApy,\n },\n // Health indicators\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n // Filter to only positions with activity\n const activePositions = positions.filter((p) => parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0);\n // Calculate summary metrics\n const totalSupplyUsd = activePositions.reduce((sum, p) => sum + (parseFloat(p.supply.balanceUsd) || 0), 0);\n const totalBorrowUsd = activePositions.reduce((sum, p) => sum + (parseFloat(p.borrow.balanceUsd) || 0), 0);\n const netWorthUsd = totalSupplyUsd - totalBorrowUsd;\n const healthFactor = totalBorrowUsd > 0 ? totalSupplyUsd / totalBorrowUsd : Infinity;\n return {\n success: true,\n walletId,\n address: walletAddress,\n chainId,\n positions: activePositions,\n summary: {\n totalSupplyUsd: totalSupplyUsd.toFixed(2),\n totalBorrowUsd: totalBorrowUsd.toFixed(2),\n netWorthUsd: netWorthUsd.toFixed(2),\n healthFactor: healthFactor === Infinity ? '\u221e' : healthFactor.toFixed(2),\n positionCount: activePositions.length,\n },\n };\n}\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\nasync function handleMoneyMarketReserves(params) {\n const { chainId } = params;\n const sodax = getSodaxClient();\n // Get reserves in humanized format (hub-centric)\n const reservesResult = await sodax.moneyMarket.data.getReservesHumanized();\n // SDK may return ReservesDataHumanized object with .reservesData array or just array\n const reservesArray = Array.isArray(reservesResult)\n ? reservesResult\n : reservesResult.reservesData || [];\n // Filter by chainId if provided\n let filteredReserves = reservesArray;\n if (chainId) {\n filteredReserves = reservesArray.filter((r) => r.token?.chainId === chainId || r.hubChainId === chainId || r.chainId === chainId);\n }\n // Format reserves for readability\n const formattedReserves = filteredReserves.map((reserve) => ({\n token: {\n address: reserve.token?.address || reserve.underlyingAsset || '',\n symbol: reserve.token?.symbol || reserve.symbol || '',\n name: reserve.token?.name || reserve.name || '',\n decimals: reserve.token?.decimals || reserve.decimals || 18,\n chainId: reserve.token?.chainId || reserve.chainId || '',\n },\n liquidity: {\n totalSupply: reserve.liquidity?.totalSupply || reserve.totalScaledVariableDebt || '0',\n availableLiquidity: reserve.liquidity?.availableLiquidity || reserve.availableLiquidity || '0',\n totalBorrow: reserve.liquidity?.totalBorrow || reserve.totalVariableDebt || '0',\n utilizationRate: reserve.liquidity?.utilizationRate || reserve.utilizationRate || '0',\n },\n rates: {\n supplyApy: reserve.rates?.supplyApy || reserve.supplyAPY || '0',\n borrowApy: reserve.rates?.borrowApy || reserve.variableBorrowAPY || '0',\n },\n parameters: {\n loanToValue: reserve.parameters?.loanToValue || reserve.baseLTVasCollateral || '0',\n liquidationThreshold: reserve.parameters?.liquidationThreshold || reserve.reserveLiquidationThreshold || '0',\n liquidationBonus: reserve.parameters?.liquidationBonus || reserve.reserveLiquidationBonus || '0',\n },\n hubChainId: reserve.hubChainId || 'sonic',\n }));\n // Calculate aggregate metrics\n const totalAvailableLiquidity = formattedReserves.reduce((sum, r) => sum + (parseFloat(r.liquidity.availableLiquidity) || 0), 0);\n const totalBorrowed = formattedReserves.reduce((sum, r) => sum + (parseFloat(r.liquidity.totalBorrow) || 0), 0);\n return {\n success: true,\n chainId: chainId || 'all',\n reserves: formattedReserves,\n summary: {\n reserveCount: formattedReserves.length,\n totalAvailableLiquidity: totalAvailableLiquidity.toFixed(2),\n totalBorrowed: totalBorrowed.toFixed(2),\n globalUtilizationRate: totalAvailableLiquidity + totalBorrowed > 0\n ? ((totalBorrowed / (totalAvailableLiquidity + totalBorrowed)) *\n 100).toFixed(2) + '%'\n : '0%',\n },\n };\n}\n// ============================================================================\n// Cross-Chain Positions Tool\n// ============================================================================\n/**\n * Get aggregated money market positions across all chains\n *\n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\nasync function handleCrossChainPositions(params) {\n const { walletId, chainIds, includeZeroBalances, minUsdValue } = params;\n console.log('[discovery:crossChainPositions] Aggregating positions', {\n walletId,\n chainIds: chainIds || 'all',\n includeZeroBalances,\n minUsdValue,\n });\n try {\n const view = await aggregateCrossChainPositions(walletId, {\n chainIds,\n includeZeroBalances,\n minUsdValue,\n });\n // Get recommendations\n const recommendations = getPositionRecommendation(view);\n // Format response\n const response = {\n success: true,\n walletId: view.walletId,\n address: view.address,\n timestamp: view.timestamp,\n summary: {\n totalSupplyUsd: view.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: view.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: view.summary.netWorthUsd.toFixed(2),\n availableBorrowUsd: view.summary.availableBorrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(view.summary.healthFactor),\n healthFactorStatus: getHealthFactorStatus(view.summary.healthFactor),\n liquidationRisk: view.summary.liquidationRisk,\n weightedSupplyApy: `${(view.summary.weightedSupplyApy * 100).toFixed(2)}%`,\n weightedBorrowApy: `${(view.summary.weightedBorrowApy * 100).toFixed(2)}%`,\n netApy: `${(view.summary.netApy * 100).toFixed(2)}%`,\n },\n chainBreakdown: view.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n netWorthUsd: cs.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n positionCount: cs.positionCount,\n })),\n collateralUtilization: {\n totalCollateralUsd: view.collateralUtilization.totalCollateralUsd.toFixed(2),\n usedCollateralUsd: view.collateralUtilization.usedCollateralUsd.toFixed(2),\n availableCollateralUsd: view.collateralUtilization.availableCollateralUsd.toFixed(2),\n utilizationRate: `${view.collateralUtilization.utilizationRate.toFixed(2)}%`,\n },\n riskMetrics: {\n maxLtv: `${(view.riskMetrics.maxLtv * 100).toFixed(2)}%`,\n currentLtv: `${(view.riskMetrics.currentLtv * 100).toFixed(2)}%`,\n bufferUntilLiquidation: `${view.riskMetrics.bufferUntilLiquidation.toFixed(2)}%`,\n safeMaxBorrowUsd: view.riskMetrics.safeMaxBorrowUsd.toFixed(2),\n },\n positions: view.positions.map(pos => ({\n chainId: pos.chainId,\n token: pos.token,\n supply: {\n balance: pos.supply.balance,\n balanceUsd: pos.supply.balanceUsd,\n apy: `${(pos.supply.apy * 100).toFixed(2)}%`,\n isCollateral: pos.supply.isCollateral,\n },\n borrow: {\n balance: pos.borrow.balance,\n balanceUsd: pos.borrow.balanceUsd,\n apy: `${(pos.borrow.apy * 100).toFixed(2)}%`,\n },\n loanToValue: `${(pos.loanToValue * 100).toFixed(2)}%`,\n liquidationThreshold: `${(pos.liquidationThreshold * 100).toFixed(2)}%`,\n })),\n recommendations,\n };\n console.log('[discovery:crossChainPositions] Aggregation complete', {\n totalPositions: view.positions.length,\n totalSupplyUsd: view.summary.totalSupplyUsd,\n totalBorrowUsd: view.summary.totalBorrowUsd,\n healthFactor: view.summary.healthFactor,\n });\n return response;\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:crossChainPositions] Failed to aggregate positions', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to aggregate cross-chain positions: ${errorMessage}`);\n }\n}\n// ============================================================================\n// User Intents Tool (SODAX API)\n// ============================================================================\n/**\n * Get user intents from SODAX API\n *\n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\nasync function handleUserIntents(params) {\n const { walletId, status = 'all', limit = 50, offset = 0 } = params;\n console.log('[discovery:userIntents] Fetching user intents', {\n walletId,\n status,\n limit,\n offset,\n });\n try {\n // Get wallet address from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n // Initialize API client\n const apiClient = getSodaxApiClient();\n // Determine filters based on status\n let filters;\n if (status === 'open') {\n filters = { open: true };\n }\n else if (status === 'closed') {\n filters = { open: false };\n }\n // Fetch intents\n const response = await apiClient.getUserIntents(walletAddress, { limit, offset }, filters);\n // Format response\n const formattedIntents = response.items.map(intent => ({\n intentHash: intent.intentHash,\n txHash: intent.txHash,\n chainId: intent.chainId,\n blockNumber: intent.blockNumber,\n status: intent.open ? 'open' : 'closed',\n createdAt: intent.createdAt,\n input: {\n token: intent.intent.inputToken,\n amount: intent.intent.inputAmount,\n chainId: intent.intent.srcChain,\n },\n output: {\n token: intent.intent.outputToken,\n minAmount: intent.intent.minOutputAmount,\n chainId: intent.intent.dstChain,\n },\n deadline: new Date(parseInt(intent.intent.deadline) * 1000).toISOString(),\n allowPartialFill: intent.intent.allowPartialFill,\n events: intent.events\n .filter((event) => event.intentState != null)\n .map(event => ({\n type: event.eventType,\n txHash: event.txHash,\n blockNumber: event.blockNumber,\n state: {\n remainingInput: event.intentState.remainingInput,\n receivedOutput: event.intentState.receivedOutput,\n pendingPayment: event.intentState.pendingPayment,\n },\n })),\n }));\n const result = {\n success: true,\n walletId,\n address: walletAddress,\n pagination: {\n total: response.total,\n offset: response.offset,\n limit: response.limit,\n hasMore: response.offset + response.items.length < response.total,\n },\n intents: formattedIntents,\n summary: {\n totalIntents: response.total,\n returned: formattedIntents.length,\n openIntents: formattedIntents.filter((i) => i.status === 'open').length,\n closedIntents: formattedIntents.filter((i) => i.status === 'closed').length,\n },\n };\n console.log('[discovery:userIntents] User intents fetched', {\n total: response.total,\n returned: formattedIntents.length,\n });\n return result;\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:userIntents] Failed to fetch user intents', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to fetch user intents: ${errorMessage}`);\n }\n}\n// ============================================================================\n// List Wallets Tool\n// ============================================================================\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\nasync function handleListWallets(_params) {\n console.log('[discovery:listWallets] Listing configured wallets');\n const walletManager = getWalletManager();\n const wallets = await walletManager.listWallets();\n const formattedWallets = wallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n address: w.address,\n addressKnown: w.address !== '0x...',\n supportedChains: w.chains,\n isDefault: w.isDefault,\n note: w.address === '0x...' && w.type === 'bankr'\n ? 'Address pending - will be fetched on first use'\n : undefined,\n }));\n const defaultWallet = await walletManager.getDefaultWalletName();\n // Group by type for summary\n const byType = {\n 'evm-wallet-skill': formattedWallets.filter(w => w.type === 'evm-wallet-skill'),\n 'bankr': formattedWallets.filter(w => w.type === 'bankr'),\n 'env': formattedWallets.filter(w => w.type === 'env'),\n };\n // Check if Bankr is configured but wallet not found\n const bankrKeyPresent = !!process.env.BANKR_API_KEY;\n const bankrWalletFound = byType.bankr.length > 0;\n return {\n success: true,\n wallets: formattedWallets,\n defaultWallet,\n count: formattedWallets.length,\n summary: {\n selfCustody: byType['evm-wallet-skill'].length + byType.env.length,\n bankrManaged: byType.bankr.length,\n },\n sources: {\n evmWalletSkill: byType['evm-wallet-skill'].length > 0,\n bankr: bankrWalletFound,\n bankrKeyConfigured: bankrKeyPresent,\n env: byType.env.length > 0,\n },\n hint: wallets.length === 0\n ? 'No wallets configured. Install evm-wallet-skill: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill'\n : bankrKeyPresent && !bankrWalletFound\n ? 'Bankr API key found but wallet not loaded. Try \"Add my bankr wallet\" to register it.'\n : 'Use wallet nickname in operations, e.g., \"swap 100 USDC to ETH using main\"',\n };\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport function registerDiscoveryTools(agentTools) {\n // 1. amped_supported_chains - Get supported spoke chains\n agentTools.register({\n name: 'amped_supported_chains',\n summary: 'Get a list of all supported spoke chains for swaps, bridging, and money market operations',\n schema: SupportedChainsSchema,\n handler: wrapHandler(handleSupportedChains),\n });\n // 2. amped_supported_tokens - Get supported tokens by module\n agentTools.register({\n name: 'amped_supported_tokens',\n summary: 'Get supported tokens for a specific module (swaps, bridge, or moneyMarket) on a given chain',\n schema: SupportedTokensSchema,\n handler: wrapHandler(handleSupportedTokens),\n });\n // 3. amped_wallet_address - Get wallet address\n agentTools.register({\n name: 'amped_wallet_address',\n summary: 'Get the resolved wallet address for a given walletId. Validates private key matches in execute mode.',\n schema: WalletAddressSchema,\n handler: wrapHandler(handleWalletAddress),\n });\n // 4. amped_money_market_positions - Get user positions (humanized)\n agentTools.register({\n name: 'amped_money_market_positions',\n summary: 'Get humanized money market positions for a wallet on a specific chain, including supply/borrow balances and health metrics',\n schema: MoneyMarketPositionsSchema,\n handler: wrapHandler(handleMoneyMarketPositions),\n });\n // 5. amped_money_market_reserves - Get market reserves (humanized)\n agentTools.register({\n name: 'amped_money_market_reserves',\n summary: 'Get humanized money market reserves data including liquidity, rates, and parameters. Hub-centric with optional chain filtering.',\n schema: MoneyMarketReservesSchema,\n handler: wrapHandler(handleMoneyMarketReserves),\n });\n // 6. amped_cross_chain_positions - Get aggregated positions across all chains\n agentTools.register({\n name: 'amped_cross_chain_positions',\n summary: 'Get a unified view of money market positions across ALL chains. Shows total supply/borrow, health factor, borrowing power, net APY, and risk metrics.',\n description: 'Aggregates money market positions across all supported chains to provide a comprehensive portfolio view. ' +\n 'Includes: total supply/borrow in USD, health factor with risk status, available borrowing power, ' +\n 'weighted APYs, collateral utilization, and personalized recommendations. ' +\n 'This is the recommended tool for getting a complete picture of money market positions.',\n schema: CrossChainPositionsSchema,\n handler: wrapHandler(handleCrossChainPositions),\n });\n // 7. amped_user_intents - Query user intent history from SODAX API\n agentTools.register({\n name: 'amped_user_intents',\n summary: 'Query user swap intent history from SODAX backend API. Shows open, filled, and cancelled intents with event details.',\n description: 'Retrieves the complete intent history for a wallet from the SODAX backend API. ' +\n 'Includes open intents (pending), filled intents (completed), and cancelled/expired intents. ' +\n 'Each intent includes input/output tokens, amounts, chain IDs, and event history. ' +\n 'Use this to track the status of past swaps and bridge operations.',\n schema: UserIntentsSchema,\n handler: wrapHandler(handleUserIntents),\n });\n // 8. amped_list_wallets - List all configured wallets\n agentTools.register({\n name: 'amped_list_wallets',\n summary: 'List all configured wallets with their nicknames, types, addresses, and supported chains.',\n description: 'Shows all available wallets from evm-wallet-skill (~/.evm-wallet.json), Bankr API, ' +\n 'and environment variables (AMPED_OC_WALLETS_JSON). Each wallet has a nickname that can be ' +\n 'used in operations like \"swap 100 USDC using bankr\" or \"check balance on main\". ' +\n 'Also shows which chains each wallet supports.',\n schema: ListWalletsSchema,\n handler: wrapHandler(handleListWallets),\n });\n}\n// Export schemas for testing and reuse\nexport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketPositionsSchema, MoneyMarketReservesSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, };\n// Export handlers for testing\nexport { handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketPositions, handleMoneyMarketReserves, handleCrossChainPositions, handleUserIntents, handleListWallets, };\n//# sourceMappingURL=discovery.js.map",
697 "inputSchema": {},
698 "outputSchema": null,
699 "icons": null,
700 "annotations": null,
701 "meta": null,
702 "execution": null
703 },
704 {
705 "name": "bridge.js",
706 "title": null,
707 "description": "Script: bridge.js. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts\n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\nimport { handleSwapQuote, handleSwapExecute } from './swap';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\nconst BridgeDiscoverSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID (e.g., \"ethereum\", \"arbitrum\")',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID (e.g., \"sonic\", \"optimism\")',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n});\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\nconst BridgeQuoteSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol',\n }),\n});\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\nconst BridgeExecuteSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet to use',\n }),\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol to bridge from',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol to bridge to',\n }),\n amount: Type.String({\n description: 'Amount to bridge in human-readable units (e.g., \"100.5\")',\n }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain (defaults to wallet address)',\n })),\n timeoutMs: Type.Optional(Type.Number({\n description: 'Timeout for bridge operation in milliseconds',\n default: 300000, // 5 minutes\n })),\n policyId: Type.Optional(Type.String({\n description: 'Optional policy profile ID for custom limits',\n })),\n});\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\nasync function handleBridgeDiscover(params) {\n const { srcChainId, dstChainId, srcToken } = params;\n // Resolve token symbol to address\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n console.log('[bridge:discover] Discovering bridgeable tokens', {\n srcChainId,\n dstChainId,\n srcToken,\n });\n try {\n const sodax = getSodaxClient();\n // Get bridgeable tokens from SODAX SDK\n // SDK API: getBridgeableTokens(from: SpokeChainId, to: SpokeChainId, token: string)\n const result = sodax.bridge.getBridgeableTokens(toSodaxChainId(srcChainId), toSodaxChainId(dstChainId), srcTokenAddr);\n // Handle Result type - SDK returns Result<XToken[], unknown>\n if (!result.ok) {\n throw new Error(`Failed to get bridgeable tokens: ${serializeError(result.error) || 'Unknown error'}`);\n }\n const tokens = result.value;\n const bridgeableTokens = tokens.map((t) => t.address || t.symbol || String(t));\n console.log('[bridge:discover] Found bridgeable tokens', {\n count: bridgeableTokens.length,\n tokens: bridgeableTokens,\n });\n return { bridgeableTokens };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:discover] Failed to discover bridgeable tokens', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n });\n throw new Error(`Failed to discover bridgeable tokens: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Bridge Quote Tool\n// ============================================================================\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\nasync function handleBridgeQuote(params) {\n const { srcChainId, dstChainId, srcToken, dstToken } = params;\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n const dstTokenAddr = await resolveToken(dstChainId, dstToken);\n console.log('[bridge:quote] Checking bridge quote', {\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n try {\n const sodax = getSodaxClient();\n // Create XToken objects for the SDK\n const fromToken = { chainId: toSodaxChainId(srcChainId), address: srcTokenAddr };\n const toToken = { chainId: toSodaxChainId(dstChainId), address: dstTokenAddr };\n // Check if the route is bridgeable using isBridgeable\n // SDK may have different signature - adapting based on available methods\n let isBridgeable = false;\n try {\n // Try to get bridgeable tokens to check if route exists\n const result = sodax.bridge.getBridgeableTokens(toSodaxChainId(srcChainId), toSodaxChainId(dstChainId), srcTokenAddr);\n if (result.ok && result.value.length > 0) {\n isBridgeable = result.value.some((t) => t.address?.toLowerCase() === dstTokenAddr.toLowerCase() ||\n t === dstTokenAddr);\n }\n }\n catch {\n isBridgeable = false;\n }\n // Get maximum bridgeable amount\n let maxBridgeableAmount = '0';\n if (isBridgeable) {\n try {\n // SDK API: getBridgeableAmount(from: XToken, to: XToken)\n const maxAmountResult = await sodax.bridge.getBridgeableAmount(fromToken, toToken);\n if (maxAmountResult.ok) {\n const val = maxAmountResult.value;\n // BridgeLimit may have different property names depending on SDK version\n maxBridgeableAmount = val?.max?.toString() ||\n val?.maxAmount?.toString() ||\n val?.limit?.toString() ||\n val?.toString() || '0';\n }\n }\n catch (e) {\n console.warn('[bridge:quote] Could not get max bridgeable amount:', e);\n }\n }\n console.log('[bridge:quote] Bridge quote result', {\n isBridgeable,\n maxBridgeableAmount,\n });\n return { isBridgeable, maxBridgeableAmount };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:quote] Failed to get bridge quote', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n throw new Error(`Failed to get bridge quote: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Bridge Execute Tool (Delegates to Swap)\n// ============================================================================\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\nasync function handleBridgeExecute(params) {\n const { walletId, srcChainId, dstChainId, srcToken, dstToken, amount, recipient, timeoutMs = 300000, policyId, } = params;\n console.log('[bridge:execute] Delegating to swap infrastructure', {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n });\n try {\n // Step 1: Get a swap quote for this bridge route\n const quoteResult = await handleSwapQuote({\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n type: 'exact_input',\n slippageBps: 100, // 1% slippage for bridges\n recipient,\n });\n console.log('[bridge:execute] Got swap quote', quoteResult);\n // Step 2: Execute the swap\n const swapResult = await handleSwapExecute({\n walletId,\n quote: {\n srcChainId,\n dstChainId,\n srcToken: String(quoteResult.srcToken),\n dstToken: String(quoteResult.dstToken),\n inputAmount: String(quoteResult.inputAmount),\n outputAmount: String(quoteResult.outputAmount),\n slippageBps: Number(quoteResult.slippageBps),\n deadline: Number(quoteResult.deadline),\n recipient,\n },\n policyId,\n timeoutMs,\n });\n console.log('[bridge:execute] Swap executed', swapResult);\n // Map swap result to bridge result format\n return {\n spokeTxHash: String(swapResult.initiationTx || swapResult.spokeTxHash || ''),\n hubTxHash: swapResult.hubTxHash ? String(swapResult.hubTxHash) : undefined,\n status: String(swapResult.status),\n message: swapResult.message ? String(swapResult.message) : 'Bridge executed via swap infrastructure',\n sodaxScanUrl: swapResult.sodaxScanUrl ? String(swapResult.sodaxScanUrl) : undefined,\n };\n }\n catch (error) {\n const errorMessage = serializeError(error);\n console.error('[bridge:execute] Bridge via swap failed:', errorMessage);\n throw new Error(`Bridge execution failed: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport function registerBridgeTools(agentTools) {\n // Register bridge discover tool\n agentTools.register({\n name: 'amped_bridge_discover',\n summary: 'Discover bridgeable tokens for a given source chain and token',\n description: 'Retrieves a list of tokens that can be bridged from the specified source chain ' +\n 'to the destination chain, starting from a specific source token. ' +\n 'Use this to find valid bridge routes before requesting a quote.',\n schema: BridgeDiscoverSchema,\n handler: handleBridgeDiscover,\n });\n console.log('[bridge] Registered tool: amped_bridge_discover');\n // Register bridge quote tool\n agentTools.register({\n name: 'amped_bridge_quote',\n summary: 'Check bridgeability and get maximum bridgeable amount',\n description: 'Validates whether a specific bridge route (source chain/token \u2192 destination chain/token) ' +\n 'is supported and returns the maximum amount that can be bridged. ' +\n 'Always call this before executing a bridge to verify the route is valid.',\n schema: BridgeQuoteSchema,\n handler: handleBridgeQuote,\n });\n console.log('[bridge] Registered tool: amped_bridge_quote');\n // Register bridge execute tool\n agentTools.register({\n name: 'amped_bridge_execute',\n summary: 'Execute a cross-chain bridge operation',\n description: 'Executes a bridge operation that moves tokens from a source chain to a destination chain. ' +\n 'This tool handles the complete flow: policy validation, allowance checking, ' +\n 'token approval (if needed), and bridge execution. ' +\n 'Returns transaction hashes for both the spoke chain and hub chain.',\n schema: BridgeExecuteSchema,\n handler: handleBridgeExecute,\n });\n console.log('[bridge] Registered tool: amped_bridge_execute');\n}\n// Export schemas for testing and reuse\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\n// Export handlers\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n//# sourceMappingURL=bridge.js.map",
708 "inputSchema": {},
709 "outputSchema": null,
710 "icons": null,
711 "annotations": null,
712 "meta": null,
713 "execution": null
714 },
715 {
716 "name": "walletManagement.d.ts",
717 "title": null,
718 "description": "Script: walletManagement.d.ts. Code:\n/**\n * Wallet Management Tools\n *\n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n *\n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_add_wallet\n */\ndeclare const AddWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n source: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"evm-wallet-skill\">, import(\"@sinclair/typebox\").TLiteral<\"bankr\">, import(\"@sinclair/typebox\").TLiteral<\"env\">]>;\n path: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n apiKey: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n apiUrl: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n address: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n privateKey: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n chains: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n}>;\n/**\n * Schema for amped_rename_wallet\n */\ndeclare const RenameWalletSchema: import(\"@sinclair/typebox\").TObject<{\n currentNickname: import(\"@sinclair/typebox\").TString;\n newNickname: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_remove_wallet\n */\ndeclare const RemoveWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n confirm: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\n/**\n * Schema for amped_set_default_wallet\n */\ndeclare const SetDefaultWalletSchema: import(\"@sinclair/typebox\").TObject<{\n nickname: import(\"@sinclair/typebox\").TString;\n}>;\ntype AddWalletParams = Static<typeof AddWalletSchema>;\ntype RenameWalletParams = Static<typeof RenameWalletSchema>;\ntype RemoveWalletParams = Static<typeof RemoveWalletSchema>;\ntype SetDefaultWalletParams = Static<typeof SetDefaultWalletSchema>;\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n/**\n * Add a new wallet with a nickname\n */\ndeclare function handleAddWallet(params: AddWalletParams): Promise<unknown>;\n/**\n * Rename a wallet\n */\ndeclare function handleRenameWallet(params: RenameWalletParams): Promise<unknown>;\n/**\n * Remove a wallet\n */\ndeclare function handleRemoveWallet(params: RemoveWalletParams): Promise<unknown>;\n/**\n * Set default wallet\n */\ndeclare function handleSetDefaultWallet(params: SetDefaultWalletParams): Promise<unknown>;\n/**\n * Register wallet management tools\n */\nexport declare function registerWalletManagementTools(agentTools: AgentTools): void;\nexport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet, };\n//# sourceMappingURL=walletManagement.d.ts.map",
719 "inputSchema": {},
720 "outputSchema": null,
721 "icons": null,
722 "annotations": null,
723 "meta": null,
724 "execution": null
725 },
726 {
727 "name": "portfolio.js",
728 "title": null,
729 "description": "Script: portfolio.js. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\nimport { Type } from '@sinclair/typebox';\nimport { createPublicClient, http, formatUnits } from 'viem';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus } from '../utils/positionAggregator';\nimport { fetchTokenPrices } from '../utils/priceService';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId, CHAIN_IDS, } from '../wallet/providers/chainConfig';\n// ============================================================================\n// TypeBox Schema\n// ============================================================================\n/**\n * Schema for amped_portfolio_summary\n */\nexport const PortfolioSummarySchema = Type.Object({\n walletId: Type.Optional(Type.String({\n description: 'Specific wallet to query (defaults to all wallets)',\n })),\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Specific chains to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include tokens with zero balance',\n default: false,\n })),\n});\n/**\n * Major tokens to check on each chain\n */\nconst MAJOR_TOKENS = {\n [CHAIN_IDS.ETHEREUM]: [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 },\n { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BASE]: [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.ARBITRUM]: [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', decimals: 6 },\n { address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.OPTIMISM]: [\n { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', symbol: 'USDC', decimals: 6 },\n { address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', symbol: 'USDT', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.POLYGON]: [\n { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', decimals: 6 },\n { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 },\n { address: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BSC]: [\n { address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', symbol: 'USDC', decimals: 18 },\n { address: '0x55d398326f99059fF775485246999027B3197955', symbol: 'USDT', decimals: 18 },\n { address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.AVALANCHE]: [\n { address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', symbol: 'USDC', decimals: 6 },\n { address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', symbol: 'USDT', decimals: 6 },\n { address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.SONIC]: [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', decimals: 6 },\n ],\n [CHAIN_IDS.LIGHTLINK]: [\n { address: '0xbCF8C1B03bBDDA88D579330BDF236B58F8bb2cFd', symbol: 'USDC', decimals: 6 },\n ],\n};\n/**\n * Native token symbols by chain\n */\nconst NATIVE_SYMBOLS = {\n [CHAIN_IDS.ETHEREUM]: 'ETH',\n [CHAIN_IDS.ARBITRUM]: 'ETH',\n [CHAIN_IDS.OPTIMISM]: 'ETH',\n [CHAIN_IDS.BASE]: 'ETH',\n [CHAIN_IDS.POLYGON]: 'POL',\n [CHAIN_IDS.BSC]: 'BNB',\n [CHAIN_IDS.AVALANCHE]: 'AVAX',\n [CHAIN_IDS.SONIC]: 'S',\n [CHAIN_IDS.LIGHTLINK]: 'ETH',\n [CHAIN_IDS.HYPEREVM]: 'HYPE',\n [CHAIN_IDS.KAIA]: 'KAIA',\n};\n/**\n * Chain ID to name mapping\n */\nconst CHAIN_NAMES = {\n [CHAIN_IDS.ETHEREUM]: 'Ethereum',\n [CHAIN_IDS.ARBITRUM]: 'Arbitrum',\n [CHAIN_IDS.OPTIMISM]: 'Optimism',\n [CHAIN_IDS.BASE]: 'Base',\n [CHAIN_IDS.POLYGON]: 'Polygon',\n [CHAIN_IDS.BSC]: 'BSC',\n [CHAIN_IDS.AVALANCHE]: 'Avalanche',\n [CHAIN_IDS.SONIC]: 'Sonic',\n [CHAIN_IDS.LIGHTLINK]: 'LightLink',\n [CHAIN_IDS.HYPEREVM]: 'HyperEVM',\n [CHAIN_IDS.KAIA]: 'Kaia',\n};\n/**\n * Chain name strings for wallet support check\n */\nconst CHAIN_NAME_STRINGS = {\n [CHAIN_IDS.ETHEREUM]: ['ethereum'],\n [CHAIN_IDS.BASE]: ['base'],\n [CHAIN_IDS.ARBITRUM]: ['arbitrum'],\n [CHAIN_IDS.OPTIMISM]: ['optimism'],\n [CHAIN_IDS.POLYGON]: ['polygon'],\n [CHAIN_IDS.BSC]: ['bsc'],\n [CHAIN_IDS.AVALANCHE]: ['avalanche', 'avax'],\n [CHAIN_IDS.SONIC]: ['sonic'],\n [CHAIN_IDS.LIGHTLINK]: ['lightlink'],\n};\n// ============================================================================\n// Helper Functions\n// ============================================================================\n/**\n * Create a viem public client for a chain\n */\nfunction createClient(chainId) {\n const chain = getViemChain(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n return createPublicClient({\n chain,\n transport: http(rpcUrl, { timeout: 10000 }),\n });\n}\n/**\n * Get native balance for a wallet on a chain\n */\nasync function getNativeBalance(client, address, chainId) {\n try {\n const balance = await client.getBalance({ address });\n return {\n symbol: NATIVE_SYMBOLS[chainId] || 'ETH',\n balance: formatUnits(balance, 18),\n balanceRaw: balance,\n };\n }\n catch (error) {\n console.error(`[portfolio] Failed to get native balance on chain ${chainId}:`, error);\n return { symbol: NATIVE_SYMBOLS[chainId] || 'ETH', balance: '0', balanceRaw: 0n };\n }\n}\n/**\n * Get ERC20 token balance using eth_call directly (avoids viem type issues)\n */\nasync function getTokenBalance(rpcUrl, walletAddress, tokenAddress, decimals, symbol) {\n try {\n // balanceOf(address) selector: 0x70a08231\n const paddedAddress = walletAddress.slice(2).toLowerCase().padStart(64, '0');\n const callData = `0x70a08231${paddedAddress}`;\n const response = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to: tokenAddress, data: callData }, 'latest'],\n id: 1,\n }),\n });\n const json = await response.json();\n const result = json.result;\n if (!result || result === '0x' || result === '0x0') {\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n const balanceRaw = BigInt(result);\n const balance = formatUnits(balanceRaw, decimals);\n return { symbol, balance, balanceRaw, address: tokenAddress };\n }\n catch (error) {\n console.error(`[portfolio] Failed to get ${symbol} balance:`, error);\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n}\n/**\n * Query all balances for a wallet on a specific chain\n */\nasync function getChainBalances(address, chainId, includeZeroBalances) {\n const client = createClient(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n const chainName = CHAIN_NAMES[chainId] || `Chain ${chainId}`;\n // Get native balance\n const native = await getNativeBalance(client, address, chainId);\n // Get token balances\n const tokenConfigs = MAJOR_TOKENS[chainId] || [];\n const tokenPromises = tokenConfigs.map((t) => getTokenBalance(rpcUrl, address, t.address, t.decimals, t.symbol));\n const tokenResults = await Promise.all(tokenPromises);\n // Filter zero balances if requested\n const tokens = includeZeroBalances\n ? tokenResults.map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }))\n : tokenResults\n .filter((t) => t.balanceRaw > 0n)\n .map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }));\n return {\n chainId: chainId.toString(),\n chainName,\n native: {\n symbol: native.symbol,\n balance: parseFloat(native.balance).toFixed(6),\n },\n tokens,\n };\n}\n/**\n * Handle portfolio summary request\n */\nexport async function handlePortfolioSummary(params) {\n const { walletId, chains, includeZeroBalances = false } = params;\n console.log('[portfolio:summary] Fetching portfolio summary', {\n walletId: walletId || 'all',\n chains: chains || 'all',\n includeZeroBalances,\n });\n // Fetch token prices from SODAX (cached, 1 min TTL)\n let priceMap = null;\n try {\n priceMap = await fetchTokenPrices();\n }\n catch (err) {\n console.warn('[portfolio] Failed to fetch prices, USD values will be unavailable:', err);\n }\n const walletManager = getWalletManager();\n const allWallets = await walletManager.listWallets();\n // Filter to specific wallet if requested\n const walletsToQuery = walletId\n ? allWallets.filter((w) => w.nickname === walletId)\n : allWallets;\n if (walletsToQuery.length === 0) {\n return {\n success: false,\n error: walletId ? `Wallet not found: ${walletId}` : 'No wallets configured',\n };\n }\n // Determine chains to query\n // Query all chains with configured tokens by default\n const defaultChains = [\n CHAIN_IDS.BASE,\n CHAIN_IDS.ETHEREUM,\n CHAIN_IDS.ARBITRUM,\n CHAIN_IDS.OPTIMISM,\n CHAIN_IDS.POLYGON,\n CHAIN_IDS.SONIC,\n CHAIN_IDS.BSC,\n CHAIN_IDS.AVALANCHE,\n CHAIN_IDS.LIGHTLINK,\n ];\n const chainIdsToQuery = chains\n ? chains.map((c) => resolveChainId(c))\n : defaultChains;\n const results = [];\n let totalValueUsd = 0;\n // Helper to get USD price for a symbol\n const getPrice = (symbol) => {\n if (!priceMap)\n return null;\n const lower = symbol.toLowerCase();\n return priceMap.bySymbol.get(lower) ?? priceMap.bySymbol.get('soda' + lower) ?? null;\n };\n for (const wallet of walletsToQuery) {\n // Skip wallets without known addresses\n if (wallet.address === '0x...') {\n console.log(`[portfolio] Skipping wallet ${wallet.nickname} - address not resolved`);\n continue;\n }\n const address = wallet.address;\n // Filter chains to those the wallet supports\n const supportedChains = wallet.chains || [];\n const chainsForWallet = chainIdsToQuery.filter((cid) => {\n const names = CHAIN_NAME_STRINGS[cid] || [];\n return supportedChains.length === 0 || names.some((n) => supportedChains.includes(n));\n });\n // Query balances for each chain (in parallel)\n const balancePromises = chainsForWallet.map((cid) => getChainBalances(address, cid, includeZeroBalances).catch((err) => {\n console.error(`[portfolio] Failed to query chain ${cid}:`, err);\n return null;\n }));\n const balanceResults = (await Promise.all(balancePromises)).filter((b) => b !== null);\n // Filter out chains with no balances if not including zeros\n const filteredBalances = includeZeroBalances\n ? balanceResults\n : balanceResults.filter((b) => parseFloat(b.native.balance) > 0 || b.tokens.length > 0);\n // Add USD values to balances\n let walletBalanceUsd = 0;\n const balancesWithUsd = filteredBalances.map((chainBalance) => {\n let chainTotalUsd = 0;\n // Native token USD value\n const nativePrice = getPrice(chainBalance.native.symbol);\n const nativeBalance = parseFloat(chainBalance.native.balance);\n const nativeUsdValue = nativePrice ? nativeBalance * nativePrice : null;\n if (nativeUsdValue)\n chainTotalUsd += nativeUsdValue;\n // Token USD values\n const tokensWithUsd = chainBalance.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue)\n chainTotalUsd += usdValue;\n return {\n ...token,\n usdValue: usdValue ? `$${usdValue.toFixed(2)}` : undefined,\n };\n });\n walletBalanceUsd += chainTotalUsd;\n return {\n chainId: chainBalance.chainId,\n chainName: chainBalance.chainName,\n native: {\n symbol: chainBalance.native.symbol,\n balance: chainBalance.native.balance,\n usdValue: nativeUsdValue ? `$${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: chainTotalUsd > 0 ? `$${chainTotalUsd.toFixed(2)}` : undefined,\n };\n });\n // Query Solana balances if wallet has a Solana address\n // Bankr wallets have a separate Solana address that can be cached\n const solanaAddress = wallet.solanaAddress;\n if (solanaAddress) {\n try {\n const solanaBalances = await getSolanaWalletBalances(solanaAddress, includeZeroBalances);\n if (solanaBalances) {\n // Add USD values for Solana\n let solanaTotalUsd = 0;\n const solPrice = getPrice('SOL');\n const nativeBalance = parseFloat(solanaBalances.native.balance);\n const nativeUsdValue = solPrice ? nativeBalance * solPrice : null;\n if (nativeUsdValue)\n solanaTotalUsd += nativeUsdValue;\n const tokensWithUsd = solanaBalances.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue)\n solanaTotalUsd += usdValue;\n return { ...token, usdValue: usdValue ? `${usdValue.toFixed(2)}` : undefined };\n });\n walletBalanceUsd += solanaTotalUsd;\n balancesWithUsd.push({\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: solanaBalances.native.symbol,\n balance: solanaBalances.native.balance,\n usdValue: nativeUsdValue ? `${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: solanaTotalUsd > 0 ? `${solanaTotalUsd.toFixed(2)}` : undefined,\n });\n }\n }\n catch (err) {\n console.error(`[portfolio] Failed to get Solana balances for ${wallet.nickname}:`, err);\n }\n }\n // Get money market positions (aggregate)\n let mmSummary;\n try {\n const positions = await aggregateCrossChainPositions(wallet.nickname);\n if (positions && (positions.summary.totalSupplyUsd > 0 || positions.summary.totalBorrowUsd > 0)) {\n const hfStatus = getHealthFactorStatus(positions.summary.healthFactor);\n // Build per-chain breakdown with individual health factors\n const chainBreakdown = positions.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n healthStatus: getHealthFactorStatus(cs.healthFactor),\n }));\n mmSummary = {\n totalSupplyUsd: positions.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: positions.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: positions.summary.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(positions.summary.healthFactor),\n healthStatus: hfStatus,\n chainBreakdown,\n };\n // MM net worth is already USD - add to wallet total\n walletBalanceUsd += positions.summary.netWorthUsd;\n }\n }\n catch (err) {\n console.error(`[portfolio] Failed to get MM positions for ${wallet.nickname}:`, err);\n }\n totalValueUsd += walletBalanceUsd;\n results.push({\n wallet: {\n nickname: wallet.nickname,\n address: wallet.address,\n type: wallet.type,\n },\n balances: balancesWithUsd,\n moneyMarket: mmSummary,\n walletTotalUsd: walletBalanceUsd > 0 ? `$${walletBalanceUsd.toFixed(2)}` : undefined,\n });\n }\n // Build summary\n const summary = {\n walletCount: results.length,\n chainsQueried: chainIdsToQuery.length,\n timestamp: new Date().toISOString(),\n estimatedTotalUsd: totalValueUsd > 0 ? `$${totalValueUsd.toFixed(2)}` : 'No positions',\n priceSource: priceMap ? 'SODAX' : 'unavailable',\n };\n return {\n success: true,\n summary,\n wallets: results,\n };\n}\n// ============================================================================\n// Solana Balance Functions\n// ============================================================================\nconst SOLANA_RPC_URL = 'https://api.mainnet-beta.solana.com';\n/**\n * Major SPL tokens to check on Solana\n */\nconst SOLANA_TOKENS = [\n { mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', decimals: 6 },\n { mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', symbol: 'USDT', decimals: 6 },\n];\n/**\n * Get native SOL balance for a Solana wallet\n */\nasync function getSolanaBalance(address) {\n try {\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getBalance',\n params: [address],\n }),\n });\n const json = await response.json();\n const lamports = BigInt(json.result?.value || 0);\n // SOL has 9 decimals\n const balance = Number(lamports) / 1e9;\n return {\n symbol: 'SOL',\n balance: balance.toFixed(6),\n balanceRaw: lamports,\n };\n }\n catch (error) {\n console.error('[portfolio] Failed to get SOL balance:', error);\n return { symbol: 'SOL', balance: '0', balanceRaw: 0n };\n }\n}\n/**\n * Get SPL token balances for a Solana wallet\n */\nasync function getSolanaTokenBalances(address) {\n const results = [];\n try {\n // Query all token accounts owned by this wallet\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [\n address,\n { programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' },\n { encoding: 'jsonParsed' },\n ],\n }),\n });\n const json = await response.json();\n const accounts = json.result?.value || [];\n // Match against known tokens\n for (const tokenConfig of SOLANA_TOKENS) {\n const account = accounts.find((a) => a.account.data.parsed.info.mint === tokenConfig.mint);\n if (account) {\n const amount = account.account.data.parsed.info.tokenAmount.uiAmount || 0;\n if (amount > 0) {\n results.push({\n symbol: tokenConfig.symbol,\n balance: amount.toFixed(6),\n address: tokenConfig.mint,\n });\n }\n }\n }\n }\n catch (error) {\n console.error('[portfolio] Failed to get Solana token balances:', error);\n }\n return results;\n}\n/**\n * Get all Solana balances for a wallet\n */\nexport async function getSolanaWalletBalances(address, includeZeroBalances = false) {\n // Validate Solana address format (base58, 32-44 chars)\n if (!address || address.startsWith('0x') || address.length < 32 || address.length > 44) {\n return null;\n }\n try {\n const native = await getSolanaBalance(address);\n const tokens = await getSolanaTokenBalances(address);\n // Skip if no balances and not including zeros\n if (!includeZeroBalances && native.balanceRaw === 0n && tokens.length === 0) {\n return null;\n }\n return {\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: native.symbol,\n balance: native.balance,\n },\n tokens,\n };\n }\n catch (error) {\n console.error('[portfolio] Failed to get Solana balances:', error);\n return null;\n }\n}\n//# sourceMappingURL=portfolio.js.map",
730 "inputSchema": {},
731 "outputSchema": null,
732 "icons": null,
733 "annotations": null,
734 "meta": null,
735 "execution": null
736 },
737 {
738 "name": "moneyMarket.js",
739 "title": null,
740 "description": "Script: moneyMarket.js. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n *\n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n *\n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n *\n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\nimport { Type } from \"@sinclair/typebox\";\nimport { getSodaxClient } from \"../sodax/client\";\nimport { getSpokeProvider } from \"../providers/spokeProviderFactory\";\nimport { PolicyEngine } from \"../policy/policyEngine\";\nimport { getWalletManager } from '../wallet/walletManager';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Base schema for money market operations\n */\nconst MoneyMarketBaseSchema = Type.Object({\n walletId: Type.String({\n description: \"Unique identifier for the wallet\"\n }),\n chainId: Type.String({\n description: \"Source SODAX spoke chain ID where the operation originates (e.g., 'ethereum', 'arbitrum', 'sonic')\"\n }),\n token: Type.String({\n description: \"Token address or symbol to supply/borrow/withdraw/repay\",\n }),\n amount: Type.String({\n description: \"Amount in human-readable units (e.g., '100.5' for 100.5 USDC). Use '-1' for max repay (repay full debt).\",\n }),\n timeoutMs: Type.Optional(Type.Number({\n description: \"Operation timeout in milliseconds\",\n default: 180000,\n })),\n policyId: Type.Optional(Type.String({ description: \"Optional policy profile identifier for custom limits\" })),\n skipSimulation: Type.Optional(Type.Boolean({\n description: \"Skip transaction simulation (not recommended)\",\n default: false,\n })),\n});\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\nconst MoneyMarketSupplySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n useAsCollateral: Type.Optional(Type.Boolean({\n description: \"Whether to use the supplied tokens as collateral for borrowing (default: true)\",\n default: true,\n })),\n // Cross-chain supply options\n dstChainId: Type.Optional(Type.String({\n description: \"Optional destination chain for the supply operation. If different from chainId, performs cross-chain supply.\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address for the supplied position (defaults to wallet address)\",\n })),\n }),\n]);\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\nconst MoneyMarketWithdrawSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n withdrawType: Type.Optional(Type.Union([\n Type.Literal('default'),\n Type.Literal('collateral'),\n Type.Literal('all'),\n ], {\n description: \"Withdraw type: 'default' (standard), 'collateral' (withdraw collateral only), 'all' (withdraw maximum)\",\n default: 'default',\n })),\n // Cross-chain withdraw options\n dstChainId: Type.Optional(Type.String({\n description: \"Optional destination chain to receive withdrawn tokens. If different from chainId, performs cross-chain withdraw.\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address to receive withdrawn tokens (defaults to wallet address)\",\n })),\n }),\n]);\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n *\n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\nconst MoneyMarketBorrowSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate (recommended)\" }),\n ], {\n description: \"Interest rate mode: 1 = Stable, 2 = Variable\",\n default: 2,\n })),\n referralCode: Type.Optional(Type.String({\n description: \"Optional referral code for the borrow operation\",\n })),\n // Cross-chain borrow options (key feature!)\n dstChainId: Type.Optional(Type.String({\n description: \"Destination chain to receive borrowed tokens. If different from chainId, performs cross-chain borrow (supply on chainId, receive borrowed tokens on dstChainId).\",\n })),\n recipient: Type.Optional(Type.String({\n description: \"Optional recipient address to receive borrowed tokens (defaults to wallet address on dstChainId or chainId)\",\n })),\n }),\n]);\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\nconst MoneyMarketRepaySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate\" }),\n ], {\n description: \"Interest rate mode of the debt to repay: 1 = Stable, 2 = Variable\",\n default: 2,\n })),\n repayAll: Type.Optional(Type.Boolean({\n description: \"If true, repays the full debt amount (useful for closing position)\",\n default: false,\n })),\n // Cross-chain repay options\n collateralChainId: Type.Optional(Type.String({\n description: \"Optional chain ID where collateral is held (for cross-chain repay scenarios)\",\n })),\n }),\n]);\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\nconst CreateSupplyIntentSchema = Type.Composite([\n MoneyMarketSupplySchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateBorrowIntentSchema = Type.Composite([\n MoneyMarketBorrowSchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateWithdrawIntentSchema = Type.Composite([\n MoneyMarketWithdrawSchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\nconst CreateRepayIntentSchema = Type.Composite([\n MoneyMarketRepaySchema,\n Type.Object({\n raw: Type.Optional(Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })),\n }),\n]);\n// ============================================================================\n// Helper Functions\n// ============================================================================\n/**\n * Converts human-readable amount to token units (wei)\n */\nfunction parseTokenAmount(amount, decimals = 18) {\n // Handle special case for max repay\n if (amount === '-1') {\n return BigInt(-1);\n }\n const parsed = parseFloat(amount);\n if (isNaN(parsed)) {\n throw new Error(`Invalid amount: ${amount}`);\n }\n const multiplier = Math.pow(10, decimals);\n return BigInt(Math.floor(parsed * multiplier));\n}\n/**\n * Resolves wallet and creates spoke provider for the operation\n */\nasync function resolveWalletAndProvider(walletId, chainId) {\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n return { walletAddress, spokeProvider };\n}\n/**\n * Common pre-operation checks and allowance handling\n */\nasync function prepareMoneyMarketOperation(walletId, chainId, token, amount, operation, policyId) {\n // ============================================================================\n // Hub Chain Validation\n // ============================================================================\n // SODAX architecture: Money market operations must be initiated from spoke chains,\n // not the hub chain (Sonic). The hub chain is the settlement layer where contracts\n // live, but users interact via spoke chains that relay operations to the hub.\n // Reference: sodax-tests/tests/crossChainSdk.test.ts explicitly omits SONIC_MAINNET_CHAIN_ID\n const isHubChainSource = chainId.toLowerCase() === 'sonic' || chainId === '146';\n if (isHubChainSource) {\n throw new Error(`Money market operations cannot be initiated from the hub chain (Sonic). ` +\n `Please use a spoke chain (base, arbitrum, ethereum, optimism, avalanche, bsc, polygon) as the source chain.`);\n }\n // Ensure sodax client is initialized\n const _sodaxClient = getSodaxClient(); // Just verify it's ready\n void _sodaxClient;\n // Normalize chain ID to SDK format for token resolution\n const sdkChainId = toSodaxChainId(chainId);\n // Resolve token symbol to address\n const tokenAddr = await resolveToken(sdkChainId, token);\n // Resolve wallet and create spoke provider\n const { walletAddress, spokeProvider } = await resolveWalletAndProvider(walletId, chainId);\n // Policy check\n const policyEngine = new PolicyEngine();\n const policyResult = await policyEngine.checkMoneyMarket({\n walletId,\n chainId,\n token,\n amount, // Add required amount parameter\n amountUsd: parseFloat(amount), // Simplified - would need actual price lookup\n operation,\n policyId,\n });\n if (!policyResult.allowed) {\n throw new Error(`Policy check failed: ${policyResult.reason || \"Operation not permitted\"}.`);\n }\n return { walletAddress, spokeProvider, policyResult, tokenAddr };\n}\n/**\n * Resolves token and returns its decimals\n * Falls back to 18 decimals if token info not found\n */\nasync function getTokenDecimals(chainId, token) {\n try {\n const sdkChainId = toSodaxChainId(chainId);\n const tokenInfo = await getTokenInfo(sdkChainId, token);\n return tokenInfo?.decimals ?? 18;\n }\n catch {\n // If token info lookup fails, fall back to 18 decimals\n return 18;\n }\n}\n/**\n * Checks and handles token approval if needed\n */\nasync function ensureAllowance(params, spokeProvider, raw = false) {\n const sodaxClient = await getSodaxClient();\n // Check if allowance is sufficient\n const isAllowanceValid = await sodaxClient.moneyMarket.isAllowanceValid(params, spokeProvider);\n if (!isAllowanceValid.ok || !isAllowanceValid.value) {\n if (raw) {\n // Return raw approval transaction\n const rawApproval = await sodaxClient.moneyMarket.approve(params, spokeProvider, true // raw mode\n );\n return { rawApproval };\n }\n else {\n // Execute approval transaction\n const approvalResult = await sodaxClient.moneyMarket.approve(params, spokeProvider, false);\n // Handle Result type from SDK\n const txHash = approvalResult.ok\n ? approvalResult.value\n : approvalResult.txHash || approvalResult;\n return { approvalTxHash: String(txHash) };\n }\n }\n return {};\n}\n/**\n * Determine if operation is cross-chain\n */\nfunction isCrossChainOperation(srcChainId, dstChainId) {\n return !!dstChainId && dstChainId !== srcChainId;\n}\n// ============================================================================\n// Tool Handlers\n// ============================================================================\n/**\n * Supply tokens to the money market\n *\n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\nasync function handleSupply(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, useAsCollateral = true, dstChainId, recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"supply\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n // Check allowance for supply\n const { approvalTxHash } = await ensureAllowance({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n const sodaxClient = await getSodaxClient();\n // Build supply parameters\n const supplyParams = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain supply: tokens supplied on ${chainId}, collateral recorded on ${dstChainId}`);\n }\n // Check and handle allowance (required for ALL supply operations)\n // Reference: sodax-frontend moneymarket-ops.ts - supply ALWAYS checks allowance\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:supply] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'supply' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:supply] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:supply] Allowance check failed, proceeding anyway:', allowanceError);\n }\n // Execute supply\n const supplyResult = await sodaxClient.moneyMarket.supply(supplyParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (supplyResult.ok === false) {\n throw new Error(`Supply failed: ${serializeError(supplyResult.error)}`);\n }\n const value = supplyResult.ok ? supplyResult.value : supplyResult;\n // SDK may return [spokeTxHash, hubTxHash] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully supplied ${amount} ${token} on ${chainId}. Collateral available on ${dstChainId || chainId}.`\n : `Successfully supplied ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during supply\";\n return {\n success: false,\n status: \"failed\",\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market supply failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Withdraw tokens from the money market\n *\n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\nasync function handleWithdraw(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, withdrawType = 'default', dstChainId, recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"withdraw\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n // Build withdraw parameters\n const withdrawParams = {\n action: 'withdraw',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n withdrawParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain withdraw: withdrawing from ${chainId}, receiving tokens on ${dstChainId}`);\n }\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'withdraw' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:withdraw] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'withdraw' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:withdraw] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:withdraw] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n // Execute withdraw\n const withdrawResult = await sodaxClient.moneyMarket.withdraw(withdrawParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (withdrawResult.ok === false) {\n throw new Error(`Withdraw failed: ${serializeError(withdrawResult.error)}`);\n }\n const value = withdrawResult.ok ? withdrawResult.value : withdrawResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully withdrew ${amount} ${token} from ${chainId} to ${dstChainId}`\n : `Successfully withdrew ${amount} ${token} from money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during withdraw\";\n return {\n success: false,\n status: \"failed\",\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market withdraw failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Borrow tokens from the money market\n *\n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n *\n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\nasync function handleBorrow(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, interestRateMode = 2, referralCode, dstChainId, // KEY: This can be different from chainId for cross-chain borrow!\n recipient, skipSimulation = false } = params;\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"borrow\", policyId);\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n // Get user's positions to check health factor (best practice)\n const sodaxClient = await getSodaxClient();\n // For cross-chain borrow, resolve token on DESTINATION chain\n // SDK expects: getMoneyMarketToken(toChainId, params.token)\n // So params.token must be the destination chain's token address\n let borrowTokenAddr = tokenAddr;\n if (crossChain && dstChainId) {\n borrowTokenAddr = await resolveToken(dstChainId, token);\n console.log('[mm:borrow] Cross-chain: resolved token on destination chain', {\n srcChain: chainId,\n dstChain: dstChainId,\n srcTokenAddr: tokenAddr,\n dstTokenAddr: borrowTokenAddr,\n });\n }\n // Build borrow parameters\n const borrowParams = {\n action: 'borrow',\n token: borrowTokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n // KEY CROSS-CHAIN FEATURE:\n // If dstChainId is provided and different from chainId, the borrowed tokens\n // will be delivered to dstChainId instead of chainId where the borrow is initiated\n if (crossChain && dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain borrow: Using collateral on ${chainId}, receiving borrowed tokens on ${dstChainId}`);\n warnings.push(`Ensure you have sufficient collateral on ${chainId} to support this borrow`);\n }\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:borrow] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:borrow] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:borrow] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n // Execute borrow\n const borrowResult = await sodaxClient.moneyMarket.borrow(borrowParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (borrowResult.ok === false) {\n throw new Error(`Borrow failed: ${serializeError(borrowResult.error)}`);\n }\n const value = borrowResult.ok ? borrowResult.value : borrowResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully borrowed ${amount} ${token} on ${dstChainId} using collateral from ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`\n : `Successfully borrowed ${amount} ${token} from money market on ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during borrow\";\n return {\n success: false,\n status: \"failed\",\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market borrow failed: ${errorMessage}`,\n };\n }\n}\n/**\n * Repay borrowed tokens to the money market\n *\n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\nasync function handleRepay(params) {\n const { walletId, chainId, token, amount, timeoutMs = 180000, policyId, interestRateMode = 2, repayAll = false, collateralChainId, skipSimulation = false } = params;\n const crossChain = !!collateralChainId && collateralChainId !== chainId;\n const warnings = [];\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"repay\", policyId);\n // Parse amount with actual token decimals (use -1 for max repay if repayAll is true)\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = repayAll ? BigInt(-1) : parseTokenAmount(amount, decimals);\n // Check allowance for repay\n const { approvalTxHash } = await ensureAllowance({ token: tokenAddr, amount: amountBigInt === BigInt(-1) ? BigInt(0) : amountBigInt, action: 'repay' }, spokeProvider);\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n const sodaxClient = await getSodaxClient();\n // Build repay parameters\n const repayParams = {\n action: 'repay',\n token: tokenAddr,\n amount: amountBigInt,\n };\n // Add cross-chain parameters if applicable\n if (crossChain && collateralChainId) {\n repayParams.toChainId = collateralChainId;\n warnings.push(`Cross-chain repay: Repaying debt on ${collateralChainId} using tokens from ${chainId}`);\n }\n // Check and handle allowance (required for ALL repay operations)\n // Reference: sodax-frontend moneymarket-ops.ts - repay ALWAYS checks allowance\n try {\n const allowanceResult = await sodaxClient.moneyMarket.isAllowanceValid({ token: tokenAddr, amount: amountBigInt, action: 'repay' }, spokeProvider);\n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:repay] Approval needed, approving...');\n const approveResult = await sodaxClient.moneyMarket.approve({ token: tokenAddr, amount: amountBigInt, action: 'repay' }, spokeProvider);\n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:repay] Approval confirmed');\n }\n }\n }\n catch (allowanceError) {\n console.warn('[mm:repay] Allowance check failed, proceeding anyway:', allowanceError);\n }\n // Execute repay\n const repayResult = await sodaxClient.moneyMarket.repay(repayParams, spokeProvider, timeoutMs);\n // Handle Result type from SDK\n if (repayResult.ok === false) {\n throw new Error(`Repay failed: ${serializeError(repayResult.error)}`);\n }\n const value = repayResult.ok ? repayResult.value : repayResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || solverResponse?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || intent?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: repayAll\n ? `Successfully repaid full debt for ${token} on ${chainId}`\n : `Successfully repaid ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during repay\";\n return {\n success: false,\n status: \"failed\",\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: `Money market repay failed: ${errorMessage}`,\n };\n }\n}\n// ============================================================================\n// Intent Creation Handlers (Advanced)\n// ============================================================================\n/**\n * Create a supply intent without executing (for custom flows)\n */\nasync function handleCreateSupplyIntent(params) {\n const { walletId, chainId, token, amount, useAsCollateral = true, dstChainId, recipient, raw = true } = params;\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"supply\");\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n const supplyParams = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n if (dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n }\n const intentData = await sodaxClient.moneyMarket.createSupplyIntent(supplyParams, spokeProvider, raw);\n return {\n success: true,\n status: \"pending\",\n operation: \"createSupplyIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: \"Supply intent created. Submit this intent to execute the supply operation.\",\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create supply intent failed: ${errorMessage}`);\n }\n}\n/**\n * Create a borrow intent without executing (for custom flows)\n */\nasync function handleCreateBorrowIntent(params) {\n const { walletId, chainId, token, amount, interestRateMode = 2, dstChainId, recipient, raw = true } = params;\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(walletId, chainId, token, amount, \"borrow\");\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n const borrowParams = {\n action: 'borrow',\n token: tokenAddr,\n amount: amountBigInt,\n toAddress: recipient || walletAddress,\n };\n if (dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n }\n const intentData = await sodaxClient.moneyMarket.createBorrowIntent(borrowParams, spokeProvider, raw);\n return {\n success: true,\n status: \"pending\",\n operation: \"createBorrowIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: dstChainId && dstChainId !== chainId\n ? `Cross-chain borrow intent created. Collateral on ${chainId}, borrowed tokens to ${dstChainId}. Submit this intent to execute.`\n : \"Borrow intent created. Submit this intent to execute the borrow operation.\",\n };\n }\n catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create borrow intent failed: ${errorMessage}`);\n }\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport function registerMoneyMarketTools(agentTools) {\n // Supply\n agentTools.register({\n name: \"amped_mm_supply\",\n summary: \"Supply tokens as collateral to the SODAX money market. Supports same-chain and cross-chain supply (supply on chain A, collateral available on chain B).\",\n schema: MoneyMarketSupplySchema,\n handler: handleSupply,\n });\n // Withdraw\n agentTools.register({\n name: \"amped_mm_withdraw\",\n summary: \"Withdraw supplied tokens from the SODAX money market. Supports cross-chain withdraw (withdraw from chain A, receive tokens on chain B).\",\n schema: MoneyMarketWithdrawSchema,\n handler: handleWithdraw,\n });\n // Borrow\n agentTools.register({\n name: \"amped_mm_borrow\",\n summary: \"Borrow tokens from the SODAX money market. KEY FEATURE: Can borrow to a different chain than collateral! Example: Supply on Ethereum, borrow to Arbitrum using dstChainId parameter.\",\n schema: MoneyMarketBorrowSchema,\n handler: handleBorrow,\n });\n // Repay\n agentTools.register({\n name: \"amped_mm_repay\",\n summary: \"Repay borrowed tokens to the SODAX money market. Use amount='-1' or repayAll=true to repay full debt. Supports cross-chain repay.\",\n schema: MoneyMarketRepaySchema,\n handler: handleRepay,\n });\n // Advanced: Create Intent variants for custom flows\n agentTools.register({\n name: \"amped_mm_create_supply_intent\",\n summary: \"[Advanced] Create a supply intent without executing. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateSupplyIntentSchema,\n handler: handleCreateSupplyIntent,\n });\n agentTools.register({\n name: \"amped_mm_create_borrow_intent\",\n summary: \"[Advanced] Create a borrow intent without executing. Supports cross-chain borrow intents. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateBorrowIntentSchema,\n handler: handleCreateBorrowIntent,\n });\n}\n// ============================================================================\n// Re-exports for testing and direct usage\n// ============================================================================\nexport { MoneyMarketBaseSchema, MoneyMarketSupplySchema, MoneyMarketWithdrawSchema, MoneyMarketBorrowSchema, MoneyMarketRepaySchema, CreateSupplyIntentSchema, CreateWithdrawIntentSchema, CreateBorrowIntentSchema, CreateRepayIntentSchema, handleSupply, handleWithdraw, handleBorrow, handleRepay, handleCreateSupplyIntent, handleCreateBorrowIntent, };\n// Aliases for index.ts compatibility\nexport { MoneyMarketSupplySchema as MmSupplySchema, MoneyMarketWithdrawSchema as MmWithdrawSchema, MoneyMarketBorrowSchema as MmBorrowSchema, MoneyMarketRepaySchema as MmRepaySchema, handleSupply as handleMmSupply, handleWithdraw as handleMmWithdraw, handleBorrow as handleMmBorrow, handleRepay as handleMmRepay, };\n//# sourceMappingURL=moneyMarket.js.map",
741 "inputSchema": {},
742 "outputSchema": null,
743 "icons": null,
744 "annotations": null,
745 "meta": null,
746 "execution": null
747 },
748 {
749 "name": "swap.d.ts",
750 "title": null,
751 "description": "Script: swap.d.ts. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n *\n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\nimport { Static } from '@sinclair/typebox';\nimport type { AgentTools } from '../types';\ndeclare const SwapQuoteRequestSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n type: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"exact_input\">, import(\"@sinclair/typebox\").TLiteral<\"exact_output\">]>;\n slippageBps: import(\"@sinclair/typebox\").TNumber;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ndeclare const SwapExecuteParamsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n quote: import(\"@sinclair/typebox\").TObject<{\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n inputAmount: import(\"@sinclair/typebox\").TString;\n outputAmount: import(\"@sinclair/typebox\").TString;\n slippageBps: import(\"@sinclair/typebox\").TNumber;\n deadline: import(\"@sinclair/typebox\").TNumber;\n minOutputAmount: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n maxInputAmount: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n recipient: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n }>;\n maxSlippageBps: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n policyId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n skipSimulation: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n timeoutMs: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\ndeclare const SwapStatusParamsSchema: import(\"@sinclair/typebox\").TObject<{\n txHash: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n intentHash: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\ndeclare const SwapCancelParamsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n intent: import(\"@sinclair/typebox\").TObject<{\n id: import(\"@sinclair/typebox\").TString;\n srcChainId: import(\"@sinclair/typebox\").TString;\n dstChainId: import(\"@sinclair/typebox\").TString;\n srcToken: import(\"@sinclair/typebox\").TString;\n dstToken: import(\"@sinclair/typebox\").TString;\n amount: import(\"@sinclair/typebox\").TString;\n deadline: import(\"@sinclair/typebox\").TNumber;\n }>;\n srcChainId: import(\"@sinclair/typebox\").TString;\n}>;\ntype SwapQuoteRequest = Static<typeof SwapQuoteRequestSchema>;\ntype SwapExecuteParams = Static<typeof SwapExecuteParamsSchema>;\ntype SwapStatusParams = Static<typeof SwapStatusParamsSchema>;\ntype SwapCancelParams = Static<typeof SwapCancelParamsSchema>;\ndeclare function handleSwapQuote(params: SwapQuoteRequest): Promise<Record<string, unknown>>;\ndeclare function handleSwapExecute(params: SwapExecuteParams): Promise<Record<string, unknown>>;\ndeclare function handleSwapStatus(params: SwapStatusParams): Promise<Record<string, unknown>>;\ndeclare function handleSwapCancel(params: SwapCancelParams): Promise<Record<string, unknown>>;\nexport declare function registerSwapTools(agentTools: AgentTools): void;\nexport { SwapQuoteRequestSchema as SwapQuoteSchema, SwapExecuteParamsSchema as SwapExecuteSchema, SwapStatusParamsSchema as SwapStatusSchema, SwapCancelParamsSchema as SwapCancelSchema, };\nexport { handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel, };\n//# sourceMappingURL=swap.d.ts.map",
752 "inputSchema": {},
753 "outputSchema": null,
754 "icons": null,
755 "annotations": null,
756 "meta": null,
757 "execution": null
758 },
759 {
760 "name": "swap.js",
761 "title": null,
762 "description": "Script: swap.js. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n *\n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\nimport { Type } from '@sinclair/typebox';\nimport { serializeError } from '../utils/errorUtils';\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n// ============================================================================\n// SODAX API & Explorer Links\n// ============================================================================\nconst SODAX_CANARY_API = 'https://canary-api.sodax.com/v1/be';\n// Chain ID to block explorer mapping\nconst CHAIN_EXPLORERS = {\n 'ethereum': 'https://etherscan.io/tx/',\n 'base': 'https://basescan.org/tx/',\n 'arbitrum': 'https://arbiscan.io/tx/',\n 'optimism': 'https://optimistic.etherscan.io/tx/',\n 'polygon': 'https://polygonscan.com/tx/',\n 'sonic': 'https://sonicscan.org/tx/',\n 'avalanche': 'https://snowtrace.io/tx/',\n 'bsc': 'https://bscscan.com/tx/',\n 'solana': 'https://solscan.io/tx/',\n};\nfunction getExplorerLink(chainId, txHash) {\n // Normalize chain ID (remove 0x prefix and suffix if present)\n const normalizedChainId = chainId.replace(/^0x[\\\\da-f]+\\\\./, '').toLowerCase();\n const explorer = CHAIN_EXPLORERS[normalizedChainId];\n return explorer ? `${explorer}${txHash}` : undefined;\n}\nasync function fetchIntentFromSodax(intentHash) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok)\n return null;\n return await response.json();\n }\n catch {\n return null;\n }\n}\n/**\n * Ensure intent hash is in hex format (0x prefixed)\n */\nfunction toHexIntentHash(hash) {\n if (!hash)\n return undefined;\n const str = String(hash);\n // Already hex format\n if (str.startsWith('0x'))\n return str;\n // Convert decimal BigInt string to hex\n try {\n return '0x' + BigInt(str).toString(16);\n }\n catch {\n return str; // Return as-is if conversion fails\n }\n}\nfunction getSodaxScanUrl(txHash) {\n return `https://sodaxscan.com/messages/search?value=${txHash}`;\n}\nfunction getSodaxIntentApiUrl(intentHash) {\n return `${SODAX_CANARY_API}/intent/${intentHash}`;\n}\n// SODAX internal chain ID to block explorer mapping\nconst SODAX_CHAIN_EXPLORERS = {\n 1: 'https://solscan.io/tx/', // Solana\n 30: 'https://basescan.org/tx/', // Base\n 146: 'https://sonicscan.org/tx/', // Sonic (hub)\n 42161: 'https://arbiscan.io/tx/', // Arbitrum\n 10: 'https://optimistic.etherscan.io/tx/', // Optimism\n 137: 'https://polygonscan.com/tx/', // Polygon\n 56: 'https://bscscan.com/tx/', // BSC\n 43114: 'https://snowtrace.io/tx/', // Avalanche\n};\n/**\n * Poll SODAX API until intent is delivered, then return delivery tx explorer link\n */\nasync function pollForDelivery(intentHash, timeoutMs = 60000, pollIntervalMs = 3000) {\n const startTime = Date.now();\n while (Date.now() - startTime < timeoutMs) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n continue;\n }\n const data = await response.json();\n // Check if intent is filled (closed)\n if (data.open === false && data.events?.length > 0) {\n const fillEvent = data.events.find((e) => e.eventType === 'intent-filled');\n if (fillEvent) {\n const dstChainId = data.intent?.dstChain;\n const explorer = SODAX_CHAIN_EXPLORERS[dstChainId] || '';\n return {\n delivered: true,\n deliveryTxHash: fillEvent.txHash,\n deliveryExplorer: explorer ? `${explorer}${fillEvent.txHash}` : undefined,\n dstChainId\n };\n }\n }\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n catch {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n }\n return { delivered: false };\n}\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\nconst SwapTypeSchema = Type.Union([\n Type.Literal('exact_input'),\n Type.Literal('exact_output')\n]);\nconst SwapQuoteRequestSchema = Type.Object({\n walletId: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n type: SwapTypeSchema,\n slippageBps: Type.Number({ default: 50, minimum: 0, maximum: 10000 }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain. For cross-chain swaps to Solana, provide a Solana base58 address. Defaults to wallet address if omitted.'\n }))\n});\n// Result schema for documentation (not used at runtime)\nconst _SwapQuoteResultSchema = Type.Object({\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n fees: Type.Object({\n solverFee: Type.String(),\n protocolFee: Type.Optional(Type.String()),\n partnerFee: Type.Optional(Type.String())\n }),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n});\nvoid _SwapQuoteResultSchema; // Suppress unused warning\nconst SwapExecuteParamsSchema = Type.Object({\n walletId: Type.String(),\n quote: Type.Object({\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n }),\n maxSlippageBps: Type.Optional(Type.Number({ minimum: 0, maximum: 10000 })),\n policyId: Type.Optional(Type.String()),\n skipSimulation: Type.Optional(Type.Boolean({ default: false })),\n timeoutMs: Type.Optional(Type.Number({ default: 120000 }))\n});\nconst SwapExecuteResultSchema = Type.Object({\n spokeTxHash: Type.String(),\n hubTxHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String()),\n status: Type.String(),\n message: Type.Optional(Type.String())\n});\nconst SwapStatusParamsSchema = Type.Object({\n txHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String())\n});\nconst SwapStatusResultSchema = Type.Object({\n status: Type.String(),\n intentHash: Type.Optional(Type.String()),\n spokeTxHash: Type.Optional(Type.String()),\n hubTxHash: Type.Optional(Type.String()),\n filledAmount: Type.Optional(Type.String()),\n error: Type.Optional(Type.String()),\n createdAt: Type.Optional(Type.Number()),\n expiresAt: Type.Optional(Type.Number())\n});\nconst SwapCancelParamsSchema = Type.Object({\n walletId: Type.String(),\n intent: Type.Object({\n id: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n deadline: Type.Number()\n }),\n srcChainId: Type.String()\n});\nconst SwapCancelResultSchema = Type.Object({\n success: Type.Boolean(),\n txHash: Type.Optional(Type.String()),\n message: Type.String()\n});\n// ============================================================================\n// Swap Quote Tool\n// ============================================================================\nasync function handleSwapQuote(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.srcChainId, params.srcToken);\n const dstTokenAddr = await resolveToken(params.dstChainId, params.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.dstChainId, dstTokenAddr);\n // Get token config to determine decimals for amount conversion\n const configService = sodaxClient.configService;\n const decimals = srcTokenInfo?.decimals ?? 18; // Default to 18 (most EVM tokens) if not found\n // Convert human-readable amount to raw amount (bigint)\n const amountFloat = parseFloat(params.amount);\n const rawAmount = BigInt(Math.floor(amountFloat * Math.pow(10, decimals)));\n // Build SDK-compatible request with snake_case parameters\n const quoteRequest = {\n token_src: srcTokenAddr,\n token_src_blockchain_id: toSodaxChainId(params.srcChainId),\n token_dst: dstTokenAddr,\n token_dst_blockchain_id: toSodaxChainId(params.dstChainId),\n amount: rawAmount,\n quote_type: params.type\n };\n console.log('[swap_quote] SDK request:', JSON.stringify(quoteRequest, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n const quoteResult = await sodaxClient.swaps.getQuote(quoteRequest);\n // Handle Result type from SDK\n if (quoteResult.ok === false) {\n const errorMsg = quoteResult.error instanceof Error\n ? quoteResult.error.message\n : typeof quoteResult.error === 'string'\n ? quoteResult.error\n : JSON.stringify(quoteResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Quote failed: ${errorMsg}`);\n }\n const quote = quoteResult.ok ? quoteResult.value : quoteResult;\n console.log('[swap_quote] SDK response:', JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n // Get output token decimals with multiple fallbacks\n // SDK returns quoted_amount as bigint - convert to human-readable string\n let dstDecimals = quote.token_dst_decimals || quote.tokenDstDecimals;\n if (!dstDecimals && dstTokenInfo) {\n dstDecimals = dstTokenInfo.decimals;\n }\n if (!dstDecimals) {\n // Hardcoded decimals for common stablecoins\n const KNOWN_DECIMALS = {\n usdc: 6, USDC: 6, usdt: 6, USDT: 6, sol: 9, SOL: 9,\n dai: 18, DAI: 18, bnusd: 18, bnUSD: 18\n };\n const tokenSymbol = params.dstToken.toUpperCase();\n dstDecimals = KNOWN_DECIMALS[tokenSymbol] || 18;\n console.warn(`[swap_quote] Using fallback decimals (${dstDecimals}) for token ${params.dstToken}`);\n }\n const quotedAmount = quote.quoted_amount || quote.quotedAmount || quote.outputAmount;\n const outputAmountStr = quotedAmount\n ? (Number(quotedAmount) / Math.pow(10, dstDecimals)).toString()\n : '0';\n // Normalize and return quote (SDK uses snake_case, we return camelCase)\n const result = {\n inputAmount: params.amount,\n outputAmount: outputAmountStr,\n srcToken: srcTokenAddr,\n dstToken: dstTokenAddr,\n srcChainId: params.srcChainId,\n dstChainId: params.dstChainId,\n slippageBps: params.slippageBps,\n deadline: quote.deadline || calculateDeadline(300), // 5 min default\n fees: {\n solverFee: quote.solver_fee || quote.fees?.solverFee || '0',\n protocolFee: quote.protocol_fee || quote.fees?.protocolFee,\n partnerFee: quote.partner_fee || quote.fees?.partnerFee\n },\n minOutputAmount: quote.min_output_amount || quote.minOutputAmount,\n maxInputAmount: quote.max_input_amount || quote.maxInputAmount,\n recipient: params.recipient, // Pass through for execute\n // Include raw SDK response for debugging\n _raw: JSON.parse(JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v))\n };\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n tokenAddresses: [params.srcToken, params.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to get swap quote: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Execute Tool\n// ============================================================================\nasync function handleSwapExecute(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n // 1. Initialize dependencies\n const policyEngine = new PolicyEngine();\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.quote.srcChainId, params.quote.srcToken);\n const dstTokenAddr = await resolveToken(params.quote.dstChainId, params.quote.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.quote.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.quote.dstChainId, dstTokenAddr);\n // 2. Resolve wallet\n const wallet = await walletManager.resolve(params.walletId);\n const walletAddress = await wallet.getAddress();\n // 3. Policy check\n const policyCheck = await policyEngine.checkSwap({\n walletId: params.walletId,\n srcChainId: params.quote.srcChainId,\n dstChainId: params.quote.dstChainId,\n srcToken: params.quote.srcToken,\n dstToken: params.quote.dstToken,\n inputAmount: params.quote.inputAmount,\n slippageBps: params.maxSlippageBps || params.quote.slippageBps,\n policyId: params.policyId\n });\n if (!policyCheck.allowed) {\n throw new Error(`Policy check failed: ${policyCheck.reason}`);\n }\n // 4. Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(params.walletId, params.quote.srcChainId);\n // 5. Convert amounts to bigint FIRST (needed for intentParams)\n const srcDecimals = srcTokenInfo?.decimals ?? 18;\n const dstDecimals = dstTokenInfo?.decimals ?? 18;\n const inputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.inputAmount) * Math.pow(10, srcDecimals)));\n const outputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.outputAmount) * Math.pow(10, dstDecimals)));\n // Calculate minOutputAmount with slippage\n const slippageBps = params.maxSlippageBps || params.quote.slippageBps || 100;\n const minOutputAmountRaw = outputAmountRaw - (outputAmountRaw * BigInt(slippageBps) / 10000n);\n console.log(\"[swap_execute] Amount conversion:\", {\n inputAmount: params.quote.inputAmount,\n inputAmountRaw: inputAmountRaw.toString(),\n outputAmount: params.quote.outputAmount,\n outputAmountRaw: outputAmountRaw.toString(),\n minOutputAmountRaw: minOutputAmountRaw.toString(),\n srcDecimals,\n dstDecimals\n });\n // 6. Build intentParams (used for allowance check, approval, and swap)\n const intentParams = {\n srcAddress: walletAddress,\n dstAddress: params.quote.recipient || walletAddress,\n srcChain: toSodaxChainId(params.quote.srcChainId),\n dstChain: toSodaxChainId(params.quote.dstChainId),\n inputToken: srcTokenAddr,\n outputToken: dstTokenAddr,\n inputAmount: inputAmountRaw,\n minOutputAmount: minOutputAmountRaw,\n deadline: BigInt(params.quote.deadline),\n allowPartialFill: false,\n solver: \"0x0000000000000000000000000000000000000000\",\n data: \"0x\"\n };\n // 7. Check allowance using SDK's expected API\n let allowanceValid = false;\n try {\n const allowanceResult = await sodaxClient.swaps.isAllowanceValid({\n intentParams,\n spokeProvider\n });\n allowanceValid = allowanceResult?.ok ? allowanceResult.value : !!allowanceResult;\n }\n catch (e) {\n console.warn('[swap_execute] Allowance check failed, assuming approval needed:', e);\n allowanceValid = false;\n }\n // 8. Approve if needed using SDK's expected API\n if (!allowanceValid) {\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n message: 'Token approval required'\n });\n const approvalResult = await sodaxClient.swaps.approve({\n intentParams,\n spokeProvider\n });\n const approvalTx = approvalResult?.ok ? approvalResult.value : approvalResult;\n // Wait for approval confirmation if possible\n if (spokeProvider.walletProvider?.waitForTransactionReceipt && approvalTx) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTx);\n }\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n approvalTx: String(approvalTx),\n success: true\n });\n }\n // 9. Execute swap\n const swapResult = await sodaxClient.swaps.swap({\n intentParams,\n spokeProvider,\n skipSimulation: params.skipSimulation || false,\n timeout: params.timeoutMs || 120000\n });\n // Handle Result type from SDK\n if (swapResult.ok === false) {\n const errorMsg = swapResult.error instanceof Error\n ? swapResult.error.message\n : typeof swapResult.error === 'string'\n ? swapResult.error\n : JSON.stringify(swapResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Swap failed: ${errorMsg}`);\n }\n const value = swapResult.ok ? swapResult.value : swapResult;\n // SDK may return [response, intent, deliveryInfo] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n // Extract internal tracking info\n const srcTxHash = deliveryInfo?.srcTxHash;\n const intentHash = toHexIntentHash(solverResponse?.intent_hash) || toHexIntentHash(intent?.intentId);\n // Poll for delivery confirmation (wait up to 60s)\n let deliveryResult = { delivered: false };\n if (intentHash) {\n console.log('[swap_execute] Waiting for delivery confirmation...');\n deliveryResult = await pollForDelivery(intentHash, 60000, 3000);\n }\n // Build user-friendly result\n const result = {\n status: deliveryResult.delivered ? 'delivered' : 'submitted',\n message: deliveryResult.delivered\n ? 'Swap completed! Funds delivered to destination.'\n : 'Swap submitted, awaiting cross-chain delivery...',\n // User-friendly tracking link\n sodaxScanUrl: srcTxHash ? getSodaxScanUrl(srcTxHash) : undefined,\n // Source chain: where user initiated the swap\n // Destination chain: where user RECEIVED funds\n initiationTx: srcTxHash ? getExplorerLink(params.quote.srcChainId, srcTxHash) : undefined,\n receiptTx: deliveryResult.deliveryExplorer,\n };\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n tokenAddresses: [params.quote.srcToken, params.quote.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Swap execution failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Status Tool\n// ============================================================================\nasync function handleSwapStatus(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n if (!params.txHash && !params.intentHash) {\n throw new Error('Either txHash or intentHash must be provided');\n }\n // Use our SodaxApiClient for Backend API access (not SDK)\n const sodaxApi = getSodaxApiClient();\n let intentData = null;\n // Try intentHash first (most reliable)\n if (params.intentHash) {\n try {\n intentData = await sodaxApi.getIntentByHash(params.intentHash);\n }\n catch (err) {\n console.warn(`[swap_status] getIntentByHash failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n // If intentHash lookup failed or wasn't provided, try txHash\n // Note: txHash should be from the HUB chain (Sonic), not spoke chain\n if (!intentData && params.txHash) {\n try {\n intentData = await sodaxApi.getIntentByTxHash(params.txHash);\n }\n catch (err) {\n // txHash lookup failed - provide helpful error message\n const errorMsg = err instanceof Error ? err.message : String(err);\n throw new Error(`Unable to find intent for txHash: ${params.txHash}. ` +\n `Note: The txHash must be from the HUB chain (Sonic), not the spoke chain. ` +\n `If you have a spoke chain txHash (Base, Arbitrum, etc.), use amped_user_intents to find the intent first. ` +\n `Error: ${errorMsg}`);\n }\n }\n if (!intentData) {\n throw new Error('Unable to retrieve swap status. Provide a valid intentHash or hub chain txHash.');\n }\n // Extract intent details\n const intentHash = intentData.intentHash;\n const hubTxHash = intentData.txHash; // This is the hub chain tx that created the intent\n const isOpen = intentData.open;\n const intent = intentData.intent;\n // Determine status from intent state\n let status = 'unknown';\n if (intentData.open === true) {\n status = 'pending';\n }\n else if (intentData.open === false) {\n // Check events to determine if filled or cancelled/expired\n const filledEvent = intentData.events?.find((e) => e.eventType === 'intent-filled');\n const cancelledEvent = intentData.events?.find((e) => e.eventType === 'intent-cancelled' || e.eventType === 'intent-expired');\n if (filledEvent) {\n status = 'filled';\n }\n else if (cancelledEvent) {\n status = cancelledEvent.eventType === 'intent-cancelled' ? 'cancelled' : 'expired';\n }\n else {\n status = 'closed';\n }\n }\n // Extract fulfillment details if available\n const filledEvent = intentData.events?.find((e) => e.eventType === 'intent-filled');\n const fulfillmentTxHash = filledEvent?.txHash;\n const receivedOutput = filledEvent?.intentState?.receivedOutput;\n // Build result\n const result = {\n status,\n intentHash,\n hubTxHash, // Hub chain tx that created the intent\n spokeTxHash: params.txHash !== hubTxHash ? params.txHash : undefined, // Original spoke tx if different\n open: isOpen,\n // Intent details\n srcChain: intent?.srcChain,\n dstChain: intent?.dstChain,\n inputToken: intent?.inputToken,\n outputToken: intent?.outputToken,\n inputAmount: intent?.inputAmount,\n minOutputAmount: intent?.minOutputAmount,\n receivedOutput: receivedOutput,\n deadline: intent?.deadline ? new Date(parseInt(intent.deadline) * 1000).toISOString() : undefined,\n createdAt: intentData.createdAt,\n // Fulfillment details\n fulfillmentTxHash,\n fulfillmentChain: intent?.dstChain,\n // Tracking links\n sodaxScanUrl: `https://sodaxscan.com/intents/${intentHash}`,\n };\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash,\n txHash: params.txHash,\n status,\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash: params.intentHash,\n txHash: params.txHash,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to get swap status: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Swap Cancel Tool\n// ============================================================================\nasync function handleSwapCancel(params) {\n const requestId = generateRequestId();\n const startTime = Date.now();\n try {\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.intent.srcChainId, params.intent.srcToken);\n const dstTokenAddr = await resolveToken(params.intent.dstChainId, params.intent.dstToken);\n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.intent.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.intent.dstChainId, dstTokenAddr);\n // Resolve wallet (validates it exists)\n await walletManager.resolve(params.walletId);\n // Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(params.walletId, params.srcChainId);\n // Construct intent object for cancellation\n const intent = {\n id: params.intent.id,\n srcChainId: params.intent.srcChainId,\n dstChainId: params.intent.dstChainId,\n srcToken: params.intent.srcToken,\n dstToken: params.intent.dstToken,\n amount: params.intent.amount,\n deadline: BigInt(params.intent.deadline),\n createdAt: Date.now(),\n status: 'pending'\n };\n // Cancel the intent - SDK expects (intent, spokeProvider)\n const cancelResult = await sodaxClient.swaps.cancelIntent(intent, spokeProvider);\n // Handle Result type\n if (cancelResult.ok === false) {\n throw new Error(`Cancel failed: ${serializeError(cancelResult.error)}`);\n }\n const cancelTx = cancelResult.ok ? cancelResult.value : cancelResult;\n const cancelTxHash = typeof cancelTx === 'string' ? cancelTx : String(cancelTx);\n // Wait for cancellation confirmation if possible\n // SDK may expose waitForTransactionReceipt on the underlying wallet provider\n if (spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(cancelTxHash);\n }\n const result = {\n success: true,\n txHash: cancelTxHash,\n message: 'Intent cancelled successfully'\n };\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n txHash: cancelTxHash,\n durationMs: Date.now() - startTime,\n success: true\n });\n return result;\n }\n catch (error) {\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n throw new Error(`Failed to cancel swap: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n// ============================================================================\n// Utility Functions\n// ============================================================================\nfunction generateRequestId() {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\nfunction calculateDeadline(secondsFromNow) {\n return Math.floor(Date.now() / 1000) + secondsFromNow;\n}\nfunction logStructured(entry) {\n // Structured JSON logging for observability\n // Use replacer to handle BigInt serialization\n console.log(JSON.stringify({\n ...entry,\n timestamp: new Date().toISOString(),\n component: 'amped-defi-swap'\n }, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\nexport function registerSwapTools(agentTools) {\n // Register swap quote tool\n agentTools.register({\n name: 'amped_swap_quote',\n summary: 'Get a swap quote for exact-in or exact-out swaps across chains',\n description: 'Retrieves a quote for swapping tokens across chains using the SODAX swap protocol. ' +\n 'Supports both exact input (specify input amount, get output estimate) and ' +\n 'exact output (specify desired output, get required input) modes.',\n schema: SwapQuoteRequestSchema,\n handler: handleSwapQuote\n });\n // Register swap execute tool\n agentTools.register({\n name: 'amped_swap_execute',\n summary: 'Execute a swap with policy enforcement and allowance handling',\n description: 'Executes a swap using a previously obtained quote. ' +\n 'Performs policy checks, validates allowances, approves tokens if needed, ' +\n 'and executes the swap transaction. Returns transaction hashes and intent status.',\n schema: SwapExecuteParamsSchema,\n handler: handleSwapExecute\n });\n // Register swap status tool\n agentTools.register({\n name: 'amped_swap_status',\n summary: 'Check the status of a swap intent or transaction',\n description: 'Polls the status of a swap by intent hash or transaction hash. ' +\n 'Returns current status, fill amount, error details if failed, and timing information.',\n schema: SwapStatusParamsSchema,\n handler: handleSwapStatus\n });\n // Register swap cancel tool\n agentTools.register({\n name: 'amped_swap_cancel',\n summary: 'Cancel an active swap intent',\n description: 'Cancels a pending swap intent on the source chain. ' +\n 'Requires the intent details and source chain ID. Returns cancellation transaction hash.',\n schema: SwapCancelParamsSchema,\n handler: handleSwapCancel\n });\n}\n// Silence unused variable warnings for result schemas (used for documentation)\nvoid SwapExecuteResultSchema;\nvoid SwapStatusResultSchema;\nvoid SwapCancelResultSchema;\n// Export schemas with consistent naming\nexport { SwapQuoteRequestSchema as SwapQuoteSchema, SwapExecuteParamsSchema as SwapExecuteSchema, SwapStatusParamsSchema as SwapStatusSchema, SwapCancelParamsSchema as SwapCancelSchema, };\n// Export handlers\nexport { handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel, };\n//# sourceMappingURL=swap.js.map",
763 "inputSchema": {},
764 "outputSchema": null,
765 "icons": null,
766 "annotations": null,
767 "meta": null,
768 "execution": null
769 },
770 {
771 "name": "discovery.d.ts",
772 "title": null,
773 "description": "Script: discovery.d.ts. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_supported_chains - no parameters required\n */\ndeclare const SupportedChainsSchema: import(\"@sinclair/typebox\").TObject<{}>;\n/**\n * Schema for amped_supported_tokens\n */\ndeclare const SupportedTokensSchema: import(\"@sinclair/typebox\").TObject<{\n module: import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"swaps\">, import(\"@sinclair/typebox\").TLiteral<\"bridge\">, import(\"@sinclair/typebox\").TLiteral<\"moneyMarket\">]>;\n chainId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_wallet_address\n */\ndeclare const WalletAddressSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_money_market_positions\n */\ndeclare const MoneyMarketPositionsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainId: import(\"@sinclair/typebox\").TString;\n}>;\n/**\n * Schema for amped_money_market_reserves\n */\ndeclare const MoneyMarketReservesSchema: import(\"@sinclair/typebox\").TObject<{\n chainId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n}>;\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\ndeclare const CrossChainPositionsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n chainIds: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n includeZeroBalances: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n minUsdValue: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\ndeclare const UserIntentsSchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TString;\n status: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TUnion<[import(\"@sinclair/typebox\").TLiteral<\"all\">, import(\"@sinclair/typebox\").TLiteral<\"open\">, import(\"@sinclair/typebox\").TLiteral<\"closed\">]>>;\n limit: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n offset: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TNumber>;\n}>;\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\ndeclare const ListWalletsSchema: import(\"@sinclair/typebox\").TObject<{}>;\ntype SupportedChainsParams = Static<typeof SupportedChainsSchema>;\ntype SupportedTokensParams = Static<typeof SupportedTokensSchema>;\ntype WalletAddressParams = Static<typeof WalletAddressSchema>;\ntype MoneyMarketPositionsParams = Static<typeof MoneyMarketPositionsSchema>;\ntype ListWalletsParams = Static<typeof ListWalletsSchema>;\ntype MoneyMarketReservesParams = Static<typeof MoneyMarketReservesSchema>;\ntype CrossChainPositionsParams = Static<typeof CrossChainPositionsSchema>;\ntype UserIntentsParams = Static<typeof UserIntentsSchema>;\n/**\n * AgentTools interface for registering tools with the OpenClaw framework\n */\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n/**\n * Get supported spoke chains from SODAX configuration\n */\ndeclare function handleSupportedChains(_params: SupportedChainsParams): Promise<unknown>;\n/**\n * Get supported tokens for a specific module and chain\n */\ndeclare function handleSupportedTokens(params: SupportedTokensParams): Promise<unknown>;\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\ndeclare function handleWalletAddress(params: WalletAddressParams): Promise<unknown>;\n/**\n * Get user money market positions (humanized format)\n */\ndeclare function handleMoneyMarketPositions(params: MoneyMarketPositionsParams): Promise<unknown>;\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\ndeclare function handleMoneyMarketReserves(params: MoneyMarketReservesParams): Promise<unknown>;\n/**\n * Get aggregated money market positions across all chains\n *\n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\ndeclare function handleCrossChainPositions(params: CrossChainPositionsParams): Promise<unknown>;\n/**\n * Get user intents from SODAX API\n *\n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\ndeclare function handleUserIntents(params: UserIntentsParams): Promise<unknown>;\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\ndeclare function handleListWallets(_params: ListWalletsParams): Promise<unknown>;\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport declare function registerDiscoveryTools(agentTools: AgentTools): void;\nexport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketPositionsSchema, MoneyMarketReservesSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, };\nexport { handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketPositions, handleMoneyMarketReserves, handleCrossChainPositions, handleUserIntents, handleListWallets, };\n//# sourceMappingURL=discovery.d.ts.map",
774 "inputSchema": {},
775 "outputSchema": null,
776 "icons": null,
777 "annotations": null,
778 "meta": null,
779 "execution": null
780 },
781 {
782 "name": "walletManagement.js",
783 "title": null,
784 "description": "Script: walletManagement.js. Code:\n/**\n * Wallet Management Tools\n *\n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n *\n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\nimport { Type } from '@sinclair/typebox';\nimport { getWalletManager } from '../wallet';\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n/**\n * Schema for amped_add_wallet\n */\nconst AddWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Nickname for the wallet (e.g., \"trading\", \"savings\", \"degen\")',\n }),\n source: Type.Union([\n Type.Literal('evm-wallet-skill'),\n Type.Literal('bankr'),\n Type.Literal('env'),\n ], {\n description: 'Wallet source type',\n }),\n // For evm-wallet-skill\n path: Type.Optional(Type.String({\n description: 'Path to wallet JSON file (for evm-wallet-skill). Defaults to ~/.evm-wallet.json',\n })),\n // For bankr\n apiKey: Type.Optional(Type.String({\n description: 'Bankr API key (for bankr source)',\n })),\n apiUrl: Type.Optional(Type.String({\n description: 'Bankr API URL (optional, defaults to https://api.bankr.bot)',\n })),\n // For env\n address: Type.Optional(Type.String({\n description: 'Wallet address (for env source)',\n })),\n privateKey: Type.Optional(Type.String({\n description: 'Private key (for env source). WARNING: Will be stored in config file.',\n })),\n // Chain restrictions\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional list of chains this wallet can use',\n })),\n});\n/**\n * Schema for amped_rename_wallet\n */\nconst RenameWalletSchema = Type.Object({\n currentNickname: Type.String({\n description: 'Current wallet nickname',\n }),\n newNickname: Type.String({\n description: 'New wallet nickname',\n }),\n});\n/**\n * Schema for amped_remove_wallet\n */\nconst RemoveWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to remove',\n }),\n confirm: Type.Optional(Type.Boolean({\n description: 'Set to true to confirm removal',\n default: false,\n })),\n});\n/**\n * Schema for amped_set_default_wallet\n */\nconst SetDefaultWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to set as default',\n }),\n});\n// ============================================================================\n// Handlers\n// ============================================================================\n/**\n * Add a new wallet with a nickname\n */\nasync function handleAddWallet(params) {\n const { nickname, source, path, apiKey, apiUrl, address, privateKey, chains } = params;\n console.log('[walletManagement:addWallet] Adding wallet', { nickname, source });\n // Validate required fields based on source\n if (source === 'bankr' && !apiKey) {\n throw new Error('Bankr wallet requires apiKey');\n }\n if (source === 'env' && (!address || !privateKey)) {\n throw new Error('Env wallet requires both address and privateKey');\n }\n // Build config\n const config = {\n source: source,\n chains,\n };\n if (source === 'evm-wallet-skill') {\n if (path)\n config.path = path;\n }\n else if (source === 'bankr') {\n config.apiKey = apiKey;\n if (apiUrl)\n config.apiUrl = apiUrl;\n }\n else if (source === 'env') {\n config.address = address;\n config.privateKey = privateKey;\n }\n // Add wallet\n const walletManager = getWalletManager();\n await walletManager.addWallet(nickname, config);\n // Get the new wallet info\n const wallet = await walletManager.resolve(nickname);\n const walletAddress = await wallet.getAddress();\n return {\n success: true,\n message: `Wallet \"${nickname}\" added successfully`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: source,\n address: walletAddress,\n chains: wallet.supportedChains,\n },\n hint: `You can now use: \"swap 100 USDC to ETH using ${nickname}\"`,\n };\n}\n/**\n * Rename a wallet\n */\nasync function handleRenameWallet(params) {\n const { currentNickname, newNickname } = params;\n console.log('[walletManagement:renameWallet] Renaming wallet', {\n from: currentNickname,\n to: newNickname\n });\n const walletManager = getWalletManager();\n // Get current info before rename\n const wallet = await walletManager.resolve(currentNickname);\n const address = await wallet.getAddress();\n // Rename\n await walletManager.renameWallet(currentNickname, newNickname);\n return {\n success: true,\n message: `Wallet renamed from \"${currentNickname}\" to \"${newNickname}\"`,\n wallet: {\n nickname: newNickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: `Now use: \"swap 100 USDC using ${newNickname}\"`,\n };\n}\n/**\n * Remove a wallet\n */\nasync function handleRemoveWallet(params) {\n const { nickname, confirm } = params;\n console.log('[walletManagement:removeWallet] Removing wallet', { nickname, confirm });\n const walletManager = getWalletManager();\n // Get wallet info before removal\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n if (!confirm) {\n return {\n success: false,\n requiresConfirmation: true,\n message: `Are you sure you want to remove wallet \"${nickname}\" (${address})?`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: 'Call again with confirm: true to proceed',\n };\n }\n // Remove\n await walletManager.removeWallet(nickname);\n // List remaining wallets\n const remainingWallets = await walletManager.listWallets();\n return {\n success: true,\n message: `Wallet \"${nickname}\" removed`,\n remainingWallets: remainingWallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n isDefault: w.isDefault,\n })),\n };\n}\n/**\n * Set default wallet\n */\nasync function handleSetDefaultWallet(params) {\n const { nickname } = params;\n console.log('[walletManagement:setDefaultWallet] Setting default', { nickname });\n const walletManager = getWalletManager();\n // Validate wallet exists\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n // Set default\n await walletManager.setDefaultWallet(nickname);\n return {\n success: true,\n message: `Default wallet set to \"${nickname}\"`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n chains: [...wallet.supportedChains],\n },\n hint: 'Operations without a wallet specified will now use this wallet',\n };\n}\n// ============================================================================\n// Error Wrapper\n// ============================================================================\nfunction wrapHandler(handler) {\n return async (params) => {\n try {\n return await handler(params);\n }\n catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[walletManagement] Error:', message);\n return {\n success: false,\n error: message,\n };\n }\n };\n}\n// ============================================================================\n// Tool Registration\n// ============================================================================\n/**\n * Register wallet management tools\n */\nexport function registerWalletManagementTools(agentTools) {\n // 1. Add wallet\n agentTools.register({\n name: 'amped_add_wallet',\n summary: 'Add a new wallet with a nickname for easy reference',\n description: 'Add a wallet from evm-wallet-skill, Bankr, or environment variables. ' +\n 'Give it a memorable nickname like \"trading\", \"savings\", or \"degen\". ' +\n 'The wallet will be saved to config and available across sessions.',\n schema: AddWalletSchema,\n handler: wrapHandler(handleAddWallet),\n });\n // 2. Rename wallet\n agentTools.register({\n name: 'amped_rename_wallet',\n summary: 'Rename a wallet to a new nickname',\n description: 'Change the nickname of an existing wallet. ' +\n 'The wallet address and configuration remain the same, only the nickname changes.',\n schema: RenameWalletSchema,\n handler: wrapHandler(handleRenameWallet),\n });\n // 3. Remove wallet\n agentTools.register({\n name: 'amped_remove_wallet',\n summary: 'Remove a wallet from configuration',\n description: 'Remove a wallet by nickname. This only removes it from the config file, ' +\n 'it does NOT delete the actual wallet or funds. Requires confirmation.',\n schema: RemoveWalletSchema,\n handler: wrapHandler(handleRemoveWallet),\n });\n // 4. Set default wallet\n agentTools.register({\n name: 'amped_set_default_wallet',\n summary: 'Set which wallet to use by default',\n description: 'Set the default wallet for operations. When you don\\'t specify a wallet, ' +\n 'this one will be used automatically.',\n schema: SetDefaultWalletSchema,\n handler: wrapHandler(handleSetDefaultWallet),\n });\n}\n// Export schemas and handlers for testing\nexport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet, };\n//# sourceMappingURL=walletManagement.js.map",
785 "inputSchema": {},
786 "outputSchema": null,
787 "icons": null,
788 "annotations": null,
789 "meta": null,
790 "execution": null
791 },
792 {
793 "name": "portfolio.d.ts",
794 "title": null,
795 "description": "Script: portfolio.d.ts. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\nimport { Static } from '@sinclair/typebox';\n/**\n * Schema for amped_portfolio_summary\n */\nexport declare const PortfolioSummarySchema: import(\"@sinclair/typebox\").TObject<{\n walletId: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TString>;\n chains: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TArray<import(\"@sinclair/typebox\").TString>>;\n includeZeroBalances: import(\"@sinclair/typebox\").TOptional<import(\"@sinclair/typebox\").TBoolean>;\n}>;\ntype PortfolioSummaryParams = Static<typeof PortfolioSummarySchema>;\n/**\n * Handle portfolio summary request\n */\nexport declare function handlePortfolioSummary(params: PortfolioSummaryParams): Promise<unknown>;\n/**\n * Get all Solana balances for a wallet\n */\nexport declare function getSolanaWalletBalances(address: string, includeZeroBalances?: boolean): Promise<{\n chainId: string;\n chainName: string;\n native: {\n symbol: string;\n balance: string;\n usdValue?: string;\n };\n tokens: Array<{\n symbol: string;\n balance: string;\n address: string;\n usdValue?: string;\n }>;\n chainTotalUsd?: string;\n} | null>;\nexport {};\n//# sourceMappingURL=portfolio.d.ts.map",
796 "inputSchema": {},
797 "outputSchema": null,
798 "icons": null,
799 "annotations": null,
800 "meta": null,
801 "execution": null
802 },
803 {
804 "name": "types.js",
805 "title": null,
806 "description": "Script: types.js. Code:\n/**\n * Common types for Amped DeFi plugin\n */\nexport {};\n//# sourceMappingURL=types.js.map",
807 "inputSchema": {},
808 "outputSchema": null,
809 "icons": null,
810 "annotations": null,
811 "meta": null,
812 "execution": null
813 },
814 {
815 "name": "spokeProviderFactory.js",
816 "title": null,
817 "description": "Script: spokeProviderFactory.js. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n *\n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\n// Official SDK wallet provider\nimport { EvmWalletProvider } from '@sodax/wallet-sdk-core';\n// Import spoke providers and chain config from SDK\nimport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\n// Import chain configuration from types\nimport { spokeChainConfig } from '@sodax/types';\n// Import wallet management\nimport { getWalletManager, createBankrWalletProvider } from '../wallet';\nimport { getWalletAdapter } from '../wallet/skillWalletAdapter';\nimport { normalizeChainId, getBankrChainId } from '../wallet/types';\n// Cache for providers: Map<cacheKey, SpokeProvider>\nconst providerCache = new Map();\n// Sonic hub chain identifier\nconst SONIC_CHAIN_ID = 'sonic';\n// Chain ID mapping for SDK (some chains need specific format)\nconst CHAIN_ID_MAP = {\n 'sonic': 'sonic',\n 'ethereum': 'ethereum',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n/**\n * Get RPC URL for a chain\n */\nasync function getRpcUrl(chainId) {\n const skillAdapter = getWalletAdapter();\n return skillAdapter.getRpcUrl(chainId);\n}\n/**\n * Get the SDK chain ID for a given chain\n */\nfunction getSdkChainId(chainId) {\n return (CHAIN_ID_MAP[chainId] || chainId);\n}\n/**\n * Validate that wallet supports the requested chain\n */\nfunction validateChainSupport(wallet, chainId) {\n const normalizedForWallet = normalizeChainId(chainId);\n if (!wallet.supportsChain(normalizedForWallet)) {\n throw new Error(`Wallet \"${wallet.nickname}\" doesn't support chain \"${chainId}\". ` +\n `Supported chains: ${wallet.supportedChains.join(', ')}. ` +\n `Try a different wallet.`);\n }\n}\n/**\n * Create a spoke provider for local key signing\n */\nasync function createLocalSpokeProvider(wallet, chainId, rpcUrl) {\n if (!wallet.getPrivateKey) {\n throw new Error(`Wallet \"${wallet.nickname}\" does not support local signing`);\n }\n const privateKey = await wallet.getPrivateKey();\n const sdkChainId = getSdkChainId(chainId);\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}. Available: ${Object.keys(spokeChainConfig).join(', ')}`);\n }\n // Create wallet provider using official SDK\n const walletProvider = new EvmWalletProvider({\n privateKey,\n chainId: sdkChainId,\n rpcUrl: rpcUrl,\n });\n // Use SonicSpokeProvider for Sonic hub chain, EvmSpokeProvider for others\n if (chainId === SONIC_CHAIN_ID) {\n console.log('[spokeProviderFactory] Creating SonicSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n });\n return new SonicSpokeProvider(walletProvider, chainConfig, rpcUrl);\n }\n else {\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n });\n return new EvmSpokeProvider(walletProvider, chainConfig, rpcUrl);\n }\n}\n/**\n * Create a spoke provider for Bankr wallet\n * Uses BankrWalletProvider which submits transactions to Bankr API\n */\nasync function createBankrSpokeProvider(wallet, chainId, rpcUrl) {\n const sdkChainId = getSdkChainId(chainId);\n // Normalize chain ID for Bankr lookup (0x2105.base -> base)\n const normalizedChainId = normalizeChainId(chainId);\n const numericChainId = getBankrChainId(normalizedChainId);\n console.log('[spokeProviderFactory] Bankr chain resolution', {\n input: chainId,\n normalized: normalizedChainId,\n numeric: numericChainId,\n });\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}`);\n }\n // Get Bankr API key from environment\n const apiKey = process.env.BANKR_API_KEY;\n if (!apiKey) {\n throw new Error('BANKR_API_KEY environment variable not set');\n }\n // Get the Bankr wallet address (cached after first call)\n const walletAddress = await wallet.getAddress();\n // Create BankrWalletProvider which implements IEvmWalletProvider\n const walletProvider = createBankrWalletProvider({\n apiKey,\n apiUrl: process.env.BANKR_API_URL,\n chainId: numericChainId,\n rpcUrl,\n cachedAddress: walletAddress,\n });\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider with Bankr backend', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n address: walletAddress?.slice(0, 10) + '...',\n });\n // Use standard EvmSpokeProvider with our BankrWalletProvider\n // The SDK doesn't care how transactions are signed - it just calls the interface methods\n return new EvmSpokeProvider(walletProvider, // BankrWalletProvider implements IEvmWalletProvider\n chainConfig, rpcUrl);\n}\n/**\n * Create a spoke provider for the given wallet and chain\n *\n * @param walletId - Wallet nickname (e.g., \"main\", \"bankr\", \"trading\")\n * @param chainId - Chain identifier (e.g., \"ethereum\", \"base\")\n */\nasync function createSpokeProvider(walletId, chainId) {\n // Get wallet from unified manager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n // Validate chain support\n validateChainSupport(wallet, chainId);\n const rpcUrl = await getRpcUrl(chainId);\n // Route based on wallet type\n if (wallet.type === 'bankr') {\n // Use BankrWalletProvider for Bankr wallets\n return createBankrSpokeProvider(wallet, chainId, rpcUrl);\n }\n // Local key signing (evm-wallet-skill or env)\n return createLocalSpokeProvider(wallet, chainId, rpcUrl);\n}\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport async function getSpokeProvider(walletId, chainId, raw = false) {\n const cacheKey = `${walletId}:${chainId}`;\n // Check cache\n const cached = providerCache.get(cacheKey);\n if (cached) {\n console.log('[spokeProviderFactory] Using cached provider', {\n walletId,\n chainId,\n });\n return cached;\n }\n // Create new provider\n const provider = await createSpokeProvider(walletId, chainId);\n // Cache the provider\n providerCache.set(cacheKey, provider);\n return provider;\n}\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport function clearProviderCache() {\n providerCache.clear();\n console.log('[spokeProviderFactory] Provider cache cleared');\n}\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport function getCacheStats() {\n return {\n size: providerCache.size,\n keys: Array.from(providerCache.keys()),\n };\n}\n//# sourceMappingURL=spokeProviderFactory.js.map",
818 "inputSchema": {},
819 "outputSchema": null,
820 "icons": null,
821 "annotations": null,
822 "meta": null,
823 "execution": null
824 },
825 {
826 "name": "spokeProviderFactory.d.ts",
827 "title": null,
828 "description": "Script: spokeProviderFactory.d.ts. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n *\n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\nimport { type SpokeProvider } from '@sodax/sdk';\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport declare function getSpokeProvider(walletId: string, chainId: string, raw?: boolean): Promise<SpokeProvider>;\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport declare function clearProviderCache(): void;\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport declare function getCacheStats(): {\n size: number;\n keys: string[];\n};\nexport type { SpokeProvider };\n//# sourceMappingURL=spokeProviderFactory.d.ts.map",
829 "inputSchema": {},
830 "outputSchema": null,
831 "icons": null,
832 "annotations": null,
833 "meta": null,
834 "execution": null
835 },
836 {
837 "name": "policyEngine.d.ts",
838 "title": null,
839 "description": "Script: policyEngine.d.ts. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\nimport { BridgeOperation, PolicyConfig } from '../types';\n/**\n * Policy check result\n */\nexport interface PolicyCheckResult {\n allowed: boolean;\n reason?: string;\n details?: Record<string, unknown>;\n}\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport declare class PolicyEngine {\n private config;\n constructor(policyId?: string);\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n private loadPolicyConfig;\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n private checkChainAllowed;\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n private checkTokenAllowed;\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n private checkRecipientNotBlocked;\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n private checkBridgeAmount;\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n checkBridge(operation: BridgeOperation): Promise<PolicyCheckResult>;\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig(): PolicyConfig;\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies(): string[];\n /**\n * Check swap input amount against USD limits\n */\n private checkSwapAmount;\n /**\n * Check slippage against maximum allowed\n */\n private checkSlippage;\n /**\n * Check a swap operation against all policies\n */\n checkSwap(params: {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n inputAmount: string;\n slippageBps: number;\n policyId?: string;\n }): Promise<PolicyCheckResult>;\n /**\n * Check borrow amount against limits\n */\n private checkBorrowAmount;\n /**\n * Check a money market operation against all policies\n */\n checkMoneyMarket(params: {\n walletId: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n amountUsd?: number;\n operation: 'supply' | 'withdraw' | 'borrow' | 'repay';\n policyId?: string;\n }): Promise<PolicyCheckResult>;\n}\n//# sourceMappingURL=policyEngine.d.ts.map",
840 "inputSchema": {},
841 "outputSchema": null,
842 "icons": null,
843 "annotations": null,
844 "meta": null,
845 "execution": null
846 },
847 {
848 "name": "policyEngine.js",
849 "title": null,
850 "description": "Script: policyEngine.js. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\nimport { normalizeChainId } from '../wallet/types';\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport class PolicyEngine {\n config;\n constructor(policyId) {\n this.config = this.loadPolicyConfig(policyId);\n }\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n loadPolicyConfig(policyId) {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson) {\n return {};\n }\n try {\n const allConfigs = JSON.parse(limitsJson);\n // If policyId is specified, use that config; otherwise use 'default' or empty\n const config = policyId\n ? allConfigs[policyId]\n : allConfigs['default'] || allConfigs;\n if (policyId && !config) {\n return allConfigs['default'] || {};\n }\n return config || {};\n }\n catch (_error) {\n return {};\n }\n }\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n checkChainAllowed(chainId) {\n const { allowedChains } = this.config;\n if (allowedChains && allowedChains.length > 0) {\n const normalizedChain = normalizeChainId(chainId);\n if (!allowedChains.includes(normalizedChain)) {\n return {\n allowed: false,\n reason: `Chain not allowed: ${chainId}. Allowed chains: ${allowedChains.join(', ')}`,\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n checkTokenAllowed(chainId, token) {\n const { allowedTokensByChain } = this.config;\n if (allowedTokensByChain) {\n const normalizedChainForTokens = normalizeChainId(chainId);\n const allowedTokens = allowedTokensByChain[normalizedChainForTokens];\n if (allowedTokens && allowedTokens.length > 0) {\n if (!allowedTokens.includes(token)) {\n return {\n allowed: false,\n reason: `Token not allowed on ${chainId}: ${token}. Allowed tokens: ${allowedTokens.join(', ')}`,\n };\n }\n }\n }\n return { allowed: true };\n }\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n checkRecipientNotBlocked(recipient) {\n const { blockedRecipients } = this.config;\n if (blockedRecipients && blockedRecipients.length > 0) {\n if (blockedRecipients.includes(recipient.toLowerCase())) {\n return {\n allowed: false,\n reason: `Recipient is blocked: ${recipient}`,\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n checkBridgeAmount(token, amount) {\n const { maxBridgeAmountToken } = this.config;\n if (maxBridgeAmountToken) {\n const maxAmount = maxBridgeAmountToken[token];\n if (maxAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxAmount) {\n return {\n allowed: false,\n reason: `Bridge amount ${amount} exceeds maximum ${maxAmount} for token ${token}`,\n details: { maxAllowed: maxAmount, requested: amountNum },\n };\n }\n }\n }\n return { allowed: true };\n }\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n async checkBridge(operation) {\n const { srcChainId, dstChainId, srcToken, dstToken, amount, recipient } = operation;\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed)\n return srcChainCheck;\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed)\n return srcTokenCheck;\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed)\n return dstTokenCheck;\n // Check amount limits\n const amountCheck = this.checkBridgeAmount(srcToken, amount);\n if (!amountCheck.allowed)\n return amountCheck;\n // Check recipient if specified\n if (recipient) {\n const recipientCheck = this.checkRecipientNotBlocked(recipient);\n if (!recipientCheck.allowed)\n return recipientCheck;\n }\n return { allowed: true };\n }\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig() {\n return { ...this.config };\n }\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies() {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson)\n return [];\n try {\n const allConfigs = JSON.parse(limitsJson);\n return Object.keys(allConfigs);\n }\n catch {\n return [];\n }\n }\n // ============================================================================\n // Swap Policy Checks\n // ============================================================================\n /**\n * Check swap input amount against USD limits\n */\n checkSwapAmount(inputAmount, srcToken) {\n const { maxSwapInputUsd, maxSwapInputToken } = this.config;\n // Check per-token limit if configured\n if (maxSwapInputToken) {\n const maxTokenAmount = maxSwapInputToken[srcToken];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(inputAmount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Swap input amount ${inputAmount} exceeds maximum ${maxTokenAmount} for token ${srcToken}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n // Note: USD limit check would require price oracle integration\n // For now, we skip enforcement without prices\n return { allowed: true };\n }\n /**\n * Check slippage against maximum allowed\n */\n checkSlippage(slippageBps) {\n const { maxSlippageBps } = this.config;\n if (maxSlippageBps !== undefined && slippageBps > maxSlippageBps) {\n return {\n allowed: false,\n reason: `Slippage ${slippageBps} bps exceeds maximum allowed ${maxSlippageBps} bps`,\n details: { maxAllowed: maxSlippageBps, requested: slippageBps },\n };\n }\n return { allowed: true };\n }\n /**\n * Check a swap operation against all policies\n */\n async checkSwap(params) {\n const { srcChainId, dstChainId, srcToken, dstToken, inputAmount, slippageBps } = params;\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed)\n return srcChainCheck;\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed)\n return srcTokenCheck;\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed)\n return dstTokenCheck;\n // Check swap amount limits\n const amountCheck = this.checkSwapAmount(inputAmount, srcToken);\n if (!amountCheck.allowed)\n return amountCheck;\n // Check slippage\n const slippageCheck = this.checkSlippage(slippageBps);\n if (!slippageCheck.allowed)\n return slippageCheck;\n return { allowed: true };\n }\n // ============================================================================\n // Money Market Policy Checks\n // ============================================================================\n /**\n * Check borrow amount against limits\n */\n checkBorrowAmount(token, amount, amountUsd) {\n const { maxBorrowUsd, maxBorrowToken } = this.config;\n // Check per-token limit if configured\n if (maxBorrowToken) {\n const maxTokenAmount = maxBorrowToken[token];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Borrow amount ${amount} exceeds maximum ${maxTokenAmount} for token ${token}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n // Check USD limit if amountUsd is provided\n if (maxBorrowUsd !== undefined && amountUsd !== undefined) {\n if (amountUsd > maxBorrowUsd) {\n return {\n allowed: false,\n reason: `Borrow amount $${amountUsd} exceeds maximum $${maxBorrowUsd}`,\n details: { maxAllowed: maxBorrowUsd, requested: amountUsd },\n };\n }\n }\n return { allowed: true };\n }\n /**\n * Check a money market operation against all policies\n */\n async checkMoneyMarket(params) {\n const { chainId, dstChainId, token, amount, amountUsd, operation } = params;\n // Check source chain\n const chainCheck = this.checkChainAllowed(chainId);\n if (!chainCheck.allowed)\n return chainCheck;\n // Check destination chain if cross-chain operation\n if (dstChainId) {\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed)\n return dstChainCheck;\n }\n // Check token\n const tokenCheck = this.checkTokenAllowed(chainId, token);\n if (!tokenCheck.allowed)\n return tokenCheck;\n // Operation-specific checks\n if (operation === 'borrow') {\n const borrowCheck = this.checkBorrowAmount(token, amount, amountUsd);\n if (!borrowCheck.allowed)\n return borrowCheck;\n }\n return { allowed: true };\n }\n}\n//# sourceMappingURL=policyEngine.js.map",
851 "inputSchema": {},
852 "outputSchema": null,
853 "icons": null,
854 "annotations": null,
855 "meta": null,
856 "execution": null
857 },
858 {
859 "name": "index.js",
860 "title": null,
861 "description": "Script: index.js. Code:\n/**\n * Amped DeFi Plugin\n *\n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\nimport { Type } from '@sinclair/typebox';\nimport { getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nimport { getCacheStats } from './providers/spokeProviderFactory';\nimport { PolicyEngine } from './policy/policyEngine';\nimport { getWalletManager } from './wallet/walletManager';\n// Tool schemas and handlers\nimport { SwapQuoteSchema, SwapExecuteSchema, SwapStatusSchema, SwapCancelSchema, handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel } from './tools/swap';\nimport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema, handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute } from './tools/bridge';\nimport { MmSupplySchema, MmWithdrawSchema, MmBorrowSchema, MmRepaySchema, handleMmSupply, handleMmWithdraw, handleMmBorrow, handleMmRepay } from './tools/moneyMarket';\nimport { SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema, MoneyMarketReservesSchema, MoneyMarketPositionsSchema, CrossChainPositionsSchema, UserIntentsSchema, ListWalletsSchema, handleSupportedChains, handleSupportedTokens, handleWalletAddress, handleMoneyMarketReserves, handleMoneyMarketPositions, handleCrossChainPositions, handleUserIntents, handleListWallets } from './tools/discovery';\nimport { AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema, handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet } from './tools/walletManagement';\nimport { PortfolioSummarySchema, handlePortfolioSummary } from './tools/portfolio';\n/**\n * Plugin configuration schema (matches openclaw.plugin.json)\n */\nconst configSchema = Type.Object({\n walletsJson: Type.Optional(Type.String()),\n rpcUrlsJson: Type.Optional(Type.String()),\n mode: Type.Optional(Type.Union([Type.Literal('execute'), Type.Literal('simulate')])),\n dynamicConfig: Type.Optional(Type.Boolean()),\n});\n/**\n * Apply plugin config to environment\n */\nfunction applyConfig(config) {\n if (config.walletsJson && typeof config.walletsJson === 'string') {\n process.env.AMPED_OC_WALLETS_JSON = config.walletsJson;\n }\n if (config.rpcUrlsJson && typeof config.rpcUrlsJson === 'string') {\n process.env.AMPED_OC_RPC_URLS_JSON = config.rpcUrlsJson;\n }\n if (config.mode && typeof config.mode === 'string') {\n process.env.AMPED_OC_MODE = config.mode;\n }\n if (config.dynamicConfig !== undefined) {\n process.env.AMPED_OC_SODAX_DYNAMIC_CONFIG = config.dynamicConfig ? 'true' : 'false';\n }\n}\n/**\n * Validate required environment variables\n */\nfunction validateEnvironment() {\n const missing = [];\n if (!process.env.AMPED_OC_WALLETS_JSON) {\n missing.push('AMPED_OC_WALLETS_JSON');\n }\n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (mode === 'execute' && !process.env.AMPED_OC_RPC_URLS_JSON) {\n missing.push('AMPED_OC_RPC_URLS_JSON');\n }\n return missing;\n}\n/**\n * Deep-clone an object while converting BigInt values to strings\n * Prevents serialization errors when OpenClaw framework handles the details field\n */\nfunction sanitizeBigInt(obj) {\n return JSON.parse(JSON.stringify(obj, (_, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n/**\n * Helper to wrap a handler for OpenClaw's tool format\n */\nfunction wrapHandler(handler) {\n return async (_toolCallId, params) => {\n const result = await handler(params);\n // Sanitize BigInt values in details to prevent framework serialization errors\n const sanitizedResult = sanitizeBigInt(result);\n return {\n content: [{ type: 'text', text: JSON.stringify(result, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }],\n details: sanitizedResult,\n };\n };\n}\n/**\n * OpenClaw Plugin Definition\n */\nexport default {\n id: 'amped-defi',\n name: 'Amped DeFi',\n description: 'DeFi operations plugin for swaps, bridging, and money market via SODAX SDK',\n kind: 'tools',\n configSchema,\n register(api) {\n // Apply config from OpenClaw\n const config = api.pluginConfig || {};\n applyConfig(config);\n // Check for missing env vars (silent)\n validateEnvironment();\n // Initialize core components (async, non-blocking, silent)\n (async () => {\n try {\n await getSodaxClientAsync();\n getCacheStats();\n new PolicyEngine();\n getWalletManager();\n }\n catch (_error) {\n // Silent initialization - errors will surface when tools are used\n }\n })();\n // Register Discovery Tools\n api.registerTool({\n name: 'amped_supported_chains',\n description: 'List all blockchain networks supported by the Amped DeFi plugin',\n parameters: SupportedChainsSchema,\n execute: wrapHandler(handleSupportedChains),\n });\n api.registerTool({\n name: 'amped_supported_tokens',\n description: 'List tokens supported on a specific chain for swaps and bridging',\n parameters: SupportedTokensSchema,\n execute: wrapHandler(handleSupportedTokens),\n });\n api.registerTool({\n name: 'amped_wallet_address',\n description: 'Get the wallet address for a specific wallet ID',\n parameters: WalletAddressSchema,\n execute: wrapHandler(handleWalletAddress),\n });\n api.registerTool({\n name: 'amped_money_market_reserves',\n description: 'Get money market reserve info (APY, utilization, liquidity)',\n parameters: MoneyMarketReservesSchema,\n execute: wrapHandler(handleMoneyMarketReserves),\n });\n api.registerTool({\n name: 'amped_money_market_positions',\n description: 'Get user positions in money market on a single chain',\n parameters: MoneyMarketPositionsSchema,\n execute: wrapHandler(handleMoneyMarketPositions),\n });\n api.registerTool({\n name: 'amped_cross_chain_positions',\n description: 'Get aggregated money market positions across all chains',\n parameters: CrossChainPositionsSchema,\n execute: wrapHandler(handleCrossChainPositions),\n });\n api.registerTool({\n name: 'amped_user_intents',\n description: 'Query user intent history from SODAX API',\n parameters: UserIntentsSchema,\n execute: wrapHandler(handleUserIntents),\n });\n api.registerTool({\n name: 'amped_list_wallets',\n description: 'List ALL configured wallets including evm-wallet-skill, Bankr, and env wallets. Shows nicknames, addresses, types, and supported chains. Use this when user asks \"what wallets do I have\" or \"show my wallets\".',\n parameters: ListWalletsSchema,\n execute: wrapHandler(handleListWallets),\n });\n api.registerTool({\n name: 'amped_portfolio_summary',\n description: 'Get a comprehensive portfolio summary including wallet balances (native + major tokens) across chains and money market positions. Use when user asks for \"portfolio\", \"balances\", or \"summary of positions\".',\n parameters: PortfolioSummarySchema,\n execute: wrapHandler(handlePortfolioSummary),\n });\n // Register Wallet Management Tools\n api.registerTool({\n name: 'amped_add_wallet',\n description: 'Add a new wallet with a nickname (evm-wallet-skill, bankr, or env)',\n parameters: AddWalletSchema,\n execute: wrapHandler(handleAddWallet),\n });\n api.registerTool({\n name: 'amped_rename_wallet',\n description: 'Rename a wallet to a new nickname',\n parameters: RenameWalletSchema,\n execute: wrapHandler(handleRenameWallet),\n });\n api.registerTool({\n name: 'amped_remove_wallet',\n description: 'Remove a wallet from configuration (does not delete funds)',\n parameters: RemoveWalletSchema,\n execute: wrapHandler(handleRemoveWallet),\n });\n api.registerTool({\n name: 'amped_set_default_wallet',\n description: 'Set which wallet to use by default for operations',\n parameters: SetDefaultWalletSchema,\n execute: wrapHandler(handleSetDefaultWallet),\n });\n // Register Swap Tools\n api.registerTool({\n name: 'amped_swap_quote',\n description: 'Get a quote for swapping tokens (same chain or cross-chain)',\n parameters: SwapQuoteSchema,\n execute: wrapHandler(handleSwapQuote),\n });\n api.registerTool({\n name: 'amped_swap_execute',\n description: 'Execute a token swap using a previously obtained quote',\n parameters: SwapExecuteSchema,\n execute: wrapHandler(handleSwapExecute),\n });\n api.registerTool({\n name: 'amped_swap_status',\n description: 'Check the status of a swap/bridge operation by intent ID',\n parameters: SwapStatusSchema,\n execute: wrapHandler(handleSwapStatus),\n });\n api.registerTool({\n name: 'amped_swap_cancel',\n description: 'Cancel a pending swap/bridge operation',\n parameters: SwapCancelSchema,\n execute: wrapHandler(handleSwapCancel),\n });\n // Register Bridge Tools\n api.registerTool({\n name: 'amped_bridge_discover',\n description: 'Discover available bridge routes between chains',\n parameters: BridgeDiscoverSchema,\n execute: wrapHandler(handleBridgeDiscover),\n });\n api.registerTool({\n name: 'amped_bridge_quote',\n description: 'Get a quote for bridging tokens between chains',\n parameters: BridgeQuoteSchema,\n execute: wrapHandler(handleBridgeQuote),\n });\n api.registerTool({\n name: 'amped_bridge_execute',\n description: 'Execute a bridge transfer using a previously obtained quote',\n parameters: BridgeExecuteSchema,\n execute: wrapHandler(handleBridgeExecute),\n });\n // Register Money Market Tools\n api.registerTool({\n name: 'amped_mm_supply',\n description: 'Supply (deposit) tokens to money market to earn interest',\n parameters: MmSupplySchema,\n execute: wrapHandler(handleMmSupply),\n });\n api.registerTool({\n name: 'amped_mm_withdraw',\n description: 'Withdraw supplied tokens from money market',\n parameters: MmWithdrawSchema,\n execute: wrapHandler(handleMmWithdraw),\n });\n api.registerTool({\n name: 'amped_mm_borrow',\n description: 'Borrow tokens from money market (cross-chain capable)',\n parameters: MmBorrowSchema,\n execute: wrapHandler(handleMmBorrow),\n });\n api.registerTool({\n name: 'amped_mm_repay',\n description: 'Repay borrowed tokens to money market',\n parameters: MmRepaySchema,\n execute: wrapHandler(handleMmRepay),\n });\n // Register cleanup service\n api.registerService({\n id: 'amped-defi',\n start: () => { },\n stop: async () => {\n resetSodaxClient();\n },\n });\n },\n};\n// Re-export types and utilities for external use\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\n// Legacy exports for backward compatibility\nexport async function activate() {\n // Deprecated - use default export\n}\nexport async function deactivate() {\n resetSodaxClient();\n}\n//# sourceMappingURL=index.js.map",
862 "inputSchema": {},
863 "outputSchema": null,
864 "icons": null,
865 "annotations": null,
866 "meta": null,
867 "execution": null
868 },
869 {
870 "name": "setup.js",
871 "title": null,
872 "description": "Script: setup.js. Code:\n/**\n * Jest Test Setup\n */\n// Mock console methods to reduce noise during tests\nglobal.console = {\n ...console,\n log: jest.fn(),\n debug: jest.fn(),\n info: jest.fn(),\n warn: jest.fn(),\n error: jest.fn(),\n};\n// Set default test environment\nprocess.env.AMPED_OC_MODE = 'execute';\nprocess.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n test: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123def456',\n },\n});\nprocess.env.AMPED_OC_RPC_URLS_JSON = JSON.stringify({\n ethereum: 'https://eth-mainnet.example.com',\n arbitrum: 'https://arb-mainnet.example.com',\n sonic: 'https://rpc.sonic.example.com',\n});\n// Global test timeout\njest.setTimeout(10000);\n// Clean up after each test\nafterEach(() => {\n jest.clearAllMocks();\n});\n//# sourceMappingURL=setup.js.map",
873 "inputSchema": {},
874 "outputSchema": null,
875 "icons": null,
876 "annotations": null,
877 "meta": null,
878 "execution": null
879 },
880 {
881 "name": "setup.d.ts",
882 "title": null,
883 "description": "Script: setup.d.ts. Code:\n/**\n * Jest Test Setup\n */\n//# sourceMappingURL=setup.d.ts.map",
884 "inputSchema": {},
885 "outputSchema": null,
886 "icons": null,
887 "annotations": null,
888 "meta": null,
889 "execution": null
890 },
891 {
892 "name": "basic-usage.ts",
893 "title": null,
894 "description": "Script: basic-usage.ts. Code:\n/**\n * Basic Usage Examples for Amped DeFi Plugin\n */\n\nimport { activate, deactivate } from '../src/index';\n\n// Mock agent tools for demonstration\nconst mockAgentTools = {\n register: (tool: { name: string; summary: string; schema: unknown; handler: Function }) => {\n console.log(`Registered: ${tool.name}`);\n },\n};\n\nasync function main() {\n console.log('=== Amped DeFi Plugin Examples ===\\n');\n\n // Initialize plugin\n console.log('1. Initializing plugin...');\n await activate(mockAgentTools as any);\n console.log();\n\n // Example workflows\n await demonstrateCrossChainPositionView();\n await demonstrateSwapWorkflow();\n await demonstrateCrossChainBorrow();\n\n // Cleanup\n console.log('\\n5. Deactivating plugin...');\n await deactivate();\n}\n\nasync function demonstrateCrossChainPositionView() {\n console.log('2. Cross-Chain Position View Example');\n console.log(' Tool: amped_cross_chain_positions');\n console.log(' Purpose: Get unified portfolio view across all chains');\n console.log(' Parameters: { walletId: \"main\" }');\n console.log(' Returns:');\n console.log(' - Total supply/borrow across all chains');\n console.log(' - Health factor and liquidation risk');\n console.log(' - Available borrowing power');\n console.log(' - Weighted APYs and net yield');\n console.log(' - Per-chain breakdowns');\n console.log(' - Risk metrics and recommendations');\n console.log();\n}\n\nasync function demonstrateSwapWorkflow() {\n console.log('3. Swap Workflow Example');\n console.log(' Step 1: Get Quote');\n console.log(' Tool: amped_swap_quote');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' srcChainId: \"ethereum\",');\n console.log(' dstChainId: \"arbitrum\",');\n console.log(' srcToken: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", // USDC');\n console.log(' dstToken: \"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT');\n console.log(' amount: \"1000\",');\n console.log(' type: \"exact_input\",');\n console.log(' slippageBps: 100');\n console.log(' }');\n console.log();\n console.log(' Step 2: Execute Swap');\n console.log(' Tool: amped_swap_execute');\n console.log(' Parameters: { walletId, quote, maxSlippageBps: 100 }');\n console.log();\n}\n\nasync function demonstrateCrossChainBorrow() {\n console.log('4. Cross-Chain Money Market Borrow Example');\n console.log(' Feature: Supply on Chain A, Borrow to Chain B');\n console.log();\n console.log(' Step 1: Supply on Ethereum');\n console.log(' Tool: amped_mm_supply');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' chainId: \"ethereum\",');\n console.log(' token: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\", // USDC');\n console.log(' amount: \"50000\",');\n console.log(' useAsCollateral: true');\n console.log(' }');\n console.log();\n console.log(' Step 2: Borrow to Arbitrum (Cross-Chain!)');\n console.log(' Tool: amped_mm_borrow');\n console.log(' Parameters: {');\n console.log(' walletId: \"main\",');\n console.log(' chainId: \"ethereum\", // Collateral source');\n console.log(' dstChainId: \"arbitrum\", // Borrowed tokens destination');\n console.log(' token: \"0xaf88d065e77c8cC2239327C5EDb3A432268e5831\", // USDT');\n console.log(' amount: \"20000\",');\n console.log(' interestRateMode: 2 // Variable rate');\n console.log(' }');\n console.log();\n console.log(' Result: User receives 20k USDT on Arbitrum while collateral stays on Ethereum!');\n console.log();\n}\n\n// Run examples\nmain().catch(console.error);\n",
895 "inputSchema": {},
896 "outputSchema": null,
897 "icons": null,
898 "annotations": null,
899 "meta": null,
900 "execution": null
901 },
902 {
903 "name": "tokenResolver.ts",
904 "title": null,
905 "description": "Script: tokenResolver.ts. Code:\n/**\n * Token Resolution Utility\n * \n * Resolves token symbols to addresses using the SODAX SDK config service.\n * Supports case-insensitive symbol lookup with caching.\n * Handles both EVM (0x) and Solana (base58) address formats.\n */\n\nimport type { Token } from '@sodax/types';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\n\n// Cache tokens per chain to avoid repeated lookups\nconst tokenCache = new Map<string, Token[]>();\n\n// Native token addresses\nconst EVM_NATIVE_ADDRESS = '0x0000000000000000000000000000000000000000';\nconst SOLANA_NATIVE_ADDRESS = '11111111111111111111111111111111';\n\n// Native token configs per chain (18 decimals for all EVM chains, 9 for Solana)\nconst NATIVE_TOKENS: Record<string, { symbol: string; name: string; decimals: number; address: string }> = {\n sonic: { symbol: 'S', name: 'Sonic', decimals: 18, address: EVM_NATIVE_ADDRESS },\n ethereum: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa4b1.arbitrum': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x2105.base': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa.optimism': { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x38.bsc': { symbol: 'BNB', name: 'BNB', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0x89.polygon': { symbol: 'POL', name: 'POL', decimals: 18, address: EVM_NATIVE_ADDRESS },\n '0xa86a.avax': { symbol: 'AVAX', name: 'Avalanche', decimals: 18, address: EVM_NATIVE_ADDRESS },\n hyper: { symbol: 'HYPE', name: 'Hyperliquid', decimals: 18, address: EVM_NATIVE_ADDRESS },\n lightlink: { symbol: 'ETH', name: 'Ether', decimals: 18, address: EVM_NATIVE_ADDRESS },\n solana: { symbol: 'SOL', name: 'Solana', decimals: 9, address: SOLANA_NATIVE_ADDRESS },\n};\n\n// Fallback token list for common chains when SDK config is unavailable\nconst FALLBACK_TOKENS: Record<string, { address: string; symbol: string; name: string; decimals: number }[]> = {\n '0x2105.base': [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'ethereum': [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', symbol: 'DAI', name: 'Dai Stablecoin', decimals: 18 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n '0xa4b1.arbitrum': [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'ETH', name: 'Ether', decimals: 18 },\n ],\n 'sonic': [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n { address: '0x6047828dc181963ba44974801FF68e538dA5eaF9', symbol: 'USDT', name: 'Tether USD', decimals: 6 },\n { address: EVM_NATIVE_ADDRESS, symbol: 'S', name: 'Sonic', decimals: 18 },\n ],\n 'solana': [\n { address: SOLANA_NATIVE_ADDRESS, symbol: 'SOL', name: 'Solana', decimals: 9 },\n { address: '3rSPCLNEF7Quw4wX8S1NyKivELoyij8eYA2gJwBgt4V5', symbol: 'bnUSD', name: 'bnUSD', decimals: 9 },\n { address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', name: 'USD Coin', decimals: 6 },\n ],\n};\n\n// Chain type detection\nconst SOLANA_CHAINS = new Set(['solana']);\n\nfunction isSolanaChain(chainId: string): boolean {\n return SOLANA_CHAINS.has(chainId.toLowerCase());\n}\n\n/**\n * Check if an address is a native token for the given chain\n */\nfunction isNativeToken(address: string, chainId?: string): boolean {\n const addrLower = address.toLowerCase();\n if (chainId && isSolanaChain(chainId)) {\n return addrLower === SOLANA_NATIVE_ADDRESS.toLowerCase();\n }\n return addrLower === EVM_NATIVE_ADDRESS;\n}\n\n/**\n * Get native token info for a chain\n */\nfunction getNativeTokenInfo(chainId: string): { address: string; symbol: string; name: string; decimals: number } | null {\n const native = NATIVE_TOKENS[chainId];\n if (!native) return null;\n return { ...native };\n}\n\n/**\n * Check if a string is a valid EVM address (0x format)\n */\nfunction isEvmAddress(value: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/i.test(value);\n}\n\n/**\n * Check if a string is a valid Solana address (base58 format)\n * Solana addresses are 32-44 characters, base58 encoded\n */\nfunction isSolanaAddress(value: string): boolean {\n // Base58 charset: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\n // Excludes: 0, O, I, l\n return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);\n}\n\n/**\n * Check if a string is a valid token address (EVM or Solana)\n */\nfunction isValidTokenAddress(value: string, chainId?: string): boolean {\n if (chainId && isSolanaChain(chainId)) {\n return isSolanaAddress(value);\n }\n // For EVM chains or unknown chains, check both formats\n return isEvmAddress(value) || isSolanaAddress(value);\n}\n\n/**\n * Populate the token cache for a chain from SDK config service\n * This is the canonical way to get tokens - used by both resolveToken and getTokenInfo\n */\nfunction populateTokenCache(chainId: string): Token[] {\n // Convert to SDK chain ID format (e.g., \"base\" -> \"0x2105.base\")\n const sdkChainId = toSodaxChainId(chainId);\n let tokens = tokenCache.get(chainId);\n if (tokens) return tokens;\n \n try {\n const client = getSodaxClient();\n const configService = (client as any).config;\n \n if (configService?.getSupportedSwapTokensByChainId) {\n // Preferred method - returns readonly Token[]\n tokens = [...configService.getSupportedSwapTokensByChainId(sdkChainId)] as Token[];\n } else if (configService?.getSwapTokensByChainId) {\n tokens = configService.getSwapTokensByChainId(sdkChainId) as Token[];\n } else if (configService?.getSwapTokens) {\n const allTokens = configService.getSwapTokens();\n tokens = allTokens[sdkChainId] || [];\n } else {\n console.warn(`[tokenResolver] configService not available for chain ${chainId}`);\n tokens = [];\n }\n \n // Log what we got from SDK\n if (tokens && tokens.length > 0) {\n console.log(`[tokenResolver] Loaded ${tokens.length} tokens from SDK for ${chainId}`);\n }\n } catch (err) {\n console.error(`[tokenResolver] Failed to fetch tokens for chain ${chainId}:`, err);\n tokens = [];\n }\n \n // Use fallback tokens if SDK returned empty list\n if ((!tokens || tokens.length === 0) && FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId]) {\n console.log(`[tokenResolver] Using fallback token list for ${chainId}`);\n tokens = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId] as unknown as Token[];\n }\n \n tokenCache.set(chainId, tokens || []);\n return tokens || [];\n}\n\n/**\n * Resolve a token symbol or address to a normalized address\n * \n * @param chainId - The chain ID to resolve the token on\n * @param tokenInput - Token symbol (e.g., \"USDC\") or address\n * @returns The token address (lowercase for EVM, original case for Solana)\n * @throws Error if token symbol is not found on the chain\n */\nexport async function resolveToken(\n chainId: string,\n tokenInput: string\n): Promise<string> {\n // If already a valid address, normalize and return\n if (isValidTokenAddress(tokenInput, chainId)) {\n // EVM addresses are lowercased, Solana addresses preserve case\n return isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n }\n\n // Get tokens from cache or SDK\n const tokens = populateTokenCache(chainId);\n\n // Find by symbol (case-insensitive)\n const symbolUpper = tokenInput.toUpperCase();\n const token = tokens.find(t => t.symbol.toUpperCase() === symbolUpper);\n \n if (!token) {\n // Build helpful error message with available tokens\n const available = tokens.length > 0 \n ? tokens.map(t => t.symbol).join(', ')\n : 'No tokens loaded';\n throw new Error(\n `Unknown token \"${tokenInput}\" on chain ${chainId}. ` +\n `Available: ${available}. ` +\n `Alternatively, provide the token address directly.`\n );\n }\n\n return isEvmAddress(token.address) ? token.address.toLowerCase() : token.address;\n}\n\n/**\n * Resolve multiple tokens at once (for efficiency)\n * \n * @param chainId - The chain ID\n * @param tokenInputs - Array of token symbols or addresses\n * @returns Array of resolved addresses\n */\nexport async function resolveTokens(\n chainId: string,\n tokenInputs: string[]\n): Promise<string[]> {\n return Promise.all(tokenInputs.map(t => resolveToken(chainId, t)));\n}\n\n/**\n * Get token info by symbol or address\n * Returns null if not found\n */\nexport async function getTokenInfo(\n chainId: string,\n tokenInput: string\n): Promise<Token | null> {\n const sdkChainId = toSodaxChainId(chainId);\n // Handle native tokens first\n if (isValidTokenAddress(tokenInput, chainId) && isNativeToken(tokenInput, chainId)) {\n const nativeInfo = getNativeTokenInfo(chainId);\n if (nativeInfo) {\n return nativeInfo as unknown as Token;\n }\n }\n\n // Get tokens from cache or SDK (same path as resolveToken)\n const tokens = populateTokenCache(chainId);\n\n // Find by address or symbol\n if (isValidTokenAddress(tokenInput, chainId)) {\n // Normalize address for comparison\n const addrNorm = isEvmAddress(tokenInput) ? tokenInput.toLowerCase() : tokenInput;\n const found = tokens.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (found) {\n return found;\n }\n // Check fallback tokens even if SDK tokens were loaded\n const fallback = FALLBACK_TOKENS[chainId] || FALLBACK_TOKENS[sdkChainId];\n if (fallback) {\n const fallbackToken = fallback.find(t => {\n const tokenAddr = isEvmAddress(t.address) ? t.address.toLowerCase() : t.address;\n return tokenAddr === addrNorm;\n });\n if (fallbackToken) {\n console.log(`[tokenResolver] Found ${fallbackToken.symbol} in fallback for ${chainId}`);\n return fallbackToken as unknown as Token;\n }\n }\n return null;\n } else {\n const symbolUpper = tokenInput.toUpperCase();\n return tokens.find(t => t.symbol.toUpperCase() === symbolUpper) || null;\n }\n}\n\n/**\n * Clear the token cache (useful for testing or after config refresh)\n */\nexport function clearTokenCache(): void {\n tokenCache.clear();\n}\n\n/**\n * Get all cached tokens for a chain\n */\nexport function getCachedTokens(chainId: string): Token[] | undefined {\n return tokenCache.get(chainId);\n}\n\n// Export address validation utilities for use by other modules\nexport { isEvmAddress, isSolanaAddress, isValidTokenAddress, isSolanaChain };\n",
906 "inputSchema": {},
907 "outputSchema": null,
908 "icons": null,
909 "annotations": null,
910 "meta": null,
911 "execution": null
912 },
913 {
914 "name": "priceService.ts",
915 "title": null,
916 "description": "Script: priceService.ts. Code:\n/**\n * Price Service - Fetches USD prices from SODAX reserves\n *\n * Uses the money market reserve data to get accurate USD prices\n * for tokens supported by the protocol.\n *\n * @module utils/priceService\n */\n\nimport { getSodaxClient } from '../sodax/client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TokenPrice {\n symbol: string;\n priceUsd: number;\n underlyingAsset: string;\n}\n\nexport interface PriceMap {\n /** Map of symbol (lowercase) to USD price */\n bySymbol: Map<string, number>;\n /** Map of address (lowercase) to USD price */\n byAddress: Map<string, number>;\n /** Last update timestamp */\n timestamp: number;\n}\n\n// ============================================================================\n// Cache\n// ============================================================================\n\nlet cachedPrices: PriceMap | null = null;\nconst CACHE_TTL_MS = 60_000; // 1 minute cache\n\n// ============================================================================\n// Price Fetching\n// ============================================================================\n\n/**\n * Fetch token prices from SODAX money market reserves\n *\n * The reserves contain `priceInMarketReferenceCurrency` which represents\n * the price in 8 decimal USD (100000000 = $1.00)\n */\nexport async function fetchTokenPrices(): Promise<PriceMap> {\n // Return cached if fresh\n if (cachedPrices && Date.now() - cachedPrices.timestamp < CACHE_TTL_MS) {\n return cachedPrices;\n }\n\n console.log('[priceService] Fetching token prices from SODAX reserves');\n\n const sodax = await getSodaxClient();\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n\n const bySymbol = new Map<string, number>();\n const byAddress = new Map<string, number>();\n\n // Market reference currency decimals (typically 8)\n const PRICE_DECIMALS = 8;\n\n for (const reserve of reserves.reservesData) {\n // priceInMarketReferenceCurrency is a string representing the raw value\n const priceRaw = BigInt(reserve.priceInMarketReferenceCurrency);\n const priceUsd = Number(priceRaw) / Math.pow(10, PRICE_DECIMALS);\n\n // Use symbol for matching (e.g., \"sodaUSDC\" -> \"USDC\")\n const symbol = reserve.symbol.toLowerCase();\n const normalizedSymbol = normalizeSymbol(reserve.symbol);\n const address = reserve.underlyingAsset.toLowerCase();\n\n bySymbol.set(symbol, priceUsd);\n bySymbol.set(normalizedSymbol, priceUsd);\n byAddress.set(address, priceUsd);\n\n console.log(`[priceService] ${reserve.symbol}: $${priceUsd.toFixed(4)}`);\n }\n\n cachedPrices = {\n bySymbol,\n byAddress,\n timestamp: Date.now(),\n };\n\n console.log(`[priceService] Cached ${bySymbol.size} token prices`);\n\n return cachedPrices;\n}\n\n/**\n * Normalize SODAX symbol to standard symbol\n * e.g., \"sodaUSDC\" -> \"usdc\", \"sodaETH\" -> \"eth\"\n */\nfunction normalizeSymbol(symbol: string): string {\n const lower = symbol.toLowerCase();\n if (lower.startsWith('soda')) {\n return lower.slice(4); // Remove 'soda' prefix\n }\n return lower;\n}\n\n/**\n * Get USD price for a token by symbol\n */\nexport async function getTokenPriceBySymbol(symbol: string): Promise<number | null> {\n const prices = await fetchTokenPrices();\n const normalizedSymbol = symbol.toLowerCase();\n\n // Try exact match first\n if (prices.bySymbol.has(normalizedSymbol)) {\n return prices.bySymbol.get(normalizedSymbol)!;\n }\n\n // Try with 'soda' prefix\n const sodaSymbol = 'soda' + normalizedSymbol;\n if (prices.bySymbol.has(sodaSymbol)) {\n return prices.bySymbol.get(sodaSymbol)!;\n }\n\n return null;\n}\n\n/**\n * Get USD price for a token by address\n */\nexport async function getTokenPriceByAddress(address: string): Promise<number | null> {\n const prices = await fetchTokenPrices();\n return prices.byAddress.get(address.toLowerCase()) ?? null;\n}\n\n/**\n * Calculate USD value for a token amount\n */\nexport async function calculateUsdValue(\n symbol: string,\n amount: string | number\n): Promise<number | null> {\n const price = await getTokenPriceBySymbol(symbol);\n if (price === null) return null;\n\n const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;\n return amountNum * price;\n}\n\n/**\n * Clear the price cache (useful for testing)\n */\nexport function clearPriceCache(): void {\n cachedPrices = null;\n}\n",
917 "inputSchema": {},
918 "outputSchema": null,
919 "icons": null,
920 "annotations": null,
921 "meta": null,
922 "execution": null
923 },
924 {
925 "name": "sodaxApi.ts",
926 "title": null,
927 "description": "Script: sodaxApi.ts. Code:\n/**\n * SODAX API Client\n * \n * Provides access to SODAX backend API endpoints for querying intents,\n * user history, and other off-chain data.\n */\n\nimport { ErrorCode, AmpedDefiError } from './errors';\n\nconst DEFAULT_BASE_URL = 'https://canary-api.sodax.com';\nconst API_VERSION = 'v1';\n\nexport interface SodaxApiConfig {\n baseUrl?: string;\n apiKey?: string;\n timeoutMs?: number;\n}\n\nexport interface PaginationParams {\n offset?: number;\n limit?: number;\n}\n\nexport interface PaginatedResponse<T> {\n items: T[];\n total: number;\n offset: number;\n limit: number;\n}\n\nexport interface IntentState {\n exists: boolean;\n remainingInput: string;\n receivedOutput: string;\n pendingPayment: boolean;\n}\n\nexport interface IntentEvent {\n eventType: string;\n txHash: string;\n logIndex: number;\n blockNumber: number;\n intentState: IntentState;\n}\n\nexport interface IntentDetails {\n intentId: string;\n creator: string;\n inputToken: string;\n outputToken: string;\n inputAmount: string;\n minOutputAmount: string;\n deadline: string;\n allowPartialFill: boolean;\n srcChain: number;\n dstChain: number;\n srcAddress: string;\n dstAddress: string;\n solver: string;\n data: string;\n}\n\nexport interface UserIntent {\n intentHash: string;\n txHash: string;\n logIndex: number;\n chainId: number;\n blockNumber: number;\n open: boolean;\n intent: IntentDetails;\n events: IntentEvent[];\n createdAt: string;\n}\n\nexport interface UserIntentFilters {\n open?: boolean;\n srcChain?: number;\n dstChain?: number;\n inputToken?: string;\n outputToken?: string;\n}\n\nexport class SodaxApiClient {\n private baseUrl: string;\n private apiKey?: string;\n private timeoutMs: number;\n\n constructor(config: SodaxApiConfig = {}) {\n this.baseUrl = config.baseUrl || process.env.SODAX_API_URL || DEFAULT_BASE_URL;\n this.apiKey = config.apiKey || process.env.SODAX_API_KEY;\n this.timeoutMs = config.timeoutMs || 30000;\n }\n\n /**\n * Get intent by intentHash\n * Most reliable lookup method - works for all intents\n */\n async getIntentByHash(intentHash: string): Promise<UserIntent | null> {\n const normalizedHash = intentHash.startsWith('0x') ? intentHash : `0x${intentHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/${normalizedHash}`;\n\n console.log('[sodaxApi] Fetching intent by hash:', { intentHash: normalizedHash });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as UserIntent;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch intent by hash: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Get intent by transaction hash\n * NOTE: This expects the HUB chain (Sonic) transaction hash, NOT spoke chain tx\n */\n async getIntentByTxHash(txHash: string): Promise<UserIntent | null> {\n const normalizedHash = txHash.startsWith('0x') ? txHash : `0x${txHash}`;\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/tx/${normalizedHash}`;\n\n console.log('[sodaxApi] Fetching intent by txHash:', { txHash: normalizedHash });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as UserIntent;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch intent by txHash: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getUserIntents(\n userAddress: string,\n pagination: PaginationParams = {},\n filters?: UserIntentFilters\n ): Promise<PaginatedResponse<UserIntent>> {\n if (!this.isValidAddress(userAddress)) {\n throw new AmpedDefiError(\n ErrorCode.WALLET_INVALID_ADDRESS,\n `Invalid user address: ${userAddress}`\n );\n }\n\n const normalizedAddress = userAddress.toLowerCase();\n const queryParams = new URLSearchParams();\n \n if (pagination.offset !== undefined) {\n queryParams.set('offset', pagination.offset.toString());\n }\n if (pagination.limit !== undefined) {\n queryParams.set('limit', pagination.limit.toString());\n }\n\n if (filters) {\n if (filters.open !== undefined) queryParams.set('open', filters.open.toString());\n if (filters.srcChain !== undefined) queryParams.set('srcChain', filters.srcChain.toString());\n if (filters.dstChain !== undefined) queryParams.set('dstChain', filters.dstChain.toString());\n if (filters.inputToken) queryParams.set('inputToken', filters.inputToken.toLowerCase());\n if (filters.outputToken) queryParams.set('outputToken', filters.outputToken.toLowerCase());\n }\n\n const queryString = queryParams.toString();\n const url = `${this.baseUrl}/${API_VERSION}/be/intent/user/${normalizedAddress}${queryString ? `?${queryString}` : ''}`;\n\n console.log('[sodaxApi] Fetching user intents:', { userAddress: normalizedAddress });\n\n try {\n const response = await this.fetchWithTimeout(url);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `SODAX API error: ${response.status} ${errorText}`\n );\n }\n\n return await response.json() as PaginatedResponse<UserIntent>;\n } catch (error) {\n if (error instanceof AmpedDefiError) throw error;\n throw new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n `Failed to fetch user intents: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getOpenIntents(\n userAddress: string,\n pagination: PaginationParams = {}\n ): Promise<PaginatedResponse<UserIntent>> {\n return this.getUserIntents(userAddress, pagination, { open: true });\n }\n\n async getIntentHistory(\n userAddress: string,\n pagination: PaginationParams = {}\n ): Promise<PaginatedResponse<UserIntent>> {\n return this.getUserIntents(userAddress, pagination, { open: false });\n }\n\n private async fetchWithTimeout(url: string): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const headers: Record<string, string> = { 'Accept': 'application/json' };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n return await fetch(url, { signal: controller.signal, headers });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private isValidAddress(address: string): boolean {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n }\n}\n\nlet apiClient: SodaxApiClient | null = null;\n\nexport function getSodaxApiClient(config?: SodaxApiConfig): SodaxApiClient {\n if (!apiClient) {\n apiClient = new SodaxApiClient(config);\n }\n return apiClient;\n}\n\nexport function resetSodaxApiClient(): void {\n apiClient = null;\n}\n",
928 "inputSchema": {},
929 "outputSchema": null,
930 "icons": null,
931 "annotations": null,
932 "meta": null,
933 "execution": null
934 },
935 {
936 "name": "errors.ts",
937 "title": null,
938 "description": "Script: errors.ts. Code:\n/**\n * Error Handling Utilities\n * \n * Provides standardized error handling, error codes, and user-friendly error messages\n * for all Amped DeFi operations.\n */\n\n/**\n * Standard error codes for Amped DeFi operations\n */\nexport enum ErrorCode {\n // Policy errors\n POLICY_SLIPPAGE_EXCEEDED = 'POLICY_SLIPPAGE_EXCEEDED',\n POLICY_SPEND_LIMIT_EXCEEDED = 'POLICY_SPEND_LIMIT_EXCEEDED',\n POLICY_CHAIN_NOT_ALLOWED = 'POLICY_CHAIN_NOT_ALLOWED',\n POLICY_TOKEN_NOT_ALLOWED = 'POLICY_TOKEN_NOT_ALLOWED',\n POLICY_RECIPIENT_BLOCKED = 'POLICY_RECIPIENT_BLOCKED',\n\n // Wallet errors\n WALLET_NOT_FOUND = 'WALLET_NOT_FOUND',\n WALLET_INVALID_ADDRESS = 'WALLET_INVALID_ADDRESS',\n WALLET_MISSING_PRIVATE_KEY = 'WALLET_MISSING_PRIVATE_KEY',\n WALLET_RESOLUTION_FAILED = 'WALLET_RESOLUTION_FAILED',\n\n // Chain/Provider errors\n CHAIN_NOT_SUPPORTED = 'CHAIN_NOT_SUPPORTED',\n RPC_URL_NOT_CONFIGURED = 'RPC_URL_NOT_CONFIGURED',\n PROVIDER_CREATION_FAILED = 'PROVIDER_CREATION_FAILED',\n SONIC_PROVIDER_REQUIRED = 'SONIC_PROVIDER_REQUIRED',\n\n // Token errors\n INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',\n INSUFFICIENT_ALLOWANCE = 'INSUFFICIENT_ALLOWANCE',\n TOKEN_NOT_SUPPORTED = 'TOKEN_NOT_SUPPORTED',\n TOKEN_DECIMALS_NOT_FOUND = 'TOKEN_DECIMALS_NOT_FOUND',\n\n // Operation errors\n QUOTE_EXPIRED = 'QUOTE_EXPIRED',\n QUOTE_NOT_FOUND = 'QUOTE_NOT_FOUND',\n BRIDGE_NOT_AVAILABLE = 'BRIDGE_NOT_AVAILABLE',\n SWAP_EXECUTION_FAILED = 'SWAP_EXECUTION_FAILED',\n BRIDGE_EXECUTION_FAILED = 'BRIDGE_EXECUTION_FAILED',\n MM_HEALTH_FACTOR_LOW = 'MM_HEALTH_FACTOR_LOW',\n MM_CROSS_CHAIN_NOT_SUPPORTED = 'MM_CROSS_CHAIN_NOT_SUPPORTED',\n MM_INSUFFICIENT_COLLATERAL = 'MM_INSUFFICIENT_COLLATERAL',\n MM_POSITION_NOT_FOUND = 'MM_POSITION_NOT_FOUND',\n\n // Transaction errors\n TRANSACTION_FAILED = 'TRANSACTION_FAILED',\n TRANSACTION_TIMEOUT = 'TRANSACTION_TIMEOUT',\n TRANSACTION_REJECTED = 'TRANSACTION_REJECTED',\n TRANSACTION_SIMULATION_FAILED = 'TRANSACTION_SIMULATION_FAILED',\n\n // SDK/Configuration errors\n SDK_NOT_INITIALIZED = 'SDK_NOT_INITIALIZED',\n SDK_INITIALIZATION_FAILED = 'SDK_INITIALIZATION_FAILED',\n INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',\n CONFIG_PARSE_ERROR = 'CONFIG_PARSE_ERROR',\n\n // Unknown/Generic\n UNKNOWN_ERROR = 'UNKNOWN_ERROR',\n OPERATION_CANCELLED = 'OPERATION_CANCELLED',\n}\n\n/**\n * Error severity levels\n */\nexport enum ErrorSeverity {\n INFO = 'info',\n WARNING = 'warning',\n ERROR = 'error',\n CRITICAL = 'critical',\n}\n\n/**\n * Structured error information\n */\nexport interface AmpedError {\n code: ErrorCode;\n message: string;\n severity: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n}\n\n/**\n * Error context for logging\n */\nexport interface ErrorContext {\n operation?: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokens?: string[];\n amount?: string;\n requestId?: string;\n txHash?: string;\n [key: string]: unknown;\n}\n\n/**\n * Amped DeFi Error class\n */\nexport class AmpedDefiError extends Error {\n public readonly code: ErrorCode;\n public readonly severity: ErrorSeverity;\n public readonly remediation?: string;\n public readonly details?: Record<string, unknown>;\n public readonly context?: ErrorContext;\n\n constructor(\n code: ErrorCode,\n message: string,\n options?: {\n severity?: ErrorSeverity;\n remediation?: string;\n details?: Record<string, unknown>;\n context?: ErrorContext;\n cause?: Error;\n }\n ) {\n super(message, { cause: options?.cause });\n this.name = 'AmpedDefiError';\n this.code = code;\n this.severity = options?.severity || ErrorSeverity.ERROR;\n this.remediation = options?.remediation;\n this.details = options?.details;\n this.context = options?.context;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, AmpedDefiError);\n }\n }\n\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): AmpedError {\n return {\n code: this.code,\n message: this.message,\n severity: this.severity,\n remediation: this.remediation,\n details: this.details,\n };\n }\n\n /**\n * Get user-friendly error message\n */\n toUserMessage(): string {\n let msg = `[${this.code}] ${this.message}`;\n if (this.remediation) {\n msg += `\\n\\nSuggestion: ${this.remediation}`;\n }\n return msg;\n }\n}\n\n// ============================================================================\n// Error Factory Functions\n// ============================================================================\n\n/**\n * Create a policy error\n */\nexport function createPolicyError(\n code: ErrorCode,\n message: string,\n details?: { current?: unknown; limit?: unknown; [key: string]: unknown },\n context?: ErrorContext\n): AmpedDefiError {\n const remediation = getPolicyRemediation(code, details);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.WARNING,\n remediation,\n details,\n context,\n });\n}\n\n/**\n * Create a wallet error\n */\nexport function createWalletError(\n code: ErrorCode,\n walletId: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n const message = getWalletErrorMessage(code, walletId);\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getWalletRemediation(code),\n context: { ...context, walletId },\n cause,\n });\n}\n\n/**\n * Create a transaction error\n */\nexport function createTransactionError(\n code: ErrorCode,\n message: string,\n txHash?: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.ERROR,\n remediation: getTransactionRemediation(code),\n details: txHash ? { txHash } : undefined,\n context: txHash ? { ...context, txHash } : context,\n cause,\n });\n}\n\n/**\n * Create an SDK error\n */\nexport function createSDKError(\n code: ErrorCode,\n message: string,\n cause?: Error,\n context?: ErrorContext\n): AmpedDefiError {\n return new AmpedDefiError(code, message, {\n severity: ErrorSeverity.CRITICAL,\n remediation: 'Please check your configuration and try again. If the issue persists, contact support.',\n context,\n cause,\n });\n}\n\n/**\n * Wrap an unknown error into an AmpedDefiError\n */\nexport function wrapError(\n error: unknown,\n fallbackCode: ErrorCode = ErrorCode.UNKNOWN_ERROR,\n context?: ErrorContext\n): AmpedDefiError {\n if (error instanceof AmpedDefiError) {\n return error;\n }\n\n if (error instanceof Error) {\n // Try to infer error code from message\n const code = inferErrorCode(error.message) || fallbackCode;\n return new AmpedDefiError(code, error.message, {\n severity: ErrorSeverity.ERROR,\n context,\n cause: error,\n });\n }\n\n return new AmpedDefiError(fallbackCode, String(error), {\n severity: ErrorSeverity.ERROR,\n context,\n });\n}\n\n// ============================================================================\n// Remediation Helpers\n// ============================================================================\n\nfunction getPolicyRemediation(code: ErrorCode, details?: Record<string, unknown>): string {\n switch (code) {\n case ErrorCode.POLICY_SLIPPAGE_EXCEEDED:\n return `Slippage ${details?.current} bps exceeds limit of ${details?.limit} bps. Increase maxSlippageBps in your policy configuration or wait for better market conditions.`;\n case ErrorCode.POLICY_SPEND_LIMIT_EXCEEDED:\n return `Reduce the operation amount or request a policy limit increase. Current limit: ${details?.limit}`;\n case ErrorCode.POLICY_CHAIN_NOT_ALLOWED:\n return `Add the chain to your allowedChains policy configuration or use a different chain.`;\n case ErrorCode.POLICY_TOKEN_NOT_ALLOWED:\n return `Add the token to your allowedTokensByChain policy configuration or use a different token.`;\n case ErrorCode.POLICY_RECIPIENT_BLOCKED:\n return `Use a different recipient address. This address has been blocked by policy.`;\n default:\n return 'Review your policy configuration or contact your administrator.';\n }\n}\n\nfunction getWalletRemediation(code: ErrorCode): string {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return 'Check your AMPED_OC_WALLETS_JSON configuration and ensure the walletId is correct.';\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return 'Verify the wallet address format (should be 0x-prefixed Ethereum address).';\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return 'Add the private key to your wallet configuration for execute mode, or switch to prepare mode.';\n default:\n return 'Check your wallet configuration and try again.';\n }\n}\n\nfunction getTransactionRemediation(code: ErrorCode): string {\n switch (code) {\n case ErrorCode.TRANSACTION_FAILED:\n return 'Check the transaction on a block explorer for revert reasons. You may need to adjust parameters or try again later.';\n case ErrorCode.TRANSACTION_TIMEOUT:\n return 'The operation timed out. You can check the status later using the transaction hash.';\n case ErrorCode.TRANSACTION_REJECTED:\n return 'The transaction was rejected. This may be due to network congestion or insufficient gas.';\n case ErrorCode.TRANSACTION_SIMULATION_FAILED:\n return 'The transaction would fail if executed. Check your balances, allowances, and parameters.';\n default:\n return 'Try again or contact support if the issue persists.';\n }\n}\n\nfunction getWalletErrorMessage(code: ErrorCode, walletId: string): string {\n switch (code) {\n case ErrorCode.WALLET_NOT_FOUND:\n return `Wallet not found: ${walletId}`;\n case ErrorCode.WALLET_INVALID_ADDRESS:\n return `Wallet ${walletId} has an invalid address`;\n case ErrorCode.WALLET_MISSING_PRIVATE_KEY:\n return `Wallet ${walletId} is missing private key (required in execute mode)`;\n default:\n return `Wallet error for ${walletId}`;\n }\n}\n\nfunction inferErrorCode(message: string): ErrorCode | null {\n const lowerMsg = message.toLowerCase();\n \n if (lowerMsg.includes('insufficient balance')) return ErrorCode.INSUFFICIENT_BALANCE;\n if (lowerMsg.includes('allowance')) return ErrorCode.INSUFFICIENT_ALLOWANCE;\n if (lowerMsg.includes('slippage')) return ErrorCode.POLICY_SLIPPAGE_EXCEEDED;\n if (lowerMsg.includes('health factor')) return ErrorCode.MM_HEALTH_FACTOR_LOW;\n if (lowerMsg.includes('timeout')) return ErrorCode.TRANSACTION_TIMEOUT;\n if (lowerMsg.includes('rejected')) return ErrorCode.TRANSACTION_REJECTED;\n if (lowerMsg.includes('simulation')) return ErrorCode.TRANSACTION_SIMULATION_FAILED;\n if (lowerMsg.includes('not initialized')) return ErrorCode.SDK_NOT_INITIALIZED;\n if (lowerMsg.includes('bridge') && lowerMsg.includes('not')) return ErrorCode.BRIDGE_NOT_AVAILABLE;\n if (lowerMsg.includes('quote') && lowerMsg.includes('expir')) return ErrorCode.QUOTE_EXPIRED;\n \n return null;\n}\n\n// ============================================================================\n// Logging and Observability\n// ============================================================================\n\n/**\n * Log an error with structured context\n */\nexport function logError(error: AmpedDefiError | Error, context?: ErrorContext): void {\n const structuredLog = {\n timestamp: new Date().toISOString(),\n component: 'amped-defi',\n level: error instanceof AmpedDefiError ? error.severity : 'error',\n code: error instanceof AmpedDefiError ? error.code : ErrorCode.UNKNOWN_ERROR,\n message: error.message,\n context,\n stack: error.stack,\n cause: error.cause,\n };\n\n // Log as JSON for structured logging systems\n console.error(JSON.stringify(structuredLog, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n\n/**\n * Check if an error is retryable\n */\nexport function isRetryableError(error: AmpedDefiError | Error): boolean {\n if (error instanceof AmpedDefiError) {\n const retryableCodes = [\n ErrorCode.TRANSACTION_TIMEOUT,\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n ErrorCode.SDK_NOT_INITIALIZED,\n ErrorCode.UNKNOWN_ERROR,\n ];\n return retryableCodes.includes(error.code);\n }\n \n // For generic errors, check message patterns\n const lowerMsg = error.message.toLowerCase();\n return lowerMsg.includes('timeout') || \n lowerMsg.includes('network') || \n lowerMsg.includes('connection') ||\n lowerMsg.includes('rate limit');\n}\n\n/**\n * Get retry delay in milliseconds with exponential backoff\n */\nexport function getRetryDelay(attempt: number, baseDelay = 1000): number {\n return Math.min(baseDelay * Math.pow(2, attempt), 30000); // Cap at 30 seconds\n}\n",
939 "inputSchema": {},
940 "outputSchema": null,
941 "icons": null,
942 "annotations": null,
943 "meta": null,
944 "execution": null
945 },
946 {
947 "name": "errorUtils.ts",
948 "title": null,
949 "description": "Script: errorUtils.ts. Code:\n/**\n * Serialize SDK error objects for readable error messages\n */\nfunction bigintReplacer(key: string, value: any): any {\n return typeof value === 'bigint' ? value.toString() : value;\n}\n\nexport function serializeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error, bigintReplacer, 2);\n } catch {\n return String(error);\n }\n}\n",
950 "inputSchema": {},
951 "outputSchema": null,
952 "icons": null,
953 "annotations": null,
954 "meta": null,
955 "execution": null
956 },
957 {
958 "name": "positionAggregator.ts",
959 "title": null,
960 "description": "Script: positionAggregator.ts. Code:\n/**\n * Cross-Chain Money Market Position Aggregator\n * \n * Aggregates user positions across all supported chains to provide a unified view\n * of their money market portfolio, including:\n * - Total supplied/borrowed across all chains\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position (supply - borrow)\n * - Cross-chain collateral utilization\n */\n\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { normalizeChainId } from '../wallet/types';\n\n/**\n * Position data for a single token on a single chain\n */\nexport interface TokenPosition {\n chainId: string;\n token: {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n };\n supply: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n isCollateral: boolean;\n };\n borrow: {\n balance: string;\n balanceUsd: string;\n balanceRaw: string;\n apy: number;\n };\n loanToValue: number;\n liquidationThreshold: number;\n}\n\n/**\n * Aggregated position summary across all chains\n */\nexport interface AggregatedPositionSummary {\n totalSupplyUsd: number;\n totalBorrowUsd: number;\n netWorthUsd: number;\n availableBorrowUsd: number;\n healthFactor: number | null;\n liquidationRisk: 'none' | 'low' | 'medium' | 'high';\n weightedSupplyApy: number;\n weightedBorrowApy: number;\n netApy: number;\n}\n\n/**\n * Chain-specific position summary\n */\nexport interface ChainPositionSummary {\n chainId: string;\n supplyUsd: number;\n borrowUsd: number;\n netWorthUsd: number;\n healthFactor: number | null;\n positionCount: number;\n}\n\n/**\n * Complete cross-chain position view\n */\nexport interface CrossChainPositionView {\n walletId: string;\n address: string;\n timestamp: string;\n summary: AggregatedPositionSummary;\n chainSummaries: ChainPositionSummary[];\n positions: TokenPosition[];\n collateralUtilization: {\n totalCollateralUsd: number;\n usedCollateralUsd: number;\n availableCollateralUsd: number;\n utilizationRate: number;\n };\n riskMetrics: {\n maxLtv: number;\n currentLtv: number;\n bufferUntilLiquidation: number;\n safeMaxBorrowUsd: number;\n };\n}\n\n/**\n * Options for position aggregation\n */\nexport interface AggregationOptions {\n /** Specific chains to query (defaults to all supported chains) */\n chainIds?: string[];\n /** Include zero-balance positions */\n includeZeroBalances?: boolean;\n /** Minimum USD value to include (positions below this are filtered out unless includeZeroBalances is true) */\n minUsdValue?: number;\n}\n\n// ============================================================================\n// Position Aggregation Functions\n// ============================================================================\n\n/**\n * Aggregate money market positions across all supported chains\n * \n * @param walletId - The wallet identifier\n * @param options - Aggregation options\n * @returns Complete cross-chain position view\n */\nexport async function aggregateCrossChainPositions(\n walletId: string,\n options: AggregationOptions = {}\n): Promise<CrossChainPositionView> {\n const startTime = Date.now();\n \n // Get wallet\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Get supported chains from SODAX\n const sodax = getSodaxClient();\n const sodaxChains = sodax.config.getSupportedSpokeChains();\n \n // Map SDK chains to string IDs\n const allSodaxChains = sodaxChains.map((c: any) => \n typeof c === 'string' ? c : c.id\n );\n \n // Filter chains by what the wallet supports\n // This is important for Bankr which only supports ethereum/polygon/base\n const walletSupportedChains = wallet.supportedChains;\n const filteredChains = allSodaxChains.filter((chainId: string) => \n wallet.supportsChain(normalizeChainId(chainId))\n );\n \n // Determine which chains to query\n const chainsToQuery = options.chainIds || filteredChains;\n \n console.log('[positionAggregator] Wallet chain filter', {\n walletType: wallet.type,\n walletSupports: walletSupportedChains,\n sodaxChains: allSodaxChains,\n filteredChains: filteredChains,\n normalizedFiltered: filteredChains.map(normalizeChainId),\n });\n \n console.log('[positionAggregator] Querying positions across chains', {\n walletId,\n address: walletAddress,\n chains: chainsToQuery,\n });\n\n // Query positions from all chains in parallel\n const chainResults = await Promise.allSettled(\n chainsToQuery.map(chainId => queryChainPositions(walletId, walletAddress, chainId))\n );\n\n // Collect all positions\n const allPositions: TokenPosition[] = [];\n const chainSummaries: ChainPositionSummary[] = [];\n\n chainResults.forEach((result, index) => {\n const chainId = chainsToQuery[index];\n \n if (result.status === 'fulfilled') {\n const { positions, summary } = result.value;\n \n if (positions.length > 0 || options.includeZeroBalances) {\n allPositions.push(...positions);\n chainSummaries.push(summary);\n }\n } else {\n console.warn(`[positionAggregator] Failed to query chain ${chainId}:`, result.reason);\n }\n });\n\n // Calculate aggregated summary\n const summary = calculateAggregatedSummary(allPositions);\n \n // Calculate collateral utilization\n const collateralUtilization = calculateCollateralUtilization(allPositions, summary);\n \n // Calculate risk metrics\n const riskMetrics = calculateRiskMetrics(allPositions, summary);\n \n const view: CrossChainPositionView = {\n walletId,\n address: walletAddress,\n timestamp: new Date().toISOString(),\n summary,\n chainSummaries: chainSummaries.sort((a, b) => b.netWorthUsd - a.netWorthUsd),\n positions: allPositions.sort((a, b) => \n (parseFloat(b.supply.balanceUsd) + parseFloat(b.borrow.balanceUsd)) -\n (parseFloat(a.supply.balanceUsd) + parseFloat(a.borrow.balanceUsd))\n ),\n collateralUtilization,\n riskMetrics,\n };\n\n console.log('[positionAggregator] Aggregation complete', {\n durationMs: Date.now() - startTime,\n totalPositions: allPositions.length,\n totalSupplyUsd: summary.totalSupplyUsd,\n totalBorrowUsd: summary.totalBorrowUsd,\n healthFactor: summary.healthFactor,\n });\n\n return view;\n}\n\n/**\n * Query positions for a single chain\n * \n * IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n * To get token symbols/names, we must:\n * 1. Fetch getReservesHumanized() for token metadata\n * 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n * 3. Join with formatUserSummary(buildUserSummaryRequest())\n * \n * Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n */\nasync function queryChainPositions(\n walletId: string,\n address: string,\n chainId: string\n): Promise<{ positions: TokenPosition[]; summary: ChainPositionSummary }> {\n try {\n // Use address for spoke provider lookup\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n const sodax = getSodaxClient();\n\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n // This is the key fix - getUserReservesHumanized alone doesn't include token metadata\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n \n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(\n sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)\n );\n \n // Step 3: Fetch user-specific balances\n const userReserves = await sodax.moneyMarket.data.getUserReservesHumanized(spokeProvider);\n \n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(\n sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReserves)\n );\n\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = (userSummary as any).userReservesData || [];\n\n // Convert to TokenPosition format\n const positions: TokenPosition[] = userReservesData.map((reserve: any) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n \n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n \n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n \n return {\n chainId,\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n logoURI: reserve.reserve?.iconSymbol || undefined,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n balanceRaw: reserve.scaledATokenBalance || '0',\n apy: supplyApy,\n isCollateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n balanceRaw: reserve.scaledVariableDebt || '0',\n apy: borrowApy,\n },\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n\n // Filter out positions with zero balance (unless explicitly requested)\n const activePositions = positions.filter(p => \n parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0\n );\n\n // Calculate chain summary\n const supplyUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n const borrowUsd = activePositions.reduce((sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'), 0);\n \n // Calculate health factor for this chain\n const healthFactor = calculateChainHealthFactor(activePositions);\n\n const summary: ChainPositionSummary = {\n chainId,\n supplyUsd,\n borrowUsd,\n netWorthUsd: supplyUsd - borrowUsd,\n healthFactor,\n positionCount: activePositions.length,\n };\n\n console.log(`[positionAggregator] Chain ${chainId}: ${activePositions.length} positions, supply=$${supplyUsd.toFixed(2)}, borrow=$${borrowUsd.toFixed(2)}`);\n\n return { positions: activePositions, summary };\n } catch (error) {\n console.error(`[positionAggregator] Error querying ${chainId}:`, error);\n throw error;\n }\n}\n\n// ============================================================================\n// Calculation Helpers\n// ============================================================================\n\n/**\n * Calculate aggregated summary across all positions\n */\nfunction calculateAggregatedSummary(positions: TokenPosition[]): AggregatedPositionSummary {\n let totalSupplyUsd = 0;\n let totalBorrowUsd = 0;\n let weightedSupplyApy = 0;\n let weightedBorrowApy = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n\n totalSupplyUsd += supplyUsd;\n totalBorrowUsd += borrowUsd;\n weightedSupplyApy += supplyUsd * pos.supply.apy;\n weightedBorrowApy += borrowUsd * pos.borrow.apy;\n });\n\n // Calculate weighted average APYs\n const avgSupplyApy = totalSupplyUsd > 0 ? weightedSupplyApy / totalSupplyUsd : 0;\n const avgBorrowApy = totalBorrowUsd > 0 ? weightedBorrowApy / totalBorrowUsd : 0;\n\n // Calculate health factor\n const healthFactor = calculateHealthFactor(positions);\n\n // Determine liquidation risk\n let liquidationRisk: AggregatedPositionSummary['liquidationRisk'] = 'none';\n if (healthFactor !== null) {\n if (healthFactor < 1.1) liquidationRisk = 'high';\n else if (healthFactor < 1.5) liquidationRisk = 'medium';\n else if (healthFactor < 2) liquidationRisk = 'low';\n }\n\n // Calculate available borrow (simplified - would need proper oracle prices)\n // This is a conservative estimate based on average LTV\n const avgLtv = positions.length > 0\n ? positions.reduce((sum, p) => sum + p.loanToValue, 0) / positions.length\n : 0;\n const availableBorrowUsd = totalSupplyUsd * avgLtv - totalBorrowUsd;\n\n return {\n totalSupplyUsd,\n totalBorrowUsd,\n netWorthUsd: totalSupplyUsd - totalBorrowUsd,\n availableBorrowUsd: Math.max(0, availableBorrowUsd),\n healthFactor,\n liquidationRisk,\n weightedSupplyApy: avgSupplyApy,\n weightedBorrowApy: avgBorrowApy,\n netApy: totalSupplyUsd > 0 \n ? (avgSupplyApy * totalSupplyUsd - avgBorrowApy * totalBorrowUsd) / totalSupplyUsd \n : 0,\n };\n}\n\n/**\n * Calculate collateral utilization metrics\n */\nfunction calculateCollateralUtilization(\n positions: TokenPosition[],\n summary: AggregatedPositionSummary\n): CrossChainPositionView['collateralUtilization'] {\n // Only count collateral-enabled supplies\n const totalCollateralUsd = positions\n .filter(p => p.supply.isCollateral)\n .reduce((sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'), 0);\n\n const usedCollateralUsd = summary.totalBorrowUsd;\n const availableCollateralUsd = Math.max(0, totalCollateralUsd - usedCollateralUsd);\n const utilizationRate = totalCollateralUsd > 0 ? (usedCollateralUsd / totalCollateralUsd) * 100 : 0;\n\n return {\n totalCollateralUsd,\n usedCollateralUsd,\n availableCollateralUsd,\n utilizationRate,\n };\n}\n\n/**\n * Calculate risk metrics\n */\nfunction calculateRiskMetrics(\n positions: TokenPosition[],\n summary: AggregatedPositionSummary\n): CrossChainPositionView['riskMetrics'] {\n // Calculate max LTV across all positions (weighted by supply)\n let totalSupply = 0;\n let weightedLtvSum = 0;\n let liquidationThresholdSum = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n totalSupply += supplyUsd;\n weightedLtvSum += supplyUsd * pos.loanToValue;\n liquidationThresholdSum += supplyUsd * pos.liquidationThreshold;\n });\n\n const maxLtv = totalSupply > 0 ? weightedLtvSum / totalSupply : 0;\n const avgLiquidationThreshold = totalSupply > 0 ? liquidationThresholdSum / totalSupply : 0;\n\n // Current LTV\n const currentLtv = summary.totalSupplyUsd > 0 \n ? summary.totalBorrowUsd / summary.totalSupplyUsd \n : 0;\n\n // Buffer until liquidation (percentage points)\n const bufferUntilLiquidation = Math.max(0, avgLiquidationThreshold - currentLtv) * 100;\n\n // Safe max borrow (at 80% of liquidation threshold for safety)\n const safeMaxBorrowUsd = summary.totalSupplyUsd * avgLiquidationThreshold * 0.8;\n\n return {\n maxLtv,\n currentLtv,\n bufferUntilLiquidation,\n safeMaxBorrowUsd,\n };\n}\n\n/**\n * Calculate health factor for a set of positions\n * Health Factor = (Total Collateral in ETH * Liquidation Threshold) / Total Borrow in ETH\n */\nfunction calculateHealthFactor(positions: TokenPosition[]): number | null {\n let totalCollateralEth = 0;\n let totalBorrowEth = 0;\n\n positions.forEach(pos => {\n const supplyUsd = parseFloat(pos.supply.balanceUsd || '0');\n const borrowUsd = parseFloat(pos.borrow.balanceUsd || '0');\n\n // Only count collateral-enabled supplies\n if (pos.supply.isCollateral) {\n totalCollateralEth += supplyUsd * pos.liquidationThreshold;\n }\n totalBorrowEth += borrowUsd;\n });\n\n if (totalBorrowEth === 0) {\n return totalCollateralEth > 0 ? Infinity : null;\n }\n\n return totalCollateralEth / totalBorrowEth;\n}\n\n/**\n * Calculate health factor for a single chain\n */\nfunction calculateChainHealthFactor(positions: TokenPosition[]): number | null {\n return calculateHealthFactor(positions);\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Format health factor for display\n */\nexport function formatHealthFactor(hf: number | null): string {\n if (hf === null) return 'N/A';\n if (hf === Infinity) return '\u221e';\n return hf.toFixed(2);\n}\n\n/**\n * Get health factor color/styling indicator\n */\nexport function getHealthFactorStatus(hf: number | null): {\n status: 'healthy' | 'caution' | 'danger' | 'critical';\n color: 'green' | 'yellow' | 'orange' | 'red';\n} {\n if (hf === null) return { status: 'healthy', color: 'green' };\n if (hf === Infinity) return { status: 'healthy', color: 'green' };\n if (hf < 1.1) return { status: 'critical', color: 'red' };\n if (hf < 1.5) return { status: 'danger', color: 'orange' };\n if (hf < 2) return { status: 'caution', color: 'yellow' };\n return { status: 'healthy', color: 'green' };\n}\n\n/**\n * Get recommendation based on position health\n */\nexport function getPositionRecommendation(view: CrossChainPositionView): string[] {\n const recommendations: string[] = [];\n const { summary } = view;\n\n // Health factor recommendations\n if (summary.healthFactor !== null && summary.healthFactor < 1.5) {\n recommendations.push('\u26a0\ufe0f Health factor is low. Consider repaying debt or adding collateral.');\n }\n\n // Borrowing capacity recommendations\n if (summary.availableBorrowUsd > 1000 && summary.healthFactor !== null && summary.healthFactor > 2) {\n recommendations.push(`\ud83d\udca1 You have $${summary.availableBorrowUsd.toFixed(2)} in available borrowing power.`);\n }\n\n // Collateral utilization\n if (view.collateralUtilization.utilizationRate > 80) {\n recommendations.push('\u26a0\ufe0f High collateral utilization. Avoid borrowing more to maintain safety margin.');\n }\n\n // Net APY optimization\n if (summary.netApy < 0) {\n recommendations.push('\ud83d\udcc9 Your borrowing costs exceed supply earnings. Consider reducing debt or finding higher APY supply opportunities.');\n }\n\n // Cross-chain opportunities\n const highApyChains = view.chainSummaries\n .filter(cs => cs.supplyUsd > 100)\n .sort((a, b) => (b.healthFactor || Infinity) - (a.healthFactor || Infinity));\n \n if (highApyChains.length > 1) {\n recommendations.push(`\ud83c\udf10 You have positions across ${highApyChains.length} chains. Monitor each chain's health factor independently.`);\n }\n\n return recommendations;\n}\n",
961 "inputSchema": {},
962 "outputSchema": null,
963 "icons": null,
964 "annotations": null,
965 "meta": null,
966 "execution": null
967 },
968 {
969 "name": "index.ts",
970 "title": null,
971 "description": "Script: index.ts. Code:\n/**\n * Wallet Module\n * \n * Multi-source wallet management with nicknames\n */\n\n// Types\nexport * from './types';\n\n// Backends\nexport * from './backends';\n\n// Manager\nexport { WalletManager, getWalletManager, resetWalletManager } from './walletManager';\n",
972 "inputSchema": {},
973 "outputSchema": null,
974 "icons": null,
975 "annotations": null,
976 "meta": null,
977 "execution": null
978 },
979 {
980 "name": "walletRegistry.ts",
981 "title": null,
982 "description": "Script: walletRegistry.ts. Code:\n/**\n * Wallet Registry\n *\n * Manages wallet resolution by walletId.\n * Supports execution mode (with private key) and prepare mode (address-only).\n * \n * Now integrates with evm-wallet-skill for seamless wallet configuration.\n * @see https://github.com/surfer77/evm-wallet-skill\n */\n\nimport { WalletConfig } from '../types';\nimport { EvmWalletSkillAdapter, getWalletAdapter } from './skillWalletAdapter';\n\n/**\n * Wallet registry entry\n */\ninterface WalletEntry extends WalletConfig {\n mode: 'execute' | 'prepare';\n}\n\n/**\n * Wallet Registry class for resolving wallet configurations\n */\nexport class WalletRegistry {\n private wallets: Map<string, WalletEntry>;\n private skillAdapter: EvmWalletSkillAdapter;\n\n constructor() {\n this.skillAdapter = getWalletAdapter();\n this.wallets = this.loadWallets();\n \n // Log skill adapter status\n if (this.skillAdapter.isUsingSkillWallets()) {\n console.log('[walletRegistry] evm-wallet-skill integration active');\n }\n }\n\n /**\n * Load wallet configurations from environment\n *\n * @returns Map of walletId to wallet entry\n */\n private loadWallets(): Map<string, WalletEntry> {\n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n const mode = (process.env.AMPED_OC_MODE as 'execute' | 'prepare') || 'execute';\n\n if (!walletsJson) {\n console.warn('[walletRegistry] AMPED_OC_WALLETS_JSON not set');\n return new Map();\n }\n\n try {\n const walletConfigs = JSON.parse(walletsJson) as Record<string, WalletConfig>;\n const wallets = new Map<string, WalletEntry>();\n\n for (const [walletId, config] of Object.entries(walletConfigs)) {\n wallets.set(walletId, {\n ...config,\n mode,\n });\n }\n\n console.log(`[walletRegistry] Loaded ${wallets.size} wallet(s) in ${mode} mode`);\n return wallets;\n } catch (error) {\n console.error('[walletRegistry] Failed to parse AMPED_OC_WALLETS_JSON', error);\n return new Map();\n }\n }\n\n /**\n * Get a wallet by its ID (synchronous version)\n * Only checks local registry, not skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n getWallet(walletId: string): WalletEntry | null {\n const wallet = this.wallets.get(walletId);\n if (wallet) {\n return this.validateWallet(wallet, walletId);\n }\n\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n\n /**\n * Resolve a wallet by its ID (async version)\n * Checks local registry first, then tries skill adapter\n *\n * @param walletId - The wallet identifier\n * @returns The wallet configuration or null if not found\n */\n async resolveWallet(walletId: string): Promise<WalletEntry | null> {\n // Try local registry first (synchronous)\n const wallet = this.getWallet(walletId);\n if (wallet) {\n return wallet;\n }\n\n // Try skill adapter (includes ~/.evm-wallet.json)\n if (this.skillAdapter.isUsingSkillWallets()) {\n try {\n const config = await this.skillAdapter.getWalletConfig(walletId);\n const mode = this.getMode();\n \n return {\n address: config.address,\n privateKey: config.privateKey,\n mode,\n };\n } catch (error) {\n console.error(`[walletRegistry] Skill wallet resolution failed: ${error}`);\n }\n }\n\n console.error(`[walletRegistry] Wallet not found: ${walletId}`);\n return null;\n }\n\n /**\n * Validate a wallet entry\n */\n private validateWallet(wallet: WalletEntry, walletId: string): WalletEntry | null {\n // In execute mode, validate that private key is present\n if (wallet.mode === 'execute' && !wallet.privateKey) {\n console.error(`[walletRegistry] Wallet ${walletId} missing privateKey in execute mode`);\n return null;\n }\n\n // Validate address format (basic check)\n if (!wallet.address || !wallet.address.startsWith('0x')) {\n console.error(`[walletRegistry] Wallet ${walletId} has invalid address: ${wallet.address}`);\n return null;\n }\n\n return wallet;\n }\n\n /**\n * Get the wallet mode (execute or prepare)\n *\n * @returns The current wallet mode\n */\n getMode(): 'execute' | 'prepare' {\n return (process.env.AMPED_OC_MODE as 'execute' | 'prepare') || 'execute';\n }\n\n /**\n * Check if running in execute mode\n *\n * @returns True if in execute mode\n */\n isExecuteMode(): boolean {\n return this.getMode() === 'execute';\n }\n\n /**\n * Check if running in prepare mode\n *\n * @returns True if in prepare mode\n */\n isPrepareMode(): boolean {\n return this.getMode() === 'prepare';\n }\n\n /**\n * Get all registered wallet IDs (local + skill)\n *\n * @returns Array of wallet IDs\n */\n getWalletIds(): string[] {\n const localIds = Array.from(this.wallets.keys());\n const skillIds = this.skillAdapter.getWalletIds();\n // Merge unique IDs\n return [...new Set([...localIds, ...skillIds])];\n }\n\n /**\n * Get the count of registered wallets (local + skill)\n *\n * @returns Number of wallets\n */\n getWalletCount(): number {\n return this.getWalletIds().length;\n }\n\n /**\n * Reload wallets from environment (useful for hot-reloading)\n */\n reload(): void {\n this.wallets = this.loadWallets();\n }\n}\n\n// Singleton instance\nlet walletRegistryInstance: WalletRegistry | null = null;\n\n/**\n * Get the singleton wallet registry instance\n * @returns The WalletRegistry singleton\n */\nexport function getWalletRegistry(): WalletRegistry {\n if (!walletRegistryInstance) {\n walletRegistryInstance = new WalletRegistry();\n }\n return walletRegistryInstance;\n}\n\n/**\n * Reset the wallet registry (useful for testing)\n */\nexport function resetWalletRegistry(): void {\n walletRegistryInstance = null;\n}\n",
983 "inputSchema": {},
984 "outputSchema": null,
985 "icons": null,
986 "annotations": null,
987 "meta": null,
988 "execution": null
989 },
990 {
991 "name": "index.ts",
992 "title": null,
993 "description": "Script: index.ts. Code:\n/**\n * Wallet Backends\n * \n * Export all wallet backend implementations\n */\n\nexport { EvmWalletSkillBackend, createEvmWalletSkillBackend } from './EvmWalletSkillBackend';\nexport { BankrBackend, createBankrBackend, type BankrBackendConfig } from './BankrBackend';\nexport { EnvBackend, createEnvBackend, loadWalletsFromEnv, type EnvBackendConfig } from './EnvBackend';\nexport { BankrWalletProvider, createBankrWalletProvider, type BankrWalletProviderConfig } from './BankrWalletProvider';\n",
994 "inputSchema": {},
995 "outputSchema": null,
996 "icons": null,
997 "annotations": null,
998 "meta": null,
999 "execution": null
1000 },
1001 {
1002 "name": "EvmWalletSkillBackend.ts",
1003 "title": null,
1004 "description": "Script: EvmWalletSkillBackend.ts. Code:\n/**\n * EVM Wallet Skill Backend\n * \n * Loads wallet from ~/.evm-wallet.json (created by evm-wallet-skill)\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n\n/**\n * Default path to evm-wallet-skill wallet file\n */\nconst DEFAULT_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n\n/**\n * EVM Wallet Skill wallet file structure\n */\ninterface EvmWalletFile {\n address: string;\n privateKey: string;\n}\n\n/**\n * Backend for evm-wallet-skill wallets\n * Supports all SODAX chains (local key signing)\n */\nexport class EvmWalletSkillBackend implements IWalletBackend {\n readonly type = 'evm-wallet-skill' as const;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n private walletPath: string;\n private cachedWallet: EvmWalletFile | null = null;\n\n constructor(options: {\n nickname: string;\n path?: string;\n chains?: string[];\n }) {\n this.nickname = options.nickname;\n this.walletPath = options.path || DEFAULT_WALLET_PATH;\n this.supportedChains = options.chains || [...SODAX_SUPPORTED_CHAINS];\n }\n\n /**\n * Load wallet from file (cached)\n */\n private loadWallet(): EvmWalletFile {\n if (this.cachedWallet) return this.cachedWallet;\n \n if (!existsSync(this.walletPath)) {\n throw new Error(\n `Wallet file not found: ${this.walletPath}\\n` +\n `Run: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n` +\n ` cd ~/.openclaw/skills/evm-wallet-skill && npm install && node src/setup.js`\n );\n }\n \n try {\n const content = readFileSync(this.walletPath, 'utf-8');\n this.cachedWallet = JSON.parse(content) as EvmWalletFile;\n return this.cachedWallet;\n } catch (error) {\n throw new Error(`Failed to load wallet from ${this.walletPath}: ${error}`);\n }\n }\n\n async getAddress(): Promise<Address> {\n const wallet = this.loadWallet();\n return wallet.address as Address;\n }\n\n supportsChain(chainId: string): boolean {\n return this.supportedChains.includes(chainId);\n }\n\n async getPrivateKey(): Promise<`0x${string}`> {\n const wallet = this.loadWallet();\n const key = wallet.privateKey;\n return key.startsWith('0x') ? key as `0x${string}` : `0x${key}` as `0x${string}`;\n }\n\n async isReady(): Promise<boolean> {\n try {\n this.loadWallet();\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Create an evm-wallet-skill backend\n */\nexport function createEvmWalletSkillBackend(options: {\n nickname?: string;\n path?: string;\n chains?: string[];\n} = {}): EvmWalletSkillBackend {\n return new EvmWalletSkillBackend({\n nickname: options.nickname || 'main',\n path: options.path,\n chains: options.chains,\n });\n}\n",
1005 "inputSchema": {},
1006 "outputSchema": null,
1007 "icons": null,
1008 "annotations": null,
1009 "meta": null,
1010 "execution": null
1011 },
1012 {
1013 "name": "BankrWalletProvider.ts",
1014 "title": null,
1015 "description": "Script: BankrWalletProvider.ts. Code:\n/**\n * Bankr Wallet Provider for SODAX SDK\n * \n * Implements IEvmWalletProvider interface to allow SODAX SDK\n * to execute transactions through Bankr's API.\n * \n * Instead of signing locally, transactions are submitted to Bankr\n * which signs and broadcasts them server-side.\n * \n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\n\nimport type { Address, Hash, PublicClient } from 'viem';\nimport { createPublicClient, http } from 'viem';\nimport { mainnet, polygon, base } from 'viem/chains';\nimport type { \n IEvmWalletProvider, \n EvmRawTransaction, \n EvmRawTransactionReceipt \n} from '@sodax/types';\n\n/**\n * Chain configurations for Bankr\n */\nconst BANKR_CHAINS: Record<number, { chain: any; name: string }> = {\n 1: { chain: mainnet, name: 'ethereum' },\n 137: { chain: polygon, name: 'polygon' },\n 8453: { chain: base, name: 'base' },\n};\n\n/**\n * Bankr API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message?: string;\n error?: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ \n type?: string;\n transactionHash?: string;\n txHash?: string;\n hash?: string;\n [key: string]: unknown;\n }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n}\n\n/**\n * Configuration for BankrWalletProvider\n */\nexport interface BankrWalletProviderConfig {\n apiKey: string;\n apiUrl?: string;\n chainId: number;\n rpcUrl?: string;\n /** Pre-cached address (avoids initial API call) */\n cachedAddress?: Address;\n}\n\n/**\n * Bankr Wallet Provider\n * \n * Implements IEvmWalletProvider for use with SODAX SDK's SpokeProvider.\n * Transactions are signed and broadcast via Bankr's Agent API.\n */\nexport class BankrWalletProvider implements IEvmWalletProvider {\n readonly publicClient: PublicClient;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private readonly chainId: number;\n private cachedAddress: Address | null;\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrWalletProviderConfig) {\n this.apiKey = config.apiKey;\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.chainId = config.chainId;\n this.cachedAddress = config.cachedAddress || null;\n \n // Validate chain support\n const chainConfig = BANKR_CHAINS[config.chainId];\n if (!chainConfig) {\n throw new Error(\n `Bankr does not support chainId ${config.chainId}. ` +\n `Supported: Ethereum (1), Polygon (137), Base (8453)`\n );\n }\n \n // Create public client for read operations\n this.publicClient = createPublicClient({\n chain: chainConfig.chain,\n transport: http(config.rpcUrl),\n }) as PublicClient;\n \n console.log(`[BankrWalletProvider] Initialized for ${chainConfig.name} (${config.chainId})`);\n }\n\n /**\n * Get the Bankr wallet address\n */\n async getWalletAddress(): Promise<Address> {\n if (this.cachedAddress) return this.cachedAddress;\n \n console.log('[BankrWalletProvider] Fetching wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n \n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n throw new Error('Could not parse wallet address from Bankr response');\n }\n \n this.cachedAddress = addressMatch[0] as Address;\n console.log(`[BankrWalletProvider] Wallet address: ${this.cachedAddress}`);\n \n return this.cachedAddress;\n } catch (error) {\n console.error('[BankrWalletProvider] Failed to get address:', error);\n throw error;\n }\n }\n\n /**\n * Send a transaction via Bankr\n * \n * This is the key method - it receives raw transaction data from SODAX SDK\n * and submits it to Bankr for signing and broadcasting.\n */\n async sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash> {\n console.log('[BankrWalletProvider] Sending transaction via Bankr');\n console.log(`[BankrWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[BankrWalletProvider] Value: ${evmRawTx.value}`);\n console.log(`[BankrWalletProvider] Data: ${evmRawTx.data.slice(0, 20)}...`);\n\n // Format transaction for Bankr's arbitrary transaction endpoint\n const txJson = JSON.stringify({\n to: evmRawTx.to,\n data: evmRawTx.data,\n value: evmRawTx.value.toString(),\n chainId: this.chainId,\n }, null, 2);\n\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n\n console.log('[BankrWalletProvider] Submitting to Bankr API...');\n \n const result = await this.submitAndWaitForJob(prompt);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`Transaction failed: ${errorMsg}`);\n }\n\n console.log(`[BankrWalletProvider] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Wait for transaction receipt\n * \n * Uses the public client to query the blockchain directly.\n */\n async waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt> {\n console.log(`[BankrWalletProvider] Waiting for receipt: ${txHash}`);\n \n const receipt = await this.publicClient.waitForTransactionReceipt({\n hash: txHash,\n timeout: 120_000, // 2 minutes\n });\n\n // Convert viem receipt to SODAX format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: `0x${receipt.transactionIndex.toString(16)}`,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: `0x${receipt.cumulativeGasUsed.toString(16)}`,\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: receipt.contractAddress,\n logs: receipt.logs.map(log => ({\n address: log.address as Address,\n topics: (log as any).topics || [],\n data: log.data,\n blockHash: log.blockHash,\n blockNumber: log.blockNumber ? `0x${log.blockNumber.toString(16)}` : null,\n logIndex: log.logIndex !== null ? `0x${log.logIndex.toString(16)}` : null,\n transactionHash: log.transactionHash,\n transactionIndex: log.transactionIndex !== null ? `0x${log.transactionIndex.toString(16)}` : null,\n removed: log.removed,\n })) as any,\n logsBloom: receipt.logsBloom,\n status: receipt.status === 'success' ? '0x1' : '0x0',\n type: receipt.type,\n effectiveGasPrice: receipt.effectiveGasPrice ? `0x${receipt.effectiveGasPrice.toString(16)}` : undefined,\n };\n }\n\n /**\n * Submit prompt and wait for text response\n */\n private async submitAndWait(prompt: string): Promise<string> {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n\n /**\n * Submit prompt and wait for job completion\n */\n private async submitAndWaitForJob(prompt: string): Promise<BankrJobStatusResponse> {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`Failed to submit job: ${submitResponse.status} ${error}`);\n }\n\n const submitData = await submitResponse.json() as BankrJobSubmitResponse;\n \n if (!submitData.success || !submitData.jobId) {\n throw new Error(`Invalid job response: ${JSON.stringify(submitData)}`);\n }\n\n const jobId = submitData.jobId;\n console.log(`[BankrWalletProvider] Job submitted: ${jobId}`);\n\n // Poll for completion\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`Failed to get job status: ${statusResponse.status} ${error}`);\n }\n\n const result = await statusResponse.json() as BankrJobStatusResponse;\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrWalletProvider] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`Job was cancelled`);\n }\n }\n\n throw new Error(`Job ${jobId} timed out`);\n }\n\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) return item.transactionHash as Hash;\n if (item.txHash) return item.txHash as Hash;\n if (item.hash) return item.hash as Hash;\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) return hashMatch[0] as Hash;\n }\n\n return null;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a BankrWalletProvider\n */\nexport function createBankrWalletProvider(config: BankrWalletProviderConfig): BankrWalletProvider {\n return new BankrWalletProvider(config);\n}\n",
1016 "inputSchema": {},
1017 "outputSchema": null,
1018 "icons": null,
1019 "annotations": null,
1020 "meta": null,
1021 "execution": null
1022 },
1023 {
1024 "name": "BankrBackend.ts",
1025 "title": null,
1026 "description": "Script: BankrBackend.ts. Code:\n/**\n * Bankr Backend - Transaction Execution via Bankr API\n *\n * Submits raw transactions to Bankr's Agent API using the\n * arbitrary transaction format documented at:\n * https://github.com/BankrBot/openclaw-skills/blob/main/bankr/references/arbitrary-transaction.md\n *\n * Supported chains: Ethereum (1), Polygon (137), Base (8453)\n */\n\nimport type { Address, Hash } from 'viem';\nimport type { IWalletBackend, RawTransaction } from '../types';\nimport { BANKR_SUPPORTED_CHAINS, isBankrSupportedChain } from '../types';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\n\n/**\n * Disk cache path for bankr address\n */\nconst BANKR_CACHE_DIR = join(homedir(), '.openclaw', 'cache');\nconst getBankrCachePath = (nickname: string) => join(BANKR_CACHE_DIR, `bankr-${nickname}-address.json`);\n\n/**\n * Bankr API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message?: string;\n error?: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ \n type?: string;\n transactionHash?: string;\n txHash?: string;\n hash?: string;\n [key: string]: unknown;\n }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n createdAt: string;\n completedAt?: string;\n}\n\n/**\n * Bankr backend configuration\n */\nexport interface BankrBackendConfig {\n nickname?: string;\n apiKey: string;\n apiUrl?: string;\n}\n\n/**\n * Bankr wallet backend\n * Submits raw transactions via Bankr Agent API\n */\nexport class BankrBackend implements IWalletBackend {\n readonly type = 'bankr' as const;\n readonly nickname: string;\n readonly supportedChains = BANKR_SUPPORTED_CHAINS;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private cachedAddress: Address | null = null;\n private cachedSolanaAddress: string | null = null;\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrBackendConfig) {\n this.nickname = config.nickname || 'bankr';\n this.apiUrl = config.apiUrl || 'https://api.bankr.bot';\n this.apiKey = config.apiKey;\n \n // Try to load cached address from disk\n this.loadCachedAddress();\n \n console.log(`[BankrBackend] Initialized as \"${this.nickname}\"`);\n console.log(`[BankrBackend] Supported chains: ${this.supportedChains.join(', ')}`);\n if (this.cachedAddress) {\n console.log(`[BankrBackend] Loaded cached address: ${this.cachedAddress}`);\n }\n }\n\n /**\n * Load cached address from disk\n */\n private loadCachedAddress(): void {\n const cachePath = getBankrCachePath(this.nickname);\n if (existsSync(cachePath)) {\n try {\n const data = JSON.parse(readFileSync(cachePath, 'utf-8'));\n if (data.address && data.address.match(/^0x[a-fA-F0-9]{40}$/)) {\n this.cachedAddress = data.address as Address;\n }\n } catch (e) {\n console.warn(`[BankrBackend] Failed to load cached address: ${e}`);\n }\n }\n }\n\n /**\n * Save address to disk cache\n */\n private saveCachedAddress(address: Address): void {\n const cachePath = getBankrCachePath(this.nickname);\n try {\n if (!existsSync(BANKR_CACHE_DIR)) {\n mkdirSync(BANKR_CACHE_DIR, { recursive: true });\n }\n writeFileSync(cachePath, JSON.stringify({ address, timestamp: Date.now() }));\n console.log(`[BankrBackend] Cached address to ${cachePath}`);\n } catch (e) {\n console.warn(`[BankrBackend] Failed to cache address: ${e}`);\n }\n }\n\n async getAddress(): Promise<Address> {\n if (this.cachedAddress) return this.cachedAddress;\n \n // Query Bankr for the wallet address\n console.log('[BankrBackend] Fetching wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my EVM wallet address?');\n \n // Extract address from response\n const addressMatch = response.match(/0x[a-fA-F0-9]{40}/);\n if (!addressMatch) {\n console.warn('[BankrBackend] Could not parse address from response:', response.slice(0, 100));\n throw new Error('[BankrBackend] Could not determine wallet address from Bankr');\n }\n \n this.cachedAddress = addressMatch[0] as Address;\n \n // Save to disk for next time\n this.saveCachedAddress(this.cachedAddress);\n \n console.log(`[BankrBackend] Wallet address: ${this.cachedAddress}`);\n \n return this.cachedAddress;\n } catch (error) {\n console.error('[BankrBackend] Failed to get address:', error);\n throw error;\n }\n }\n\n /**\n * Get the Solana wallet address from Bankr\n */\n async getSolanaAddress(): Promise<string | null> {\n if (this.cachedSolanaAddress) return this.cachedSolanaAddress;\n \n // Check for cached address on disk\n const cachePath = `${process.env.HOME}/.openclaw/cache/bankr-${this.nickname}-solana-address.json`;\n try {\n const fs = await import('fs');\n if (fs.existsSync(cachePath)) {\n const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));\n if (cached.address && Date.now() - cached.timestamp < 86400000) {\n this.cachedSolanaAddress = cached.address;\n console.log(`[BankrBackend] Loaded cached Solana address: ${this.cachedSolanaAddress}`);\n return this.cachedSolanaAddress;\n }\n }\n } catch (e) {\n // Cache miss, continue to query\n }\n \n console.log('[BankrBackend] Fetching Solana wallet address from Bankr...');\n \n try {\n const response = await this.submitAndWait('What is my Solana wallet address?');\n \n // Solana addresses are base58, typically 32-44 chars, no 0x prefix\n const solanaMatch = response.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/);\n if (!solanaMatch) {\n console.warn('[BankrBackend] Could not parse Solana address from response');\n return null;\n }\n \n this.cachedSolanaAddress = solanaMatch[0];\n console.log(`[BankrBackend] Solana address: ${this.cachedSolanaAddress}`);\n \n // Cache to disk\n try {\n const fs = await import('fs');\n const path = await import('path');\n const cacheDir = path.dirname(cachePath);\n if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify({ address: this.cachedSolanaAddress, timestamp: Date.now() }));\n } catch (e) {\n console.warn('[BankrBackend] Failed to cache Solana address:', e);\n }\n \n return this.cachedSolanaAddress;\n } catch (error) {\n console.error('[BankrBackend] Failed to get Solana address:', error);\n return null;\n }\n }\n\n supportsChain(chainId: string): boolean {\n // Normalize chain ID to handle SODAX format (0x2105.base -> base)\n return isBankrSupportedChain(chainId);\n }\n\n async isReady(): Promise<boolean> {\n if (!this.apiKey) return false;\n \n try {\n // Test API connectivity\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n \n return response.status !== 503 && response.status !== 502;\n } catch {\n return false;\n }\n }\n\n /**\n * Send raw transaction via Bankr\n * Uses the arbitrary transaction format\n */\n async sendRawTransaction(tx: RawTransaction): Promise<Hash> {\n console.log(`[BankrBackend] Sending raw transaction`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Chain: ${tx.chainId}`);\n console.log(`[BankrBackend] Value: ${tx.value}`);\n console.log(`[BankrBackend] Data: ${tx.data.slice(0, 20)}...`);\n\n // Format as documented in arbitrary-transaction.md\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data,\n value: tx.value,\n chainId: tx.chainId,\n }, null, 2);\n\n // Use the documented prompt format\n const prompt = `Submit this transaction:\n${txJson}`;\n\n console.log(`[BankrBackend] Submitting to Bankr API...`);\n \n const result = await this.submitAndWaitForJob(prompt);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n const errorMsg = result.error || result.response || 'Unknown error';\n throw new Error(`[BankrBackend] Transaction failed: ${errorMsg}`);\n }\n\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Submit prompt and wait for text response\n */\n private async submitAndWait(prompt: string): Promise<string> {\n const result = await this.submitAndWaitForJob(prompt);\n return result.response || '';\n }\n\n /**\n * Submit prompt and wait for job completion\n */\n private async submitAndWaitForJob(prompt: string): Promise<BankrJobStatusResponse> {\n // Submit job\n const submitResponse = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!submitResponse.ok) {\n const error = await submitResponse.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${submitResponse.status} ${error}`);\n }\n\n const submitData = await submitResponse.json() as BankrJobSubmitResponse;\n \n if (!submitData.success || !submitData.jobId) {\n throw new Error(`[BankrBackend] Invalid job response: ${JSON.stringify(submitData)}`);\n }\n\n const jobId = submitData.jobId;\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n\n // Poll for completion\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const statusResponse = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n });\n\n if (!statusResponse.ok) {\n const error = await statusResponse.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${statusResponse.status} ${error}`);\n }\n\n const result = await statusResponse.json() as BankrJobStatusResponse;\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId}: ${result.status}`);\n lastStatus = result.status;\n }\n \n // Log progress updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n\n // Check terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${result.error || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n }\n }\n\n throw new Error(`[BankrBackend] Job ${jobId} timed out`);\n }\n\n /**\n * Extract transaction hash from Bankr response\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) return item.transactionHash as Hash;\n if (item.txHash) return item.txHash as Hash;\n if (item.hash) return item.hash as Hash;\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for 0x + 64 hex chars\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) return hashMatch[0] as Hash;\n \n // Check for failure indicators\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n\n return null;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a Bankr backend from API key\n */\nexport function createBankrBackend(config: BankrBackendConfig): BankrBackend {\n return new BankrBackend(config);\n}\n",
1027 "inputSchema": {},
1028 "outputSchema": null,
1029 "icons": null,
1030 "annotations": null,
1031 "meta": null,
1032 "execution": null
1033 },
1034 {
1035 "name": "EnvBackend.ts",
1036 "title": null,
1037 "description": "Script: EnvBackend.ts. Code:\n/**\n * Environment Variable Backend\n * \n * Loads wallet from environment variables:\n * - AMPED_OC_WALLETS_JSON: JSON with wallet configs\n * - Or individual env vars for address/privateKey\n */\n\nimport type { Address } from 'viem';\nimport type { IWalletBackend } from '../types';\nimport { SODAX_SUPPORTED_CHAINS } from '../types';\n\n/**\n * Wallet entry from AMPED_OC_WALLETS_JSON\n */\ninterface EnvWalletEntry {\n address: string;\n privateKey: string;\n}\n\n/**\n * Environment variable backend configuration\n */\nexport interface EnvBackendConfig {\n nickname: string;\n address?: Address;\n privateKey?: `0x${string}`;\n envVar?: string; // Name of env var containing JSON\n chains?: string[];\n}\n\n/**\n * Environment variable wallet backend\n * Supports all SODAX chains (local key signing)\n */\nexport class EnvBackend implements IWalletBackend {\n readonly type = 'env' as const;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n private address: Address | null = null;\n private privateKey: `0x${string}` | null = null;\n private envVar: string | null = null;\n\n constructor(config: EnvBackendConfig) {\n this.nickname = config.nickname;\n this.supportedChains = config.chains || [...SODAX_SUPPORTED_CHAINS];\n \n if (config.address && config.privateKey) {\n // Direct address/key provided\n this.address = config.address;\n this.privateKey = config.privateKey;\n } else if (config.envVar) {\n // Will load from env var\n this.envVar = config.envVar;\n }\n }\n\n /**\n * Load wallet from environment variable if needed\n */\n private loadFromEnv(): { address: Address; privateKey: `0x${string}` } {\n if (this.address && this.privateKey) {\n return { address: this.address, privateKey: this.privateKey };\n }\n\n if (this.envVar) {\n const envValue = process.env[this.envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${this.envVar} not set`);\n }\n \n try {\n const data = JSON.parse(envValue) as EnvWalletEntry;\n this.address = data.address as Address;\n this.privateKey = (data.privateKey.startsWith('0x') \n ? data.privateKey \n : `0x${data.privateKey}`) as `0x${string}`;\n return { address: this.address, privateKey: this.privateKey };\n } catch (error) {\n throw new Error(`Failed to parse ${this.envVar}: ${error}`);\n }\n }\n\n throw new Error(`No wallet configuration for \"${this.nickname}\"`);\n }\n\n async getAddress(): Promise<Address> {\n const { address } = this.loadFromEnv();\n return address;\n }\n\n supportsChain(chainId: string): boolean {\n return this.supportedChains.includes(chainId);\n }\n\n async getPrivateKey(): Promise<`0x${string}`> {\n const { privateKey } = this.loadFromEnv();\n return privateKey;\n }\n\n async isReady(): Promise<boolean> {\n try {\n this.loadFromEnv();\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Create env backend from direct config\n */\nexport function createEnvBackend(config: EnvBackendConfig): EnvBackend {\n return new EnvBackend(config);\n}\n\n/**\n * Load wallets from AMPED_OC_WALLETS_JSON environment variable\n * Returns multiple backends keyed by wallet name\n */\nexport function loadWalletsFromEnv(): Map<string, EnvBackend> {\n const wallets = new Map<string, EnvBackend>();\n \n const walletsJson = process.env.AMPED_OC_WALLETS_JSON;\n if (!walletsJson) return wallets;\n \n try {\n const parsed = JSON.parse(walletsJson) as Record<string, EnvWalletEntry>;\n \n for (const [name, wallet] of Object.entries(parsed)) {\n const backend = new EnvBackend({\n nickname: name,\n address: wallet.address as Address,\n privateKey: (wallet.privateKey.startsWith('0x') \n ? wallet.privateKey \n : `0x${wallet.privateKey}`) as `0x${string}`,\n });\n wallets.set(name.toLowerCase(), backend);\n }\n \n console.log(`[EnvBackend] Loaded ${wallets.size} wallet(s) from AMPED_OC_WALLETS_JSON`);\n } catch (error) {\n console.warn(`[EnvBackend] Failed to parse AMPED_OC_WALLETS_JSON: ${error}`);\n }\n \n return wallets;\n}\n",
1038 "inputSchema": {},
1039 "outputSchema": null,
1040 "icons": null,
1041 "annotations": null,
1042 "meta": null,
1043 "execution": null
1044 },
1045 {
1046 "name": "walletManager.ts",
1047 "title": null,
1048 "description": "Script: walletManager.ts. Code:\n/**\n * Unified Wallet Manager\n * \n * Manages multiple wallet sources with nicknames:\n * - evm-wallet-skill (main)\n * - Bankr (bankr)\n * - Environment variables (custom names)\n * \n * Auto-discovery order:\n * 1. wallets.json config file\n * 2. ~/.evm-wallet.json (evm-wallet-skill) \u2192 \"main\"\n * 3. BANKR_API_KEY env \u2192 \"bankr\"\n * 4. AMPED_OC_WALLETS_JSON env \u2192 named wallets\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport type { Address } from 'viem';\nimport type { IWalletBackend, WalletInfo, WalletsConfigFile, WalletConfig } from './types';\nimport { \n createEvmWalletSkillBackend, \n createBankrBackend, \n createEnvBackend,\n loadWalletsFromEnv \n} from './backends';\n\n/**\n * Config file path\n */\nconst CONFIG_PATH = join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'wallets.json');\nconst EVM_WALLET_PATH = join(homedir(), '.evm-wallet.json');\n\n/**\n * Singleton WalletManager instance\n */\nlet instance: WalletManager | null = null;\n\n/**\n * Unified wallet manager\n */\nexport class WalletManager {\n private wallets = new Map<string, IWalletBackend>();\n private defaultWallet: string | null = null;\n private initialized = false;\n\n /**\n * Initialize the wallet manager\n * Auto-discovers wallets from all sources\n */\n async initialize(): Promise<void> {\n if (this.initialized) return;\n \n console.log('[WalletManager] Initializing...');\n \n // 1. Load from config file if exists\n await this.loadConfigFile();\n \n // 2. Auto-discover from environment\n await this.autoDiscover();\n \n // 3. Set default\n this.determineDefault();\n \n this.initialized = true;\n \n console.log(`[WalletManager] Initialized with ${this.wallets.size} wallet(s)`);\n if (this.defaultWallet) {\n console.log(`[WalletManager] Default wallet: ${this.defaultWallet}`);\n }\n }\n\n /**\n * Load wallets from config file\n */\n private async loadConfigFile(): Promise<void> {\n if (!existsSync(CONFIG_PATH)) return;\n \n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const config = JSON.parse(content) as WalletsConfigFile;\n \n for (const [name, walletConfig] of Object.entries(config.wallets)) {\n const backend = this.createBackendFromConfig(name, walletConfig);\n if (backend) {\n this.wallets.set(name.toLowerCase(), backend);\n console.log(`[WalletManager] Loaded wallet \"${name}\" from config`);\n }\n }\n \n if (config.default) {\n this.defaultWallet = config.default.toLowerCase();\n }\n } catch (error) {\n console.warn(`[WalletManager] Failed to load config: ${error}`);\n }\n }\n\n /**\n * Create backend from config entry\n */\n private createBackendFromConfig(name: string, config: WalletConfig): IWalletBackend | null {\n try {\n switch (config.source) {\n case 'evm-wallet-skill':\n return createEvmWalletSkillBackend({\n nickname: name,\n path: config.path,\n chains: config.chains,\n });\n \n case 'bankr':\n if (!config.apiKey) {\n console.warn(`[WalletManager] Bankr wallet \"${name}\" missing apiKey`);\n return null;\n }\n return createBankrBackend({\n nickname: name,\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n });\n \n case 'env':\n return createEnvBackend({\n nickname: name,\n address: config.address,\n privateKey: config.privateKey,\n envVar: config.envVar,\n chains: config.chains,\n });\n \n default:\n console.warn(`[WalletManager] Unknown wallet source: ${config.source}`);\n return null;\n }\n } catch (error) {\n console.warn(`[WalletManager] Failed to create backend for \"${name}\": ${error}`);\n return null;\n }\n }\n\n /**\n * Auto-discover wallets from environment\n */\n private async autoDiscover(): Promise<void> {\n // evm-wallet-skill (if not already configured)\n if (!this.wallets.has('main') && existsSync(EVM_WALLET_PATH)) {\n try {\n const backend = createEvmWalletSkillBackend({ nickname: 'main' });\n if (await backend.isReady()) {\n this.wallets.set('main', backend);\n console.log('[WalletManager] Auto-discovered: evm-wallet-skill \u2192 \"main\"');\n }\n } catch (error) {\n console.debug(`[WalletManager] evm-wallet-skill not available: ${error}`);\n }\n }\n \n // Bankr (if API key present and not already configured)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (!this.wallets.has('bankr') && bankrApiKey) {\n console.log('[WalletManager] Found BANKR_API_KEY, attempting to add Bankr wallet...');\n try {\n const backend = createBankrBackend({\n nickname: 'bankr',\n apiKey: bankrApiKey,\n apiUrl: process.env.BANKR_API_URL,\n });\n const ready = await backend.isReady();\n if (ready) {\n this.wallets.set('bankr', backend);\n console.log('[WalletManager] Auto-discovered: BANKR_API_KEY \u2192 \"bankr\"');\n } else {\n console.warn('[WalletManager] Bankr API key present but connectivity check failed');\n }\n } catch (error) {\n console.warn(`[WalletManager] Bankr auto-discovery failed: ${error}`);\n }\n }\n \n // Environment variable wallets\n const envWallets = loadWalletsFromEnv();\n for (const [name, backend] of envWallets) {\n if (!this.wallets.has(name)) {\n this.wallets.set(name, backend);\n console.log(`[WalletManager] Auto-discovered: AMPED_OC_WALLETS_JSON \u2192 \"${name}\"`);\n }\n }\n }\n\n /**\n * Determine default wallet\n */\n private determineDefault(): void {\n // If already set from config, verify it exists\n if (this.defaultWallet && this.wallets.has(this.defaultWallet)) {\n return;\n }\n \n // Priority: main > first available\n if (this.wallets.has('main')) {\n this.defaultWallet = 'main';\n } else if (this.wallets.size > 0) {\n this.defaultWallet = Array.from(this.wallets.keys())[0];\n } else {\n this.defaultWallet = null;\n }\n }\n\n /**\n * Resolve a wallet by nickname\n * @param nickname Optional wallet nickname (uses default if not provided)\n */\n async resolve(nickname?: string): Promise<IWalletBackend> {\n await this.initialize();\n \n const name = (nickname || this.defaultWallet)?.toLowerCase();\n \n if (!name) {\n throw new Error(\n 'No wallet configured.\\n\\n' +\n 'To set up a wallet, install evm-wallet-skill:\\n' +\n ' git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill\\n' +\n ' cd ~/.openclaw/skills/evm-wallet-skill && npm install\\n' +\n ' node src/setup.js'\n );\n }\n \n const wallet = this.wallets.get(name);\n if (!wallet) {\n const available = Array.from(this.wallets.keys()).join(', ') || '(none)';\n throw new Error(`Wallet \"${name}\" not found. Available wallets: ${available}`);\n }\n \n return wallet;\n }\n\n /**\n * Check if a wallet exists\n */\n async has(nickname: string): Promise<boolean> {\n await this.initialize();\n return this.wallets.has(nickname.toLowerCase());\n }\n\n /**\n * List all available wallets\n */\n async listWallets(): Promise<WalletInfo[]> {\n await this.initialize();\n \n const wallets: WalletInfo[] = [];\n \n for (const [name, backend] of this.wallets) {\n try {\n // Add timeout for slow backends (like Bankr)\n const addressPromise = backend.getAddress();\n const timeoutPromise = new Promise<never>((_, reject) => \n setTimeout(() => reject(new Error('Timeout')), 30000)\n );\n \n const address = await Promise.race([addressPromise, timeoutPromise]);\n \n // Get Solana address for Bankr wallets\n let solanaAddress: string | undefined;\n if (backend.type === 'bankr' && (backend as any).getSolanaAddress) {\n try {\n solanaAddress = await (backend as any).getSolanaAddress() || undefined;\n } catch (e) {\n console.warn(`[WalletManager] Failed to get Solana address for ${name}`);\n }\n }\n \n wallets.push({\n nickname: name,\n type: backend.type,\n address,\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n solanaAddress,\n });\n } catch (error) {\n // Include wallet with placeholder address if we can't get it\n console.warn(`[WalletManager] Failed to get address for \"${name}\": ${error}`);\n wallets.push({\n nickname: name,\n type: backend.type,\n address: '0x...' as Address, // Placeholder\n chains: [...backend.supportedChains],\n isDefault: name === this.defaultWallet,\n });\n }\n }\n \n return wallets;\n }\n\n /**\n * Get the default wallet nickname\n */\n async getDefaultWalletName(): Promise<string | null> {\n await this.initialize();\n return this.defaultWallet;\n }\n\n /**\n * Register a new wallet backend\n */\n registerWallet(nickname: string, backend: IWalletBackend): void {\n this.wallets.set(nickname.toLowerCase(), backend);\n console.log(`[WalletManager] Registered wallet: ${nickname}`);\n }\n\n /**\n * Get available wallet IDs (nicknames)\n * Synchronous version - requires prior initialization\n */\n getAvailableWalletIds(): string[] {\n return Array.from(this.wallets.keys());\n }\n\n /**\n * Add a new wallet to the config file\n */\n async addWallet(nickname: string, config: WalletConfig): Promise<void> {\n await this.initialize();\n \n const normalizedName = nickname.toLowerCase();\n \n // Check if wallet already exists\n if (this.wallets.has(normalizedName)) {\n throw new Error(`Wallet \"${nickname}\" already exists. Use rename to change it.`);\n }\n \n // Create the backend to validate config\n const backend = this.createBackendFromConfig(normalizedName, config);\n if (!backend) {\n throw new Error(`Failed to create wallet backend for \"${nickname}\"`);\n }\n \n // Validate the backend works\n const ready = await backend.isReady();\n if (!ready) {\n throw new Error(`Wallet \"${nickname}\" configuration is invalid or not accessible`);\n }\n \n // Load existing config\n const fileConfig = this.loadConfigFromFile();\n \n // Add new wallet\n fileConfig.wallets[normalizedName] = config;\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Register in memory\n this.wallets.set(normalizedName, backend);\n \n console.log(`[WalletManager] Added wallet \"${nickname}\"`);\n }\n\n /**\n * Rename a wallet\n */\n async renameWallet(currentNickname: string, newNickname: string): Promise<void> {\n await this.initialize();\n \n const currentName = currentNickname.toLowerCase();\n const newName = newNickname.toLowerCase();\n \n // Check source exists\n if (!this.wallets.has(currentName)) {\n throw new Error(`Wallet \"${currentNickname}\" not found`);\n }\n \n // Check target doesn't exist\n if (this.wallets.has(newName)) {\n throw new Error(`Wallet \"${newNickname}\" already exists`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Move wallet config\n if (fileConfig.wallets[currentName]) {\n fileConfig.wallets[newName] = fileConfig.wallets[currentName];\n delete fileConfig.wallets[currentName];\n } else {\n // Wallet was auto-discovered, need to add it to config\n const backend = this.wallets.get(currentName)!;\n const config = await this.backendToConfig(backend);\n fileConfig.wallets[newName] = config;\n }\n \n // Update default if needed\n if (fileConfig.default === currentName) {\n fileConfig.default = newName;\n }\n if (this.defaultWallet === currentName) {\n this.defaultWallet = newName;\n }\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Update in-memory\n const backend = this.wallets.get(currentName)!;\n this.wallets.delete(currentName);\n this.wallets.set(newName, backend);\n \n console.log(`[WalletManager] Renamed wallet \"${currentNickname}\" to \"${newNickname}\"`);\n }\n\n /**\n * Remove a wallet from config\n */\n async removeWallet(nickname: string): Promise<void> {\n await this.initialize();\n \n const name = nickname.toLowerCase();\n \n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Remove from config\n delete fileConfig.wallets[name];\n \n // Update default if needed\n if (fileConfig.default === name) {\n delete fileConfig.default;\n }\n if (this.defaultWallet === name) {\n this.defaultWallet = null;\n this.determineDefault();\n }\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n // Remove from memory\n this.wallets.delete(name);\n \n console.log(`[WalletManager] Removed wallet \"${nickname}\"`);\n }\n\n /**\n * Set the default wallet\n */\n async setDefaultWallet(nickname: string): Promise<void> {\n await this.initialize();\n \n const name = nickname.toLowerCase();\n \n if (!this.wallets.has(name)) {\n throw new Error(`Wallet \"${nickname}\" not found`);\n }\n \n // Load config\n const fileConfig = this.loadConfigFromFile();\n \n // Update default\n fileConfig.default = name;\n this.defaultWallet = name;\n \n // Save config\n this.saveConfigToFile(fileConfig);\n \n console.log(`[WalletManager] Set default wallet to \"${nickname}\"`);\n }\n\n /**\n * Load config from file (creates empty if doesn't exist)\n */\n private loadConfigFromFile(): WalletsConfigFile {\n if (!existsSync(CONFIG_PATH)) {\n return { wallets: {} };\n }\n \n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n return JSON.parse(content) as WalletsConfigFile;\n } catch {\n return { wallets: {} };\n }\n }\n\n /**\n * Save config to file\n */\n private saveConfigToFile(config: WalletsConfigFile): void {\n // Ensure directory exists\n const dir = dirname(CONFIG_PATH);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n \n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n console.log(`[WalletManager] Config saved to ${CONFIG_PATH}`);\n }\n\n /**\n * Convert a backend to config (for saving auto-discovered wallets)\n */\n private async backendToConfig(backend: IWalletBackend): Promise<WalletConfig> {\n const config: WalletConfig = {\n source: backend.type,\n chains: [...backend.supportedChains],\n };\n \n // For evm-wallet-skill, just reference the default path\n if (backend.type === 'evm-wallet-skill') {\n config.path = EVM_WALLET_PATH;\n }\n \n // For env backends, we need address (privateKey should NOT be saved)\n if (backend.type === 'env') {\n config.address = await backend.getAddress();\n // Note: We don't save privateKey to config for security\n }\n \n // For bankr, we need the API key\n if (backend.type === 'bankr') {\n config.apiKey = process.env.BANKR_API_KEY;\n }\n \n return config;\n }\n\n /**\n * Reset the manager (for testing)\n */\n reset(): void {\n this.wallets.clear();\n this.defaultWallet = null;\n this.initialized = false;\n }\n}\n\n/**\n * Get the singleton WalletManager instance\n */\nexport function getWalletManager(): WalletManager {\n if (!instance) {\n instance = new WalletManager();\n }\n return instance;\n}\n\n/**\n * Reset the singleton (for testing)\n */\nexport function resetWalletManager(): void {\n if (instance) {\n instance.reset();\n instance = null;\n }\n}\n",
1049 "inputSchema": {},
1050 "outputSchema": null,
1051 "icons": null,
1052 "annotations": null,
1053 "meta": null,
1054 "execution": null
1055 },
1056 {
1057 "name": "LocalKeyBackend.ts",
1058 "title": null,
1059 "description": "Script: LocalKeyBackend.ts. Code:\n/**\n * Local Key Backend\n * \n * Wallet backend implementation using local private keys.\n * Compatible with evm-wallet-skill's key storage.\n * \n * Uses viem for transaction signing and submission.\n */\n\nimport {\n createPublicClient,\n createWalletClient,\n http,\n type Hash,\n type Address,\n type Chain,\n} from 'viem';\nimport { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts';\nimport type { \n IWalletBackend, \n LocalKeyBackendConfig, \n TransactionRequest, \n TransactionReceipt \n} from './types';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n\n/**\n * Local private key wallet backend\n * \n * Signs transactions locally using the provided private key.\n * This is the standard backend for self-custody wallets.\n */\nexport class LocalKeyBackend implements IWalletBackend {\n readonly type = 'localKey' as const;\n \n private readonly account: PrivateKeyAccount;\n private readonly walletClient: ReturnType<typeof createWalletClient>;\n private readonly _publicClient: ReturnType<typeof createPublicClient>;\n private readonly chainId: number;\n private readonly chain: Chain;\n\n constructor(config: LocalKeyBackendConfig) {\n // Resolve chain configuration\n this.chainId = resolveChainId(config.chainId);\n this.chain = getViemChain(this.chainId);\n \n // Get RPC URL (custom or default)\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(this.chainId);\n \n // Create account from private key\n this.account = privateKeyToAccount(config.privateKey);\n \n // Create viem clients\n this._publicClient = createPublicClient({\n chain: this.chain,\n transport: http(rpcUrl),\n });\n \n this.walletClient = createWalletClient({\n account: this.account,\n chain: this.chain,\n transport: http(rpcUrl),\n });\n \n console.log(`[LocalKeyBackend] Initialized for chain ${this.chain.name} (${this.chainId})`);\n console.log(`[LocalKeyBackend] Address: ${this.account.address}`);\n }\n\n /**\n * Get the wallet address\n */\n async getAddress(): Promise<Address> {\n return this.account.address;\n }\n\n /**\n * Send a transaction\n * \n * Signs locally and submits via RPC.\n */\n async sendTransaction(tx: TransactionRequest): Promise<Hash> {\n console.log(`[LocalKeyBackend] Sending transaction to ${tx.to}`);\n \n // Build transaction params\n const txParams: any = {\n account: this.account,\n chain: this.chain,\n to: tx.to,\n value: tx.value || 0n,\n data: tx.data,\n };\n \n // Add optional gas parameters\n if (tx.gasLimit) txParams.gas = tx.gasLimit;\n if (tx.gasPrice) txParams.gasPrice = tx.gasPrice;\n if (tx.maxFeePerGas) txParams.maxFeePerGas = tx.maxFeePerGas;\n if (tx.maxPriorityFeePerGas) txParams.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;\n if (tx.nonce !== undefined) txParams.nonce = tx.nonce;\n \n const hash = await this.walletClient.sendTransaction(txParams);\n \n console.log(`[LocalKeyBackend] Transaction sent: ${hash}`);\n return hash;\n }\n\n /**\n * Wait for transaction confirmation\n */\n async waitForTransaction(txHash: Hash): Promise<TransactionReceipt> {\n console.log(`[LocalKeyBackend] Waiting for transaction: ${txHash}`);\n \n const receipt = await this._publicClient.waitForTransactionReceipt({\n hash: txHash,\n });\n \n console.log(`[LocalKeyBackend] Transaction confirmed in block ${receipt.blockNumber}`);\n \n return {\n transactionHash: receipt.transactionHash,\n blockNumber: receipt.blockNumber,\n blockHash: receipt.blockHash,\n from: receipt.from,\n to: receipt.to,\n gasUsed: receipt.gasUsed,\n status: receipt.status === 'success' ? 'success' : 'reverted',\n logs: receipt.logs.map((log: any) => ({\n address: log.address,\n topics: [...(log.topics || [])] as `0x${string}`[],\n data: log.data,\n })),\n };\n }\n\n /**\n * Check if backend is ready\n * \n * For local key backend, we verify RPC connectivity.\n */\n async isReady(): Promise<boolean> {\n try {\n await this._publicClient.getChainId();\n return true;\n } catch (error) {\n console.error('[LocalKeyBackend] RPC connectivity check failed:', error);\n return false;\n }\n }\n\n /**\n * Get the chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n\n /**\n * Get the public client (for external use)\n */\n getPublicClient(): ReturnType<typeof createPublicClient> {\n return this._publicClient;\n }\n\n /**\n * Get the wallet client (for advanced use cases)\n */\n getWalletClient(): ReturnType<typeof createWalletClient> {\n return this.walletClient;\n }\n}\n\n/**\n * Create a LocalKeyBackend from configuration\n */\nexport async function createLocalKeyBackend(config: LocalKeyBackendConfig): Promise<LocalKeyBackend> {\n const backend = new LocalKeyBackend(config);\n \n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[LocalKeyBackend] Backend created but RPC connectivity check failed');\n }\n \n return backend;\n}\n",
1060 "inputSchema": {},
1061 "outputSchema": null,
1062 "icons": null,
1063 "annotations": null,
1064 "meta": null,
1065 "execution": null
1066 },
1067 {
1068 "name": "index.ts",
1069 "title": null,
1070 "description": "Script: index.ts. Code:\n/**\n * Wallet Providers\n * \n * Pluggable wallet backend architecture for Amped DeFi plugin.\n * \n * @example\n * ```typescript\n * import { AmpedWalletProvider } from './wallet/providers';\n * \n * // Create with local private key (evm-wallet-skill compatible)\n * const provider = await AmpedWalletProvider.fromPrivateKey({\n * privateKey: '0x...',\n * chainId: 'lightlink',\n * });\n * \n * // Or create with Bankr backend\n * const bankrProvider = await AmpedWalletProvider.fromBankr({\n * bankrApiUrl: 'https://api.bankr.xyz',\n * bankrApiKey: 'your-api-key',\n * userAddress: '0x...',\n * chainId: 'base',\n * });\n * ```\n */\n\n// Main provider\nexport { AmpedWalletProvider } from './AmpedWalletProvider';\n\n// Backends\nexport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nexport { BankrBackend, createBankrBackend } from './BankrBackend';\n\n// Chain configuration\nexport {\n CHAIN_IDS,\n SDK_CHAIN_ID_MAP,\n DEFAULT_RPC_URLS,\n hyper,\n resolveChainId,\n getViemChain,\n getDefaultRpcUrl,\n isChainSupported,\n getSupportedChainIds,\n getChainName,\n} from './chainConfig';\n\n// Types\nexport type {\n WalletBackendType,\n WalletBackendBaseConfig,\n LocalKeyBackendConfig,\n BankrBackendConfig,\n PrivyBackendConfig,\n SmartWalletBackendConfig,\n WalletBackendConfig,\n TransactionRequest,\n TransactionReceipt,\n IWalletBackend,\n WalletBackendFactory,\n AmpedWalletProviderConfig,\n IAmpedWalletProvider,\n} from './types';\n",
1071 "inputSchema": {},
1072 "outputSchema": null,
1073 "icons": null,
1074 "annotations": null,
1075 "meta": null,
1076 "execution": null
1077 },
1078 {
1079 "name": "AmpedWalletProvider.ts",
1080 "title": null,
1081 "description": "Script: AmpedWalletProvider.ts. Code:\n/**\n * Amped Wallet Provider\n * \n * Custom wallet provider implementing IEvmWalletProvider from @sodax/types.\n * \n * This replaces wallet-sdk-core's EvmWalletProvider with a more flexible\n * implementation that:\n * 1. Supports all chains including LightLink and HyperEVM\n * 2. Has pluggable backends (local keys, Bankr, etc.)\n * 3. Provides a unified interface for the SODAX SDK\n * \n * Architecture:\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 AmpedWalletProvider \\u2502\n * \\u2502 (implements IEvmWalletProvider) \\u2502\n * \\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524\n * \\u2502 - SDK-compatible interface \\u2502\n * \\u2502 - Chain resolution (all chains) \\u2502\n * \\u2502 - Transaction formatting \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u252c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n * \\u2502\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u25bc \\u25bc\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\n * \\u2502 LocalKeyBack. \\u2502 \\u2502 BankrBackend \\u2502\n * \\u2502 (evm-wallet) \\u2502 \\u2502 (API calls) \\u2502\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\n */\n\nimport {\n createPublicClient,\n http,\n type Hash,\n type Address,\n} from 'viem';\nimport type { \n IEvmWalletProvider, \n EvmRawTransaction, \n EvmRawTransactionReceipt \n} from '@sodax/types';\nimport type { \n IWalletBackend, \n WalletBackendConfig, \n WalletBackendType,\n IAmpedWalletProvider,\n LocalKeyBackendConfig,\n BankrBackendConfig,\n} from './types';\nimport { LocalKeyBackend, createLocalKeyBackend } from './LocalKeyBackend';\nimport { BankrBackend, createBankrBackend } from './BankrBackend';\nimport { getViemChain, getDefaultRpcUrl, resolveChainId } from './chainConfig';\n\n/**\n * Amped Wallet Provider\n * \n * A drop-in replacement for wallet-sdk-core's EvmWalletProvider\n * that supports all SODAX chains including LightLink and HyperEVM.\n */\nexport class AmpedWalletProvider implements IAmpedWalletProvider {\n readonly publicClient: ReturnType<typeof createPublicClient>;\n \n private readonly backend: IWalletBackend;\n private readonly chainId: number;\n\n private constructor(backend: IWalletBackend, publicClient: ReturnType<typeof createPublicClient>) {\n this.backend = backend;\n this.publicClient = publicClient;\n this.chainId = backend.getChainId();\n \n console.log(`[AmpedWalletProvider] Initialized with ${backend.type} backend`);\n console.log(`[AmpedWalletProvider] Chain ID: ${this.chainId}`);\n }\n\n /**\n * Create an AmpedWalletProvider with a local key backend\n * \n * @param config - Configuration matching EvmWalletProvider's PrivateKeyEvmWalletConfig\n * @returns AmpedWalletProvider instance\n */\n static async fromPrivateKey(config: {\n privateKey: `0x${string}`;\n chainId: string | number;\n rpcUrl?: string;\n }): Promise<AmpedWalletProvider> {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n\n // Create backend\n const backend = await createLocalKeyBackend({\n type: 'localKey',\n privateKey: config.privateKey,\n chainId: config.chainId,\n rpcUrl,\n });\n\n // Use the backend's public client\n const publicClient = backend.getPublicClient() as any;\n\n return new AmpedWalletProvider(backend, publicClient);\n }\n\n /**\n * Create an AmpedWalletProvider with a Bankr backend\n * \n * @param config - Bankr backend configuration\n * @returns AmpedWalletProvider instance\n */\n static async fromBankr(config: {\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n chainId: string | number;\n rpcUrl?: string;\n policy?: BankrBackendConfig['policy'];\n }): Promise<AmpedWalletProvider> {\n const chainId = resolveChainId(config.chainId);\n const rpcUrl = config.rpcUrl || getDefaultRpcUrl(chainId);\n const chain = getViemChain(chainId);\n\n // Create backend\n const backend = await createBankrBackend({\n type: 'bankr',\n bankrApiUrl: config.bankrApiUrl,\n bankrApiKey: config.bankrApiKey,\n userAddress: config.userAddress,\n chainId: config.chainId,\n rpcUrl,\n policy: config.policy,\n });\n\n // Create public client (for read operations)\n // Bankr backend doesn't have its own public client\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n }) as any; // Type cast needed due to viem's strict typing\n\n return new AmpedWalletProvider(backend, publicClient);\n }\n\n /**\n * Create from generic backend configuration\n */\n static async fromConfig(config: WalletBackendConfig): Promise<AmpedWalletProvider> {\n switch (config.type) {\n case 'localKey':\n return AmpedWalletProvider.fromPrivateKey({\n privateKey: (config as LocalKeyBackendConfig).privateKey,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n });\n \n case 'bankr':\n const bankrConfig = config as BankrBackendConfig;\n return AmpedWalletProvider.fromBankr({\n bankrApiUrl: bankrConfig.bankrApiUrl,\n bankrApiKey: bankrConfig.bankrApiKey,\n userAddress: bankrConfig.userAddress,\n chainId: config.chainId,\n rpcUrl: config.rpcUrl,\n policy: bankrConfig.policy,\n });\n \n default:\n throw new Error(`Unsupported backend type: ${(config as any).type}`);\n }\n }\n\n // ===== IEvmWalletProvider Implementation =====\n\n /**\n * Get the wallet address\n */\n async getWalletAddress(): Promise<Address> {\n return this.backend.getAddress();\n }\n\n /**\n * Send a transaction\n * \n * Converts SDK's EvmRawTransaction format to internal format\n * and delegates to the backend.\n */\n async sendTransaction(evmRawTx: EvmRawTransaction): Promise<Hash> {\n console.log(`[AmpedWalletProvider] sendTransaction`);\n console.log(`[AmpedWalletProvider] From: ${evmRawTx.from}`);\n console.log(`[AmpedWalletProvider] To: ${evmRawTx.to}`);\n console.log(`[AmpedWalletProvider] Value: ${evmRawTx.value}`);\n\n return this.backend.sendTransaction({\n to: evmRawTx.to,\n value: evmRawTx.value,\n data: evmRawTx.data,\n });\n }\n\n /**\n * Wait for transaction receipt\n * \n * Converts internal receipt format to SDK's EvmRawTransactionReceipt format.\n */\n async waitForTransactionReceipt(txHash: Hash): Promise<EvmRawTransactionReceipt> {\n console.log(`[AmpedWalletProvider] waitForTransactionReceipt: ${txHash}`);\n \n const receipt = await this.backend.waitForTransaction(txHash);\n \n // Convert to SDK format\n return {\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0', // Not tracked in our simplified receipt\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n from: receipt.from,\n to: receipt.to,\n cumulativeGasUsed: '0x0', // Not tracked\n gasUsed: `0x${receipt.gasUsed.toString(16)}`,\n contractAddress: null, // Would need to check if this was a deployment\n logs: receipt.logs.map(log => ({\n address: log.address,\n topics: log.topics as [`0x${string}`, ...`0x${string}`[]] | [],\n data: log.data,\n blockHash: receipt.blockHash,\n blockNumber: `0x${receipt.blockNumber.toString(16)}`,\n logIndex: '0x0',\n transactionHash: receipt.transactionHash,\n transactionIndex: '0x0',\n removed: false,\n })),\n logsBloom: '0x',\n status: receipt.status === 'success' ? '0x1' : '0x0',\n };\n }\n\n // ===== IAmpedWalletProvider Extensions =====\n\n /**\n * Get the underlying backend\n */\n getBackend(): IWalletBackend {\n return this.backend;\n }\n\n /**\n * Get the backend type\n */\n getBackendType(): WalletBackendType {\n return this.backend.type;\n }\n\n /**\n * Check if ready for transactions\n */\n async isReady(): Promise<boolean> {\n return this.backend.isReady();\n }\n\n /**\n * Get chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n}\n\n// Re-export for convenience\nexport type { IAmpedWalletProvider };\n",
1082 "inputSchema": {},
1083 "outputSchema": null,
1084 "icons": null,
1085 "annotations": null,
1086 "meta": null,
1087 "execution": null
1088 },
1089 {
1090 "name": "chainConfig.ts",
1091 "title": null,
1092 "description": "Script: chainConfig.ts. Code:\n/**\n * Chain Configuration for Amped Wallet Provider\n *\n * Complete chain configuration for all SODAX-supported EVM chains.\n * \n * Note: We maintain our own chain definitions to avoid viem version\n * mismatches with @sodax/wallet-sdk-core. The SDK's getEvmViemChain()\n * is used as a fallback for future chain additions.\n */\n\nimport { defineChain, type Chain } from 'viem';\nimport {\n mainnet,\n arbitrum,\n optimism,\n base,\n polygon,\n bsc,\n avalanche,\n sonic,\n lightlinkPhoenix,\n} from 'viem/chains';\n\n/**\n * Chain ID constants matching @sodax/types\n */\nexport const CHAIN_IDS = {\n ETHEREUM: 1,\n ARBITRUM: 42161,\n OPTIMISM: 10,\n BASE: 8453,\n POLYGON: 137,\n BSC: 56,\n AVALANCHE: 43114,\n SONIC: 146,\n LIGHTLINK: 1890,\n HYPEREVM: 999,\n KAIA: 8217,\n} as const;\n\n/**\n * SDK chain ID format mapping (e.g., 'ethereum', '0x2105.base')\n */\nexport const SDK_CHAIN_ID_MAP: Record<string, number> = {\n 'ethereum': CHAIN_IDS.ETHEREUM,\n 'arbitrum': CHAIN_IDS.ARBITRUM,\n '0xa4b1.arbitrum': CHAIN_IDS.ARBITRUM,\n 'optimism': CHAIN_IDS.OPTIMISM,\n '0xa.optimism': CHAIN_IDS.OPTIMISM,\n 'base': CHAIN_IDS.BASE,\n '0x2105.base': CHAIN_IDS.BASE,\n 'polygon': CHAIN_IDS.POLYGON,\n '0x89.polygon': CHAIN_IDS.POLYGON,\n 'bsc': CHAIN_IDS.BSC,\n '0x38.bsc': CHAIN_IDS.BSC,\n 'avalanche': CHAIN_IDS.AVALANCHE,\n 'avax': CHAIN_IDS.AVALANCHE,\n '0xa86a.avax': CHAIN_IDS.AVALANCHE,\n 'sonic': CHAIN_IDS.SONIC,\n 'lightlink': CHAIN_IDS.LIGHTLINK,\n 'hyperevm': CHAIN_IDS.HYPEREVM,\n 'hyper': CHAIN_IDS.HYPEREVM,\n 'kaia': CHAIN_IDS.KAIA,\n '0x2019.kaia': CHAIN_IDS.KAIA,\n};\n\n/**\n * HyperEVM chain definition\n * Matches @sodax/wallet-sdk-core hyper definition\n */\nexport const hyper = defineChain({\n id: CHAIN_IDS.HYPEREVM,\n name: 'HyperEVM',\n nativeCurrency: { decimals: 18, name: 'HYPE', symbol: 'HYPE' },\n rpcUrls: { default: { http: ['https://rpc.hyperliquid.xyz/evm'] } },\n blockExplorers: { default: { name: 'HyperEVMScan', url: 'https://hyperevmscan.io/' } },\n contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11', blockCreated: 13051 } },\n});\n\n/**\n * Kaia chain definition\n */\nexport const kaia = defineChain({\n id: CHAIN_IDS.KAIA,\n name: 'Kaia',\n nativeCurrency: { decimals: 18, name: 'KAIA', symbol: 'KAIA' },\n rpcUrls: { default: { http: ['https://public-en.node.kaia.io'] } },\n blockExplorers: { default: { name: 'KaiaScan', url: 'https://kaiascan.io/' } },\n});\n\n/**\n * Chain configuration by numeric ID\n */\nconst CHAIN_CONFIG: Record<number, Chain> = {\n [CHAIN_IDS.ETHEREUM]: mainnet,\n [CHAIN_IDS.ARBITRUM]: arbitrum,\n [CHAIN_IDS.OPTIMISM]: optimism,\n [CHAIN_IDS.BASE]: base,\n [CHAIN_IDS.POLYGON]: polygon,\n [CHAIN_IDS.BSC]: bsc,\n [CHAIN_IDS.AVALANCHE]: avalanche,\n [CHAIN_IDS.SONIC]: sonic,\n [CHAIN_IDS.LIGHTLINK]: lightlinkPhoenix,\n [CHAIN_IDS.HYPEREVM]: hyper,\n [CHAIN_IDS.KAIA]: kaia,\n};\n\n/**\n * Default RPC URLs for all supported chains\n */\n/**\n * FALLBACK RPC URLs for all supported chains\n * Primary RPCs should come from evm-wallet-skill (chains.js)\n * @see https://github.com/amped-finance/evm-wallet-skill\n */\nexport const DEFAULT_RPC_URLS: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'https://ethereum.publicnode.com',\n [CHAIN_IDS.ARBITRUM]: 'https://arb1.arbitrum.io/rpc',\n [CHAIN_IDS.OPTIMISM]: 'https://mainnet.optimism.io',\n [CHAIN_IDS.BASE]: 'https://mainnet.base.org',\n [CHAIN_IDS.POLYGON]: 'https://polygon-bor-rpc.publicnode.com',\n [CHAIN_IDS.BSC]: 'https://bsc-dataseed.binance.org',\n [CHAIN_IDS.AVALANCHE]: 'https://api.avax.network/ext/bc/C/rpc',\n [CHAIN_IDS.SONIC]: 'https://rpc.soniclabs.com',\n [CHAIN_IDS.LIGHTLINK]: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n [CHAIN_IDS.HYPEREVM]: 'https://rpc.hyperliquid.xyz/evm',\n [CHAIN_IDS.KAIA]: 'https://public-en.node.kaia.io',\n};\n\n/**\n * Resolve SDK chain ID format to numeric chain ID\n */\nexport function resolveChainId(sdkChainId: string | number): number {\n if (typeof sdkChainId === 'number') return sdkChainId;\n\n const lower = sdkChainId.toLowerCase();\n if (SDK_CHAIN_ID_MAP[lower] !== undefined) return SDK_CHAIN_ID_MAP[lower];\n\n if (lower.startsWith('0x')) {\n const parsed = parseInt(lower, 16);\n if (!isNaN(parsed)) return parsed;\n }\n\n const parsed = parseInt(sdkChainId, 10);\n if (!isNaN(parsed)) return parsed;\n\n throw new Error(`Unable to resolve chain ID: ${sdkChainId}`);\n}\n\n/**\n * Get viem Chain configuration for a chain ID\n */\nexport function getViemChain(chainId: string | number): Chain {\n const numericId = resolveChainId(chainId);\n const chain = CHAIN_CONFIG[numericId];\n if (!chain) throw new Error(`Unsupported chain ID: ${chainId} (resolved to ${numericId})`);\n return chain;\n}\n\n/**\n * Get default RPC URL for a chain\n */\nexport function getDefaultRpcUrl(chainId: string | number): string {\n const numericId = resolveChainId(chainId);\n const rpcUrl = DEFAULT_RPC_URLS[numericId];\n if (!rpcUrl) throw new Error(`No default RPC URL for chain ID: ${chainId}`);\n return rpcUrl;\n}\n\n/**\n * Check if a chain is supported\n */\nexport function isChainSupported(chainId: string | number): boolean {\n try {\n getViemChain(chainId);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get all supported chain IDs\n */\nexport function getSupportedChainIds(): number[] {\n return Object.keys(CHAIN_CONFIG).map(Number);\n}\n\n/**\n * Get chain name\n */\nexport function getChainName(chainId: string | number): string {\n return getViemChain(chainId).name;\n}\n",
1093 "inputSchema": {},
1094 "outputSchema": null,
1095 "icons": null,
1096 "annotations": null,
1097 "meta": null,
1098 "execution": null
1099 },
1100 {
1101 "name": "BankrBackend.ts",
1102 "title": null,
1103 "description": "Script: BankrBackend.ts. Code:\n/**\n * Bankr Backend - Transaction Execution Layer\n *\n * CRITICAL: This backend is for EXECUTION ONLY, not routing.\n *\n * Architecture:\n * SODAX SDK (routing) \u2192 BankrBackend (execution) \u2192 Blockchain\n *\n * What Bankr DOES:\n * \u2713 Signs the pre-computed transaction from SODAX\n * \u2713 Submits to blockchain via Bankr API\n * \u2713 Returns transaction hash\n *\n * What Bankr does NOT do:\n * \u2717 NO routing decisions\n * \u2717 NO DeFi protocol selection\n * \u2717 NO swap optimization\n * \u2717 NO interpretation of intent\n *\n * The SODAX SDK always handles routing logic. Bankr receives the exact\n * transaction data (to, data, value, chainId) and submits it verbatim.\n *\n * @see SKILL.md \"Transaction Execution Architecture\" section\n */\n\nimport type { Hash, Address } from 'viem';\nimport type { \n IWalletBackend, \n BankrBackendConfig, \n TransactionRequest, \n TransactionReceipt \n} from './types';\nimport { resolveChainId } from './chainConfig';\n\n/**\n * Serialize error objects for readable error messages\n */\nfunction serializeError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Bankr Agent API response types\n */\ninterface BankrJobSubmitResponse {\n success: boolean;\n jobId: string;\n status: 'pending';\n message: string;\n}\n\ninterface BankrJobStatusResponse {\n success: boolean;\n jobId: string;\n status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';\n prompt: string;\n response?: string;\n error?: string;\n richData?: Array<{ type?: string; [key: string]: unknown }>;\n statusUpdates?: Array<{ message: string; timestamp: string }>;\n createdAt: string;\n completedAt?: string;\n processingTime?: number;\n}\n\n/**\n * Chain ID to chain name mapping for Bankr prompts\n */\nconst CHAIN_NAMES: Record<number, string> = {\n 1: 'ethereum',\n 8453: 'base',\n 137: 'polygon',\n 42161: 'arbitrum',\n 10: 'optimism',\n 1890: 'lightlink',\n 146: 'sonic',\n};\n\n/**\n * Bankr execution backend\n * \n * Delegates transaction execution to Bankr's Agent API.\n * The agent never has direct access to private keys.\n */\nexport class BankrBackend implements IWalletBackend {\n readonly type = 'bankr' as const;\n \n private readonly apiUrl: string;\n private readonly apiKey: string;\n private readonly userAddress: Address;\n private readonly chainId: number;\n private readonly policy?: BankrBackendConfig['policy'];\n \n // Polling configuration\n private readonly pollIntervalMs = 2000;\n private readonly maxPollAttempts = 150; // 5 minutes max\n\n constructor(config: BankrBackendConfig) {\n this.apiUrl = config.bankrApiUrl || 'https://api.bankr.bot';\n this.apiKey = config.bankrApiKey;\n this.userAddress = config.userAddress;\n this.chainId = resolveChainId(config.chainId);\n this.policy = config.policy;\n \n console.log(`[BankrBackend] Initialized for chain ${this.chainId}`);\n console.log(`[BankrBackend] User address: ${this.userAddress}`);\n console.log(`[BankrBackend] API URL: ${this.apiUrl}`);\n }\n\n /**\n * Get the wallet address (Bankr-provisioned)\n */\n async getAddress(): Promise<Address> {\n return this.userAddress;\n }\n\n /**\n * Send a transaction via Bankr Agent API\n * \n * Formats the transaction as a natural language prompt and submits\n * to Bankr's async job system.\n */\n async sendTransaction(tx: TransactionRequest): Promise<Hash> {\n console.log(`[BankrBackend] Sending transaction via Bankr API`);\n console.log(`[BankrBackend] To: ${tx.to}`);\n console.log(`[BankrBackend] Value: ${tx.value || 0n}`);\n console.log(`[BankrBackend] Data: ${tx.data ? tx.data.slice(0, 20) + '...' : '0x'}`);\n \n // Validate against policy\n if (this.policy) {\n await this.validatePolicy(tx);\n }\n\n // Format transaction as JSON for Bankr prompt\n const txJson = JSON.stringify({\n to: tx.to,\n data: tx.data || '0x',\n value: (tx.value || 0n).toString(),\n chainId: this.chainId,\n });\n\n // Create natural language prompt for Bankr\n const prompt = `Submit this transaction: ${txJson}`;\n \n console.log(`[BankrBackend] Submitting prompt to Bankr API`);\n\n // Submit job to Bankr\n const jobId = await this.submitJob(prompt);\n console.log(`[BankrBackend] Job submitted: ${jobId}`);\n\n // Poll for completion\n const result = await this.pollJobUntilComplete(jobId);\n \n // Extract transaction hash from response\n const txHash = this.extractTransactionHash(result);\n \n if (!txHash) {\n throw new Error(`[BankrBackend] Transaction failed: ${serializeError(result.response || result.error) || 'Unknown error'}`);\n }\n\n console.log(`[BankrBackend] Transaction hash: ${txHash}`);\n return txHash;\n }\n\n /**\n * Wait for transaction confirmation\n * \n * Note: With Bankr, the transaction is already confirmed when we get\n * the response. This method exists for interface compatibility but\n * returns a minimal receipt.\n */\n async waitForTransaction(txHash: Hash): Promise<TransactionReceipt> {\n console.log(`[BankrBackend] waitForTransaction called for: ${txHash}`);\n \n // Bankr transactions are confirmed when the job completes\n // We return a minimal receipt since we don't have full details\n return {\n transactionHash: txHash,\n blockNumber: 0n, // Unknown - would need to query chain\n blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000' as Hash,\n from: this.userAddress,\n to: null,\n gasUsed: 0n,\n status: 'success',\n logs: [],\n };\n }\n\n /**\n * Check if backend is ready\n * \n * Verifies Bankr API connectivity with a simple balance query.\n */\n async isReady(): Promise<boolean> {\n if (!this.apiUrl || !this.apiKey || !this.userAddress) {\n return false;\n }\n\n try {\n // Test API connectivity with a simple query\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt: 'ping' }),\n });\n \n // Even a 4xx error means API is reachable\n return response.status !== 503 && response.status !== 502;\n } catch (error) {\n console.error('[BankrBackend] Connectivity check failed:', error);\n return false;\n }\n }\n\n /**\n * Get the chain ID\n */\n getChainId(): number {\n return this.chainId;\n }\n\n /**\n * Submit a job to Bankr Agent API\n */\n private async submitJob(prompt: string): Promise<string> {\n const response = await fetch(`${this.apiUrl}/agent/prompt`, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ prompt }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to submit job: ${response.status} ${error}`);\n }\n\n const data = await response.json() as BankrJobSubmitResponse;\n \n if (!data.success || !data.jobId) {\n throw new Error(`[BankrBackend] Invalid job submission response: ${JSON.stringify(data)}`);\n }\n\n return data.jobId;\n }\n\n /**\n * Poll for job completion\n */\n private async pollJobUntilComplete(jobId: string): Promise<BankrJobStatusResponse> {\n let lastStatus = '';\n \n for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {\n await this.sleep(this.pollIntervalMs);\n \n const result = await this.getJobStatus(jobId);\n \n // Log status changes\n if (result.status !== lastStatus) {\n console.log(`[BankrBackend] Job ${jobId} status: ${result.status}`);\n lastStatus = result.status;\n }\n \n // Log status updates\n if (result.statusUpdates && result.statusUpdates.length > 0) {\n const lastUpdate = result.statusUpdates[result.statusUpdates.length - 1];\n console.log(`[BankrBackend] Progress: ${lastUpdate.message}`);\n }\n\n // Check for terminal states\n switch (result.status) {\n case 'completed':\n return result;\n case 'failed':\n throw new Error(`[BankrBackend] Job failed: ${serializeError(result.error) || 'Unknown error'}`);\n case 'cancelled':\n throw new Error(`[BankrBackend] Job was cancelled`);\n case 'pending':\n case 'processing':\n // Continue polling\n break;\n default:\n console.warn(`[BankrBackend] Unknown status: ${result.status}`);\n }\n }\n\n throw new Error(`[BankrBackend] Job ${jobId} timed out after ${this.maxPollAttempts * this.pollIntervalMs / 1000} seconds`);\n }\n\n /**\n * Get job status from Bankr API\n */\n private async getJobStatus(jobId: string): Promise<BankrJobStatusResponse> {\n const response = await fetch(`${this.apiUrl}/agent/job/${jobId}`, {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`[BankrBackend] Failed to get job status: ${response.status} ${error}`);\n }\n\n return await response.json() as BankrJobStatusResponse;\n }\n\n /**\n * Extract transaction hash from Bankr response\n * \n * The response may contain the tx hash in various formats:\n * - In richData array\n * - In the response text (e.g., \"Transaction hash: 0x...\")\n */\n private extractTransactionHash(result: BankrJobStatusResponse): Hash | null {\n // Check richData for transaction info\n if (result.richData) {\n for (const item of result.richData) {\n if (item.transactionHash) {\n return item.transactionHash as Hash;\n }\n if (item.txHash) {\n return item.txHash as Hash;\n }\n if (item.hash) {\n return item.hash as Hash;\n }\n }\n }\n\n // Try to extract from response text\n if (result.response) {\n // Look for hex transaction hash pattern (0x followed by 64 hex chars)\n const hashMatch = result.response.match(/0x[a-fA-F0-9]{64}/);\n if (hashMatch) {\n return hashMatch[0] as Hash;\n }\n \n // Check if response indicates failure\n if (result.response.toLowerCase().includes('reverted') ||\n result.response.toLowerCase().includes('failed') ||\n result.response.toLowerCase().includes('insufficient')) {\n console.error(`[BankrBackend] Transaction failed: ${result.response}`);\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Validate transaction against policy\n */\n private async validatePolicy(tx: TransactionRequest): Promise<void> {\n if (!this.policy) return;\n\n // Check max value per transaction\n if (this.policy.maxValuePerTx && tx.value && tx.value > this.policy.maxValuePerTx) {\n throw new Error(\n `Transaction value ${tx.value} exceeds max allowed ${this.policy.maxValuePerTx}`\n );\n }\n\n // Check allowed contracts\n if (this.policy.allowedContracts && this.policy.allowedContracts.length > 0) {\n if (!this.policy.allowedContracts.includes(tx.to)) {\n throw new Error(\n `Contract ${tx.to} is not in the allowed contracts list`\n );\n }\n }\n }\n\n /**\n * Sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Create a BankrBackend from configuration\n */\nexport async function createBankrBackend(config: BankrBackendConfig): Promise<BankrBackend> {\n const backend = new BankrBackend(config);\n \n // Verify connectivity\n const ready = await backend.isReady();\n if (!ready) {\n console.warn('[BankrBackend] Backend created but connectivity check failed');\n }\n \n return backend;\n}\n",
1104 "inputSchema": {},
1105 "outputSchema": null,
1106 "icons": null,
1107 "annotations": null,
1108 "meta": null,
1109 "execution": null
1110 },
1111 {
1112 "name": "types.ts",
1113 "title": null,
1114 "description": "Script: types.ts. Code:\n/**\n * Wallet Provider Types\n * \n * Interfaces for the pluggable wallet backend architecture.\n * Allows the same AmpedWalletProvider to work with:\n * - Local private keys (evm-wallet-skill)\n * - Bankr execution API\n * - Future: Privy, smart contract wallets, etc.\n */\n\nimport type { Hash, Address } from 'viem';\nimport { createPublicClient } from 'viem';\nimport type { \n EvmRawTransaction, \n EvmRawTransactionReceipt,\n IEvmWalletProvider \n} from '@sodax/types';\n\n/**\n * Wallet backend type identifiers\n */\nexport type WalletBackendType = 'localKey' | 'bankr' | 'privy' | 'smartWallet';\n\n/**\n * Base configuration for all backends\n */\nexport interface WalletBackendBaseConfig {\n type: WalletBackendType;\n chainId: string | number;\n rpcUrl?: string;\n}\n\n/**\n * Configuration for local private key backend\n */\nexport interface LocalKeyBackendConfig extends WalletBackendBaseConfig {\n type: 'localKey';\n privateKey: `0x${string}`;\n}\n\n/**\n * Configuration for Bankr execution backend\n */\nexport interface BankrBackendConfig extends WalletBackendBaseConfig {\n type: 'bankr';\n bankrApiUrl: string;\n bankrApiKey: string;\n userAddress: Address;\n /** Optional: policy limits for transactions */\n policy?: {\n maxValuePerTx?: bigint;\n maxDailyVolume?: bigint;\n allowedContracts?: Address[];\n };\n}\n\n/**\n * Configuration for Privy server wallet backend (future)\n */\nexport interface PrivyBackendConfig extends WalletBackendBaseConfig {\n type: 'privy';\n appId: string;\n appSecret: string;\n walletId: string;\n}\n\n/**\n * Configuration for smart contract wallet backend (future)\n */\nexport interface SmartWalletBackendConfig extends WalletBackendBaseConfig {\n type: 'smartWallet';\n walletAddress: Address;\n sessionKey: `0x${string}`;\n entryPointAddress: Address;\n}\n\n/**\n * Union of all backend configurations\n */\nexport type WalletBackendConfig = \n | LocalKeyBackendConfig \n | BankrBackendConfig\n | PrivyBackendConfig\n | SmartWalletBackendConfig;\n\n/**\n * Transaction request (simplified)\n */\nexport interface TransactionRequest {\n to: Address;\n value?: bigint;\n data?: `0x${string}`;\n gasLimit?: bigint;\n gasPrice?: bigint;\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n nonce?: number;\n}\n\n/**\n * Transaction receipt (simplified)\n */\nexport interface TransactionReceipt {\n transactionHash: Hash;\n blockNumber: bigint;\n blockHash: Hash;\n from: Address;\n to: Address | null;\n gasUsed: bigint;\n status: 'success' | 'reverted';\n logs: Array<{\n address: Address;\n topics: `0x${string}`[];\n data: `0x${string}`;\n }>;\n}\n\n/**\n * Wallet backend interface\n * \n * All wallet backends must implement this interface.\n * This allows AmpedWalletProvider to delegate to different backends\n * without changing its own implementation.\n */\nexport interface IWalletBackend {\n /** Backend type identifier */\n readonly type: WalletBackendType;\n \n /** Get the wallet address */\n getAddress(): Promise<Address>;\n \n /** \n * Send a transaction and return the transaction hash\n * The backend is responsible for signing and submitting the transaction.\n */\n sendTransaction(tx: TransactionRequest): Promise<Hash>;\n \n /**\n * Wait for a transaction to be confirmed\n * Returns when the transaction is included in a block.\n */\n waitForTransaction(txHash: Hash): Promise<TransactionReceipt>;\n \n /**\n * Check if the backend can execute transactions\n * (e.g., Bankr backend may need API connectivity)\n */\n isReady(): Promise<boolean>;\n \n /**\n * Get the numeric chain ID this backend is configured for\n */\n getChainId(): number;\n}\n\n/**\n * Factory function type for creating backends\n */\nexport type WalletBackendFactory = (config: WalletBackendConfig) => Promise<IWalletBackend>;\n\n/**\n * Amped Wallet Provider configuration\n */\nexport interface AmpedWalletProviderConfig {\n /** Backend configuration */\n backend: WalletBackendConfig;\n /** Optional custom RPC URL (overrides chain default) */\n rpcUrl?: string;\n}\n\n/**\n * Extended IEvmWalletProvider with Amped-specific methods\n */\nexport interface IAmpedWalletProvider extends IEvmWalletProvider {\n /** Get the underlying backend */\n getBackend(): IWalletBackend;\n \n /** Get the backend type */\n getBackendType(): WalletBackendType;\n \n /** Check if ready for transactions */\n isReady(): Promise<boolean>;\n \n /** Get chain ID */\n getChainId(): number;\n}\n",
1115 "inputSchema": {},
1116 "outputSchema": null,
1117 "icons": null,
1118 "annotations": null,
1119 "meta": null,
1120 "execution": null
1121 },
1122 {
1123 "name": "skillWalletAdapter.ts",
1124 "title": null,
1125 "description": "Script: skillWalletAdapter.ts. Code:\n/**\n * EVM Wallet Skill Adapter\n * \n * Integrates with the evm-wallet-skill to reuse existing wallet configuration\n * instead of requiring custom AMPED_OC_WALLETS_JSON.\n * \n * Supports multiple wallet sources:\n * - ~/.evm-wallet.json (evm-wallet-skill default location)\n * - EVM_WALLETS_JSON environment variable\n * - WALLET_CONFIG_JSON environment variable\n * \n * @see https://github.com/surfer77/evm-wallet-skill\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ErrorCode, AmpedDefiError } from '../utils/errors';\nimport { normalizeChainId } from './types';\n\n// Try to import viem for address derivation\nlet privateKeyToAccount: ((key: `0x${string}`) => { address: string }) | null = null;\ntry {\n const viem = require('viem/accounts');\n privateKeyToAccount = viem.privateKeyToAccount;\n} catch {\n // viem not available, will use address from config\n}\n\n/**\n * FALLBACK RPC URLs - primary RPCs come from evm-wallet-skill\n * These are only used when evm-wallet-skill does not provide an RPC\n */\nconst FALLBACK_RPCS: Record<string, string> = {\n // SODAX supported spoke chains\n ethereum: 'https://ethereum.publicnode.com',\n arbitrum: 'https://arb1.arbitrum.io/rpc',\n base: 'https://mainnet.base.org',\n optimism: 'https://mainnet.optimism.io',\n avalanche: 'https://api.avax.network/ext/bc/C/rpc',\n bsc: 'https://bsc-dataseed.binance.org',\n polygon: 'https://polygon-bor-rpc.publicnode.com',\n // Sonic hub chain\n sonic: 'https://rpc.soniclabs.com',\n // Additional chains (may not be SODAX-supported but useful)\n lightlink: 'https://replicator.phoenix.lightlink.io/rpc/v1',\n};\n\n/**\n * Wallet information from evm-wallet-skill\n */\nexport interface EvmWalletInfo {\n id: string;\n address: string;\n chainId?: number;\n provider?: 'privateKey' | 'kms' | 'hardware' | 'web3Auth';\n}\n\n/**\n * Wallet adapter options\n */\nexport interface WalletAdapterOptions {\n preferSkill?: boolean;\n walletId?: string;\n}\n\n/**\n * EVM Wallet Skill Adapter\n */\nexport class EvmWalletSkillAdapter {\n private skillWallets: Map<string, EvmWalletInfo> = new Map();\n private skillRpcs: Map<string, string> = new Map();\n private useSkill: boolean;\n\n constructor(options: WalletAdapterOptions = {}) {\n this.useSkill = options.preferSkill !== false;\n if (this.useSkill) {\n this.loadSkillConfig();\n }\n }\n\n /**\n * Load configuration from evm-wallet-skill\n * Checks multiple sources in order:\n * 1. ~/.evm-wallet.json (evm-wallet-skill default)\n * 2. EVM_WALLETS_JSON environment variable\n * 3. WALLET_CONFIG_JSON environment variable\n */\n private loadSkillConfig(): void {\n // 1. Try ~/.evm-wallet.json first (evm-wallet-skill default location)\n this.loadEvmWalletFile();\n \n // 2. Try environment variables\n this.loadEnvWallets();\n \n // 3. Load RPC URLs from environment\n this.loadEnvRpcs();\n }\n\n /**\n * Load wallet from ~/.evm-wallet.json (evm-wallet-skill format)\n */\n private loadEvmWalletFile(): void {\n try {\n const walletPath = path.join(os.homedir(), '.evm-wallet.json');\n \n if (!fs.existsSync(walletPath)) {\n return;\n }\n\n const content = fs.readFileSync(walletPath, 'utf-8');\n const walletData = JSON.parse(content);\n\n // evm-wallet-skill stores: { privateKey: \"0x...\" } or { privateKey: \"0x...\", address: \"0x...\" }\n if (walletData.privateKey) {\n let address = walletData.address;\n \n // Derive address from private key if not provided\n if (!address && privateKeyToAccount) {\n try {\n const account = privateKeyToAccount(walletData.privateKey as `0x${string}`);\n address = account.address;\n } catch (e) {\n console.warn('[walletAdapter] Failed to derive address from private key');\n }\n }\n\n if (address) {\n this.skillWallets.set('default', {\n id: 'default',\n address,\n provider: 'privateKey',\n });\n // Store private key for later use\n (this.skillWallets.get('default') as any).privateKey = walletData.privateKey;\n console.log(`[walletAdapter] Loaded wallet from ~/.evm-wallet.json (${address.slice(0, 8)}...)`);\n }\n }\n } catch (error) {\n // Silently ignore - file may not exist\n }\n }\n\n /**\n * Load wallets from environment variables\n */\n private loadEnvWallets(): void {\n try {\n const skillWalletsJson = process.env.EVM_WALLETS_JSON || process.env.WALLET_CONFIG_JSON;\n\n if (skillWalletsJson) {\n const wallets = JSON.parse(skillWalletsJson);\n \n if (Array.isArray(wallets)) {\n wallets.forEach(w => {\n this.skillWallets.set(w.id || w.name || 'default', {\n id: w.id || w.name || 'default',\n address: w.address,\n chainId: w.chainId,\n provider: w.provider || w.type,\n });\n });\n } else if (typeof wallets === 'object') {\n Object.entries(wallets).forEach(([id, config]: [string, any]) => {\n this.skillWallets.set(id, {\n id,\n address: config.address,\n chainId: config.chainId,\n provider: config.provider || 'privateKey',\n });\n });\n }\n\n console.log(`[walletAdapter] Loaded ${this.skillWallets.size} wallets from environment`);\n }\n } catch (error) {\n console.warn('[walletAdapter] Failed to parse wallet environment variables:', error);\n }\n }\n\n /**\n * Load RPC URLs - uses defaults, then overrides with environment variables\n */\n private loadEnvRpcs(): void {\n // Start with default RPCs\n Object.entries(FALLBACK_RPCS).forEach(([chain, url]) => {\n this.skillRpcs.set(chain.toLowerCase(), url);\n });\n\n // Override with environment variables if provided\n try {\n const skillRpcsJson = process.env.AMPED_OC_RPC_URLS_JSON || \n process.env.EVM_RPC_URLS_JSON || \n process.env.RPC_URLS_JSON;\n\n if (skillRpcsJson) {\n const rpcs = JSON.parse(skillRpcsJson);\n Object.entries(rpcs).forEach(([chain, url]) => {\n this.skillRpcs.set(String(chain).toLowerCase(), url as string);\n });\n console.log(`[walletAdapter] Custom RPC URLs configured for: ${Object.keys(rpcs).join(', ')}`);\n }\n } catch (error) {\n console.warn('[walletAdapter] Failed to parse RPC environment variables:', error);\n }\n\n console.log(`[walletAdapter] ${this.skillRpcs.size} RPC URLs available (includes defaults)`);\n }\n\n /**\n * Get wallet address - tries skill first, then legacy config\n */\n async getWalletAddress(walletId?: string): Promise<string> {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') || \n Array.from(this.skillWallets.values())[0];\n if (wallet) return wallet.address;\n }\n\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.address) return wallet.address;\n }\n\n throw new AmpedDefiError(\n ErrorCode.WALLET_NOT_FOUND,\n `Wallet not found: ${walletId || 'default'}`,\n { remediation: 'Configure ~/.evm-wallet.json, EVM_WALLETS_JSON, or AMPED_OC_WALLETS_JSON' }\n );\n }\n\n /**\n * Get wallet private key - for signing transactions\n */\n async getPrivateKey(walletId?: string): Promise<string | null> {\n // Try skill wallets\n if (this.skillWallets.size > 0) {\n const wallet = this.skillWallets.get(walletId || 'default') || \n Array.from(this.skillWallets.values())[0];\n if (wallet && (wallet as any).privateKey) {\n return (wallet as any).privateKey;\n }\n }\n\n // Fallback to AMPED_OC_WALLETS_JSON\n const legacy = process.env.AMPED_OC_WALLETS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n const wallet = config[walletId || 'main'] || config.default || Object.values(config)[0];\n if (wallet?.privateKey) return wallet.privateKey;\n }\n\n return null;\n }\n\n /**\n * Get full wallet config (address + privateKey if available)\n */\n async getWalletConfig(walletId?: string): Promise<{ address: string; privateKey?: string }> {\n const address = await this.getWalletAddress(walletId);\n const privateKey = await this.getPrivateKey(walletId);\n return { address, privateKey: privateKey || undefined };\n }\n\n /**\n * Get RPC URL - tries skill first, then legacy config\n */\n async getRpcUrl(chainId: string | number): Promise<string> {\n const key = normalizeChainId(String(chainId)).toLowerCase();\n\n // Try skill RPCs\n if (this.skillRpcs.has(key)) {\n return this.skillRpcs.get(key)!;\n }\n\n // Fallback to AMPED_OC_RPC_URLS_JSON\n const legacy = process.env.AMPED_OC_RPC_URLS_JSON;\n if (legacy) {\n const config = JSON.parse(legacy);\n if (config[key] || config[chainId]) return config[key] || config[chainId];\n }\n\n throw new AmpedDefiError(\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n `RPC URL not configured for chain: ${chainId}`,\n { remediation: 'Configure EVM_RPC_URLS_JSON or AMPED_OC_RPC_URLS_JSON' }\n );\n }\n\n /**\n * Check if using skill wallets\n */\n isUsingSkillWallets(): boolean {\n return this.skillWallets.size > 0;\n }\n\n /**\n * Check if using skill RPCs\n */\n isUsingSkillRpcs(): boolean {\n return this.skillRpcs.size > 0;\n }\n\n /**\n * Get all skill wallet IDs\n */\n getWalletIds(): string[] {\n return Array.from(this.skillWallets.keys());\n }\n\n /**\n * Get all skill RPC chain IDs\n */\n getRpcChainIds(): string[] {\n return Array.from(this.skillRpcs.keys());\n }\n}\n\n// Singleton\nlet adapter: EvmWalletSkillAdapter | null = null;\n\nexport function getWalletAdapter(options?: WalletAdapterOptions): EvmWalletSkillAdapter {\n if (!adapter) {\n adapter = new EvmWalletSkillAdapter(options);\n }\n return adapter;\n}\n\nexport function resetWalletAdapter(): void {\n adapter = null;\n}\n",
1126 "inputSchema": {},
1127 "outputSchema": null,
1128 "icons": null,
1129 "annotations": null,
1130 "meta": null,
1131 "execution": null
1132 },
1133 {
1134 "name": "types.ts",
1135 "title": null,
1136 "description": "Script: types.ts. Code:\n/**\n * Wallet Types - Multi-source wallet management\n * \n * Supports:\n * - evm-wallet-skill (local key from ~/.evm-wallet.json)\n * - Bankr (API-based, limited chains)\n * - Environment variables (AMPED_OC_WALLETS_JSON)\n */\n\nimport type { Address, Hash } from 'viem';\n\n/**\n * Supported wallet backend types\n */\nexport type WalletBackendType = 'evm-wallet-skill' | 'bankr' | 'env';\n\n/**\n * Raw transaction for Bankr submission\n */\nexport interface RawTransaction {\n to: Address;\n data: `0x${string}`;\n value: string; // Wei as string\n chainId: number;\n}\n\n/**\n * Wallet info returned by list operations\n */\nexport interface WalletInfo {\n nickname: string;\n type: WalletBackendType;\n address: Address;\n chains: string[];\n isDefault: boolean;\n /** Solana address (if wallet has one, e.g., Bankr) */\n solanaAddress?: string;\n}\n\n/**\n * Wallet backend interface\n * Different implementations for different sources\n */\nexport interface IWalletBackend {\n readonly type: WalletBackendType;\n readonly nickname: string;\n readonly supportedChains: readonly string[];\n \n /**\n * Get the wallet address\n */\n getAddress(): Promise<Address>;\n \n /**\n * Check if this wallet supports a specific chain\n */\n supportsChain(chainId: string): boolean;\n \n /**\n * Get private key (for local/env wallets)\n * Returns undefined for Bankr (no local key access)\n */\n getPrivateKey?(): Promise<`0x${string}`>;\n \n /**\n * Send raw transaction via Bankr API\n * Only available for Bankr backend\n */\n sendRawTransaction?(tx: RawTransaction): Promise<Hash>;\n \n /**\n * Check if backend is ready/configured\n */\n isReady(): Promise<boolean>;\n}\n\n/**\n * Wallet configuration from wallets.json\n */\nexport interface WalletConfig {\n source: WalletBackendType;\n \n // For evm-wallet-skill\n path?: string;\n \n // For Bankr\n apiKey?: string;\n apiUrl?: string;\n \n // For env\n envVar?: string;\n address?: Address;\n privateKey?: `0x${string}`;\n \n // Chain restrictions (optional)\n chains?: string[];\n}\n\n/**\n * Wallets config file structure\n */\nexport interface WalletsConfigFile {\n wallets: Record<string, WalletConfig>;\n default?: string;\n}\n\n/**\n * Chain IDs for Bankr submission\n */\nexport const BANKR_CHAIN_IDS: Record<string, number> = {\n ethereum: 1,\n polygon: 137,\n base: 8453,\n unichain: 130,\n};\n\n/**\n * Chains supported by Bankr\n */\nexport const BANKR_SUPPORTED_CHAINS = ['ethereum', 'polygon', 'base'] as const;\n\n/**\n * All SODAX-supported EVM chains\n * NOTE: Keep in sync with SODAX SDK supported chains\n * Non-EVM chains (solana, sui, stellar, injective) are excluded\n */\nexport const SODAX_SUPPORTED_CHAINS = [\n 'ethereum',\n 'base', \n 'polygon',\n 'arbitrum',\n 'optimism',\n 'sonic',\n 'avalanche',\n 'bsc',\n 'lightlink',\n 'hyper',\n 'kaia',\n] as const;\n\n/**\n * SODAX to simple chain ID mapping\n * SODAX uses prefixed format: 0x2105.base, 0x89.polygon\n * Simple format: base, polygon, ethereum\n */\nexport const SODAX_TO_SIMPLE_CHAIN: Record<string, string> = {\n // SODAX format -> simple\n '0x2105.base': 'base',\n '0x89.polygon': 'polygon',\n '0xa4b1.arbitrum': 'arbitrum',\n '0xa.optimism': 'optimism',\n '0x38.bsc': 'bsc',\n '0xa86a.avax': 'avalanche',\n '0x2019.kaia': 'kaia',\n // These don't have prefixes in SODAX\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyper': 'hyperevm',\n 'kaia': 'kaia',\n};\n\n/**\n * Simple to SODAX chain ID mapping\n */\nexport const SIMPLE_TO_SODAX_CHAIN: Record<string, string> = {\n // Simple -> SODAX format\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'kaia': '0x2019.kaia',\n // No prefix needed\n 'ethereum': 'ethereum',\n 'sonic': 'sonic',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n};\n\n/**\n * Normalize chain ID to simple format (base, polygon, ethereum)\n * Handles both SODAX prefixed format and simple format\n */\nexport function normalizeChainId(chainId: string): string {\n // Already in mapping\n if (SODAX_TO_SIMPLE_CHAIN[chainId]) {\n return SODAX_TO_SIMPLE_CHAIN[chainId];\n }\n \n // Check if it's already simple format\n if (SIMPLE_TO_SODAX_CHAIN[chainId]) {\n return chainId;\n }\n \n // Try to extract from prefixed format (0xNNN.name -> name)\n const match = chainId.match(/^0x[a-fA-F0-9]+\\.(.+)$/);\n if (match) {\n return match[1];\n }\n \n // Return as-is\n return chainId;\n}\n\n/**\n * Convert simple chain ID to SODAX format\n */\nexport function toSodaxChainId(chainId: string): string {\n // Already in SODAX format\n if (chainId.startsWith('0x') && chainId.includes('.')) {\n return chainId;\n }\n \n return SIMPLE_TO_SODAX_CHAIN[chainId] || chainId;\n}\n\n/**\n * Check if two chain IDs refer to the same chain\n * Handles format differences between SODAX and simple\n */\nexport function isSameChain(chainId1: string, chainId2: string): boolean {\n return normalizeChainId(chainId1) === normalizeChainId(chainId2);\n}\n\n/**\n * Check if a chain is supported by Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function isBankrSupportedChain(chainId: string): boolean {\n const normalized = normalizeChainId(chainId);\n return BANKR_SUPPORTED_CHAINS.includes(normalized as any);\n}\n\n/**\n * Get numeric chain ID for Bankr\n * Handles both SODAX and simple chain ID formats\n */\nexport function getBankrChainId(chainId: string): number {\n const normalized = normalizeChainId(chainId);\n const id = BANKR_CHAIN_IDS[normalized];\n if (!id) {\n throw new Error(`Chain ${chainId} (normalized: ${normalized}) not supported by Bankr. Supported: ${BANKR_SUPPORTED_CHAINS.join(', ')}`);\n }\n return id;\n}\n",
1137 "inputSchema": {},
1138 "outputSchema": null,
1139 "icons": null,
1140 "annotations": null,
1141 "meta": null,
1142 "execution": null
1143 },
1144 {
1145 "name": "backendConfig.ts",
1146 "title": null,
1147 "description": "Script: backendConfig.ts. Code:\n/**\n * Wallet Backend Configuration\n * \n * Detects and configures the appropriate wallet backend based on\n * environment variables or config file.\n * \n * Supported backends:\n * - localKey (default): Uses evm-wallet-skill local private keys\n * - bankr: Uses Bankr Agent API for transaction execution\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { WalletBackendType } from './providers';\n\nexport interface BackendConfig {\n backend: WalletBackendType;\n bankrApiKey?: string;\n bankrApiUrl?: string;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: BackendConfig = {\n backend: 'localKey',\n};\n\n/**\n * Path to plugin config file\n */\nfunction getConfigPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'amped-defi', 'config.json');\n}\n\n/**\n * Load configuration from file\n */\nfunction loadConfigFile(): Partial<BackendConfig> | null {\n const configPath = getConfigPath();\n \n if (!existsSync(configPath)) {\n return null;\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(content);\n return {\n backend: config.walletBackend as WalletBackendType,\n bankrApiKey: config.bankrApiKey,\n bankrApiUrl: config.bankrApiUrl,\n };\n } catch (error) {\n console.warn('[backendConfig] Failed to load config file:', error);\n return null;\n }\n}\n\n/**\n * Load configuration from environment variables\n */\nfunction loadEnvConfig(): Partial<BackendConfig> {\n const config: Partial<BackendConfig> = {};\n\n // Check for explicit backend selection\n const backendEnv = process.env.AMPED_OC_WALLET_BACKEND;\n if (backendEnv === 'bankr' || backendEnv === 'localKey') {\n config.backend = backendEnv;\n }\n\n // Check for Bankr API key (implies bankr backend)\n const bankrApiKey = process.env.BANKR_API_KEY;\n if (bankrApiKey) {\n config.bankrApiKey = bankrApiKey;\n // Auto-select bankr backend if API key is present\n if (!config.backend) {\n config.backend = 'bankr';\n }\n }\n\n // Optional Bankr API URL override\n const bankrApiUrl = process.env.BANKR_API_URL;\n if (bankrApiUrl) {\n config.bankrApiUrl = bankrApiUrl;\n }\n\n return config;\n}\n\n/**\n * Get the resolved backend configuration\n * \n * Priority:\n * 1. Environment variables\n * 2. Config file\n * 3. Defaults\n */\nexport function getBackendConfig(): BackendConfig {\n const fileConfig = loadConfigFile() || {};\n const envConfig = loadEnvConfig();\n\n // Merge with priority: env > file > default\n const config: BackendConfig = {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n ...envConfig,\n };\n\n // Validate bankr configuration\n if (config.backend === 'bankr' && !config.bankrApiKey) {\n console.warn('[backendConfig] Bankr backend selected but no API key provided');\n console.warn('[backendConfig] Set BANKR_API_KEY environment variable or add bankrApiKey to config.json');\n console.warn('[backendConfig] Falling back to localKey backend');\n config.backend = 'localKey';\n }\n\n // Set default Bankr API URL if not specified\n if (config.backend === 'bankr' && !config.bankrApiUrl) {\n config.bankrApiUrl = 'https://api.bankr.bot';\n }\n\n console.log(`[backendConfig] Using wallet backend: ${config.backend}`);\n\n return config;\n}\n\n/**\n * Check if Bankr backend is configured and available\n */\nexport function isBankrConfigured(): boolean {\n const config = getBackendConfig();\n return config.backend === 'bankr' && !!config.bankrApiKey;\n}\n\n/**\n * Get Bankr configuration if available\n */\nexport function getBankrConfig(): { apiKey: string; apiUrl: string } | null {\n const config = getBackendConfig();\n \n if (config.backend !== 'bankr' || !config.bankrApiKey) {\n return null;\n }\n\n return {\n apiKey: config.bankrApiKey,\n apiUrl: config.bankrApiUrl || 'https://api.bankr.bot',\n };\n}\n",
1148 "inputSchema": {},
1149 "outputSchema": null,
1150 "icons": null,
1151 "annotations": null,
1152 "meta": null,
1153 "execution": null
1154 },
1155 {
1156 "name": "index.ts",
1157 "title": null,
1158 "description": "Script: index.ts. Code:\n/**\n * Amped DeFi Plugin\n * \n * OpenClaw plugin for DeFi operations (swaps, bridging, money market)\n * via the SODAX SDK.\n */\n\nimport { Type, TSchema } from '@sinclair/typebox';\n\n/**\n * OpenClaw Plugin API (defined locally to avoid SDK dependency)\n */\ninterface OpenClawPluginApi {\n pluginConfig: Record<string, unknown>;\n logger: {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n debug: (msg: string) => void;\n };\n registerTool: (tool: {\n name: string;\n description: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: unknown) => Promise<{\n content: Array<{ type: 'text'; text: string }>;\n details?: unknown;\n }>;\n }) => void;\n registerService: (service: {\n id: string;\n start: () => void;\n stop: () => Promise<void> | void;\n }) => void;\n on: (event: string, handler: (event: unknown, ctx: unknown) => unknown) => void;\n}\nimport { getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nimport { getCacheStats } from './providers/spokeProviderFactory';\nimport { PolicyEngine } from './policy/policyEngine';\nimport { getWalletManager } from './wallet/walletManager';\nimport { getWalletRegistry } from './wallet/walletRegistry';\n\n// Tool schemas and handlers\nimport { \n SwapQuoteSchema, SwapExecuteSchema, SwapStatusSchema, SwapCancelSchema,\n handleSwapQuote, handleSwapExecute, handleSwapStatus, handleSwapCancel\n} from './tools/swap';\nimport {\n BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema,\n handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute\n} from './tools/bridge';\nimport {\n MmSupplySchema, MmWithdrawSchema, MmBorrowSchema, MmRepaySchema,\n handleMmSupply, handleMmWithdraw, handleMmBorrow, handleMmRepay\n} from './tools/moneyMarket';\nimport {\n SupportedChainsSchema, SupportedTokensSchema, WalletAddressSchema,\n MoneyMarketReservesSchema, MoneyMarketPositionsSchema, CrossChainPositionsSchema,\n UserIntentsSchema, ListWalletsSchema,\n handleSupportedChains, handleSupportedTokens, handleWalletAddress,\n handleMoneyMarketReserves, handleMoneyMarketPositions, handleCrossChainPositions,\n handleUserIntents, handleListWallets\n} from './tools/discovery';\nimport {\n AddWalletSchema, RenameWalletSchema, RemoveWalletSchema, SetDefaultWalletSchema,\n handleAddWallet, handleRenameWallet, handleRemoveWallet, handleSetDefaultWallet\n} from './tools/walletManagement';\nimport {\n PortfolioSummarySchema,\n handlePortfolioSummary\n} from './tools/portfolio';\n\n/**\n * Plugin configuration schema (matches openclaw.plugin.json)\n */\nconst configSchema = Type.Object({\n walletsJson: Type.Optional(Type.String()),\n rpcUrlsJson: Type.Optional(Type.String()),\n mode: Type.Optional(Type.Union([Type.Literal('execute'), Type.Literal('simulate')])),\n dynamicConfig: Type.Optional(Type.Boolean()),\n});\n\n/**\n * Apply plugin config to environment\n */\nfunction applyConfig(config: Record<string, unknown>): void {\n if (config.walletsJson && typeof config.walletsJson === 'string') {\n process.env.AMPED_OC_WALLETS_JSON = config.walletsJson;\n }\n if (config.rpcUrlsJson && typeof config.rpcUrlsJson === 'string') {\n process.env.AMPED_OC_RPC_URLS_JSON = config.rpcUrlsJson;\n }\n if (config.mode && typeof config.mode === 'string') {\n process.env.AMPED_OC_MODE = config.mode;\n }\n if (config.dynamicConfig !== undefined) {\n process.env.AMPED_OC_SODAX_DYNAMIC_CONFIG = config.dynamicConfig ? 'true' : 'false';\n }\n}\n\n/**\n * Validate required environment variables\n */\nfunction validateEnvironment(): string[] {\n const missing: string[] = [];\n \n if (!process.env.AMPED_OC_WALLETS_JSON) {\n missing.push('AMPED_OC_WALLETS_JSON');\n }\n \n const mode = process.env.AMPED_OC_MODE || 'execute';\n if (mode === 'execute' && !process.env.AMPED_OC_RPC_URLS_JSON) {\n missing.push('AMPED_OC_RPC_URLS_JSON');\n }\n \n return missing;\n}\n\n\n/**\n * Deep-clone an object while converting BigInt values to strings\n * Prevents serialization errors when OpenClaw framework handles the details field\n */\nfunction sanitizeBigInt(obj: unknown): unknown {\n return JSON.parse(JSON.stringify(obj, (_, v) => \n typeof v === 'bigint' ? v.toString() : v\n ));\n}\n\n/**\n * Helper to wrap a handler for OpenClaw's tool format\n */\nfunction wrapHandler(handler: (params: unknown) => Promise<unknown>) {\n return async (_toolCallId: string, params: unknown) => {\n const result = await handler(params);\n // Sanitize BigInt values in details to prevent framework serialization errors\n const sanitizedResult = sanitizeBigInt(result);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2) }],\n details: sanitizedResult,\n };\n };\n}\n\n/**\n * OpenClaw Plugin Definition\n */\nexport default {\n id: 'amped-defi',\n name: 'Amped DeFi',\n description: 'DeFi operations plugin for swaps, bridging, and money market via SODAX SDK',\n kind: 'tools' as const,\n configSchema,\n\n register(api: OpenClawPluginApi) {\n // Apply config from OpenClaw\n const config = api.pluginConfig as Record<string, unknown> || {};\n applyConfig(config);\n \n // Check for missing env vars (silent)\n validateEnvironment();\n\n // Initialize core components (async, non-blocking, silent)\n (async () => {\n try {\n await getSodaxClientAsync();\n getCacheStats();\n new PolicyEngine();\n getWalletManager();\n } catch (_error) {\n // Silent initialization - errors will surface when tools are used\n }\n })();\n\n // Register Discovery Tools\n api.registerTool({\n name: 'amped_supported_chains',\n description: 'List all blockchain networks supported by the Amped DeFi plugin',\n parameters: SupportedChainsSchema,\n execute: wrapHandler(handleSupportedChains),\n });\n\n api.registerTool({\n name: 'amped_supported_tokens',\n description: 'List tokens supported on a specific chain for swaps and bridging',\n parameters: SupportedTokensSchema,\n execute: wrapHandler(handleSupportedTokens),\n });\n\n api.registerTool({\n name: 'amped_wallet_address',\n description: 'Get the wallet address for a specific wallet ID',\n parameters: WalletAddressSchema,\n execute: wrapHandler(handleWalletAddress),\n });\n\n api.registerTool({\n name: 'amped_money_market_reserves',\n description: 'Get money market reserve info (APY, utilization, liquidity)',\n parameters: MoneyMarketReservesSchema,\n execute: wrapHandler(handleMoneyMarketReserves),\n });\n\n api.registerTool({\n name: 'amped_money_market_positions',\n description: 'Get user positions in money market on a single chain',\n parameters: MoneyMarketPositionsSchema,\n execute: wrapHandler(handleMoneyMarketPositions),\n });\n\n api.registerTool({\n name: 'amped_cross_chain_positions',\n description: 'Get aggregated money market positions across all chains',\n parameters: CrossChainPositionsSchema,\n execute: wrapHandler(handleCrossChainPositions),\n });\n\n api.registerTool({\n name: 'amped_user_intents',\n description: 'Query user intent history from SODAX API',\n parameters: UserIntentsSchema,\n execute: wrapHandler(handleUserIntents),\n });\n\n api.registerTool({\n name: 'amped_list_wallets',\n description: 'List ALL configured wallets including evm-wallet-skill, Bankr, and env wallets. Shows nicknames, addresses, types, and supported chains. Use this when user asks \"what wallets do I have\" or \"show my wallets\".',\n parameters: ListWalletsSchema,\n execute: wrapHandler(handleListWallets),\n });\n\n api.registerTool({\n name: 'amped_portfolio_summary',\n description: 'Get a comprehensive portfolio summary including wallet balances (native + major tokens) across chains and money market positions. Use when user asks for \"portfolio\", \"balances\", or \"summary of positions\".',\n parameters: PortfolioSummarySchema,\n execute: wrapHandler(handlePortfolioSummary),\n });\n\n // Register Wallet Management Tools\n api.registerTool({\n name: 'amped_add_wallet',\n description: 'Add a new wallet with a nickname (evm-wallet-skill, bankr, or env)',\n parameters: AddWalletSchema,\n execute: wrapHandler(handleAddWallet),\n });\n\n api.registerTool({\n name: 'amped_rename_wallet',\n description: 'Rename a wallet to a new nickname',\n parameters: RenameWalletSchema,\n execute: wrapHandler(handleRenameWallet),\n });\n\n api.registerTool({\n name: 'amped_remove_wallet',\n description: 'Remove a wallet from configuration (does not delete funds)',\n parameters: RemoveWalletSchema,\n execute: wrapHandler(handleRemoveWallet),\n });\n\n api.registerTool({\n name: 'amped_set_default_wallet',\n description: 'Set which wallet to use by default for operations',\n parameters: SetDefaultWalletSchema,\n execute: wrapHandler(handleSetDefaultWallet),\n });\n\n // Register Swap Tools\n api.registerTool({\n name: 'amped_swap_quote',\n description: 'Get a quote for swapping tokens (same chain or cross-chain)',\n parameters: SwapQuoteSchema,\n execute: wrapHandler(handleSwapQuote),\n });\n\n api.registerTool({\n name: 'amped_swap_execute',\n description: 'Execute a token swap using a previously obtained quote',\n parameters: SwapExecuteSchema,\n execute: wrapHandler(handleSwapExecute),\n });\n\n api.registerTool({\n name: 'amped_swap_status',\n description: 'Check the status of a swap/bridge operation by intent ID',\n parameters: SwapStatusSchema,\n execute: wrapHandler(handleSwapStatus),\n });\n\n api.registerTool({\n name: 'amped_swap_cancel',\n description: 'Cancel a pending swap/bridge operation',\n parameters: SwapCancelSchema,\n execute: wrapHandler(handleSwapCancel),\n });\n\n // Register Bridge Tools\n api.registerTool({\n name: 'amped_bridge_discover',\n description: 'Discover available bridge routes between chains',\n parameters: BridgeDiscoverSchema,\n execute: wrapHandler(handleBridgeDiscover),\n });\n\n api.registerTool({\n name: 'amped_bridge_quote',\n description: 'Get a quote for bridging tokens between chains',\n parameters: BridgeQuoteSchema,\n execute: wrapHandler(handleBridgeQuote),\n });\n\n api.registerTool({\n name: 'amped_bridge_execute',\n description: 'Execute a bridge transfer using a previously obtained quote',\n parameters: BridgeExecuteSchema,\n execute: wrapHandler(handleBridgeExecute),\n });\n\n // Register Money Market Tools\n api.registerTool({\n name: 'amped_mm_supply',\n description: 'Supply (deposit) tokens to money market to earn interest',\n parameters: MmSupplySchema,\n execute: wrapHandler(handleMmSupply),\n });\n\n api.registerTool({\n name: 'amped_mm_withdraw',\n description: 'Withdraw supplied tokens from money market',\n parameters: MmWithdrawSchema,\n execute: wrapHandler(handleMmWithdraw),\n });\n\n api.registerTool({\n name: 'amped_mm_borrow',\n description: 'Borrow tokens from money market (cross-chain capable)',\n parameters: MmBorrowSchema,\n execute: wrapHandler(handleMmBorrow),\n });\n\n api.registerTool({\n name: 'amped_mm_repay',\n description: 'Repay borrowed tokens to money market',\n parameters: MmRepaySchema,\n execute: wrapHandler(handleMmRepay),\n });\n\n // Register cleanup service\n api.registerService({\n id: 'amped-defi',\n start: () => {},\n stop: async () => {\n resetSodaxClient();\n },\n });\n },\n};\n\n// Re-export types and utilities for external use\nexport * from './types';\nexport { getSodaxClient, getSodaxClientAsync, resetSodaxClient } from './sodax/client';\nexport { getSpokeProvider, getCacheStats, clearProviderCache } from './providers/spokeProviderFactory';\nexport type { SpokeProvider } from './providers/spokeProviderFactory';\nexport { EvmSpokeProvider, SonicSpokeProvider } from '@sodax/sdk';\nexport { PolicyEngine } from './policy/policyEngine';\nexport { WalletRegistry, getWalletRegistry } from './wallet/walletRegistry';\nexport { WalletManager, getWalletManager, resetWalletManager } from './wallet/walletManager';\nexport type { IWalletBackend, WalletInfo, WalletBackendType } from './wallet/types';\n\n// Legacy exports for backward compatibility\nexport async function activate() {\n // Deprecated - use default export\n}\nexport async function deactivate() {\n resetSodaxClient();\n}\n",
1159 "inputSchema": {},
1160 "outputSchema": null,
1161 "icons": null,
1162 "annotations": null,
1163 "meta": null,
1164 "execution": null
1165 },
1166 {
1167 "name": "client.ts",
1168 "title": null,
1169 "description": "Script: client.ts. Code:\n/**\n * SODAX SDK Client Singleton\n *\n * Provides a singleton instance of the SODAX SDK client with lazy initialization.\n * Uses dynamic configuration by default to fetch live token lists and routes.\n */\n\nimport { Sodax } from \"@sodax/sdk\";\n\n// Singleton instance\nlet sodaxClient: Sodax | null = null;\n\n/**\n * HARDCODED PARTNER CONFIGURATION\n * These values are baked in and cannot be overridden.\n * \n * Fee is 0.2% (20 basis points)\n * SDK expects: percentage in bps where 100 = 1%, so 20 = 0.2%\n */\nconst PARTNER_FEE = {\n address: \"0xd99C871c8130B03C8BB597A74fb5EAA7a46864Bb\" as `0x${string}`,\n percentage: 20, // 20 bps = 0.2%\n};\n\n/**\n * Initialize the SODAX SDK client\n * Always uses dynamic config to fetch live token lists and routes\n */\nasync function initializeSodax(): Promise<Sodax> {\n // Initialize SODAX with hardcoded partner fee on ALL services\n const sodax = new Sodax({\n swaps: { partnerFee: PARTNER_FEE },\n moneyMarket: { partnerFee: PARTNER_FEE },\n bridge: { partnerFee: PARTNER_FEE },\n } as any);\n\n // Suppress SDK console output during initialization\n const originalWarn = console.warn;\n const originalLog = console.log;\n console.warn = () => {};\n console.log = () => {};\n \n try {\n // Initialize with dynamic config\n await sodax.initialize();\n } finally {\n // Restore console\n console.warn = originalWarn;\n console.log = originalLog;\n }\n\n return sodax;\n}\n\n/**\n * Get the singleton SODAX client instance\n * Initializes on first call if not already initialized\n */\nexport async function getSodaxClientAsync(): Promise<Sodax> {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n return sodaxClient;\n}\n\n/**\n * Synchronous accessor for the SODAX client\n * Throws if the client hasn't been initialized yet\n */\nexport function getSodaxClient(): Sodax {\n if (!sodaxClient) {\n throw new Error(\n \"SODAX client not initialized. Call getSodaxClientAsync() first.\",\n );\n }\n return sodaxClient;\n}\n\n/**\n * Pre-initialize the SODAX client at plugin startup\n */\nexport async function preInitializeSodax(): Promise<void> {\n if (!sodaxClient) {\n sodaxClient = await initializeSodax();\n }\n}\n\n/**\n * Reset the SODAX client (useful for testing)\n */\nexport function resetSodaxClient(): void {\n sodaxClient = null;\n}\n\n/**\n * SodaxClient class wrapper for backward compatibility\n */\nexport class SodaxClient {\n private static instance: Sodax | null = null;\n\n static async getClient(): Promise<Sodax> {\n if (!SodaxClient.instance) {\n SodaxClient.instance = await initializeSodax();\n sodaxClient = SodaxClient.instance;\n }\n return SodaxClient.instance;\n }\n\n static reset(): void {\n SodaxClient.instance = null;\n sodaxClient = null;\n }\n}\n",
1170 "inputSchema": {},
1171 "outputSchema": null,
1172 "icons": null,
1173 "annotations": null,
1174 "meta": null,
1175 "execution": null
1176 },
1177 {
1178 "name": "sdk.ts",
1179 "title": null,
1180 "description": "Script: sdk.ts. Code:\n/**\n * Mock for @sodax/sdk\n */\n\nexport class Sodax {\n static initialize = jest.fn().mockResolvedValue(undefined);\n \n swaps = {\n getQuote: jest.fn(),\n executeSwap: jest.fn(),\n cancelIntent: jest.fn(),\n };\n \n bridge = {\n getBridgeableTokens: jest.fn(),\n bridge: jest.fn(),\n };\n \n moneyMarket = {\n supply: jest.fn(),\n withdraw: jest.fn(),\n borrow: jest.fn(),\n repay: jest.fn(),\n getUserAccountDataOnSpoke: jest.fn(),\n };\n}\n\nexport const Intent = {};\n",
1181 "inputSchema": {},
1182 "outputSchema": null,
1183 "icons": null,
1184 "annotations": null,
1185 "meta": null,
1186 "execution": null
1187 },
1188 {
1189 "name": "portfolio.ts",
1190 "title": null,
1191 "description": "Script: portfolio.ts. Code:\n/**\n * Portfolio Summary Tool\n *\n * Provides a unified view of all wallet balances and positions.\n * Queries native tokens and major stablecoins via RPC, plus money market positions.\n *\n * @module tools/portfolio\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { createPublicClient, http, formatUnits, type PublicClient, type Address } from 'viem';\nimport { getWalletManager } from '../wallet';\nimport { aggregateCrossChainPositions, formatHealthFactor, getHealthFactorStatus } from '../utils/positionAggregator';\nimport { fetchTokenPrices, getTokenPriceBySymbol } from '../utils/priceService';\nimport { \n getViemChain, \n getDefaultRpcUrl, \n resolveChainId,\n CHAIN_IDS,\n} from '../wallet/providers/chainConfig';\n\n// ============================================================================\n// TypeBox Schema\n// ============================================================================\n\n/**\n * Schema for amped_portfolio_summary\n */\nexport const PortfolioSummarySchema = Type.Object({\n walletId: Type.Optional(Type.String({\n description: 'Specific wallet to query (defaults to all wallets)',\n })),\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Specific chains to query (defaults to all supported chains)',\n })),\n includeZeroBalances: Type.Optional(Type.Boolean({\n description: 'Include tokens with zero balance',\n default: false,\n })),\n});\n\ntype PortfolioSummaryParams = Static<typeof PortfolioSummarySchema>;\n\n// ============================================================================\n// Token Configuration\n// ============================================================================\n\ninterface TokenConfig {\n address: string;\n symbol: string;\n decimals: number;\n}\n\n/**\n * Major tokens to check on each chain\n */\nconst MAJOR_TOKENS: Record<number, TokenConfig[]> = {\n [CHAIN_IDS.ETHEREUM]: [\n { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 },\n { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 },\n { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BASE]: [\n { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.ARBITRUM]: [\n { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', decimals: 6 },\n { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', decimals: 6 },\n { address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.OPTIMISM]: [\n { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', symbol: 'USDC', decimals: 6 },\n { address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', symbol: 'USDT', decimals: 6 },\n { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.POLYGON]: [\n { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', decimals: 6 },\n { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 },\n { address: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.BSC]: [\n { address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', symbol: 'USDC', decimals: 18 },\n { address: '0x55d398326f99059fF775485246999027B3197955', symbol: 'USDT', decimals: 18 },\n { address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.AVALANCHE]: [\n { address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', symbol: 'USDC', decimals: 6 },\n { address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', symbol: 'USDT', decimals: 6 },\n { address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', symbol: 'WETH', decimals: 18 },\n ],\n [CHAIN_IDS.SONIC]: [\n { address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894', symbol: 'USDC', decimals: 6 },\n ],\n [CHAIN_IDS.LIGHTLINK]: [\n { address: '0xbCF8C1B03bBDDA88D579330BDF236B58F8bb2cFd', symbol: 'USDC', decimals: 6 },\n ],\n};\n\n/**\n * Native token symbols by chain\n */\nconst NATIVE_SYMBOLS: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'ETH',\n [CHAIN_IDS.ARBITRUM]: 'ETH',\n [CHAIN_IDS.OPTIMISM]: 'ETH',\n [CHAIN_IDS.BASE]: 'ETH',\n [CHAIN_IDS.POLYGON]: 'POL',\n [CHAIN_IDS.BSC]: 'BNB',\n [CHAIN_IDS.AVALANCHE]: 'AVAX',\n [CHAIN_IDS.SONIC]: 'S',\n [CHAIN_IDS.LIGHTLINK]: 'ETH',\n [CHAIN_IDS.HYPEREVM]: 'HYPE',\n [CHAIN_IDS.KAIA]: 'KAIA',\n};\n\n/**\n * Chain ID to name mapping\n */\nconst CHAIN_NAMES: Record<number, string> = {\n [CHAIN_IDS.ETHEREUM]: 'Ethereum',\n [CHAIN_IDS.ARBITRUM]: 'Arbitrum',\n [CHAIN_IDS.OPTIMISM]: 'Optimism',\n [CHAIN_IDS.BASE]: 'Base',\n [CHAIN_IDS.POLYGON]: 'Polygon',\n [CHAIN_IDS.BSC]: 'BSC',\n [CHAIN_IDS.AVALANCHE]: 'Avalanche',\n [CHAIN_IDS.SONIC]: 'Sonic',\n [CHAIN_IDS.LIGHTLINK]: 'LightLink',\n [CHAIN_IDS.HYPEREVM]: 'HyperEVM',\n [CHAIN_IDS.KAIA]: 'Kaia',\n};\n\n/**\n * Chain name strings for wallet support check\n */\nconst CHAIN_NAME_STRINGS: Record<number, string[]> = {\n [CHAIN_IDS.ETHEREUM]: ['ethereum'],\n [CHAIN_IDS.BASE]: ['base'],\n [CHAIN_IDS.ARBITRUM]: ['arbitrum'],\n [CHAIN_IDS.OPTIMISM]: ['optimism'],\n [CHAIN_IDS.POLYGON]: ['polygon'],\n [CHAIN_IDS.BSC]: ['bsc'],\n [CHAIN_IDS.AVALANCHE]: ['avalanche', 'avax'],\n [CHAIN_IDS.SONIC]: ['sonic'],\n [CHAIN_IDS.LIGHTLINK]: ['lightlink'],\n};\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Create a viem public client for a chain\n */\nfunction createClient(chainId: number): PublicClient {\n const chain = getViemChain(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n return createPublicClient({\n chain,\n transport: http(rpcUrl, { timeout: 10000 }),\n }) as PublicClient;\n}\n\n/**\n * Get native balance for a wallet on a chain\n */\nasync function getNativeBalance(\n client: PublicClient,\n address: Address,\n chainId: number\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint }> {\n try {\n const balance = await client.getBalance({ address });\n return {\n symbol: NATIVE_SYMBOLS[chainId] || 'ETH',\n balance: formatUnits(balance, 18),\n balanceRaw: balance,\n };\n } catch (error) {\n console.error(`[portfolio] Failed to get native balance on chain ${chainId}:`, error);\n return { symbol: NATIVE_SYMBOLS[chainId] || 'ETH', balance: '0', balanceRaw: 0n };\n }\n}\n\n/**\n * Get ERC20 token balance using eth_call directly (avoids viem type issues)\n */\nasync function getTokenBalance(\n rpcUrl: string,\n walletAddress: string,\n tokenAddress: string,\n decimals: number,\n symbol: string\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint; address: string }> {\n try {\n // balanceOf(address) selector: 0x70a08231\n const paddedAddress = walletAddress.slice(2).toLowerCase().padStart(64, '0');\n const callData = `0x70a08231${paddedAddress}`;\n\n const response = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n method: 'eth_call',\n params: [{ to: tokenAddress, data: callData }, 'latest'],\n id: 1,\n }),\n });\n\n const json = await response.json() as { result?: string };\n const result = json.result;\n\n if (!result || result === '0x' || result === '0x0') {\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n\n const balanceRaw = BigInt(result);\n const balance = formatUnits(balanceRaw, decimals);\n\n return { symbol, balance, balanceRaw, address: tokenAddress };\n } catch (error) {\n console.error(`[portfolio] Failed to get ${symbol} balance:`, error);\n return { symbol, balance: '0', balanceRaw: 0n, address: tokenAddress };\n }\n}\n\n/**\n * Query all balances for a wallet on a specific chain\n */\nasync function getChainBalances(\n address: Address,\n chainId: number,\n includeZeroBalances: boolean\n): Promise<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>; chainTotalUsd?: string;\n}> {\n const client = createClient(chainId);\n const rpcUrl = getDefaultRpcUrl(chainId);\n const chainName = CHAIN_NAMES[chainId] || `Chain ${chainId}`;\n\n // Get native balance\n const native = await getNativeBalance(client, address, chainId);\n\n // Get token balances\n const tokenConfigs = MAJOR_TOKENS[chainId] || [];\n const tokenPromises = tokenConfigs.map((t) =>\n getTokenBalance(rpcUrl, address, t.address, t.decimals, t.symbol)\n );\n const tokenResults = await Promise.all(tokenPromises);\n\n // Filter zero balances if requested\n const tokens = includeZeroBalances\n ? tokenResults.map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }))\n : tokenResults\n .filter((t) => t.balanceRaw > 0n)\n .map((t) => ({ symbol: t.symbol, balance: t.balance, address: t.address }));\n\n return {\n chainId: chainId.toString(),\n chainName,\n native: {\n symbol: native.symbol,\n balance: parseFloat(native.balance).toFixed(6),\n },\n tokens,\n };\n}\n\n// ============================================================================\n// Main Handler\n// ============================================================================\n\ninterface WalletBalanceResult {\n wallet: {\n nickname: string;\n address: string;\n type: string;\n };\n balances: Array<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>;\n chainTotalUsd?: string;\n }>;\n moneyMarket?: {\n totalSupplyUsd: string;\n totalBorrowUsd: string;\n netWorthUsd: string;\n healthFactor: string;\n healthStatus: { status: string; color: string };\n /** Per-chain breakdown - CRITICAL: each chain has independent health factor */\n chainBreakdown: Array<{\n chainId: string;\n supplyUsd: string;\n borrowUsd: string;\n healthFactor: string;\n healthStatus: { status: string; color: string };\n }>;\n };\n walletTotalUsd?: string;\n}\n\n/**\n * Handle portfolio summary request\n */\nexport async function handlePortfolioSummary(\n params: PortfolioSummaryParams\n): Promise<unknown> {\n const { walletId, chains, includeZeroBalances = false } = params;\n\n console.log('[portfolio:summary] Fetching portfolio summary', {\n walletId: walletId || 'all',\n chains: chains || 'all',\n includeZeroBalances,\n });\n\n // Fetch token prices from SODAX (cached, 1 min TTL)\n let priceMap: Awaited<ReturnType<typeof fetchTokenPrices>> | null = null;\n try {\n priceMap = await fetchTokenPrices();\n } catch (err) {\n console.warn('[portfolio] Failed to fetch prices, USD values will be unavailable:', err);\n }\n\n const walletManager = getWalletManager();\n const allWallets = await walletManager.listWallets();\n\n // Filter to specific wallet if requested\n const walletsToQuery = walletId\n ? allWallets.filter((w) => w.nickname === walletId)\n : allWallets;\n\n if (walletsToQuery.length === 0) {\n return {\n success: false,\n error: walletId ? `Wallet not found: ${walletId}` : 'No wallets configured',\n };\n }\n\n // Determine chains to query\n // Query all chains with configured tokens by default\n const defaultChains = [\n CHAIN_IDS.BASE,\n CHAIN_IDS.ETHEREUM,\n CHAIN_IDS.ARBITRUM,\n CHAIN_IDS.OPTIMISM,\n CHAIN_IDS.POLYGON,\n CHAIN_IDS.SONIC,\n CHAIN_IDS.BSC,\n CHAIN_IDS.AVALANCHE,\n CHAIN_IDS.LIGHTLINK,\n ];\n const chainIdsToQuery = chains\n ? chains.map((c) => resolveChainId(c))\n : defaultChains;\n\n const results: WalletBalanceResult[] = [];\n let totalValueUsd = 0;\n\n // Helper to get USD price for a symbol\n const getPrice = (symbol: string): number | null => {\n if (!priceMap) return null;\n const lower = symbol.toLowerCase();\n return priceMap.bySymbol.get(lower) ?? priceMap.bySymbol.get('soda' + lower) ?? null;\n };\n\n for (const wallet of walletsToQuery) {\n // Skip wallets without known addresses\n if (wallet.address === '0x...') {\n console.log(`[portfolio] Skipping wallet ${wallet.nickname} - address not resolved`);\n continue;\n }\n\n const address = wallet.address as Address;\n\n // Filter chains to those the wallet supports\n const supportedChains = wallet.chains || [];\n const chainsForWallet = chainIdsToQuery.filter((cid) => {\n const names = CHAIN_NAME_STRINGS[cid] || [];\n return supportedChains.length === 0 || names.some((n) => supportedChains.includes(n));\n });\n\n // Query balances for each chain (in parallel)\n const balancePromises = chainsForWallet.map((cid) =>\n getChainBalances(address, cid, includeZeroBalances).catch((err) => {\n console.error(`[portfolio] Failed to query chain ${cid}:`, err);\n return null;\n })\n );\n const balanceResults = (await Promise.all(balancePromises)).filter(\n (b): b is NonNullable<typeof b> => b !== null\n );\n\n // Filter out chains with no balances if not including zeros\n const filteredBalances = includeZeroBalances\n ? balanceResults\n : balanceResults.filter(\n (b) => parseFloat(b.native.balance) > 0 || b.tokens.length > 0\n );\n\n // Add USD values to balances\n let walletBalanceUsd = 0;\n const balancesWithUsd = filteredBalances.map((chainBalance) => {\n let chainTotalUsd = 0;\n\n // Native token USD value\n const nativePrice = getPrice(chainBalance.native.symbol);\n const nativeBalance = parseFloat(chainBalance.native.balance);\n const nativeUsdValue = nativePrice ? nativeBalance * nativePrice : null;\n if (nativeUsdValue) chainTotalUsd += nativeUsdValue;\n\n // Token USD values\n const tokensWithUsd = chainBalance.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue) chainTotalUsd += usdValue;\n return {\n ...token,\n usdValue: usdValue ? `$${usdValue.toFixed(2)}` : undefined,\n };\n });\n\n walletBalanceUsd += chainTotalUsd;\n\n return {\n chainId: chainBalance.chainId,\n chainName: chainBalance.chainName,\n native: {\n symbol: chainBalance.native.symbol,\n balance: chainBalance.native.balance,\n usdValue: nativeUsdValue ? `$${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: chainTotalUsd > 0 ? `$${chainTotalUsd.toFixed(2)}` : undefined,\n };\n });\n\n // Query Solana balances if wallet has a Solana address\n // Bankr wallets have a separate Solana address that can be cached\n const solanaAddress = (wallet as any).solanaAddress;\n if (solanaAddress) {\n try {\n const solanaBalances = await getSolanaWalletBalances(solanaAddress, includeZeroBalances);\n if (solanaBalances) {\n // Add USD values for Solana\n let solanaTotalUsd = 0;\n const solPrice = getPrice('SOL');\n const nativeBalance = parseFloat(solanaBalances.native.balance);\n const nativeUsdValue = solPrice ? nativeBalance * solPrice : null;\n if (nativeUsdValue) solanaTotalUsd += nativeUsdValue;\n \n const tokensWithUsd = solanaBalances.tokens.map((token) => {\n const price = getPrice(token.symbol);\n const balance = parseFloat(token.balance);\n const usdValue = price ? balance * price : null;\n if (usdValue) solanaTotalUsd += usdValue;\n return { ...token, usdValue: usdValue ? `${usdValue.toFixed(2)}` : undefined };\n });\n \n walletBalanceUsd += solanaTotalUsd;\n balancesWithUsd.push({\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: solanaBalances.native.symbol,\n balance: solanaBalances.native.balance,\n usdValue: nativeUsdValue ? `${nativeUsdValue.toFixed(2)}` : undefined,\n },\n tokens: tokensWithUsd,\n chainTotalUsd: solanaTotalUsd > 0 ? `${solanaTotalUsd.toFixed(2)}` : undefined,\n });\n }\n } catch (err) {\n console.error(`[portfolio] Failed to get Solana balances for ${wallet.nickname}:`, err);\n }\n }\n\n // Get money market positions (aggregate)\n let mmSummary: WalletBalanceResult['moneyMarket'] | undefined;\n try {\n const positions = await aggregateCrossChainPositions(wallet.nickname);\n if (positions && (positions.summary.totalSupplyUsd > 0 || positions.summary.totalBorrowUsd > 0)) {\n const hfStatus = getHealthFactorStatus(positions.summary.healthFactor);\n // Build per-chain breakdown with individual health factors\n const chainBreakdown = positions.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n healthStatus: getHealthFactorStatus(cs.healthFactor),\n }));\n mmSummary = {\n totalSupplyUsd: positions.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: positions.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: positions.summary.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(positions.summary.healthFactor),\n healthStatus: hfStatus,\n chainBreakdown,\n };\n // MM net worth is already USD - add to wallet total\n walletBalanceUsd += positions.summary.netWorthUsd;\n }\n } catch (err) {\n console.error(`[portfolio] Failed to get MM positions for ${wallet.nickname}:`, err);\n }\n\n totalValueUsd += walletBalanceUsd;\n\n results.push({\n wallet: {\n nickname: wallet.nickname,\n address: wallet.address,\n type: wallet.type,\n },\n balances: balancesWithUsd as WalletBalanceResult['balances'],\n moneyMarket: mmSummary,\n walletTotalUsd: walletBalanceUsd > 0 ? `$${walletBalanceUsd.toFixed(2)}` : undefined,\n } as WalletBalanceResult);\n }\n\n // Build summary\n const summary = {\n walletCount: results.length,\n chainsQueried: chainIdsToQuery.length,\n timestamp: new Date().toISOString(),\n estimatedTotalUsd: totalValueUsd > 0 ? `$${totalValueUsd.toFixed(2)}` : 'No positions',\n priceSource: priceMap ? 'SODAX' : 'unavailable',\n };\n\n return {\n success: true,\n summary,\n wallets: results,\n };\n}\n\n// ============================================================================\n// Solana Balance Functions\n// ============================================================================\n\nconst SOLANA_RPC_URL = 'https://api.mainnet-beta.solana.com';\n\n/**\n * Major SPL tokens to check on Solana\n */\nconst SOLANA_TOKENS: Array<{ mint: string; symbol: string; decimals: number }> = [\n { mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', decimals: 6 },\n { mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', symbol: 'USDT', decimals: 6 },\n];\n\n/**\n * Get native SOL balance for a Solana wallet\n */\nasync function getSolanaBalance(\n address: string\n): Promise<{ symbol: string; balance: string; balanceRaw: bigint }> {\n try {\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getBalance',\n params: [address],\n }),\n });\n const json = await response.json() as { result?: { value: number } };\n const lamports = BigInt(json.result?.value || 0);\n // SOL has 9 decimals\n const balance = Number(lamports) / 1e9;\n return {\n symbol: 'SOL',\n balance: balance.toFixed(6),\n balanceRaw: lamports,\n };\n } catch (error) {\n console.error('[portfolio] Failed to get SOL balance:', error);\n return { symbol: 'SOL', balance: '0', balanceRaw: 0n };\n }\n}\n\n/**\n * Get SPL token balances for a Solana wallet\n */\nasync function getSolanaTokenBalances(\n address: string\n): Promise<Array<{ symbol: string; balance: string; address: string }>> {\n const results: Array<{ symbol: string; balance: string; address: string }> = [];\n \n try {\n // Query all token accounts owned by this wallet\n const response = await fetch(SOLANA_RPC_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [\n address,\n { programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' },\n { encoding: 'jsonParsed' },\n ],\n }),\n });\n \n const json = await response.json() as {\n result?: {\n value: Array<{\n account: {\n data: {\n parsed: {\n info: {\n mint: string;\n tokenAmount: { uiAmount: number; decimals: number };\n };\n };\n };\n };\n }>;\n };\n };\n \n const accounts = json.result?.value || [];\n \n // Match against known tokens\n for (const tokenConfig of SOLANA_TOKENS) {\n const account = accounts.find(\n (a) => a.account.data.parsed.info.mint === tokenConfig.mint\n );\n if (account) {\n const amount = account.account.data.parsed.info.tokenAmount.uiAmount || 0;\n if (amount > 0) {\n results.push({\n symbol: tokenConfig.symbol,\n balance: amount.toFixed(6),\n address: tokenConfig.mint,\n });\n }\n }\n }\n } catch (error) {\n console.error('[portfolio] Failed to get Solana token balances:', error);\n }\n \n return results;\n}\n\n/**\n * Get all Solana balances for a wallet\n */\nexport async function getSolanaWalletBalances(\n address: string,\n includeZeroBalances: boolean = false\n): Promise<{\n chainId: string;\n chainName: string;\n native: { symbol: string; balance: string; usdValue?: string };\n tokens: Array<{ symbol: string; balance: string; address: string; usdValue?: string }>;\n chainTotalUsd?: string;\n} | null> {\n // Validate Solana address format (base58, 32-44 chars)\n if (!address || address.startsWith('0x') || address.length < 32 || address.length > 44) {\n return null;\n }\n \n try {\n const native = await getSolanaBalance(address);\n const tokens = await getSolanaTokenBalances(address);\n \n // Skip if no balances and not including zeros\n if (!includeZeroBalances && native.balanceRaw === 0n && tokens.length === 0) {\n return null;\n }\n \n return {\n chainId: 'solana',\n chainName: 'Solana',\n native: {\n symbol: native.symbol,\n balance: native.balance,\n },\n tokens,\n };\n } catch (error) {\n console.error('[portfolio] Failed to get Solana balances:', error);\n return null;\n }\n}\n",
1192 "inputSchema": {},
1193 "outputSchema": null,
1194 "icons": null,
1195 "annotations": null,
1196 "meta": null,
1197 "execution": null
1198 },
1199 {
1200 "name": "walletManagement.ts",
1201 "title": null,
1202 "description": "Script: walletManagement.ts. Code:\n/**\n * Wallet Management Tools\n * \n * Agent-driven wallet configuration:\n * - Add wallets with nicknames\n * - Rename existing wallets\n * - Remove wallets\n * - Set default wallet\n * \n * Changes persist to: ~/.openclaw/extensions/amped-defi/wallets.json\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { getWalletManager } from '../wallet';\nimport type { WalletConfig, WalletBackendType } from '../wallet/types';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_add_wallet\n */\nconst AddWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Nickname for the wallet (e.g., \"trading\", \"savings\", \"degen\")',\n }),\n source: Type.Union([\n Type.Literal('evm-wallet-skill'),\n Type.Literal('bankr'),\n Type.Literal('env'),\n ], {\n description: 'Wallet source type',\n }),\n // For evm-wallet-skill\n path: Type.Optional(Type.String({\n description: 'Path to wallet JSON file (for evm-wallet-skill). Defaults to ~/.evm-wallet.json',\n })),\n // For bankr\n apiKey: Type.Optional(Type.String({\n description: 'Bankr API key (for bankr source)',\n })),\n apiUrl: Type.Optional(Type.String({\n description: 'Bankr API URL (optional, defaults to https://api.bankr.bot)',\n })),\n // For env\n address: Type.Optional(Type.String({\n description: 'Wallet address (for env source)',\n })),\n privateKey: Type.Optional(Type.String({\n description: 'Private key (for env source). WARNING: Will be stored in config file.',\n })),\n // Chain restrictions\n chains: Type.Optional(Type.Array(Type.String(), {\n description: 'Optional list of chains this wallet can use',\n })),\n});\n\n/**\n * Schema for amped_rename_wallet\n */\nconst RenameWalletSchema = Type.Object({\n currentNickname: Type.String({\n description: 'Current wallet nickname',\n }),\n newNickname: Type.String({\n description: 'New wallet nickname',\n }),\n});\n\n/**\n * Schema for amped_remove_wallet\n */\nconst RemoveWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to remove',\n }),\n confirm: Type.Optional(Type.Boolean({\n description: 'Set to true to confirm removal',\n default: false,\n })),\n});\n\n/**\n * Schema for amped_set_default_wallet\n */\nconst SetDefaultWalletSchema = Type.Object({\n nickname: Type.String({\n description: 'Wallet nickname to set as default',\n }),\n});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype AddWalletParams = Static<typeof AddWalletSchema>;\ntype RenameWalletParams = Static<typeof RenameWalletSchema>;\ntype RemoveWalletParams = Static<typeof RemoveWalletSchema>;\ntype SetDefaultWalletParams = Static<typeof SetDefaultWalletSchema>;\n\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n\n// ============================================================================\n// Handlers\n// ============================================================================\n\n/**\n * Add a new wallet with a nickname\n */\nasync function handleAddWallet(params: AddWalletParams): Promise<unknown> {\n const { nickname, source, path, apiKey, apiUrl, address, privateKey, chains } = params;\n\n console.log('[walletManagement:addWallet] Adding wallet', { nickname, source });\n\n // Validate required fields based on source\n if (source === 'bankr' && !apiKey) {\n throw new Error('Bankr wallet requires apiKey');\n }\n if (source === 'env' && (!address || !privateKey)) {\n throw new Error('Env wallet requires both address and privateKey');\n }\n\n // Build config\n const config: WalletConfig = {\n source: source as WalletBackendType,\n chains,\n };\n\n if (source === 'evm-wallet-skill') {\n if (path) config.path = path;\n } else if (source === 'bankr') {\n config.apiKey = apiKey;\n if (apiUrl) config.apiUrl = apiUrl;\n } else if (source === 'env') {\n config.address = address as `0x${string}`;\n config.privateKey = privateKey as `0x${string}`;\n }\n\n // Add wallet\n const walletManager = getWalletManager();\n await walletManager.addWallet(nickname, config);\n\n // Get the new wallet info\n const wallet = await walletManager.resolve(nickname);\n const walletAddress = await wallet.getAddress();\n\n return {\n success: true,\n message: `Wallet \"${nickname}\" added successfully`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: source,\n address: walletAddress,\n chains: wallet.supportedChains,\n },\n hint: `You can now use: \"swap 100 USDC to ETH using ${nickname}\"`,\n };\n}\n\n/**\n * Rename a wallet\n */\nasync function handleRenameWallet(params: RenameWalletParams): Promise<unknown> {\n const { currentNickname, newNickname } = params;\n\n console.log('[walletManagement:renameWallet] Renaming wallet', { \n from: currentNickname, \n to: newNickname \n });\n\n const walletManager = getWalletManager();\n \n // Get current info before rename\n const wallet = await walletManager.resolve(currentNickname);\n const address = await wallet.getAddress();\n\n // Rename\n await walletManager.renameWallet(currentNickname, newNickname);\n\n return {\n success: true,\n message: `Wallet renamed from \"${currentNickname}\" to \"${newNickname}\"`,\n wallet: {\n nickname: newNickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: `Now use: \"swap 100 USDC using ${newNickname}\"`,\n };\n}\n\n/**\n * Remove a wallet\n */\nasync function handleRemoveWallet(params: RemoveWalletParams): Promise<unknown> {\n const { nickname, confirm } = params;\n\n console.log('[walletManagement:removeWallet] Removing wallet', { nickname, confirm });\n\n const walletManager = getWalletManager();\n \n // Get wallet info before removal\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n\n if (!confirm) {\n return {\n success: false,\n requiresConfirmation: true,\n message: `Are you sure you want to remove wallet \"${nickname}\" (${address})?`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n },\n hint: 'Call again with confirm: true to proceed',\n };\n }\n\n // Remove\n await walletManager.removeWallet(nickname);\n\n // List remaining wallets\n const remainingWallets = await walletManager.listWallets();\n\n return {\n success: true,\n message: `Wallet \"${nickname}\" removed`,\n remainingWallets: remainingWallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n isDefault: w.isDefault,\n })),\n };\n}\n\n/**\n * Set default wallet\n */\nasync function handleSetDefaultWallet(params: SetDefaultWalletParams): Promise<unknown> {\n const { nickname } = params;\n\n console.log('[walletManagement:setDefaultWallet] Setting default', { nickname });\n\n const walletManager = getWalletManager();\n \n // Validate wallet exists\n const wallet = await walletManager.resolve(nickname);\n const address = await wallet.getAddress();\n\n // Set default\n await walletManager.setDefaultWallet(nickname);\n\n return {\n success: true,\n message: `Default wallet set to \"${nickname}\"`,\n wallet: {\n nickname: nickname.toLowerCase(),\n type: wallet.type,\n address,\n chains: [...wallet.supportedChains],\n },\n hint: 'Operations without a wallet specified will now use this wallet',\n };\n}\n\n// ============================================================================\n// Error Wrapper\n// ============================================================================\n\nfunction wrapHandler<T>(handler: (params: T) => Promise<unknown>) {\n return async (params: unknown): Promise<unknown> => {\n try {\n return await handler(params as T);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[walletManagement] Error:', message);\n return {\n success: false,\n error: message,\n };\n }\n };\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register wallet management tools\n */\nexport function registerWalletManagementTools(agentTools: AgentTools): void {\n // 1. Add wallet\n agentTools.register({\n name: 'amped_add_wallet',\n summary: 'Add a new wallet with a nickname for easy reference',\n description:\n 'Add a wallet from evm-wallet-skill, Bankr, or environment variables. ' +\n 'Give it a memorable nickname like \"trading\", \"savings\", or \"degen\". ' +\n 'The wallet will be saved to config and available across sessions.',\n schema: AddWalletSchema,\n handler: wrapHandler(handleAddWallet),\n });\n\n // 2. Rename wallet\n agentTools.register({\n name: 'amped_rename_wallet',\n summary: 'Rename a wallet to a new nickname',\n description:\n 'Change the nickname of an existing wallet. ' +\n 'The wallet address and configuration remain the same, only the nickname changes.',\n schema: RenameWalletSchema,\n handler: wrapHandler(handleRenameWallet),\n });\n\n // 3. Remove wallet\n agentTools.register({\n name: 'amped_remove_wallet',\n summary: 'Remove a wallet from configuration',\n description:\n 'Remove a wallet by nickname. This only removes it from the config file, ' +\n 'it does NOT delete the actual wallet or funds. Requires confirmation.',\n schema: RemoveWalletSchema,\n handler: wrapHandler(handleRemoveWallet),\n });\n\n // 4. Set default wallet\n agentTools.register({\n name: 'amped_set_default_wallet',\n summary: 'Set which wallet to use by default',\n description:\n 'Set the default wallet for operations. When you don\\'t specify a wallet, ' +\n 'this one will be used automatically.',\n schema: SetDefaultWalletSchema,\n handler: wrapHandler(handleSetDefaultWallet),\n });\n}\n\n// Export schemas and handlers for testing\nexport {\n AddWalletSchema,\n RenameWalletSchema,\n RemoveWalletSchema,\n SetDefaultWalletSchema,\n handleAddWallet,\n handleRenameWallet,\n handleRemoveWallet,\n handleSetDefaultWallet,\n};\n",
1203 "inputSchema": {},
1204 "outputSchema": null,
1205 "icons": null,
1206 "annotations": null,
1207 "meta": null,
1208 "execution": null
1209 },
1210 {
1211 "name": "discovery.ts",
1212 "title": null,
1213 "description": "Script: discovery.ts. Code:\n/**\n * Discovery/Read Tools for Amped DeFi Plugin\n *\n * These tools provide read-only access to:\n * - Supported chains and tokens\n * - Wallet address resolution\n * - Money market positions and reserves\n *\n * @module tools/discovery\n */\n\nimport { Type, Static } from '@sinclair/typebox';\nimport { getSodaxClient } from '../sodax/client';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { getWalletManager, type IWalletBackend, type WalletInfo } from '../wallet';\nimport { \n aggregateCrossChainPositions, \n formatHealthFactor,\n getHealthFactorStatus,\n getPositionRecommendation \n} from '../utils/positionAggregator';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_supported_chains - no parameters required\n */\nconst SupportedChainsSchema = Type.Object({});\n\n/**\n * Schema for amped_supported_tokens\n */\nconst SupportedTokensSchema = Type.Object({\n module: Type.Union([\n Type.Literal('swaps'),\n Type.Literal('bridge'),\n Type.Literal('moneyMarket'),\n ]),\n chainId: Type.String({\n description: 'Spoke chain ID (e.g., \"ethereum\", \"arbitrum\", \"sonic\")',\n }),\n});\n\n/**\n * Schema for amped_wallet_address\n */\nconst WalletAddressSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n});\n\n/**\n * Schema for amped_money_market_positions\n */\nconst MoneyMarketPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainId: Type.String({\n description: 'Spoke chain ID to query positions on',\n }),\n});\n\n/**\n * Schema for amped_money_market_reserves\n */\nconst MoneyMarketReservesSchema = Type.Object({\n chainId: Type.Optional(\n Type.String({\n description:\n 'Optional chain ID. Money market is hub-centric, so this filters results for a specific spoke chain if needed',\n })\n ),\n});\n\n/**\n * Schema for amped_cross_chain_positions\n * Get aggregated positions view across all chains\n */\nconst CrossChainPositionsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n chainIds: Type.Optional(\n Type.Array(Type.String(), {\n description: 'Optional array of specific chain IDs to query (defaults to all supported chains)',\n })\n ),\n includeZeroBalances: Type.Optional(\n Type.Boolean({\n description: 'Include positions with zero balance',\n default: false,\n })\n ),\n minUsdValue: Type.Optional(\n Type.Number({\n description: 'Minimum USD value threshold for including positions',\n default: 0.01,\n })\n ),\n});\n\n/**\n * Schema for amped_user_intents\n * Query user intent history from SODAX API\n */\nconst UserIntentsSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet',\n }),\n status: Type.Optional(\n Type.Union([\n Type.Literal('all', { description: 'All intents (open and closed)' }),\n Type.Literal('open', { description: 'Only open/pending intents' }),\n Type.Literal('closed', { description: 'Only filled/cancelled/expired intents' }),\n ], {\n description: 'Filter by intent status',\n default: 'all',\n })\n ),\n limit: Type.Optional(\n Type.Number({\n description: 'Number of items to return (default: 50, max: 100)',\n default: 50,\n minimum: 1,\n maximum: 100,\n })\n ),\n offset: Type.Optional(\n Type.Number({\n description: 'Number of items to skip (for pagination)',\n default: 0,\n minimum: 0,\n })\n ),\n});\n\n/**\n * Schema for amped_list_wallets - List all configured wallets\n */\nconst ListWalletsSchema = Type.Object({});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype SupportedChainsParams = Static<typeof SupportedChainsSchema>;\ntype SupportedTokensParams = Static<typeof SupportedTokensSchema>;\ntype WalletAddressParams = Static<typeof WalletAddressSchema>;\ntype MoneyMarketPositionsParams = Static<typeof MoneyMarketPositionsSchema>;\ntype ListWalletsParams = Static<typeof ListWalletsSchema>;\ntype MoneyMarketReservesParams = Static<typeof MoneyMarketReservesSchema>;\ntype CrossChainPositionsParams = Static<typeof CrossChainPositionsSchema>;\ntype UserIntentsParams = Static<typeof UserIntentsSchema>;\n\n/**\n * AgentTools interface for registering tools with the OpenClaw framework\n */\ninterface AgentTools {\n register(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: unknown;\n handler: (params: unknown) => Promise<unknown>;\n }): void;\n}\n\n// Helper to wrap typed handlers for AgentTools registration\nfunction wrapHandler<T>(handler: (params: T) => Promise<unknown>): (params: unknown) => Promise<unknown> {\n return (params: unknown) => handler(params as T);\n}\n\n// ============================================================================\n// Tool Implementations\n// ============================================================================\n\n/**\n * Get supported spoke chains from SODAX configuration\n */\nasync function handleSupportedChains(\n _params: SupportedChainsParams\n): Promise<unknown> {\n const sodax = getSodaxClient();\n const chains = sodax.config.getSupportedSpokeChains();\n\n // SDK may return chain IDs as strings or chain objects\n return {\n success: true,\n chains: chains.map((chain: any) => {\n // Handle both string IDs and chain objects\n if (typeof chain === 'string') {\n return {\n id: chain,\n name: chain,\n type: 'evm',\n isHub: chain === 'sonic',\n nativeCurrency: undefined,\n };\n }\n return {\n id: chain.id || chain,\n name: chain.name || chain.id || chain,\n type: chain.type || 'evm',\n isHub: (chain.id || chain) === 'sonic',\n nativeCurrency: chain.nativeCurrency,\n };\n }),\n };\n}\n\n/**\n * Get supported tokens for a specific module and chain\n */\nasync function handleSupportedTokens(\n params: SupportedTokensParams\n): Promise<unknown> {\n const sodax = getSodaxClient();\n const { module, chainId: rawChainId } = params;\n const chainId = toSodaxChainId(rawChainId);\n\n let tokens: Array<{\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n logoURI?: string;\n }> = [];\n\n // Helper to normalize token data\n const normalizeToken = (token: any) => ({\n address: token.address || '',\n symbol: token.symbol || '',\n name: token.name || token.symbol || '',\n decimals: token.decimals || 18,\n logoURI: token.logoURI || token.logoUri,\n });\n\n switch (module) {\n case 'swaps': {\n // Get supported swap tokens by chain ID\n // SDK may require chainId to be cast to specific type\n try {\n const swapTokens = sodax.config.getSupportedSwapTokensByChainId(chainId as any);\n tokens = (swapTokens || []).map(normalizeToken);\n } catch (e) {\n console.warn('[discovery] Failed to get swap tokens:', e);\n tokens = [];\n }\n break;\n }\n\n\n case 'bridge': {\n // Get bridgeable tokens via hub assets\n // Hub assets represent tokens that can be bridged between chains\n // Reference: sodax-frontend uses getHubAssets() for bridge token discovery\n try {\n const hubAssets = sodax.config.getHubAssets();\n \n // Check if this is the hub chain (Sonic)\n const isHubChain = rawChainId === 'sonic' || chainId === 'sonic';\n \n if (isHubChain) {\n // For Sonic (hub), show all bridgeable assets from all spoke chains\n // These are the assets that can be bridged FROM Sonic to other chains\n const allTokens: typeof tokens = [];\n const seenAddresses = new Set<string>();\n \n for (const spokeChainId of Object.keys(hubAssets)) {\n const chainAssets = hubAssets[spokeChainId as keyof typeof hubAssets] || {};\n for (const asset of Object.values(chainAssets)) {\n // Add the hub asset (on Sonic) - dedupe by hub address\n const hubAddress = (asset as any).asset || (asset as any).hubAddress || (asset as any).address;\n if (hubAddress && !seenAddresses.has(hubAddress.toLowerCase())) {\n seenAddresses.add(hubAddress.toLowerCase());\n allTokens.push(normalizeToken({\n address: hubAddress,\n symbol: (asset as any).symbol || '',\n name: (asset as any).name || (asset as any).symbol || '',\n decimals: (asset as any).decimals || 18,\n logoURI: (asset as any).logoURI || (asset as any).logoUri,\n }));\n }\n }\n }\n tokens = allTokens;\n } else {\n // For spoke chains, get assets bridgeable from that specific chain\n const chainAssets = hubAssets[chainId as keyof typeof hubAssets] || {};\n tokens = Object.values(chainAssets).map((asset: any) => normalizeToken({\n address: (asset as any).asset || (asset as any).address || (asset as any).originalAddress || '',\n symbol: asset.symbol || '',\n name: asset.name || asset.symbol || '',\n decimals: (asset as any).decimal || (asset as any).decimals || 18,\n logoURI: asset.logoURI || asset.logoUri,\n }));\n }\n } catch (e) {\n console.warn('[discovery] Failed to get bridge tokens:', e);\n tokens = [];\n }\n break;\n }\n\n case 'moneyMarket': {\n // Get money market supported tokens from config\n // Reference: sodax-frontend ConfigService.getSupportedMoneyMarketTokensByChainId\n try {\n const mmTokens = sodax.config.getSupportedMoneyMarketTokensByChainId?.(chainId as any);\n if (mmTokens && Array.isArray(mmTokens)) {\n tokens = mmTokens.map(normalizeToken);\n } else {\n // Fallback: try supportedMoneyMarketTokens directly from config\n const allMmTokens = (sodax.config as any).sodaxConfig?.supportedMoneyMarketTokens;\n if (allMmTokens && allMmTokens[chainId]) {\n tokens = allMmTokens[chainId].map(normalizeToken);\n } else {\n console.warn('[discovery] No money market tokens found for chain', chainId);\n tokens = [];\n }\n }\n } catch (e) {\n console.warn('[discovery] Failed to get money market tokens:', e);\n tokens = [];\n }\n break;\n }\n\n default:\n throw new Error(`Unknown module: ${module}`);\n }\n\n return {\n success: true,\n module,\n chainId,\n tokens,\n count: tokens.length,\n };\n}\n\n/**\n * Get wallet address by walletId\n * Returns enhanced wallet info with source and supported chains\n */\nasync function handleWalletAddress(\n params: WalletAddressParams\n): Promise<unknown> {\n const { walletId } = params;\n\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n\n const address = await wallet.getAddress();\n\n return {\n success: true,\n walletId: wallet.nickname,\n address,\n type: wallet.type,\n chains: [...wallet.supportedChains],\n };\n}\n\n/**\n * Get user money market positions (humanized format)\n */\nasync function handleMoneyMarketPositions(\n params: MoneyMarketPositionsParams\n): Promise<unknown> {\n const { walletId, chainId } = params;\n\n // Get wallet from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Get spoke provider for this wallet and chain\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n\n const sodax = getSodaxClient();\n\n // IMPORTANT: getUserReservesHumanized() only returns raw balances without token metadata.\n // To get token symbols/names, we must:\n // 1. Fetch getReservesHumanized() for token metadata\n // 2. Format with formatReservesUSD(buildReserveDataWithPrice())\n // 3. Join with formatUserSummary(buildUserSummaryRequest())\n // Reference: sodax-frontend/packages/dapp-kit/src/hooks/mm/useUserFormattedSummary.ts\n\n // Step 1: Fetch reserves with token metadata (symbols, names, decimals)\n const reserves = await sodax.moneyMarket.data.getReservesHumanized();\n\n // Step 2: Format reserves with USD prices\n const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(\n sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)\n );\n\n // Step 3: Fetch user-specific balances\n const userReservesResult = await sodax.moneyMarket.data.getUserReservesHumanized(\n spokeProvider\n );\n\n // Step 4: Join reserves metadata with user balances via formatUserSummary\n const userSummary = sodax.moneyMarket.data.formatUserSummary(\n sodax.moneyMarket.data.buildUserSummaryRequest(reserves, formattedReserves, userReservesResult)\n );\n\n // Extract user reserves from the formatted summary\n // The formatted summary has userReservesData with proper token metadata\n const userReservesData = (userSummary as any).userReservesData || [];\n\n // Format positions for readability\n const positions = userReservesData.map((reserve: any) => {\n // Get supply balance (underlyingBalance is the human-readable supply amount)\n const supplyBalance = reserve.underlyingBalance || '0';\n const supplyBalanceUsd = reserve.underlyingBalanceUSD || '0';\n\n // Get borrow balance (variableBorrows is the human-readable borrow amount)\n const borrowBalance = reserve.variableBorrows || reserve.totalBorrows || '0';\n const borrowBalanceUsd = reserve.variableBorrowsUSD || reserve.totalBorrowsUSD || '0';\n\n // Get APY values (formatted reserves have these)\n const supplyApy = parseFloat(reserve.reserve?.supplyAPY || '0') * 100;\n const borrowApy = parseFloat(reserve.reserve?.variableBorrowAPY || '0') * 100;\n\n return {\n token: {\n address: reserve.underlyingAsset || reserve.reserve?.underlyingAsset || '',\n symbol: reserve.reserve?.symbol || '',\n name: reserve.reserve?.name || '',\n decimals: reserve.reserve?.decimals || 18,\n },\n supply: {\n balance: supplyBalance,\n balanceUsd: supplyBalanceUsd,\n apy: supplyApy,\n collateral: reserve.usageAsCollateralEnabledOnUser ?? false,\n },\n borrow: {\n balance: borrowBalance,\n balanceUsd: borrowBalanceUsd,\n apy: borrowApy,\n },\n // Health indicators\n loanToValue: parseFloat(reserve.reserve?.baseLTVasCollateral || '0') / 10000,\n liquidationThreshold: parseFloat(reserve.reserve?.reserveLiquidationThreshold || '0') / 10000,\n };\n });\n\n // Filter to only positions with activity\n const activePositions = positions.filter((p: any) =>\n parseFloat(p.supply.balance) > 0 || parseFloat(p.borrow.balance) > 0\n );\n\n // Calculate summary metrics\n const totalSupplyUsd = activePositions.reduce(\n (sum: number, p: any) => sum + (parseFloat(p.supply.balanceUsd) || 0),\n 0\n );\n const totalBorrowUsd = activePositions.reduce(\n (sum: number, p: any) => sum + (parseFloat(p.borrow.balanceUsd) || 0),\n 0\n );\n const netWorthUsd = totalSupplyUsd - totalBorrowUsd;\n const healthFactor =\n totalBorrowUsd > 0 ? totalSupplyUsd / totalBorrowUsd : Infinity;\n\n return {\n success: true,\n walletId,\n address: walletAddress,\n chainId,\n positions: activePositions,\n summary: {\n totalSupplyUsd: totalSupplyUsd.toFixed(2),\n totalBorrowUsd: totalBorrowUsd.toFixed(2),\n netWorthUsd: netWorthUsd.toFixed(2),\n healthFactor: healthFactor === Infinity ? '\u221e' : healthFactor.toFixed(2),\n positionCount: activePositions.length,\n },\n };\n}\n\n/**\n * Get money market reserves (humanized format)\n * Hub-centric: returns reserves across all markets\n */\nasync function handleMoneyMarketReserves(\n params: MoneyMarketReservesParams\n): Promise<unknown> {\n const { chainId } = params;\n\n const sodax = getSodaxClient();\n\n // Get reserves in humanized format (hub-centric)\n const reservesResult = await sodax.moneyMarket.data.getReservesHumanized();\n\n // SDK may return ReservesDataHumanized object with .reservesData array or just array\n const reservesArray = Array.isArray(reservesResult) \n ? reservesResult \n : (reservesResult as any).reservesData || [];\n\n // Filter by chainId if provided\n let filteredReserves = reservesArray;\n if (chainId) {\n filteredReserves = reservesArray.filter(\n (r: any) => r.token?.chainId === chainId || r.hubChainId === chainId || r.chainId === chainId\n );\n }\n\n // Format reserves for readability\n const formattedReserves = filteredReserves.map((reserve: any) => ({\n token: {\n address: reserve.token?.address || reserve.underlyingAsset || '',\n symbol: reserve.token?.symbol || reserve.symbol || '',\n name: reserve.token?.name || reserve.name || '',\n decimals: reserve.token?.decimals || reserve.decimals || 18,\n chainId: reserve.token?.chainId || reserve.chainId || '',\n },\n liquidity: {\n totalSupply: reserve.liquidity?.totalSupply || reserve.totalScaledVariableDebt || '0',\n availableLiquidity: reserve.liquidity?.availableLiquidity || reserve.availableLiquidity || '0',\n totalBorrow: reserve.liquidity?.totalBorrow || reserve.totalVariableDebt || '0',\n utilizationRate: reserve.liquidity?.utilizationRate || reserve.utilizationRate || '0',\n },\n rates: {\n supplyApy: reserve.rates?.supplyApy || reserve.supplyAPY || '0',\n borrowApy: reserve.rates?.borrowApy || reserve.variableBorrowAPY || '0',\n },\n parameters: {\n loanToValue: reserve.parameters?.loanToValue || reserve.baseLTVasCollateral || '0',\n liquidationThreshold: reserve.parameters?.liquidationThreshold || reserve.reserveLiquidationThreshold || '0',\n liquidationBonus: reserve.parameters?.liquidationBonus || reserve.reserveLiquidationBonus || '0',\n },\n hubChainId: reserve.hubChainId || 'sonic',\n }));\n\n // Calculate aggregate metrics\n const totalAvailableLiquidity = formattedReserves.reduce(\n (sum: number, r: any) => sum + (parseFloat(r.liquidity.availableLiquidity) || 0),\n 0\n );\n const totalBorrowed = formattedReserves.reduce(\n (sum: number, r: any) => sum + (parseFloat(r.liquidity.totalBorrow) || 0),\n 0\n );\n\n return {\n success: true,\n chainId: chainId || 'all',\n reserves: formattedReserves,\n summary: {\n reserveCount: formattedReserves.length,\n totalAvailableLiquidity: totalAvailableLiquidity.toFixed(2),\n totalBorrowed: totalBorrowed.toFixed(2),\n globalUtilizationRate:\n totalAvailableLiquidity + totalBorrowed > 0\n ? (\n (totalBorrowed / (totalAvailableLiquidity + totalBorrowed)) *\n 100\n ).toFixed(2) + '%'\n : '0%',\n },\n };\n}\n\n// ============================================================================\n// Cross-Chain Positions Tool\n// ============================================================================\n\n/**\n * Get aggregated money market positions across all chains\n * \n * This provides a unified view of:\n * - Total supply/borrow across all networks\n * - Health factor and liquidation risk\n * - Available borrowing power\n * - Net position and APY\n * - Risk metrics and recommendations\n */\nasync function handleCrossChainPositions(\n params: CrossChainPositionsParams\n): Promise<unknown> {\n const { walletId, chainIds, includeZeroBalances, minUsdValue } = params;\n\n console.log('[discovery:crossChainPositions] Aggregating positions', {\n walletId,\n chainIds: chainIds || 'all',\n includeZeroBalances,\n minUsdValue,\n });\n\n try {\n const view = await aggregateCrossChainPositions(walletId, {\n chainIds,\n includeZeroBalances,\n minUsdValue,\n });\n\n // Get recommendations\n const recommendations = getPositionRecommendation(view);\n\n // Format response\n const response = {\n success: true,\n walletId: view.walletId,\n address: view.address,\n timestamp: view.timestamp,\n summary: {\n totalSupplyUsd: view.summary.totalSupplyUsd.toFixed(2),\n totalBorrowUsd: view.summary.totalBorrowUsd.toFixed(2),\n netWorthUsd: view.summary.netWorthUsd.toFixed(2),\n availableBorrowUsd: view.summary.availableBorrowUsd.toFixed(2),\n healthFactor: formatHealthFactor(view.summary.healthFactor),\n healthFactorStatus: getHealthFactorStatus(view.summary.healthFactor),\n liquidationRisk: view.summary.liquidationRisk,\n weightedSupplyApy: `${(view.summary.weightedSupplyApy * 100).toFixed(2)}%`,\n weightedBorrowApy: `${(view.summary.weightedBorrowApy * 100).toFixed(2)}%`,\n netApy: `${(view.summary.netApy * 100).toFixed(2)}%`,\n },\n chainBreakdown: view.chainSummaries.map(cs => ({\n chainId: cs.chainId,\n supplyUsd: cs.supplyUsd.toFixed(2),\n borrowUsd: cs.borrowUsd.toFixed(2),\n netWorthUsd: cs.netWorthUsd.toFixed(2),\n healthFactor: formatHealthFactor(cs.healthFactor),\n positionCount: cs.positionCount,\n })),\n collateralUtilization: {\n totalCollateralUsd: view.collateralUtilization.totalCollateralUsd.toFixed(2),\n usedCollateralUsd: view.collateralUtilization.usedCollateralUsd.toFixed(2),\n availableCollateralUsd: view.collateralUtilization.availableCollateralUsd.toFixed(2),\n utilizationRate: `${view.collateralUtilization.utilizationRate.toFixed(2)}%`,\n },\n riskMetrics: {\n maxLtv: `${(view.riskMetrics.maxLtv * 100).toFixed(2)}%`,\n currentLtv: `${(view.riskMetrics.currentLtv * 100).toFixed(2)}%`,\n bufferUntilLiquidation: `${view.riskMetrics.bufferUntilLiquidation.toFixed(2)}%`,\n safeMaxBorrowUsd: view.riskMetrics.safeMaxBorrowUsd.toFixed(2),\n },\n positions: view.positions.map(pos => ({\n chainId: pos.chainId,\n token: pos.token,\n supply: {\n balance: pos.supply.balance,\n balanceUsd: pos.supply.balanceUsd,\n apy: `${(pos.supply.apy * 100).toFixed(2)}%`,\n isCollateral: pos.supply.isCollateral,\n },\n borrow: {\n balance: pos.borrow.balance,\n balanceUsd: pos.borrow.balanceUsd,\n apy: `${(pos.borrow.apy * 100).toFixed(2)}%`,\n },\n loanToValue: `${(pos.loanToValue * 100).toFixed(2)}%`,\n liquidationThreshold: `${(pos.liquidationThreshold * 100).toFixed(2)}%`,\n })),\n recommendations,\n };\n\n console.log('[discovery:crossChainPositions] Aggregation complete', {\n totalPositions: view.positions.length,\n totalSupplyUsd: view.summary.totalSupplyUsd,\n totalBorrowUsd: view.summary.totalBorrowUsd,\n healthFactor: view.summary.healthFactor,\n });\n\n return response;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:crossChainPositions] Failed to aggregate positions', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to aggregate cross-chain positions: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// User Intents Tool (SODAX API)\n// ============================================================================\n\n/**\n * Get user intents from SODAX API\n * \n * Queries the backend API for intent history including:\n * - Open/pending intents\n * - Filled intents\n * - Cancelled/expired intents\n * - Event history for each intent\n */\nasync function handleUserIntents(\n params: UserIntentsParams\n): Promise<unknown> {\n const { walletId, status = 'all', limit = 50, offset = 0 } = params;\n\n console.log('[discovery:userIntents] Fetching user intents', {\n walletId,\n status,\n limit,\n offset,\n });\n\n try {\n // Get wallet address from unified WalletManager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n // Initialize API client\n const apiClient = getSodaxApiClient();\n\n // Determine filters based on status\n let filters;\n if (status === 'open') {\n filters = { open: true };\n } else if (status === 'closed') {\n filters = { open: false };\n }\n\n // Fetch intents\n const response = await apiClient.getUserIntents(\n walletAddress,\n { limit, offset },\n filters\n );\n\n // Format response\n const formattedIntents = response.items.map(intent => ({\n intentHash: intent.intentHash,\n txHash: intent.txHash,\n chainId: intent.chainId,\n blockNumber: intent.blockNumber,\n status: intent.open ? 'open' : 'closed',\n createdAt: intent.createdAt,\n input: {\n token: intent.intent.inputToken,\n amount: intent.intent.inputAmount,\n chainId: intent.intent.srcChain,\n },\n output: {\n token: intent.intent.outputToken,\n minAmount: intent.intent.minOutputAmount,\n chainId: intent.intent.dstChain,\n },\n deadline: new Date(parseInt(intent.intent.deadline) * 1000).toISOString(),\n allowPartialFill: intent.intent.allowPartialFill,\n events: intent.events\n .filter((event): event is typeof event & { intentState: NonNullable<typeof event.intentState> } => \n event.intentState != null\n )\n .map(event => ({\n type: event.eventType,\n txHash: event.txHash,\n blockNumber: event.blockNumber,\n state: {\n remainingInput: event.intentState.remainingInput,\n receivedOutput: event.intentState.receivedOutput,\n pendingPayment: event.intentState.pendingPayment,\n },\n })),\n }));\n\n const result = {\n success: true,\n walletId,\n address: walletAddress,\n pagination: {\n total: response.total,\n offset: response.offset,\n limit: response.limit,\n hasMore: response.offset + response.items.length < response.total,\n },\n intents: formattedIntents,\n summary: {\n totalIntents: response.total,\n returned: formattedIntents.length,\n openIntents: formattedIntents.filter((i: { status: string }) => i.status === 'open').length,\n closedIntents: formattedIntents.filter((i: { status: string }) => i.status === 'closed').length,\n },\n };\n\n console.log('[discovery:userIntents] User intents fetched', {\n total: response.total,\n returned: formattedIntents.length,\n });\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n console.error('[discovery:userIntents] Failed to fetch user intents', {\n error: errorMessage,\n walletId,\n });\n throw new Error(`Failed to fetch user intents: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// List Wallets Tool\n// ============================================================================\n\n/**\n * List all configured wallets with their nicknames, types, and supported chains\n */\nasync function handleListWallets(\n _params: ListWalletsParams\n): Promise<unknown> {\n console.log('[discovery:listWallets] Listing configured wallets');\n\n const walletManager = getWalletManager();\n const wallets = await walletManager.listWallets();\n\n const formattedWallets = wallets.map(w => ({\n nickname: w.nickname,\n type: w.type,\n address: w.address,\n addressKnown: w.address !== '0x...',\n supportedChains: w.chains,\n isDefault: w.isDefault,\n note: w.address === '0x...' && w.type === 'bankr' \n ? 'Address pending - will be fetched on first use' \n : undefined,\n }));\n\n const defaultWallet = await walletManager.getDefaultWalletName();\n\n // Group by type for summary\n const byType = {\n 'evm-wallet-skill': formattedWallets.filter(w => w.type === 'evm-wallet-skill'),\n 'bankr': formattedWallets.filter(w => w.type === 'bankr'),\n 'env': formattedWallets.filter(w => w.type === 'env'),\n };\n\n // Check if Bankr is configured but wallet not found\n const bankrKeyPresent = !!process.env.BANKR_API_KEY;\n const bankrWalletFound = byType.bankr.length > 0;\n\n return {\n success: true,\n wallets: formattedWallets,\n defaultWallet,\n count: formattedWallets.length,\n summary: {\n selfCustody: byType['evm-wallet-skill'].length + byType.env.length,\n bankrManaged: byType.bankr.length,\n },\n sources: {\n evmWalletSkill: byType['evm-wallet-skill'].length > 0,\n bankr: bankrWalletFound,\n bankrKeyConfigured: bankrKeyPresent,\n env: byType.env.length > 0,\n },\n hint: wallets.length === 0\n ? 'No wallets configured. Install evm-wallet-skill: git clone https://github.com/amped-finance/evm-wallet-skill.git ~/.openclaw/skills/evm-wallet-skill'\n : bankrKeyPresent && !bankrWalletFound\n ? 'Bankr API key found but wallet not loaded. Try \"Add my bankr wallet\" to register it.'\n : 'Use wallet nickname in operations, e.g., \"swap 100 USDC to ETH using main\"',\n };\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register all discovery tools with the agent tools registry\n *\n * @param agentTools - The OpenClaw AgentTools instance\n */\nexport function registerDiscoveryTools(agentTools: AgentTools): void {\n // 1. amped_supported_chains - Get supported spoke chains\n agentTools.register({\n name: 'amped_supported_chains',\n summary:\n 'Get a list of all supported spoke chains for swaps, bridging, and money market operations',\n schema: SupportedChainsSchema,\n handler: wrapHandler(handleSupportedChains),\n });\n\n // 2. amped_supported_tokens - Get supported tokens by module\n agentTools.register({\n name: 'amped_supported_tokens',\n summary:\n 'Get supported tokens for a specific module (swaps, bridge, or moneyMarket) on a given chain',\n schema: SupportedTokensSchema,\n handler: wrapHandler(handleSupportedTokens),\n });\n\n // 3. amped_wallet_address - Get wallet address\n agentTools.register({\n name: 'amped_wallet_address',\n summary:\n 'Get the resolved wallet address for a given walletId. Validates private key matches in execute mode.',\n schema: WalletAddressSchema,\n handler: wrapHandler(handleWalletAddress),\n });\n\n // 4. amped_money_market_positions - Get user positions (humanized)\n agentTools.register({\n name: 'amped_money_market_positions',\n summary:\n 'Get humanized money market positions for a wallet on a specific chain, including supply/borrow balances and health metrics',\n schema: MoneyMarketPositionsSchema,\n handler: wrapHandler(handleMoneyMarketPositions),\n });\n\n // 5. amped_money_market_reserves - Get market reserves (humanized)\n agentTools.register({\n name: 'amped_money_market_reserves',\n summary:\n 'Get humanized money market reserves data including liquidity, rates, and parameters. Hub-centric with optional chain filtering.',\n schema: MoneyMarketReservesSchema,\n handler: wrapHandler(handleMoneyMarketReserves),\n });\n\n // 6. amped_cross_chain_positions - Get aggregated positions across all chains\n agentTools.register({\n name: 'amped_cross_chain_positions',\n summary:\n 'Get a unified view of money market positions across ALL chains. Shows total supply/borrow, health factor, borrowing power, net APY, and risk metrics.',\n description:\n 'Aggregates money market positions across all supported chains to provide a comprehensive portfolio view. ' +\n 'Includes: total supply/borrow in USD, health factor with risk status, available borrowing power, ' +\n 'weighted APYs, collateral utilization, and personalized recommendations. ' +\n 'This is the recommended tool for getting a complete picture of money market positions.',\n schema: CrossChainPositionsSchema,\n handler: wrapHandler(handleCrossChainPositions),\n });\n\n // 7. amped_user_intents - Query user intent history from SODAX API\n agentTools.register({\n name: 'amped_user_intents',\n summary:\n 'Query user swap intent history from SODAX backend API. Shows open, filled, and cancelled intents with event details.',\n description:\n 'Retrieves the complete intent history for a wallet from the SODAX backend API. ' +\n 'Includes open intents (pending), filled intents (completed), and cancelled/expired intents. ' +\n 'Each intent includes input/output tokens, amounts, chain IDs, and event history. ' +\n 'Use this to track the status of past swaps and bridge operations.',\n schema: UserIntentsSchema,\n handler: wrapHandler(handleUserIntents),\n });\n\n // 8. amped_list_wallets - List all configured wallets\n agentTools.register({\n name: 'amped_list_wallets',\n summary:\n 'List all configured wallets with their nicknames, types, addresses, and supported chains.',\n description:\n 'Shows all available wallets from evm-wallet-skill (~/.evm-wallet.json), Bankr API, ' +\n 'and environment variables (AMPED_OC_WALLETS_JSON). Each wallet has a nickname that can be ' +\n 'used in operations like \"swap 100 USDC using bankr\" or \"check balance on main\". ' +\n 'Also shows which chains each wallet supports.',\n schema: ListWalletsSchema,\n handler: wrapHandler(handleListWallets),\n });\n}\n\n// Export schemas for testing and reuse\nexport {\n SupportedChainsSchema,\n SupportedTokensSchema,\n WalletAddressSchema,\n MoneyMarketPositionsSchema,\n MoneyMarketReservesSchema,\n CrossChainPositionsSchema,\n UserIntentsSchema,\n ListWalletsSchema,\n};\n\n// Export handlers for testing\nexport {\n handleSupportedChains,\n handleSupportedTokens,\n handleWalletAddress,\n handleMoneyMarketPositions,\n handleMoneyMarketReserves,\n handleCrossChainPositions,\n handleUserIntents,\n handleListWallets,\n};\n",
1214 "inputSchema": {},
1215 "outputSchema": null,
1216 "icons": null,
1217 "annotations": null,
1218 "meta": null,
1219 "execution": null
1220 },
1221 {
1222 "name": "moneyMarket.ts",
1223 "title": null,
1224 "description": "Script: moneyMarket.ts. Code:\n/**\n * Money Market Tools for Amped DeFi Plugin\n * \n * Provides advanced supply, withdraw, borrow, and repay operations for the SODAX money market.\n * Supports both same-chain and cross-chain operations (e.g., supply on Chain A, borrow to Chain B).\n * \n * Key capabilities:\n * - Supply: Deposit tokens as collateral on any supported chain\n * - Borrow: Borrow tokens to any chain (cross-chain capable)\n * - Withdraw: Withdraw supplied tokens from any chain\n * - Repay: Repay borrowed tokens from any chain\n * - Intent-based operations: Create intents for custom flows\n * \n * Cross-chain flows:\n * 1. Supply on Chain A \u2192 Borrow to Chain B (different destination)\n * 2. Supply on Chain A \u2192 Borrow on Chain A (same chain)\n * 3. Cross-chain repay: Repay debt from any chain\n * 4. Cross-chain withdraw: Withdraw collateral to any chain\n */\n\nimport { Type, Static } from \"@sinclair/typebox\";\nimport { getSodaxClient } from \"../sodax/client\";\nimport { getSpokeProvider } from \"../providers/spokeProviderFactory\";\nimport { PolicyEngine } from \"../policy/policyEngine\";\nimport { getWalletManager } from '../wallet/walletManager';\nimport { AgentTools } from \"../types\";\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Base schema for money market operations\n */\nconst MoneyMarketBaseSchema = Type.Object({\n walletId: Type.String({ \n description: \"Unique identifier for the wallet\" \n }),\n chainId: Type.String({ \n description: \"Source SODAX spoke chain ID where the operation originates (e.g., 'ethereum', 'arbitrum', 'sonic')\" \n }),\n token: Type.String({\n description: \"Token address or symbol to supply/borrow/withdraw/repay\",\n }),\n amount: Type.String({\n description: \"Amount in human-readable units (e.g., '100.5' for 100.5 USDC). Use '-1' for max repay (repay full debt).\",\n }),\n timeoutMs: Type.Optional(\n Type.Number({\n description: \"Operation timeout in milliseconds\",\n default: 180000,\n })\n ),\n policyId: Type.Optional(\n Type.String({ description: \"Optional policy profile identifier for custom limits\" })\n ),\n skipSimulation: Type.Optional(\n Type.Boolean({\n description: \"Skip transaction simulation (not recommended)\",\n default: false,\n })\n ),\n});\n\n/**\n * Supply operation schema\n * Supply tokens as collateral to the money market on the specified chain\n */\nconst MoneyMarketSupplySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n useAsCollateral: Type.Optional(\n Type.Boolean({\n description: \"Whether to use the supplied tokens as collateral for borrowing (default: true)\",\n default: true,\n })\n ),\n // Cross-chain supply options\n dstChainId: Type.Optional(\n Type.String({\n description: \"Optional destination chain for the supply operation. If different from chainId, performs cross-chain supply.\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address for the supplied position (defaults to wallet address)\",\n })\n ),\n }),\n]);\n\n/**\n * Withdraw operation schema\n * Withdraw supplied tokens from the money market\n */\nconst MoneyMarketWithdrawSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n withdrawType: Type.Optional(\n Type.Union([\n Type.Literal('default'),\n Type.Literal('collateral'),\n Type.Literal('all'),\n ], {\n description: \"Withdraw type: 'default' (standard), 'collateral' (withdraw collateral only), 'all' (withdraw maximum)\",\n default: 'default',\n })\n ),\n // Cross-chain withdraw options\n dstChainId: Type.Optional(\n Type.String({\n description: \"Optional destination chain to receive withdrawn tokens. If different from chainId, performs cross-chain withdraw.\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address to receive withdrawn tokens (defaults to wallet address)\",\n })\n ),\n }),\n]);\n\n/**\n * Borrow operation schema\n * Borrow tokens from the money market\n * \n * Key feature: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum, borrow USDT to Arbitrum\n */\nconst MoneyMarketBorrowSchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(\n Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate (recommended)\" }),\n ], {\n description: \"Interest rate mode: 1 = Stable, 2 = Variable\",\n default: 2,\n })\n ),\n referralCode: Type.Optional(\n Type.String({\n description: \"Optional referral code for the borrow operation\",\n })\n ),\n // Cross-chain borrow options (key feature!)\n dstChainId: Type.Optional(\n Type.String({\n description: \"Destination chain to receive borrowed tokens. If different from chainId, performs cross-chain borrow (supply on chainId, receive borrowed tokens on dstChainId).\",\n })\n ),\n recipient: Type.Optional(\n Type.String({\n description: \"Optional recipient address to receive borrowed tokens (defaults to wallet address on dstChainId or chainId)\",\n })\n ),\n }),\n]);\n\n/**\n * Repay operation schema\n * Repay borrowed tokens to the money market\n */\nconst MoneyMarketRepaySchema = Type.Composite([\n MoneyMarketBaseSchema,\n Type.Object({\n interestRateMode: Type.Optional(\n Type.Union([\n Type.Literal(1, { description: \"Stable interest rate\" }),\n Type.Literal(2, { description: \"Variable interest rate\" }),\n ], {\n description: \"Interest rate mode of the debt to repay: 1 = Stable, 2 = Variable\",\n default: 2,\n })\n ),\n repayAll: Type.Optional(\n Type.Boolean({\n description: \"If true, repays the full debt amount (useful for closing position)\",\n default: false,\n })\n ),\n // Cross-chain repay options\n collateralChainId: Type.Optional(\n Type.String({\n description: \"Optional chain ID where collateral is held (for cross-chain repay scenarios)\",\n })\n ),\n }),\n]);\n\n/**\n * Create Intent schemas for advanced users\n * These allow building custom multi-step flows\n */\nconst CreateSupplyIntentSchema = Type.Composite([\n MoneyMarketSupplySchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateBorrowIntentSchema = Type.Composite([\n MoneyMarketBorrowSchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateWithdrawIntentSchema = Type.Composite([\n MoneyMarketWithdrawSchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\nconst CreateRepayIntentSchema = Type.Composite([\n MoneyMarketRepaySchema,\n Type.Object({\n raw: Type.Optional(\n Type.Boolean({\n description: \"If true, returns raw transaction data instead of executing (for custom signing flows)\",\n default: false,\n })\n ),\n }),\n]);\n\n// ============================================================================\n// Output Types\n// ============================================================================\n\ninterface MoneyMarketOperationResult {\n success: boolean;\n txHash?: string;\n status: \"success\" | \"pending\" | \"failed\";\n spokeTxHash?: string;\n hubTxHash?: string;\n intentHash?: string;\n operation: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n message?: string;\n warnings?: string[];\n // Cross-chain specific\n isCrossChain?: boolean;\n srcSpokeTxHash?: string;\n dstSpokeTxHash?: string;\n // Raw intent data (for createIntent operations)\n rawIntent?: unknown;\n}\n\ninterface IntentResult extends MoneyMarketOperationResult {\n intentData: unknown;\n requiresSubmission: boolean;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Converts human-readable amount to token units (wei)\n */\nfunction parseTokenAmount(amount: string, decimals: number = 18): bigint {\n // Handle special case for max repay\n if (amount === '-1') {\n return BigInt(-1);\n }\n \n const parsed = parseFloat(amount);\n if (isNaN(parsed)) {\n throw new Error(`Invalid amount: ${amount}`);\n }\n \n const multiplier = Math.pow(10, decimals);\n return BigInt(Math.floor(parsed * multiplier));\n}\n\n/**\n * Resolves wallet and creates spoke provider for the operation\n */\nasync function resolveWalletAndProvider(\n walletId: string,\n chainId: string\n): Promise<{\n walletAddress: string;\n spokeProvider: any;\n}> {\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n const walletAddress = await wallet.getAddress();\n\n const spokeProvider = await getSpokeProvider(walletId, chainId);\n\n return { walletAddress, spokeProvider };\n}\n\n/**\n * Common pre-operation checks and allowance handling\n */\nasync function prepareMoneyMarketOperation(\n walletId: string,\n chainId: string,\n token: string,\n amount: string,\n operation: \"supply\" | \"withdraw\" | \"borrow\" | \"repay\",\n policyId?: string\n): Promise<{ walletAddress: string; spokeProvider: any; policyResult: any; tokenAddr: string }> {\n // ============================================================================\n // Hub Chain Validation\n // ============================================================================\n // SODAX architecture: Money market operations must be initiated from spoke chains,\n // not the hub chain (Sonic). The hub chain is the settlement layer where contracts\n // live, but users interact via spoke chains that relay operations to the hub.\n // Reference: sodax-tests/tests/crossChainSdk.test.ts explicitly omits SONIC_MAINNET_CHAIN_ID\n const isHubChainSource = chainId.toLowerCase() === 'sonic' || chainId === '146';\n if (isHubChainSource) {\n throw new Error(\n `Money market operations cannot be initiated from the hub chain (Sonic). ` +\n `Please use a spoke chain (base, arbitrum, ethereum, optimism, avalanche, bsc, polygon) as the source chain.`\n );\n }\n // Ensure sodax client is initialized\n const _sodaxClient = getSodaxClient(); // Just verify it's ready\n void _sodaxClient;\n\n // Normalize chain ID to SDK format for token resolution\n const sdkChainId = toSodaxChainId(chainId);\n\n // Resolve token symbol to address\n const tokenAddr = await resolveToken(sdkChainId, token);\n\n // Resolve wallet and create spoke provider\n const { walletAddress, spokeProvider } = await resolveWalletAndProvider(walletId, chainId);\n\n // Policy check\n const policyEngine = new PolicyEngine();\n const policyResult = await policyEngine.checkMoneyMarket({\n walletId,\n chainId,\n token,\n amount, // Add required amount parameter\n amountUsd: parseFloat(amount), // Simplified - would need actual price lookup\n operation,\n policyId,\n });\n\n if (!policyResult.allowed) {\n throw new Error(\n `Policy check failed: ${policyResult.reason || \"Operation not permitted\"}.`\n );\n }\n\n return { walletAddress, spokeProvider, policyResult, tokenAddr };\n}\n/**\n * Resolves token and returns its decimals\n * Falls back to 18 decimals if token info not found\n */\nasync function getTokenDecimals(\n chainId: string,\n token: string\n): Promise<number> {\n try {\n const sdkChainId = toSodaxChainId(chainId);\n const tokenInfo = await getTokenInfo(sdkChainId, token);\n return tokenInfo?.decimals ?? 18;\n } catch {\n // If token info lookup fails, fall back to 18 decimals\n return 18;\n }\n}\n\n\n/**\n * Checks and handles token approval if needed\n */\nasync function ensureAllowance(\n params: {\n token: string;\n amount: bigint;\n action: 'supply' | 'repay';\n },\n spokeProvider: any,\n raw: boolean = false\n): Promise<{ approvalTxHash?: string; rawApproval?: unknown }> {\n const sodaxClient = await getSodaxClient();\n\n // Check if allowance is sufficient\n const isAllowanceValid = await sodaxClient.moneyMarket.isAllowanceValid(\n params,\n spokeProvider\n );\n\n if (!isAllowanceValid.ok || !isAllowanceValid.value) {\n if (raw) {\n // Return raw approval transaction\n const rawApproval = await sodaxClient.moneyMarket.approve(\n params,\n spokeProvider,\n true // raw mode\n );\n return { rawApproval };\n } else {\n // Execute approval transaction\n const approvalResult = await sodaxClient.moneyMarket.approve(\n params,\n spokeProvider,\n false\n );\n // Handle Result type from SDK\n const txHash = (approvalResult as any).ok \n ? (approvalResult as any).value \n : (approvalResult as any).txHash || approvalResult;\n return { approvalTxHash: String(txHash) };\n }\n }\n\n return {};\n}\n\n/**\n * Determine if operation is cross-chain\n */\nfunction isCrossChainOperation(srcChainId: string, dstChainId?: string): boolean {\n return !!dstChainId && dstChainId !== srcChainId;\n}\n\n// ============================================================================\n// Tool Handlers\n// ============================================================================\n\n/**\n * Supply tokens to the money market\n * \n * Supports cross-chain supply: supply tokens on chainId, collateral is recorded on dstChainId (if different)\n */\nasync function handleSupply(\n params: Static<typeof MoneyMarketSupplySchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n useAsCollateral = true,\n dstChainId,\n recipient,\n skipSimulation = false \n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"supply\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n // Check allowance for supply\n const { approvalTxHash } = await ensureAllowance(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n\n const sodaxClient = await getSodaxClient();\n\n // Build supply parameters\n const supplyParams: any = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain supply: tokens supplied on ${chainId}, collateral recorded on ${dstChainId}`);\n }\n\n // Check and handle allowance (required for ALL supply operations)\n // Reference: sodax-frontend moneymarket-ops.ts - supply ALWAYS checks allowance\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:supply] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'supply' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:supply] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:supply] Allowance check failed, proceeding anyway:', allowanceError);\n }\n\n // Execute supply\n const supplyResult = await (sodaxClient as any).moneyMarket.supply(\n supplyParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (supplyResult.ok === false) {\n throw new Error(`Supply failed: ${serializeError(supplyResult.error)}`);\n }\n \n const value = supplyResult.ok ? supplyResult.value : supplyResult;\n // SDK may return [spokeTxHash, hubTxHash] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain \n ? `Successfully supplied ${amount} ${token} on ${chainId}. Collateral available on ${dstChainId || chainId}.`\n : `Successfully supplied ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during supply\";\n return {\n success: false,\n status: \"failed\",\n operation: \"supply\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market supply failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Withdraw tokens from the money market\n * \n * Supports cross-chain withdraw: withdraw collateral from chainId, receive tokens on dstChainId\n */\nasync function handleWithdraw(\n params: Static<typeof MoneyMarketWithdrawSchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n withdrawType = 'default',\n dstChainId,\n recipient,\n skipSimulation = false\n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"withdraw\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n const sodaxClient = await getSodaxClient();\n\n // Build withdraw parameters\n const withdrawParams: any = {\n action: 'withdraw',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && dstChainId) {\n withdrawParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain withdraw: withdrawing from ${chainId}, receiving tokens on ${dstChainId}`);\n }\n\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'withdraw' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:withdraw] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'withdraw' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:withdraw] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:withdraw] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n\n // Execute withdraw\n const withdrawResult = await (sodaxClient as any).moneyMarket.withdraw(\n withdrawParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (withdrawResult.ok === false) {\n throw new Error(`Withdraw failed: ${serializeError(withdrawResult.error)}`);\n }\n \n const value = withdrawResult.ok ? withdrawResult.value : withdrawResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully withdrew ${amount} ${token} from ${chainId} to ${dstChainId}`\n : `Successfully withdrew ${amount} ${token} from money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during withdraw\";\n return {\n success: false,\n status: \"failed\",\n operation: \"withdraw\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market withdraw failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Borrow tokens from the money market\n * \n * KEY FEATURE: Can borrow to a DIFFERENT chain than where collateral is supplied!\n * Example: Supply USDC on Ethereum (chainId), borrow USDT to Arbitrum (dstChainId)\n * \n * This is a powerful cross-chain DeFi primitive that allows:\n * 1. Accessing liquidity without moving collateral\n * 2. Arbitraging interest rates across chains\n * 3. Efficient capital utilization across the entire SODAX network\n */\nasync function handleBorrow(\n params: Static<typeof MoneyMarketBorrowSchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n interestRateMode = 2,\n referralCode,\n dstChainId, // KEY: This can be different from chainId for cross-chain borrow!\n recipient,\n skipSimulation = false\n } = params;\n\n const crossChain = isCrossChainOperation(chainId, dstChainId);\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"borrow\",\n policyId\n );\n\n // Parse amount with actual token decimals\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n\n // Get user's positions to check health factor (best practice)\n const sodaxClient = await getSodaxClient();\n \n // For cross-chain borrow, resolve token on DESTINATION chain\n // SDK expects: getMoneyMarketToken(toChainId, params.token)\n // So params.token must be the destination chain's token address\n let borrowTokenAddr = tokenAddr;\n if (crossChain && dstChainId) {\n borrowTokenAddr = await resolveToken(dstChainId, token);\n console.log('[mm:borrow] Cross-chain: resolved token on destination chain', {\n srcChain: chainId,\n dstChain: dstChainId,\n srcTokenAddr: tokenAddr,\n dstTokenAddr: borrowTokenAddr,\n });\n }\n\n // Build borrow parameters\n const borrowParams: any = {\n action: 'borrow',\n token: borrowTokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n // KEY CROSS-CHAIN FEATURE:\n // If dstChainId is provided and different from chainId, the borrowed tokens\n // will be delivered to dstChainId instead of chainId where the borrow is initiated\n if (crossChain && dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n warnings.push(`Cross-chain borrow: Using collateral on ${chainId}, receiving borrowed tokens on ${dstChainId}`);\n warnings.push(`Ensure you have sufficient collateral on ${chainId} to support this borrow`);\n }\n\n // Check and handle allowance (required for hub chain operations)\n // Reference: sodax-frontend moneymarket-ops.ts\n const isHubChain = chainId === 'sonic' || chainId === '146';\n if (isHubChain) {\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:borrow] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: borrowTokenAddr, amount: amountBigInt, action: 'borrow' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:borrow] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:borrow] Allowance check failed, proceeding anyway:', allowanceError);\n }\n }\n\n // Execute borrow\n const borrowResult = await (sodaxClient as any).moneyMarket.borrow(\n borrowParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (borrowResult.ok === false) {\n throw new Error(`Borrow failed: ${serializeError(borrowResult.error)}`);\n }\n \n const value = borrowResult.ok ? borrowResult.value : borrowResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: crossChain\n ? `Successfully borrowed ${amount} ${token} on ${dstChainId} using collateral from ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`\n : `Successfully borrowed ${amount} ${token} from money market on ${chainId}. Interest rate mode: ${interestRateMode === 1 ? 'Stable' : 'Variable'}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during borrow\";\n return {\n success: false,\n status: \"failed\",\n operation: \"borrow\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n isCrossChain: crossChain,\n message: `Money market borrow failed: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Repay borrowed tokens to the money market\n * \n * Supports cross-chain repay: repay debt using tokens from a different chain\n */\nasync function handleRepay(\n params: Static<typeof MoneyMarketRepaySchema>\n): Promise<MoneyMarketOperationResult> {\n const { \n walletId, \n chainId, \n token, \n amount, \n timeoutMs = 180000, \n policyId,\n interestRateMode = 2,\n repayAll = false,\n collateralChainId,\n skipSimulation = false\n } = params;\n\n const crossChain = !!collateralChainId && collateralChainId !== chainId;\n const warnings: string[] = [];\n\n try {\n // Pre-operation checks\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId,\n chainId,\n token,\n amount,\n \"repay\",\n policyId\n );\n\n // Parse amount with actual token decimals (use -1 for max repay if repayAll is true)\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = repayAll ? BigInt(-1) : parseTokenAmount(amount, decimals);\n\n // Check allowance for repay\n const { approvalTxHash } = await ensureAllowance(\n { token: tokenAddr, amount: amountBigInt === BigInt(-1) ? BigInt(0) : amountBigInt, action: 'repay' },\n spokeProvider\n );\n\n if (approvalTxHash) {\n warnings.push(`Approval transaction executed: ${approvalTxHash}`);\n }\n\n const sodaxClient = await getSodaxClient();\n\n // Build repay parameters\n const repayParams: any = {\n action: 'repay',\n token: tokenAddr,\n amount: amountBigInt,\n \n };\n\n // Add cross-chain parameters if applicable\n if (crossChain && collateralChainId) {\n repayParams.toChainId = collateralChainId;\n warnings.push(`Cross-chain repay: Repaying debt on ${collateralChainId} using tokens from ${chainId}`);\n }\n\n // Check and handle allowance (required for ALL repay operations)\n // Reference: sodax-frontend moneymarket-ops.ts - repay ALWAYS checks allowance\n try {\n const allowanceResult = await (sodaxClient as any).moneyMarket.isAllowanceValid(\n { token: tokenAddr, amount: amountBigInt, action: 'repay' },\n spokeProvider\n );\n \n if (allowanceResult.ok && !allowanceResult.value) {\n console.log('[mm:repay] Approval needed, approving...');\n const approveResult = await (sodaxClient as any).moneyMarket.approve(\n { token: tokenAddr, amount: amountBigInt, action: 'repay' },\n spokeProvider\n );\n \n if (!approveResult.ok) {\n throw new Error(`Approval failed: ${serializeError(approveResult.error)}`);\n }\n \n // Wait for approval confirmation\n const approvalTxHash = approveResult.value;\n if (approvalTxHash && spokeProvider.walletProvider?.waitForTransactionReceipt) {\n await spokeProvider.walletProvider.waitForTransactionReceipt(approvalTxHash);\n console.log('[mm:repay] Approval confirmed');\n }\n }\n } catch (allowanceError) {\n console.warn('[mm:repay] Allowance check failed, proceeding anyway:', allowanceError);\n }\n\n // Execute repay\n const repayResult = await (sodaxClient as any).moneyMarket.repay(\n repayParams,\n spokeProvider,\n timeoutMs\n );\n\n // Handle Result type from SDK\n if (repayResult.ok === false) {\n throw new Error(`Repay failed: ${serializeError(repayResult.error)}`);\n }\n \n const value = repayResult.ok ? repayResult.value : repayResult;\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract tx hashes from deliveryInfo (SDK returns 3-element tuple)\n const spokeTxHash = deliveryInfo?.srcTxHash || (solverResponse as any)?.txHash || solverResponse;\n const hubTxHash = deliveryInfo?.hubTxHash || (intent as any)?.hubTxHash;\n const dstTxHash = deliveryInfo?.dstTxHash;\n\n return {\n success: true,\n txHash: String(spokeTxHash),\n status: \"success\",\n spokeTxHash: String(spokeTxHash),\n hubTxHash: hubTxHash ? String(hubTxHash) : undefined,\n intentHash: undefined,\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: repayAll\n ? `Successfully repaid full debt for ${token} on ${chainId}`\n : `Successfully repaid ${amount} ${token} to money market on ${chainId}`,\n warnings: warnings.length > 0 ? warnings : undefined,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error during repay\";\n return {\n success: false,\n status: \"failed\",\n operation: \"repay\",\n chainId,\n token,\n amount: repayAll ? \"max (full debt)\" : amount,\n isCrossChain: crossChain,\n message: `Money market repay failed: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Intent Creation Handlers (Advanced)\n// ============================================================================\n\n/**\n * Create a supply intent without executing (for custom flows)\n */\nasync function handleCreateSupplyIntent(\n params: Static<typeof CreateSupplyIntentSchema>\n): Promise<IntentResult> {\n const { walletId, chainId, token, amount, useAsCollateral = true, dstChainId, recipient, raw = true } = params;\n\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId, chainId, token, amount, \"supply\"\n );\n\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n\n const supplyParams: any = {\n action: 'supply',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n if (dstChainId) {\n supplyParams.toChainId = toSodaxChainId(dstChainId);\n }\n\n const intentData = await sodaxClient.moneyMarket.createSupplyIntent(\n supplyParams,\n spokeProvider,\n raw\n );\n\n return {\n success: true,\n status: \"pending\",\n operation: \"createSupplyIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: \"Supply intent created. Submit this intent to execute the supply operation.\",\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create supply intent failed: ${errorMessage}`);\n }\n}\n\n/**\n * Create a borrow intent without executing (for custom flows)\n */\nasync function handleCreateBorrowIntent(\n params: Static<typeof CreateBorrowIntentSchema>\n): Promise<IntentResult> {\n const { walletId, chainId, token, amount, interestRateMode = 2, dstChainId, recipient, raw = true } = params;\n\n try {\n const { walletAddress, spokeProvider, tokenAddr } = await prepareMoneyMarketOperation(\n walletId, chainId, token, amount, \"borrow\"\n );\n\n const decimals = await getTokenDecimals(chainId, token);\n const amountBigInt = parseTokenAmount(amount, decimals);\n const sodaxClient = await getSodaxClient();\n\n const borrowParams: any = {\n action: 'borrow',\n token: tokenAddr,\n amount: amountBigInt,\n \n toAddress: recipient || walletAddress,\n };\n\n if (dstChainId) {\n borrowParams.toChainId = toSodaxChainId(dstChainId);\n }\n\n const intentData = await sodaxClient.moneyMarket.createBorrowIntent(\n borrowParams,\n spokeProvider,\n raw\n );\n\n return {\n success: true,\n status: \"pending\",\n operation: \"createBorrowIntent\",\n chainId,\n dstChainId: dstChainId || chainId,\n token,\n amount,\n intentData,\n requiresSubmission: true,\n message: dstChainId && dstChainId !== chainId\n ? `Cross-chain borrow intent created. Collateral on ${chainId}, borrowed tokens to ${dstChainId}. Submit this intent to execute.`\n : \"Borrow intent created. Submit this intent to execute the borrow operation.\",\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n throw new Error(`Create borrow intent failed: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Registers all money market tools with the agent tools registry\n */\nexport function registerMoneyMarketTools(agentTools: AgentTools): void {\n // Supply\n agentTools.register({\n name: \"amped_mm_supply\",\n summary: \"Supply tokens as collateral to the SODAX money market. Supports same-chain and cross-chain supply (supply on chain A, collateral available on chain B).\",\n schema: MoneyMarketSupplySchema,\n handler: handleSupply,\n });\n\n // Withdraw\n agentTools.register({\n name: \"amped_mm_withdraw\",\n summary: \"Withdraw supplied tokens from the SODAX money market. Supports cross-chain withdraw (withdraw from chain A, receive tokens on chain B).\",\n schema: MoneyMarketWithdrawSchema,\n handler: handleWithdraw,\n });\n\n // Borrow\n agentTools.register({\n name: \"amped_mm_borrow\",\n summary: \"Borrow tokens from the SODAX money market. KEY FEATURE: Can borrow to a different chain than collateral! Example: Supply on Ethereum, borrow to Arbitrum using dstChainId parameter.\",\n schema: MoneyMarketBorrowSchema,\n handler: handleBorrow,\n });\n\n // Repay\n agentTools.register({\n name: \"amped_mm_repay\",\n summary: \"Repay borrowed tokens to the SODAX money market. Use amount='-1' or repayAll=true to repay full debt. Supports cross-chain repay.\",\n schema: MoneyMarketRepaySchema,\n handler: handleRepay,\n });\n\n // Advanced: Create Intent variants for custom flows\n agentTools.register({\n name: \"amped_mm_create_supply_intent\",\n summary: \"[Advanced] Create a supply intent without executing. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateSupplyIntentSchema,\n handler: handleCreateSupplyIntent,\n });\n\n agentTools.register({\n name: \"amped_mm_create_borrow_intent\",\n summary: \"[Advanced] Create a borrow intent without executing. Supports cross-chain borrow intents. Returns raw intent data for custom signing or multi-step flows.\",\n schema: CreateBorrowIntentSchema,\n handler: handleCreateBorrowIntent,\n });\n}\n\n// ============================================================================\n// Re-exports for testing and direct usage\n// ============================================================================\n\nexport {\n MoneyMarketBaseSchema,\n MoneyMarketSupplySchema,\n MoneyMarketWithdrawSchema,\n MoneyMarketBorrowSchema,\n MoneyMarketRepaySchema,\n CreateSupplyIntentSchema,\n CreateWithdrawIntentSchema,\n CreateBorrowIntentSchema,\n CreateRepayIntentSchema,\n handleSupply,\n handleWithdraw,\n handleBorrow,\n handleRepay,\n handleCreateSupplyIntent,\n handleCreateBorrowIntent,\n};\n\n// Aliases for index.ts compatibility\nexport {\n MoneyMarketSupplySchema as MmSupplySchema,\n MoneyMarketWithdrawSchema as MmWithdrawSchema,\n MoneyMarketBorrowSchema as MmBorrowSchema,\n MoneyMarketRepaySchema as MmRepaySchema,\n handleSupply as handleMmSupply,\n handleWithdraw as handleMmWithdraw,\n handleBorrow as handleMmBorrow,\n handleRepay as handleMmRepay,\n};\n\nexport type { MoneyMarketOperationResult, IntentResult };\n",
1225 "inputSchema": {},
1226 "outputSchema": null,
1227 "icons": null,
1228 "annotations": null,
1229 "meta": null,
1230 "execution": null
1231 },
1232 {
1233 "name": "swap.ts",
1234 "title": null,
1235 "description": "Script: swap.ts. Code:\n/**\n * Swap Tools for Amped DeFi Plugin\n * \n * Provides OpenClaw tools for cross-chain swap operations using SODAX SDK:\n * - amped_swap_quote: Get exact-in/exact-out quotes\n * - amped_swap_execute: Execute swaps with policy enforcement\n * - amped_swap_status: Poll intent status\n * - amped_swap_cancel: Cancel active intents\n */\n\nimport { Static, Type } from '@sinclair/typebox';\nimport { serializeError } from '../utils/errorUtils';\n// SDK types - using any for now due to beta API changes\nimport { Intent } from '@sodax/sdk';\ntype QuoteRequest = any;\ntype SwapQuote = any;\ntype IntentStatus = any;\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport type { AgentTools } from '../types';\nimport { toSodaxChainId } from '../wallet/types';\nimport { getSodaxApiClient } from '../utils/sodaxApi';\n\n// ============================================================================\n// SODAX API & Explorer Links\n// ============================================================================\n\nconst SODAX_CANARY_API = 'https://canary-api.sodax.com/v1/be';\n\n// Chain ID to block explorer mapping\nconst CHAIN_EXPLORERS: Record<string, string> = {\n 'ethereum': 'https://etherscan.io/tx/',\n 'base': 'https://basescan.org/tx/',\n 'arbitrum': 'https://arbiscan.io/tx/',\n 'optimism': 'https://optimistic.etherscan.io/tx/',\n 'polygon': 'https://polygonscan.com/tx/',\n 'sonic': 'https://sonicscan.org/tx/',\n 'avalanche': 'https://snowtrace.io/tx/',\n 'bsc': 'https://bscscan.com/tx/',\n 'solana': 'https://solscan.io/tx/',\n};\n\nfunction getExplorerLink(chainId: string, txHash: string): string | undefined {\n // Normalize chain ID (remove 0x prefix and suffix if present)\n const normalizedChainId = chainId.replace(/^0x[\\\\da-f]+\\\\./, '').toLowerCase();\n const explorer = CHAIN_EXPLORERS[normalizedChainId];\n return explorer ? `${explorer}${txHash}` : undefined;\n}\n\nasync function fetchIntentFromSodax(intentHash: string): Promise<any> {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) return null;\n return await response.json();\n } catch {\n return null;\n }\n}\n\n/**\n * Ensure intent hash is in hex format (0x prefixed)\n */\nfunction toHexIntentHash(hash: unknown): string | undefined {\n if (!hash) return undefined;\n const str = String(hash);\n // Already hex format\n if (str.startsWith('0x')) return str;\n // Convert decimal BigInt string to hex\n try {\n return '0x' + BigInt(str).toString(16);\n } catch {\n return str; // Return as-is if conversion fails\n }\n}\n\nfunction getSodaxScanUrl(txHash: string): string {\n return `https://sodaxscan.com/messages/search?value=${txHash}`;\n}\n\nfunction getSodaxIntentApiUrl(intentHash: string): string {\n return `${SODAX_CANARY_API}/intent/${intentHash}`;\n}\n\n// SODAX internal chain ID to block explorer mapping\nconst SODAX_CHAIN_EXPLORERS: Record<number, string> = {\n 1: 'https://solscan.io/tx/', // Solana\n 30: 'https://basescan.org/tx/', // Base\n 146: 'https://sonicscan.org/tx/', // Sonic (hub)\n 42161: 'https://arbiscan.io/tx/', // Arbitrum\n 10: 'https://optimistic.etherscan.io/tx/', // Optimism\n 137: 'https://polygonscan.com/tx/', // Polygon\n 56: 'https://bscscan.com/tx/', // BSC\n 43114: 'https://snowtrace.io/tx/', // Avalanche\n};\n\n/**\n * Poll SODAX API until intent is delivered, then return delivery tx explorer link\n */\nasync function pollForDelivery(\n intentHash: string,\n timeoutMs: number = 60000,\n pollIntervalMs: number = 3000\n): Promise<{ delivered: boolean; deliveryTxHash?: string; deliveryExplorer?: string; dstChainId?: number }> {\n const startTime = Date.now();\n \n while (Date.now() - startTime < timeoutMs) {\n try {\n const response = await fetch(`${SODAX_CANARY_API}/intent/${intentHash}`);\n if (!response.ok) {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n continue;\n }\n \n const data: any = await response.json();\n \n // Check if intent is filled (closed)\n if (data.open === false && data.events?.length > 0) {\n const fillEvent = data.events.find((e: any) => e.eventType === 'intent-filled');\n if (fillEvent) {\n const dstChainId = data.intent?.dstChain;\n const explorer = SODAX_CHAIN_EXPLORERS[dstChainId] || '';\n \n return {\n delivered: true,\n deliveryTxHash: fillEvent.txHash,\n deliveryExplorer: explorer ? `${explorer}${fillEvent.txHash}` : undefined,\n dstChainId\n };\n }\n }\n \n await new Promise(r => setTimeout(r, pollIntervalMs));\n } catch {\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n }\n \n return { delivered: false };\n}\n\nimport { resolveToken, getTokenInfo } from '../utils/tokenResolver';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\nconst SwapTypeSchema = Type.Union([\n Type.Literal('exact_input'),\n Type.Literal('exact_output')\n]);\n\nconst SwapQuoteRequestSchema = Type.Object({\n walletId: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n type: SwapTypeSchema,\n slippageBps: Type.Number({ default: 50, minimum: 0, maximum: 10000 }),\n recipient: Type.Optional(Type.String({\n description: 'Recipient address on destination chain. For cross-chain swaps to Solana, provide a Solana base58 address. Defaults to wallet address if omitted.'\n }))\n});\n\n// Result schema for documentation (not used at runtime)\nconst _SwapQuoteResultSchema = Type.Object({\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n fees: Type.Object({\n solverFee: Type.String(),\n protocolFee: Type.Optional(Type.String()),\n partnerFee: Type.Optional(Type.String())\n }),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n});\nvoid _SwapQuoteResultSchema; // Suppress unused warning\n\nconst SwapExecuteParamsSchema = Type.Object({\n walletId: Type.String(),\n quote: Type.Object({\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n inputAmount: Type.String(),\n outputAmount: Type.String(),\n slippageBps: Type.Number(),\n deadline: Type.Number(),\n minOutputAmount: Type.Optional(Type.String()),\n maxInputAmount: Type.Optional(Type.String()),\n recipient: Type.Optional(Type.String())\n }),\n maxSlippageBps: Type.Optional(Type.Number({ minimum: 0, maximum: 10000 })),\n policyId: Type.Optional(Type.String()),\n skipSimulation: Type.Optional(Type.Boolean({ default: false })),\n timeoutMs: Type.Optional(Type.Number({ default: 120000 }))\n});\n\nconst SwapExecuteResultSchema = Type.Object({\n spokeTxHash: Type.String(),\n hubTxHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String()),\n status: Type.String(),\n message: Type.Optional(Type.String())\n});\n\nconst SwapStatusParamsSchema = Type.Object({\n txHash: Type.Optional(Type.String()),\n intentHash: Type.Optional(Type.String())\n});\n\nconst SwapStatusResultSchema = Type.Object({\n status: Type.String(),\n intentHash: Type.Optional(Type.String()),\n spokeTxHash: Type.Optional(Type.String()),\n hubTxHash: Type.Optional(Type.String()),\n filledAmount: Type.Optional(Type.String()),\n error: Type.Optional(Type.String()),\n createdAt: Type.Optional(Type.Number()),\n expiresAt: Type.Optional(Type.Number())\n});\n\nconst SwapCancelParamsSchema = Type.Object({\n walletId: Type.String(),\n intent: Type.Object({\n id: Type.String(),\n srcChainId: Type.String(),\n dstChainId: Type.String(),\n srcToken: Type.String(),\n dstToken: Type.String(),\n amount: Type.String(),\n deadline: Type.Number()\n }),\n srcChainId: Type.String()\n});\n\nconst SwapCancelResultSchema = Type.Object({\n success: Type.Boolean(),\n txHash: Type.Optional(Type.String()),\n message: Type.String()\n});\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\ntype SwapQuoteRequest = Static<typeof SwapQuoteRequestSchema>;\ntype SwapExecuteParams = Static<typeof SwapExecuteParamsSchema>;\ntype SwapStatusParams = Static<typeof SwapStatusParamsSchema>;\ntype SwapCancelParams = Static<typeof SwapCancelParamsSchema>;\n\n// ============================================================================\n// Swap Quote Tool\n// ============================================================================\n\nasync function handleSwapQuote(params: SwapQuoteRequest): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.srcChainId, params.srcToken);\n const dstTokenAddr = await resolveToken(params.dstChainId, params.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.dstChainId, dstTokenAddr);\n \n // Get token config to determine decimals for amount conversion\n const configService = (sodaxClient as any).configService;\n const decimals = srcTokenInfo?.decimals ?? 18; // Default to 18 (most EVM tokens) if not found\n \n // Convert human-readable amount to raw amount (bigint)\n const amountFloat = parseFloat(params.amount);\n const rawAmount = BigInt(Math.floor(amountFloat * Math.pow(10, decimals)));\n \n // Build SDK-compatible request with snake_case parameters\n const quoteRequest = {\n token_src: srcTokenAddr,\n token_src_blockchain_id: toSodaxChainId(params.srcChainId),\n token_dst: dstTokenAddr,\n token_dst_blockchain_id: toSodaxChainId(params.dstChainId),\n amount: rawAmount,\n quote_type: params.type\n };\n \n console.log('[swap_quote] SDK request:', JSON.stringify(quoteRequest, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n\n const quoteResult = await (sodaxClient as any).swaps.getQuote(quoteRequest);\n \n // Handle Result type from SDK\n if (quoteResult.ok === false) {\n const errorMsg = quoteResult.error instanceof Error \n ? quoteResult.error.message \n : typeof quoteResult.error === 'string' \n ? quoteResult.error \n : JSON.stringify(quoteResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Quote failed: ${errorMsg}`);\n }\n \n const quote = quoteResult.ok ? quoteResult.value : quoteResult;\n \n console.log('[swap_quote] SDK response:', JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n \n // Get output token decimals with multiple fallbacks\n // SDK returns quoted_amount as bigint - convert to human-readable string\n let dstDecimals = (quote as any).token_dst_decimals || (quote as any).tokenDstDecimals;\n \n if (!dstDecimals && dstTokenInfo) {\n dstDecimals = dstTokenInfo.decimals;\n }\n \n if (!dstDecimals) {\n // Hardcoded decimals for common stablecoins\n const KNOWN_DECIMALS: Record<string, number> = {\n usdc: 6, USDC: 6, usdt: 6, USDT: 6, sol: 9, SOL: 9,\n dai: 18, DAI: 18, bnusd: 18, bnUSD: 18\n };\n const tokenSymbol = params.dstToken.toUpperCase();\n dstDecimals = KNOWN_DECIMALS[tokenSymbol] || 18;\n console.warn(`[swap_quote] Using fallback decimals (${dstDecimals}) for token ${params.dstToken}`);\n }\n const quotedAmount = quote.quoted_amount || quote.quotedAmount || quote.outputAmount;\n const outputAmountStr = quotedAmount \n ? (Number(quotedAmount) / Math.pow(10, dstDecimals)).toString()\n : '0';\n \n // Normalize and return quote (SDK uses snake_case, we return camelCase)\n const result = {\n inputAmount: params.amount,\n outputAmount: outputAmountStr,\n srcToken: srcTokenAddr,\n dstToken: dstTokenAddr,\n srcChainId: params.srcChainId,\n dstChainId: params.dstChainId,\n slippageBps: params.slippageBps,\n deadline: quote.deadline || calculateDeadline(300), // 5 min default\n fees: {\n solverFee: quote.solver_fee || quote.fees?.solverFee || '0',\n protocolFee: quote.protocol_fee || quote.fees?.protocolFee,\n partnerFee: quote.partner_fee || quote.fees?.partnerFee\n },\n minOutputAmount: quote.min_output_amount || quote.minOutputAmount,\n maxInputAmount: quote.max_input_amount || quote.maxInputAmount,\n recipient: params.recipient, // Pass through for execute\n // Include raw SDK response for debugging\n _raw: JSON.parse(JSON.stringify(quote, (k, v) => typeof v === 'bigint' ? v.toString() : v))\n };\n \n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n tokenAddresses: [params.srcToken, params.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_quote',\n walletId: params.walletId,\n chainIds: [params.srcChainId, params.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to get swap quote: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Execute Tool\n// ============================================================================\n\nasync function handleSwapExecute(params: SwapExecuteParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n // 1. Initialize dependencies\n const policyEngine = new PolicyEngine();\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.quote.srcChainId, params.quote.srcToken);\n const dstTokenAddr = await resolveToken(params.quote.dstChainId, params.quote.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.quote.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.quote.dstChainId, dstTokenAddr);\n \n // 2. Resolve wallet\n const wallet = await walletManager.resolve(params.walletId);\n const walletAddress = await wallet.getAddress();\n \n // 3. Policy check\n const policyCheck = await policyEngine.checkSwap({\n walletId: params.walletId,\n srcChainId: params.quote.srcChainId,\n dstChainId: params.quote.dstChainId,\n srcToken: params.quote.srcToken,\n dstToken: params.quote.dstToken,\n inputAmount: params.quote.inputAmount,\n slippageBps: params.maxSlippageBps || params.quote.slippageBps,\n policyId: params.policyId\n });\n \n if (!policyCheck.allowed) {\n throw new Error(`Policy check failed: ${policyCheck.reason}`);\n }\n \n // 4. Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(\n params.walletId,\n params.quote.srcChainId\n );\n \n // 5. Convert amounts to bigint FIRST (needed for intentParams)\n const srcDecimals = srcTokenInfo?.decimals ?? 18;\n const dstDecimals = dstTokenInfo?.decimals ?? 18;\n const inputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.inputAmount) * Math.pow(10, srcDecimals)));\n const outputAmountRaw = BigInt(Math.floor(parseFloat(params.quote.outputAmount) * Math.pow(10, dstDecimals)));\n \n // Calculate minOutputAmount with slippage\n const slippageBps = params.maxSlippageBps || params.quote.slippageBps || 100;\n const minOutputAmountRaw = outputAmountRaw - (outputAmountRaw * BigInt(slippageBps) / 10000n);\n \n console.log(\"[swap_execute] Amount conversion:\", {\n inputAmount: params.quote.inputAmount,\n inputAmountRaw: inputAmountRaw.toString(),\n outputAmount: params.quote.outputAmount,\n outputAmountRaw: outputAmountRaw.toString(),\n minOutputAmountRaw: minOutputAmountRaw.toString(),\n srcDecimals,\n dstDecimals\n });\n \n // 6. Build intentParams (used for allowance check, approval, and swap)\n const intentParams = {\n srcAddress: walletAddress,\n dstAddress: params.quote.recipient || walletAddress,\n srcChain: toSodaxChainId(params.quote.srcChainId),\n dstChain: toSodaxChainId(params.quote.dstChainId),\n inputToken: srcTokenAddr,\n outputToken: dstTokenAddr,\n inputAmount: inputAmountRaw,\n minOutputAmount: minOutputAmountRaw,\n deadline: BigInt(params.quote.deadline),\n allowPartialFill: false,\n solver: \"0x0000000000000000000000000000000000000000\" as `0x${string}`,\n data: \"0x\" as `0x${string}`\n };\n \n // 7. Check allowance using SDK's expected API\n let allowanceValid = false;\n try {\n const allowanceResult = await (sodaxClient as any).swaps.isAllowanceValid({\n intentParams,\n spokeProvider\n });\n allowanceValid = allowanceResult?.ok ? allowanceResult.value : !!allowanceResult;\n } catch (e) {\n console.warn('[swap_execute] Allowance check failed, assuming approval needed:', e);\n allowanceValid = false;\n }\n \n // 8. Approve if needed using SDK's expected API\n if (!allowanceValid) {\n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n message: 'Token approval required'\n });\n \n const approvalResult = await (sodaxClient as any).swaps.approve({\n intentParams,\n spokeProvider\n });\n \n const approvalTx = approvalResult?.ok ? approvalResult.value : approvalResult;\n \n // Wait for approval confirmation if possible\n if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt && approvalTx) {\n await (spokeProvider as any).walletProvider.waitForTransactionReceipt(approvalTx);\n }\n \n logStructured({\n requestId,\n opType: 'swap_approve',\n walletId: params.walletId,\n chainId: params.quote.srcChainId,\n token: srcTokenAddr,\n approvalTx: String(approvalTx),\n success: true\n });\n }\n \n // 9. Execute swap\n const swapResult = await (sodaxClient as any).swaps.swap({\n intentParams,\n spokeProvider,\n skipSimulation: params.skipSimulation || false,\n timeout: params.timeoutMs || 120000\n });\n \n // Handle Result type from SDK\n if (swapResult.ok === false) {\n const errorMsg = swapResult.error instanceof Error \n ? swapResult.error.message \n : typeof swapResult.error === 'string' \n ? swapResult.error \n : JSON.stringify(swapResult.error, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2);\n throw new Error(`Swap failed: ${errorMsg}`);\n }\n \n const value = swapResult.ok ? swapResult.value : swapResult;\n \n // SDK may return [response, intent, deliveryInfo] tuple\n const [solverResponse, intent, deliveryInfo] = Array.isArray(value) ? value : [value, undefined, undefined];\n \n // Extract internal tracking info\n const srcTxHash = deliveryInfo?.srcTxHash;\n const intentHash = toHexIntentHash((solverResponse as any)?.intent_hash) || toHexIntentHash(intent?.intentId);\n \n // Poll for delivery confirmation (wait up to 60s)\n let deliveryResult: { delivered: boolean; deliveryExplorer?: string } = { delivered: false };\n if (intentHash) {\n console.log('[swap_execute] Waiting for delivery confirmation...');\n deliveryResult = await pollForDelivery(intentHash, 60000, 3000);\n }\n \n // Build user-friendly result\n const result = {\n status: deliveryResult.delivered ? 'delivered' : 'submitted',\n message: deliveryResult.delivered \n ? 'Swap completed! Funds delivered to destination.' \n : 'Swap submitted, awaiting cross-chain delivery...',\n // User-friendly tracking link\n sodaxScanUrl: srcTxHash ? getSodaxScanUrl(srcTxHash) : undefined,\n // Source chain: where user initiated the swap\n // Destination chain: where user RECEIVED funds\n initiationTx: srcTxHash ? getExplorerLink(params.quote.srcChainId, srcTxHash) : undefined,\n receiptTx: deliveryResult.deliveryExplorer,\n };\n \n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n tokenAddresses: [params.quote.srcToken, params.quote.dstToken],\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_execute',\n walletId: params.walletId,\n chainIds: [params.quote.srcChainId, params.quote.dstChainId],\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Swap execution failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Status Tool\n// ============================================================================\n\n\nasync function handleSwapStatus(params: SwapStatusParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n if (!params.txHash && !params.intentHash) {\n throw new Error('Either txHash or intentHash must be provided');\n }\n \n // Use our SodaxApiClient for Backend API access (not SDK)\n const sodaxApi = getSodaxApiClient();\n \n let intentData: any = null;\n \n // Try intentHash first (most reliable)\n if (params.intentHash) {\n try {\n intentData = await sodaxApi.getIntentByHash(params.intentHash);\n } catch (err) {\n console.warn(`[swap_status] getIntentByHash failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n \n // If intentHash lookup failed or wasn't provided, try txHash\n // Note: txHash should be from the HUB chain (Sonic), not spoke chain\n if (!intentData && params.txHash) {\n try {\n intentData = await sodaxApi.getIntentByTxHash(params.txHash);\n } catch (err) {\n // txHash lookup failed - provide helpful error message\n const errorMsg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Unable to find intent for txHash: ${params.txHash}. ` +\n `Note: The txHash must be from the HUB chain (Sonic), not the spoke chain. ` +\n `If you have a spoke chain txHash (Base, Arbitrum, etc.), use amped_user_intents to find the intent first. ` +\n `Error: ${errorMsg}`\n );\n }\n }\n \n if (!intentData) {\n throw new Error('Unable to retrieve swap status. Provide a valid intentHash or hub chain txHash.');\n }\n \n // Extract intent details\n const intentHash = intentData.intentHash;\n const hubTxHash = intentData.txHash; // This is the hub chain tx that created the intent\n const isOpen = intentData.open;\n const intent = intentData.intent;\n \n // Determine status from intent state\n let status = 'unknown';\n if (intentData.open === true) {\n status = 'pending';\n } else if (intentData.open === false) {\n // Check events to determine if filled or cancelled/expired\n const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');\n const cancelledEvent = intentData.events?.find((e: any) => \n e.eventType === 'intent-cancelled' || e.eventType === 'intent-expired'\n );\n if (filledEvent) {\n status = 'filled';\n } else if (cancelledEvent) {\n status = cancelledEvent.eventType === 'intent-cancelled' ? 'cancelled' : 'expired';\n } else {\n status = 'closed';\n }\n }\n \n // Extract fulfillment details if available\n const filledEvent = intentData.events?.find((e: any) => e.eventType === 'intent-filled');\n const fulfillmentTxHash = filledEvent?.txHash;\n const receivedOutput = filledEvent?.intentState?.receivedOutput;\n \n // Build result\n const result: Record<string, unknown> = {\n status,\n intentHash,\n hubTxHash, // Hub chain tx that created the intent\n spokeTxHash: params.txHash !== hubTxHash ? params.txHash : undefined, // Original spoke tx if different\n open: isOpen,\n // Intent details\n srcChain: intent?.srcChain,\n dstChain: intent?.dstChain,\n inputToken: intent?.inputToken,\n outputToken: intent?.outputToken,\n inputAmount: intent?.inputAmount,\n minOutputAmount: intent?.minOutputAmount,\n receivedOutput: receivedOutput,\n deadline: intent?.deadline ? new Date(parseInt(intent.deadline) * 1000).toISOString() : undefined,\n createdAt: intentData.createdAt,\n // Fulfillment details\n fulfillmentTxHash,\n fulfillmentChain: intent?.dstChain,\n // Tracking links\n sodaxScanUrl: `https://sodaxscan.com/intents/${intentHash}`,\n };\n \n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash,\n txHash: params.txHash,\n status,\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_status',\n intentHash: params.intentHash,\n txHash: params.txHash,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to get swap status: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Swap Cancel Tool\n// ============================================================================\n\nasync function handleSwapCancel(params: SwapCancelParams): Promise<Record<string, unknown>> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n \n try {\n const walletManager = getWalletManager();\n const sodaxClient = getSodaxClient();\n \n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(params.intent.srcChainId, params.intent.srcToken);\n const dstTokenAddr = await resolveToken(params.intent.dstChainId, params.intent.dstToken);\n \n // Get token info for decimals\n const srcTokenInfo = await getTokenInfo(params.intent.srcChainId, srcTokenAddr);\n const dstTokenInfo = await getTokenInfo(params.intent.dstChainId, dstTokenAddr);\n \n // Resolve wallet (validates it exists)\n await walletManager.resolve(params.walletId);\n \n // Get spoke provider for source chain\n const spokeProvider = await getSpokeProvider(\n params.walletId,\n params.srcChainId\n );\n \n // Construct intent object for cancellation\n const intent = {\n id: params.intent.id,\n srcChainId: params.intent.srcChainId,\n dstChainId: params.intent.dstChainId,\n srcToken: params.intent.srcToken,\n dstToken: params.intent.dstToken,\n amount: params.intent.amount,\n deadline: BigInt(params.intent.deadline),\n createdAt: Date.now(),\n status: 'pending'\n } as unknown as Intent;\n \n // Cancel the intent - SDK expects (intent, spokeProvider)\n const cancelResult = await (sodaxClient as any).swaps.cancelIntent(intent, spokeProvider);\n \n // Handle Result type\n if (cancelResult.ok === false) {\n throw new Error(`Cancel failed: ${serializeError(cancelResult.error)}`);\n }\n \n const cancelTx = cancelResult.ok ? cancelResult.value : cancelResult;\n const cancelTxHash = typeof cancelTx === 'string' ? cancelTx : String(cancelTx);\n \n // Wait for cancellation confirmation if possible\n // SDK may expose waitForTransactionReceipt on the underlying wallet provider\n if ((spokeProvider as any).walletProvider?.waitForTransactionReceipt) {\n await (spokeProvider as any).walletProvider.waitForTransactionReceipt(cancelTxHash);\n }\n \n const result = {\n success: true,\n txHash: cancelTxHash,\n message: 'Intent cancelled successfully'\n };\n \n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n txHash: cancelTxHash,\n durationMs: Date.now() - startTime,\n success: true\n });\n \n return result;\n } catch (error) {\n logStructured({\n requestId,\n opType: 'swap_cancel',\n walletId: params.walletId,\n chainId: params.srcChainId,\n intentId: params.intent.id,\n durationMs: Date.now() - startTime,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n });\n \n throw new Error(`Failed to cancel swap: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\nfunction generateRequestId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\n\nfunction calculateDeadline(secondsFromNow: number): number {\n return Math.floor(Date.now() / 1000) + secondsFromNow;\n}\n\ninterface LogEntry {\n requestId: string;\n opType: string;\n walletId?: string;\n chainId?: string;\n chainIds?: string[];\n token?: string;\n tokenAddresses?: string[];\n intentHash?: string;\n txHash?: string;\n spokeTxHash?: string;\n hubTxHash?: string;\n approvalTx?: string;\n status?: string;\n durationMs?: number;\n success?: boolean;\n error?: string;\n message?: string;\n intentId?: string;\n}\n\nfunction logStructured(entry: LogEntry): void {\n // Structured JSON logging for observability\n // Use replacer to handle BigInt serialization\n console.log(JSON.stringify({\n ...entry,\n timestamp: new Date().toISOString(),\n component: 'amped-defi-swap'\n }, (k, v) => typeof v === 'bigint' ? v.toString() : v));\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\nexport function registerSwapTools(agentTools: AgentTools): void {\n // Register swap quote tool\n agentTools.register({\n name: 'amped_swap_quote',\n summary: 'Get a swap quote for exact-in or exact-out swaps across chains',\n description: 'Retrieves a quote for swapping tokens across chains using the SODAX swap protocol. ' +\n 'Supports both exact input (specify input amount, get output estimate) and ' +\n 'exact output (specify desired output, get required input) modes.',\n schema: SwapQuoteRequestSchema,\n handler: handleSwapQuote\n });\n \n // Register swap execute tool\n agentTools.register({\n name: 'amped_swap_execute',\n summary: 'Execute a swap with policy enforcement and allowance handling',\n description: 'Executes a swap using a previously obtained quote. ' +\n 'Performs policy checks, validates allowances, approves tokens if needed, ' +\n 'and executes the swap transaction. Returns transaction hashes and intent status.',\n schema: SwapExecuteParamsSchema,\n handler: handleSwapExecute\n });\n \n // Register swap status tool\n agentTools.register({\n name: 'amped_swap_status',\n summary: 'Check the status of a swap intent or transaction',\n description: 'Polls the status of a swap by intent hash or transaction hash. ' +\n 'Returns current status, fill amount, error details if failed, and timing information.',\n schema: SwapStatusParamsSchema,\n handler: handleSwapStatus\n });\n \n // Register swap cancel tool\n agentTools.register({\n name: 'amped_swap_cancel',\n summary: 'Cancel an active swap intent',\n description: 'Cancels a pending swap intent on the source chain. ' +\n 'Requires the intent details and source chain ID. Returns cancellation transaction hash.',\n schema: SwapCancelParamsSchema,\n handler: handleSwapCancel\n });\n}\n\n// Silence unused variable warnings for result schemas (used for documentation)\nvoid SwapExecuteResultSchema;\nvoid SwapStatusResultSchema;\nvoid SwapCancelResultSchema;\n\n// Export schemas with consistent naming\nexport {\n SwapQuoteRequestSchema as SwapQuoteSchema,\n SwapExecuteParamsSchema as SwapExecuteSchema,\n SwapStatusParamsSchema as SwapStatusSchema,\n SwapCancelParamsSchema as SwapCancelSchema,\n};\n\n// Export handlers\nexport {\n handleSwapQuote,\n handleSwapExecute,\n handleSwapStatus,\n handleSwapCancel,\n};\n",
1236 "inputSchema": {},
1237 "outputSchema": null,
1238 "icons": null,
1239 "annotations": null,
1240 "meta": null,
1241 "execution": null
1242 },
1243 {
1244 "name": "bridge.ts",
1245 "title": null,
1246 "description": "Script: bridge.ts. Code:\n/**\n * Bridge Tools for Amped DeFi Plugin\n *\n * NOTE: Bridge operations use the swap infrastructure internally.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Tools:\n * - amped_bridge_discover: Get bridgeable tokens for a route\n * - amped_bridge_quote: Check bridgeability and max amounts \n * - amped_bridge_execute: Execute bridge (delegates to swap)\n *\n * @module tools/bridge\n */\n\nimport { Static, Type } from '@sinclair/typebox';\nimport { AgentTools, BridgeOperation } from '../types';\nimport { getSodaxClient } from '../sodax/client';\nimport { getSpokeProvider } from '../providers/spokeProviderFactory';\nimport { PolicyEngine } from '../policy/policyEngine';\nimport { getWalletManager } from '../wallet/walletManager';\nimport { serializeError } from '../utils/errorUtils';\nimport { resolveToken } from '../utils/tokenResolver';\nimport { toSodaxChainId } from '../wallet/types';\nimport { handleSwapQuote, handleSwapExecute } from './swap';\n\n// ============================================================================\n// TypeBox Schemas\n// ============================================================================\n\n/**\n * Schema for amped_bridge_discover tool\n * Discover bridgeable tokens for a given source chain, destination chain, and source token\n */\nconst BridgeDiscoverSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID (e.g., \"ethereum\", \"arbitrum\")',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID (e.g., \"sonic\", \"optimism\")',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n});\n\n/**\n * Schema for amped_bridge_quote tool\n * Check if a bridge route is valid and get maximum bridgeable amount\n */\nconst BridgeQuoteSchema = Type.Object({\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol',\n }),\n});\n\n/**\n * Schema for amped_bridge_execute tool\n * Execute a bridge operation with full allowance check and approval flow\n */\nconst BridgeExecuteSchema = Type.Object({\n walletId: Type.String({\n description: 'Unique identifier for the wallet to use',\n }),\n srcChainId: Type.String({\n description: 'Source chain ID',\n }),\n dstChainId: Type.String({\n description: 'Destination chain ID',\n }),\n srcToken: Type.String({\n description: 'Source token address or symbol to bridge from',\n }),\n dstToken: Type.String({\n description: 'Destination token address or symbol to bridge to',\n }),\n amount: Type.String({\n description: 'Amount to bridge in human-readable units (e.g., \"100.5\")',\n }),\n recipient: Type.Optional(\n Type.String({\n description: 'Recipient address on destination chain (defaults to wallet address)',\n })\n ),\n timeoutMs: Type.Optional(\n Type.Number({\n description: 'Timeout for bridge operation in milliseconds',\n default: 300000, // 5 minutes\n })\n ),\n policyId: Type.Optional(\n Type.String({\n description: 'Optional policy profile ID for custom limits',\n })\n ),\n});\n\n// Type inference from schemas\ntype BridgeDiscoverParams = Static<typeof BridgeDiscoverSchema>;\ntype BridgeQuoteParams = Static<typeof BridgeQuoteSchema>;\ntype BridgeExecuteParams = Static<typeof BridgeExecuteSchema>;\n\n// ============================================================================\n// Bridge Discover Tool\n// ============================================================================\n\n/**\n * Transaction result type for bridge execute\n */\ninterface TransactionResult {\n spokeTxHash: string;\n hubTxHash?: string;\n}\n\n/**\n * Handler for amped_bridge_discover\n * Retrieves tokens that can be bridged from the source chain to destination chain\n *\n * @param params - Discovery parameters (srcChainId, dstChainId, srcToken)\n * @returns List of bridgeable tokens\n */\nasync function handleBridgeDiscover(\n params: BridgeDiscoverParams\n): Promise<{ bridgeableTokens: string[] }> {\n const { srcChainId, dstChainId, srcToken } = params;\n\n // Resolve token symbol to address\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n\n console.log('[bridge:discover] Discovering bridgeable tokens', {\n srcChainId,\n dstChainId,\n srcToken,\n });\n\n try {\n const sodax = getSodaxClient();\n\n // Get bridgeable tokens from SODAX SDK\n // SDK API: getBridgeableTokens(from: SpokeChainId, to: SpokeChainId, token: string)\n const result = sodax.bridge.getBridgeableTokens(\n toSodaxChainId(srcChainId) as any,\n toSodaxChainId(dstChainId) as any,\n srcTokenAddr\n );\n\n // Handle Result type - SDK returns Result<XToken[], unknown>\n if (!result.ok) {\n throw new Error(`Failed to get bridgeable tokens: ${serializeError((result as any).error) || 'Unknown error'}`);\n }\n\n const tokens = result.value;\n const bridgeableTokens = tokens.map((t: any) => t.address || t.symbol || String(t));\n\n console.log('[bridge:discover] Found bridgeable tokens', {\n count: bridgeableTokens.length,\n tokens: bridgeableTokens,\n });\n\n return { bridgeableTokens };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:discover] Failed to discover bridgeable tokens', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n });\n throw new Error(`Failed to discover bridgeable tokens: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Bridge Quote Tool\n// ============================================================================\n\n/**\n * Handler for amped_bridge_quote\n * Checks if a bridge route is valid and returns the maximum bridgeable amount\n *\n * @param params - Quote parameters (srcChainId, dstChainId, srcToken, dstToken)\n * @returns Bridgeability status and maximum amount\n */\nasync function handleBridgeQuote(\n params: BridgeQuoteParams\n): Promise<{ isBridgeable: boolean; maxBridgeableAmount: string }> {\n const { srcChainId, dstChainId, srcToken, dstToken } = params;\n\n // Resolve token symbols to addresses\n const srcTokenAddr = await resolveToken(srcChainId, srcToken);\n const dstTokenAddr = await resolveToken(dstChainId, dstToken);\n\n console.log('[bridge:quote] Checking bridge quote', {\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n\n try {\n const sodax = getSodaxClient();\n\n // Create XToken objects for the SDK\n const fromToken = { chainId: toSodaxChainId(srcChainId), address: srcTokenAddr } as any;\n const toToken = { chainId: toSodaxChainId(dstChainId), address: dstTokenAddr } as any;\n\n // Check if the route is bridgeable using isBridgeable\n // SDK may have different signature - adapting based on available methods\n let isBridgeable = false;\n try {\n // Try to get bridgeable tokens to check if route exists\n const result = sodax.bridge.getBridgeableTokens(\n toSodaxChainId(srcChainId) as any,\n toSodaxChainId(dstChainId) as any,\n srcTokenAddr\n );\n if (result.ok && result.value.length > 0) {\n isBridgeable = result.value.some((t: any) => \n t.address?.toLowerCase() === dstTokenAddr.toLowerCase() ||\n t === dstTokenAddr\n );\n }\n } catch {\n isBridgeable = false;\n }\n\n // Get maximum bridgeable amount\n let maxBridgeableAmount = '0';\n if (isBridgeable) {\n try {\n // SDK API: getBridgeableAmount(from: XToken, to: XToken)\n const maxAmountResult = await sodax.bridge.getBridgeableAmount(fromToken, toToken);\n if (maxAmountResult.ok) {\n const val = maxAmountResult.value as any;\n // BridgeLimit may have different property names depending on SDK version\n maxBridgeableAmount = val?.max?.toString() || \n val?.maxAmount?.toString() ||\n val?.limit?.toString() ||\n val?.toString() || '0';\n }\n } catch (e) {\n console.warn('[bridge:quote] Could not get max bridgeable amount:', e);\n }\n }\n\n console.log('[bridge:quote] Bridge quote result', {\n isBridgeable,\n maxBridgeableAmount,\n });\n\n return { isBridgeable, maxBridgeableAmount };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error('[bridge:quote] Failed to get bridge quote', {\n error: errorMessage,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n });\n throw new Error(`Failed to get bridge quote: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Bridge Execute Tool (Delegates to Swap)\n// ============================================================================\n\n/**\n * Handler for amped_bridge_execute\n *\n * NOTE: Bridge operations are implemented via swap infrastructure.\n * Cross-chain swaps and bridges are functionally equivalent in SODAX -\n * both use the intent-based cross-chain messaging system.\n *\n * Flow:\n * 1. Get swap quote for the bridge route\n * 2. Execute swap (handles allowance, approval, and execution)\n *\n * @param params - Execution parameters\n * @returns Transaction result with status and tracking links\n */\nasync function handleBridgeExecute(\n params: BridgeExecuteParams\n): Promise<TransactionResult> {\n const {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n recipient,\n timeoutMs = 300000,\n policyId,\n } = params;\n\n console.log('[bridge:execute] Delegating to swap infrastructure', {\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n });\n\n try {\n // Step 1: Get a swap quote for this bridge route\n const quoteResult = await handleSwapQuote({\n walletId,\n srcChainId,\n dstChainId,\n srcToken,\n dstToken,\n amount,\n type: 'exact_input',\n slippageBps: 100, // 1% slippage for bridges\n recipient,\n });\n\n console.log('[bridge:execute] Got swap quote', quoteResult);\n\n // Step 2: Execute the swap\n const swapResult = await handleSwapExecute({\n walletId,\n quote: {\n srcChainId,\n dstChainId,\n srcToken: String(quoteResult.srcToken),\n dstToken: String(quoteResult.dstToken),\n inputAmount: String(quoteResult.inputAmount),\n outputAmount: String(quoteResult.outputAmount),\n slippageBps: Number(quoteResult.slippageBps),\n deadline: Number(quoteResult.deadline),\n recipient,\n },\n policyId,\n timeoutMs,\n });\n\n console.log('[bridge:execute] Swap executed', swapResult);\n\n // Map swap result to bridge result format\n return {\n spokeTxHash: String(swapResult.initiationTx || swapResult.spokeTxHash || ''),\n hubTxHash: swapResult.hubTxHash ? String(swapResult.hubTxHash) : undefined,\n status: String(swapResult.status),\n message: swapResult.message ? String(swapResult.message) : 'Bridge executed via swap infrastructure',\n sodaxScanUrl: swapResult.sodaxScanUrl ? String(swapResult.sodaxScanUrl) : undefined,\n } as TransactionResult;\n } catch (error) {\n const errorMessage = serializeError(error);\n console.error('[bridge:execute] Bridge via swap failed:', errorMessage);\n throw new Error(`Bridge execution failed: ${errorMessage}`);\n }\n}\n\n// ============================================================================\n// Tool Registration\n// ============================================================================\n\n/**\n * Register all bridge tools with the agent tools registry\n *\n * @param agentTools - The agent tools registry\n */\nexport function registerBridgeTools(agentTools: AgentTools): void {\n // Register bridge discover tool\n agentTools.register({\n name: 'amped_bridge_discover',\n summary: 'Discover bridgeable tokens for a given source chain and token',\n description:\n 'Retrieves a list of tokens that can be bridged from the specified source chain ' +\n 'to the destination chain, starting from a specific source token. ' +\n 'Use this to find valid bridge routes before requesting a quote.',\n schema: BridgeDiscoverSchema,\n handler: handleBridgeDiscover,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_discover');\n\n // Register bridge quote tool\n agentTools.register({\n name: 'amped_bridge_quote',\n summary: 'Check bridgeability and get maximum bridgeable amount',\n description:\n 'Validates whether a specific bridge route (source chain/token \u2192 destination chain/token) ' +\n 'is supported and returns the maximum amount that can be bridged. ' +\n 'Always call this before executing a bridge to verify the route is valid.',\n schema: BridgeQuoteSchema,\n handler: handleBridgeQuote,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_quote');\n\n // Register bridge execute tool\n agentTools.register({\n name: 'amped_bridge_execute',\n summary: 'Execute a cross-chain bridge operation',\n description:\n 'Executes a bridge operation that moves tokens from a source chain to a destination chain. ' +\n 'This tool handles the complete flow: policy validation, allowance checking, ' +\n 'token approval (if needed), and bridge execution. ' +\n 'Returns transaction hashes for both the spoke chain and hub chain.',\n schema: BridgeExecuteSchema,\n handler: handleBridgeExecute,\n });\n\n console.log('[bridge] Registered tool: amped_bridge_execute');\n}\n\n// Export schemas for testing and reuse\nexport { BridgeDiscoverSchema, BridgeQuoteSchema, BridgeExecuteSchema };\n\n// Export handlers\nexport { handleBridgeDiscover, handleBridgeQuote, handleBridgeExecute };\n",
1247 "inputSchema": {},
1248 "outputSchema": null,
1249 "icons": null,
1250 "annotations": null,
1251 "meta": null,
1252 "execution": null
1253 },
1254 {
1255 "name": "spokeProviderFactory.ts",
1256 "title": null,
1257 "description": "Script: spokeProviderFactory.ts. Code:\n/**\n * Spoke Provider Factory\n *\n * Creates spoke providers for SODAX operations.\n * Supports both local key signing and Bankr API execution.\n * \n * Flow:\n * 1. Resolve wallet by nickname using WalletManager\n * 2. Check if wallet supports requested chain\n * 3. For local wallets: use SDK's EvmWalletProvider\n * 4. For Bankr wallets: use BankrWalletProvider (submits to Bankr API)\n */\n\n// Official SDK wallet provider\nimport { EvmWalletProvider } from '@sodax/wallet-sdk-core';\n\n// Import spoke providers and chain config from SDK\nimport { \n EvmSpokeProvider, \n SonicSpokeProvider,\n type SpokeProvider \n} from '@sodax/sdk';\n\n// Import chain configuration from types\nimport { spokeChainConfig, type SpokeChainId } from '@sodax/types';\n\n// Import wallet management\nimport { getWalletManager, type IWalletBackend, createBankrWalletProvider } from '../wallet';\nimport { getWalletAdapter } from '../wallet/skillWalletAdapter';\nimport { BANKR_CHAIN_IDS, normalizeChainId, getBankrChainId } from '../wallet/types';\n\n// Cache for providers: Map<cacheKey, SpokeProvider>\nconst providerCache = new Map<string, SpokeProvider>();\n\n// Sonic hub chain identifier\nconst SONIC_CHAIN_ID = 'sonic';\n\n// Chain ID mapping for SDK (some chains need specific format)\nconst CHAIN_ID_MAP: Record<string, SpokeChainId> = {\n 'sonic': 'sonic',\n 'ethereum': 'ethereum',\n 'arbitrum': '0xa4b1.arbitrum',\n 'optimism': '0xa.optimism',\n 'base': '0x2105.base',\n 'polygon': '0x89.polygon',\n 'bsc': '0x38.bsc',\n 'avalanche': '0xa86a.avax',\n 'lightlink': 'lightlink',\n 'hyperevm': 'hyper',\n 'hyper': 'hyper',\n} as Record<string, SpokeChainId>;\n\n/**\n * Get RPC URL for a chain\n */\nasync function getRpcUrl(chainId: string): Promise<string> {\n const skillAdapter = getWalletAdapter();\n return skillAdapter.getRpcUrl(chainId);\n}\n\n/**\n * Get the SDK chain ID for a given chain\n */\nfunction getSdkChainId(chainId: string): SpokeChainId {\n return (CHAIN_ID_MAP[chainId] || chainId) as SpokeChainId;\n}\n\n/**\n * Validate that wallet supports the requested chain\n */\nfunction validateChainSupport(wallet: IWalletBackend, chainId: string): void {\n const normalizedForWallet = normalizeChainId(chainId);\n if (!wallet.supportsChain(normalizedForWallet)) {\n throw new Error(\n `Wallet \"${wallet.nickname}\" doesn't support chain \"${chainId}\". ` +\n `Supported chains: ${wallet.supportedChains.join(', ')}. ` +\n `Try a different wallet.`\n );\n }\n}\n\n/**\n * Create a spoke provider for local key signing\n */\nasync function createLocalSpokeProvider(\n wallet: IWalletBackend,\n chainId: string,\n rpcUrl: string\n): Promise<SpokeProvider> {\n if (!wallet.getPrivateKey) {\n throw new Error(`Wallet \"${wallet.nickname}\" does not support local signing`);\n }\n\n const privateKey = await wallet.getPrivateKey();\n const sdkChainId = getSdkChainId(chainId);\n\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}. Available: ${Object.keys(spokeChainConfig).join(', ')}`);\n }\n\n // Create wallet provider using official SDK\n const walletProvider = new EvmWalletProvider({\n privateKey,\n chainId: sdkChainId,\n rpcUrl: rpcUrl as `http${string}`,\n });\n\n // Use SonicSpokeProvider for Sonic hub chain, EvmSpokeProvider for others\n if (chainId === SONIC_CHAIN_ID) {\n console.log('[spokeProviderFactory] Creating SonicSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n });\n\n return new SonicSpokeProvider(\n walletProvider,\n chainConfig as any,\n rpcUrl\n );\n } else {\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n });\n\n return new EvmSpokeProvider(\n walletProvider,\n chainConfig as any,\n rpcUrl\n );\n }\n}\n\n/**\n * Create a spoke provider for Bankr wallet\n * Uses BankrWalletProvider which submits transactions to Bankr API\n */\nasync function createBankrSpokeProvider(\n wallet: IWalletBackend,\n chainId: string,\n rpcUrl: string\n): Promise<SpokeProvider> {\n const sdkChainId = getSdkChainId(chainId);\n \n // Normalize chain ID for Bankr lookup (0x2105.base -> base)\n const normalizedChainId = normalizeChainId(chainId);\n const numericChainId = getBankrChainId(normalizedChainId);\n \n console.log('[spokeProviderFactory] Bankr chain resolution', {\n input: chainId,\n normalized: normalizedChainId,\n numeric: numericChainId,\n });\n\n // Get chain config from SDK\n const chainConfig = spokeChainConfig[sdkChainId];\n if (!chainConfig) {\n throw new Error(`Chain config not found for: ${sdkChainId}`);\n }\n\n // Get Bankr API key from environment\n const apiKey = process.env.BANKR_API_KEY;\n if (!apiKey) {\n throw new Error('BANKR_API_KEY environment variable not set');\n }\n\n // Get the Bankr wallet address (cached after first call)\n const walletAddress = await wallet.getAddress();\n\n // Create BankrWalletProvider which implements IEvmWalletProvider\n const walletProvider = createBankrWalletProvider({\n apiKey,\n apiUrl: process.env.BANKR_API_URL,\n chainId: numericChainId,\n rpcUrl,\n cachedAddress: walletAddress,\n });\n\n console.log('[spokeProviderFactory] Creating EvmSpokeProvider with Bankr backend', {\n wallet: wallet.nickname,\n chainId,\n sdkChainId,\n address: walletAddress?.slice(0, 10) + '...',\n });\n\n // Use standard EvmSpokeProvider with our BankrWalletProvider\n // The SDK doesn't care how transactions are signed - it just calls the interface methods\n return new EvmSpokeProvider(\n walletProvider as any, // BankrWalletProvider implements IEvmWalletProvider\n chainConfig as any,\n rpcUrl\n );\n}\n\n/**\n * Create a spoke provider for the given wallet and chain\n * \n * @param walletId - Wallet nickname (e.g., \"main\", \"bankr\", \"trading\")\n * @param chainId - Chain identifier (e.g., \"ethereum\", \"base\")\n */\nasync function createSpokeProvider(\n walletId: string,\n chainId: string\n): Promise<SpokeProvider> {\n // Get wallet from unified manager\n const walletManager = getWalletManager();\n const wallet = await walletManager.resolve(walletId);\n \n // Validate chain support\n validateChainSupport(wallet, chainId);\n\n const rpcUrl = await getRpcUrl(chainId);\n\n // Route based on wallet type\n if (wallet.type === 'bankr') {\n // Use BankrWalletProvider for Bankr wallets\n return createBankrSpokeProvider(wallet, chainId, rpcUrl);\n }\n\n // Local key signing (evm-wallet-skill or env)\n return createLocalSpokeProvider(wallet, chainId, rpcUrl);\n}\n\n/**\n * Get a spoke provider for the given wallet and chain\n * Returns cached provider if available, otherwise creates a new one\n *\n * @param walletId - The wallet identifier/nickname\n * @param chainId - The chain identifier\n * @param raw - If true, still creates full provider (raw mode not yet supported)\n * @returns The spoke provider instance\n */\nexport async function getSpokeProvider(\n walletId: string,\n chainId: string,\n raw = false\n): Promise<SpokeProvider> {\n const cacheKey = `${walletId}:${chainId}`;\n\n // Check cache\n const cached = providerCache.get(cacheKey);\n if (cached) {\n console.log('[spokeProviderFactory] Using cached provider', {\n walletId,\n chainId,\n });\n return cached;\n }\n\n // Create new provider\n const provider = await createSpokeProvider(walletId, chainId);\n\n // Cache the provider\n providerCache.set(cacheKey, provider);\n\n return provider;\n}\n\n/**\n * Clear the provider cache\n * Useful for testing or when wallet configuration changes\n */\nexport function clearProviderCache(): void {\n providerCache.clear();\n console.log('[spokeProviderFactory] Provider cache cleared');\n}\n\n/**\n * Get cache statistics\n * @returns Object with cache size and keys\n */\nexport function getCacheStats(): { size: number; keys: string[] } {\n return {\n size: providerCache.size,\n keys: Array.from(providerCache.keys()),\n };\n}\n\n// Export the type for use in other modules\nexport type { SpokeProvider };\n",
1258 "inputSchema": {},
1259 "outputSchema": null,
1260 "icons": null,
1261 "annotations": null,
1262 "meta": null,
1263 "execution": null
1264 },
1265 {
1266 "name": "policyEngine.ts",
1267 "title": null,
1268 "description": "Script: policyEngine.ts. Code:\n/**\n * Policy Engine\n *\n * Enforces security policies for DeFi operations including:\n * - Spend limits per transaction and daily\n * - Allowed chain and token allowlists\n * - Blocked recipient addresses\n * - Maximum slippage tolerance\n * - Simulation requirements\n */\n\nimport { BridgeOperation, PolicyConfig } from '../types';\nimport { normalizeChainId } from '../wallet/types';\n\n/**\n * Policy check result\n */\nexport interface PolicyCheckResult {\n allowed: boolean;\n reason?: string;\n details?: Record<string, unknown>;\n}\n\n/**\n * Policy Engine class for enforcing security constraints\n */\nexport class PolicyEngine {\n private config: PolicyConfig;\n\n constructor(policyId?: string) {\n this.config = this.loadPolicyConfig(policyId);\n }\n\n /**\n * Load policy configuration from environment\n *\n * @param policyId - Optional policy profile ID for custom limits\n * @returns The policy configuration\n */\n private loadPolicyConfig(policyId?: string): PolicyConfig {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n\n if (!limitsJson) {\n return {};\n }\n\n try {\n const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;\n\n // If policyId is specified, use that config; otherwise use 'default' or empty\n const config = policyId\n ? allConfigs[policyId]\n : allConfigs['default'] || allConfigs;\n\n if (policyId && !config) {\n return allConfigs['default'] || {};\n }\n\n return config || {};\n } catch (_error) {\n return {};\n }\n }\n\n /**\n * Check if a chain is allowed\n *\n * @param chainId - The chain ID to check\n * @returns Policy check result\n */\n private checkChainAllowed(chainId: string): PolicyCheckResult {\n const { allowedChains } = this.config;\n\n if (allowedChains && allowedChains.length > 0) {\n const normalizedChain = normalizeChainId(chainId);\n if (!allowedChains.includes(normalizedChain)) {\n return {\n allowed: false,\n reason: `Chain not allowed: ${chainId}. Allowed chains: ${allowedChains.join(', ')}`,\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check if a token is allowed on a specific chain\n *\n * @param chainId - The chain ID\n * @param token - The token address or symbol\n * @returns Policy check result\n */\n private checkTokenAllowed(chainId: string, token: string): PolicyCheckResult {\n const { allowedTokensByChain } = this.config;\n\n if (allowedTokensByChain) {\n const normalizedChainForTokens = normalizeChainId(chainId);\n const allowedTokens = allowedTokensByChain[normalizedChainForTokens];\n if (allowedTokens && allowedTokens.length > 0) {\n if (!allowedTokens.includes(token)) {\n return {\n allowed: false,\n reason: `Token not allowed on ${chainId}: ${token}. Allowed tokens: ${allowedTokens.join(', ')}`,\n };\n }\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check if a recipient is blocked\n *\n * @param recipient - The recipient address\n * @returns Policy check result\n */\n private checkRecipientNotBlocked(recipient: string): PolicyCheckResult {\n const { blockedRecipients } = this.config;\n\n if (blockedRecipients && blockedRecipients.length > 0) {\n if (blockedRecipients.includes(recipient.toLowerCase())) {\n return {\n allowed: false,\n reason: `Recipient is blocked: ${recipient}`,\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check bridge amount against limits\n *\n * @param token - The token address or symbol\n * @param amount - The amount in human-readable units\n * @returns Policy check result\n */\n private checkBridgeAmount(token: string, amount: string): PolicyCheckResult {\n const { maxBridgeAmountToken } = this.config;\n\n if (maxBridgeAmountToken) {\n const maxAmount = maxBridgeAmountToken[token];\n if (maxAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxAmount) {\n return {\n allowed: false,\n reason: `Bridge amount ${amount} exceeds maximum ${maxAmount} for token ${token}`,\n details: { maxAllowed: maxAmount, requested: amountNum },\n };\n }\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a bridge operation against all policies\n *\n * @param operation - The bridge operation to validate\n * @returns Policy check result\n */\n async checkBridge(operation: BridgeOperation): Promise<PolicyCheckResult> {\n const { srcChainId, dstChainId, srcToken, dstToken, amount, recipient } = operation;\n\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed) return srcChainCheck;\n\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed) return srcTokenCheck;\n\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed) return dstTokenCheck;\n\n // Check amount limits\n const amountCheck = this.checkBridgeAmount(srcToken, amount);\n if (!amountCheck.allowed) return amountCheck;\n\n // Check recipient if specified\n if (recipient) {\n const recipientCheck = this.checkRecipientNotBlocked(recipient);\n if (!recipientCheck.allowed) return recipientCheck;\n }\n\n return { allowed: true };\n }\n\n /**\n * Get the current policy configuration\n * @returns The policy configuration\n */\n getConfig(): PolicyConfig {\n return { ...this.config };\n }\n\n /**\n * Get available policy IDs from the configuration\n * @returns Array of available policy IDs\n */\n getAvailablePolicies(): string[] {\n const limitsJson = process.env.AMPED_OC_LIMITS_JSON;\n if (!limitsJson) return [];\n \n try {\n const allConfigs = JSON.parse(limitsJson) as Record<string, PolicyConfig>;\n return Object.keys(allConfigs);\n } catch {\n return [];\n }\n }\n\n // ============================================================================\n // Swap Policy Checks\n // ============================================================================\n\n /**\n * Check swap input amount against USD limits\n */\n private checkSwapAmount(inputAmount: string, srcToken: string): PolicyCheckResult {\n const { maxSwapInputUsd, maxSwapInputToken } = this.config;\n\n // Check per-token limit if configured\n if (maxSwapInputToken) {\n const maxTokenAmount = maxSwapInputToken[srcToken];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(inputAmount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Swap input amount ${inputAmount} exceeds maximum ${maxTokenAmount} for token ${srcToken}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n\n // Note: USD limit check would require price oracle integration\n // For now, we skip enforcement without prices\n\n return { allowed: true };\n }\n\n /**\n * Check slippage against maximum allowed\n */\n private checkSlippage(slippageBps: number): PolicyCheckResult {\n const { maxSlippageBps } = this.config;\n\n if (maxSlippageBps !== undefined && slippageBps > maxSlippageBps) {\n return {\n allowed: false,\n reason: `Slippage ${slippageBps} bps exceeds maximum allowed ${maxSlippageBps} bps`,\n details: { maxAllowed: maxSlippageBps, requested: slippageBps },\n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a swap operation against all policies\n */\n async checkSwap(params: {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n inputAmount: string;\n slippageBps: number;\n policyId?: string;\n }): Promise<PolicyCheckResult> {\n const { srcChainId, dstChainId, srcToken, dstToken, inputAmount, slippageBps } = params;\n\n // Check source chain\n const srcChainCheck = this.checkChainAllowed(srcChainId);\n if (!srcChainCheck.allowed) return srcChainCheck;\n\n // Check destination chain\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n\n // Check source token\n const srcTokenCheck = this.checkTokenAllowed(srcChainId, srcToken);\n if (!srcTokenCheck.allowed) return srcTokenCheck;\n\n // Check destination token\n const dstTokenCheck = this.checkTokenAllowed(dstChainId, dstToken);\n if (!dstTokenCheck.allowed) return dstTokenCheck;\n\n // Check swap amount limits\n const amountCheck = this.checkSwapAmount(inputAmount, srcToken);\n if (!amountCheck.allowed) return amountCheck;\n\n // Check slippage\n const slippageCheck = this.checkSlippage(slippageBps);\n if (!slippageCheck.allowed) return slippageCheck;\n\n return { allowed: true };\n }\n\n // ============================================================================\n // Money Market Policy Checks\n // ============================================================================\n\n /**\n * Check borrow amount against limits\n */\n private checkBorrowAmount(token: string, amount: string, amountUsd?: number): PolicyCheckResult {\n const { maxBorrowUsd, maxBorrowToken } = this.config;\n\n // Check per-token limit if configured\n if (maxBorrowToken) {\n const maxTokenAmount = maxBorrowToken[token];\n if (maxTokenAmount !== undefined) {\n const amountNum = parseFloat(amount);\n if (amountNum > maxTokenAmount) {\n return {\n allowed: false,\n reason: `Borrow amount ${amount} exceeds maximum ${maxTokenAmount} for token ${token}`,\n details: { maxAllowed: maxTokenAmount, requested: amountNum },\n };\n }\n }\n }\n\n // Check USD limit if amountUsd is provided\n if (maxBorrowUsd !== undefined && amountUsd !== undefined) {\n if (amountUsd > maxBorrowUsd) {\n return {\n allowed: false,\n reason: `Borrow amount $${amountUsd} exceeds maximum $${maxBorrowUsd}`,\n details: { maxAllowed: maxBorrowUsd, requested: amountUsd },\n };\n }\n }\n\n return { allowed: true };\n }\n\n /**\n * Check a money market operation against all policies\n */\n async checkMoneyMarket(params: {\n walletId: string;\n chainId: string;\n dstChainId?: string;\n token: string;\n amount: string;\n amountUsd?: number;\n operation: 'supply' | 'withdraw' | 'borrow' | 'repay';\n policyId?: string;\n }): Promise<PolicyCheckResult> {\n const { chainId, dstChainId, token, amount, amountUsd, operation } = params;\n\n // Check source chain\n const chainCheck = this.checkChainAllowed(chainId);\n if (!chainCheck.allowed) return chainCheck;\n\n // Check destination chain if cross-chain operation\n if (dstChainId) {\n const dstChainCheck = this.checkChainAllowed(dstChainId);\n if (!dstChainCheck.allowed) return dstChainCheck;\n }\n\n // Check token\n const tokenCheck = this.checkTokenAllowed(chainId, token);\n if (!tokenCheck.allowed) return tokenCheck;\n\n // Operation-specific checks\n if (operation === 'borrow') {\n const borrowCheck = this.checkBorrowAmount(token, amount, amountUsd);\n if (!borrowCheck.allowed) return borrowCheck;\n }\n\n return { allowed: true };\n }\n}\n",
1269 "inputSchema": {},
1270 "outputSchema": null,
1271 "icons": null,
1272 "annotations": null,
1273 "meta": null,
1274 "execution": null
1275 },
1276 {
1277 "name": "types.ts",
1278 "title": null,
1279 "description": "Script: types.ts. Code:\n/**\n * Common types for Amped DeFi plugin\n */\n\nimport { Static, TSchema } from '@sinclair/typebox';\n\n/**\n * Tool handler function type\n */\nexport type ToolHandler<T extends TSchema> = (params: Static<T>) => Promise<unknown>;\n\n/**\n * Agent tools registry interface\n */\nexport interface AgentTools {\n register: <T extends TSchema>(tool: {\n name: string;\n summary: string;\n description?: string;\n schema: T;\n handler: ToolHandler<T>;\n }) => void;\n}\n\n/**\n * Alternative registration interface with input/output schemas\n */\nexport interface AgentToolsTyped {\n register<T extends TSchema, R extends TSchema>(config: {\n name: string;\n summary: string;\n description?: string;\n schema: {\n input: T;\n output?: R;\n };\n handler: (input: Static<T>) => Promise<Static<R>>;\n }): void;\n}\n\n/**\n * Standard tool result wrapper\n */\nexport interface ToolResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\n/**\n * Transaction status\n */\nexport type TransactionStatus = \n | 'pending'\n | 'submitted'\n | 'confirmed'\n | 'failed'\n | 'cancelled'\n | 'unknown';\n\n/**\n * Intent status from SODAX\n */\nexport interface IntentStatus {\n intentHash: string;\n status: 'pending' | 'filled' | 'cancelled' | 'expired' | 'failed';\n spokeTxHash?: string;\n hubTxHash?: string;\n filledAmount?: string;\n error?: string;\n createdAt?: number;\n updatedAt?: number;\n}\n\n/**\n * Quote result\n */\nexport interface QuoteResult {\n quoteId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n srcAmount: string;\n dstAmount: string;\n minDstAmount?: string;\n slippageBps: number;\n deadline: number;\n fees: {\n solverFee?: string;\n partnerFee?: string;\n gasFee?: string;\n };\n route?: unknown;\n}\n\n/**\n * Bridge result\n */\nexport interface BridgeResult {\n spokeTxHash: string;\n hubTxHash?: string;\n status: TransactionStatus;\n}\n\n/**\n * Money market position\n */\nexport interface MoneyMarketPosition {\n token: string;\n supplied: string;\n borrowed: string;\n supplyApy: number;\n borrowApy: number;\n collateralFactor: number;\n}\n\n/**\n * Money market reserve\n */\nexport interface MoneyMarketReserve {\n token: string;\n totalSupplied: string;\n totalBorrowed: string;\n supplyApy: number;\n borrowApy: number;\n utilizationRate: number;\n collateralFactor: number;\n liquidationThreshold: number;\n}\n\n/**\n * Chain configuration\n */\nexport interface ChainConfig {\n chainId: string;\n name: string;\n isHub: boolean;\n nativeCurrency: {\n symbol: string;\n decimals: number;\n };\n rpcUrl?: string;\n}\n\n/**\n * Token configuration\n */\nexport interface TokenConfig {\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n chainId: string;\n}\n\n/**\n * Wallet information (safe to log)\n */\nexport interface WalletInfo {\n walletId: string;\n address: string;\n mode: 'execute' | 'prepare';\n chains: string[];\n}\n\n/**\n * Operation context for logging\n */\nexport interface OperationContext {\n requestId: string;\n agentId?: string;\n walletId: string;\n operation: string;\n chainIds: string[];\n tokenAddresses?: string[];\n timestamp: number;\n}\n\n/**\n * Bridge operation parameters\n */\nexport interface BridgeOperation {\n walletId: string;\n srcChainId: string;\n dstChainId: string;\n srcToken: string;\n dstToken: string;\n amount: string;\n recipient?: string;\n timeoutMs?: number;\n policyId?: string;\n}\n\n/**\n * Wallet configuration\n */\nexport interface WalletConfig {\n address: string;\n privateKey?: string;\n}\n\n/**\n * Policy configuration\n */\nexport interface PolicyConfig {\n /** Maximum USD value for swap inputs */\n maxSwapInputUsd?: number;\n /** Maximum per-token amount for swap inputs */\n maxSwapInputToken?: Record<string, number>;\n /** Maximum per-token amount for bridge operations */\n maxBridgeAmountToken?: Record<string, number>;\n /** Maximum USD value for borrows */\n maxBorrowUsd?: number;\n /** Maximum per-token amount for borrows */\n maxBorrowToken?: Record<string, number>;\n /** Allowed chain IDs for operations */\n allowedChains?: string[];\n /** Allowed tokens per chain */\n allowedTokensByChain?: Record<string, string[]>;\n /** Blocked recipient addresses */\n blockedRecipients?: string[];\n /** Maximum slippage in basis points (100 = 1%) */\n maxSlippageBps?: number;\n /** Whether to require transaction simulation */\n requireSimulation?: boolean;\n}\n",
1280 "inputSchema": {},
1281 "outputSchema": null,
1282 "icons": null,
1283 "annotations": null,
1284 "meta": null,
1285 "execution": null
1286 },
1287 {
1288 "name": "positionAggregator.test.ts",
1289 "title": null,
1290 "description": "Script: positionAggregator.test.ts. Code:\n/**\n * Position Aggregator Tests\n */\n\nimport {\n formatHealthFactor,\n getHealthFactorStatus,\n getPositionRecommendation,\n TokenPosition,\n CrossChainPositionView,\n} from '../utils/positionAggregator';\n\n// Mock dependencies\njest.mock('../sodax/client');\njest.mock('../providers/spokeProviderFactory');\njest.mock('../wallet/walletRegistry');\n\ndescribe('Position Aggregator Utilities', () => {\n describe('formatHealthFactor', () => {\n it('should format finite health factors', () => {\n expect(formatHealthFactor(1.5)).toBe('1.50');\n expect(formatHealthFactor(2.345)).toBe('2.35');\n expect(formatHealthFactor(0.95)).toBe('0.95');\n });\n\n it('should format infinity', () => {\n expect(formatHealthFactor(Infinity)).toBe('\u221e');\n });\n\n it('should handle null', () => {\n expect(formatHealthFactor(null)).toBe('N/A');\n });\n });\n\n describe('getHealthFactorStatus', () => {\n it('should return critical for HF < 1.1', () => {\n expect(getHealthFactorStatus(1.0)).toEqual({ status: 'critical', color: 'red' });\n expect(getHealthFactorStatus(1.05)).toEqual({ status: 'critical', color: 'red' });\n });\n\n it('should return danger for HF 1.1-1.5', () => {\n expect(getHealthFactorStatus(1.2)).toEqual({ status: 'danger', color: 'orange' });\n expect(getHealthFactorStatus(1.49)).toEqual({ status: 'danger', color: 'orange' });\n });\n\n it('should return caution for HF 1.5-2', () => {\n expect(getHealthFactorStatus(1.6)).toEqual({ status: 'caution', color: 'yellow' });\n expect(getHealthFactorStatus(1.9)).toEqual({ status: 'caution', color: 'yellow' });\n });\n\n it('should return healthy for HF >= 2', () => {\n expect(getHealthFactorStatus(2.0)).toEqual({ status: 'healthy', color: 'green' });\n expect(getHealthFactorStatus(5.0)).toEqual({ status: 'healthy', color: 'green' });\n expect(getHealthFactorStatus(Infinity)).toEqual({ status: 'healthy', color: 'green' });\n });\n\n it('should return healthy for null', () => {\n expect(getHealthFactorStatus(null)).toEqual({ status: 'healthy', color: 'green' });\n });\n });\n\n describe('getPositionRecommendation', () => {\n it('should warn about low health factor', () => {\n const view = createMockView({\n healthFactor: 1.3,\n availableBorrowUsd: 0,\n utilizationRate: 60,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('Health factor is low'))).toBe(true);\n });\n\n it('should suggest available borrowing power', () => {\n const view = createMockView({\n healthFactor: 2.5,\n availableBorrowUsd: 5000,\n utilizationRate: 40,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('borrowing power'))).toBe(true);\n });\n\n it('should warn about high utilization', () => {\n const view = createMockView({\n healthFactor: 1.8,\n availableBorrowUsd: 100,\n utilizationRate: 85,\n netApy: 0.02,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('High collateral utilization'))).toBe(true);\n });\n\n it('should warn about negative net APY', () => {\n const view = createMockView({\n healthFactor: 2.0,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: -0.01,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('borrowing costs exceed'))).toBe(true);\n });\n\n it('should mention cross-chain positions', () => {\n const view = createMockView({\n healthFactor: 2.0,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: 0.02,\n chainCount: 3,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.some(r => r.includes('chains'))).toBe(true);\n });\n\n it('should return empty array for healthy position', () => {\n const view = createMockView({\n healthFactor: 2.5,\n availableBorrowUsd: 100,\n utilizationRate: 50,\n netApy: 0.05,\n chainCount: 1,\n });\n\n const recommendations = getPositionRecommendation(view);\n expect(recommendations.length).toBe(0);\n });\n });\n});\n\n// Helper function to create mock views\nfunction createMockView(params: {\n healthFactor: number;\n availableBorrowUsd: number;\n utilizationRate: number;\n netApy: number;\n chainCount?: number;\n}): CrossChainPositionView {\n const chainSummaries = Array(params.chainCount || 1).fill(null).map((_, i) => ({\n chainId: `chain${i}`,\n supplyUsd: 10000,\n borrowUsd: 5000,\n netWorthUsd: 5000,\n healthFactor: params.healthFactor,\n positionCount: 2,\n }));\n\n return {\n walletId: 'test',\n address: '0x123',\n timestamp: new Date().toISOString(),\n summary: {\n totalSupplyUsd: chainSummaries.reduce((s, c) => s + c.supplyUsd, 0),\n totalBorrowUsd: chainSummaries.reduce((s, c) => s + c.borrowUsd, 0),\n netWorthUsd: chainSummaries.reduce((s, c) => s + c.netWorthUsd, 0),\n availableBorrowUsd: params.availableBorrowUsd,\n healthFactor: params.healthFactor,\n liquidationRisk: params.healthFactor < 1.5 ? 'medium' : 'none',\n weightedSupplyApy: 0.05,\n weightedBorrowApy: 0.03,\n netApy: params.netApy,\n },\n chainSummaries,\n positions: [],\n collateralUtilization: {\n totalCollateralUsd: 10000,\n usedCollateralUsd: params.utilizationRate * 100,\n availableCollateralUsd: 10000 - params.utilizationRate * 100,\n utilizationRate: params.utilizationRate,\n },\n riskMetrics: {\n maxLtv: 0.8,\n currentLtv: 0.5,\n bufferUntilLiquidation: 30,\n safeMaxBorrowUsd: 8000,\n },\n };\n}\n\ndescribe('Position Calculations', () => {\n const mockPositions: TokenPosition[] = [\n {\n chainId: 'ethereum',\n token: {\n address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n symbol: 'USDC',\n name: 'USD Coin',\n decimals: 6,\n },\n supply: {\n balance: '10000',\n balanceUsd: '10000',\n balanceRaw: '10000000000',\n apy: 0.05,\n isCollateral: true,\n },\n borrow: {\n balance: '0',\n balanceUsd: '0',\n balanceRaw: '0',\n apy: 0,\n },\n loanToValue: 0.8,\n liquidationThreshold: 0.85,\n },\n {\n chainId: 'ethereum',\n token: {\n address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',\n symbol: 'WETH',\n name: 'Wrapped Ether',\n decimals: 18,\n },\n supply: {\n balance: '0',\n balanceUsd: '0',\n balanceRaw: '0',\n apy: 0,\n isCollateral: false,\n },\n borrow: {\n balance: '2',\n balanceUsd: '5000',\n balanceRaw: '2000000000000000000',\n apy: 0.03,\n },\n loanToValue: 0.75,\n liquidationThreshold: 0.8,\n },\n ];\n\n it('should calculate total supply correctly', () => {\n const totalSupply = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.supply.balanceUsd || '0'),\n 0\n );\n expect(totalSupply).toBe(10000);\n });\n\n it('should calculate total borrow correctly', () => {\n const totalBorrow = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.borrow.balanceUsd || '0'),\n 0\n );\n expect(totalBorrow).toBe(5000);\n });\n\n it('should identify collateral assets', () => {\n const collateralPositions = mockPositions.filter(p => p.supply.isCollateral);\n expect(collateralPositions).toHaveLength(1);\n expect(collateralPositions[0].token.symbol).toBe('USDC');\n });\n\n it('should calculate weighted APY correctly', () => {\n const totalSupply = 10000;\n const weightedSupplyApy = mockPositions.reduce(\n (sum, p) => sum + parseFloat(p.supply.balanceUsd || '0') * p.supply.apy,\n 0\n ) / totalSupply;\n \n expect(weightedSupplyApy).toBe(0.05);\n });\n});\n",
1291 "inputSchema": {},
1292 "outputSchema": null,
1293 "icons": null,
1294 "annotations": null,
1295 "meta": null,
1296 "execution": null
1297 },
1298 {
1299 "name": "sodaxApi.test.ts",
1300 "title": null,
1301 "description": "Script: sodaxApi.test.ts. Code:\n/**\n * SODAX API Client Tests\n */\n\nimport { SodaxApiClient, getSodaxApiClient, resetSodaxApiClient } from '../utils/sodaxApi';\nimport { AmpedDefiError, ErrorCode } from '../utils/errors';\n\n// Mock fetch\nglobal.fetch = jest.fn();\n\ndescribe('SodaxApiClient', () => {\n beforeEach(() => {\n jest.resetAllMocks();\n resetSodaxApiClient();\n });\n\n describe('Configuration', () => {\n it('should use default base URL', () => {\n const client = new SodaxApiClient();\n expect(client).toBeDefined();\n });\n\n it('should use custom base URL', () => {\n const client = new SodaxApiClient({ baseUrl: 'https://custom.api.com' });\n expect(client).toBeDefined();\n });\n\n it('should use environment variable for base URL', () => {\n process.env.SODAX_API_URL = 'https://env.api.com';\n const client = new SodaxApiClient();\n expect(client).toBeDefined();\n delete process.env.SODAX_API_URL;\n });\n });\n\n describe('Address Validation', () => {\n it('should reject invalid addresses', async () => {\n const client = new SodaxApiClient();\n \n await expect(client.getUserIntents('invalid-address'))\n .rejects\n .toThrow(AmpedDefiError);\n });\n\n it('should accept valid addresses', async () => {\n const mockResponse = {\n items: [],\n total: 0,\n offset: 0,\n limit: 50,\n };\n\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => mockResponse,\n });\n\n const client = new SodaxApiClient();\n const result = await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n \n expect(result).toEqual(mockResponse);\n });\n });\n\n describe('API Requests', () => {\n it('should fetch user intents with default pagination', async () => {\n const mockResponse = {\n items: [\n {\n intentHash: '0xabc',\n txHash: '0xdef',\n chainId: 146,\n open: true,\n intent: {\n inputToken: '0xinput',\n outputToken: '0xoutput',\n inputAmount: '1000',\n },\n events: [],\n createdAt: '2025-01-01T00:00:00Z',\n },\n ],\n total: 1,\n offset: 0,\n limit: 50,\n };\n\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => mockResponse,\n });\n\n const client = new SodaxApiClient();\n const result = await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n expect(result.items).toHaveLength(1);\n expect(result.total).toBe(1);\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('/v1/be/intent/user/0xf48cd107faaa95de81afc2436e0a044196e21825'),\n expect.any(Object)\n );\n });\n\n it('should apply pagination parameters', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 100, offset: 10, limit: 20 }),\n });\n\n const client = new SodaxApiClient();\n await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825', {\n offset: 10,\n limit: 20,\n });\n\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('offset=10'),\n expect.any(Object)\n );\n expect(global.fetch).toHaveBeenCalledWith(\n expect.stringContaining('limit=20'),\n expect.any(Object)\n );\n });\n\n it('should apply filters', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825', {}, {\n open: true,\n srcChain: 1,\n inputToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n });\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=true');\n expect(callUrl).toContain('srcChain=1');\n expect(callUrl).toContain('inputToken=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48');\n });\n\n it('should handle API errors', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: false,\n status: 500,\n statusText: 'Internal Server Error',\n text: async () => 'Server error',\n });\n\n const client = new SodaxApiClient();\n \n await expect(client.getUserIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825'))\n .rejects\n .toThrow(AmpedDefiError);\n });\n });\n\n describe('Convenience Methods', () => {\n it('should get open intents', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getOpenIntents('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=true');\n });\n\n it('should get intent history (closed)', async () => {\n (global.fetch as jest.Mock).mockResolvedValueOnce({\n ok: true,\n json: async () => ({ items: [], total: 0, offset: 0, limit: 50 }),\n });\n\n const client = new SodaxApiClient();\n await client.getIntentHistory('0xf48Cd107FaaA95DE81afC2436e0A044196E21825');\n\n const callUrl = (global.fetch as jest.Mock).mock.calls[0][0];\n expect(callUrl).toContain('open=false');\n });\n });\n\n describe('Singleton', () => {\n it('should return same instance', () => {\n const client1 = getSodaxApiClient();\n const client2 = getSodaxApiClient();\n expect(client1).toBe(client2);\n });\n\n it('should create new instance after reset', () => {\n const client1 = getSodaxApiClient();\n resetSodaxApiClient();\n const client2 = getSodaxApiClient();\n expect(client1).not.toBe(client2);\n });\n });\n});\n",
1302 "inputSchema": {},
1303 "outputSchema": null,
1304 "icons": null,
1305 "annotations": null,
1306 "meta": null,
1307 "execution": null
1308 },
1309 {
1310 "name": "policyEngine.test.ts",
1311 "title": null,
1312 "description": "Script: policyEngine.test.ts. Code:\n/**\n * Policy Engine Tests\n */\n\nimport { PolicyEngine, PolicyCheckResult } from '../policy/policyEngine';\n\n// Mock environment variables\nconst originalEnv = process.env;\n\ndescribe('PolicyEngine', () => {\n beforeEach(() => {\n jest.resetModules();\n process.env = { ...originalEnv };\n delete process.env.AMPED_OC_LIMITS_JSON;\n });\n\n afterAll(() => {\n process.env = originalEnv;\n });\n\n describe('Configuration Loading', () => {\n it('should load empty config when env not set', () => {\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config).toEqual({});\n });\n\n it('should load default policy config', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n maxSlippageBps: 100,\n allowedChains: ['ethereum', 'arbitrum'],\n },\n });\n\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(100);\n expect(config.allowedChains).toEqual(['ethereum', 'arbitrum']);\n });\n\n it('should load specific policy by ID', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: { maxSlippageBps: 100 },\n aggressive: { maxSlippageBps: 300 },\n conservative: { maxSlippageBps: 50 },\n });\n\n const engine = new PolicyEngine('aggressive');\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(300);\n });\n\n it('should fallback to default when policy ID not found', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: { maxSlippageBps: 100 },\n });\n\n const engine = new PolicyEngine('nonexistent');\n const config = engine.getConfig();\n expect(config.maxSlippageBps).toBe(100);\n });\n\n it('should handle invalid JSON gracefully', () => {\n process.env.AMPED_OC_LIMITS_JSON = 'invalid json';\n\n const engine = new PolicyEngine();\n const config = engine.getConfig();\n expect(config).toEqual({});\n });\n });\n\n describe('Bridge Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'arbitrum', 'sonic'],\n allowedTokensByChain: {\n ethereum: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],\n },\n maxBridgeAmountToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 10000,\n },\n blockedRecipients: ['0x0000000000000000000000000000000000000000'],\n },\n });\n });\n\n it('should allow valid bridge operation', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject disallowed source chain', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'polygon',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n\n it('should reject disallowed destination chain', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'polygon',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n\n it('should reject disallowed token', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xInvalidToken',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Token not allowed');\n });\n\n it('should reject amount exceeding limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '15000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n expect(result.details).toEqual({ maxAllowed: 10000, requested: 15000 });\n });\n\n it('should reject blocked recipient', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkBridge({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n amount: '1000',\n recipient: '0x0000000000000000000000000000000000000000',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Recipient is blocked');\n });\n });\n\n describe('Swap Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'arbitrum'],\n maxSlippageBps: 100,\n maxSwapInputToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 50000,\n },\n },\n });\n });\n\n it('should allow valid swap', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '1000',\n slippageBps: 50,\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject excessive slippage', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '1000',\n slippageBps: 150,\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Slippage');\n expect(result.details).toEqual({ maxAllowed: 100, requested: 150 });\n });\n\n it('should reject swap exceeding token limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkSwap({\n walletId: 'test',\n srcChainId: 'ethereum',\n dstChainId: 'arbitrum',\n srcToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n dstToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',\n inputAmount: '60000',\n slippageBps: 50,\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n });\n\n describe('Money Market Policy Checks', () => {\n beforeEach(() => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {\n allowedChains: ['ethereum', 'sonic'],\n allowedTokensByChain: {\n ethereum: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],\n sonic: ['0x29219dd400f2bf60e5a23d13be72b486d4038894'],\n },\n maxBorrowUsd: 50000,\n maxBorrowToken: {\n '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 30000,\n },\n },\n });\n });\n\n it('should allow valid supply', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '10000',\n operation: 'supply',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should allow valid borrow within limits', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '20000',\n amountUsd: 20000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(true);\n });\n\n it('should reject borrow exceeding USD limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Use allowed token\n amount: '60000',\n amountUsd: 60000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n\n it('should reject borrow exceeding token limit', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '35000',\n amountUsd: 35000,\n operation: 'borrow',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('exceeds maximum');\n });\n\n it('should check cross-chain destination', async () => {\n const engine = new PolicyEngine();\n const result = await engine.checkMoneyMarket({\n walletId: 'test',\n chainId: 'ethereum',\n dstChainId: 'polygon',\n token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n amount: '10000',\n operation: 'supply',\n });\n\n expect(result.allowed).toBe(false);\n expect(result.reason).toContain('Chain not allowed');\n });\n });\n\n describe('getAvailablePolicies', () => {\n it('should return empty array when no config', () => {\n const engine = new PolicyEngine();\n expect(engine.getAvailablePolicies()).toEqual([]);\n });\n\n it('should return policy IDs', () => {\n process.env.AMPED_OC_LIMITS_JSON = JSON.stringify({\n default: {},\n aggressive: {},\n conservative: {},\n });\n\n const engine = new PolicyEngine();\n const policies = engine.getAvailablePolicies();\n expect(policies).toContain('default');\n expect(policies).toContain('aggressive');\n expect(policies).toContain('conservative');\n expect(policies).toHaveLength(3);\n });\n });\n});\n",
1313 "inputSchema": {},
1314 "outputSchema": null,
1315 "icons": null,
1316 "annotations": null,
1317 "meta": null,
1318 "execution": null
1319 },
1320 {
1321 "name": "walletRegistry.test.ts",
1322 "title": null,
1323 "description": "Script: walletRegistry.test.ts. Code:\n/**\n * Wallet Registry Tests\n */\n\nimport { WalletRegistry } from '../wallet/walletRegistry';\n\n// Mock environment\nconst originalEnv = process.env;\n\ndescribe('WalletRegistry', () => {\n beforeEach(() => {\n jest.resetModules();\n process.env = { ...originalEnv };\n delete process.env.AMPED_OC_WALLETS_JSON;\n delete process.env.AMPED_OC_MODE;\n });\n\n afterAll(() => {\n process.env = originalEnv;\n });\n\n describe('Configuration Loading', () => {\n it('should load empty wallets when env not set', () => {\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(0);\n expect(registry.getWalletIds()).toEqual([]);\n });\n\n it('should load wallets in execute mode', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123',\n },\n trading: {\n address: '0x0987654321098765432109876543210987654321',\n privateKey: '0xdef456',\n },\n });\n process.env.AMPED_OC_MODE = 'execute';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(2);\n expect(registry.getWalletIds()).toContain('main');\n expect(registry.getWalletIds()).toContain('trading');\n expect(registry.isExecuteMode()).toBe(true);\n expect(registry.isPrepareMode()).toBe(false);\n });\n\n it('should load wallets in prepare mode', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n },\n });\n process.env.AMPED_OC_MODE = 'prepare';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(1);\n expect(registry.isPrepareMode()).toBe(true);\n expect(registry.isExecuteMode()).toBe(false);\n });\n\n it('should handle invalid JSON gracefully', () => {\n process.env.AMPED_OC_WALLETS_JSON = 'invalid json';\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(0);\n });\n });\n\n describe('Wallet Resolution', () => {\n beforeEach(() => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n main: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123',\n },\n nopk: {\n address: '0x0987654321098765432109876543210987654321',\n },\n invalid: {\n address: 'not-an-address',\n },\n });\n process.env.AMPED_OC_MODE = 'execute';\n });\n\n it('should resolve existing wallet', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('main');\n\n expect(wallet).not.toBeNull();\n expect(wallet?.address).toBe('0x1234567890123456789012345678901234567890');\n expect(wallet?.privateKey).toBe('0xabc123');\n expect(wallet?.mode).toBe('execute');\n });\n\n it('should return null for non-existent wallet', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('nonexistent');\n\n expect(wallet).toBeNull();\n });\n\n it('should reject wallet without private key in execute mode', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('nopk');\n\n expect(wallet).toBeNull();\n });\n\n it('should reject wallet with invalid address', async () => {\n const registry = new WalletRegistry();\n const wallet = await registry.resolveWallet('invalid');\n\n expect(wallet).toBeNull();\n });\n });\n\n describe('Mode Detection', () => {\n it('should default to execute mode', () => {\n delete process.env.AMPED_OC_MODE;\n const registry = new WalletRegistry();\n\n expect(registry.getMode()).toBe('execute');\n });\n\n it('should detect prepare mode', () => {\n process.env.AMPED_OC_MODE = 'prepare';\n const registry = new WalletRegistry();\n\n expect(registry.getMode()).toBe('prepare');\n });\n });\n\n describe('Hot Reload', () => {\n it('should reload wallets from environment', () => {\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n wallet1: { address: '0x1234567890123456789012345678901234567890' },\n });\n\n const registry = new WalletRegistry();\n expect(registry.getWalletCount()).toBe(1);\n\n process.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n wallet1: { address: '0x1234567890123456789012345678901234567890' },\n wallet2: { address: '0x0987654321098765432109876543210987654321' },\n });\n\n registry.reload();\n expect(registry.getWalletCount()).toBe(2);\n });\n });\n});\n",
1324 "inputSchema": {},
1325 "outputSchema": null,
1326 "icons": null,
1327 "annotations": null,
1328 "meta": null,
1329 "execution": null
1330 },
1331 {
1332 "name": "setup.ts",
1333 "title": null,
1334 "description": "Script: setup.ts. Code:\n/**\n * Jest Test Setup\n */\n\n// Mock console methods to reduce noise during tests\nglobal.console = {\n ...console,\n log: jest.fn(),\n debug: jest.fn(),\n info: jest.fn(),\n warn: jest.fn(),\n error: jest.fn(),\n};\n\n// Set default test environment\nprocess.env.AMPED_OC_MODE = 'execute';\nprocess.env.AMPED_OC_WALLETS_JSON = JSON.stringify({\n test: {\n address: '0x1234567890123456789012345678901234567890',\n privateKey: '0xabc123def456',\n },\n});\nprocess.env.AMPED_OC_RPC_URLS_JSON = JSON.stringify({\n ethereum: 'https://eth-mainnet.example.com',\n arbitrum: 'https://arb-mainnet.example.com',\n sonic: 'https://rpc.sonic.example.com',\n});\n\n// Global test timeout\njest.setTimeout(10000);\n\n// Clean up after each test\nafterEach(() => {\n jest.clearAllMocks();\n});\n",
1335 "inputSchema": {},
1336 "outputSchema": null,
1337 "icons": null,
1338 "annotations": null,
1339 "meta": null,
1340 "execution": null
1341 },
1342 {
1343 "name": "errors.test.ts",
1344 "title": null,
1345 "description": "Script: errors.test.ts. Code:\n/**\n * Error Handling Utilities Tests\n */\n\nimport {\n ErrorCode,\n ErrorSeverity,\n AmpedDefiError,\n createPolicyError,\n createWalletError,\n createTransactionError,\n wrapError,\n isRetryableError,\n getRetryDelay,\n logError,\n} from '../utils/errors';\n\ndescribe('Error Utilities', () => {\n describe('AmpedDefiError', () => {\n it('should create error with all properties', () => {\n const error = new AmpedDefiError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage too high',\n {\n severity: ErrorSeverity.WARNING,\n remediation: 'Increase slippage tolerance',\n details: { current: 100, limit: 50 },\n context: { operation: 'swap', walletId: 'test' },\n }\n );\n\n expect(error.code).toBe(ErrorCode.POLICY_SLIPPAGE_EXCEEDED);\n expect(error.message).toBe('Slippage too high');\n expect(error.severity).toBe(ErrorSeverity.WARNING);\n expect(error.remediation).toBe('Increase slippage tolerance');\n expect(error.details).toEqual({ current: 100, limit: 50 });\n expect(error.context).toEqual({ operation: 'swap', walletId: 'test' });\n expect(error.name).toBe('AmpedDefiError');\n });\n\n it('should default severity to ERROR', () => {\n const error = new AmpedDefiError(\n ErrorCode.UNKNOWN_ERROR,\n 'Something went wrong'\n );\n\n expect(error.severity).toBe(ErrorSeverity.ERROR);\n });\n\n it('should serialize to JSON correctly', () => {\n const error = new AmpedDefiError(\n ErrorCode.WALLET_NOT_FOUND,\n 'Wallet not found',\n { remediation: 'Check configuration' }\n );\n\n const json = error.toJSON();\n expect(json.code).toBe(ErrorCode.WALLET_NOT_FOUND);\n expect(json.message).toBe('Wallet not found');\n expect(json.remediation).toBe('Check configuration');\n });\n\n it('should generate user-friendly message', () => {\n const error = new AmpedDefiError(\n ErrorCode.TRANSACTION_FAILED,\n 'Transaction failed',\n { remediation: 'Try again later' }\n );\n\n const userMsg = error.toUserMessage();\n expect(userMsg).toContain('[TRANSACTION_FAILED] Transaction failed');\n expect(userMsg).toContain('Suggestion: Try again later');\n });\n });\n\n describe('Error Factory Functions', () => {\n it('should create policy error with remediation', () => {\n const error = createPolicyError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage exceeds limit',\n { current: 150, limit: 100 }\n );\n\n expect(error.code).toBe(ErrorCode.POLICY_SLIPPAGE_EXCEEDED);\n expect(error.severity).toBe(ErrorSeverity.WARNING);\n expect(error.remediation).toContain('150');\n expect(error.remediation).toContain('100');\n expect(error.remediation).toContain('exceeds limit');\n });\n\n it('should create wallet error with context', () => {\n const cause = new Error('Original error');\n const error = createWalletError(\n ErrorCode.WALLET_NOT_FOUND,\n 'my-wallet',\n cause,\n { operation: 'borrow' }\n );\n\n expect(error.code).toBe(ErrorCode.WALLET_NOT_FOUND);\n expect(error.message).toContain('my-wallet');\n expect(error.context).toEqual({ operation: 'borrow', walletId: 'my-wallet' });\n expect(error.cause).toBe(cause);\n });\n\n it('should create transaction error with txHash', () => {\n const error = createTransactionError(\n ErrorCode.TRANSACTION_TIMEOUT,\n 'Transaction timed out',\n '0xabc123'\n );\n\n expect(error.code).toBe(ErrorCode.TRANSACTION_TIMEOUT);\n expect(error.details).toEqual({ txHash: '0xabc123' });\n expect(error.context).toEqual({ txHash: '0xabc123' });\n });\n });\n\n describe('wrapError', () => {\n it('should return AmpedDefiError as-is', () => {\n const original = new AmpedDefiError(\n ErrorCode.SDK_NOT_INITIALIZED,\n 'SDK not ready'\n );\n const wrapped = wrapError(original);\n\n expect(wrapped).toBe(original);\n });\n\n it('should wrap standard Error and infer code', () => {\n const original = new Error('Insufficient balance for transaction');\n const wrapped = wrapError(original);\n\n expect(wrapped).toBeInstanceOf(AmpedDefiError);\n expect(wrapped.code).toBe(ErrorCode.INSUFFICIENT_BALANCE);\n expect(wrapped.message).toBe('Insufficient balance for transaction');\n });\n\n it('should use fallback code when unable to infer', () => {\n const original = new Error('Some random error');\n const wrapped = wrapError(original, ErrorCode.UNKNOWN_ERROR);\n\n expect(wrapped.code).toBe(ErrorCode.UNKNOWN_ERROR);\n });\n\n it('should wrap non-error values', () => {\n const wrapped = wrapError('string error');\n\n expect(wrapped).toBeInstanceOf(AmpedDefiError);\n expect(wrapped.message).toBe('string error');\n });\n });\n\n describe('isRetryableError', () => {\n it('should identify retryable error codes', () => {\n const timeoutError = new AmpedDefiError(\n ErrorCode.TRANSACTION_TIMEOUT,\n 'Timeout'\n );\n expect(isRetryableError(timeoutError)).toBe(true);\n\n const rpcError = new AmpedDefiError(\n ErrorCode.RPC_URL_NOT_CONFIGURED,\n 'RPC error'\n );\n expect(isRetryableError(rpcError)).toBe(true);\n });\n\n it('should identify non-retryable error codes', () => {\n const policyError = new AmpedDefiError(\n ErrorCode.POLICY_SLIPPAGE_EXCEEDED,\n 'Slippage'\n );\n expect(isRetryableError(policyError)).toBe(false);\n });\n\n it('should check message patterns for generic errors', () => {\n const networkError = new Error('Network connection failed');\n expect(isRetryableError(networkError)).toBe(true);\n\n const randomError = new Error('Something broke');\n expect(isRetryableError(randomError)).toBe(false);\n });\n });\n\n describe('getRetryDelay', () => {\n it('should calculate exponential backoff', () => {\n expect(getRetryDelay(0)).toBe(1000);\n expect(getRetryDelay(1)).toBe(2000);\n expect(getRetryDelay(2)).toBe(4000);\n expect(getRetryDelay(3)).toBe(8000);\n });\n\n it('should cap at 30 seconds', () => {\n expect(getRetryDelay(10)).toBe(30000);\n expect(getRetryDelay(20)).toBe(30000);\n });\n\n it('should use custom base delay', () => {\n expect(getRetryDelay(0, 500)).toBe(500);\n expect(getRetryDelay(1, 500)).toBe(1000);\n });\n });\n\n describe('logError', () => {\n it('should log structured error', () => {\n const consoleSpy = jest.spyOn(console, 'error').mockImplementation();\n\n const error = new AmpedDefiError(\n ErrorCode.TRANSACTION_FAILED,\n 'Transaction failed'\n );\n\n logError(error, { operation: 'swap', walletId: 'test' });\n\n expect(consoleSpy).toHaveBeenCalled();\n const logged = JSON.parse(consoleSpy.mock.calls[0][0]);\n expect(logged.component).toBe('amped-defi');\n expect(logged.code).toBe(ErrorCode.TRANSACTION_FAILED);\n expect(logged.level).toBe('error');\n\n consoleSpy.mockRestore();\n });\n\n it('should handle standard errors', () => {\n const consoleSpy = jest.spyOn(console, 'error').mockImplementation();\n\n const error = new Error('Standard error');\n logError(error);\n\n expect(consoleSpy).toHaveBeenCalled();\n const logged = JSON.parse(consoleSpy.mock.calls[0][0]);\n expect(logged.code).toBe(ErrorCode.UNKNOWN_ERROR);\n\n consoleSpy.mockRestore();\n });\n });\n});\n",
1346 "inputSchema": {},
1347 "outputSchema": null,
1348 "icons": null,
1349 "annotations": null,
1350 "meta": null,
1351 "execution": null
1352 },
1353 {
1354 "name": "index.js",
1355 "title": null,
1356 "description": "Script: index.js. Code:\n// Re-export from compiled dist\nmodule.exports = require('./dist/index.js');\n",
1357 "inputSchema": {},
1358 "outputSchema": null,
1359 "icons": null,
1360 "annotations": null,
1361 "meta": null,
1362 "execution": null
1363 }
1364 ]
1365 },
1366 "error": null
1367 }
1368 ],
1369 "issues": [
1370 {
1371 "code": "W004",
1372 "message": "The MCP server is not in our registry.",
1373 "reference": [
1374 0,
1375 null
1376 ],
1377 "extra_data": null
1378 },
1379 {
1380 "code": "W011",
1381 "message": "Third-party content exposure detected (high risk: 0.70). The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.",
1382 "reference": [
1383 0,
1384 null
1385 ],
1386 "extra_data": {
1387 "risk_score": 0.7,
1388 "reason": "The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.",
1389 "thought_process": "<reason>The skill directly queries the public SODAX backend API (e.g., SodaxApiClient / amped_user_intents, and services like priceService and positionAggregator that call sodax.moneyMarket.data.getReservesHumanized) to ingest user intent history, reserve data and token metadata from third-party, user-associated sources, which the agent reads and interprets as part of its workflows.</reason>\n<answer>0.7</answer>",
1390 "severity": "high"
1391 }
1392 },
1393 {
1394 "code": "W009",
1395 "message": "Direct money access detected (high risk: 1.00). The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.",
1396 "reference": [
1397 0,
1398 null
1399 ],
1400 "extra_data": {
1401 "risk_score": 1.0,
1402 "reason": "The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.",
1403 "thought_process": "<reason>The skill is explicitly designed to perform on-chain financial operations. It exposes concrete crypto/blockchain transaction tools (amped_swap_execute, amped_bridge_execute, amped_mm_supply, amped_mm_borrow, amped_mm_repay, amped_mm_withdraw, etc.), wallet management with private-key-backed execute mode, signing/execution backends (localKey, Bankr), and cross-chain borrow/bridge functionality. These are specific APIs to move funds, create/send transactions, and manage wallets \u2014 i.e., direct financial execution (crypto/blockchain). Therefore it meets the \"Direct Financial Execution\" criteria.</reason>\n<answer>1</answer>",
1404 "severity": "high"
1405 }
1406 }
1407 ],
1408 "labels": [
1409 [
1410 {
1411 "is_public_sink": 0,
1412 "destructive": 0,
1413 "untrusted_content": 0,
1414 "private_data": 0
1415 },
1416 {
1417 "is_public_sink": 0,
1418 "destructive": 0,
1419 "untrusted_content": 0,
1420 "private_data": 0
1421 },
1422 {
1423 "is_public_sink": 0,
1424 "destructive": 0,
1425 "untrusted_content": 0,
1426 "private_data": 0
1427 },
1428 {
1429 "is_public_sink": 0,
1430 "destructive": 0,
1431 "untrusted_content": 0,
1432 "private_data": 0
1433 },
1434 {
1435 "is_public_sink": 0,
1436 "destructive": 0,
1437 "untrusted_content": 0,
1438 "private_data": 0
1439 },
1440 {
1441 "is_public_sink": 0,
1442 "destructive": 0,
1443 "untrusted_content": 0,
1444 "private_data": 0
1445 },
1446 {
1447 "is_public_sink": 0,
1448 "destructive": 0,
1449 "untrusted_content": 0,
1450 "private_data": 0
1451 },
1452 {
1453 "is_public_sink": 0,
1454 "destructive": 0,
1455 "untrusted_content": 0,
1456 "private_data": 0
1457 },
1458 {
1459 "is_public_sink": 0,
1460 "destructive": 0,
1461 "untrusted_content": 0,
1462 "private_data": 0
1463 },
1464 {
1465 "is_public_sink": 0,
1466 "destructive": 0,
1467 "untrusted_content": 0,
1468 "private_data": 0
1469 },
1470 {
1471 "is_public_sink": 0,
1472 "destructive": 0,
1473 "untrusted_content": 0,
1474 "private_data": 0
1475 },
1476 {
1477 "is_public_sink": 0,
1478 "destructive": 0,
1479 "untrusted_content": 0,
1480 "private_data": 0
1481 },
1482 {
1483 "is_public_sink": 0,
1484 "destructive": 0,
1485 "untrusted_content": 0,
1486 "private_data": 0
1487 },
1488 {
1489 "is_public_sink": 0,
1490 "destructive": 0,
1491 "untrusted_content": 0,
1492 "private_data": 0
1493 },
1494 {
1495 "is_public_sink": 0,
1496 "destructive": 0,
1497 "untrusted_content": 0,
1498 "private_data": 0
1499 },
1500 {
1501 "is_public_sink": 0,
1502 "destructive": 0,
1503 "untrusted_content": 0,
1504 "private_data": 0
1505 },
1506 {
1507 "is_public_sink": 0,
1508 "destructive": 0,
1509 "untrusted_content": 0,
1510 "private_data": 0
1511 },
1512 {
1513 "is_public_sink": 0,
1514 "destructive": 0,
1515 "untrusted_content": 0,
1516 "private_data": 0
1517 },
1518 {
1519 "is_public_sink": 0,
1520 "destructive": 0,
1521 "untrusted_content": 0,
1522 "private_data": 0
1523 },
1524 {
1525 "is_public_sink": 0,
1526 "destructive": 0,
1527 "untrusted_content": 0,
1528 "private_data": 0
1529 },
1530 {
1531 "is_public_sink": 0,
1532 "destructive": 0,
1533 "untrusted_content": 0,
1534 "private_data": 0
1535 },
1536 {
1537 "is_public_sink": 0,
1538 "destructive": 0,
1539 "untrusted_content": 0,
1540 "private_data": 0
1541 },
1542 {
1543 "is_public_sink": 0,
1544 "destructive": 0,
1545 "untrusted_content": 0,
1546 "private_data": 0
1547 },
1548 {
1549 "is_public_sink": 0,
1550 "destructive": 0,
1551 "untrusted_content": 0,
1552 "private_data": 0
1553 },
1554 {
1555 "is_public_sink": 0,
1556 "destructive": 0,
1557 "untrusted_content": 0,
1558 "private_data": 0
1559 },
1560 {
1561 "is_public_sink": 0,
1562 "destructive": 0,
1563 "untrusted_content": 0,
1564 "private_data": 0
1565 },
1566 {
1567 "is_public_sink": 0,
1568 "destructive": 0,
1569 "untrusted_content": 0,
1570 "private_data": 0
1571 },
1572 {
1573 "is_public_sink": 0,
1574 "destructive": 0,
1575 "untrusted_content": 0,
1576 "private_data": 0
1577 },
1578 {
1579 "is_public_sink": 0,
1580 "destructive": 0,
1581 "untrusted_content": 0,
1582 "private_data": 0
1583 },
1584 {
1585 "is_public_sink": 0,
1586 "destructive": 0,
1587 "untrusted_content": 0,
1588 "private_data": 0
1589 },
1590 {
1591 "is_public_sink": 0,
1592 "destructive": 0,
1593 "untrusted_content": 0,
1594 "private_data": 0
1595 },
1596 {
1597 "is_public_sink": 0,
1598 "destructive": 0,
1599 "untrusted_content": 0,
1600 "private_data": 0
1601 },
1602 {
1603 "is_public_sink": 0,
1604 "destructive": 0,
1605 "untrusted_content": 0,
1606 "private_data": 0
1607 },
1608 {
1609 "is_public_sink": 0,
1610 "destructive": 0,
1611 "untrusted_content": 0,
1612 "private_data": 0
1613 },
1614 {
1615 "is_public_sink": 0,
1616 "destructive": 0,
1617 "untrusted_content": 0,
1618 "private_data": 0
1619 },
1620 {
1621 "is_public_sink": 0,
1622 "destructive": 0,
1623 "untrusted_content": 0,
1624 "private_data": 0
1625 },
1626 {
1627 "is_public_sink": 0,
1628 "destructive": 0,
1629 "untrusted_content": 0,
1630 "private_data": 0
1631 },
1632 {
1633 "is_public_sink": 0,
1634 "destructive": 0,
1635 "untrusted_content": 0,
1636 "private_data": 0
1637 },
1638 {
1639 "is_public_sink": 0,
1640 "destructive": 0,
1641 "untrusted_content": 0,
1642 "private_data": 0
1643 },
1644 {
1645 "is_public_sink": 0,
1646 "destructive": 0,
1647 "untrusted_content": 0,
1648 "private_data": 0
1649 },
1650 {
1651 "is_public_sink": 0,
1652 "destructive": 0,
1653 "untrusted_content": 0,
1654 "private_data": 0
1655 },
1656 {
1657 "is_public_sink": 0,
1658 "destructive": 0,
1659 "untrusted_content": 0,
1660 "private_data": 0
1661 },
1662 {
1663 "is_public_sink": 0,
1664 "destructive": 0,
1665 "untrusted_content": 0,
1666 "private_data": 0
1667 },
1668 {
1669 "is_public_sink": 0,
1670 "destructive": 0,
1671 "untrusted_content": 0,
1672 "private_data": 0
1673 },
1674 {
1675 "is_public_sink": 0,
1676 "destructive": 0,
1677 "untrusted_content": 0,
1678 "private_data": 0
1679 },
1680 {
1681 "is_public_sink": 0,
1682 "destructive": 0,
1683 "untrusted_content": 0,
1684 "private_data": 0
1685 },
1686 {
1687 "is_public_sink": 0,
1688 "destructive": 0,
1689 "untrusted_content": 0,
1690 "private_data": 0
1691 },
1692 {
1693 "is_public_sink": 0,
1694 "destructive": 0,
1695 "untrusted_content": 0,
1696 "private_data": 0
1697 },
1698 {
1699 "is_public_sink": 0,
1700 "destructive": 0,
1701 "untrusted_content": 0,
1702 "private_data": 0
1703 },
1704 {
1705 "is_public_sink": 0,
1706 "destructive": 0,
1707 "untrusted_content": 0,
1708 "private_data": 0
1709 },
1710 {
1711 "is_public_sink": 0,
1712 "destructive": 0,
1713 "untrusted_content": 0,
1714 "private_data": 0
1715 },
1716 {
1717 "is_public_sink": 0,
1718 "destructive": 0,
1719 "untrusted_content": 0,
1720 "private_data": 0
1721 },
1722 {
1723 "is_public_sink": 0,
1724 "destructive": 0,
1725 "untrusted_content": 0,
1726 "private_data": 0
1727 },
1728 {
1729 "is_public_sink": 0,
1730 "destructive": 0,
1731 "untrusted_content": 0,
1732 "private_data": 0
1733 },
1734 {
1735 "is_public_sink": 0,
1736 "destructive": 0,
1737 "untrusted_content": 0,
1738 "private_data": 0
1739 },
1740 {
1741 "is_public_sink": 0,
1742 "destructive": 0,
1743 "untrusted_content": 0,
1744 "private_data": 0
1745 },
1746 {
1747 "is_public_sink": 0,
1748 "destructive": 0,
1749 "untrusted_content": 0,
1750 "private_data": 0
1751 },
1752 {
1753 "is_public_sink": 0,
1754 "destructive": 0,
1755 "untrusted_content": 0,
1756 "private_data": 0
1757 },
1758 {
1759 "is_public_sink": 0,
1760 "destructive": 0,
1761 "untrusted_content": 0,
1762 "private_data": 0
1763 },
1764 {
1765 "is_public_sink": 0,
1766 "destructive": 0,
1767 "untrusted_content": 0,
1768 "private_data": 0
1769 },
1770 {
1771 "is_public_sink": 0,
1772 "destructive": 0,
1773 "untrusted_content": 0,
1774 "private_data": 0
1775 },
1776 {
1777 "is_public_sink": 0,
1778 "destructive": 0,
1779 "untrusted_content": 0,
1780 "private_data": 0
1781 },
1782 {
1783 "is_public_sink": 0,
1784 "destructive": 0,
1785 "untrusted_content": 0,
1786 "private_data": 0
1787 },
1788 {
1789 "is_public_sink": 0,
1790 "destructive": 0,
1791 "untrusted_content": 0,
1792 "private_data": 0
1793 },
1794 {
1795 "is_public_sink": 0,
1796 "destructive": 0,
1797 "untrusted_content": 0,
1798 "private_data": 0
1799 },
1800 {
1801 "is_public_sink": 0,
1802 "destructive": 0,
1803 "untrusted_content": 0,
1804 "private_data": 0
1805 },
1806 {
1807 "is_public_sink": 0,
1808 "destructive": 0,
1809 "untrusted_content": 0,
1810 "private_data": 0
1811 },
1812 {
1813 "is_public_sink": 0,
1814 "destructive": 0,
1815 "untrusted_content": 0,
1816 "private_data": 0
1817 },
1818 {
1819 "is_public_sink": 0,
1820 "destructive": 0,
1821 "untrusted_content": 0,
1822 "private_data": 0
1823 },
1824 {
1825 "is_public_sink": 0,
1826 "destructive": 0,
1827 "untrusted_content": 0,
1828 "private_data": 0
1829 },
1830 {
1831 "is_public_sink": 0,
1832 "destructive": 0,
1833 "untrusted_content": 0,
1834 "private_data": 0
1835 },
1836 {
1837 "is_public_sink": 0,
1838 "destructive": 0,
1839 "untrusted_content": 0,
1840 "private_data": 0
1841 },
1842 {
1843 "is_public_sink": 0,
1844 "destructive": 0,
1845 "untrusted_content": 0,
1846 "private_data": 0
1847 },
1848 {
1849 "is_public_sink": 0,
1850 "destructive": 0,
1851 "untrusted_content": 0,
1852 "private_data": 0
1853 },
1854 {
1855 "is_public_sink": 0,
1856 "destructive": 0,
1857 "untrusted_content": 0,
1858 "private_data": 0
1859 },
1860 {
1861 "is_public_sink": 0,
1862 "destructive": 0,
1863 "untrusted_content": 0,
1864 "private_data": 0
1865 },
1866 {
1867 "is_public_sink": 0,
1868 "destructive": 0,
1869 "untrusted_content": 0,
1870 "private_data": 0
1871 },
1872 {
1873 "is_public_sink": 0,
1874 "destructive": 0,
1875 "untrusted_content": 0,
1876 "private_data": 0
1877 },
1878 {
1879 "is_public_sink": 0,
1880 "destructive": 0,
1881 "untrusted_content": 0,
1882 "private_data": 0
1883 },
1884 {
1885 "is_public_sink": 0,
1886 "destructive": 0,
1887 "untrusted_content": 0,
1888 "private_data": 0
1889 },
1890 {
1891 "is_public_sink": 0,
1892 "destructive": 0,
1893 "untrusted_content": 0,
1894 "private_data": 0
1895 },
1896 {
1897 "is_public_sink": 0,
1898 "destructive": 0,
1899 "untrusted_content": 0,
1900 "private_data": 0
1901 },
1902 {
1903 "is_public_sink": 0,
1904 "destructive": 0,
1905 "untrusted_content": 0,
1906 "private_data": 0
1907 },
1908 {
1909 "is_public_sink": 0,
1910 "destructive": 0,
1911 "untrusted_content": 0,
1912 "private_data": 0
1913 },
1914 {
1915 "is_public_sink": 0,
1916 "destructive": 0,
1917 "untrusted_content": 0,
1918 "private_data": 0
1919 },
1920 {
1921 "is_public_sink": 0,
1922 "destructive": 0,
1923 "untrusted_content": 0,
1924 "private_data": 0
1925 },
1926 {
1927 "is_public_sink": 0,
1928 "destructive": 0,
1929 "untrusted_content": 0,
1930 "private_data": 0
1931 },
1932 {
1933 "is_public_sink": 0,
1934 "destructive": 0,
1935 "untrusted_content": 0,
1936 "private_data": 0
1937 },
1938 {
1939 "is_public_sink": 0,
1940 "destructive": 0,
1941 "untrusted_content": 0,
1942 "private_data": 0
1943 },
1944 {
1945 "is_public_sink": 0,
1946 "destructive": 0,
1947 "untrusted_content": 0,
1948 "private_data": 0
1949 },
1950 {
1951 "is_public_sink": 0,
1952 "destructive": 0,
1953 "untrusted_content": 0,
1954 "private_data": 0
1955 },
1956 {
1957 "is_public_sink": 0,
1958 "destructive": 0,
1959 "untrusted_content": 0,
1960 "private_data": 0
1961 },
1962 {
1963 "is_public_sink": 0,
1964 "destructive": 0,
1965 "untrusted_content": 0,
1966 "private_data": 0
1967 },
1968 {
1969 "is_public_sink": 0,
1970 "destructive": 0,
1971 "untrusted_content": 0,
1972 "private_data": 0
1973 },
1974 {
1975 "is_public_sink": 0,
1976 "destructive": 0,
1977 "untrusted_content": 0,
1978 "private_data": 0
1979 },
1980 {
1981 "is_public_sink": 0,
1982 "destructive": 0,
1983 "untrusted_content": 0,
1984 "private_data": 0
1985 },
1986 {
1987 "is_public_sink": 0,
1988 "destructive": 0,
1989 "untrusted_content": 0,
1990 "private_data": 0
1991 },
1992 {
1993 "is_public_sink": 0,
1994 "destructive": 0,
1995 "untrusted_content": 0,
1996 "private_data": 0
1997 },
1998 {
1999 "is_public_sink": 0,
2000 "destructive": 0,
2001 "untrusted_content": 0,
2002 "private_data": 0
2003 },
2004 {
2005 "is_public_sink": 0,
2006 "destructive": 0,
2007 "untrusted_content": 0,
2008 "private_data": 0
2009 },
2010 {
2011 "is_public_sink": 0,
2012 "destructive": 0,
2013 "untrusted_content": 0,
2014 "private_data": 0
2015 },
2016 {
2017 "is_public_sink": 0,
2018 "destructive": 0,
2019 "untrusted_content": 0,
2020 "private_data": 0
2021 },
2022 {
2023 "is_public_sink": 0,
2024 "destructive": 0,
2025 "untrusted_content": 0,
2026 "private_data": 0
2027 },
2028 {
2029 "is_public_sink": 0,
2030 "destructive": 0,
2031 "untrusted_content": 0,
2032 "private_data": 0
2033 },
2034 {
2035 "is_public_sink": 0,
2036 "destructive": 0,
2037 "untrusted_content": 0,
2038 "private_data": 0
2039 },
2040 {
2041 "is_public_sink": 0,
2042 "destructive": 0,
2043 "untrusted_content": 0,
2044 "private_data": 0
2045 },
2046 {
2047 "is_public_sink": 0,
2048 "destructive": 0,
2049 "untrusted_content": 0,
2050 "private_data": 0
2051 },
2052 {
2053 "is_public_sink": 0,
2054 "destructive": 0,
2055 "untrusted_content": 0,
2056 "private_data": 0
2057 },
2058 {
2059 "is_public_sink": 0,
2060 "destructive": 0,
2061 "untrusted_content": 0,
2062 "private_data": 0
2063 },
2064 {
2065 "is_public_sink": 0,
2066 "destructive": 0,
2067 "untrusted_content": 0,
2068 "private_data": 0
2069 },
2070 {
2071 "is_public_sink": 0,
2072 "destructive": 0,
2073 "untrusted_content": 0,
2074 "private_data": 0
2075 },
2076 {
2077 "is_public_sink": 0,
2078 "destructive": 0,
2079 "untrusted_content": 0,
2080 "private_data": 0
2081 },
2082 {
2083 "is_public_sink": 0,
2084 "destructive": 0,
2085 "untrusted_content": 0,
2086 "private_data": 0
2087 },
2088 {
2089 "is_public_sink": 0,
2090 "destructive": 0,
2091 "untrusted_content": 0,
2092 "private_data": 0
2093 },
2094 {
2095 "is_public_sink": 0,
2096 "destructive": 0,
2097 "untrusted_content": 0,
2098 "private_data": 0
2099 },
2100 {
2101 "is_public_sink": 0,
2102 "destructive": 0,
2103 "untrusted_content": 0,
2104 "private_data": 0
2105 },
2106 {
2107 "is_public_sink": 0,
2108 "destructive": 0,
2109 "untrusted_content": 0,
2110 "private_data": 0
2111 },
2112 {
2113 "is_public_sink": 0,
2114 "destructive": 0,
2115 "untrusted_content": 0,
2116 "private_data": 0
2117 },
2118 {
2119 "is_public_sink": 0,
2120 "destructive": 0,
2121 "untrusted_content": 0,
2122 "private_data": 0
2123 },
2124 {
2125 "is_public_sink": 0,
2126 "destructive": 0,
2127 "untrusted_content": 0,
2128 "private_data": 0
2129 }
2130 ]
2131 ],
2132 "error": null
2133 }
2134}
2135
2136Process exited with code 0
2137✓ Completed in 218643ms
npm-audit
0 findings154249ms
No findings — all checks passed.
View logs
npm-audit154249ms
1[2026-02-11T18:38:52.512Z] $ npm audit --json --prefix /tmp/clawguard-scan-m2CGz8/repo/skills/ampedfinance/amped-defi-publish
2{
3 "error": {
4 "code": "ENOLOCK",
5 "summary": "This command requires an existing lockfile.",
6 "detail": "Try creating one first with: npm i --package-lock-only\nOriginal error: loadVirtual requires existing shrinkwrap file"
7 }
8}
9
10⚠ stderr output:
11npm error code ENOLOCK
12npm error audit This command requires an existing lockfile.
13npm error audit Try creating one first with: npm i --package-lock-only
14npm error audit Original error: loadVirtual requires existing shrinkwrap file
15npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-02-11T18_38_35_265Z-debug-0.log
16
17Process exited with code 1
18✓ Completed in 154249ms

Scanned: 2/11/2026, 6:41:12 PM