@continuumdao/ctm-mpc-defi 0.2.1 → 0.2.3

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 (57) hide show
  1. package/dist/agent/catalog.cjs +96 -25
  2. package/dist/agent/catalog.cjs.map +1 -1
  3. package/dist/agent/catalog.d.ts +27 -1
  4. package/dist/agent/catalog.js +95 -26
  5. package/dist/agent/catalog.js.map +1 -1
  6. package/dist/agent/skills/curve-dao/SKILL.md +2 -1
  7. package/dist/chains/evm/index.cjs +54 -0
  8. package/dist/chains/evm/index.cjs.map +1 -1
  9. package/dist/chains/evm/index.d.ts +13 -1
  10. package/dist/chains/evm/index.js +52 -2
  11. package/dist/chains/evm/index.js.map +1 -1
  12. package/dist/core/index.cjs +64 -0
  13. package/dist/core/index.cjs.map +1 -1
  14. package/dist/core/index.d.ts +20 -1
  15. package/dist/core/index.js +56 -1
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/index.cjs +165 -14
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +154 -16
  21. package/dist/index.js.map +1 -1
  22. package/dist/protocols/evm/aave-v4/index.cjs +766 -6
  23. package/dist/protocols/evm/aave-v4/index.cjs.map +1 -1
  24. package/dist/protocols/evm/aave-v4/index.d.ts +417 -1
  25. package/dist/protocols/evm/aave-v4/index.js +741 -8
  26. package/dist/protocols/evm/aave-v4/index.js.map +1 -1
  27. package/dist/protocols/evm/curve-dao/index.cjs +268 -21
  28. package/dist/protocols/evm/curve-dao/index.cjs.map +1 -1
  29. package/dist/protocols/evm/curve-dao/index.d.ts +74 -9
  30. package/dist/protocols/evm/curve-dao/index.js +262 -23
  31. package/dist/protocols/evm/curve-dao/index.js.map +1 -1
  32. package/dist/protocols/evm/ethena/index.cjs +104 -0
  33. package/dist/protocols/evm/ethena/index.cjs.map +1 -1
  34. package/dist/protocols/evm/ethena/index.d.ts +46 -1
  35. package/dist/protocols/evm/ethena/index.js +99 -1
  36. package/dist/protocols/evm/ethena/index.js.map +1 -1
  37. package/dist/protocols/evm/euler-v2/index.cjs +2334 -37
  38. package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
  39. package/dist/protocols/evm/euler-v2/index.d.ts +464 -1
  40. package/dist/protocols/evm/euler-v2/index.js +2286 -39
  41. package/dist/protocols/evm/euler-v2/index.js.map +1 -1
  42. package/dist/protocols/evm/lido/index.cjs +110 -0
  43. package/dist/protocols/evm/lido/index.cjs.map +1 -1
  44. package/dist/protocols/evm/lido/index.d.ts +33 -2
  45. package/dist/protocols/evm/lido/index.js +107 -2
  46. package/dist/protocols/evm/lido/index.js.map +1 -1
  47. package/dist/protocols/evm/maple/index.cjs +83 -0
  48. package/dist/protocols/evm/maple/index.cjs.map +1 -1
  49. package/dist/protocols/evm/maple/index.d.ts +22 -1
  50. package/dist/protocols/evm/maple/index.js +82 -1
  51. package/dist/protocols/evm/maple/index.js.map +1 -1
  52. package/dist/protocols/evm/sky/index.cjs +217 -0
  53. package/dist/protocols/evm/sky/index.cjs.map +1 -1
  54. package/dist/protocols/evm/sky/index.d.ts +56 -1
  55. package/dist/protocols/evm/sky/index.js +210 -2
  56. package/dist/protocols/evm/sky/index.js.map +1 -1
  57. package/package.json +3 -3
@@ -65,6 +65,17 @@ function isCurveApiChainSupported(chainId) {
65
65
  if (Number.isNaN(n) || n < 0) return false;
66
66
  return CURVE_FULL_NETWORK_CONSTANTS_CHAIN_IDS.has(n);
67
67
  }
