recon-crypto-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/abis/aave-pool.d.ts +174 -0
  4. package/dist/abis/aave-pool.js +101 -0
  5. package/dist/abis/aave-pool.js.map +1 -0
  6. package/dist/abis/aave-ui-pool-data-provider.d.ts +232 -0
  7. package/dist/abis/aave-ui-pool-data-provider.js +107 -0
  8. package/dist/abis/aave-ui-pool-data-provider.js.map +1 -0
  9. package/dist/abis/access-control.d.ts +94 -0
  10. package/dist/abis/access-control.js +51 -0
  11. package/dist/abis/access-control.js.map +1 -0
  12. package/dist/abis/compound-comet.d.ts +120 -0
  13. package/dist/abis/compound-comet.js +84 -0
  14. package/dist/abis/compound-comet.js.map +1 -0
  15. package/dist/abis/eigenlayer-delegation-manager.d.ts +23 -0
  16. package/dist/abis/eigenlayer-delegation-manager.js +18 -0
  17. package/dist/abis/eigenlayer-delegation-manager.js.map +1 -0
  18. package/dist/abis/eigenlayer-strategy-manager.d.ts +53 -0
  19. package/dist/abis/eigenlayer-strategy-manager.js +47 -0
  20. package/dist/abis/eigenlayer-strategy-manager.js.map +1 -0
  21. package/dist/abis/erc20.d.ts +78 -0
  22. package/dist/abis/erc20.js +10 -0
  23. package/dist/abis/erc20.js.map +1 -0
  24. package/dist/abis/lido.d.ts +80 -0
  25. package/dist/abis/lido.js +60 -0
  26. package/dist/abis/lido.js.map +1 -0
  27. package/dist/abis/morpho-blue.d.ts +321 -0
  28. package/dist/abis/morpho-blue.js +193 -0
  29. package/dist/abis/morpho-blue.js.map +1 -0
  30. package/dist/abis/uniswap-pool.d.ts +70 -0
  31. package/dist/abis/uniswap-pool.js +53 -0
  32. package/dist/abis/uniswap-pool.js.map +1 -0
  33. package/dist/abis/uniswap-position-manager.d.ts +71 -0
  34. package/dist/abis/uniswap-position-manager.js +41 -0
  35. package/dist/abis/uniswap-position-manager.js.map +1 -0
  36. package/dist/config/cache.d.ts +12 -0
  37. package/dist/config/cache.js +12 -0
  38. package/dist/config/cache.js.map +1 -0
  39. package/dist/config/chains.d.ts +24 -0
  40. package/dist/config/chains.js +158 -0
  41. package/dist/config/chains.js.map +1 -0
  42. package/dist/config/contracts.d.ts +107 -0
  43. package/dist/config/contracts.js +123 -0
  44. package/dist/config/contracts.js.map +1 -0
  45. package/dist/config/user-config.d.ts +15 -0
  46. package/dist/config/user-config.js +93 -0
  47. package/dist/config/user-config.js.map +1 -0
  48. package/dist/data/apis/etherscan.d.ts +30 -0
  49. package/dist/data/apis/etherscan.js +109 -0
  50. package/dist/data/apis/etherscan.js.map +1 -0
  51. package/dist/data/cache.d.ts +18 -0
  52. package/dist/data/cache.js +47 -0
  53. package/dist/data/cache.js.map +1 -0
  54. package/dist/data/format.d.ts +6 -0
  55. package/dist/data/format.js +44 -0
  56. package/dist/data/format.js.map +1 -0
  57. package/dist/data/prices.d.ts +12 -0
  58. package/dist/data/prices.js +81 -0
  59. package/dist/data/prices.js.map +1 -0
  60. package/dist/data/rpc.d.ts +17 -0
  61. package/dist/data/rpc.js +68 -0
  62. package/dist/data/rpc.js.map +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.js +344 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/modules/balances/index.d.ts +20 -0
  67. package/dist/modules/balances/index.js +49 -0
  68. package/dist/modules/balances/index.js.map +1 -0
  69. package/dist/modules/balances/schemas.d.ts +32 -0
  70. package/dist/modules/balances/schemas.js +21 -0
  71. package/dist/modules/balances/schemas.js.map +1 -0
  72. package/dist/modules/bitcoin/index.d.ts +26 -0
  73. package/dist/modules/bitcoin/index.js +128 -0
  74. package/dist/modules/bitcoin/index.js.map +1 -0
  75. package/dist/modules/bitcoin/schemas.d.ts +49 -0
  76. package/dist/modules/bitcoin/schemas.js +51 -0
  77. package/dist/modules/bitcoin/schemas.js.map +1 -0
  78. package/dist/modules/bitcoin/send.d.ts +52 -0
  79. package/dist/modules/bitcoin/send.js +158 -0
  80. package/dist/modules/bitcoin/send.js.map +1 -0
  81. package/dist/modules/bitcoin/utxo.d.ts +99 -0
  82. package/dist/modules/bitcoin/utxo.js +116 -0
  83. package/dist/modules/bitcoin/utxo.js.map +1 -0
  84. package/dist/modules/compound/actions.d.ts +8 -0
  85. package/dist/modules/compound/actions.js +154 -0
  86. package/dist/modules/compound/actions.js.map +1 -0
  87. package/dist/modules/compound/index.d.ts +26 -0
  88. package/dist/modules/compound/index.js +141 -0
  89. package/dist/modules/compound/index.js.map +1 -0
  90. package/dist/modules/compound/schemas.d.ts +95 -0
  91. package/dist/modules/compound/schemas.js +37 -0
  92. package/dist/modules/compound/schemas.js.map +1 -0
  93. package/dist/modules/execution/index.d.ts +51 -0
  94. package/dist/modules/execution/index.js +271 -0
  95. package/dist/modules/execution/index.js.map +1 -0
  96. package/dist/modules/execution/schemas.d.ts +183 -0
  97. package/dist/modules/execution/schemas.js +88 -0
  98. package/dist/modules/execution/schemas.js.map +1 -0
  99. package/dist/modules/feedback/index.d.ts +28 -0
  100. package/dist/modules/feedback/index.js +161 -0
  101. package/dist/modules/feedback/index.js.map +1 -0
  102. package/dist/modules/feedback/rate-limit.d.ts +15 -0
  103. package/dist/modules/feedback/rate-limit.js +110 -0
  104. package/dist/modules/feedback/rate-limit.js.map +1 -0
  105. package/dist/modules/feedback/schemas.d.ts +41 -0
  106. package/dist/modules/feedback/schemas.js +40 -0
  107. package/dist/modules/feedback/schemas.js.map +1 -0
  108. package/dist/modules/morpho/actions.d.ts +8 -0
  109. package/dist/modules/morpho/actions.js +265 -0
  110. package/dist/modules/morpho/actions.js.map +1 -0
  111. package/dist/modules/morpho/index.d.ts +26 -0
  112. package/dist/modules/morpho/index.js +94 -0
  113. package/dist/modules/morpho/index.js.map +1 -0
  114. package/dist/modules/morpho/schemas.d.ts +130 -0
  115. package/dist/modules/morpho/schemas.js +34 -0
  116. package/dist/modules/morpho/schemas.js.map +1 -0
  117. package/dist/modules/portfolio/index.d.ts +3 -0
  118. package/dist/modules/portfolio/index.js +197 -0
  119. package/dist/modules/portfolio/index.js.map +1 -0
  120. package/dist/modules/portfolio/schemas.d.ts +20 -0
  121. package/dist/modules/portfolio/schemas.js +15 -0
  122. package/dist/modules/portfolio/schemas.js.map +1 -0
  123. package/dist/modules/positions/aave.d.ts +22 -0
  124. package/dist/modules/positions/aave.js +197 -0
  125. package/dist/modules/positions/aave.js.map +1 -0
  126. package/dist/modules/positions/actions.d.ts +18 -0
  127. package/dist/modules/positions/actions.js +205 -0
  128. package/dist/modules/positions/actions.js.map +1 -0
  129. package/dist/modules/positions/index.d.ts +37 -0
  130. package/dist/modules/positions/index.js +59 -0
  131. package/dist/modules/positions/index.js.map +1 -0
  132. package/dist/modules/positions/schemas.d.ts +55 -0
  133. package/dist/modules/positions/schemas.js +25 -0
  134. package/dist/modules/positions/schemas.js.map +1 -0
  135. package/dist/modules/positions/uniswap.d.ts +4 -0
  136. package/dist/modules/positions/uniswap.js +181 -0
  137. package/dist/modules/positions/uniswap.js.map +1 -0
  138. package/dist/modules/prices/index.d.ts +19 -0
  139. package/dist/modules/prices/index.js +22 -0
  140. package/dist/modules/prices/index.js.map +1 -0
  141. package/dist/modules/security/index.d.ts +26 -0
  142. package/dist/modules/security/index.js +13 -0
  143. package/dist/modules/security/index.js.map +1 -0
  144. package/dist/modules/security/permissions.d.ts +8 -0
  145. package/dist/modules/security/permissions.js +96 -0
  146. package/dist/modules/security/permissions.js.map +1 -0
  147. package/dist/modules/security/risk-score.d.ts +18 -0
  148. package/dist/modules/security/risk-score.js +116 -0
  149. package/dist/modules/security/risk-score.js.map +1 -0
  150. package/dist/modules/security/schemas.d.ts +31 -0
  151. package/dist/modules/security/schemas.js +16 -0
  152. package/dist/modules/security/schemas.js.map +1 -0
  153. package/dist/modules/security/verification.d.ts +4 -0
  154. package/dist/modules/security/verification.js +82 -0
  155. package/dist/modules/security/verification.js.map +1 -0
  156. package/dist/modules/shared/approval.d.ts +71 -0
  157. package/dist/modules/shared/approval.js +130 -0
  158. package/dist/modules/shared/approval.js.map +1 -0
  159. package/dist/modules/staking/actions.d.ts +22 -0
  160. package/dist/modules/staking/actions.js +100 -0
  161. package/dist/modules/staking/actions.js.map +1 -0
  162. package/dist/modules/staking/eigenlayer.d.ts +14 -0
  163. package/dist/modules/staking/eigenlayer.js +105 -0
  164. package/dist/modules/staking/eigenlayer.js.map +1 -0
  165. package/dist/modules/staking/index.d.ts +24 -0
  166. package/dist/modules/staking/index.js +59 -0
  167. package/dist/modules/staking/index.js.map +1 -0
  168. package/dist/modules/staking/lido.d.ts +14 -0
  169. package/dist/modules/staking/lido.js +113 -0
  170. package/dist/modules/staking/lido.js.map +1 -0
  171. package/dist/modules/staking/schemas.d.ts +34 -0
  172. package/dist/modules/staking/schemas.js +20 -0
  173. package/dist/modules/staking/schemas.js.map +1 -0
  174. package/dist/modules/swap/index.d.ts +47 -0
  175. package/dist/modules/swap/index.js +376 -0
  176. package/dist/modules/swap/index.js.map +1 -0
  177. package/dist/modules/swap/lifi.d.ts +17 -0
  178. package/dist/modules/swap/lifi.js +44 -0
  179. package/dist/modules/swap/lifi.js.map +1 -0
  180. package/dist/modules/swap/oneinch.d.ts +26 -0
  181. package/dist/modules/swap/oneinch.js +33 -0
  182. package/dist/modules/swap/oneinch.js.map +1 -0
  183. package/dist/modules/swap/schemas.d.ts +65 -0
  184. package/dist/modules/swap/schemas.js +46 -0
  185. package/dist/modules/swap/schemas.js.map +1 -0
  186. package/dist/setup.d.ts +2 -0
  187. package/dist/setup.js +257 -0
  188. package/dist/setup.js.map +1 -0
  189. package/dist/signing/pre-sign-check.d.ts +8 -0
  190. package/dist/signing/pre-sign-check.js +231 -0
  191. package/dist/signing/pre-sign-check.js.map +1 -0
  192. package/dist/signing/session.d.ts +28 -0
  193. package/dist/signing/session.js +26 -0
  194. package/dist/signing/session.js.map +1 -0
  195. package/dist/signing/tx-store.d.ts +29 -0
  196. package/dist/signing/tx-store.js +85 -0
  197. package/dist/signing/tx-store.js.map +1 -0
  198. package/dist/signing/walletconnect.d.ts +33 -0
  199. package/dist/signing/walletconnect.js +226 -0
  200. package/dist/signing/walletconnect.js.map +1 -0
  201. package/dist/types/index.d.ts +222 -0
  202. package/dist/types/index.js +18 -0
  203. package/dist/types/index.js.map +1 -0
  204. package/package.json +134 -0
