@hypurrquant/defi-cli 1.0.11 → 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 (54) 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 +1590 -309
  36. package/dist/index.js.map +1 -1
  37. package/dist/main.js +1595 -314
  38. package/dist/main.js.map +1 -1
  39. package/dist/mcp-server.js +810 -196
  40. package/dist/mcp-server.js.map +1 -1
  41. package/package.json +1 -1
  42. package/skills/defi-cli/SKILL.md +54 -5
  43. package/skills/defi-cli/references/commands.md +41 -8
  44. package/skills/defi-cli/references/protocols.md +2 -2
  45. package/skills/defi-cli/scripts/bridge-quote.sh +34 -0
  46. package/skills/defi-cli/scripts/lending-supply-flow.sh +28 -0
  47. package/skills/defi-cli/scripts/lp-claim-all.sh +25 -0
  48. package/skills/defi-cli/scripts/lp-emission-discover.sh +20 -0
  49. package/skills/defi-cli/scripts/portfolio-snapshot.sh +0 -0
  50. package/skills/defi-cli/scripts/preflight.sh +0 -0
  51. package/skills/defi-cli/scripts/swap-quote.sh +37 -0
  52. package/skills/defi-cli/scripts/wallet-status.sh +30 -0
  53. package/skills/defi-cli/scripts/yield-scan.sh +0 -0
  54. package/skills/defi-cli/scripts/exploit-scan.sh +0 -10
@@ -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,28 +2173,42 @@ 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
  });
2186
+ const approvals = [];
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 });
2083
2189
  return {
2084
- description: `[${this.protocolName}] Curve add liquidity`,
2085
- to: this.router,
2190
+ description: `[${this.protocolName}] Curve add liquidity to ${params.pool}`,
2191
+ to: params.pool,
2086
2192
  data,
2087
2193
  value: 0n,
2088
- gas_estimate: 4e5
2194
+ gas_estimate: 4e5,
2195
+ approvals
2089
2196
  };
2090
2197
  }
2091
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
+ }
2092
2204
  const data = encodeFunctionData5({
2093
2205
  abi: poolAbi,
2094
2206
  functionName: "remove_liquidity",
2095
2207
  args: [params.liquidity, [0n, 0n]]
2096
2208
  });
2097
2209
  return {
2098
- description: `[${this.protocolName}] Curve remove liquidity`,
2099
- to: this.router,
2210
+ description: `[${this.protocolName}] Curve remove liquidity from ${params.pool}`,
2211
+ to: params.pool,
2100
2212
  data,
2101
2213
  value: 0n,
2102
2214
  gas_estimate: 35e4
@@ -2262,7 +2374,12 @@ var init_dist2 = __esm({
2262
2374
  to: this.router,
2263
2375
  data,
2264
2376
  value: 0n,
2265
- 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 }] } : {}
2266
2383
  };
2267
2384
  }
2268
2385
  };
@@ -2309,6 +2426,11 @@ var init_dist2 = __esm({
2309
2426
  return this.protocolName;
2310
2427
  }
2311
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
+ }
2312
2434
  const data = encodeFunctionData7({
2313
2435
  abi: thenaRouterAbi,
2314
2436
  functionName: "exactInputSingle",
@@ -2319,7 +2441,7 @@ var init_dist2 = __esm({
2319
2441
  recipient: params.recipient,
2320
2442
  deadline: BigInt(params.deadline ?? 18446744073709551615n),
2321
2443
  amountIn: params.amount_in,
2322
- amountOutMinimum: 0n,
2444
+ amountOutMinimum: params.amount_out_min,
2323
2445
  sqrtPriceLimitX96: 0n
2324
2446
  }]
2325
2447
  });
@@ -2339,7 +2461,12 @@ var init_dist2 = __esm({
2339
2461
  const pm = this.positionManager;
2340
2462
  if (!pm) throw new DefiError("CONTRACT_ERROR", "Position manager not configured");
2341
2463
  if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
2342
- 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];
2343
2470
  const client = createPublicClient4({ transport: http4(this.rpcUrl) });
2344
2471
  const poolAddr = params.pool;
2345
2472
  let tickSpacing = this.defaultTickSpacing;
@@ -2354,7 +2481,7 @@ var init_dist2 = __esm({
2354
2481
  functionName: "getPool",
2355
2482
  args: [token0, token1, tickSpacing]
2356
2483
  });
2357
- if (pool === zeroAddress3) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2484
+ if (pool === zeroAddress2) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2358
2485
  }