68
+ function normalizeHumanDecimalAmount(raw, tokenDecimals) {
69
+ const t = raw.trim().replace(/,/g, "");
70
+ if (!t) return "";
71
+ if (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 18) {
72
+ throw new Error("Invalid token decimals for amount normalization.");
73
+ }
74
+ const wei = viem.parseUnits(t, tokenDecimals);
75
+ return viem.formatUnits(wei, tokenDecimals);
76
+ }
77
+
78
+ // src/protocols/evm/curve-dao/swapDestinations.ts
68
79
  var CURVE_NATIVE_PLACEHOLDER = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
69
80
  var ETH_PLACEHOLDER = CURVE_NATIVE_PLACEHOLDER;
70
81
  function normalizeAddr(a) {
@@ -97,13 +108,7 @@ function toCurveRouterTokenId(tokenAddress, wrappedNative) {
97
108
  }
98
109
  }
99
110
  function normalizeCurveRouterAmountString(raw, tokenInDecimals) {
100
- const t = raw.trim().replace(/,/g, "");
101
- if (!t) return "";
102
- if (!Number.isInteger(tokenInDecimals) || tokenInDecimals < 0 || tokenInDecimals > 18) {
103
- throw new Error("Invalid token-in decimals for amount normalization.");
104
- }
105
- const wei = viem.parseUnits(t, tokenInDecimals);
106
- return viem.formatUnits(wei, tokenInDecimals);
111
+ return normalizeHumanDecimalAmount(raw, tokenInDecimals);
107
112
  }