@@ -0,0 +1,161 @@
1
+ import { requestCapabilityInput } from "./schemas.js";
2
+ import { checkAndRecord, hashPayload, RATE_LIMITS } from "./rate-limit.js";
3
+ const REPO_OWNER = "szhygulin";
4
+ const REPO_NAME = "recon-crypto-mcp";
5
+ const ISSUE_LABEL = "agent-request";
6
+ const USER_AGENT = "recon-crypto-mcp/0.1.0 capability-request";
7
+ const POST_TIMEOUT_MS = 8_000;
8
+ const MAX_POST_BODY_BYTES = 16_384;
9
+ const MAX_PREFILLED_URL_BYTES = 7168;
10
+ const TRUNCATION_MARKER = "\n\n_...(body truncated to fit in GitHub's prefilled-URL length limit — paste the full context into the issue after opening)_";
11
+ /**
12
+ * Neutralize GitHub @-mentions in agent-supplied strings before they enter the
13
+ * issue body. Without this, a prompt-injected agent could craft a feedback
14
+ * payload that pings arbitrary GitHub users when the user clicks "Submit".
15
+ * Inserting a zero-width space after `@` keeps the text readable while
16
+ * breaking the mention parser.
17
+ */
18
+ function neutralizeMentions(input) {
19
+ return input.replace(/@/g, "@\u200B");
20
+ }
21
+ /** Escape triple-backticks so agent-supplied strings can't break out of fenced code blocks. */
22
+ function escapeCodeFences(input) {
23
+ return input.replace(/```/g, "`\u200B``");
24
+ }
25
+ export { requestCapabilityInput };
26
+ export async function requestCapability(args) {
27
+ const { summary, description, category, context, agentName } = args;
28
+ const hash = hashPayload(summary, description);
29
+ const check = checkAndRecord(hash);
30
+ if (!check.ok) {
31
+ const err = new Error(check.reason);
32
+ if ("retryAfterSeconds" in check && check.retryAfterSeconds !== undefined) {
33
+ err.retryAfterSeconds = check.retryAfterSeconds;
34
+ }
35
+ throw err;
36
+ }
37
+ const title = `[agent-request] ${summary}`;
38
+ const body = buildIssueBody({ description, category, context, agentName });
39
+ const labels = [ISSUE_LABEL, category].filter((v) => Boolean(v));
40
+ const payload = { title, body, labels };
41
+ const endpoint = process.env.RECON_FEEDBACK_ENDPOINT?.trim();
42
+ if (endpoint) {
43
+ if (!/^https:\/\//i.test(endpoint)) {
44
+ throw new Error("RECON_FEEDBACK_ENDPOINT must be an https:// URL. Refusing to submit over plaintext.");
45
+ }
46
+ return await postToEndpoint(endpoint, payload);
47
+ }
48
+ const { url: issueUrl, truncated } = buildPrefilledIssueUrl(payload);
49
+ return {
50
+ status: "prefilled_url",
51
+ message: "No data has been transmitted. Show this URL to the user — opening it prefills a GitHub issue on the recon-crypto-mcp repo; " +
52
+ "submission requires the user to click 'Submit new issue'." +
53
+ (truncated
54
+ ? " The issue body was truncated to fit GitHub's prefilled-URL length limit; tell the user to paste the full context into the issue before submitting."
55
+ : ""),
56
+ issueUrl,
57
+ repo: `${REPO_OWNER}/${REPO_NAME}`,
58
+ title,
59
+ labels,
60
+ bodyTruncated: truncated,
61
+ rateLimit: RATE_LIMITS,
62
+ };
63
+ }
64
+ function buildIssueBody(opts) {
65
+ const lines = [];
66
+ lines.push("### Description", "", neutralizeMentions(opts.description), "");
67
+ if (opts.category) {
68
+ lines.push(`**Category:** \`${opts.category}\``, "");
69
+ }
70
+ if (opts.context && Object.values(opts.context).some((v) => v)) {
71
+ lines.push("### Context");
72
+ if (opts.context.toolAttempted) {
73
+ lines.push(`- **Tool attempted:** \`${escapeCodeFences(opts.context.toolAttempted)}\``);
74
+ }
75
+ if (opts.context.chain) {
76
+ lines.push(`- **Chain:** ${neutralizeMentions(opts.context.chain)}`);
77
+ }
78
+ if (opts.context.errorObserved) {
79
+ const safe = escapeCodeFences(neutralizeMentions(opts.context.errorObserved));
80
+ lines.push("- **Error observed:**");
81
+ lines.push(" ```");
82
+ for (const l of safe.split("\n"))
83
+ lines.push(` ${l}`);
84
+ lines.push(" ```");
85
+ }
86
+ lines.push("");
87
+ }
88
+ const agent = opts.agentName ? neutralizeMentions(opts.agentName) : undefined;
89
+ const footer = "_Submitted via the `request_capability` tool in recon-crypto-mcp by an AI agent" +
90
+ (agent ? ` (${agent})` : "") +
91
+ "._";
92
+ lines.push("---", footer);
93
+ return lines.join("\n");
94
+ }
95
+ function buildPrefilledIssueUrl(payload) {
96
+ const base = `https://github.com/${REPO_OWNER}/${REPO_NAME}/issues/new`;
97
+ const build = (body) => {
98
+ const params = new URLSearchParams({
99
+ title: payload.title,
100
+ body,
101
+ labels: payload.labels.join(","),
102
+ });
103
+ return `${base}?${params.toString()}`;
104
+ };
105
+ const fullUrl = build(payload.body);
106
+ if (Buffer.byteLength(fullUrl, "utf8") <= MAX_PREFILLED_URL_BYTES) {
107
+ return { url: fullUrl, truncated: false };
108
+ }
109
+ // Binary-search the largest body length whose resulting URL still fits. The
110
+ // relationship body-length → encoded-URL-length isn't strictly linear
111
+ // (high-byte chars encode as %XX%XX), so we don't guess a ratio.
112
+ let lo = 0;
113
+ let hi = payload.body.length;
114
+ while (lo < hi) {
115
+ const mid = Math.floor((lo + hi + 1) / 2);
116
+ const candidate = payload.body.slice(0, mid) + TRUNCATION_MARKER;
117
+ if (Buffer.byteLength(build(candidate), "utf8") <= MAX_PREFILLED_URL_BYTES) {
118
+ lo = mid;
119
+ }
120
+ else {
121
+ hi = mid - 1;
122
+ }
123
+ }
124
+ const trimmedBody = payload.body.slice(0, lo) + TRUNCATION_MARKER;
125
+ return { url: build(trimmedBody), truncated: true };
126
+ }
127
+ async function postToEndpoint(url, payload) {
128
+ const serialized = JSON.stringify(payload);
129
+ if (Buffer.byteLength(serialized, "utf8") > MAX_POST_BODY_BYTES) {
130
+ throw new Error(`Capability-request payload exceeds ${MAX_POST_BODY_BYTES} bytes after serialization. Trim the description.`);
131
+ }
132
+ const controller = new AbortController();
133
+ const timeout = setTimeout(() => controller.abort(), POST_TIMEOUT_MS);
134
+ let res;
135
+ try {
136
+ res = await fetch(url, {
137
+ method: "POST",
138
+ headers: {
139
+ "content-type": "application/json",
140
+ "user-agent": USER_AGENT,
141
+ },
142
+ body: serialized,
143
+ signal: controller.signal,
144
+ });
145
+ }
146
+ finally {
147
+ clearTimeout(timeout);
148
+ }
149
+ if (!res.ok) {
150
+ const text = await res.text().catch(() => "");
151
+ throw new Error(`Capability-request endpoint returned ${res.status} ${res.statusText}${text ? `: ${text.slice(0, 200)}` : ""}`);
152
+ }
153
+ const data = await res.json().catch(() => null);
154
+ return {
155
+ status: "submitted",
156
+ endpoint: url,
157
+ response: data,
158
+ rateLimit: RATE_LIMITS,
159
+ };
160
+ }
161
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/feedback/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA8B,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE3E,MAAM,UAAU,GAAG,WAAW,CAAC;AAC/B,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,MAAM,WAAW,GAAG,eAAe,CAAC;AACpC,MAAM,UAAU,GAAG,2CAA2C,CAAC;AAC/D,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,iBAAiB,GACrB,+HAA+H,CAAC;AAElI;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACxC,CAAC;AAED,+FAA+F;AAC/F,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC5C,CAAC;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAKlC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAA2B;IACjE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACpE,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAE/C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAA2C,CAAC;QAC9E,IAAI,mBAAmB,IAAI,KAAK,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;YAC1E,GAAG,CAAC,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,CAAC;QAClD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG,mBAAmB,OAAO,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAEtD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,EAAE,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,qFAAqF,CACtF,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACrE,OAAO;QACL,MAAM,EAAE,eAAwB;QAChC,OAAO,EACL,6HAA6H;YAC7H,2DAA2D;YAC3D,CAAC,SAAS;gBACR,CAAC,CAAC,qJAAqJ;gBACvJ,CAAC,CAAC,EAAE,CAAC;QACT,QAAQ;QACR,IAAI,EAAE,GAAG,UAAU,IAAI,SAAS,EAAE;QAClC,KAAK;QACL,MAAM;QACN,aAAa,EAAE,SAAS;QACxB,SAAS,EAAE,WAAW;KACvB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAKvB;IACC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,EAAE,kBAAkB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;IAE5E,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,mBAAmB,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CACR,2BAA2B,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAC5E,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;YAC9E,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACvD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,MAAM,MAAM,GACV,iFAAiF;QACjF,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;IACP,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,sBAAsB,CAAC,OAAqB;IACnD,MAAM,IAAI,GAAG,sBAAsB,UAAU,IAAI,SAAS,aAAa,CAAC;IACxE,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI;YACJ,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;SACjC,CAAC,CAAC;QACH,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IACxC,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;QAClE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,iEAAiE;IACjE,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAAC;QACjE,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YAC3E,EAAE,GAAG,GAAG,CAAC;QACX,CAAC;aAAM,CAAC;YACN,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,iBAAiB,CAAC;IAClE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACtD,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,OAAqB;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,mBAAmB,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,sCAAsC,mBAAmB,mDAAmD,CAC7G,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,eAAe,CAAC,CAAC;IACtE,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,UAAU;aACzB;YACD,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,wCAAwC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,GAClE,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EACrC,EAAE,CACH,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAY,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO;QACL,MAAM,EAAE,WAAoB;QAC5B,QAAQ,EAAE,GAAG;QACb,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,WAAW;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare const RATE_LIMITS: {
2
+ readonly minIntervalSeconds: number;
3
+ readonly perHour: 3;
4
+ readonly perDay: 10;
5
+ readonly dedupeWindowDays: number;
6
+ };
7
+ export declare function hashPayload(summary: string, description: string): string;
8
+ export type RateLimitResult = {
9
+ ok: true;
10
+ } | {
11
+ ok: false;
12
+ reason: string;
13
+ retryAfterSeconds?: number;
14
+ };
15
+ export declare function checkAndRecord(hash: string, now?: number): RateLimitResult;
@@ -0,0 +1,110 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ /**
6
+ * The state-file path is computed lazily from an env-var override so tests can
7
+ * redirect writes into a temp dir. Default: `~/.recon-crypto-mcp/feedback-log.json`.
8
+ */
9
+ function getStateFilePath() {
10
+ return (process.env.RECON_FEEDBACK_STATE_FILE ??
11
+ join(homedir(), ".recon-crypto-mcp", "feedback-log.json"));
12
+ }
13
+ const MIN_INTERVAL_MS = 30_000;
14
+ const MAX_PER_HOUR = 3;
15
+ const MAX_PER_DAY = 10;
16
+ const DEDUPE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
17
+ const RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
18
+ const MAX_EVENTS_STORED = 200;
19
+ export const RATE_LIMITS = {
20
+ minIntervalSeconds: MIN_INTERVAL_MS / 1000,
21
+ perHour: MAX_PER_HOUR,
22
+ perDay: MAX_PER_DAY,
23
+ dedupeWindowDays: DEDUPE_WINDOW_MS / (24 * 60 * 60 * 1000),
24
+ };
25
+ export function hashPayload(summary, description) {
26
+ return createHash("sha256")
27
+ .update(`${summary.trim().toLowerCase()}\n${description.trim().toLowerCase()}`)
28
+ .digest("hex")
29
+ .slice(0, 16);
30
+ }
31
+ export function checkAndRecord(hash, now = Date.now()) {
32
+ const events = readEvents();
33
+ const last = events.length > 0 ? events[events.length - 1] : undefined;
34
+ if (last && now - last.ts < MIN_INTERVAL_MS) {
35
+ const retry = Math.ceil((MIN_INTERVAL_MS - (now - last.ts)) / 1000);
36
+ return {
37
+ ok: false,
38
+ reason: `Too many capability requests in quick succession. Wait ${retry}s between calls.`,
39
+ retryAfterSeconds: retry,
40
+ };
41
+ }
42
+ const hourAgo = now - 60 * 60 * 1000;
43
+ if (events.filter((e) => e.ts > hourAgo).length >= MAX_PER_HOUR) {
44
+ return {
45
+ ok: false,
46
+ reason: `Hourly capability-request limit (${MAX_PER_HOUR}/hour) reached. Try again later.`,
47
+ };
48
+ }
49
+ const dayAgo = now - 24 * 60 * 60 * 1000;
50
+ if (events.filter((e) => e.ts > dayAgo).length >= MAX_PER_DAY) {
51
+ return {
52
+ ok: false,
53
+ reason: `Daily capability-request limit (${MAX_PER_DAY}/24h) reached. Try again tomorrow.`,
54
+ };
55
+ }
56
+ const dedupeAgo = now - DEDUPE_WINDOW_MS;
57
+ if (events.some((e) => e.hash === hash && e.ts > dedupeAgo)) {
58
+ return {
59
+ ok: false,
60
+ reason: "An equivalent capability request (same summary + description) was already submitted in the last 7 days. " +
61
+ "Refine the summary/description with new information, or wait for triage.",
62
+ };
63
+ }
64
+ const next = [...events, { ts: now, hash }];
65
+ const retentionCutoff = now - RETENTION_MS;
66
+ const pruned = next.filter((e) => e.ts > retentionCutoff).slice(-MAX_EVENTS_STORED);
67
+ writeEvents(pruned);
68
+ return { ok: true };
69
+ }
70
+ function readEvents() {
71
+ const stateFile = getStateFilePath();
72
+ if (!existsSync(stateFile))
73
+ return [];
74
+ try {
75
+ const raw = readFileSync(stateFile, "utf8");
76
+ const parsed = JSON.parse(raw);
77
+ if (!Array.isArray(parsed))
78
+ return [];
79
+ return parsed.filter((e) => typeof e === "object" &&
80
+ e !== null &&
81
+ Number.isFinite(e.ts) &&
82
+ typeof e.hash === "string" &&
83
+ e.hash.length > 0);
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
89
+ /**
90
+ * Writes the event log atomically. Throws on any condition that would make the
91
+ * rate limiter silently lose state (refused symlink, EPERM, etc.) — callers
92
+ * MUST propagate, so a failed write fails the capability-request outright
93
+ * rather than letting it proceed with no counter increment (fail-closed).
94
+ */
95
+ function writeEvents(events) {
96
+ const stateFile = getStateFilePath();
97
+ const dir = dirname(stateFile);
98
+ if (!existsSync(dir)) {
99
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
100
+ }
101
+ if (existsSync(stateFile)) {
102
+ const st = lstatSync(stateFile);
103
+ if (!st.isFile() || st.isSymbolicLink() || st.nlink > 1) {
104
+ throw new Error(`Refusing to write feedback rate-limit log at ${stateFile}: path is a symlink, hardlink, or non-regular file. ` +
105
+ `Inspect it manually and remove it to restore DDoS protection.`);
106
+ }
107
+ }
108
+ writeFileSync(stateFile, JSON.stringify(events, null, 2) + "\n", { mode: 0o600 });
109
+ }
110
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/modules/feedback/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ export declare const requestCapabilityInput: z.ZodObject<{
3
+ summary: z.ZodString;
4
+ description: z.ZodString;
5
+ category: z.ZodOptional<z.ZodEnum<["new_protocol", "new_chain", "tool_gap", "bug_report", "other"]>>;
6
+ context: z.ZodOptional<z.ZodObject<{
7
+ toolAttempted: z.ZodOptional<z.ZodString>;
8
+ chain: z.ZodOptional<z.ZodString>;
9
+ errorObserved: z.ZodOptional<z.ZodString>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ chain?: string | undefined;
12
+ toolAttempted?: string | undefined;
13
+ errorObserved?: string | undefined;
14
+ }, {
15
+ chain?: string | undefined;
16
+ toolAttempted?: string | undefined;
17
+ errorObserved?: string | undefined;
18
+ }>>;
19
+ agentName: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ description: string;
22
+ summary: string;
23
+ category?: "new_protocol" | "new_chain" | "tool_gap" | "bug_report" | "other" | undefined;
24
+ context?: {
25
+ chain?: string | undefined;
26
+ toolAttempted?: string | undefined;
27
+ errorObserved?: string | undefined;
28
+ } | undefined;
29
+ agentName?: string | undefined;
30
+ }, {
31
+ description: string;
32
+ summary: string;
33
+ category?: "new_protocol" | "new_chain" | "tool_gap" | "bug_report" | "other" | undefined;
34
+ context?: {
35
+ chain?: string | undefined;
36
+ toolAttempted?: string | undefined;
37
+ errorObserved?: string | undefined;
38
+ } | undefined;
39
+ agentName?: string | undefined;
40
+ }>;
41
+ export type RequestCapabilityArgs = z.infer<typeof requestCapabilityInput>;
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ export const requestCapabilityInput = z.object({
3
+ summary: z
4
+ .string()
5
+ .trim()
6
+ .min(10)
7
+ .max(120)
8
+ .describe("One-line title of the missing capability (used as the GitHub issue title). E.g. 'Support Aerodrome LP positions on Base' or 'Add Pendle PT/YT position reader'."),
9
+ description: z
10
+ .string()
11
+ .trim()
12
+ .min(20)
13
+ .max(4000)
14
+ .describe("What the user asked for, what the agent tried, what's missing, and why the existing tools don't cover it. Include protocol name, chain, contract addresses, and a concrete example if relevant."),
15
+ category: z
16
+ .enum(["new_protocol", "new_chain", "tool_gap", "bug_report", "other"])
17
+ .optional()
18
+ .describe("Rough bucket to help triage."),
19
+ context: z
20
+ .object({
21
+ toolAttempted: z
22
+ .string()
23
+ .max(100)
24
+ .optional()
25
+ .describe("Name of the recon-crypto-mcp tool the agent tried first, if any."),
26
+ chain: z.string().max(50).optional().describe("Chain involved, if relevant."),
27
+ errorObserved: z
28
+ .string()
29
+ .max(800)
30
+ .optional()
31
+ .describe("Error message or unexpected output from an existing tool."),
32
+ })
33
+ .optional(),
34
+ agentName: z
35
+ .string()
36
+ .max(80)
37
+ .optional()
38
+ .describe("MCP client identifier (e.g. 'Claude Code', 'Cursor'). Helps triage."),
39
+ });
40
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../src/modules/feedback/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,IAAI,EAAE;SACN,GAAG,CAAC,EAAE,CAAC;SACP,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,CACP,iKAAiK,CAClK;IACH,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,IAAI,EAAE;SACN,GAAG,CAAC,EAAE,CAAC;SACP,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,CACP,iMAAiM,CAClM;IACH,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,CAAC,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SACtE,QAAQ,EAAE;SACV,QAAQ,CAAC,8BAA8B,CAAC;IAC3C,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kEAAkE,CAAC;QAC/E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QAC7E,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,2DAA2D,CAAC;KACzE,CAAC;SACD,QAAQ,EAAE;IACb,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,qEAAqE,CAAC;CACnF,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { PrepareMorphoSupplyArgs, PrepareMorphoWithdrawArgs, PrepareMorphoBorrowArgs, PrepareMorphoRepayArgs, PrepareMorphoSupplyCollateralArgs, PrepareMorphoWithdrawCollateralArgs } from "./schemas.js";
2
+ import type { UnsignedTx } from "../../types/index.js";
3
+ export declare function buildMorphoSupply(p: PrepareMorphoSupplyArgs): Promise<UnsignedTx>;
4
+ export declare function buildMorphoWithdraw(p: PrepareMorphoWithdrawArgs): Promise<UnsignedTx>;
5
+ export declare function buildMorphoBorrow(p: PrepareMorphoBorrowArgs): Promise<UnsignedTx>;
6
+ export declare function buildMorphoRepay(p: PrepareMorphoRepayArgs): Promise<UnsignedTx>;
7
+ export declare function buildMorphoSupplyCollateral(p: PrepareMorphoSupplyCollateralArgs): Promise<UnsignedTx>;
8
+ export declare function buildMorphoWithdrawCollateral(p: PrepareMorphoWithdrawCollateralArgs): Promise<UnsignedTx>;
@@ -0,0 +1,265 @@
1
+ import { encodeFunctionData, parseUnits } from "viem";
2
+ import { morphoBlueAbi } from "../../abis/morpho-blue.js";
3
+ import { erc20Abi } from "../../abis/erc20.js";
4
+ import { getClient } from "../../data/rpc.js";
5
+ import { CONTRACTS } from "../../config/contracts.js";
6
+ import { buildApprovalTx, resolveApprovalCap } from "../shared/approval.js";
7
+ function morphoAddress(chain) {
8
+ const addr = CONTRACTS[chain]?.morpho
9
+ ?.blue;
10
+ if (!addr)
11
+ throw new Error(`Morpho Blue is not deployed on ${chain}`);
12
+ return addr;
13
+ }
14
+ async function resolveMarketParams(chain, marketId) {
15
+ const client = getClient(chain);
16
+ const morpho = morphoAddress(chain);
17
+ const result = (await client.readContract({
18
+ address: morpho,
19
+ abi: morphoBlueAbi,
20
+ functionName: "idToMarketParams",
21
+ args: [marketId],
22
+ }));
23
+ const [loanToken, collateralToken, oracle, irm, lltv] = result;
24
+ if (loanToken === "0x0000000000000000000000000000000000000000") {
25
+ throw new Error(`Unknown Morpho market id ${marketId} on ${chain}`);
26
+ }
27
+ return { loanToken, collateralToken, oracle, irm, lltv };
28
+ }
29
+ async function tokenMeta(chain, asset) {
30
+ const client = getClient(chain);
31
+ const [decimals, symbol] = await client.multicall({
32
+ contracts: [
33
+ { address: asset, abi: erc20Abi, functionName: "decimals" },
34
+ { address: asset, abi: erc20Abi, functionName: "symbol" },
35
+ ],
36
+ allowFailure: false,
37
+ });
38
+ return { decimals: Number(decimals), symbol: symbol };
39
+ }
40
+ function paramsTuple(p) {
41
+ return {
42
+ loanToken: p.loanToken,
43
+ collateralToken: p.collateralToken,
44
+ oracle: p.oracle,
45
+ irm: p.irm,
46
+ lltv: p.lltv,
47
+ };
48
+ }
49
+ export async function buildMorphoSupply(p) {
50
+ const chain = p.chain;
51
+ const wallet = p.wallet;
52
+ const morpho = morphoAddress(chain);
53
+ const params = await resolveMarketParams(chain, p.marketId);
54
+ const meta = await tokenMeta(chain, params.loanToken);
55
+ const amountWei = parseUnits(p.amount, meta.decimals);
56
+ const { approvalAmount, display } = resolveApprovalCap(p.approvalCap, amountWei, meta.decimals);
57
+ const approval = await buildApprovalTx({
58
+ chain,
59
+ wallet,
60
+ asset: params.loanToken,
61
+ spender: morpho,
62
+ amountWei,
63
+ approvalAmount,
64
+ approvalDisplay: display,
65
+ symbol: meta.symbol,
66
+ spenderLabel: "Morpho Blue",
67
+ });
68
+ const supplyTx = {
69
+ chain,
70
+ to: morpho,
71
+ data: encodeFunctionData({
72
+ abi: morphoBlueAbi,
73
+ functionName: "supply",
74
+ args: [paramsTuple(params), amountWei, 0n, wallet, "0x"],
75
+ }),
76
+ value: "0",
77
+ from: wallet,
78
+ description: `Supply ${p.amount} ${meta.symbol} to Morpho Blue market ${p.marketId} on ${chain}`,
79
+ decoded: {
80
+ functionName: "supply",
81
+ args: { marketId: p.marketId, amount: p.amount, onBehalf: wallet },
82
+ },
83
+ };
84
+ if (approval) {
85
+ let tail = approval;
86
+ while (tail.next)
87
+ tail = tail.next;
88
+ tail.next = supplyTx;
89
+ return approval;
90
+ }
91
+ return supplyTx;
92
+ }
93
+ export async function buildMorphoWithdraw(p) {
94
+ const chain = p.chain;
95
+ const wallet = p.wallet;
96
+ const morpho = morphoAddress(chain);
97
+ const params = await resolveMarketParams(chain, p.marketId);
98
+ const meta = await tokenMeta(chain, params.loanToken);
99
+ // "max" withdraw is encoded as shares=MaxUint256/2 would exceed position; safer to ask by assets with
100
+ // a very large number. Morpho reverts on overdraw, so callers should read their position first.
101
+ // Here we only support explicit amounts for withdraw.
102
+ if (p.amount === "max") {
103
+ throw new Error(`"max" is not supported for Morpho withdraw — read position and pass an explicit amount.`);
104
+ }
105
+ const amountWei = parseUnits(p.amount, meta.decimals);
106
+ return {
107
+ chain,
108
+ to: morpho,
109
+ data: encodeFunctionData({
110
+ abi: morphoBlueAbi,
111
+ functionName: "withdraw",
112
+ args: [paramsTuple(params), amountWei, 0n, wallet, wallet],
113
+ }),
114
+ value: "0",
115
+ from: wallet,
116
+ description: `Withdraw ${p.amount} ${meta.symbol} from Morpho Blue market ${p.marketId} on ${chain}`,
117
+ decoded: {
118
+ functionName: "withdraw",
119
+ args: { marketId: p.marketId, amount: p.amount, receiver: wallet },
120
+ },
121
+ };
122
+ }
123
+ export async function buildMorphoBorrow(p) {
124
+ const chain = p.chain;
125
+ const wallet = p.wallet;
126
+ const morpho = morphoAddress(chain);
127
+ const params = await resolveMarketParams(chain, p.marketId);
128
+ const meta = await tokenMeta(chain, params.loanToken);
129
+ const amountWei = parseUnits(p.amount, meta.decimals);
130
+ return {
131
+ chain,
132
+ to: morpho,
133
+ data: encodeFunctionData({
134
+ abi: morphoBlueAbi,
135
+ functionName: "borrow",
136
+ args: [paramsTuple(params), amountWei, 0n, wallet, wallet],
137
+ }),
138
+ value: "0",
139
+ from: wallet,
140
+ description: `Borrow ${p.amount} ${meta.symbol} from Morpho Blue market ${p.marketId} on ${chain}`,
141
+ decoded: {
142
+ functionName: "borrow",
143
+ args: { marketId: p.marketId, amount: p.amount, receiver: wallet },
144
+ },
145
+ };
146
+ }
147
+ export async function buildMorphoRepay(p) {
148
+ const chain = p.chain;
149
+ const wallet = p.wallet;
150
+ const morpho = morphoAddress(chain);
151
+ const params = await resolveMarketParams(chain, p.marketId);
152
+ const meta = await tokenMeta(chain, params.loanToken);
153
+ if (p.amount === "max") {
154
+ throw new Error(`"max" is not supported for Morpho repay — read borrowShares and pass an explicit amount.`);
155
+ }
156
+ const amountWei = parseUnits(p.amount, meta.decimals);
157
+ const { approvalAmount, display } = resolveApprovalCap(p.approvalCap, amountWei, meta.decimals);
158
+ const approval = await buildApprovalTx({
159
+ chain,
160
+ wallet,
161
+ asset: params.loanToken,
162
+ spender: morpho,
163
+ amountWei,
164
+ approvalAmount,
165
+ approvalDisplay: display,
166
+ symbol: meta.symbol,
167
+ spenderLabel: "Morpho Blue",
168
+ });
169
+ const repayTx = {
170
+ chain,
171
+ to: morpho,
172
+ data: encodeFunctionData({
173
+ abi: morphoBlueAbi,
174
+ functionName: "repay",
175
+ args: [paramsTuple(params), amountWei, 0n, wallet, "0x"],
176
+ }),
177
+ value: "0",
178
+ from: wallet,
179
+ description: `Repay ${p.amount} ${meta.symbol} to Morpho Blue market ${p.marketId} on ${chain}`,
180
+ decoded: {
181
+ functionName: "repay",
182
+ args: { marketId: p.marketId, amount: p.amount, onBehalf: wallet },
183
+ },
184
+ };
185
+ if (approval) {
186
+ let tail = approval;
187
+ while (tail.next)
188
+ tail = tail.next;
189
+ tail.next = repayTx;
190
+ return approval;
191
+ }
192
+ return repayTx;
193
+ }
194
+ export async function buildMorphoSupplyCollateral(p) {
195
+ const chain = p.chain;
196
+ const wallet = p.wallet;
197
+ const morpho = morphoAddress(chain);
198
+ const params = await resolveMarketParams(chain, p.marketId);
199
+ const meta = await tokenMeta(chain, params.collateralToken);
200
+ const amountWei = parseUnits(p.amount, meta.decimals);
201
+ const { approvalAmount, display } = resolveApprovalCap(p.approvalCap, amountWei, meta.decimals);
202
+ const approval = await buildApprovalTx({
203
+ chain,
204
+ wallet,
205
+ asset: params.collateralToken,
206
+ spender: morpho,
207
+ amountWei,
208
+ approvalAmount,
209
+ approvalDisplay: display,
210
+ symbol: meta.symbol,
211
+ spenderLabel: "Morpho Blue",
212
+ });
213
+ const tx = {
214
+ chain,
215
+ to: morpho,
216
+ data: encodeFunctionData({
217
+ abi: morphoBlueAbi,
218
+ functionName: "supplyCollateral",
219
+ args: [paramsTuple(params), amountWei, wallet, "0x"],
220
+ }),
221
+ value: "0",
222
+ from: wallet,
223
+ description: `Supply ${p.amount} ${meta.symbol} as collateral to Morpho Blue market ${p.marketId} on ${chain}`,
224
+ decoded: {
225
+ functionName: "supplyCollateral",
226
+ args: { marketId: p.marketId, amount: p.amount, onBehalf: wallet },
227
+ },
228
+ };
229
+ if (approval) {
230
+ let tail = approval;
231
+ while (tail.next)
232
+ tail = tail.next;
233
+ tail.next = tx;
234
+ return approval;
235
+ }
236
+ return tx;
237
+ }
238
+ export async function buildMorphoWithdrawCollateral(p) {
239
+ const chain = p.chain;
240
+ const wallet = p.wallet;
241
+ const morpho = morphoAddress(chain);
242
+ const params = await resolveMarketParams(chain, p.marketId);
243
+ const meta = await tokenMeta(chain, params.collateralToken);
244
+ if (p.amount === "max") {
245
+ throw new Error(`"max" is not supported for Morpho withdrawCollateral — read position.collateral and pass an explicit amount.`);
246
+ }
247
+ const amountWei = parseUnits(p.amount, meta.decimals);
248
+ return {
249
+ chain,
250
+ to: morpho,
251
+ data: encodeFunctionData({
252
+ abi: morphoBlueAbi,
253
+ functionName: "withdrawCollateral",
254
+ args: [paramsTuple(params), amountWei, wallet, wallet],
255
+ }),
256
+ value: "0",
257
+ from: wallet,
258
+ description: `Withdraw ${p.amount} ${meta.symbol} collateral from Morpho Blue market ${p.marketId} on ${chain}`,
259
+ decoded: {
260
+ functionName: "withdrawCollateral",
261
+ args: { marketId: p.marketId, amount: p.amount, receiver: wallet },
262
+ },
263
+ };
264
+ }
265
+ //# sourceMappingURL=actions.js.map