2359
2486
  if (pool) {
2360
2487
  const [slot0, ts] = await Promise.all([
@@ -2397,8 +2524,8 @@ var init_dist2 = __esm({
2397
2524
  tickUpper,
2398
2525
  amount0Desired: rawAmount0,
2399
2526
  amount1Desired: rawAmount1,
2400
- amount0Min: 0n,
2401
- amount1Min: 0n,
2527
+ amount0Min,
2528
+ amount1Min,
2402
2529
  recipient: params.recipient,
2403
2530
  deadline: BigInt("18446744073709551615"),
2404
2531
  sqrtPriceX96: 0n
@@ -2420,12 +2547,19 @@ var init_dist2 = __esm({
2420
2547
  const pm = this.positionManager;
2421
2548
  if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
2422
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];
2423
2557
  const MAX_UINT128 = (1n << 128n) - 1n;
2424
2558
  const deadline = BigInt("18446744073709551615");
2425
2559
  const decreaseData = encodeFunctionData7({
2426
2560
  abi: thenaPmAbi,
2427
2561
  functionName: "decreaseLiquidity",
2428
- 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 }]
2429
2563
  });
2430
2564
  const collectData = encodeFunctionData7({
2431
2565
  abi: thenaPmAbi,
@@ -2492,8 +2626,8 @@ var init_dist2 = __esm({
2492
2626
  const ve = entry.contracts?.["ve_token"];
2493
2627
  if (!ve) throw new DefiError("CONTRACT_ERROR", "Missing 've_token' contract");
2494
2628
  this.veToken = ve;
2495
- this.voter = entry.contracts?.["voter"] ?? zeroAddress4;
2496
- this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress4;
2629
+ this.voter = entry.contracts?.["voter"] ?? zeroAddress3;
2630
+ this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress3;
2497
2631
  this.poolFactory = entry.contracts?.["pool_factory"];
2498
2632
  this.rpcUrl = rpcUrl;
2499
2633
  }
@@ -2535,7 +2669,7 @@ var init_dist2 = __esm({
2535
2669
  ]);
2536
2670
  }
2537
2671
  const poolAddressResults = await multicallRead(this.rpcUrl, poolAddressCalls);
2538
- 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);
2539
2673
  if (pools.length === 0) return [];
2540
2674
  const gaugeCalls = pools.map((pool) => [
2541
2675
  this.gaugeManager,
@@ -2545,7 +2679,7 @@ var init_dist2 = __esm({
2545
2679
  const gaugedPools = [];
2546
2680
  for (let i = 0; i < pools.length; i++) {
2547
2681
  const gauge = decodeAddress(gaugeResults[i] ?? null);
2548
- if (gauge && gauge !== zeroAddress4) {
2682
+ if (gauge && gauge !== zeroAddress3) {
2549
2683
  gaugedPools.push({ pool: pools[i], gauge });
2550
2684
  }
2551
2685
  }
@@ -2560,8 +2694,8 @@ var init_dist2 = __esm({
2560
2694
  for (let i = 0; i < gaugedPools.length; i++) {
2561
2695
  const t0 = decodeAddress(tokenResults[i * 2] ?? null);
2562
2696
  const t1 = decodeAddress(tokenResults[i * 2 + 1] ?? null);
2563
- if (t0 && t0 !== zeroAddress4) tokenAddrs.add(t0);
2564
- if (t1 && t1 !== zeroAddress4) tokenAddrs.add(t1);
2697
+ if (t0 && t0 !== zeroAddress3) tokenAddrs.add(t0);
2698
+ if (t1 && t1 !== zeroAddress3) tokenAddrs.add(t1);
2565
2699
  }
2566
2700
  const uniqueTokens = Array.from(tokenAddrs);
2567
2701
  const symbolCalls = uniqueTokens.map((t) => [
@@ -2600,7 +2734,7 @@ var init_dist2 = __esm({
2600
2734
  functionName: "gauges",
2601
2735
  args: [pool]
2602
2736
  });
2603
- 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}`);
2604
2738
  return gauge;
2605
2739
  }
2606
2740
  // ─── CL Gauge: NFT Deposit/Withdraw ──────────────────────────
@@ -2762,7 +2896,7 @@ var init_dist2 = __esm({
2762
2896
  params.amount_in,
2763
2897
  minToAmount,
2764
2898
  params.recipient,
2765
- zeroAddress5
2899
+ zeroAddress4
2766
2900
  ]
2767
2901
  });
2768
2902
  return {
@@ -2987,7 +3121,7 @@ var init_dist2 = __esm({
2987
3121
  poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
2988
3122
  }
2989
3123
  const poolResults = await multicallRead(this.rpcUrl, poolCalls);
2990
- 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);
2991
3125
  } else {
2992
3126
  const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
2993
3127
  const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
@@ -3012,17 +3146,17 @@ var init_dist2 = __esm({
3012
3146
  const pairCalls = [];
3013
3147
  for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
3014
3148
  const pairResults = await multicallRead(this.rpcUrl, pairCalls);
3015
- 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);
3016
3150
  }
3017
3151
  if (pairs.length === 0) return;
3018
3152
  const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
3019
3153
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
3020
- 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);
3021
3155
  if (allNull) {
3022
3156
  const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
3023
3157
  gaugeResults = await multicallRead(this.rpcUrl, fb1);
3024
3158
  }
3025
- 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);
3026
3160
  if (allNull2) {
3027
3161
  const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
3028
3162
  gaugeResults = await multicallRead(this.rpcUrl, fb2);
@@ -3030,7 +3164,7 @@ var init_dist2 = __esm({
3030
3164
  const gaugedPairs = [];
3031
3165
  for (let i = 0; i < pairs.length; i++) {
3032
3166
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
3033
- if (gauge && gauge !== zeroAddress6) {
3167
+ if (gauge && gauge !== zeroAddress5) {
3034
3168
  gaugedPairs.push({ pair: pairs[i], gauge });
3035
3169
  }
3036
3170
  }
@@ -3046,8 +3180,8 @@ var init_dist2 = __esm({
3046
3180
  for (let i = 0; i < gaugedPairs.length; i++) {
3047
3181
  const t0 = decodeAddress2(metaResults[i * 3] ?? null);
3048
3182
  const t1 = decodeAddress2(metaResults[i * 3 + 1] ?? null);
3049
- if (t0 && t0 !== zeroAddress6) tokenAddrs.add(t0);
3050
- if (t1 && t1 !== zeroAddress6) tokenAddrs.add(t1);
3183
+ if (t0 && t0 !== zeroAddress5) tokenAddrs.add(t0);
3184
+ if (t1 && t1 !== zeroAddress5) tokenAddrs.add(t1);
3051
3185
  }
3052
3186
  const uniqueTokens = Array.from(tokenAddrs);
3053
3187
  const symbolCalls = uniqueTokens.map((t) => [
@@ -3137,7 +3271,7 @@ var init_dist2 = __esm({
3137
3271
  const candidatePools = [];
3138
3272
  for (let i = 0; i < getPoolCalls.length; i++) {
3139
3273
  const pool = decodeAddress2(getPoolResults[i] ?? null);
3140
- if (pool && pool !== zeroAddress6) {
3274
+ if (pool && pool !== zeroAddress5) {
3141
3275
  const { pairIdx, tickSpacing } = callMeta[i];
3142
3276
  const [tokenA, tokenB] = pairs[pairIdx];
3143
3277
  candidatePools.push({ pool, tokenA, tokenB, tickSpacing });
@@ -3149,7 +3283,7 @@ var init_dist2 = __esm({
3149
3283
  encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pool] })
3150
3284
  ]);
3151
3285
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
3152
- 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);
3153
3287
  if (allNull) {
3154
3288
  const fallbackCalls = candidatePools.map(({ pool }) => [
3155
3289
  this.voter,
@@ -3160,7 +3294,7 @@ var init_dist2 = __esm({
3160
3294
  const gaugedCL = [];
3161
3295
  for (let i = 0; i < candidatePools.length; i++) {
3162
3296
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
3163
- if (gauge && gauge !== zeroAddress6) {
3297
+ if (gauge && gauge !== zeroAddress5) {
3164
3298
  gaugedCL.push({ ...candidatePools[i], gauge });
3165
3299
  }
3166
3300
  }
@@ -3190,8 +3324,8 @@ var init_dist2 = __esm({
3190
3324
  const { pool, gauge, tokenA, tokenB, tickSpacing } = gaugedCL[i];
3191
3325
  const rawT0 = decodeAddress2(poolTokenResults[i * 2] ?? null);
3192
3326
  const rawT1 = decodeAddress2(poolTokenResults[i * 2 + 1] ?? null);
3193
- const t0 = rawT0 && rawT0 !== zeroAddress6 ? rawT0 : tokenA;
3194
- const t1 = rawT1 && rawT1 !== zeroAddress6 ? rawT1 : tokenB;
3327
+ const t0 = rawT0 && rawT0 !== zeroAddress5 ? rawT0 : tokenA;
3328
+ const t1 = rawT1 && rawT1 !== zeroAddress5 ? rawT1 : tokenB;
3195
3329
  out.push({
3196
3330
  pool,
3197
3331
  gauge,
@@ -3293,7 +3427,7 @@ var init_dist2 = __esm({
3293
3427
  functionName: fn,
3294
3428
  args: [pool]
3295
3429
  });
3296
- if (gauge !== zeroAddress6) return gauge;
3430
+ if (gauge !== zeroAddress5) return gauge;
3297
3431
  } catch {
3298
3432
  }
3299
3433
  }
@@ -3347,7 +3481,7 @@ var init_dist2 = __esm({
3347
3481
  abi: gaugeAbi,
3348
3482
  functionName: "rewardToken"
3349
3483
  });
3350
- if (rt !== zeroAddress6) return { tokens: [rt], multiToken: false };
3484
+ if (rt !== zeroAddress5) return { tokens: [rt], multiToken: false };
3351
3485
  } catch {
3352
3486
  }
3353
3487
  return { tokens: [], multiToken: false };
@@ -3357,7 +3491,7 @@ var init_dist2 = __esm({
3357
3491
  const data = encodeFunctionData10({
3358
3492
  abi: gaugeAbi,
3359
3493
  functionName: "getReward",
3360
- args: [account ?? zeroAddress6]
3494
+ args: [account ?? zeroAddress5]
3361
3495
  });
3362
3496
  return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
3363
3497
  }
@@ -3581,7 +3715,7 @@ var init_dist2 = __esm({
3581
3715
  functionName: "earned",
3582
3716
  args: [user]
3583
3717
  });
3584
- results.push({ token: zeroAddress6, symbol: "unknown", amount: earned });
3718
+ results.push({ token: zeroAddress5, symbol: "unknown", amount: earned });
3585
3719
  } catch {
3586
3720
  }
3587
3721
  }
@@ -3943,20 +4077,47 @@ var init_dist2 = __esm({
3943
4077
  /**
3944
4078
  * Build an addLiquidity transaction for a Liquidity Book pair.
3945
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.
3946
4086
  */
3947
4087
  async buildAddLiquidity(params) {
3948
4088
  const numBins = params.numBins ?? 5;
3949
4089
  const deadline = params.deadline ?? BigInt("18446744073709551615");
3950
- let activeIdDesired = params.activeIdDesired;
3951
- if (activeIdDesired === void 0) {
3952
- const rpcUrl = this.requireRpc();
3953
- const client = createPublicClient8({ transport: http8(rpcUrl) });
3954
- const activeId = await client.readContract({
3955
- address: params.pool,
3956
- abi: lbPairAbi,
3957
- functionName: "getActiveId"
3958
- });
3959
- 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
+ );
3960
4121
  }
3961
4122
  const deltaIds = [];
3962
4123
  for (let d = -numBins; d <= numBins; d++) {
@@ -3968,11 +4129,11 @@ var init_dist2 = __esm({
3968
4129
  functionName: "addLiquidity",
3969
4130
  args: [
3970
4131
  {
3971
- tokenX: params.tokenX,
3972
- tokenY: params.tokenY,
4132
+ tokenX,
4133
+ tokenY,
3973
4134
  binStep: BigInt(params.binStep),
3974
- amountX: params.amountX,
3975
- amountY: params.amountY,
4135
+ amountX,
4136
+ amountY,
3976
4137
  amountXMin: 0n,
3977
4138
  amountYMin: 0n,
3978
4139
  activeIdDesired: BigInt(activeIdDesired),
@@ -3987,31 +4148,63 @@ var init_dist2 = __esm({
3987
4148
  ]
3988
4149
  });
3989
4150
  return {
3990
- 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`,
3991
4152
  to: this.lbRouter,
3992
4153
  data,
3993
4154
  value: 0n,
3994
4155
  gas_estimate: 8e5,
3995
4156
  approvals: [
3996
- { token: params.tokenX, spender: this.lbRouter, amount: params.amountX },
3997
- { 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 }
3998
4159
  ]
3999
4160
  };
4000
4161
  }
4001
4162
  /**
4002
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.
4003
4168
  */
4004
4169
  async buildRemoveLiquidity(params) {
4005
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
+ }
4006
4199
  const data = encodeFunctionData12({
4007
4200
  abi: lbRouterAbi,
4008
4201
  functionName: "removeLiquidity",
4009
4202
  args: [
4010
- params.tokenX,
4011
- params.tokenY,
4203
+ tokenX,
4204
+ tokenY,
4012
4205
  params.binStep,
4013
- params.amountXMin ?? 0n,
4014
- params.amountYMin ?? 0n,
4206
+ amountXMin,
4207
+ amountYMin,
4015
4208
  params.binIds.map(BigInt),
4016
4209
  params.amounts,
4017
4210
  params.recipient,
@@ -4921,7 +5114,7 @@ var init_dist2 = __esm({
4921
5114
  const poolSet = /* @__PURE__ */ new Set();
4922
5115
  for (const data of poolResults) {
4923
5116
  const addr = decodeAddress3(data);
4924
- if (addr && addr !== zeroAddress7) {
5117
+ if (addr && addr !== zeroAddress6) {
4925
5118
  poolSet.add(addr.toLowerCase());
4926
5119
  }
4927
5120
  }
@@ -5086,6 +5279,34 @@ var init_dist2 = __esm({
5086
5279
  }
5087
5280
  return apr;
5088
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
+ }
5089
5310
  /** Pending NEST emissions as IGauge-compatible RewardInfo[] */
5090
5311
  async getPendingRewards(user) {
5091
5312
  const status = await this.getClaimStatus(user);
@@ -5241,6 +5462,16 @@ var init_dist2 = __esm({
5241
5462
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
5242
5463
  "function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
5243
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",
5244
5475
  "function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
5245
5476
  "function getReservesList() external view returns (address[])",
5246
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)"
@@ -5366,6 +5597,34 @@ var init_dist2 = __esm({
5366
5597
  gas_estimate: 25e4
5367
5598
  };
5368
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
+ }
5369
5628
  async getRates(asset) {
5370
5629
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5371
5630
  const reserveCallData = encodeFunctionData14({
@@ -5410,7 +5669,7 @@ var init_dist2 = __esm({
5410
5669
  [aTokenAddress, encodeFunctionData14({ abi: INCENTIVES_ABI, functionName: "getIncentivesController" })]
5411
5670
  ]);
5412
5671
  const controllerAddr = decodeAddress4(controllerRaw ?? null);
5413
- if (controllerAddr && controllerAddr !== zeroAddress8) {
5672
+ if (controllerAddr && controllerAddr !== zeroAddress7) {
5414
5673
  const [supplyRewardsRaw, borrowRewardsRaw] = await multicallRead(this.rpcUrl, [
5415
5674
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [aTokenAddress] })],
5416
5675
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [variableDebtTokenAddress] })]
@@ -5606,8 +5865,8 @@ var init_dist2 = __esm({
5606
5865
  if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
5607
5866
  }
5608
5867
  } catch {
5609
- if (collateralUsd > 0) supplies.push({ asset: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5610
- 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 });
5611
5870
  }
5612
5871
  return {
5613
5872
  protocol: this.protocolName,
@@ -5774,8 +6033,8 @@ var init_dist2 = __esm({
5774
6033
  const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
5775
6034
  const debtUsd = u256ToF642(totalDebtBase) / 1e18;
5776
6035
  const ltvBps = u256ToF642(ltv);
5777
- const supplies = collateralUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5778
- 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 }] : [];
5779
6038
  return {
5780
6039
  protocol: this.protocolName,
5781
6040
  user,
@@ -5878,14 +6137,31 @@ var init_dist2 = __esm({
5878
6137
  "function borrow(uint256 borrowAmount) external returns (uint256)",
5879
6138
  "function repayBorrow(uint256 repayAmount) external returns (uint256)"
5880
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";
5881
6149
  BSC_BLOCKS_PER_YEAR = 10512e3;
5882
6150
  CompoundV2Adapter = class {
5883
6151
  protocolName;
5884
6152
  defaultVtoken;
5885
6153
  vTokenCandidates;
6154
+ comptroller;
5886
6155
  rpcUrl;
5887
- // 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().
5888
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;
5889
6165
  constructor(entry, rpcUrl) {
5890
6166
  this.protocolName = entry.name;
5891
6167
  this.rpcUrl = rpcUrl;
@@ -5893,6 +6169,7 @@ var init_dist2 = __esm({
5893
6169
  const vtoken = contracts["vusdt"] ?? contracts["vusdc"] ?? contracts["vbnb"] ?? contracts["comptroller"];
5894
6170
  if (!vtoken) throw DefiError.contractError("Missing vToken or comptroller address");
5895
6171
  this.defaultVtoken = vtoken;
6172
+ this.comptroller = contracts["comptroller"];
5896
6173
  this.vTokenCandidates = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k)).map(([, v]) => v);
5897
6174
  if (this.vTokenCandidates.length === 0) this.vTokenCandidates = [vtoken];
5898
6175
  }
@@ -5901,19 +6178,38 @@ var init_dist2 = __esm({
5901
6178
  if (!this.vTokenByAsset) {
5902
6179
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
5903
6180
  const map = /* @__PURE__ */ new Map();
6181
+ let nativeVtoken = null;
5904
6182
  const lookups = await Promise.allSettled(
5905
6183
  this.vTokenCandidates.map(async (v) => {
5906
- const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
5907
- 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
+ }
5908
6190
  })
5909
6191
  );
5910
6192
  for (const r of lookups) {
5911
- 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);
5912
6203
  }
5913
6204
  this.vTokenByAsset = map;
6205
+ this.nativeVtoken = nativeVtoken;
5914
6206
  }
5915
6207
  return this.vTokenByAsset.get(asset.toLowerCase()) ?? null;
5916
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
+ }
5917
6213
  name() {
5918
6214
  return this.protocolName;
5919
6215
  }
@@ -5927,6 +6223,16 @@ var init_dist2 = __esm({
5927
6223
  }
5928
6224
  async buildSupply(params) {
5929
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
+ }
5930
6236
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "mint", args: [params.amount] });
5931
6237
  return {
5932
6238
  description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to Venus`,
@@ -5950,6 +6256,16 @@ var init_dist2 = __esm({
5950
6256
  }
5951
6257
  async buildRepay(params) {
5952
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
+ }
5953
6269
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "repayBorrow", args: [params.amount] });
5954
6270
  return {
5955
6271
  description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to Venus`,
@@ -5962,6 +6278,37 @@ var init_dist2 = __esm({
5962
6278
  }
5963
6279
  async buildWithdraw(params) {
5964
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
+ }
5965
6312
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeemUnderlying", args: [params.amount] });
5966
6313
  return {
5967
6314
  description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from Venus`,
@@ -5971,6 +6318,37 @@ var init_dist2 = __esm({
5971
6318
  gas_estimate: 25e4
5972
6319
  };
5973
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
+ }
5974
6352
  async getRates(asset) {
5975
6353
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5976
6354
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
@@ -6338,9 +6716,11 @@ var init_dist2 = __esm({
6338
6716
  "function market(bytes32 id) external view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
6339
6717
  "function idToMarketParams(bytes32 id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)",
6340
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",
6341
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)",
6342
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)",
6343
- "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"
6344
6724
  ]);
6345
6725
  META_MORPHO_ABI = parseAbi20([
6346
6726
  "function supplyQueueLength() external view returns (uint256)",
@@ -6367,6 +6747,8 @@ var init_dist2 = __esm({
6367
6747
  rpcUrl;
6368
6748
  metaMorphoVaults;
6369
6749
  metaMorphoVaultEntries;
6750
+ namedMarkets;
6751
+ namedMarketByName;
6370
6752
  vaultAssetMap = null;
6371
6753
  constructor(entry, rpcUrl) {
6372
6754
  this.protocolName = entry.name;
@@ -6378,6 +6760,26 @@ var init_dist2 = __esm({
6378
6760
  this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
6379
6761
  this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
6380
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;
6381
6783
  }
6382
6784
  async resolveVault(asset, preferKey) {
6383
6785
  if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
@@ -6411,10 +6813,61 @@ var init_dist2 = __esm({
6411
6813
  name() {
6412
6814
  return this.protocolName;
6413
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
+ }
6414
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
+ }
6415
6868
  const vault = await this.resolveVault(params.asset);
6416
6869
  if (vault) {
6417
- const data2 = encodeFunctionData19({
6870
+ const data = encodeFunctionData19({
6418
6871
  abi: ERC4626_ABI,
6419
6872
  functionName: "deposit",
6420
6873
  args: [params.amount, params.on_behalf_of]
@@ -6422,50 +6875,118 @@ var init_dist2 = __esm({
6422
6875
  return {
6423
6876
  description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
6424
6877
  to: vault,
6425
- data: data2,
6878
+ data,
6426
6879
  value: 0n,
6427
6880
  gas_estimate: 4e5,
6428
6881
  approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
6429
6882
  };
6430
6883
  }
6431
- 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
+ }
6432
6951
  const data = encodeFunctionData19({
6433
6952
  abi: MORPHO_ABI,
6434
- functionName: "supply",
6953
+ functionName: "repay",
6435
6954
  args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6436
6955
  });
6437
6956
  return {
6438
- 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`,
6439
6958
  to: this.morpho,
6440
6959
  data,
6441
6960
  value: 0n,
6442
- gas_estimate: 3e5
6961
+ gas_estimate: 35e4,
6962
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6443
6963
  };
6444
6964
  }
6445
- async buildBorrow(params) {
6446
- const market = defaultMarketParams(params.asset);
6965
+ async buildSupplyCollateral(params) {
6966
+ const market = await this.resolveMarketParams(params.market_id);
6447
6967
  const data = encodeFunctionData19({
6448
6968
  abi: MORPHO_ABI,
6449
- functionName: "borrow",
6450
- 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"]
6451
6971
  });
6452
6972
  return {
6453
- 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`,
6454
6974
  to: this.morpho,
6455
6975
  data,
6456
6976
  value: 0n,
6457
- gas_estimate: 35e4
6977
+ gas_estimate: 35e4,
6978
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6458
6979
  };
6459
6980
  }
6460
- async buildRepay(params) {
6461
- const market = defaultMarketParams(params.asset);
6981
+ async buildWithdrawCollateral(params) {
6982
+ const market = await this.resolveMarketParams(params.market_id);
6462
6983
  const data = encodeFunctionData19({
6463
6984
  abi: MORPHO_ABI,
6464
- functionName: "repay",
6465
- args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6985
+ functionName: "withdrawCollateral",
6986
+ args: [market, params.amount, params.to, params.to]
6466
6987
  });
6467
6988
  return {
6468
- 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`,
6469
6990
  to: this.morpho,
6470
6991
  data,
6471
6992
  value: 0n,
@@ -6473,6 +6994,21 @@ var init_dist2 = __esm({
6473
6994
  };
6474
6995
  }
6475
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
+ }
6476
7012
  const vault = await this.resolveVault(params.asset);
6477
7013
  if (vault) {
6478
7014
  if (params.amount === MAX_UINT256) {
@@ -6481,7 +7017,7 @@ var init_dist2 = __esm({
6481
7017
  [vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
6482
7018
  ]);
6483
7019
  const shares = decodeU256(balRaw ?? null);
6484
- const data3 = encodeFunctionData19({
7020
+ const data2 = encodeFunctionData19({
6485
7021
  abi: ERC4626_ABI,
6486
7022
  functionName: "redeem",
6487
7023
  args: [shares, params.to, params.to]
@@ -6489,12 +7025,12 @@ var init_dist2 = __esm({
6489
7025
  return {
6490
7026
  description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
6491
7027
  to: vault,
6492
- data: data3,
7028
+ data: data2,
6493
7029
  value: 0n,
6494
7030
  gas_estimate: 4e5
6495
7031
  };
6496
7032
  }
6497
- const data2 = encodeFunctionData19({
7033
+ const data = encodeFunctionData19({
6498
7034
  abi: ERC4626_ABI,
6499
7035
  functionName: "withdraw",
6500
7036
  args: [params.amount, params.to, params.to]
@@ -6502,24 +7038,14 @@ var init_dist2 = __esm({
6502
7038
  return {
6503
7039
  description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
6504
7040
  to: vault,
6505
- data: data2,
7041
+ data,
6506
7042
  value: 0n,
6507
7043
  gas_estimate: 4e5
6508
7044
  };
6509
7045
  }
6510
- const market = defaultMarketParams(params.asset);
6511
- const data = encodeFunctionData19({
6512
- abi: MORPHO_ABI,
6513
- functionName: "withdraw",
6514
- args: [market, params.amount, 0n, params.to, params.to]
6515
- });
6516
- return {
6517
- description: `[${this.protocolName}] Withdraw ${params.amount} from Morpho market`,
6518
- to: this.morpho,
6519
- data,
6520
- value: 0n,
6521
- gas_estimate: 25e4
6522
- };
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
+ );
6523
7049
  }
6524
7050
  async getRates(asset) {
6525
7051
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
@@ -6636,7 +7162,7 @@ var init_dist2 = __esm({
6636
7162
  if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
6637
7163
  return [0n, 0n];
6638
7164
  }
6639
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7165
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6640
7166
  const approxResult = await client.readContract({
6641
7167
  address: this.hintHelpers,
6642
7168
  abi: HINT_HELPERS_ABI,
@@ -6727,7 +7253,7 @@ var init_dist2 = __esm({
6727
7253
  async getCdpInfo(cdpId) {
6728
7254
  if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
6729
7255
  if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
6730
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7256
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6731
7257
  const data = await client.readContract({
6732
7258
  address: this.troveManager,
6733
7259
  abi: TROVE_MANAGER_ABI,
@@ -6745,13 +7271,13 @@ var init_dist2 = __esm({
6745
7271
  protocol: this.protocolName,
6746
7272
  cdp_id: cdpId,
6747
7273
  collateral: {
6748
- token: zeroAddress11,
7274
+ token: zeroAddress10,
6749
7275
  symbol: "WHYPE",
6750
7276
  amount: entireColl,
6751
7277
  decimals: 18
6752
7278
  },
6753
7279
  debt: {
6754
- token: zeroAddress11,
7280
+ token: zeroAddress10,
6755
7281
  symbol: "feUSD",
6756
7282
  amount: entireDebt,
6757
7283
  decimals: 18
@@ -6786,7 +7312,7 @@ var init_dist2 = __esm({
6786
7312
  if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
6787
7313
  throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
6788
7314
  }
6789
- const client = createPublicClient17({ transport: http17(this.rpcUrl) });
7315
+ const client = createPublicClient18({ transport: http18(this.rpcUrl) });
6790
7316
  let priceVal;
6791
7317
  try {
6792
7318
  const result = await client.readContract({
@@ -6878,7 +7404,7 @@ var init_dist2 = __esm({
6878
7404
  }
6879
7405
  async totalAssets() {
6880
7406
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6881
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7407
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6882
7408
  return client.readContract({
6883
7409
  address: this.vaultAddress,
6884
7410
  abi: ERC4626_ABI2,
@@ -6889,7 +7415,7 @@ var init_dist2 = __esm({
6889
7415
  }
6890
7416
  async convertToShares(assets) {
6891
7417
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6892
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7418
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6893
7419
  return client.readContract({
6894
7420
  address: this.vaultAddress,
6895
7421
  abi: ERC4626_ABI2,
@@ -6901,7 +7427,7 @@ var init_dist2 = __esm({
6901
7427
  }
6902
7428
  async convertToAssets(shares) {
6903
7429
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6904
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7430
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6905
7431
  return client.readContract({
6906
7432
  address: this.vaultAddress,
6907
7433
  abi: ERC4626_ABI2,
@@ -6913,7 +7439,7 @@ var init_dist2 = __esm({
6913
7439
  }
6914
7440
  async getVaultInfo() {
6915
7441
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6916
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7442
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6917
7443
  const [totalAssets, totalSupply, asset] = await Promise.all([
6918
7444
  client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
6919
7445
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
@@ -7005,7 +7531,7 @@ var init_dist2 = __esm({
7005
7531
  const data = encodeFunctionData23({
7006
7532
  abi: STHYPE_ABI,
7007
7533
  functionName: "submit",
7008
- args: [zeroAddress12]
7534
+ args: [zeroAddress11]
7009
7535
  });
7010
7536
  return {
7011
7537
  description: `[${this.protocolName}] Stake ${params.amount} HYPE for stHYPE`,
@@ -7031,7 +7557,7 @@ var init_dist2 = __esm({
7031
7557
  }
7032
7558
  async getInfo() {
7033
7559
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7034
- const client = createPublicClient19({ transport: http19(this.rpcUrl) });
7560
+ const client = createPublicClient20({ transport: http20(this.rpcUrl) });
7035
7561
  const tokenAddr = this.sthypeToken ?? this.staking;
7036
7562
  const totalSupply = await client.readContract({
7037
7563
  address: tokenAddr,
@@ -7042,7 +7568,7 @@ var init_dist2 = __esm({
7042
7568
  });
7043
7569
  return {
7044
7570
  protocol: this.protocolName,
7045
- staked_token: zeroAddress12,
7571
+ staked_token: zeroAddress11,
7046
7572
  liquid_token: tokenAddr,
7047
7573
  exchange_rate: 1,
7048
7574
  total_staked: totalSupply
@@ -7101,7 +7627,7 @@ var init_dist2 = __esm({
7101
7627
  }
7102
7628
  async getInfo() {
7103
7629
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7104
- const client = createPublicClient20({ transport: http20(this.rpcUrl) });
7630
+ const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7105
7631
  const totalStaked = await client.readContract({
7106
7632
  address: this.staking,
7107
7633
  abi: KINETIQ_ABI,
@@ -7116,7 +7642,7 @@ var init_dist2 = __esm({
7116
7642
  const rateF64 = hypePrice > 0n && khypePrice > 0n ? Number(khypePrice * 10n ** 18n / hypePrice) / 1e18 : 1;
7117
7643
  return {
7118
7644
  protocol: this.protocolName,
7119
- staked_token: zeroAddress13,
7645
+ staked_token: zeroAddress12,
7120
7646
  liquid_token: this.liquidToken,
7121
7647
  exchange_rate: rateF64,
7122
7648
  total_staked: totalStaked
@@ -7336,7 +7862,7 @@ var init_dist2 = __esm({
7336
7862
  }
7337
7863
  async getCollectionInfo(collection) {
7338
7864
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7339
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7865
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7340
7866
  const [collectionName, symbol, totalSupply] = await Promise.all([
7341
7867
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "name" }).catch((e) => {
7342
7868
  throw DefiError.rpcError(`[${this.protocolName}] name failed: ${e}`);
@@ -7355,7 +7881,7 @@ var init_dist2 = __esm({
7355
7881
  }
7356
7882
  async getTokenInfo(collection, tokenId) {
7357
7883
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7358
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7884
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7359
7885
  const [owner, tokenUri] = await Promise.all([
7360
7886
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "ownerOf", args: [tokenId] }).catch((e) => {
7361
7887
  throw DefiError.rpcError(`[${this.protocolName}] ownerOf failed: ${e}`);
@@ -7371,7 +7897,7 @@ var init_dist2 = __esm({
7371
7897
  }
7372
7898
  async getBalance(owner, collection) {
7373
7899
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7374
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7900
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7375
7901
  return client.readContract({ address: collection, abi: ERC721_ABI, functionName: "balanceOf", args: [owner] }).catch((e) => {
7376
7902
  throw DefiError.rpcError(`[${this.protocolName}] balanceOf failed: ${e}`);
7377
7903
  });
@@ -7441,15 +7967,34 @@ var Executor = class _Executor {
7441
7967
  dryRun;
7442
7968
  rpcUrl;
7443
7969
  explorerUrl;
7444
- 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) {
7445
7978
  this.dryRun = !broadcast;
7446
7979
  this.rpcUrl = rpcUrl;
7447
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 } : {};
7448
7986
  }
7449
7987
  /** Apply 20% buffer to a gas estimate */
7450
7988
  static applyGasBuffer(gas) {
7451
7989
  return gas * GAS_BUFFER_BPS / 10000n;
7452
7990
  }
7991
+ /**
7992
+ * EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
7993
+ * Extracted as a static so the math is unit-testable without mocking viem.
7994
+ */
7995
+ static computeMaxFee(baseFeePerGas, priorityFee) {
7996
+ return baseFeePerGas * 125n / 100n + priorityFee;
7997
+ }
7453
7998
  /**
7454
7999
  * Check allowance for a single token/spender pair and send an approve tx if needed.
7455
8000
  * Only called in broadcast mode (not dry-run).
@@ -7561,7 +8106,7 @@ var Executor = class _Executor {
7561
8106
  */
7562
8107
  async fetchEip1559Fees(rpcUrl) {
7563
8108
  try {
7564
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8109
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7565
8110
  let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
7566
8111
  try {
7567
8112
  priorityFee = await client.estimateMaxPriorityFeePerGas();
@@ -7570,8 +8115,7 @@ var Executor = class _Executor {
7570
8115
  try {
7571
8116
  const block = await client.getBlock({ blockTag: "latest" });
7572
8117
  if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
7573
- const maxFee = block.baseFeePerGas * 125n / 100n + priorityFee;
7574
- return [maxFee, priorityFee];
8118
+ return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
7575
8119
  }
7576
8120
  } catch {
7577
8121
  }
@@ -7584,7 +8128,7 @@ var Executor = class _Executor {
7584
8128
  /** Estimate gas dynamically with buffer, falling back to a hardcoded estimate */
7585
8129
  async estimateGasWithBuffer(rpcUrl, tx, from) {
7586
8130
  try {
7587
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8131
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7588
8132
  const estimated = await client.estimateGas({
7589
8133
  to: tx.to,
7590
8134
  data: tx.data,
@@ -7608,7 +8152,7 @@ var Executor = class _Executor {
7608
8152
  if (!rpcUrl) {
7609
8153
  throw DefiError.rpcError("No RPC URL \u2014 cannot simulate. Set HYPEREVM_RPC_URL.");
7610
8154
  }
7611
- const client = createPublicClient23({ transport: http23(rpcUrl) });
8155
+ const client = createPublicClient23({ transport: http23(rpcUrl), ...this.chainOpt() });
7612
8156
  const privateKey = process.env["DEFI_PRIVATE_KEY"];
7613
8157
  const from = privateKey ? privateKeyToAccount(privateKey).address : "0x0000000000000000000000000000000000000001";
7614
8158
  if (tx.approvals && tx.approvals.length > 0) {
@@ -7722,8 +8266,8 @@ var Executor = class _Executor {
7722
8266
  if (!rpcUrl) {
7723
8267
  throw DefiError.rpcError("No RPC URL configured for broadcasting");
7724
8268
  }
7725
- const publicClient = createPublicClient23({ transport: http23(rpcUrl) });
7726
- 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() });
7727
8271
  if (tx.pre_txs && tx.pre_txs.length > 0) {
7728
8272
  for (const preTx of tx.pre_txs) {
7729
8273
  process.stderr.write(` Pre-tx: ${preTx.description}...
@@ -8110,19 +8654,20 @@ server.tool(
8110
8654
  );
8111
8655
  server.tool(
8112
8656
  "defi_bridge",
8113
- "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.",
8114
8658
  {
8115
8659
  from_chain: z.string().describe("Source chain name (supported source chains: hyperevm, mantle, base, bnb, monad)"),
8116
- to_chain: z.string().describe("Destination chain name. CCTP V2 destinations include ethereum, arbitrum, optimism, polygon, avalanche; LI.FI/deBridge accept any chain"),
8117
- 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"),
8118
8662
  amount: z.string().describe("Amount in human-readable units, e.g. '100' for 100 USDC"),
8119
- 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.")
8120
8665
  },
8121
- async ({ from_chain, to_chain, token, amount, recipient }) => {
8666
+ async ({ from_chain, to_chain, token, amount, recipient, provider }) => {
8122
8667
  try {
8123
8668
  const tokenSymbol = token ?? "USDC";
8124
8669
  const recipientAddr = recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
8125
- const LIFI_API = "https://li.quest/v1";
8670
+ const selectedProvider = provider ?? "lifi";
8126
8671
  const registry = getRegistry();
8127
8672
  let fromChainId;
8128
8673
  let toChainId;
@@ -8134,14 +8679,81 @@ server.tool(
8134
8679
  toChainId = registry.getChain(to_chain).chain_id;
8135
8680
  } catch {
8136
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";
8137
8750
  const params = new URLSearchParams({
8138
8751
  fromChain: fromChainId ? String(fromChainId) : from_chain,
8139
8752
  toChain: toChainId ? String(toChainId) : to_chain,
8140
- fromToken: tokenSymbol,
8141
- toToken: tokenSymbol,
8142
- fromAmount: String(Math.round(parseFloat(amount) * 1e6)),
8143
- // USDC decimals
8144
- toAddress: recipientAddr
8753
+ fromToken: srcTokenAddr,
8754
+ toToken: dstTokenAddr,
8755
+ fromAmount: String(BigInt(Math.round(parseFloat(amount) * Math.pow(10, tokenDecimals)))),
8756
+ fromAddress: recipientAddr
8145
8757
  });
8146
8758
  const res = await fetch(`${LIFI_API}/quote?${params}`, {
8147
8759
  headers: { Accept: "application/json" }
@@ -8152,19 +8764,21 @@ server.tool(
8152
8764
  }
8153
8765
  const data = await res.json();
8154
8766
  const estimate = data.estimate;
8767
+ const lifiDstDecimals = estimate?.toToken?.decimals;
8768
+ const amountOutDivisor = Math.pow(10, lifiDstDecimals ?? dstTokenDecimals);
8155
8769
  const quote = {
8156
8770
  from_chain,
8157
8771
  to_chain,
8158
8772
  token: tokenSymbol,
8159
8773
  amount_in: amount,
8160
- amount_out: estimate?.toAmount ? String(Number(estimate.toAmount) / 1e6) : "unknown",
8774
+ amount_out: estimate?.toAmount ? String(Number(estimate.toAmount) / amountOutDivisor) : "unknown",
8161
8775
  fee_costs: estimate?.feeCosts ?? [],
8162
8776
  gas_costs: estimate?.gasCosts ?? [],
8163
8777
  execution_duration_seconds: estimate?.executionDuration ?? "unknown",
8164
8778
  tool: data.tool ?? "unknown",
8165
8779
  raw: data
8166
8780
  };
8167
- 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" }) }] };
8168
8782
  } catch (e) {
8169
8783
  return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { from_chain, to_chain }) }], isError: true };
8170
8784
  }