108
113
  function addEdge(adj, a, b) {
109
114
  if (a === b) return;
@@ -169,46 +174,97 @@ function bfsCurveSwapDestinations(adj, start) {
169
174
  }
170
175
 
171
176
  // src/protocols/evm/curve-dao/apiSession.ts
177
+ var CURVE_SESSION_CACHE_TTL_MS = 5 * 60 * 1e3;
178
+ var curveSessionCache = /* @__PURE__ */ new Map();
172
179
  async function fetchAllCurvePools(curve) {
173
180
  const run = (p) => p.catch(() => void 0);
174
- await Promise.all([
175
- run(curve.factory.fetchPools()),
176
- run(curve.crvUSDFactory.fetchPools()),
177
- run(curve.EYWAFactory.fetchPools()),
178
- run(curve.cryptoFactory.fetchPools()),
179
- run(curve.twocryptoFactory.fetchPools()),
180
- run(curve.tricryptoFactory.fetchPools()),
181
- run(curve.stableNgFactory.fetchPools())
182
- ]);
181
+ const tasks = [];
182
+ for (const key of [
183
+ "factory",
184
+ "crvUSDFactory",
185
+ "EYWAFactory",
186
+ "cryptoFactory",
187
+ "twocryptoFactory",
188
+ "tricryptoFactory",
189
+ "stableNgFactory"
190
+ ]) {
191
+ const f = curve[key];
192
+ if (f?.fetchPools) {
193
+ tasks.push(run(f.fetchPools()));
194
+ }
195
+ }
196
+ await Promise.all(tasks);
183
197
  }
184
198
  async function loadFullCurveSessionForRpc(rpcUrl) {
185
199
  const url = (rpcUrl ?? "").trim();
186
- if (!url) return null;
200
+ if (!url) {
201
+ throw new Error("rpcUrl is required.");
202
+ }
203
+ const cached = curveSessionCache.get(url);
204
+ if (cached && cached.expiresAt > Date.now()) {
205
+ return cached.session;
206
+ }
187
207
  try {
188
208
  const { default: curve } = await import('@curvefi/api');
189
209
  await curve.init("JsonRpc", { url }, {});
190
210
  const wrapped = curve.getNetworkConstants().NATIVE_COIN?.wrappedAddress;
191
211
  if (!curve.hasRouter || !curve.hasRouter()) {
192
- return {
212
+ const session2 = {
193
213
  curve,
194
214
  adj: /* @__PURE__ */ new Map(),
195
215
  swappableNodeKeys: /* @__PURE__ */ new Set(),
196
216
  wrappedNative: wrapped
197
217
  };
218
+ curveSessionCache.set(url, { session: session2, expiresAt: Date.now() + CURVE_SESSION_CACHE_TTL_MS });
219
+ return session2;
198
220
  }
199
221
  await fetchAllCurvePools(curve);
200
222
  const adj = buildCurveLiquidityGraphFromApi(curve);
201
223
  addNativeWethBridge(adj, wrapped);
202
- return {
224
+ const session = {
203
225
  curve,
204
226
  adj,
205
227
  swappableNodeKeys: swappableCurveGraphNodeKeys(adj),
206
228
  wrappedNative: wrapped
207
229
  };
208
- } catch {
209
- return null;
230
+ curveSessionCache.set(url, { session, expiresAt: Date.now() + CURVE_SESSION_CACHE_TTL_MS });
231
+ return session;
232
+ } catch (e) {
233
+ const msg = e instanceof Error ? e.message : String(e);
234
+ throw new Error(`Curve session init failed for RPC: ${msg}`);
210
235
  }
211
236
  }
237
+ async function loadCurveSessionSnapshotForRpc(rpcUrl) {
238
+ const session = await loadFullCurveSessionForRpc(rpcUrl);
239
+ const adj = {};
240
+ for (const [key, neighbors] of session.adj) {
241
+ adj[key] = [...neighbors];
242
+ }
243
+ return {
244
+ wrappedNative: session.wrappedNative,
245
+ swappableNodeKeys: [...session.swappableNodeKeys],
246
+ routerReady: Boolean(session.curve?.hasRouter?.() && session.curve.hasRouter()),
247
+ adj
248
+ };
249
+ }
250
+ async function fetchCurveCoinsDataForRpc(rpcUrl, addresses) {
251
+ const session = await loadFullCurveSessionForRpc(rpcUrl);
252
+ if (!session.curve?.getCoinsData) {
253
+ throw new Error("Curve getCoinsData is not available for this RPC session.");
254
+ }
255
+ const coins = await session.curve.getCoinsData(addresses);
256
+ return addresses.map((addr, i) => {
257
+ const c = coins[i];
258
+ const dec = c?.decimals;
259
+ const decimals = typeof dec === "number" && Number.isInteger(dec) && dec >= 0 && dec <= 18 ? dec : null;
260
+ return {
261
+ address: addr,
262
+ name: c?.name && String(c.name).trim() || "\u2014",
263
+ symbol: c?.symbol && String(c.symbol).trim() || addr.slice(0, 6),
264
+ decimals
265
+ };
266
+ });
267
+ }
212
268
 
213
269
  // src/protocols/evm/curve-dao/purpose.ts
214
270
  function buildCurveDaoPurposePrefill(args) {
@@ -706,6 +762,176 @@ async function buildEvmMultisignBodyCurveDaoBatch(args) {
706
762
  }
707
763
  });
708
764
  }
765
+ var erc20DecimalsAbi = viem.parseAbi(["function decimals() view returns (uint8)"]);
766
+ function normalizeNativeTokenIn(tokenIn) {
767
+ const trimmed = tokenIn.trim();
768
+ if (!trimmed) return tokenIn;
769
+ const lower = trimmed.toLowerCase();
770
+ if (lower === "eth" || lower === "native" || lower === "native_eth" || lower === viem.zeroAddress.toLowerCase() || lower === CURVE_NATIVE_PLACEHOLDER.toLowerCase()) {
771
+ return CURVE_NATIVE_PLACEHOLDER;
772
+ }
773
+ return trimmed;
774
+ }
775
+ async function resolveTokenInDecimals(args) {
776
+ if (args.tokenInDecimals != null && Number.isInteger(args.tokenInDecimals) && args.tokenInDecimals >= 0 && args.tokenInDecimals <= 18) {
777
+ return args.tokenInDecimals;
778
+ }
779
+ const normalized = normalizeNativeTokenIn(args.tokenIn);
780
+ if (normalized.toLowerCase() === CURVE_NATIVE_PLACEHOLDER.toLowerCase()) {
781
+ return 18;
782
+ }
783
+ const addr = viem.getAddress(
784
+ normalized.startsWith("0x") ? normalized : `0x${normalized}`
785
+ );
786
+ const ch = viem.defineChain({
787
+ id: args.chainId,
788
+ name: "CurveQuote",
789
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
790
+ rpcUrls: { default: { http: [args.rpcUrl] } }
791
+ });
792
+ const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl) });
793
+ const decimalsN = await publicClient.readContract({
794
+ address: addr,
795
+ abi: erc20DecimalsAbi,
796
+ functionName: "decimals"
797
+ });
798
+ return Number(decimalsN);
799
+ }
800
+ async function curveDaoQuote(args) {
801
+ const rpcUrl = (args.rpcUrl ?? "").trim();
802
+ if (!rpcUrl) {
803
+ throw new Error("rpcUrl is required.");
804
+ }
805
+ if (!Number.isFinite(args.chainId) || args.chainId <= 0) {
806
+ throw new Error("chainId must be a positive integer.");
807
+ }
808
+ if (!isCurveApiChainSupported(args.chainId)) {
809
+ throw new Error(`Chain ${args.chainId} is not supported by Curve Router NG.`);
810
+ }
811
+ const tokenInDecimals = await resolveTokenInDecimals({
812
+ rpcUrl,
813
+ chainId: args.chainId,
814
+ tokenIn: args.tokenIn,
815
+ tokenInDecimals: args.tokenInDecimals
816
+ });
817
+ const amountForRouter = normalizeCurveRouterAmountString(
818
+ args.amountHuman,
819
+ tokenInDecimals
820
+ );
821
+ if (!amountForRouter) {
822
+ throw new Error("amountHuman must be a positive decimal string.");
823
+ }
824
+ const session = await loadFullCurveSessionForRpc(rpcUrl);
825
+ if (!session.curve?.router?.getBestRouteAndOutput) {
826
+ throw new Error("Curve router is not available (session load failed or chain has no router).");
827
+ }
828
+ const { curve, wrappedNative: wn } = session;
829
+ const tokenInRouterId = toCurveRouterTokenId(normalizeNativeTokenIn(args.tokenIn), wn);
830
+ const tokenOutTrim = (args.tokenOut ?? "").trim();
831
+ const tokenOutRouterId = toCurveRouterTokenId(
832
+ tokenOutTrim.toLowerCase() === CURVE_NATIVE_PLACEHOLDER.toLowerCase() ? CURVE_NATIVE_PLACEHOLDER : viem.getAddress(
833
+ tokenOutTrim.startsWith("0x") ? tokenOutTrim : `0x${tokenOutTrim}`
834
+ ),
835
+ wn
836
+ );
837
+ const res = await curve.router.getBestRouteAndOutput(
838
+ tokenInRouterId,
839
+ tokenOutRouterId,
840
+ amountForRouter
841
+ );
842
+ const route = res?.route;
843
+ if (!route || Array.isArray(route) && route.length === 0) {
844
+ throw new Error(
845
+ "Curve router could not find a pool route for this pair and size."
846
+ );
847
+ }
848
+ const outStr = String(res?.output ?? "0");
849
+ let priceImpactPercent = null;
850
+ try {
851
+ const pi = await curve.router.swapPriceImpact(
852
+ tokenInRouterId,
853
+ tokenOutRouterId,
854
+ amountForRouter
855
+ );
856
+ if (typeof pi === "number" && Number.isFinite(pi)) {
857
+ priceImpactPercent = pi;
858
+ }
859
+ } catch {
860
+ }
861
+ return {
862
+ inputAmount: amountForRouter,
863
+ output: outStr,
864
+ route,
865
+ priceImpactPercent,
866
+ tokenInRouterId,
867
+ tokenOutRouterId
868
+ };
869
+ }
870
+ function curveProbeInAmountString(fullNormalizedHuman, tokenInDecimals) {
871
+ const t = (fullNormalizedHuman ?? "").trim();
872
+ if (!t) return null;
873
+ if (!Number.isInteger(tokenInDecimals) || tokenInDecimals < 0 || tokenInDecimals > 18) return null;
874
+ let wei;
875
+ try {
876
+ wei = viem.parseUnits(t, tokenInDecimals);
877
+ } catch {
878
+ return null;
879
+ }
880
+ if (wei <= 1n) return null;
881
+ let p = wei / 10000n;
882
+ if (p < 1n) p = 1n;
883
+ if (p >= wei) p = wei - 1n;
884
+ return viem.formatUnits(p, tokenInDecimals);
885
+ }
886
+ function computeCurveExecutionSlippagePercent(args) {
887
+ const aIn = parseFloat((args.fullInHuman ?? "").replace(/,/g, "").trim());
888
+ const aOut = parseFloat((args.fullOutHuman ?? "").replace(/,/g, "").trim());
889
+ const pIn = parseFloat((args.probeInHuman ?? "").replace(/,/g, "").trim());
890
+ const pOut = parseFloat((args.probeOutHuman ?? "").replace(/,/g, "").trim());
891
+ if (!(aIn > 0) || !(aOut >= 0) || !(pIn > 0) || !Number.isFinite(pOut) || pOut < 0) return null;
892
+ const marginal = pOut / pIn;
893
+ const avg = aOut / aIn;
894
+ if (!Number.isFinite(marginal) || marginal <= 0 || !Number.isFinite(avg)) return null;
895
+ const pct = (1 - avg / marginal) * 100;
896
+ if (!Number.isFinite(pct)) return null;
897
+ return Math.max(0, pct);
898
+ }
899
+ function formatCurveExchangeLine(amountIn, amountOut, tokenInSymbol, tokenOutSymbol) {
900
+ const a = parseFloat((amountIn ?? "").replace(/,/g, "").trim());
901
+ const b = parseFloat((amountOut ?? "").replace(/,/g, "").trim());
902
+ const sIn = (tokenInSymbol || "").trim() || "in";
903
+ const sOut = (tokenOutSymbol || "").trim() || "out";
904
+ if (!(a > 0) || !Number.isFinite(b)) return "\u2014";
905
+ const r = b / a;
906
+ let rate;
907
+ if (r >= 1e-4) {
908
+ rate = r.toFixed(8).replace(/0+$/, "").replace(/\.$/, "");
909
+ } else if (r > 0) {
910
+ rate = r.toPrecision(6);
911
+ } else {
912
+ rate = "0";
913
+ }
914
+ if (!rate) rate = "0";
915
+ return `1 ${sIn} \u2248 ${rate} ${sOut}`;
916
+ }
917
+ function formatCurvePriceImpact(percent0to100) {
918
+ if (percent0to100 == null || !Number.isFinite(percent0to100) || percent0to100 < 0) return "\u2014";
919
+ if (percent0to100 === 0) return "0%";
920
+ if (percent0to100 < 1e-4) return "<0.0001%";
921
+ const t = percent0to100 < 0.01 ? percent0to100.toFixed(4) : percent0to100 < 1 ? percent0to100.toFixed(3) : percent0to100 < 10 ? percent0to100.toFixed(2) : percent0to100.toFixed(1);
922
+ return `${t.replace(/(\.\d*?[1-9])0+$/g, "$1").replace(/\.0$/, "")}%`;
923
+ }
924
+ function formatMinOutAtUserSlippage(quoteOutDecimal, userSlippagePercentInput, tokenOutSymbol) {
925
+ const q = parseFloat((quoteOutDecimal ?? "").replace(/,/g, "").trim());
926
+ const rawS = (userSlippagePercentInput ?? "0.5").trim().replace(/,/g, "");
927
+ const slip = Number.parseFloat(rawS);
928
+ const s = Number.isFinite(slip) && slip >= 0 && slip < 100 ? slip : 0.5;
929
+ if (!Number.isFinite(q) || q < 0) return "\u2014";
930
+ const min = q * ((100 - s) / 100);
931
+ const sOut = (tokenOutSymbol || "").trim() || "out";
932
+ const t = min.toFixed(8).replace(/\.?0+$/, "").replace(/(\.\d*?[1-9])0+$/, "$1");
933
+ return `${t} ${sOut}`.trim();
934
+ }
709
935
 
