@hypurrquant/defi-cli 1.0.12 → 1.0.13

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 (44) hide show
  1. package/README.md +9 -2
  2. package/config/chains.toml +6 -1
  3. package/config/protocols/dex/aerodrome_base.toml +2 -1
  4. package/config/protocols/dex/aerodrome_cl.toml +2 -1
  5. package/config/protocols/dex/apeswap_bnb.toml +1 -1
  6. package/config/protocols/dex/babydogeswap_bnb.toml +1 -1
  7. package/config/protocols/dex/bakeryswap_bnb.toml +2 -2
  8. package/config/protocols/dex/biswap_bnb.toml +1 -1
  9. package/config/protocols/dex/bscswap_bnb.toml +1 -1
  10. package/config/protocols/dex/curve_hyperevm.toml +1 -1
  11. package/config/protocols/dex/fstswap_bnb.toml +1 -1
  12. package/config/protocols/dex/hybra.toml +4 -2
  13. package/config/protocols/dex/hyperswap.toml +1 -1
  14. package/config/protocols/dex/kittenswap.toml +1 -1
  15. package/config/protocols/dex/merchantmoe_mantle.toml +2 -1
  16. package/config/protocols/dex/nest.toml +6 -5
  17. package/config/protocols/dex/pancakeswap_v2_bnb.toml +1 -1
  18. package/config/protocols/dex/pancakeswap_v3_bnb.toml +2 -1
  19. package/config/protocols/dex/project_x.toml +1 -1
  20. package/config/protocols/dex/ramses_cl.toml +1 -1
  21. package/config/protocols/dex/ramses_hl.toml +1 -1
  22. package/config/protocols/dex/thena_v1_bnb.toml +2 -1
  23. package/config/protocols/dex/traderjoe_monad.toml +2 -1
  24. package/config/protocols/dex/uniswap_v2_monad.toml +2 -1
  25. package/config/protocols/dex/uniswap_v3_base.toml +2 -1
  26. package/config/protocols/dex/uniswap_v3_bnb.toml +3 -1
  27. package/config/protocols/dex/uniswap_v3_mantle.toml +2 -1
  28. package/config/protocols/dex/uniswap_v3_monad.toml +2 -1
  29. package/config/protocols/lending/felix_morpho.toml +16 -0
  30. package/config/protocols/lending/hyperlend.toml +1 -1
  31. package/config/protocols/lending/hypurrfi.toml +1 -1
  32. package/config/protocols/lending/kinza_bnb.toml +1 -1
  33. package/config/protocols/lending/morpho_blue_monad.toml +17 -0
  34. package/config/protocols/lending/venus_flux_bnb.toml +5 -1
  35. package/dist/index.js +1277 -246
  36. package/dist/index.js.map +1 -1
  37. package/dist/main.js +1282 -251
  38. package/dist/main.js.map +1 -1
  39. package/dist/mcp-server.js +799 -195
  40. package/dist/mcp-server.js.map +1 -1
  41. package/package.json +1 -1
  42. package/skills/defi-cli/SKILL.md +17 -4
  43. package/skills/defi-cli/references/commands.md +2 -2
  44. package/skills/defi-cli/references/protocols.md +2 -2
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
4
  var __esm = (fn, res) => function __init() {
@@ -111,11 +110,15 @@ function buildTransfer(token, to, amount) {
111
110
  gas_estimate: 65e3
112
111
  };
113
112
  }
114
- function getProvider(rpcUrl) {
115
- const cached = providerCache.get(rpcUrl);
113
+ function getProvider(rpcUrl, chain) {
114
+ const key = chain ? `${rpcUrl}@${chain.id}` : rpcUrl;
115
+ const cached = providerCache.get(key);
116
116
  if (cached) return cached;
117
- const client = createPublicClient({ transport: http(rpcUrl) });
118
- providerCache.set(rpcUrl, client);
117
+ const client = createPublicClient({
118
+ transport: http(rpcUrl),
119
+ ...chain ? { chain } : {}
120
+ });
121
+ providerCache.set(key, client);
119
122
  return client;
120
123
  }
121
124
  function clearProviderCache() {
@@ -341,6 +344,28 @@ var init_dist = __esm({
341
344
  const chainEnv = this.name.toUpperCase().replace(/ /g, "_") + "_RPC_URL";
342
345
  return process.env[chainEnv] ?? this.rpc_url;
343
346
  }
347
+ /**
348
+ * Build a viem Chain object pinned to this config so wallet/public clients
349
+ * can sign with an explicit chainId rather than auto-fetching it from the
350
+ * RPC. SSOT 7.4: anchoring chainId at client-construction time defends
351
+ * against an MITM RPC that returns the wrong eth_chainId, and keeps
352
+ * offline signing safe against RPC drift.
353
+ */
354
+ viemChain() {
355
+ const rpcUrl = this.effectiveRpcUrl();
356
+ return {
357
+ id: this.chain_id,
358
+ name: this.name,
359
+ nativeCurrency: {
360
+ name: this.native_token,
361
+ symbol: this.native_token,
362
+ decimals: 18
363
+ },
364
+ rpcUrls: { default: { http: [rpcUrl] } },
365
+ ...this.explorer_url ? { blockExplorers: { default: { name: this.name, url: this.explorer_url } } } : {},
366
+ ...this.multicall3 ? { contracts: { multicall3: { address: this.multicall3 } } } : {}
367
+ };
368
+ }
344
369
  };
345
370
  ProtocolCategory = /* @__PURE__ */ ((ProtocolCategory2) => {
346
371
  ProtocolCategory2["Dex"] = "dex";
@@ -532,13 +557,13 @@ __export(dist_exports2, {
532
557
  import { encodeFunctionData as encodeFunctionData3, parseAbi as parseAbi3, createPublicClient as createPublicClient2, http as http2, decodeAbiParameters } from "viem";
533
558
  import { encodeFunctionData as encodeFunctionData22, parseAbi as parseAbi22, createPublicClient as createPublicClient22, http as http22, decodeFunctionResult as decodeFunctionResult2, decodeAbiParameters as decodeAbiParameters2 } from "viem";
534
559
  import { encodeFunctionData as encodeFunctionData32, parseAbi as parseAbi32, createPublicClient as createPublicClient3, http as http3, decodeAbiParameters as decodeAbiParameters3, concatHex, zeroAddress } from "viem";
535
- import { encodeFunctionData as encodeFunctionData4, parseAbi as parseAbi4, zeroAddress as zeroAddress2 } from "viem";
560
+ import { encodeFunctionData as encodeFunctionData4, parseAbi as parseAbi4 } from "viem";
536
561
  import { encodeFunctionData as encodeFunctionData5, parseAbi as parseAbi5 } from "viem";
537
562
  import { encodeFunctionData as encodeFunctionData6, parseAbi as parseAbi6, decodeAbiParameters as decodeAbiParameters4 } from "viem";
538
- import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient4, http as http4, zeroAddress as zeroAddress3 } from "viem";
539
- import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress4 } from "viem";
540
- import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress5 } from "viem";
541
- import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress6 } from "viem";
563
+ import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient4, http as http4, zeroAddress as zeroAddress2 } from "viem";
564
+ import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress3 } from "viem";
565
+ import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress4 } from "viem";
566
+ import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress5 } from "viem";
542
567
  import { encodeFunctionData as encodeFunctionData11, parseAbi as parseAbi11, createPublicClient as createPublicClient7, http as http7 } from "viem";
543
568
  import {
544
569
  encodeFunctionData as encodeFunctionData12,
@@ -556,24 +581,24 @@ import {
556
581
  keccak256,
557
582
  parseAbi as parseAbi13,
558
583
  decodeFunctionResult as decodeFunctionResult5,
559
- zeroAddress as zeroAddress7
584
+ zeroAddress as zeroAddress6
560
585
  } from "viem";
561
- import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as zeroAddress8 } from "viem";
562
- import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress9 } from "viem";
586
+ import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as zeroAddress7 } from "viem";
587
+ import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress8 } from "viem";
563
588
  import { createPublicClient as createPublicClient12, http as http12, parseAbi as parseAbi16 } from "viem";
564
589
  import { createPublicClient as createPublicClient13, http as http13, parseAbi as parseAbi17, encodeFunctionData as encodeFunctionData16 } from "viem";
565
590
  import { createPublicClient as createPublicClient14, http as http14, parseAbi as parseAbi18, encodeFunctionData as encodeFunctionData17 } from "viem";
566
591
  import { createPublicClient as createPublicClient15, http as http15, parseAbi as parseAbi19, encodeFunctionData as encodeFunctionData18 } from "viem";
567
- import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as zeroAddress10 } from "viem";
568
- import { createPublicClient as createPublicClient16, http as http16, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress11 } from "viem";
569
- import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi222 } from "viem";
570
- import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
592
+ import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as zeroAddress9, createPublicClient as createPublicClient16, http as http16 } from "viem";
593
+ import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress10 } from "viem";
594
+ import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi222 } from "viem";
595
+ import { createPublicClient as createPublicClient19, http as http19, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
571
596
  import { parseAbi as parseAbi24, encodeFunctionData as encodeFunctionData222 } from "viem";
572
- import { createPublicClient as createPublicClient19, http as http19, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress12 } from "viem";
573
- import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress13 } from "viem";
597
+ import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress11 } from "viem";
598
+ import { createPublicClient as createPublicClient21, http as http21, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress12 } from "viem";
574
599
  import { parseAbi as parseAbi27, encodeFunctionData as encodeFunctionData25 } from "viem";
575
600
  import { parseAbi as parseAbi28, encodeFunctionData as encodeFunctionData26 } from "viem";
576
- import { createPublicClient as createPublicClient21, http as http21, parseAbi as parseAbi29 } from "viem";
601
+ import { createPublicClient as createPublicClient222, http as http222, parseAbi as parseAbi29 } from "viem";
577
602
  function pctToTickDelta(pct) {
578
603
  return Math.round(Math.log(1 + pct / 100) / Math.log(1.0001));
579
604
  }
@@ -827,15 +852,6 @@ function u256ToF642(v) {
827
852
  if (v > MAX_U128) return Infinity;
828
853
  return Number(v);
829
854
  }