710
936
  // src/protocols/evm/curve-dao/index.ts
711
937
  var CURVE_DAO_PROTOCOL_ID = "curve-dao";
@@ -721,6 +947,19 @@ var curveDaoProtocolModule = {
721
947
  return token.kind === "native" || token.kind === "erc20";
722
948
  },
723
949
  actions: [
950
+ {
951
+ id: "curve-dao.quote",
952
+ protocolId: CURVE_DAO_PROTOCOL_ID,
953
+ chainCategory: "evm",
954
+ description: "Quote swap via Curve Router NG (getBestRouteAndOutput)",
955
+ commonParams: [],
956
+ params: {
957
+ chainId: { type: "number", required: true, description: "EVM chain id" },
958
+ tokenIn: { type: "address", required: true, description: "Token in or native placeholder" },
959
+ tokenOut: { type: "address", required: true, description: "Token out or native placeholder" },
960
+ amountHuman: { type: "string", required: true, description: "Human-readable input amount" }
961
+ }
962
+ },
724
963
  {
725
964
  id: "curve-dao.swap",
726
965
  protocolId: CURVE_DAO_PROTOCOL_ID,
@@ -751,14 +990,22 @@ exports.bfsCurveSwapDestinations = bfsCurveSwapDestinations;
751
990
  exports.buildCurveDaoPurposePrefill = buildCurveDaoPurposePrefill;
752
991
  exports.buildCurveLiquidityGraphFromApi = buildCurveLiquidityGraphFromApi;
753
992
  exports.buildEvmMultisignBodyCurveDaoBatch = buildEvmMultisignBodyCurveDaoBatch;
993
+ exports.computeCurveExecutionSlippagePercent = computeCurveExecutionSlippagePercent;
754
994
  exports.curveDao = curveDao;
755
995
  exports.curveDaoProtocolModule = curveDaoProtocolModule;
996
+ exports.curveDaoQuote = curveDaoQuote;
997
+ exports.curveProbeInAmountString = curveProbeInAmountString;
756
998
  exports.fetchAllCurvePools = fetchAllCurvePools;
999
+ exports.fetchCurveCoinsDataForRpc = fetchCurveCoinsDataForRpc;
1000
+ exports.formatCurveExchangeLine = formatCurveExchangeLine;
1001
+ exports.formatCurvePriceImpact = formatCurvePriceImpact;
1002
+ exports.formatMinOutAtUserSlippage = formatMinOutAtUserSlippage;
757
1003
  exports.isCurveApiChainSupported = isCurveApiChainSupported;
758
1004
  exports.isCurveDaoChainSupported = isCurveApiChainSupported;
759
1005
  exports.isCurveDaoErc20ApproveEvmSignRequest = isCurveDaoErc20ApproveEvmSignRequest;
760
1006
  exports.isCurveDaoSwapEvmSignRequest = isCurveDaoSwapEvmSignRequest;
761
1007
  exports.isCurveTokenKeySwappable = isCurveTokenKeySwappable;
1008
+ exports.loadCurveSessionSnapshotForRpc = loadCurveSessionSnapshotForRpc;
762
1009
  exports.loadFullCurveSessionForRpc = loadFullCurveSessionForRpc;
763
1010
  exports.normalizeCurveRouterAmountString = normalizeCurveRouterAmountString;
764
1011
  exports.resolveCurveDaoRouterGasUnitsFromSignRequest = resolveCurveDaoRouterGasUnitsFromSignRequest;