830
- function defaultMarketParams(loanToken = zeroAddress10) {
831
- return {
832
- loanToken,
833
- collateralToken: zeroAddress10,
834
- oracle: zeroAddress10,
835
- irm: zeroAddress10,
836
- lltv: 0n
837
- };
838
- }
839
855
  function decodeMarket(data) {
840
856
  if (!data) return null;
841
857
  try {
@@ -1068,7 +1084,7 @@ function createKittenSwapFarming(entry, rpcUrl) {
1068
1084
  const bonusRewardToken = entry.contracts?.["bonus_reward_token"];
1069
1085
  return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken, bonusRewardToken);
1070
1086
  }
1071
- var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI2, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
1087
+ var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, ramsesMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI2, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, NATIVE_CTOKEN_ABI, COMPTROLLER_ABI, NATIVE_SENTINEL, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
1072
1088
  var init_dist2 = __esm({
1073
1089
  "../defi-protocols/dist/index.js"() {
1074
1090
  "use strict";
@@ -1136,6 +1152,10 @@ var init_dist2 = __esm({
1136
1152
  "struct SlipstreamMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; uint160 sqrtPriceX96; }",
1137
1153
  "function mint(SlipstreamMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1138
1154
  ]);
1155
+ ramsesMintAbi = parseAbi3([
1156
+ "struct RamsesMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
1157
+ "function mint(RamsesMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1158
+ ]);
1139
1159
  UniswapV3Adapter = class {
1140
1160
  protocolName;
1141
1161
  router;
@@ -1145,6 +1165,7 @@ var init_dist2 = __esm({
1145
1165
  fee;
1146
1166
  rpcUrl;
1147
1167
  useTickSpacingQuoter;
1168
+ clStyle;
1148
1169
  constructor(entry, rpcUrl) {
1149
1170
  this.protocolName = entry.name;
1150
1171
  const router = entry.contracts?.["router"];
@@ -1157,6 +1178,7 @@ var init_dist2 = __esm({
1157
1178
  this.factory = entry.contracts?.["factory"];
1158
1179
  this.fee = DEFAULT_FEE;
1159
1180
  this.rpcUrl = rpcUrl;
1181
+ this.clStyle = entry.cl_style;
1160
1182
  this.useTickSpacingQuoter = entry.cl_style === "slipstream" || entry.cl_style === "ramses" || entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
1161
1183
  }
1162
1184
  name() {
@@ -1164,7 +1186,15 @@ var init_dist2 = __esm({
1164
1186
  }
1165
1187
  async buildSwap(params) {
1166
1188
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
1167
- const amountOutMinimum = 0n;
1189
+ const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
1190
+ params.slippage,
1191
+ (await this.quote({
1192
+ protocol: this.protocolName,
1193
+ token_in: params.token_in,
1194
+ token_out: params.token_out,
1195
+ amount_in: params.amount_in
1196
+ })).amount_out
1197
+ );
1168
1198
  const data = encodeFunctionData3({
1169
1199
  abi: swapRouterAbi,
1170
1200
  functionName: "exactInputSingle",
@@ -1330,31 +1360,37 @@ var init_dist2 = __esm({
1330
1360
  if (!pm) {
1331
1361
  throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
1332
1362
  }
1333
- const [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
1363
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1364
+ const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
1334
1365
  const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
1335
1366
  const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
1367
+ const slippage = params.slippage ?? defaultSwapSlippage();
1368
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
1369
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
1370
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
1336
1371
  let thirdField = this.fee;
1337
1372
  let tickLower = -887220;
1338
1373
  let tickUpper = 887220;
1339
1374
  if (params.pool && this.rpcUrl) {
1340
1375
  const poolAbi2 = parseAbi3([
1341
1376
  "function fee() view returns (uint24)",
1342
- "function tickSpacing() view returns (int24)",
1343
- "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, bool)"
1377
+ "function tickSpacing() view returns (int24)"
1344
1378
  ]);
1345
1379
  const client = createPublicClient2({ transport: http2(this.rpcUrl) });
1346
- const [poolFee, poolTs, slot0] = await Promise.all([
1380
+ const [poolFee, poolTs, slot0Raw] = await Promise.all([
1347
1381
  client.readContract({ address: params.pool, abi: poolAbi2, functionName: "fee" }).catch(() => null),
1348
1382
  client.readContract({ address: params.pool, abi: poolAbi2, functionName: "tickSpacing" }).catch(() => null),
1349
- client.readContract({ address: params.pool, abi: poolAbi2, functionName: "slot0" }).catch(() => null)
1383
+ client.call({ to: params.pool, data: "0x3850c7bd" }).then((r) => r.data ?? null).catch(() => null)
1350
1384
  ]);
1351
1385
  if (this.useTickSpacingQuoter && poolTs !== null) {
1352
1386
  thirdField = poolTs;
1353
1387
  } else if (poolFee !== null) {
1354
1388
  thirdField = poolFee;
1355
1389
  }
1356
- if (params.range_pct !== void 0 && slot0 && poolTs !== null) {
1357
- const currentTick = slot0[1];
1390
+ if (params.range_pct !== void 0 && slot0Raw && poolTs !== null) {
1391
+ const tickWord = slot0Raw.slice(2 + 64, 2 + 128);
1392
+ const tickU = BigInt("0x" + tickWord);
1393
+ const currentTick = tickU >= 1n << 255n ? Number(tickU - (1n << 256n)) : Number(tickU);
1358
1394
  const rangeTicks = Math.floor(params.range_pct * 100);
1359
1395
  tickLower = Math.floor((currentTick - rangeTicks) / poolTs) * poolTs;
1360
1396
  tickUpper = Math.ceil((currentTick + rangeTicks) / poolTs) * poolTs;
@@ -1362,7 +1398,25 @@ var init_dist2 = __esm({
1362
1398
  }
1363
1399
  if (params.tick_lower !== void 0) tickLower = params.tick_lower;
1364
1400
  if (params.tick_upper !== void 0) tickUpper = params.tick_upper;
1365
- const data = this.useTickSpacingQuoter ? encodeFunctionData3({
1401
+ const data = this.clStyle === "ramses" ? encodeFunctionData3({
1402
+ abi: ramsesMintAbi,
1403
+ functionName: "mint",
1404
+ args: [
1405
+ {
1406
+ token0,
1407
+ token1,
1408
+ tickSpacing: thirdField,
1409
+ tickLower,
1410
+ tickUpper,
1411
+ amount0Desired: amount0,
1412
+ amount1Desired: amount1,
1413
+ amount0Min,
1414
+ amount1Min,
1415
+ recipient: params.recipient,
1416
+ deadline: BigInt("18446744073709551615")
1417
+ }
1418
+ ]
1419
+ }) : this.useTickSpacingQuoter ? encodeFunctionData3({
1366
1420
  abi: slipstreamMintAbi,
1367
1421
  functionName: "mint",
1368
1422
  args: [
@@ -1374,8 +1428,8 @@ var init_dist2 = __esm({
1374
1428
  tickUpper,
1375
1429
  amount0Desired: amount0,
1376
1430
  amount1Desired: amount1,
1377
- amount0Min: 0n,
1378
- amount1Min: 0n,
1431
+ amount0Min,
1432
+ amount1Min,
1379
1433
  recipient: params.recipient,
1380
1434
  deadline: BigInt("18446744073709551615"),
1381
1435
  sqrtPriceX96: 0n
@@ -1393,8 +1447,8 @@ var init_dist2 = __esm({
1393
1447
  tickUpper,
1394
1448
  amount0Desired: amount0,
1395
1449
  amount1Desired: amount1,
1396
- amount0Min: 0n,
1397
- amount1Min: 0n,
1450
+ amount0Min,
1451
+ amount1Min,
1398
1452
  recipient: params.recipient,
1399
1453
  deadline: BigInt("18446744073709551615")
1400
1454
  }
@@ -1428,10 +1482,17 @@ var init_dist2 = __esm({
1428
1482
  const liquidity = params.liquidity;
1429
1483
  const MAX_UINT128 = (1n << 128n) - 1n;
1430
1484
  const deadline = BigInt("18446744073709551615");
1485
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
1486
+ throw DefiError.invalidParam(
1487
+ `[${this.protocolName}] V3 remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Caller must compute expected token outputs from the pool state and apply a slippage tolerance before calling buildRemoveLiquidity.`
1488
+ );
1489
+ }
1490
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1491
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
1431
1492
  const decreaseData = encodeFunctionData3({
1432
1493
  abi: positionManagerAbi,
1433
1494
  functionName: "decreaseLiquidity",
1434
- args: [{ tokenId, liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
1495
+ args: [{ tokenId, liquidity, amount0Min, amount1Min, deadline }]
1435
1496
  });
1436
1497
  const collectData = encodeFunctionData3({
1437
1498
  abi: positionManagerAbi,
@@ -1729,7 +1790,13 @@ var init_dist2 = __esm({
1729
1790
  to: this.router,
1730
1791
  data,
1731
1792
  value: 0n,
1732
- gas_estimate: 25e4
1793
+ gas_estimate: 25e4,
1794
+ // Same LP-approval requirement as Solidly forks: the V2 router pulls the
1795
+ // LP pair token via transferFrom and reverts at gas ~42k otherwise.
1796
+ // Caller passes --pool so we can target the pair contract for approval.
1797
+ // Discovered live on Uniswap V2 Monad WMON/AUSD 2026-05-08
1798
+ // (failed tx 0x2268659a…09ae → recovered with this fix).
1799
+ ...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
1733
1800
  };
1734
1801
  }
1735
1802
  };
@@ -1784,7 +1851,15 @@ var init_dist2 = __esm({
1784
1851
  }
1785
1852
  async buildSwap(params) {
1786
1853
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
1787
- const amountOutMinimum = 0n;
1854
+ const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
1855
+ params.slippage,
1856
+ (await this.quote({
1857
+ protocol: this.protocolName,
1858
+ token_in: params.token_in,
1859
+ token_out: params.token_out,
1860
+ amount_in: params.amount_in
1861
+ })).amount_out
1862
+ );
1788
1863
  const data = encodeFunctionData32({
1789
1864
  abi: abi2,
1790
1865
  functionName: "exactInputSingle",
@@ -1898,7 +1973,12 @@ var init_dist2 = __esm({
1898
1973
  if (!pm) {
1899
1974
  throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
1900
1975
  }
1901
- const [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
1976
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1977
+ const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
1978
+ const slippage = params.slippage ?? defaultSwapSlippage();
1979
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
1980
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
1981
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
1902
1982
  let tickLower = params.tick_lower ?? -887220;
1903
1983
  let tickUpper = params.tick_upper ?? 887220;
1904
1984
  const isSingleSide = rawAmount0 === 0n || rawAmount1 === 0n;
@@ -1935,11 +2015,11 @@ var init_dist2 = __esm({
1935
2015
  const data = this.useSingleQuoter ? encodeFunctionData32({
1936
2016
  abi: algebraV2PmAbi,
1937
2017
  functionName: "mint",
1938
- args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
2018
+ args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1939
2019
  }) : encodeFunctionData32({
1940
2020
  abi: algebraIntegralPmAbi,
1941
2021
  functionName: "mint",
1942
- args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
2022
+ args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1943
2023
  });
1944
2024
  const approvals = [];
1945
2025
  if (amount0 > 0n) approvals.push({ token: token0, spender: pm, amount: amount0 });
@@ -1957,12 +2037,19 @@ var init_dist2 = __esm({
1957
2037
  const pm = this.positionManager;
1958
2038
  if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
1959
2039
  if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
2040
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
2041
+ throw DefiError.invalidParam(
2042
+ `[${this.protocolName}] remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Compute expected outputs from positions(tokenId) + pool.globalState off-chain and apply tolerance.`
2043
+ );
2044
+ }
2045
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
2046
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
1960
2047
  const MAX_UINT128 = (1n << 128n) - 1n;
1961
2048
  const deadline = BigInt("18446744073709551615");
1962
2049
  const decreaseData = encodeFunctionData32({
1963
2050
  abi: algebraSharedPmAbi,
1964
2051
  functionName: "decreaseLiquidity",
1965
- args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
2052
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
1966
2053
  });
1967
2054
  const collectData = encodeFunctionData32({
1968
2055
  abi: algebraSharedPmAbi,
@@ -1989,6 +2076,7 @@ var init_dist2 = __esm({
1989
2076
  BalancerV3Adapter = class {
1990
2077
  protocolName;
1991
2078
  router;
2079
+ pool;
1992
2080
  constructor(entry, _rpcUrl) {
1993
2081
  this.protocolName = entry.name;
1994
2082
  const router = entry.contracts?.["router"];
@@ -1996,19 +2084,29 @@ var init_dist2 = __esm({
1996
2084
  throw new DefiError("CONTRACT_ERROR", "Missing 'router' contract");
1997
2085
  }
1998
2086
  this.router = router;
2087
+ this.pool = entry.contracts?.["pool"];
1999
2088
  }
2000
2089
  name() {
2001
2090
  return this.protocolName;
2002
2091
  }
2003
2092
  async buildSwap(params) {
2004
- const minAmountOut = 0n;
2093
+ if (params.amount_out_min === void 0) {
2094
+ throw DefiError.invalidParam(
2095
+ `[${this.protocolName}] buildSwap requires amount_out_min for slippage protection (SSOT 7.3) \u2014 Balancer V3 has no on-chain quoter wired in this adapter. Compute the floor off-chain (e.g. via the Vault's static-call simulation) and pass it explicitly.`
2096
+ );
2097
+ }
2098
+ if (!this.pool) {
2099
+ throw DefiError.invalidParam(
2100
+ `[${this.protocolName}] buildSwap requires a pool address. Register the pool under [protocol.contracts] as \`pool = "0x..."\` in the protocol's TOML config. Multi-pool routing is intentionally not implemented in this adapter \u2014 for that, quote against the Balancer V3 BatchRouter off-chain and route via a different surface.`
2101
+ );
2102
+ }
2103
+ const minAmountOut = params.amount_out_min;
2005
2104
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
2006
2105
  const data = encodeFunctionData4({
2007
2106
  abi: abi3,
2008
2107
  functionName: "swapSingleTokenExactIn",
2009
2108
  args: [
2010
- zeroAddress2,
2011
- // TODO: resolve pool from registry
2109
+ this.pool,
2012
2110
  params.token_in,
2013
2111
  params.token_out,
2014
2112
  params.amount_in,
@@ -2019,7 +2117,7 @@ var init_dist2 = __esm({
2019
2117
  ]
2020
2118
  });
2021
2119
  return {
2022
- description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3`,
2120
+ description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3 pool ${this.pool}`,
2023
2121
  to: this.router,
2024
2122
  data,
2025
2123
  value: 0n,
@@ -2039,8 +2137,8 @@ var init_dist2 = __esm({
2039
2137
  poolAbi = parseAbi5([
2040
2138
  "function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256)",
2041
2139
  "function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256)",
2042
- "function add_liquidity(uint256[2] amounts, uint256 min_mint_amount) external returns (uint256)",
2043
- "function remove_liquidity(uint256 amount, uint256[2] min_amounts) external returns (uint256[2])"
2140
+ "function add_liquidity(uint256[] amounts, uint256 min_mint_amount) external returns (uint256)",
2141
+ "function remove_liquidity(uint256 amount, uint256[] min_amounts) external returns (uint256[])"
2044
2142
  ]);
2045
2143
  CurveStableSwapAdapter = class {
2046
2144
  protocolName;
@@ -2075,17 +2173,22 @@ var init_dist2 = __esm({
2075
2173
  throw DefiError.unsupported(`[${this.protocolName}] quote requires RPC connection`);
2076
2174
  }
2077
2175
  async buildAddLiquidity(params) {
2176
+ if (!params.pool) {
2177
+ throw DefiError.invalidParam(
2178
+ `[${this.protocolName}] Curve add_liquidity needs --pool <address>. The router does not proxy this call; it lives on the pool itself.`
2179
+ );
2180
+ }
2078
2181
  const data = encodeFunctionData5({
2079
2182
  abi: poolAbi,
2080
2183
  functionName: "add_liquidity",
2081
2184
  args: [[params.amount_a, params.amount_b], 0n]
2082
2185
  });
2083
2186
  const approvals = [];
2084
- if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: this.router, amount: params.amount_a });
2085
- if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: this.router, amount: params.amount_b });
2187
+ if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: params.pool, amount: params.amount_a });
2188
+ if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: params.pool, amount: params.amount_b });
2086
2189
  return {
2087
- description: `[${this.protocolName}] Curve add liquidity`,
2088
- to: this.router,
2190
+ description: `[${this.protocolName}] Curve add liquidity to ${params.pool}`,
2191
+ to: params.pool,
2089
2192
  data,
2090
2193
  value: 0n,
2091
2194
  gas_estimate: 4e5,
@@ -2093,14 +2196,19 @@ var init_dist2 = __esm({
2093
2196
  };
2094
2197
  }
2095
2198
  async buildRemoveLiquidity(params) {
2199
+ if (!params.pool) {
2200
+ throw DefiError.invalidParam(
2201
+ `[${this.protocolName}] Curve remove_liquidity needs --pool <address>. The router does not proxy this call.`
2202
+ );
2203
+ }
2096
2204
  const data = encodeFunctionData5({
2097
2205
  abi: poolAbi,
2098
2206
  functionName: "remove_liquidity",
2099
2207
  args: [params.liquidity, [0n, 0n]]
2100
2208
  });
2101
2209
  return {
2102
- description: `[${this.protocolName}] Curve remove liquidity`,
2103
- to: this.router,
2210
+ description: `[${this.protocolName}] Curve remove liquidity from ${params.pool}`,
2211
+ to: params.pool,
2104
2212
  data,
2105
2213
  value: 0n,
2106
2214
  gas_estimate: 35e4
@@ -2266,7 +2374,12 @@ var init_dist2 = __esm({
2266
2374
  to: this.router,
2267
2375
  data,
2268
2376
  value: 0n,
2269
- gas_estimate: 3e5
2377
+ gas_estimate: 3e5,
2378
+ // The router pulls the LP token via transferFrom; without this approval
2379
+ // the tx reverts at gas ~42k. Caller must pass --pool so we know which
2380
+ // LP pair to approve. Discovered live on Aerodrome USDC/USDT 2026-05-08
2381
+ // (failed tx 0x6d052e0a…3298 → recovered with manual approve 0xa126fc3a).
2382
+ ...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
2270
2383
  };
2271
2384
  }
2272
2385
  };
@@ -2313,6 +2426,11 @@ var init_dist2 = __esm({
2313
2426
  return this.protocolName;
2314
2427
  }
2315
2428
  async buildSwap(params) {
2429
+ if (params.amount_out_min === void 0) {
2430
+ throw DefiError.invalidParam(
2431
+ `[${this.protocolName}] buildSwap requires amount_out_min for slippage protection (SSOT 7.3) \u2014 Thena CL has no on-chain quoter. Compute the floor off-chain (e.g. via the router's static-call simulation) and pass it explicitly.`
2432
+ );
2433
+ }
2316
2434
  const data = encodeFunctionData7({
2317
2435
  abi: thenaRouterAbi,
2318
2436
  functionName: "exactInputSingle",
@@ -2323,7 +2441,7 @@ var init_dist2 = __esm({
2323
2441
  recipient: params.recipient,
2324
2442
  deadline: BigInt(params.deadline ?? 18446744073709551615n),
2325
2443
  amountIn: params.amount_in,
2326
- amountOutMinimum: 0n,
2444
+ amountOutMinimum: params.amount_out_min,
2327
2445
  sqrtPriceLimitX96: 0n
2328
2446
  }]
2329
2447
  });
@@ -2343,7 +2461,12 @@ var init_dist2 = __esm({
2343
2461
  const pm = this.positionManager;
2344
2462
  if (!pm) throw new DefiError("CONTRACT_ERROR", "Position manager not configured");
2345
2463
  if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
2346
- const [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
2464
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
2465
+ const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
2466
+ const slippage = params.slippage ?? defaultSwapSlippage();
2467
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
2468
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
2469
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
2347
2470
  const client = createPublicClient4({ transport: http4(this.rpcUrl) });
2348
2471
  const poolAddr = params.pool;
2349
2472
  let tickSpacing = this.defaultTickSpacing;
@@ -2358,7 +2481,7 @@ var init_dist2 = __esm({
2358
2481
  functionName: "getPool",
2359
2482
  args: [token0, token1, tickSpacing]
2360
2483
  });
2361
- if (pool === zeroAddress3) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2484
+ if (pool === zeroAddress2) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2362
2485
  }
2363
2486
  if (pool) {
2364
2487
  const [slot0, ts] = await Promise.all([
@@ -2401,8 +2524,8 @@ var init_dist2 = __esm({
2401
2524
  tickUpper,
2402
2525
  amount0Desired: rawAmount0,
2403
2526
  amount1Desired: rawAmount1,
2404
- amount0Min: 0n,
2405
- amount1Min: 0n,
2527
+ amount0Min,
2528
+ amount1Min,
2406
2529
  recipient: params.recipient,
2407
2530
  deadline: BigInt("18446744073709551615"),
2408
2531
  sqrtPriceX96: 0n
@@ -2424,12 +2547,19 @@ var init_dist2 = __esm({
2424
2547
  const pm = this.positionManager;
2425
2548
  if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
2426
2549
  if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
2550
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
2551
+ throw DefiError.invalidParam(
2552
+ `[${this.protocolName}] remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Compute expected outputs from positions(tokenId) + pool.slot0 off-chain and apply tolerance.`
2553
+ );
2554
+ }
2555
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
2556
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
2427
2557
  const MAX_UINT128 = (1n << 128n) - 1n;
2428
2558
  const deadline = BigInt("18446744073709551615");
2429
2559
  const decreaseData = encodeFunctionData7({
2430
2560
  abi: thenaPmAbi,
2431
2561
  functionName: "decreaseLiquidity",
2432
- args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
2562
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
2433
2563
  });
2434
2564
  const collectData = encodeFunctionData7({
2435
2565
  abi: thenaPmAbi,
@@ -2496,8 +2626,8 @@ var init_dist2 = __esm({
2496
2626
  const ve = entry.contracts?.["ve_token"];
2497
2627
  if (!ve) throw new DefiError("CONTRACT_ERROR", "Missing 've_token' contract");
2498
2628
  this.veToken = ve;
2499
- this.voter = entry.contracts?.["voter"] ?? zeroAddress4;
2500
- this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress4;
2629
+ this.voter = entry.contracts?.["voter"] ?? zeroAddress3;
2630
+ this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress3;
2501
2631
  this.poolFactory = entry.contracts?.["pool_factory"];
2502
2632
  this.rpcUrl = rpcUrl;
2503
2633
  }
@@ -2539,7 +2669,7 @@ var init_dist2 = __esm({
2539
2669
  ]);
2540
2670
  }
2541
2671
  const poolAddressResults = await multicallRead(this.rpcUrl, poolAddressCalls);
2542
- const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !== zeroAddress4);
2672
+ const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !== zeroAddress3);
2543
2673
  if (pools.length === 0) return [];
2544
2674
  const gaugeCalls = pools.map((pool) => [
2545
2675
  this.gaugeManager,
@@ -2549,7 +2679,7 @@ var init_dist2 = __esm({
2549
2679
  const gaugedPools = [];
2550
2680
  for (let i = 0; i < pools.length; i++) {
2551
2681
  const gauge = decodeAddress(gaugeResults[i] ?? null);
2552
- if (gauge && gauge !== zeroAddress4) {
2682
+ if (gauge && gauge !== zeroAddress3) {
2553
2683
  gaugedPools.push({ pool: pools[i], gauge });
2554
2684
  }
2555
2685
  }
@@ -2564,8 +2694,8 @@ var init_dist2 = __esm({
2564
2694
  for (let i = 0; i < gaugedPools.length; i++) {
2565
2695
  const t0 = decodeAddress(tokenResults[i * 2] ?? null);
2566
2696
  const t1 = decodeAddress(tokenResults[i * 2 + 1] ?? null);
2567
- if (t0 && t0 !== zeroAddress4) tokenAddrs.add(t0);
2568
- if (t1 && t1 !== zeroAddress4) tokenAddrs.add(t1);
2697
+ if (t0 && t0 !== zeroAddress3) tokenAddrs.add(t0);
2698
+ if (t1 && t1 !== zeroAddress3) tokenAddrs.add(t1);
2569
2699
  }
2570
2700
  const uniqueTokens = Array.from(tokenAddrs);
2571
2701
  const symbolCalls = uniqueTokens.map((t) => [
@@ -2604,7 +2734,7 @@ var init_dist2 = __esm({
2604
2734
  functionName: "gauges",
2605
2735
  args: [pool]
2606
2736
  });
2607
- if (gauge === zeroAddress4) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
2737
+ if (gauge === zeroAddress3) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
2608
2738
  return gauge;
2609
2739
  }
2610
2740
  // ─── CL Gauge: NFT Deposit/Withdraw ──────────────────────────
@@ -2766,7 +2896,7 @@ var init_dist2 = __esm({
2766
2896
  params.amount_in,
2767
2897
  minToAmount,
2768
2898
  params.recipient,
2769
- zeroAddress5
2899
+ zeroAddress4
2770
2900
  ]
2771
2901
  });
2772
2902
  return {
@@ -2991,7 +3121,7 @@ var init_dist2 = __esm({
2991
3121
  poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
2992
3122
  }
2993
3123
  const poolResults = await multicallRead(this.rpcUrl, poolCalls);
2994
- pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
3124
+ pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
2995
3125
  } else {
2996
3126
  const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
2997
3127
  const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
@@ -3016,17 +3146,17 @@ var init_dist2 = __esm({
3016
3146
  const pairCalls = [];
3017
3147
  for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
3018
3148
  const pairResults = await multicallRead(this.rpcUrl, pairCalls);
3019
- pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
3149
+ pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
3020
3150
  }
3021
3151
  if (pairs.length === 0) return;
3022
3152
  const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
3023
3153
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
3024
- const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3154
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
3025
3155
  if (allNull) {
3026
3156
  const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
3027
3157
  gaugeResults = await multicallRead(this.rpcUrl, fb1);
3028
3158
  }
3029
- const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3159
+ const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
3030
3160
  if (allNull2) {
3031
3161
  const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
3032
3162
  gaugeResults = await multicallRead(this.rpcUrl, fb2);
@@ -3034,7 +3164,7 @@ var init_dist2 = __esm({
3034
3164
  const gaugedPairs = [];
3035
3165
  for (let i = 0; i < pairs.length; i++) {
3036
3166
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
3037
- if (gauge && gauge !== zeroAddress6) {
3167
+ if (gauge && gauge !== zeroAddress5) {
3038
3168
  gaugedPairs.push({ pair: pairs[i], gauge });
3039
3169
  }
3040
3170
  }
@@ -3050,8 +3180,8 @@ var init_dist2 = __esm({
3050
3180
  for (let i = 0; i < gaugedPairs.length; i++) {
3051
3181
  const t0 = decodeAddress2(metaResults[i * 3] ?? null);
3052
3182
  const t1 = decodeAddress2(metaResults[i * 3 + 1] ?? null);
3053
- if (t0 && t0 !== zeroAddress6) tokenAddrs.add(t0);
3054
- if (t1 && t1 !== zeroAddress6) tokenAddrs.add(t1);
3183
+ if (t0 && t0 !== zeroAddress5) tokenAddrs.add(t0);
3184
+ if (t1 && t1 !== zeroAddress5) tokenAddrs.add(t1);
3055
3185
  }
3056
3186
  const uniqueTokens = Array.from(tokenAddrs);
3057
3187
  const symbolCalls = uniqueTokens.map((t) => [
@@ -3141,7 +3271,7 @@ var init_dist2 = __esm({
3141
3271
  const candidatePools = [];
3142
3272
  for (let i = 0; i < getPoolCalls.length; i++) {
3143
3273
  const pool = decodeAddress2(getPoolResults[i] ?? null);
3144
- if (pool && pool !== zeroAddress6) {
3274
+ if (pool && pool !== zeroAddress5) {
3145
3275
  const { pairIdx, tickSpacing } = callMeta[i];
3146
3276
  const [tokenA, tokenB] = pairs[pairIdx];
3147
3277
  candidatePools.push({ pool, tokenA, tokenB, tickSpacing });
@@ -3153,7 +3283,7 @@ var init_dist2 = __esm({
3153
3283
  encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pool] })
3154
3284
  ]);
3155
3285
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
3156
- const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3286
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
3157
3287
  if (allNull) {
3158
3288
  const fallbackCalls = candidatePools.map(({ pool }) => [
3159
3289
  this.voter,
@@ -3164,7 +3294,7 @@ var init_dist2 = __esm({
3164
3294
  const gaugedCL = [];
3165
3295
  for (let i = 0; i < candidatePools.length; i++) {
3166
3296
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
3167
- if (gauge && gauge !== zeroAddress6) {
3297
+ if (gauge && gauge !== zeroAddress5) {
3168
3298
  gaugedCL.push({ ...candidatePools[i], gauge });
3169
3299
  }
3170
3300
  }
@@ -3194,8 +3324,8 @@ var init_dist2 = __esm({
3194
3324
  const { pool, gauge, tokenA, tokenB, tickSpacing } = gaugedCL[i];
3195
3325
  const rawT0 = decodeAddress2(poolTokenResults[i * 2] ?? null);
3196
3326
  const rawT1 = decodeAddress2(poolTokenResults[i * 2 + 1] ?? null);
3197
- const t0 = rawT0 && rawT0 !== zeroAddress6 ? rawT0 : tokenA;
3198
- const t1 = rawT1 && rawT1 !== zeroAddress6 ? rawT1 : tokenB;
3327
+ const t0 = rawT0 && rawT0 !== zeroAddress5 ? rawT0 : tokenA;
3328
+ const t1 = rawT1 && rawT1 !== zeroAddress5 ? rawT1 : tokenB;
3199
3329
  out.push({
3200
3330
  pool,
3201
3331
  gauge,
@@ -3297,7 +3427,7 @@ var init_dist2 = __esm({
3297
3427
  functionName: fn,
3298
3428
  args: [pool]
3299
3429
  });
3300
- if (gauge !== zeroAddress6) return gauge;
3430
+ if (gauge !== zeroAddress5) return gauge;
3301
3431
  } catch {
3302
3432
  }
3303
3433
  }
@@ -3351,7 +3481,7 @@ var init_dist2 = __esm({
3351
3481
  abi: gaugeAbi,
3352
3482
  functionName: "rewardToken"
3353
3483
  });
3354
- if (rt !== zeroAddress6) return { tokens: [rt], multiToken: false };
3484
+ if (rt !== zeroAddress5) return { tokens: [rt], multiToken: false };
3355
3485
  } catch {
3356
3486
  }
3357
3487
  return { tokens: [], multiToken: false };
@@ -3361,7 +3491,7 @@ var init_dist2 = __esm({
3361
3491
  const data = encodeFunctionData10({
3362
3492
  abi: gaugeAbi,
3363
3493
  functionName: "getReward",
3364
- args: [account ?? zeroAddress6]
3494
+ args: [account ?? zeroAddress5]
3365
3495
  });
3366
3496
  return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
3367
3497
  }
@@ -3585,7 +3715,7 @@ var init_dist2 = __esm({
3585
3715
  functionName: "earned",
3586
3716
  args: [user]
3587
3717
  });
3588
- results.push({ token: zeroAddress6, symbol: "unknown", amount: earned });
3718
+ results.push({ token: zeroAddress5, symbol: "unknown", amount: earned });
3589
3719
  } catch {
3590
3720
  }
3591
3721
  }
@@ -3947,20 +4077,47 @@ var init_dist2 = __esm({
3947
4077
  /**
3948
4078
  * Build an addLiquidity transaction for a Liquidity Book pair.
3949
4079
  * Distributes tokenX/tokenY uniformly across active bin ± numBins.
4080
+ *
4081
+ * The LB pair stores tokenX/tokenY in the order set at factory creation,
4082
+ * which is **not** address-sorted. Callers may pass tokens in either order;
4083
+ * we always re-align with the pool's `getTokenX/getTokenY` (and swap amounts
4084
+ * accordingly) so the router's `if (tokenX != pair.tokenX) revert` path is
4085
+ * never hit.
3950
4086
  */
3951
4087
  async buildAddLiquidity(params) {
3952
4088
  const numBins = params.numBins ?? 5;
3953
4089
  const deadline = params.deadline ?? BigInt("18446744073709551615");
3954
- let activeIdDesired = params.activeIdDesired;
3955
- if (activeIdDesired === void 0) {
3956
- const rpcUrl = this.requireRpc();
3957
- const client = createPublicClient8({ transport: http8(rpcUrl) });
3958
- const activeId = await client.readContract({
3959
- address: params.pool,
3960
- abi: lbPairAbi,
3961
- functionName: "getActiveId"
3962
- });
3963
- activeIdDesired = activeId;
4090
+ const rpcUrl = this.requireRpc();
4091
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
4092
+ const [poolTokenX, poolTokenY, onChainActiveId] = await Promise.all([
4093
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
4094
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" }),
4095
+ params.activeIdDesired === void 0 ? client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getActiveId" }) : Promise.resolve(params.activeIdDesired)
4096
+ ]);
4097
+ const activeIdDesired = onChainActiveId;
4098
+ const inX = params.tokenX.toLowerCase();
4099
+ const inY = params.tokenY.toLowerCase();
4100
+ const poolX = poolTokenX.toLowerCase();
4101
+ const poolY = poolTokenY.toLowerCase();
4102
+ let tokenX;
4103
+ let tokenY;
4104
+ let amountX;
4105
+ let amountY;
4106
+ if (inX === poolX && inY === poolY) {
4107
+ tokenX = params.tokenX;
4108
+ tokenY = params.tokenY;
4109
+ amountX = params.amountX;
4110
+ amountY = params.amountY;
4111
+ } else if (inX === poolY && inY === poolX) {
4112
+ tokenX = poolTokenX;
4113
+ tokenY = poolTokenY;
4114
+ amountX = params.amountY;
4115
+ amountY = params.amountX;
4116
+ } else {
4117
+ throw new DefiError(
4118
+ "CONTRACT_ERROR",
4119
+ `[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
4120
+ );
3964
4121
  }
3965
4122
  const deltaIds = [];
3966
4123
  for (let d = -numBins; d <= numBins; d++) {
@@ -3972,11 +4129,11 @@ var init_dist2 = __esm({
3972
4129
  functionName: "addLiquidity",
3973
4130
  args: [
3974
4131
  {
3975
- tokenX: params.tokenX,
3976
- tokenY: params.tokenY,
4132
+ tokenX,
4133
+ tokenY,
3977
4134
  binStep: BigInt(params.binStep),
3978
- amountX: params.amountX,
3979
- amountY: params.amountY,
4135
+ amountX,
4136
+ amountY,
3980
4137
  amountXMin: 0n,
3981
4138
  amountYMin: 0n,
3982
4139
  activeIdDesired: BigInt(activeIdDesired),
@@ -3991,31 +4148,63 @@ var init_dist2 = __esm({
3991
4148
  ]
3992
4149
  });
3993
4150
  return {
3994
- description: `[${this.protocolName}] LB addLiquidity ${params.amountX} tokenX + ${params.amountY} tokenY across ${deltaIds.length} bins`,
4151
+ description: `[${this.protocolName}] LB addLiquidity ${amountX} tokenX + ${amountY} tokenY across ${deltaIds.length} bins`,
3995
4152
  to: this.lbRouter,
3996
4153
  data,
3997
4154
  value: 0n,
3998
4155
  gas_estimate: 8e5,
3999
4156
  approvals: [
4000
- { token: params.tokenX, spender: this.lbRouter, amount: params.amountX },
4001
- { token: params.tokenY, spender: this.lbRouter, amount: params.amountY }
4157
+ { token: tokenX, spender: this.lbRouter, amount: amountX },
4158
+ { token: tokenY, spender: this.lbRouter, amount: amountY }
4002
4159
  ]
4003
4160
  };
4004
4161
  }
4005
4162
  /**
4006
4163
  * Build a removeLiquidity transaction for specific LB bins.
4164
+ *
4165
+ * When `pool` is supplied, the adapter realigns tokenX/tokenY (and
4166
+ * amountXMin/amountYMin) to the pool's actual ordering. Otherwise it trusts
4167
+ * the caller — the router will revert if the order is wrong.
4007
4168
  */
4008
4169
  async buildRemoveLiquidity(params) {
4009
4170
  const deadline = params.deadline ?? BigInt("18446744073709551615");
4171
+ let tokenX = params.tokenX;
4172
+ let tokenY = params.tokenY;
4173
+ let amountXMin = params.amountXMin ?? 0n;
4174
+ let amountYMin = params.amountYMin ?? 0n;
4175
+ if (params.pool) {
4176
+ const rpcUrl = this.requireRpc();
4177
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
4178
+ const [poolTokenX, poolTokenY] = await Promise.all([
4179
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
4180
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" })
4181
+ ]);
4182
+ const inX = params.tokenX.toLowerCase();
4183
+ const inY = params.tokenY.toLowerCase();
4184
+ const poolX = poolTokenX.toLowerCase();
4185
+ const poolY = poolTokenY.toLowerCase();
4186
+ if (inX === poolY && inY === poolX) {
4187
+ tokenX = poolTokenX;
4188
+ tokenY = poolTokenY;
4189
+ const x = amountXMin;
4190
+ amountXMin = amountYMin;
4191
+ amountYMin = x;
4192
+ } else if (!(inX === poolX && inY === poolY)) {
4193
+ throw new DefiError(
4194
+ "CONTRACT_ERROR",
4195
+ `[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
4196
+ );
4197
+ }
4198
+ }
4010
4199
  const data = encodeFunctionData12({
4011
4200
  abi: lbRouterAbi,
4012
4201
  functionName: "removeLiquidity",
4013
4202
  args: [
4014
- params.tokenX,
4015
- params.tokenY,
4203
+ tokenX,
4204
+ tokenY,
4016
4205
  params.binStep,
4017
- params.amountXMin ?? 0n,
4018
- params.amountYMin ?? 0n,
4206
+ amountXMin,
4207
+ amountYMin,
4019
4208
  params.binIds.map(BigInt),
4020
4209
  params.amounts,
4021
4210
  params.recipient,
@@ -4925,7 +5114,7 @@ var init_dist2 = __esm({
4925
5114
  const poolSet = /* @__PURE__ */ new Set();
4926
5115
  for (const data of poolResults) {
4927
5116
  const addr = decodeAddress3(data);
4928
- if (addr && addr !== zeroAddress7) {
5117
+ if (addr && addr !== zeroAddress6) {
4929
5118
  poolSet.add(addr.toLowerCase());
4930
5119
  }
4931
5120
  }
@@ -5090,6 +5279,34 @@ var init_dist2 = __esm({
5090
5279
  }
5091
5280
  return apr;
5092
5281
  }
5282
+ /**
5283
+ * Snapshot of all NEST gauged pools from the off-chain blaze API. The
5284
+ * on-chain ve(3,3) gauges return `rewardRate=0` (emissions are credited
5285
+ * off-chain via signed claim tickets), so the on-chain Solidly gauge
5286
+ * reader cannot surface real emission APR. This API is the canonical
5287
+ * read source — used by `lp discover` to expose non-zero NEST yields.
5288
+ */
5289
+ async getLiquidityPools() {
5290
+ const raw = await this.fetchJson("/liquidity-pools");
5291
+ const out = [];
5292
+ for (const p of raw) {
5293
+ const t0 = p.token0?.basetoken;
5294
+ const t1 = p.token1?.basetoken;
5295
+ if (!t0?.address || !t1?.address) continue;
5296
+ out.push({
5297
+ pool: p.id,
5298
+ gauge: p.gauge ?? null,
5299
+ token0: { address: t0.address, symbol: t0.symbol ?? "?" },
5300
+ token1: { address: t1.address, symbol: t1.symbol ?? "?" },
5301
+ tvlUSD: Number(p.tvlUSD ?? 0),
5302
+ aprPercent: Number(p.apr ?? 0),
5303
+ curEpochEmissionRewardsUSD: Number(p.curEpochEmissionRewardsUSD ?? 0),
5304
+ poolType: p.poolType,
5305
+ isStable: p.isStable
5306
+ });
5307
+ }
5308
+ return out;
5309
+ }
5093
5310
  /** Pending NEST emissions as IGauge-compatible RewardInfo[] */
5094
5311
  async getPendingRewards(user) {
5095
5312
  const status = await this.getClaimStatus(user);
@@ -5245,6 +5462,16 @@ var init_dist2 = __esm({
5245
5462
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
5246
5463
  "function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
5247
5464
  "function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
5465
+ // Toggles required to actually borrow against an isolation-mode reserve
5466
+ // (https://aave.com/docs/aave-v3/smart-contracts/pool):
5467
+ // - setUserUseReserveAsCollateral: an isolation reserve can be enabled
5468
+ // only if no other asset is enabled; LTV=0 reserves can never be
5469
+ // enabled; disable reverts if the resulting HF would drop below the
5470
+ // liquidation threshold.
5471
+ // - setUserEMode: reverts if the user is borrowing a non-eMode-
5472
+ // compatible asset, or if the change would push HF below threshold.
5473
+ "function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external",
5474
+ "function setUserEMode(uint8 categoryId) external",
5248
5475
  "function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
5249
5476
  "function getReservesList() external view returns (address[])",
5250
5477
  "function getReserveData(address asset) external view returns (uint256 configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt)"
@@ -5370,6 +5597,34 @@ var init_dist2 = __esm({
5370
5597
  gas_estimate: 25e4
5371
5598
  };
5372
5599
  }
5600
+ async buildSetUseReserveAsCollateral(asset, useAsCollateral) {
5601
+ const data = encodeFunctionData14({
5602
+ abi: POOL_ABI,
5603
+ functionName: "setUserUseReserveAsCollateral",
5604
+ args: [asset, useAsCollateral]
5605
+ });
5606
+ return {
5607
+ description: `[${this.protocolName}] ${useAsCollateral ? "Enable" : "Disable"} ${asset} as collateral`,
5608
+ to: this.pool,
5609
+ data,
5610
+ value: 0n,
5611
+ gas_estimate: 1e5
5612
+ };
5613
+ }
5614
+ async buildSetEMode(categoryId) {
5615
+ const data = encodeFunctionData14({
5616
+ abi: POOL_ABI,
5617
+ functionName: "setUserEMode",
5618
+ args: [categoryId]
5619
+ });
5620
+ return {
5621
+ description: `[${this.protocolName}] Set eMode category to ${categoryId}${categoryId === 0 ? " (opt out)" : ""}`,
5622
+ to: this.pool,
5623
+ data,
5624
+ value: 0n,
5625
+ gas_estimate: 15e4
5626
+ };
5627
+ }
5373
5628
  async getRates(asset) {
5374
5629
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5375
5630
  const reserveCallData = encodeFunctionData14({
@@ -5414,7 +5669,7 @@ var init_dist2 = __esm({
5414
5669
  [aTokenAddress, encodeFunctionData14({ abi: INCENTIVES_ABI, functionName: "getIncentivesController" })]
5415
5670
  ]);
5416
5671
  const controllerAddr = decodeAddress4(controllerRaw ?? null);
5417
- if (controllerAddr && controllerAddr !== zeroAddress8) {
5672
+ if (controllerAddr && controllerAddr !== zeroAddress7) {
5418
5673
  const [supplyRewardsRaw, borrowRewardsRaw] = await multicallRead(this.rpcUrl, [
5419
5674
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [aTokenAddress] })],
5420
5675
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [variableDebtTokenAddress] })]
@@ -5610,8 +5865,8 @@ var init_dist2 = __esm({
5610
5865
  if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
5611
5866
  }
5612
5867
  } catch {
5613
- if (collateralUsd > 0) supplies.push({ asset: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5614
- if (debtUsd > 0) borrows.push({ asset: zeroAddress8, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
5868
+ if (collateralUsd > 0) supplies.push({ asset: zeroAddress7, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5869
+ if (debtUsd > 0) borrows.push({ asset: zeroAddress7, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
5615
5870
  }
5616
5871
  return {
5617
5872
  protocol: this.protocolName,
@@ -5778,8 +6033,8 @@ var init_dist2 = __esm({
5778
6033
  const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
5779
6034
  const debtUsd = u256ToF642(totalDebtBase) / 1e18;
5780
6035
  const ltvBps = u256ToF642(ltv);
5781
- const supplies = collateralUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5782
- const borrows = debtUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
6036
+ const supplies = collateralUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
6037
+ const borrows = debtUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
5783
6038
  return {
5784
6039
  protocol: this.protocolName,
5785
6040
  user,
@@ -5882,14 +6137,31 @@ var init_dist2 = __esm({
5882
6137
  "function borrow(uint256 borrowAmount) external returns (uint256)",
5883
6138
  "function repayBorrow(uint256 repayAmount) external returns (uint256)"
5884
6139
  ]);
6140
+ NATIVE_CTOKEN_ABI = parseAbi17([
6141
+ "function mint() external payable",
6142
+ "function repayBorrow() external payable"
6143
+ ]);
6144
+ COMPTROLLER_ABI = parseAbi17([
6145
+ "function enterMarkets(address[] cTokens) external returns (uint256[])",
6146
+ "function exitMarket(address cToken) external returns (uint256)"
6147
+ ]);
6148
+ NATIVE_SENTINEL = "0x0000000000000000000000000000000000000000";
5885
6149
  BSC_BLOCKS_PER_YEAR = 10512e3;
5886
6150
  CompoundV2Adapter = class {
5887
6151
  protocolName;
5888
6152
  defaultVtoken;
5889
6153
  vTokenCandidates;
6154
+ comptroller;
5890
6155
  rpcUrl;
5891
- // Lazy cache: underlying asset address (lowercased) → vToken address
6156
+ // Lazy cache: underlying asset address (lowercased) → vToken address.
6157
+ // The native sentinel (0x0…) is mapped to the cETH/vBNB-style vToken
6158
+ // when one is detected during resolveVtoken().
5892
6159
  vTokenByAsset = null;
6160
+ // The cETH/vBNB-style vToken whose underlying() reverts (it has no
6161
+ // ERC20 underlying — the underlying is the chain's native gas token).
6162
+ // Set lazily by resolveVtoken() and consulted by buildSupply/buildRepay
6163
+ // to switch to the payable mint() / repayBorrow() variants.
6164
+ nativeVtoken = null;
5893
6165
  constructor(entry, rpcUrl) {
5894
6166
  this.protocolName = entry.name;
5895
6167
  this.rpcUrl = rpcUrl;
@@ -5897,6 +6169,7 @@ var init_dist2 = __esm({
5897
6169
  const vtoken = contracts["vusdt"] ?? contracts["vusdc"] ?? contracts["vbnb"] ?? contracts["comptroller"];
5898
6170
  if (!vtoken) throw DefiError.contractError("Missing vToken or comptroller address");
5899
6171
  this.defaultVtoken = vtoken;
6172
+ this.comptroller = contracts["comptroller"];
5900
6173
  this.vTokenCandidates = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k)).map(([, v]) => v);
5901
6174
  if (this.vTokenCandidates.length === 0) this.vTokenCandidates = [vtoken];
5902
6175
  }
@@ -5905,19 +6178,38 @@ var init_dist2 = __esm({
5905
6178
  if (!this.vTokenByAsset) {
5906
6179
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
5907
6180
  const map = /* @__PURE__ */ new Map();
6181
+ let nativeVtoken = null;
5908
6182
  const lookups = await Promise.allSettled(
5909
6183
  this.vTokenCandidates.map(async (v) => {
5910
- const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
5911
- return [u.toLowerCase(), v];
6184
+ try {
6185
+ const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
6186
+ return { vtoken: v, underlying: u };
6187
+ } catch {
6188
+ return { vtoken: v, underlying: null };
6189
+ }
5912
6190
  })
5913
6191
  );
5914
6192
  for (const r of lookups) {
5915
- if (r.status === "fulfilled") map.set(r.value[0], r.value[1]);
6193
+ if (r.status !== "fulfilled") continue;
6194
+ const { vtoken, underlying } = r.value;
6195
+ if (underlying) {
6196
+ map.set(underlying.toLowerCase(), vtoken);
6197
+ } else if (!nativeVtoken) {
6198
+ nativeVtoken = vtoken;
6199
+ }
6200
+ }
6201
+ if (nativeVtoken) {
6202
+ map.set(NATIVE_SENTINEL, nativeVtoken);
5916
6203
  }
5917
6204
  this.vTokenByAsset = map;
6205
+ this.nativeVtoken = nativeVtoken;
5918
6206
  }
5919
6207
  return this.vTokenByAsset.get(asset.toLowerCase()) ?? null;
5920
6208
  }
6209
+ /** True iff `vtoken` is the cETH/vBNB-style native cToken for this protocol. */
6210
+ isNativeVtoken(vtoken) {
6211
+ return this.nativeVtoken !== null && vtoken.toLowerCase() === this.nativeVtoken.toLowerCase();
6212
+ }
5921
6213
  name() {
5922
6214
  return this.protocolName;
5923
6215
  }
@@ -5931,6 +6223,16 @@ var init_dist2 = __esm({
5931
6223
  }
5932
6224
  async buildSupply(params) {
5933
6225
  const vtoken = await this.vtokenFor(params.asset);
6226
+ if (this.isNativeVtoken(vtoken)) {
6227
+ const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "mint" });
6228
+ return {
6229
+ description: `[${this.protocolName}] Supply ${params.amount} (native) to Venus`,
6230
+ to: vtoken,
6231
+ data: data2,
6232
+ value: params.amount,
6233
+ gas_estimate: 3e5
6234
+ };
6235
+ }
5934
6236
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "mint", args: [params.amount] });
5935
6237
  return {
5936
6238
  description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to Venus`,
@@ -5954,6 +6256,16 @@ var init_dist2 = __esm({
5954
6256
  }
5955
6257
  async buildRepay(params) {
5956
6258
  const vtoken = await this.vtokenFor(params.asset);
6259
+ if (this.isNativeVtoken(vtoken)) {
6260
+ const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "repayBorrow" });
6261
+ return {
6262
+ description: `[${this.protocolName}] Repay ${params.amount} (native) to Venus`,
6263
+ to: vtoken,
6264
+ data: data2,
6265
+ value: params.amount,
6266
+ gas_estimate: 3e5
6267
+ };
6268
+ }
5957
6269
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "repayBorrow", args: [params.amount] });
5958
6270
  return {
5959
6271
  description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to Venus`,
@@ -5966,6 +6278,37 @@ var init_dist2 = __esm({
5966
6278
  }
5967
6279
  async buildWithdraw(params) {
5968
6280
  const vtoken = await this.vtokenFor(params.asset);
6281
+ const MAX_UINT2562 = (1n << 256n) - 1n;
6282
+ if (params.amount === MAX_UINT2562 && this.rpcUrl) {
6283
+ const client = createPublicClient13({ transport: http13(this.rpcUrl) });
6284
+ const [vtokenBalance, borrowBalance] = await Promise.all([
6285
+ client.readContract({
6286
+ address: vtoken,
6287
+ abi: CTOKEN_ABI,
6288
+ functionName: "balanceOf",
6289
+ args: [params.to]
6290
+ }),
6291
+ client.readContract({
6292
+ address: vtoken,
6293
+ abi: CTOKEN_ABI,
6294
+ functionName: "borrowBalanceStored",
6295
+ args: [params.to]
6296
+ }).catch(() => 0n)
6297
+ ]);
6298
+ if (borrowBalance > 0n) {
6299
+ throw DefiError.contractError(
6300
+ `[${this.protocolName}] Cannot withdraw all (uint256.max) \u2014 wallet has an outstanding borrow of ${borrowBalance} on this market. Repay the borrow first, or pass an explicit --amount that leaves enough collateral.`
6301
+ );
6302
+ }
6303
+ const redeemData = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeem", args: [vtokenBalance] });
6304
+ return {
6305
+ description: `[${this.protocolName}] Withdraw all (auto-max, ${vtokenBalance} vTokens) of ${params.asset} from Venus`,
6306
+ to: vtoken,
6307
+ data: redeemData,
6308
+ value: 0n,
6309
+ gas_estimate: 35e4
6310
+ };
6311
+ }
5969
6312
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeemUnderlying", args: [params.amount] });
5970
6313
  return {
5971
6314
  description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from Venus`,
@@ -5975,6 +6318,37 @@ var init_dist2 = __esm({
5975
6318
  gas_estimate: 25e4
5976
6319
  };
5977
6320
  }
6321
+ /**
6322
+ * Compound V2 family: enter cTokens as collateral via Comptroller.
6323
+ * Without this call, supplied assets sit dormant in the Comptroller's
6324
+ * accountAssets[] and `getAccountLiquidity` reports zero collateral —
6325
+ * any borrow then reverts. Mirrors the role of Aave V3's
6326
+ * setUserUseReserveAsCollateral, but the API is batch-by-cToken.
6327
+ */
6328
+ async buildEnterMarkets(cTokens) {
6329
+ if (!this.comptroller) {
6330
+ throw DefiError.contractError(
6331
+ `[${this.protocolName}] enterMarkets requires the Comptroller address to be registered under [protocol.contracts] as 'comptroller'.`
6332
+ );
6333
+ }
6334
+ if (cTokens.length === 0) {
6335
+ throw DefiError.invalidParam(
6336
+ `[${this.protocolName}] enterMarkets requires at least one cToken address.`
6337
+ );
6338
+ }
6339
+ const data = encodeFunctionData16({
6340
+ abi: COMPTROLLER_ABI,
6341
+ functionName: "enterMarkets",
6342
+ args: [cTokens]
6343
+ });
6344
+ return {
6345
+ description: `[${this.protocolName}] Enter ${cTokens.length} market(s) as collateral`,
6346
+ to: this.comptroller,
6347
+ data,
6348
+ value: 0n,
6349
+ gas_estimate: 2e5
6350
+ };
6351
+ }
5978
6352
  async getRates(asset) {
5979
6353
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5980
6354
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
@@ -6342,9 +6716,11 @@ var init_dist2 = __esm({
6342
6716
  "function market(bytes32 id) external view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
6343
6717
  "function idToMarketParams(bytes32 id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)",
6344
6718
  "function supply((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) external returns (uint256 assetsSupplied, uint256 sharesSupplied)",
6719
+ "function supplyCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, bytes data) external",
6345
6720
  "function borrow((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed)",
6346
6721
  "function repay((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) external returns (uint256 assetsRepaid, uint256 sharesRepaid)",
6347
- "function withdraw((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn)"
6722
+ "function withdraw((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn)",
6723
+ "function withdrawCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, address receiver) external"
6348
6724
  ]);
6349
6725
  META_MORPHO_ABI = parseAbi20([
6350
6726
  "function supplyQueueLength() external view returns (uint256)",
@@ -6371,6 +6747,8 @@ var init_dist2 = __esm({
6371
6747
  rpcUrl;
6372
6748
  metaMorphoVaults;
6373
6749
  metaMorphoVaultEntries;
6750
+ namedMarkets;
6751
+ namedMarketByName;
6374
6752
  vaultAssetMap = null;
6375
6753
  constructor(entry, rpcUrl) {
6376
6754
  this.protocolName = entry.name;
@@ -6382,6 +6760,26 @@ var init_dist2 = __esm({
6382
6760
  this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
6383
6761
  this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
6384
6762
  this.metaMorphoVaults = this.metaMorphoVaultEntries.map((e) => e.addr);
6763
+ this.namedMarkets = entry.markets ?? [];
6764
+ const byName = /* @__PURE__ */ new Map();
6765
+ for (const m of this.namedMarkets) byName.set(m.name.toLowerCase(), m.id);
6766
+ this.namedMarketByName = byName;
6767
+ }
6768
+ /**
6769
+ * Resolve a friendly market name (e.g. `WMON-AUSD`) to its 32-byte
6770
+ * marketId via the per-protocol TOML registry. Returns null when the
6771
+ * adapter has no markets[] block or the name doesn't match any entry —
6772
+ * callers fall back to treating the input as a raw hex marketId.
6773
+ */
6774
+ resolveMarketIdByName(name) {
6775
+ return this.namedMarketByName.get(name.toLowerCase()) ?? null;
6776
+ }
6777
+ /**
6778
+ * Returns the registered named markets for diagnostics (e.g. CLI error
6779
+ * messages listing valid choices when the user passes an unknown name).
6780
+ */
6781
+ listNamedMarkets() {
6782
+ return this.namedMarkets;
6385
6783
  }
6386
6784
  async resolveVault(asset, preferKey) {
6387
6785
  if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
@@ -6415,10 +6813,61 @@ var init_dist2 = __esm({
6415
6813
  name() {
6416
6814
  return this.protocolName;
6417
6815
  }
6816
+ /**
6817
+ * Resolve a Morpho Blue marketId into the full MarketParams tuple by
6818
+ * calling Morpho.idToMarketParams(id). Used by every direct-market
6819
+ * method (supply / borrow / repay / withdraw / supplyCollateral /
6820
+ * withdrawCollateral) so the caller only has to pass the 32-byte
6821
+ * marketId — same shape as the Morpho UI / API.
6822
+ */
6823
+ async resolveMarketParams(marketId) {
6824
+ if (!this.rpcUrl) {
6825
+ throw DefiError.rpcError(
6826
+ `[${this.protocolName}] No RPC URL configured \u2014 cannot resolve marketId ${marketId}`
6827
+ );
6828
+ }
6829
+ const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6830
+ let result;
6831
+ try {
6832
+ result = await client.readContract({
6833
+ address: this.morpho,
6834
+ abi: MORPHO_ABI,
6835
+ functionName: "idToMarketParams",
6836
+ args: [marketId]
6837
+ });
6838
+ } catch (e) {
6839
+ throw DefiError.rpcError(
6840
+ `[${this.protocolName}] idToMarketParams(${marketId}) failed: ${e}`
6841
+ );
6842
+ }
6843
+ const [loanToken, collateralToken, oracle, irm, lltv] = result;
6844
+ if (loanToken === zeroAddress9 || collateralToken === zeroAddress9 || lltv === 0n) {
6845
+ throw DefiError.invalidParam(
6846
+ `[${this.protocolName}] marketId ${marketId} resolves to an empty MarketParams (loan=${loanToken}, collateral=${collateralToken}, lltv=${lltv}). Verify the id matches a registered market on this chain.`
6847
+ );
6848
+ }
6849
+ return { loanToken, collateralToken, oracle, irm, lltv };
6850
+ }
6418
6851
  async buildSupply(params) {
6852
+ if (params.market_id) {
6853
+ const market = await this.resolveMarketParams(params.market_id);
6854
+ const data = encodeFunctionData19({
6855
+ abi: MORPHO_ABI,
6856
+ functionName: "supply",
6857
+ args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6858
+ });
6859
+ return {
6860
+ description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6861
+ to: this.morpho,
6862
+ data,
6863
+ value: 0n,
6864
+ gas_estimate: 35e4,
6865
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6866
+ };
6867
+ }
6419
6868
  const vault = await this.resolveVault(params.asset);
6420
6869
  if (vault) {
6421
- const data2 = encodeFunctionData19({
6870
+ const data = encodeFunctionData19({
6422
6871
  abi: ERC4626_ABI,
6423
6872
  functionName: "deposit",
6424
6873
  args: [params.amount, params.on_behalf_of]
@@ -6426,50 +6875,118 @@ var init_dist2 = __esm({
6426
6875
  return {
6427
6876
  description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
6428
6877
  to: vault,
6429
- data: data2,
6878
+ data,
6430
6879
  value: 0n,
6431
6880
  gas_estimate: 4e5,
6432
6881
  approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
6433
6882
  };
6434
6883
  }
6435
- const market = defaultMarketParams(params.asset);
6884
+ throw DefiError.invalidParam(
6885
+ `[${this.protocolName}] supply requires either a registered MetaMorpho vault for ${params.asset} or an explicit --market <marketId>. The legacy zero-MarketParams stub was removed (it always reverted on-chain).`
6886
+ );
6887
+ }
6888
+ async buildBorrow(params) {
6889
+ if (!params.market_id) {
6890
+ throw DefiError.invalidParam(
6891
+ `[${this.protocolName}] Morpho Blue borrow requires --market <marketId>. Find one via the Morpho API (https://blue-api.morpho.org/graphql).`
6892
+ );
6893
+ }
6894
+ const market = await this.resolveMarketParams(params.market_id);
6895
+ const data = encodeFunctionData19({
6896
+ abi: MORPHO_ABI,
6897
+ functionName: "borrow",
6898
+ args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
6899
+ });
6900
+ return {
6901
+ description: `[${this.protocolName}] Borrow ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
6902
+ to: this.morpho,
6903
+ data,
6904
+ value: 0n,
6905
+ gas_estimate: 4e5
6906
+ };
6907
+ }
6908
+ async buildRepay(params) {
6909
+ if (!params.market_id) {
6910
+ throw DefiError.invalidParam(
6911
+ `[${this.protocolName}] Morpho Blue repay requires --market <marketId>.`
6912
+ );
6913
+ }
6914
+ const market = await this.resolveMarketParams(params.market_id);
6915
+ if (params.amount === MAX_UINT256) {
6916
+ if (!this.rpcUrl) {
6917
+ throw DefiError.rpcError(
6918
+ `[${this.protocolName}] max-repay requires an RPC URL to read borrowShares.`
6919
+ );
6920
+ }
6921
+ const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6922
+ const positionAbi = parseAbi20([
6923
+ "function position(bytes32 id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)"
6924
+ ]);
6925
+ const pos = await client.readContract({
6926
+ address: this.morpho,
6927
+ abi: positionAbi,
6928
+ functionName: "position",
6929
+ args: [params.market_id, params.on_behalf_of]
6930
+ });
6931
+ const [, borrowShares] = pos;
6932
+ if (borrowShares === 0n) {
6933
+ throw DefiError.invalidParam(
6934
+ `[${this.protocolName}] cannot repay max \u2014 user has no borrow position in market ${params.market_id}.`
6935
+ );
6936
+ }
6937
+ const data2 = encodeFunctionData19({
6938
+ abi: MORPHO_ABI,
6939
+ functionName: "repay",
6940
+ args: [market, 0n, borrowShares, params.on_behalf_of, "0x"]
6941
+ });
6942
+ return {
6943
+ description: `[${this.protocolName}] Repay max (${borrowShares} shares) to market ${params.market_id.slice(0, 10)}\u2026`,
6944
+ to: this.morpho,
6945
+ data: data2,
6946
+ value: 0n,
6947
+ gas_estimate: 35e4,
6948
+ approvals: [{ token: params.asset, spender: this.morpho, amount: MAX_UINT256 }]
6949
+ };
6950
+ }
6436
6951
  const data = encodeFunctionData19({
6437
6952
  abi: MORPHO_ABI,
6438
- functionName: "supply",
6953
+ functionName: "repay",
6439
6954
  args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6440
6955
  });
6441
6956
  return {
6442
- description: `[${this.protocolName}] Supply ${params.amount} to Morpho market`,
6957
+ description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6443
6958
  to: this.morpho,
6444
6959
  data,
6445
6960
  value: 0n,
6446
- gas_estimate: 3e5
6961
+ gas_estimate: 35e4,
6962
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6447
6963
  };
6448
6964
  }
6449
- async buildBorrow(params) {
6450
- const market = defaultMarketParams(params.asset);
6965
+ async buildSupplyCollateral(params) {
6966
+ const market = await this.resolveMarketParams(params.market_id);
6451
6967
  const data = encodeFunctionData19({
6452
6968
  abi: MORPHO_ABI,
6453
- functionName: "borrow",
6454
- args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
6969
+ functionName: "supplyCollateral",
6970
+ args: [market, params.amount, params.on_behalf_of, "0x"]
6455
6971
  });
6456
6972
  return {
6457
- description: `[${this.protocolName}] Borrow ${params.amount} from Morpho market`,
6973
+ description: `[${this.protocolName}] Supply collateral ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6458
6974
  to: this.morpho,
6459
6975
  data,
6460
6976
  value: 0n,
6461
- gas_estimate: 35e4
6977
+ gas_estimate: 35e4,
6978
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6462
6979
  };
6463
6980
  }
6464
- async buildRepay(params) {
6465
- const market = defaultMarketParams(params.asset);
6981
+ async buildWithdrawCollateral(params) {
6982
+ const market = await this.resolveMarketParams(params.market_id);
6466
6983
  const data = encodeFunctionData19({
6467
6984
  abi: MORPHO_ABI,
6468
- functionName: "repay",
6469
- args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6985
+ functionName: "withdrawCollateral",
6986
+ args: [market, params.amount, params.to, params.to]
6470
6987
  });
6471
6988
  return {
6472
- description: `[${this.protocolName}] Repay ${params.amount} to Morpho market`,
6989
+ description: `[${this.protocolName}] Withdraw collateral ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
6473
6990
  to: this.morpho,
6474
6991
  data,
6475
6992
  value: 0n,
@@ -6477,6 +6994,21 @@ var init_dist2 = __esm({
6477
6994
  };
6478
6995
  }
6479
6996
  async buildWithdraw(params) {
6997
+ if (params.market_id) {
6998
+ const market = await this.resolveMarketParams(params.market_id);
6999
+ const data = encodeFunctionData19({
7000
+ abi: MORPHO_ABI,
7001
+ functionName: "withdraw",
7002
+ args: [market, params.amount, 0n, params.to, params.to]
7003
+ });
7004
+ return {
7005
+ description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
7006
+ to: this.morpho,
7007
+ data,
7008
+ value: 0n,
7009
+ gas_estimate: 3e5
7010
+ };
7011
+ }
6480
7012
  const vault = await this.resolveVault(params.asset);
6481
7013
  if (vault) {
6482
7014
  if (params.amount === MAX_UINT256) {
@@ -6485,7 +7017,7 @@ var init_dist2 = __esm({
6485
7017
  [vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
6486
7018
  ]);
6487
7019
  const shares = decodeU256(balRaw ?? null);
6488
- const data3 = encodeFunctionData19({
7020
+ const data2 = encodeFunctionData19({
6489
7021
  abi: ERC4626_ABI,
6490
7022
  functionName: "redeem",
6491
7023
  args: [shares, params.to, params.to]
@@ -6493,12 +7025,12 @@ var init_dist2 = __esm({
6493
7025
  return {
6494
7026
  description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
6495
7027
  to: vault,
6496
- data: data3,
7028
+ data: data2,
6497
7029
  value: 0n,
6498
7030
  gas_estimate: 4e5
6499
7031
  };
6500
7032
  }
6501
- const data2 = encodeFunctionData19({
7033
+ const data = encodeFunctionData19({
6502
7034
  abi: ERC4626_ABI,
6503
7035
  functionName: "withdraw",
6504
7036
  args: [params.amount, params.to, params.to]
@@ -6506,24 +7038,14 @@ var init_dist2 = __esm({
6506
7038
  return {
6507
7039
  description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
6508
7040
  to: vault,
6509
- data: data2,
7041
+ data,
6510
7042
  value: 0n,
6511
7043
  gas_estimate: 4e5
6512
7044
  };
6513
7045
  }
6514
- const market = defaultMarketParams(params.asset);
6515
- const data = encodeFunctionData19({
6516
- abi: MORPHO_ABI,
6517
- functionName: "withdraw",
6518
- args: [market, params.amount, 0n, params.to, params.to]
6519
- });
6520
- return {
6521
- description: `[${this.protocolName}] Withdraw ${params.amount} from Morpho market`,
6522
- to: this.morpho,
6523
- data,
6524
- value: 0n,
6525
- gas_estimate: 25e4
6526
- };
7046
+ throw DefiError.invalidParam(
7047
+ `[${this.protocolName}] withdraw requires either a registered MetaMorpho vault for ${params.asset} or an explicit --market <marketId>. The legacy zero-MarketParams stub was removed (it always reverted on-chain).`
7048
+ );
6527
7049
  }
6528
7050
  async getRates(asset) {
6529
7051
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
@@ -6640,7 +7162,7 @@ var init_dist2 = __esm({
6640
7162
  if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
6641
7163
  return [0n, 0n];
6642
7164
  }
6643
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7165
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6644
7166
  const approxResult = await client.readContract({
6645
7167
  address: this.hintHelpers,
6646
7168
  abi: HINT_HELPERS_ABI,
@@ -6731,7 +7253,7 @@ var init_dist2 = __esm({
6731
7253
  async getCdpInfo(cdpId) {
6732
7254
  if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
6733
7255
  if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
6734
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7256
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6735
7257
  const data = await client.readContract({
6736
7258
  address: this.troveManager,
6737
7259
  abi: TROVE_MANAGER_ABI,
@@ -6749,13 +7271,13 @@ var init_dist2 = __esm({
6749
7271
  protocol: this.protocolName,
6750
7272
  cdp_id: cdpId,
6751
7273
  collateral: {
6752
- token: zeroAddress11,
7274
+ token: zeroAddress10,
6753
7275
  symbol: "WHYPE",
6754
7276
  amount: entireColl,
6755
7277
  decimals: 18
6756
7278
  },
6757
7279
  debt: {
6758
- token: zeroAddress11,
7280
+ token: zeroAddress10,
6759
7281
  symbol: "feUSD",
6760
7282
  amount: entireDebt,
6761
7283
  decimals: 18
@@ -6790,7 +7312,7 @@ var init_dist2 = __esm({
6790
7312
  if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
6791
7313
  throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
6792
7314
  }
6793
- const client = createPublicClient17({ transport: http17(this.rpcUrl) });
7315
+ const client = createPublicClient18({ transport: http18(this.rpcUrl) });
6794
7316
  let priceVal;
6795
7317
  try {
6796
7318
  const result = await client.readContract({
@@ -6882,7 +7404,7 @@ var init_dist2 = __esm({
6882
7404
  }
6883
7405
  async totalAssets() {
6884
7406
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6885
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7407
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6886
7408
  return client.readContract({
6887
7409
  address: this.vaultAddress,
6888
7410
  abi: ERC4626_ABI2,
@@ -6893,7 +7415,7 @@ var init_dist2 = __esm({
6893
7415
  }
6894
7416
  async convertToShares(assets) {
6895
7417
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6896
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7418
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6897
7419
  return client.readContract({
6898
7420
  address: this.vaultAddress,
6899
7421
  abi: ERC4626_ABI2,
@@ -6905,7 +7427,7 @@ var init_dist2 = __esm({
6905
7427
  }
6906
7428
  async convertToAssets(shares) {
6907
7429
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6908
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7430
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6909
7431
  return client.readContract({
6910
7432
  address: this.vaultAddress,
6911
7433
  abi: ERC4626_ABI2,
@@ -6917,7 +7439,7 @@ var init_dist2 = __esm({
6917
7439
  }
6918
7440
  async getVaultInfo() {
6919
7441
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6920
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7442
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6921
7443
  const [totalAssets, totalSupply, asset] = await Promise.all([
6922
7444
  client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
6923
7445
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
@@ -7009,7 +7531,7 @@ var init_dist2 = __esm({
7009
7531
  const data = encodeFunctionData23({
7010
7532
  abi: STHYPE_ABI,
7011
7533
  functionName: "submit",
7012
- args: [zeroAddress12]
7534
+ args: [zeroAddress11]
7013
7535
  });
7014
7536
  return {
7015
7537
  description: `[${this.protocolName}] Stake ${params.amount} HYPE for stHYPE`,
@@ -7035,7 +7557,7 @@ var init_dist2 = __esm({
7035
7557
  }
7036
7558
  async getInfo() {
7037
7559
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7038
- const client = createPublicClient19({ transport: http19(this.rpcUrl) });
7560
+ const client = createPublicClient20({ transport: http20(this.rpcUrl) });
7039
7561
  const tokenAddr = this.sthypeToken ?? this.staking;
7040
7562
  const totalSupply = await client.readContract({
7041
7563
  address: tokenAddr,
@@ -7046,7 +7568,7 @@ var init_dist2 = __esm({
7046
7568
  });
7047
7569
  return {
7048
7570
  protocol: this.protocolName,
7049
- staked_token: zeroAddress12,
7571
+ staked_token: zeroAddress11,
7050
7572
  liquid_token: tokenAddr,
7051
7573
  exchange_rate: 1,
7052
7574
  total_staked: totalSupply
@@ -7105,7 +7627,7 @@ var init_dist2 = __esm({
7105
7627
  }
7106
7628
  async getInfo() {
7107
7629
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7108
- const client = createPublicClient20({ transport: http20(this.rpcUrl) });
7630
+ const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7109
7631
  const totalStaked = await client.readContract({
7110
7632
  address: this.staking,
7111
7633
  abi: KINETIQ_ABI,
@@ -7120,7 +7642,7 @@ var init_dist2 = __esm({
7120
7642
  const rateF64 = hypePrice > 0n && khypePrice > 0n ? Number(khypePrice * 10n ** 18n / hypePrice) / 1e18 : 1;
7121
7643
  return {
7122
7644
  protocol: this.protocolName,
7123
- staked_token: zeroAddress13,
7645
+ staked_token: zeroAddress12,
7124
7646
  liquid_token: this.liquidToken,
7125
7647
  exchange_rate: rateF64,
7126
7648
  total_staked: totalStaked
@@ -7340,7 +7862,7 @@ var init_dist2 = __esm({
7340
7862
  }
7341
7863
  async getCollectionInfo(collection) {
7342
7864
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7343
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7865
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7344
7866
  const [collectionName, symbol, totalSupply] = await Promise.all([
7345
7867
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "name" }).catch((e) => {
7346
7868
  throw DefiError.rpcError(`[${this.protocolName}] name failed: ${e}`);
@@ -7359,7 +7881,7 @@ var init_dist2 = __esm({
7359
7881
  }
7360
7882
  async getTokenInfo(collection, tokenId) {
7361
7883
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7362
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7884
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7363
7885
  const [owner, tokenUri] = await Promise.all([
7364
7886
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "ownerOf", args: [tokenId] }).catch((e) => {
7365
7887
  throw DefiError.rpcError(`[${this.protocolName}] ownerOf failed: ${e}`);
@@ -7375,7 +7897,7 @@ var init_dist2 = __esm({
7375
7897
  }
7376
7898
  async getBalance(owner, collection) {
7377
7899
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7378
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7900
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7379
7901
  return client.readContract({ address: collection, abi: ERC721_ABI, functionName: "balanceOf", args: [owner] }).catch((e) => {
7380
7902
  throw DefiError.rpcError(`[${this.protocolName}] balanceOf failed: ${e}`);
7381
7903
  });
@@ -7445,10 +7967,22 @@ var Executor = class _Executor {
7445
7967
  dryRun;
7446
7968
  rpcUrl;
7447
7969
  explorerUrl;
7448
- constructor(broadcast, rpcUrl, explorerUrl) {
7970
+ /**
7971
+ * Optional viem Chain object. When set, all wallet/public clients built
7972
+ * inside this executor anchor to the explicit chainId at construction
7973
+ * time, defending against MITM RPCs that lie about eth_chainId and
7974
+ * keeping offline-signing safe under RPC drift (SSOT 7.4).
7975
+ */
7976
+ chain;
7977
+ constructor(broadcast, rpcUrl, explorerUrl, chain) {
7449
7978
  this.dryRun = !broadcast;
7450
7979
  this.rpcUrl = rpcUrl;
7451
7980
  this.explorerUrl = explorerUrl;
7981
+ this.chain = chain;
7982
+ }
7983
+ /** Returns the optional `{ chain }` spread for viem client constructors. */
7984
+ chainOpt() {
7985
+ return this.chain ? { chain: this.chain } : {};
7452
7986
  }
7453
7987
  /** Apply 20% buffer to a gas estimate */
7454
7988
  static applyGasBuffer(gas) {
@@ -7572,7 +8106,7 @@ var Executor = class _Executor {
7572
8106
  */
7573
8107
  async fetchEip1559Fees(rpcUrl) {
7574
8108
  try {
7575
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8109
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7576
8110
  let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
7577
8111
  try {
7578
8112
  priorityFee = await client.estimateMaxPriorityFeePerGas();
@@ -7594,7 +8128,7 @@ var Executor = class _Executor {
7594
8128
  /** Estimate gas dynamically with buffer, falling back to a hardcoded estimate */
7595
8129
  async estimateGasWithBuffer(rpcUrl, tx, from) {
7596
8130
  try {
7597
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8131
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7598
8132
  const estimated = await client.estimateGas({
7599
8133
  to: tx.to,
7600
8134
  data: tx.data,
@@ -7618,7 +8152,7 @@ var Executor = class _Executor {
7618
8152
  if (!rpcUrl) {
7619
8153
  throw DefiError.rpcError("No RPC URL \u2014 cannot simulate. Set HYPEREVM_RPC_URL.");
7620
8154
  }
7621
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8155
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7622
8156
  const privateKey = process.env["DEFI_PRIVATE_KEY"];
7623
8157
  const from = privateKey ? privateKeyToAccount(privateKey).address : "0x0000000000000000000000000000000000000001";
7624
8158
  if (tx.approvals && tx.approvals.length > 0) {
@@ -7732,8 +8266,8 @@ var Executor = class _Executor {
7732
8266
  if (!rpcUrl) {
7733
8267
  throw DefiError.rpcError("No RPC URL configured for broadcasting");
7734
8268
  }
7735
- const publicClient = createPublicClient23({ transport: http23(rpcUrl) });
7736
- const walletClient = createWalletClient({ account, transport: http23(rpcUrl) });
8269
+ const publicClient = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
8270
+ const walletClient = createWalletClient({ account, transport: http23(rpcUrl), ...this.chainOpt() });
7737
8271
  if (tx.pre_txs && tx.pre_txs.length > 0) {
7738
8272
  for (const preTx of tx.pre_txs) {
7739
8273
  process.stderr.write(` Pre-tx: ${preTx.description}...
@@ -8120,19 +8654,20 @@ server.tool(
8120
8654
  );
8121
8655
  server.tool(
8122
8656
  "defi_bridge",
8123
- "Get a cross-chain bridge quote via LI.FI, deBridge DLN, or Circle CCTP. Returns estimated output amount and fees",
8657
+ "Get a cross-chain bridge quote via LI.FI (default) or Relay. Returns estimated output and fees. For deBridge / CCTP quotes plus broadcast use the `defi bridge --provider <name>` CLI directly.",
8124
8658
  {
8125
8659
  from_chain: z.string().describe("Source chain name (supported source chains: hyperevm, mantle, base, bnb, monad)"),
8126
- to_chain: z.string().describe("Destination chain name. CCTP V2 destinations include ethereum, arbitrum, optimism, polygon, avalanche; LI.FI/deBridge accept any chain"),
8127
- token: z.string().optional().describe("Token symbol to bridge (default: USDC). Use native for native token"),
8660
+ to_chain: z.string().describe("Destination chain name. CCTP V2 destinations include ethereum, arbitrum, optimism, polygon, avalanche; LI.FI/Relay/deBridge accept any chain they route to"),
8661
+ token: z.string().optional().describe("Token symbol or address to bridge (default: USDC). Use 0x0\u20260 for native gas token. Relay rejects some ERC20s (e.g. BNB USDC/USDT) with INVALID_INPUT_CURRENCY \u2014 fall back to native or LI.FI"),
8128
8662
  amount: z.string().describe("Amount in human-readable units, e.g. '100' for 100 USDC"),
8129
- recipient: z.string().optional().describe("Recipient address on destination chain")
8663
+ recipient: z.string().optional().describe("Recipient address on destination chain"),
8664
+ provider: z.enum(["lifi", "relay"]).optional().describe("Bridge provider for the quote (default: lifi). For debridge/cctp use the CLI.")
8130
8665
  },
8131
- async ({ from_chain, to_chain, token, amount, recipient }) => {
8666
+ async ({ from_chain, to_chain, token, amount, recipient, provider }) => {
8132
8667
  try {
8133
8668
  const tokenSymbol = token ?? "USDC";
8134
8669
  const recipientAddr = recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
8135
- const LIFI_API = "https://li.quest/v1";
8670
+ const selectedProvider = provider ?? "lifi";
8136
8671
  const registry = getRegistry();
8137
8672
  let fromChainId;
8138
8673
  let toChainId;
@@ -8144,14 +8679,81 @@ server.tool(
8144
8679
  toChainId = registry.getChain(to_chain).chain_id;
8145
8680
  } catch {
8146
8681
  }
8682
+ const isAddr = tokenSymbol.startsWith("0x");
8683
+ const NATIVE = "0x0000000000000000000000000000000000000000";
8684
+ let srcTokenAddr;
8685
+ let dstTokenAddr;
8686
+ let tokenDecimals = 18;
8687
+ let dstTokenDecimals = 18;
8688
+ if (isAddr) {
8689
+ srcTokenAddr = tokenSymbol;
8690
+ dstTokenAddr = tokenSymbol;
8691
+ } else {
8692
+ try {
8693
+ const srcResolved = registry.resolveToken(from_chain, tokenSymbol);
8694
+ srcTokenAddr = srcResolved.address;
8695
+ tokenDecimals = srcResolved.decimals ?? 18;
8696
+ } catch {
8697
+ srcTokenAddr = NATIVE;
8698
+ tokenDecimals = 18;
8699
+ }
8700
+ try {
8701
+ const dstResolved = registry.resolveToken(to_chain, tokenSymbol);
8702
+ dstTokenAddr = dstResolved.address;
8703
+ dstTokenDecimals = dstResolved.decimals ?? tokenDecimals;
8704
+ } catch {
8705
+ dstTokenAddr = srcTokenAddr;
8706
+ dstTokenDecimals = tokenDecimals;
8707
+ }
8708
+ }
8709
+ if (selectedProvider === "relay") {
8710
+ const RELAY_API = "https://api.relay.link";
8711
+ if (!fromChainId || !toChainId) {
8712
+ throw new Error("Relay requires resolvable numeric chain IDs for both source and destination");
8713
+ }
8714
+ const amountRaw = String(BigInt(Math.round(parseFloat(amount) * Math.pow(10, tokenDecimals))));
8715
+ const res2 = await fetch(`${RELAY_API}/quote`, {
8716
+ method: "POST",
8717
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
8718
+ body: JSON.stringify({
8719
+ user: recipientAddr,
8720
+ recipient: recipientAddr,
8721
+ originChainId: fromChainId,
8722
+ destinationChainId: toChainId,
8723
+ originCurrency: srcTokenAddr,
8724
+ destinationCurrency: dstTokenAddr,
8725
+ tradeType: "EXACT_INPUT",
8726
+ amount: amountRaw
8727
+ })
8728
+ });
8729
+ if (!res2.ok) {
8730
+ const text = await res2.text();
8731
+ throw new Error(`Relay quote failed (${res2.status}): ${text.slice(0, 200)}`);
8732
+ }
8733
+ const data2 = await res2.json();
8734
+ const details = data2.details;
8735
+ const fees = data2.fees;
8736
+ const quote2 = {
8737
+ from_chain,
8738
+ to_chain,
8739
+ token: tokenSymbol,
8740
+ amount_in: amount,
8741
+ amount_out: details?.currencyOut?.amountFormatted ?? "unknown",
8742
+ fees,
8743
+ execution_duration_seconds: details?.timeEstimate ?? "unknown",
8744
+ tool: "relay",
8745
+ raw: data2
8746
+ };
8747
+ return { content: [{ type: "text", text: ok(quote2, { from_chain, to_chain, token: tokenSymbol, provider: "relay" }) }] };
8748
+ }
8749
+ const LIFI_API = "https://li.quest/v1";
8147
8750
  const params = new URLSearchParams({
8148
8751
  fromChain: fromChainId ? String(fromChainId) : from_chain,
8149
8752
  toChain: toChainId ? String(toChainId) : to_chain,
8150
- fromToken: tokenSymbol,
8151
- toToken: tokenSymbol,
8152
- fromAmount: String(Math.round(parseFloat(amount) * 1e6)),
8153
- // USDC decimals
8154
- toAddress: recipientAddr
8753
+ fromToken: srcTokenAddr,
8754
+ toToken: dstTokenAddr,
8755
+ fromAmount: String(BigInt(Math.round(parseFloat(amount) * Math.pow(10, tokenDecimals)))),
8756
+ fromAddress: recipientAddr
8155
8757
  });
8156
8758
  const res = await fetch(`${LIFI_API}/quote?${params}`, {
8157
8759
  headers: { Accept: "application/json" }
@@ -8162,19 +8764,21 @@ server.tool(
8162
8764
  }
8163
8765
  const data = await res.json();
8164
8766
  const estimate = data.estimate;
8767
+ const lifiDstDecimals = estimate?.toToken?.decimals;
8768
+ const amountOutDivisor = Math.pow(10, lifiDstDecimals ?? dstTokenDecimals);
8165
8769
  const quote = {
8166
8770
  from_chain,
8167
8771
  to_chain,
8168
8772
  token: tokenSymbol,
8169
8773
  amount_in: amount,
8170
- amount_out: estimate?.toAmount ? String(Number(estimate.toAmount) / 1e6) : "unknown",
8774
+ amount_out: estimate?.toAmount ? String(Number(estimate.toAmount) / amountOutDivisor) : "unknown",
8171
8775
  fee_costs: estimate?.feeCosts ?? [],
8172
8776
  gas_costs: estimate?.gasCosts ?? [],
8173
8777
  execution_duration_seconds: estimate?.executionDuration ?? "unknown",
8174
8778
  tool: data.tool ?? "unknown",
8175
8779
  raw: data
8176
8780
  };
8177
- return { content: [{ type: "text", text: ok(quote, { from_chain, to_chain, token: tokenSymbol }) }] };
8781
+ return { content: [{ type: "text", text: ok(quote, { from_chain, to_chain, token: tokenSymbol, provider: "lifi" }) }] };
8178
8782
  } catch (e) {
8179
8783
  return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { from_chain, to_chain }) }], isError: true };
8180
8784
  }