@hypurrquant/defi-cli 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +9 -2
  2. package/config/chains.toml +6 -1
  3. package/config/protocols/dex/aerodrome_base.toml +2 -1
  4. package/config/protocols/dex/aerodrome_cl.toml +2 -1
  5. package/config/protocols/dex/apeswap_bnb.toml +1 -1
  6. package/config/protocols/dex/babydogeswap_bnb.toml +1 -1
  7. package/config/protocols/dex/bakeryswap_bnb.toml +2 -2
  8. package/config/protocols/dex/biswap_bnb.toml +1 -1
  9. package/config/protocols/dex/bscswap_bnb.toml +1 -1
  10. package/config/protocols/dex/curve_hyperevm.toml +1 -1
  11. package/config/protocols/dex/fstswap_bnb.toml +1 -1
  12. package/config/protocols/dex/hybra.toml +4 -2
  13. package/config/protocols/dex/hyperswap.toml +1 -1
  14. package/config/protocols/dex/kittenswap.toml +1 -1
  15. package/config/protocols/dex/merchantmoe_mantle.toml +2 -1
  16. package/config/protocols/dex/nest.toml +6 -5
  17. package/config/protocols/dex/pancakeswap_v2_bnb.toml +1 -1
  18. package/config/protocols/dex/pancakeswap_v3_bnb.toml +2 -1
  19. package/config/protocols/dex/project_x.toml +1 -1
  20. package/config/protocols/dex/ramses_cl.toml +1 -1
  21. package/config/protocols/dex/ramses_hl.toml +1 -1
  22. package/config/protocols/dex/thena_v1_bnb.toml +2 -1
  23. package/config/protocols/dex/traderjoe_monad.toml +2 -1
  24. package/config/protocols/dex/uniswap_v2_monad.toml +2 -1
  25. package/config/protocols/dex/uniswap_v3_base.toml +2 -1
  26. package/config/protocols/dex/uniswap_v3_bnb.toml +3 -1
  27. package/config/protocols/dex/uniswap_v3_mantle.toml +2 -1
  28. package/config/protocols/dex/uniswap_v3_monad.toml +2 -1
  29. package/config/protocols/lending/felix_morpho.toml +16 -0
  30. package/config/protocols/lending/hyperlend.toml +1 -1
  31. package/config/protocols/lending/hypurrfi.toml +1 -1
  32. package/config/protocols/lending/kinza_bnb.toml +1 -1
  33. package/config/protocols/lending/morpho_blue_monad.toml +17 -0
  34. package/config/protocols/lending/venus_flux_bnb.toml +5 -1
  35. package/dist/index.js +1277 -246
  36. package/dist/index.js.map +1 -1
  37. package/dist/main.js +1282 -251
  38. package/dist/main.js.map +1 -1
  39. package/dist/mcp-server.js +799 -195
  40. package/dist/mcp-server.js.map +1 -1
  41. package/package.json +1 -1
  42. package/skills/defi-cli/SKILL.md +17 -4
  43. package/skills/defi-cli/references/commands.md +2 -2
  44. package/skills/defi-cli/references/protocols.md +2 -2
package/dist/main.js CHANGED
@@ -18,6 +18,12 @@ import { resolve } from "path";
18
18
  import { fileURLToPath } from "url";
19
19
  import { parse } from "smol-toml";
20
20
  import { existsSync } from "fs";
21
+ function defaultSwapSlippage() {
22
+ return { bps: 50 };
23
+ }
24
+ function applyMinSlippage(slippage, amount) {
25
+ return amount * BigInt(1e4 - slippage.bps) / 10000n;
26
+ }
21
27
  function jsonReplacerDecimal(_key, value) {
22
28
  if (typeof value === "bigint") {
23
29
  return value.toString();
@@ -55,11 +61,15 @@ function buildTransfer(token, to, amount) {
55
61
  gas_estimate: 65e3
56
62
  };
57
63
  }
58
- function getProvider(rpcUrl) {
59
- const cached = providerCache.get(rpcUrl);
64
+ function getProvider(rpcUrl, chain) {
65
+ const key = chain ? `${rpcUrl}@${chain.id}` : rpcUrl;
66
+ const cached = providerCache.get(key);
60
67
  if (cached) return cached;
61
- const client = createPublicClient({ transport: http(rpcUrl) });
62
- providerCache.set(rpcUrl, client);
68
+ const client = createPublicClient({
69
+ transport: http(rpcUrl),
70
+ ...chain ? { chain } : {}
71
+ });
72
+ providerCache.set(key, client);
63
73
  return client;
64
74
  }
65
75
  async function multicallRead(rpcUrl, calls) {
@@ -229,6 +239,28 @@ var init_dist = __esm({
229
239
  const chainEnv = this.name.toUpperCase().replace(/ /g, "_") + "_RPC_URL";
230
240
  return process.env[chainEnv] ?? this.rpc_url;
231
241
  }
242
+ /**
243
+ * Build a viem Chain object pinned to this config so wallet/public clients
244
+ * can sign with an explicit chainId rather than auto-fetching it from the
245
+ * RPC. SSOT 7.4: anchoring chainId at client-construction time defends
246
+ * against an MITM RPC that returns the wrong eth_chainId, and keeps
247
+ * offline signing safe against RPC drift.
248
+ */
249
+ viemChain() {
250
+ const rpcUrl = this.effectiveRpcUrl();
251
+ return {
252
+ id: this.chain_id,
253
+ name: this.name,
254
+ nativeCurrency: {
255
+ name: this.native_token,
256
+ symbol: this.native_token,
257
+ decimals: 18
258
+ },
259
+ rpcUrls: { default: { http: [rpcUrl] } },
260
+ ...this.explorer_url ? { blockExplorers: { default: { name: this.name, url: this.explorer_url } } } : {},
261
+ ...this.multicall3 ? { contracts: { multicall3: { address: this.multicall3 } } } : {}
262
+ };
263
+ }
232
264
  };
233
265
  ProtocolCategory = /* @__PURE__ */ ((ProtocolCategory2) => {
234
266
  ProtocolCategory2["Dex"] = "dex";
@@ -420,13 +452,13 @@ __export(dist_exports, {
420
452
  import { encodeFunctionData as encodeFunctionData4, parseAbi as parseAbi4, createPublicClient as createPublicClient4, http as http4, decodeAbiParameters } from "viem";
421
453
  import { encodeFunctionData as encodeFunctionData22, parseAbi as parseAbi22, createPublicClient as createPublicClient22, http as http22, decodeFunctionResult as decodeFunctionResult2, decodeAbiParameters as decodeAbiParameters2 } from "viem";
422
454
  import { encodeFunctionData as encodeFunctionData32, parseAbi as parseAbi32, createPublicClient as createPublicClient32, http as http32, decodeAbiParameters as decodeAbiParameters3, concatHex, zeroAddress } from "viem";
423
- import { encodeFunctionData as encodeFunctionData42, parseAbi as parseAbi42, zeroAddress as zeroAddress2 } from "viem";
455
+ import { encodeFunctionData as encodeFunctionData42, parseAbi as parseAbi42 } from "viem";
424
456
  import { encodeFunctionData as encodeFunctionData5, parseAbi as parseAbi5 } from "viem";
425
457
  import { encodeFunctionData as encodeFunctionData6, parseAbi as parseAbi6, decodeAbiParameters as decodeAbiParameters4 } from "viem";
426
- import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient42, http as http42, zeroAddress as zeroAddress3 } from "viem";
427
- import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress4 } from "viem";
428
- import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress5 } from "viem";
429
- import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress6 } from "viem";
458
+ import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient42, http as http42, zeroAddress as zeroAddress2 } from "viem";
459
+ import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress3 } from "viem";
460
+ import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress4 } from "viem";
461
+ import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress5 } from "viem";
430
462
  import { encodeFunctionData as encodeFunctionData11, parseAbi as parseAbi11, createPublicClient as createPublicClient7, http as http7 } from "viem";
431
463
  import {
432
464
  encodeFunctionData as encodeFunctionData12,
@@ -444,24 +476,24 @@ import {
444
476
  keccak256,
445
477
  parseAbi as parseAbi13,
446
478
  decodeFunctionResult as decodeFunctionResult5,
447
- zeroAddress as zeroAddress7
479
+ zeroAddress as zeroAddress6
448
480
  } from "viem";
449
- import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as zeroAddress8 } from "viem";
450
- import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress9 } from "viem";
481
+ import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as zeroAddress7 } from "viem";
482
+ import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress8 } from "viem";
451
483
  import { createPublicClient as createPublicClient12, http as http12, parseAbi as parseAbi16 } from "viem";
452
484
  import { createPublicClient as createPublicClient13, http as http13, parseAbi as parseAbi17, encodeFunctionData as encodeFunctionData16 } from "viem";
453
485
  import { createPublicClient as createPublicClient14, http as http14, parseAbi as parseAbi18, encodeFunctionData as encodeFunctionData17 } from "viem";
454
486
  import { createPublicClient as createPublicClient15, http as http15, parseAbi as parseAbi19, encodeFunctionData as encodeFunctionData18 } from "viem";
455
- import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as zeroAddress10 } from "viem";
456
- import { createPublicClient as createPublicClient16, http as http16, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress11 } from "viem";
457
- import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi222 } from "viem";
458
- import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
487
+ import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as zeroAddress9, createPublicClient as createPublicClient16, http as http16 } from "viem";
488
+ import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress10 } from "viem";
489
+ import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi222 } from "viem";
490
+ import { createPublicClient as createPublicClient19, http as http19, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
459
491
  import { parseAbi as parseAbi24, encodeFunctionData as encodeFunctionData222 } from "viem";
460
- import { createPublicClient as createPublicClient19, http as http19, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress12 } from "viem";
461
- import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress13 } from "viem";
492
+ import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress11 } from "viem";
493
+ import { createPublicClient as createPublicClient21, http as http21, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress12 } from "viem";
462
494
  import { parseAbi as parseAbi27, encodeFunctionData as encodeFunctionData25 } from "viem";
463
495
  import { parseAbi as parseAbi28, encodeFunctionData as encodeFunctionData26 } from "viem";
464
- import { createPublicClient as createPublicClient21, http as http21, parseAbi as parseAbi29 } from "viem";
496
+ import { createPublicClient as createPublicClient222, http as http222, parseAbi as parseAbi29 } from "viem";
465
497
  function pctToTickDelta(pct) {
466
498
  return Math.round(Math.log(1 + pct / 100) / Math.log(1.0001));
467
499
  }
@@ -715,15 +747,6 @@ function u256ToF642(v) {
715
747
  if (v > MAX_U128) return Infinity;
716
748
  return Number(v);
717
749
  }
718
- function defaultMarketParams(loanToken = zeroAddress10) {
719
- return {
720
- loanToken,
721
- collateralToken: zeroAddress10,
722
- oracle: zeroAddress10,
723
- irm: zeroAddress10,
724
- lltv: 0n
725
- };
726
- }
727
750
  function decodeMarket(data) {
728
751
  if (!data) return null;
729
752
  try {
@@ -956,7 +979,7 @@ function createKittenSwapFarming(entry, rpcUrl) {
956
979
  const bonusRewardToken = entry.contracts?.["bonus_reward_token"];
957
980
  return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken, bonusRewardToken);
958
981
  }
959
- 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_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, 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;
982
+ 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_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, 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;
960
983
  var init_dist2 = __esm({
961
984
  "../defi-protocols/dist/index.js"() {
962
985
  "use strict";
@@ -1024,6 +1047,10 @@ var init_dist2 = __esm({
1024
1047
  "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; }",
1025
1048
  "function mint(SlipstreamMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1026
1049
  ]);
1050
+ ramsesMintAbi = parseAbi4([
1051
+ "struct RamsesMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
1052
+ "function mint(RamsesMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1053
+ ]);
1027
1054
  UniswapV3Adapter = class {
1028
1055
  protocolName;
1029
1056
  router;
@@ -1033,6 +1060,7 @@ var init_dist2 = __esm({
1033
1060
  fee;
1034
1061
  rpcUrl;
1035
1062
  useTickSpacingQuoter;
1063
+ clStyle;
1036
1064
  constructor(entry, rpcUrl) {
1037
1065
  this.protocolName = entry.name;
1038
1066
  const router = entry.contracts?.["router"];
@@ -1045,6 +1073,7 @@ var init_dist2 = __esm({
1045
1073
  this.factory = entry.contracts?.["factory"];
1046
1074
  this.fee = DEFAULT_FEE;
1047
1075
  this.rpcUrl = rpcUrl;
1076
+ this.clStyle = entry.cl_style;
1048
1077
  this.useTickSpacingQuoter = entry.cl_style === "slipstream" || entry.cl_style === "ramses" || entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
1049
1078
  }
1050
1079
  name() {
@@ -1052,7 +1081,15 @@ var init_dist2 = __esm({
1052
1081
  }
1053
1082
  async buildSwap(params) {
1054
1083
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
1055
- const amountOutMinimum = 0n;
1084
+ const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
1085
+ params.slippage,
1086
+ (await this.quote({
1087
+ protocol: this.protocolName,
1088
+ token_in: params.token_in,
1089
+ token_out: params.token_out,
1090
+ amount_in: params.amount_in
1091
+ })).amount_out
1092
+ );
1056
1093
  const data = encodeFunctionData4({
1057
1094
  abi: swapRouterAbi,
1058
1095
  functionName: "exactInputSingle",
@@ -1218,31 +1255,37 @@ var init_dist2 = __esm({
1218
1255
  if (!pm) {
1219
1256
  throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
1220
1257
  }
1221
- 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];
1258
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1259
+ 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];
1222
1260
  const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
1223
1261
  const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
1262
+ const slippage = params.slippage ?? defaultSwapSlippage();
1263
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
1264
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
1265
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
1224
1266
  let thirdField = this.fee;
1225
1267
  let tickLower = -887220;
1226
1268
  let tickUpper = 887220;
1227
1269
  if (params.pool && this.rpcUrl) {
1228
1270
  const poolAbi2 = parseAbi4([
1229
1271
  "function fee() view returns (uint24)",
1230
- "function tickSpacing() view returns (int24)",
1231
- "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, bool)"
1272
+ "function tickSpacing() view returns (int24)"
1232
1273
  ]);
1233
1274
  const client = createPublicClient4({ transport: http4(this.rpcUrl) });
1234
- const [poolFee, poolTs, slot0] = await Promise.all([
1275
+ const [poolFee, poolTs, slot0Raw] = await Promise.all([
1235
1276
  client.readContract({ address: params.pool, abi: poolAbi2, functionName: "fee" }).catch(() => null),
1236
1277
  client.readContract({ address: params.pool, abi: poolAbi2, functionName: "tickSpacing" }).catch(() => null),
1237
- client.readContract({ address: params.pool, abi: poolAbi2, functionName: "slot0" }).catch(() => null)
1278
+ client.call({ to: params.pool, data: "0x3850c7bd" }).then((r) => r.data ?? null).catch(() => null)
1238
1279
  ]);
1239
1280
  if (this.useTickSpacingQuoter && poolTs !== null) {
1240
1281
  thirdField = poolTs;
1241
1282
  } else if (poolFee !== null) {
1242
1283
  thirdField = poolFee;
1243
1284
  }
1244
- if (params.range_pct !== void 0 && slot0 && poolTs !== null) {
1245
- const currentTick = slot0[1];
1285
+ if (params.range_pct !== void 0 && slot0Raw && poolTs !== null) {
1286
+ const tickWord = slot0Raw.slice(2 + 64, 2 + 128);
1287
+ const tickU = BigInt("0x" + tickWord);
1288
+ const currentTick = tickU >= 1n << 255n ? Number(tickU - (1n << 256n)) : Number(tickU);
1246
1289
  const rangeTicks = Math.floor(params.range_pct * 100);
1247
1290
  tickLower = Math.floor((currentTick - rangeTicks) / poolTs) * poolTs;
1248
1291
  tickUpper = Math.ceil((currentTick + rangeTicks) / poolTs) * poolTs;
@@ -1250,7 +1293,25 @@ var init_dist2 = __esm({
1250
1293
  }
1251
1294
  if (params.tick_lower !== void 0) tickLower = params.tick_lower;
1252
1295
  if (params.tick_upper !== void 0) tickUpper = params.tick_upper;
1253
- const data = this.useTickSpacingQuoter ? encodeFunctionData4({
1296
+ const data = this.clStyle === "ramses" ? encodeFunctionData4({
1297
+ abi: ramsesMintAbi,
1298
+ functionName: "mint",
1299
+ args: [
1300
+ {
1301
+ token0,
1302
+ token1,
1303
+ tickSpacing: thirdField,
1304
+ tickLower,
1305
+ tickUpper,
1306
+ amount0Desired: amount0,
1307
+ amount1Desired: amount1,
1308
+ amount0Min,
1309
+ amount1Min,
1310
+ recipient: params.recipient,
1311
+ deadline: BigInt("18446744073709551615")
1312
+ }
1313
+ ]
1314
+ }) : this.useTickSpacingQuoter ? encodeFunctionData4({
1254
1315
  abi: slipstreamMintAbi,
1255
1316
  functionName: "mint",
1256
1317
  args: [
@@ -1262,8 +1323,8 @@ var init_dist2 = __esm({
1262
1323
  tickUpper,
1263
1324
  amount0Desired: amount0,
1264
1325
  amount1Desired: amount1,
1265
- amount0Min: 0n,
1266
- amount1Min: 0n,
1326
+ amount0Min,
1327
+ amount1Min,
1267
1328
  recipient: params.recipient,
1268
1329
  deadline: BigInt("18446744073709551615"),
1269
1330
  sqrtPriceX96: 0n
@@ -1281,8 +1342,8 @@ var init_dist2 = __esm({
1281
1342
  tickUpper,
1282
1343
  amount0Desired: amount0,
1283
1344
  amount1Desired: amount1,
1284
- amount0Min: 0n,
1285
- amount1Min: 0n,
1345
+ amount0Min,
1346
+ amount1Min,
1286
1347
  recipient: params.recipient,
1287
1348
  deadline: BigInt("18446744073709551615")
1288
1349
  }
@@ -1316,10 +1377,17 @@ var init_dist2 = __esm({
1316
1377
  const liquidity = params.liquidity;
1317
1378
  const MAX_UINT128 = (1n << 128n) - 1n;
1318
1379
  const deadline = BigInt("18446744073709551615");
1380
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
1381
+ throw DefiError.invalidParam(
1382
+ `[${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.`
1383
+ );
1384
+ }
1385
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1386
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
1319
1387
  const decreaseData = encodeFunctionData4({
1320
1388
  abi: positionManagerAbi,
1321
1389
  functionName: "decreaseLiquidity",
1322
- args: [{ tokenId, liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
1390
+ args: [{ tokenId, liquidity, amount0Min, amount1Min, deadline }]
1323
1391
  });
1324
1392
  const collectData = encodeFunctionData4({
1325
1393
  abi: positionManagerAbi,
@@ -1617,7 +1685,13 @@ var init_dist2 = __esm({
1617
1685
  to: this.router,
1618
1686
  data,
1619
1687
  value: 0n,
1620
- gas_estimate: 25e4
1688
+ gas_estimate: 25e4,
1689
+ // Same LP-approval requirement as Solidly forks: the V2 router pulls the
1690
+ // LP pair token via transferFrom and reverts at gas ~42k otherwise.
1691
+ // Caller passes --pool so we can target the pair contract for approval.
1692
+ // Discovered live on Uniswap V2 Monad WMON/AUSD 2026-05-08
1693
+ // (failed tx 0x2268659a…09ae → recovered with this fix).
1694
+ ...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
1621
1695
  };
1622
1696
  }
1623
1697
  };
@@ -1672,7 +1746,15 @@ var init_dist2 = __esm({
1672
1746
  }
1673
1747
  async buildSwap(params) {
1674
1748
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
1675
- const amountOutMinimum = 0n;
1749
+ const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
1750
+ params.slippage,
1751
+ (await this.quote({
1752
+ protocol: this.protocolName,
1753
+ token_in: params.token_in,
1754
+ token_out: params.token_out,
1755
+ amount_in: params.amount_in
1756
+ })).amount_out
1757
+ );
1676
1758
  const data = encodeFunctionData32({
1677
1759
  abi: abi2,
1678
1760
  functionName: "exactInputSingle",
@@ -1786,7 +1868,12 @@ var init_dist2 = __esm({
1786
1868
  if (!pm) {
1787
1869
  throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
1788
1870
  }
1789
- 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];
1871
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1872
+ 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];
1873
+ const slippage = params.slippage ?? defaultSwapSlippage();
1874
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
1875
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
1876
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
1790
1877
  let tickLower = params.tick_lower ?? -887220;
1791
1878
  let tickUpper = params.tick_upper ?? 887220;
1792
1879
  const isSingleSide = rawAmount0 === 0n || rawAmount1 === 0n;
@@ -1823,11 +1910,11 @@ var init_dist2 = __esm({
1823
1910
  const data = this.useSingleQuoter ? encodeFunctionData32({
1824
1911
  abi: algebraV2PmAbi,
1825
1912
  functionName: "mint",
1826
- args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1913
+ args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1827
1914
  }) : encodeFunctionData32({
1828
1915
  abi: algebraIntegralPmAbi,
1829
1916
  functionName: "mint",
1830
- args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1917
+ args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
1831
1918
  });
1832
1919
  const approvals = [];
1833
1920
  if (amount0 > 0n) approvals.push({ token: token0, spender: pm, amount: amount0 });
@@ -1845,12 +1932,19 @@ var init_dist2 = __esm({
1845
1932
  const pm = this.positionManager;
1846
1933
  if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
1847
1934
  if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
1935
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
1936
+ throw DefiError.invalidParam(
1937
+ `[${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.`
1938
+ );
1939
+ }
1940
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
1941
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
1848
1942
  const MAX_UINT128 = (1n << 128n) - 1n;
1849
1943
  const deadline = BigInt("18446744073709551615");
1850
1944
  const decreaseData = encodeFunctionData32({
1851
1945
  abi: algebraSharedPmAbi,
1852
1946
  functionName: "decreaseLiquidity",
1853
- args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
1947
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
1854
1948
  });
1855
1949
  const collectData = encodeFunctionData32({
1856
1950
  abi: algebraSharedPmAbi,
@@ -1877,6 +1971,7 @@ var init_dist2 = __esm({
1877
1971
  BalancerV3Adapter = class {
1878
1972
  protocolName;
1879
1973
  router;
1974
+ pool;
1880
1975
  constructor(entry, _rpcUrl) {
1881
1976
  this.protocolName = entry.name;
1882
1977
  const router = entry.contracts?.["router"];
@@ -1884,19 +1979,29 @@ var init_dist2 = __esm({
1884
1979
  throw new DefiError("CONTRACT_ERROR", "Missing 'router' contract");
1885
1980
  }
1886
1981
  this.router = router;
1982
+ this.pool = entry.contracts?.["pool"];
1887
1983
  }
1888
1984
  name() {
1889
1985
  return this.protocolName;
1890
1986
  }
1891
1987
  async buildSwap(params) {
1892
- const minAmountOut = 0n;
1988
+ if (params.amount_out_min === void 0) {
1989
+ throw DefiError.invalidParam(
1990
+ `[${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.`
1991
+ );
1992
+ }
1993
+ if (!this.pool) {
1994
+ throw DefiError.invalidParam(
1995
+ `[${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.`
1996
+ );
1997
+ }
1998
+ const minAmountOut = params.amount_out_min;
1893
1999
  const deadline = BigInt(params.deadline ?? 18446744073709551615n);
1894
2000
  const data = encodeFunctionData42({
1895
2001
  abi: abi3,
1896
2002
  functionName: "swapSingleTokenExactIn",
1897
2003
  args: [
1898
- zeroAddress2,
1899
- // TODO: resolve pool from registry
2004
+ this.pool,
1900
2005
  params.token_in,
1901
2006
  params.token_out,
1902
2007
  params.amount_in,
@@ -1907,7 +2012,7 @@ var init_dist2 = __esm({
1907
2012
  ]
1908
2013
  });
1909
2014
  return {
1910
- description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3`,
2015
+ description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3 pool ${this.pool}`,
1911
2016
  to: this.router,
1912
2017
  data,
1913
2018
  value: 0n,
@@ -1927,8 +2032,8 @@ var init_dist2 = __esm({
1927
2032
  poolAbi = parseAbi5([
1928
2033
  "function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256)",
1929
2034
  "function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256)",
1930
- "function add_liquidity(uint256[2] amounts, uint256 min_mint_amount) external returns (uint256)",
1931
- "function remove_liquidity(uint256 amount, uint256[2] min_amounts) external returns (uint256[2])"
2035
+ "function add_liquidity(uint256[] amounts, uint256 min_mint_amount) external returns (uint256)",
2036
+ "function remove_liquidity(uint256 amount, uint256[] min_amounts) external returns (uint256[])"
1932
2037
  ]);
1933
2038
  CurveStableSwapAdapter = class {
1934
2039
  protocolName;
@@ -1963,17 +2068,22 @@ var init_dist2 = __esm({
1963
2068
  throw DefiError.unsupported(`[${this.protocolName}] quote requires RPC connection`);
1964
2069
  }
1965
2070
  async buildAddLiquidity(params) {
2071
+ if (!params.pool) {
2072
+ throw DefiError.invalidParam(
2073
+ `[${this.protocolName}] Curve add_liquidity needs --pool <address>. The router does not proxy this call; it lives on the pool itself.`
2074
+ );
2075
+ }
1966
2076
  const data = encodeFunctionData5({
1967
2077
  abi: poolAbi,
1968
2078
  functionName: "add_liquidity",
1969
2079
  args: [[params.amount_a, params.amount_b], 0n]
1970
2080
  });
1971
2081
  const approvals = [];
1972
- if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: this.router, amount: params.amount_a });
1973
- if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: this.router, amount: params.amount_b });
2082
+ if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: params.pool, amount: params.amount_a });
2083
+ if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: params.pool, amount: params.amount_b });
1974
2084
  return {
1975
- description: `[${this.protocolName}] Curve add liquidity`,
1976
- to: this.router,
2085
+ description: `[${this.protocolName}] Curve add liquidity to ${params.pool}`,
2086
+ to: params.pool,
1977
2087
  data,
1978
2088
  value: 0n,
1979
2089
  gas_estimate: 4e5,
@@ -1981,14 +2091,19 @@ var init_dist2 = __esm({
1981
2091
  };
1982
2092
  }
1983
2093
  async buildRemoveLiquidity(params) {
2094
+ if (!params.pool) {
2095
+ throw DefiError.invalidParam(
2096
+ `[${this.protocolName}] Curve remove_liquidity needs --pool <address>. The router does not proxy this call.`
2097
+ );
2098
+ }
1984
2099
  const data = encodeFunctionData5({
1985
2100
  abi: poolAbi,
1986
2101
  functionName: "remove_liquidity",
1987
2102
  args: [params.liquidity, [0n, 0n]]
1988
2103
  });
1989
2104
  return {
1990
- description: `[${this.protocolName}] Curve remove liquidity`,
1991
- to: this.router,
2105
+ description: `[${this.protocolName}] Curve remove liquidity from ${params.pool}`,
2106
+ to: params.pool,
1992
2107
  data,
1993
2108
  value: 0n,
1994
2109
  gas_estimate: 35e4
@@ -2154,7 +2269,12 @@ var init_dist2 = __esm({
2154
2269
  to: this.router,
2155
2270
  data,
2156
2271
  value: 0n,
2157
- gas_estimate: 3e5
2272
+ gas_estimate: 3e5,
2273
+ // The router pulls the LP token via transferFrom; without this approval
2274
+ // the tx reverts at gas ~42k. Caller must pass --pool so we know which
2275
+ // LP pair to approve. Discovered live on Aerodrome USDC/USDT 2026-05-08
2276
+ // (failed tx 0x6d052e0a…3298 → recovered with manual approve 0xa126fc3a).
2277
+ ...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
2158
2278
  };
2159
2279
  }
2160
2280
  };
@@ -2201,6 +2321,11 @@ var init_dist2 = __esm({
2201
2321
  return this.protocolName;
2202
2322
  }
2203
2323
  async buildSwap(params) {
2324
+ if (params.amount_out_min === void 0) {
2325
+ throw DefiError.invalidParam(
2326
+ `[${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.`
2327
+ );
2328
+ }
2204
2329
  const data = encodeFunctionData7({
2205
2330
  abi: thenaRouterAbi,
2206
2331
  functionName: "exactInputSingle",
@@ -2211,7 +2336,7 @@ var init_dist2 = __esm({
2211
2336
  recipient: params.recipient,
2212
2337
  deadline: BigInt(params.deadline ?? 18446744073709551615n),
2213
2338
  amountIn: params.amount_in,
2214
- amountOutMinimum: 0n,
2339
+ amountOutMinimum: params.amount_out_min,
2215
2340
  sqrtPriceLimitX96: 0n
2216
2341
  }]
2217
2342
  });
@@ -2231,7 +2356,12 @@ var init_dist2 = __esm({
2231
2356
  const pm = this.positionManager;
2232
2357
  if (!pm) throw new DefiError("CONTRACT_ERROR", "Position manager not configured");
2233
2358
  if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
2234
- 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];
2359
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
2360
+ 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];
2361
+ const slippage = params.slippage ?? defaultSwapSlippage();
2362
+ const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
2363
+ const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
2364
+ const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
2235
2365
  const client = createPublicClient42({ transport: http42(this.rpcUrl) });
2236
2366
  const poolAddr = params.pool;
2237
2367
  let tickSpacing = this.defaultTickSpacing;
@@ -2246,7 +2376,7 @@ var init_dist2 = __esm({
2246
2376
  functionName: "getPool",
2247
2377
  args: [token0, token1, tickSpacing]
2248
2378
  });
2249
- if (pool === zeroAddress3) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2379
+ if (pool === zeroAddress2) throw new DefiError("CONTRACT_ERROR", "Pool not found");
2250
2380
  }
2251
2381
  if (pool) {
2252
2382
  const [slot0, ts] = await Promise.all([
@@ -2289,8 +2419,8 @@ var init_dist2 = __esm({
2289
2419
  tickUpper,
2290
2420
  amount0Desired: rawAmount0,
2291
2421
  amount1Desired: rawAmount1,
2292
- amount0Min: 0n,
2293
- amount1Min: 0n,
2422
+ amount0Min,
2423
+ amount1Min,
2294
2424
  recipient: params.recipient,
2295
2425
  deadline: BigInt("18446744073709551615"),
2296
2426
  sqrtPriceX96: 0n
@@ -2312,12 +2442,19 @@ var init_dist2 = __esm({
2312
2442
  const pm = this.positionManager;
2313
2443
  if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
2314
2444
  if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
2445
+ if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
2446
+ throw DefiError.invalidParam(
2447
+ `[${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.`
2448
+ );
2449
+ }
2450
+ const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
2451
+ const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
2315
2452
  const MAX_UINT128 = (1n << 128n) - 1n;
2316
2453
  const deadline = BigInt("18446744073709551615");
2317
2454
  const decreaseData = encodeFunctionData7({
2318
2455
  abi: thenaPmAbi,
2319
2456
  functionName: "decreaseLiquidity",
2320
- args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
2457
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
2321
2458
  });
2322
2459
  const collectData = encodeFunctionData7({
2323
2460
  abi: thenaPmAbi,
@@ -2384,8 +2521,8 @@ var init_dist2 = __esm({
2384
2521
  const ve = entry.contracts?.["ve_token"];
2385
2522
  if (!ve) throw new DefiError("CONTRACT_ERROR", "Missing 've_token' contract");
2386
2523
  this.veToken = ve;
2387
- this.voter = entry.contracts?.["voter"] ?? zeroAddress4;
2388
- this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress4;
2524
+ this.voter = entry.contracts?.["voter"] ?? zeroAddress3;
2525
+ this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress3;
2389
2526
  this.poolFactory = entry.contracts?.["pool_factory"];
2390
2527
  this.rpcUrl = rpcUrl;
2391
2528
  }
@@ -2427,7 +2564,7 @@ var init_dist2 = __esm({
2427
2564
  ]);
2428
2565
  }
2429
2566
  const poolAddressResults = await multicallRead(this.rpcUrl, poolAddressCalls);
2430
- const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !== zeroAddress4);
2567
+ const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !== zeroAddress3);
2431
2568
  if (pools.length === 0) return [];
2432
2569
  const gaugeCalls = pools.map((pool) => [
2433
2570
  this.gaugeManager,
@@ -2437,7 +2574,7 @@ var init_dist2 = __esm({
2437
2574
  const gaugedPools = [];
2438
2575
  for (let i = 0; i < pools.length; i++) {
2439
2576
  const gauge = decodeAddress(gaugeResults[i] ?? null);
2440
- if (gauge && gauge !== zeroAddress4) {
2577
+ if (gauge && gauge !== zeroAddress3) {
2441
2578
  gaugedPools.push({ pool: pools[i], gauge });
2442
2579
  }
2443
2580
  }
@@ -2452,8 +2589,8 @@ var init_dist2 = __esm({
2452
2589
  for (let i = 0; i < gaugedPools.length; i++) {
2453
2590
  const t0 = decodeAddress(tokenResults[i * 2] ?? null);
2454
2591
  const t1 = decodeAddress(tokenResults[i * 2 + 1] ?? null);
2455
- if (t0 && t0 !== zeroAddress4) tokenAddrs.add(t0);
2456
- if (t1 && t1 !== zeroAddress4) tokenAddrs.add(t1);
2592
+ if (t0 && t0 !== zeroAddress3) tokenAddrs.add(t0);
2593
+ if (t1 && t1 !== zeroAddress3) tokenAddrs.add(t1);
2457
2594
  }
2458
2595
  const uniqueTokens = Array.from(tokenAddrs);
2459
2596
  const symbolCalls = uniqueTokens.map((t) => [
@@ -2492,7 +2629,7 @@ var init_dist2 = __esm({
2492
2629
  functionName: "gauges",
2493
2630
  args: [pool]
2494
2631
  });
2495
- if (gauge === zeroAddress4) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
2632
+ if (gauge === zeroAddress3) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
2496
2633
  return gauge;
2497
2634
  }
2498
2635
  // ─── CL Gauge: NFT Deposit/Withdraw ──────────────────────────
@@ -2654,7 +2791,7 @@ var init_dist2 = __esm({
2654
2791
  params.amount_in,
2655
2792
  minToAmount,
2656
2793
  params.recipient,
2657
- zeroAddress5
2794
+ zeroAddress4
2658
2795
  ]
2659
2796
  });
2660
2797
  return {
@@ -2879,7 +3016,7 @@ var init_dist2 = __esm({
2879
3016
  poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
2880
3017
  }
2881
3018
  const poolResults = await multicallRead(this.rpcUrl, poolCalls);
2882
- pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
3019
+ pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
2883
3020
  } else {
2884
3021
  const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
2885
3022
  const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
@@ -2904,17 +3041,17 @@ var init_dist2 = __esm({
2904
3041
  const pairCalls = [];
2905
3042
  for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
2906
3043
  const pairResults = await multicallRead(this.rpcUrl, pairCalls);
2907
- pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
3044
+ pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
2908
3045
  }
2909
3046
  if (pairs.length === 0) return;
2910
3047
  const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
2911
3048
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
2912
- const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3049
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
2913
3050
  if (allNull) {
2914
3051
  const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
2915
3052
  gaugeResults = await multicallRead(this.rpcUrl, fb1);
2916
3053
  }
2917
- const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3054
+ const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
2918
3055
  if (allNull2) {
2919
3056
  const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
2920
3057
  gaugeResults = await multicallRead(this.rpcUrl, fb2);
@@ -2922,7 +3059,7 @@ var init_dist2 = __esm({
2922
3059
  const gaugedPairs = [];
2923
3060
  for (let i = 0; i < pairs.length; i++) {
2924
3061
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
2925
- if (gauge && gauge !== zeroAddress6) {
3062
+ if (gauge && gauge !== zeroAddress5) {
2926
3063
  gaugedPairs.push({ pair: pairs[i], gauge });
2927
3064
  }
2928
3065
  }
@@ -2938,8 +3075,8 @@ var init_dist2 = __esm({
2938
3075
  for (let i = 0; i < gaugedPairs.length; i++) {
2939
3076
  const t0 = decodeAddress2(metaResults[i * 3] ?? null);
2940
3077
  const t1 = decodeAddress2(metaResults[i * 3 + 1] ?? null);
2941
- if (t0 && t0 !== zeroAddress6) tokenAddrs.add(t0);
2942
- if (t1 && t1 !== zeroAddress6) tokenAddrs.add(t1);
3078
+ if (t0 && t0 !== zeroAddress5) tokenAddrs.add(t0);
3079
+ if (t1 && t1 !== zeroAddress5) tokenAddrs.add(t1);
2943
3080
  }
2944
3081
  const uniqueTokens = Array.from(tokenAddrs);
2945
3082
  const symbolCalls = uniqueTokens.map((t) => [
@@ -3029,7 +3166,7 @@ var init_dist2 = __esm({
3029
3166
  const candidatePools = [];
3030
3167
  for (let i = 0; i < getPoolCalls.length; i++) {
3031
3168
  const pool = decodeAddress2(getPoolResults[i] ?? null);
3032
- if (pool && pool !== zeroAddress6) {
3169
+ if (pool && pool !== zeroAddress5) {
3033
3170
  const { pairIdx, tickSpacing } = callMeta[i];
3034
3171
  const [tokenA, tokenB] = pairs[pairIdx];
3035
3172
  candidatePools.push({ pool, tokenA, tokenB, tickSpacing });
@@ -3041,7 +3178,7 @@ var init_dist2 = __esm({
3041
3178
  encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pool] })
3042
3179
  ]);
3043
3180
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
3044
- const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3181
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
3045
3182
  if (allNull) {
3046
3183
  const fallbackCalls = candidatePools.map(({ pool }) => [
3047
3184
  this.voter,
@@ -3052,7 +3189,7 @@ var init_dist2 = __esm({
3052
3189
  const gaugedCL = [];
3053
3190
  for (let i = 0; i < candidatePools.length; i++) {
3054
3191
  const gauge = decodeAddress2(gaugeResults[i] ?? null);
3055
- if (gauge && gauge !== zeroAddress6) {
3192
+ if (gauge && gauge !== zeroAddress5) {
3056
3193
  gaugedCL.push({ ...candidatePools[i], gauge });
3057
3194
  }
3058
3195
  }
@@ -3082,8 +3219,8 @@ var init_dist2 = __esm({
3082
3219
  const { pool, gauge, tokenA, tokenB, tickSpacing } = gaugedCL[i];
3083
3220
  const rawT0 = decodeAddress2(poolTokenResults[i * 2] ?? null);
3084
3221
  const rawT1 = decodeAddress2(poolTokenResults[i * 2 + 1] ?? null);
3085
- const t0 = rawT0 && rawT0 !== zeroAddress6 ? rawT0 : tokenA;
3086
- const t1 = rawT1 && rawT1 !== zeroAddress6 ? rawT1 : tokenB;
3222
+ const t0 = rawT0 && rawT0 !== zeroAddress5 ? rawT0 : tokenA;
3223
+ const t1 = rawT1 && rawT1 !== zeroAddress5 ? rawT1 : tokenB;
3087
3224
  out.push({
3088
3225
  pool,
3089
3226
  gauge,
@@ -3185,7 +3322,7 @@ var init_dist2 = __esm({
3185
3322
  functionName: fn,
3186
3323
  args: [pool]
3187
3324
  });
3188
- if (gauge !== zeroAddress6) return gauge;
3325
+ if (gauge !== zeroAddress5) return gauge;
3189
3326
  } catch {
3190
3327
  }
3191
3328
  }
@@ -3239,7 +3376,7 @@ var init_dist2 = __esm({
3239
3376
  abi: gaugeAbi,
3240
3377
  functionName: "rewardToken"
3241
3378
  });
3242
- if (rt !== zeroAddress6) return { tokens: [rt], multiToken: false };
3379
+ if (rt !== zeroAddress5) return { tokens: [rt], multiToken: false };
3243
3380
  } catch {
3244
3381
  }
3245
3382
  return { tokens: [], multiToken: false };
@@ -3249,7 +3386,7 @@ var init_dist2 = __esm({
3249
3386
  const data = encodeFunctionData10({
3250
3387
  abi: gaugeAbi,
3251
3388
  functionName: "getReward",
3252
- args: [account ?? zeroAddress6]
3389
+ args: [account ?? zeroAddress5]
3253
3390
  });
3254
3391
  return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
3255
3392
  }
@@ -3473,7 +3610,7 @@ var init_dist2 = __esm({
3473
3610
  functionName: "earned",
3474
3611
  args: [user]
3475
3612
  });
3476
- results.push({ token: zeroAddress6, symbol: "unknown", amount: earned });
3613
+ results.push({ token: zeroAddress5, symbol: "unknown", amount: earned });
3477
3614
  } catch {
3478
3615
  }
3479
3616
  }
@@ -3835,20 +3972,47 @@ var init_dist2 = __esm({
3835
3972
  /**
3836
3973
  * Build an addLiquidity transaction for a Liquidity Book pair.
3837
3974
  * Distributes tokenX/tokenY uniformly across active bin ± numBins.
3975
+ *
3976
+ * The LB pair stores tokenX/tokenY in the order set at factory creation,
3977
+ * which is **not** address-sorted. Callers may pass tokens in either order;
3978
+ * we always re-align with the pool's `getTokenX/getTokenY` (and swap amounts
3979
+ * accordingly) so the router's `if (tokenX != pair.tokenX) revert` path is
3980
+ * never hit.
3838
3981
  */
3839
3982
  async buildAddLiquidity(params) {
3840
3983
  const numBins = params.numBins ?? 5;
3841
3984
  const deadline = params.deadline ?? BigInt("18446744073709551615");
3842
- let activeIdDesired = params.activeIdDesired;
3843
- if (activeIdDesired === void 0) {
3844
- const rpcUrl = this.requireRpc();
3845
- const client = createPublicClient8({ transport: http8(rpcUrl) });
3846
- const activeId = await client.readContract({
3847
- address: params.pool,
3848
- abi: lbPairAbi,
3849
- functionName: "getActiveId"
3850
- });
3851
- activeIdDesired = activeId;
3985
+ const rpcUrl = this.requireRpc();
3986
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
3987
+ const [poolTokenX, poolTokenY, onChainActiveId] = await Promise.all([
3988
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
3989
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" }),
3990
+ params.activeIdDesired === void 0 ? client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getActiveId" }) : Promise.resolve(params.activeIdDesired)
3991
+ ]);
3992
+ const activeIdDesired = onChainActiveId;
3993
+ const inX = params.tokenX.toLowerCase();
3994
+ const inY = params.tokenY.toLowerCase();
3995
+ const poolX = poolTokenX.toLowerCase();
3996
+ const poolY = poolTokenY.toLowerCase();
3997
+ let tokenX;
3998
+ let tokenY;
3999
+ let amountX;
4000
+ let amountY;
4001
+ if (inX === poolX && inY === poolY) {
4002
+ tokenX = params.tokenX;
4003
+ tokenY = params.tokenY;
4004
+ amountX = params.amountX;
4005
+ amountY = params.amountY;
4006
+ } else if (inX === poolY && inY === poolX) {
4007
+ tokenX = poolTokenX;
4008
+ tokenY = poolTokenY;
4009
+ amountX = params.amountY;
4010
+ amountY = params.amountX;
4011
+ } else {
4012
+ throw new DefiError(
4013
+ "CONTRACT_ERROR",
4014
+ `[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
4015
+ );
3852
4016
  }
3853
4017
  const deltaIds = [];
3854
4018
  for (let d = -numBins; d <= numBins; d++) {
@@ -3860,11 +4024,11 @@ var init_dist2 = __esm({
3860
4024
  functionName: "addLiquidity",
3861
4025
  args: [
3862
4026
  {
3863
- tokenX: params.tokenX,
3864
- tokenY: params.tokenY,
4027
+ tokenX,
4028
+ tokenY,
3865
4029
  binStep: BigInt(params.binStep),
3866
- amountX: params.amountX,
3867
- amountY: params.amountY,
4030
+ amountX,
4031
+ amountY,
3868
4032
  amountXMin: 0n,
3869
4033
  amountYMin: 0n,
3870
4034
  activeIdDesired: BigInt(activeIdDesired),
@@ -3879,31 +4043,63 @@ var init_dist2 = __esm({
3879
4043
  ]
3880
4044
  });
3881
4045
  return {
3882
- description: `[${this.protocolName}] LB addLiquidity ${params.amountX} tokenX + ${params.amountY} tokenY across ${deltaIds.length} bins`,
4046
+ description: `[${this.protocolName}] LB addLiquidity ${amountX} tokenX + ${amountY} tokenY across ${deltaIds.length} bins`,
3883
4047
  to: this.lbRouter,
3884
4048
  data,
3885
4049
  value: 0n,
3886
4050
  gas_estimate: 8e5,
3887
4051
  approvals: [
3888
- { token: params.tokenX, spender: this.lbRouter, amount: params.amountX },
3889
- { token: params.tokenY, spender: this.lbRouter, amount: params.amountY }
4052
+ { token: tokenX, spender: this.lbRouter, amount: amountX },
4053
+ { token: tokenY, spender: this.lbRouter, amount: amountY }
3890
4054
  ]
3891
4055
  };
3892
4056
  }
3893
4057
  /**
3894
4058
  * Build a removeLiquidity transaction for specific LB bins.
4059
+ *
4060
+ * When `pool` is supplied, the adapter realigns tokenX/tokenY (and
4061
+ * amountXMin/amountYMin) to the pool's actual ordering. Otherwise it trusts
4062
+ * the caller — the router will revert if the order is wrong.
3895
4063
  */
3896
4064
  async buildRemoveLiquidity(params) {
3897
4065
  const deadline = params.deadline ?? BigInt("18446744073709551615");
4066
+ let tokenX = params.tokenX;
4067
+ let tokenY = params.tokenY;
4068
+ let amountXMin = params.amountXMin ?? 0n;
4069
+ let amountYMin = params.amountYMin ?? 0n;
4070
+ if (params.pool) {
4071
+ const rpcUrl = this.requireRpc();
4072
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
4073
+ const [poolTokenX, poolTokenY] = await Promise.all([
4074
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
4075
+ client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" })
4076
+ ]);
4077
+ const inX = params.tokenX.toLowerCase();
4078
+ const inY = params.tokenY.toLowerCase();
4079
+ const poolX = poolTokenX.toLowerCase();
4080
+ const poolY = poolTokenY.toLowerCase();
4081
+ if (inX === poolY && inY === poolX) {
4082
+ tokenX = poolTokenX;
4083
+ tokenY = poolTokenY;
4084
+ const x = amountXMin;
4085
+ amountXMin = amountYMin;
4086
+ amountYMin = x;
4087
+ } else if (!(inX === poolX && inY === poolY)) {
4088
+ throw new DefiError(
4089
+ "CONTRACT_ERROR",
4090
+ `[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
4091
+ );
4092
+ }
4093
+ }
3898
4094
  const data = encodeFunctionData12({
3899
4095
  abi: lbRouterAbi,
3900
4096
  functionName: "removeLiquidity",
3901
4097
  args: [
3902
- params.tokenX,
3903
- params.tokenY,
4098
+ tokenX,
4099
+ tokenY,
3904
4100
  params.binStep,
3905
- params.amountXMin ?? 0n,
3906
- params.amountYMin ?? 0n,
4101
+ amountXMin,
4102
+ amountYMin,
3907
4103
  params.binIds.map(BigInt),
3908
4104
  params.amounts,
3909
4105
  params.recipient,
@@ -4813,7 +5009,7 @@ var init_dist2 = __esm({
4813
5009
  const poolSet = /* @__PURE__ */ new Set();
4814
5010
  for (const data of poolResults) {
4815
5011
  const addr = decodeAddress3(data);
4816
- if (addr && addr !== zeroAddress7) {
5012
+ if (addr && addr !== zeroAddress6) {
4817
5013
  poolSet.add(addr.toLowerCase());
4818
5014
  }
4819
5015
  }
@@ -4978,6 +5174,34 @@ var init_dist2 = __esm({
4978
5174
  }
4979
5175
  return apr;
4980
5176
  }
5177
+ /**
5178
+ * Snapshot of all NEST gauged pools from the off-chain blaze API. The
5179
+ * on-chain ve(3,3) gauges return `rewardRate=0` (emissions are credited
5180
+ * off-chain via signed claim tickets), so the on-chain Solidly gauge
5181
+ * reader cannot surface real emission APR. This API is the canonical
5182
+ * read source — used by `lp discover` to expose non-zero NEST yields.
5183
+ */
5184
+ async getLiquidityPools() {
5185
+ const raw = await this.fetchJson("/liquidity-pools");
5186
+ const out = [];
5187
+ for (const p of raw) {
5188
+ const t0 = p.token0?.basetoken;
5189
+ const t1 = p.token1?.basetoken;
5190
+ if (!t0?.address || !t1?.address) continue;
5191
+ out.push({
5192
+ pool: p.id,
5193
+ gauge: p.gauge ?? null,
5194
+ token0: { address: t0.address, symbol: t0.symbol ?? "?" },
5195
+ token1: { address: t1.address, symbol: t1.symbol ?? "?" },
5196
+ tvlUSD: Number(p.tvlUSD ?? 0),
5197
+ aprPercent: Number(p.apr ?? 0),
5198
+ curEpochEmissionRewardsUSD: Number(p.curEpochEmissionRewardsUSD ?? 0),
5199
+ poolType: p.poolType,
5200
+ isStable: p.isStable
5201
+ });
5202
+ }
5203
+ return out;
5204
+ }
4981
5205
  /** Pending NEST emissions as IGauge-compatible RewardInfo[] */
4982
5206
  async getPendingRewards(user) {
4983
5207
  const status = await this.getClaimStatus(user);
@@ -5133,6 +5357,16 @@ var init_dist2 = __esm({
5133
5357
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
5134
5358
  "function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
5135
5359
  "function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
5360
+ // Toggles required to actually borrow against an isolation-mode reserve
5361
+ // (https://aave.com/docs/aave-v3/smart-contracts/pool):
5362
+ // - setUserUseReserveAsCollateral: an isolation reserve can be enabled
5363
+ // only if no other asset is enabled; LTV=0 reserves can never be
5364
+ // enabled; disable reverts if the resulting HF would drop below the
5365
+ // liquidation threshold.
5366
+ // - setUserEMode: reverts if the user is borrowing a non-eMode-
5367
+ // compatible asset, or if the change would push HF below threshold.
5368
+ "function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external",
5369
+ "function setUserEMode(uint8 categoryId) external",
5136
5370
  "function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
5137
5371
  "function getReservesList() external view returns (address[])",
5138
5372
  "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)"
@@ -5258,6 +5492,34 @@ var init_dist2 = __esm({
5258
5492
  gas_estimate: 25e4
5259
5493
  };
5260
5494
  }
5495
+ async buildSetUseReserveAsCollateral(asset, useAsCollateral) {
5496
+ const data = encodeFunctionData14({
5497
+ abi: POOL_ABI,
5498
+ functionName: "setUserUseReserveAsCollateral",
5499
+ args: [asset, useAsCollateral]
5500
+ });
5501
+ return {
5502
+ description: `[${this.protocolName}] ${useAsCollateral ? "Enable" : "Disable"} ${asset} as collateral`,
5503
+ to: this.pool,
5504
+ data,
5505
+ value: 0n,
5506
+ gas_estimate: 1e5
5507
+ };
5508
+ }
5509
+ async buildSetEMode(categoryId) {
5510
+ const data = encodeFunctionData14({
5511
+ abi: POOL_ABI,
5512
+ functionName: "setUserEMode",
5513
+ args: [categoryId]
5514
+ });
5515
+ return {
5516
+ description: `[${this.protocolName}] Set eMode category to ${categoryId}${categoryId === 0 ? " (opt out)" : ""}`,
5517
+ to: this.pool,
5518
+ data,
5519
+ value: 0n,
5520
+ gas_estimate: 15e4
5521
+ };
5522
+ }
5261
5523
  async getRates(asset) {
5262
5524
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5263
5525
  const reserveCallData = encodeFunctionData14({
@@ -5302,7 +5564,7 @@ var init_dist2 = __esm({
5302
5564
  [aTokenAddress, encodeFunctionData14({ abi: INCENTIVES_ABI, functionName: "getIncentivesController" })]
5303
5565
  ]);
5304
5566
  const controllerAddr = decodeAddress4(controllerRaw ?? null);
5305
- if (controllerAddr && controllerAddr !== zeroAddress8) {
5567
+ if (controllerAddr && controllerAddr !== zeroAddress7) {
5306
5568
  const [supplyRewardsRaw, borrowRewardsRaw] = await multicallRead(this.rpcUrl, [
5307
5569
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [aTokenAddress] })],
5308
5570
  [controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [variableDebtTokenAddress] })]
@@ -5498,8 +5760,8 @@ var init_dist2 = __esm({
5498
5760
  if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
5499
5761
  }
5500
5762
  } catch {
5501
- if (collateralUsd > 0) supplies.push({ asset: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5502
- if (debtUsd > 0) borrows.push({ asset: zeroAddress8, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
5763
+ if (collateralUsd > 0) supplies.push({ asset: zeroAddress7, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5764
+ if (debtUsd > 0) borrows.push({ asset: zeroAddress7, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
5503
5765
  }
5504
5766
  return {
5505
5767
  protocol: this.protocolName,
@@ -5666,8 +5928,8 @@ var init_dist2 = __esm({
5666
5928
  const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
5667
5929
  const debtUsd = u256ToF642(totalDebtBase) / 1e18;
5668
5930
  const ltvBps = u256ToF642(ltv);
5669
- const supplies = collateralUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5670
- const borrows = debtUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
5931
+ const supplies = collateralUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5932
+ const borrows = debtUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
5671
5933
  return {
5672
5934
  protocol: this.protocolName,
5673
5935
  user,
@@ -5770,14 +6032,31 @@ var init_dist2 = __esm({
5770
6032
  "function borrow(uint256 borrowAmount) external returns (uint256)",
5771
6033
  "function repayBorrow(uint256 repayAmount) external returns (uint256)"
5772
6034
  ]);
6035
+ NATIVE_CTOKEN_ABI = parseAbi17([
6036
+ "function mint() external payable",
6037
+ "function repayBorrow() external payable"
6038
+ ]);
6039
+ COMPTROLLER_ABI = parseAbi17([
6040
+ "function enterMarkets(address[] cTokens) external returns (uint256[])",
6041
+ "function exitMarket(address cToken) external returns (uint256)"
6042
+ ]);
6043
+ NATIVE_SENTINEL = "0x0000000000000000000000000000000000000000";
5773
6044
  BSC_BLOCKS_PER_YEAR = 10512e3;
5774
6045
  CompoundV2Adapter = class {
5775
6046
  protocolName;
5776
6047
  defaultVtoken;
5777
6048
  vTokenCandidates;
6049
+ comptroller;
5778
6050
  rpcUrl;
5779
- // Lazy cache: underlying asset address (lowercased) → vToken address
6051
+ // Lazy cache: underlying asset address (lowercased) → vToken address.
6052
+ // The native sentinel (0x0…) is mapped to the cETH/vBNB-style vToken
6053
+ // when one is detected during resolveVtoken().
5780
6054
  vTokenByAsset = null;
6055
+ // The cETH/vBNB-style vToken whose underlying() reverts (it has no
6056
+ // ERC20 underlying — the underlying is the chain's native gas token).
6057
+ // Set lazily by resolveVtoken() and consulted by buildSupply/buildRepay
6058
+ // to switch to the payable mint() / repayBorrow() variants.
6059
+ nativeVtoken = null;
5781
6060
  constructor(entry, rpcUrl) {
5782
6061
  this.protocolName = entry.name;
5783
6062
  this.rpcUrl = rpcUrl;
@@ -5785,6 +6064,7 @@ var init_dist2 = __esm({
5785
6064
  const vtoken = contracts["vusdt"] ?? contracts["vusdc"] ?? contracts["vbnb"] ?? contracts["comptroller"];
5786
6065
  if (!vtoken) throw DefiError.contractError("Missing vToken or comptroller address");
5787
6066
  this.defaultVtoken = vtoken;
6067
+ this.comptroller = contracts["comptroller"];
5788
6068
  this.vTokenCandidates = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k)).map(([, v]) => v);
5789
6069
  if (this.vTokenCandidates.length === 0) this.vTokenCandidates = [vtoken];
5790
6070
  }
@@ -5793,19 +6073,38 @@ var init_dist2 = __esm({
5793
6073
  if (!this.vTokenByAsset) {
5794
6074
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
5795
6075
  const map = /* @__PURE__ */ new Map();
6076
+ let nativeVtoken = null;
5796
6077
  const lookups = await Promise.allSettled(
5797
6078
  this.vTokenCandidates.map(async (v) => {
5798
- const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
5799
- return [u.toLowerCase(), v];
6079
+ try {
6080
+ const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
6081
+ return { vtoken: v, underlying: u };
6082
+ } catch {
6083
+ return { vtoken: v, underlying: null };
6084
+ }
5800
6085
  })
5801
6086
  );
5802
6087
  for (const r of lookups) {
5803
- if (r.status === "fulfilled") map.set(r.value[0], r.value[1]);
6088
+ if (r.status !== "fulfilled") continue;
6089
+ const { vtoken, underlying } = r.value;
6090
+ if (underlying) {
6091
+ map.set(underlying.toLowerCase(), vtoken);
6092
+ } else if (!nativeVtoken) {
6093
+ nativeVtoken = vtoken;
6094
+ }
6095
+ }
6096
+ if (nativeVtoken) {
6097
+ map.set(NATIVE_SENTINEL, nativeVtoken);
5804
6098
  }
5805
6099
  this.vTokenByAsset = map;
6100
+ this.nativeVtoken = nativeVtoken;
5806
6101
  }
5807
6102
  return this.vTokenByAsset.get(asset.toLowerCase()) ?? null;
5808
6103
  }
6104
+ /** True iff `vtoken` is the cETH/vBNB-style native cToken for this protocol. */
6105
+ isNativeVtoken(vtoken) {
6106
+ return this.nativeVtoken !== null && vtoken.toLowerCase() === this.nativeVtoken.toLowerCase();
6107
+ }
5809
6108
  name() {
5810
6109
  return this.protocolName;
5811
6110
  }
@@ -5819,6 +6118,16 @@ var init_dist2 = __esm({
5819
6118
  }
5820
6119
  async buildSupply(params) {
5821
6120
  const vtoken = await this.vtokenFor(params.asset);
6121
+ if (this.isNativeVtoken(vtoken)) {
6122
+ const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "mint" });
6123
+ return {
6124
+ description: `[${this.protocolName}] Supply ${params.amount} (native) to Venus`,
6125
+ to: vtoken,
6126
+ data: data2,
6127
+ value: params.amount,
6128
+ gas_estimate: 3e5
6129
+ };
6130
+ }
5822
6131
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "mint", args: [params.amount] });
5823
6132
  return {
5824
6133
  description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to Venus`,
@@ -5842,6 +6151,16 @@ var init_dist2 = __esm({
5842
6151
  }
5843
6152
  async buildRepay(params) {
5844
6153
  const vtoken = await this.vtokenFor(params.asset);
6154
+ if (this.isNativeVtoken(vtoken)) {
6155
+ const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "repayBorrow" });
6156
+ return {
6157
+ description: `[${this.protocolName}] Repay ${params.amount} (native) to Venus`,
6158
+ to: vtoken,
6159
+ data: data2,
6160
+ value: params.amount,
6161
+ gas_estimate: 3e5
6162
+ };
6163
+ }
5845
6164
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "repayBorrow", args: [params.amount] });
5846
6165
  return {
5847
6166
  description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to Venus`,
@@ -5854,6 +6173,37 @@ var init_dist2 = __esm({
5854
6173
  }
5855
6174
  async buildWithdraw(params) {
5856
6175
  const vtoken = await this.vtokenFor(params.asset);
6176
+ const MAX_UINT2562 = (1n << 256n) - 1n;
6177
+ if (params.amount === MAX_UINT2562 && this.rpcUrl) {
6178
+ const client = createPublicClient13({ transport: http13(this.rpcUrl) });
6179
+ const [vtokenBalance, borrowBalance] = await Promise.all([
6180
+ client.readContract({
6181
+ address: vtoken,
6182
+ abi: CTOKEN_ABI,
6183
+ functionName: "balanceOf",
6184
+ args: [params.to]
6185
+ }),
6186
+ client.readContract({
6187
+ address: vtoken,
6188
+ abi: CTOKEN_ABI,
6189
+ functionName: "borrowBalanceStored",
6190
+ args: [params.to]
6191
+ }).catch(() => 0n)
6192
+ ]);
6193
+ if (borrowBalance > 0n) {
6194
+ throw DefiError.contractError(
6195
+ `[${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.`
6196
+ );
6197
+ }
6198
+ const redeemData = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeem", args: [vtokenBalance] });
6199
+ return {
6200
+ description: `[${this.protocolName}] Withdraw all (auto-max, ${vtokenBalance} vTokens) of ${params.asset} from Venus`,
6201
+ to: vtoken,
6202
+ data: redeemData,
6203
+ value: 0n,
6204
+ gas_estimate: 35e4
6205
+ };
6206
+ }
5857
6207
  const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeemUnderlying", args: [params.amount] });
5858
6208
  return {
5859
6209
  description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from Venus`,
@@ -5863,6 +6213,37 @@ var init_dist2 = __esm({
5863
6213
  gas_estimate: 25e4
5864
6214
  };
5865
6215
  }
6216
+ /**
6217
+ * Compound V2 family: enter cTokens as collateral via Comptroller.
6218
+ * Without this call, supplied assets sit dormant in the Comptroller's
6219
+ * accountAssets[] and `getAccountLiquidity` reports zero collateral —
6220
+ * any borrow then reverts. Mirrors the role of Aave V3's
6221
+ * setUserUseReserveAsCollateral, but the API is batch-by-cToken.
6222
+ */
6223
+ async buildEnterMarkets(cTokens) {
6224
+ if (!this.comptroller) {
6225
+ throw DefiError.contractError(
6226
+ `[${this.protocolName}] enterMarkets requires the Comptroller address to be registered under [protocol.contracts] as 'comptroller'.`
6227
+ );
6228
+ }
6229
+ if (cTokens.length === 0) {
6230
+ throw DefiError.invalidParam(
6231
+ `[${this.protocolName}] enterMarkets requires at least one cToken address.`
6232
+ );
6233
+ }
6234
+ const data = encodeFunctionData16({
6235
+ abi: COMPTROLLER_ABI,
6236
+ functionName: "enterMarkets",
6237
+ args: [cTokens]
6238
+ });
6239
+ return {
6240
+ description: `[${this.protocolName}] Enter ${cTokens.length} market(s) as collateral`,
6241
+ to: this.comptroller,
6242
+ data,
6243
+ value: 0n,
6244
+ gas_estimate: 2e5
6245
+ };
6246
+ }
5866
6247
  async getRates(asset) {
5867
6248
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5868
6249
  const client = createPublicClient13({ transport: http13(this.rpcUrl) });
@@ -6230,9 +6611,11 @@ var init_dist2 = __esm({
6230
6611
  "function market(bytes32 id) external view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
6231
6612
  "function idToMarketParams(bytes32 id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)",
6232
6613
  "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)",
6614
+ "function supplyCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, bytes data) external",
6233
6615
  "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)",
6234
6616
  "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)",
6235
- "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)"
6617
+ "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)",
6618
+ "function withdrawCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, address receiver) external"
6236
6619
  ]);
6237
6620
  META_MORPHO_ABI = parseAbi20([
6238
6621
  "function supplyQueueLength() external view returns (uint256)",
@@ -6259,6 +6642,8 @@ var init_dist2 = __esm({
6259
6642
  rpcUrl;
6260
6643
  metaMorphoVaults;
6261
6644
  metaMorphoVaultEntries;
6645
+ namedMarkets;
6646
+ namedMarketByName;
6262
6647
  vaultAssetMap = null;
6263
6648
  constructor(entry, rpcUrl) {
6264
6649
  this.protocolName = entry.name;
@@ -6270,6 +6655,26 @@ var init_dist2 = __esm({
6270
6655
  this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
6271
6656
  this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
6272
6657
  this.metaMorphoVaults = this.metaMorphoVaultEntries.map((e) => e.addr);
6658
+ this.namedMarkets = entry.markets ?? [];
6659
+ const byName = /* @__PURE__ */ new Map();
6660
+ for (const m of this.namedMarkets) byName.set(m.name.toLowerCase(), m.id);
6661
+ this.namedMarketByName = byName;
6662
+ }
6663
+ /**
6664
+ * Resolve a friendly market name (e.g. `WMON-AUSD`) to its 32-byte
6665
+ * marketId via the per-protocol TOML registry. Returns null when the
6666
+ * adapter has no markets[] block or the name doesn't match any entry —
6667
+ * callers fall back to treating the input as a raw hex marketId.
6668
+ */
6669
+ resolveMarketIdByName(name) {
6670
+ return this.namedMarketByName.get(name.toLowerCase()) ?? null;
6671
+ }
6672
+ /**
6673
+ * Returns the registered named markets for diagnostics (e.g. CLI error
6674
+ * messages listing valid choices when the user passes an unknown name).
6675
+ */
6676
+ listNamedMarkets() {
6677
+ return this.namedMarkets;
6273
6678
  }
6274
6679
  async resolveVault(asset, preferKey) {
6275
6680
  if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
@@ -6303,10 +6708,61 @@ var init_dist2 = __esm({
6303
6708
  name() {
6304
6709
  return this.protocolName;
6305
6710
  }
6711
+ /**
6712
+ * Resolve a Morpho Blue marketId into the full MarketParams tuple by
6713
+ * calling Morpho.idToMarketParams(id). Used by every direct-market
6714
+ * method (supply / borrow / repay / withdraw / supplyCollateral /
6715
+ * withdrawCollateral) so the caller only has to pass the 32-byte
6716
+ * marketId — same shape as the Morpho UI / API.
6717
+ */
6718
+ async resolveMarketParams(marketId) {
6719
+ if (!this.rpcUrl) {
6720
+ throw DefiError.rpcError(
6721
+ `[${this.protocolName}] No RPC URL configured \u2014 cannot resolve marketId ${marketId}`
6722
+ );
6723
+ }
6724
+ const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6725
+ let result;
6726
+ try {
6727
+ result = await client.readContract({
6728
+ address: this.morpho,
6729
+ abi: MORPHO_ABI,
6730
+ functionName: "idToMarketParams",
6731
+ args: [marketId]
6732
+ });
6733
+ } catch (e) {
6734
+ throw DefiError.rpcError(
6735
+ `[${this.protocolName}] idToMarketParams(${marketId}) failed: ${e}`
6736
+ );
6737
+ }
6738
+ const [loanToken, collateralToken, oracle, irm, lltv] = result;
6739
+ if (loanToken === zeroAddress9 || collateralToken === zeroAddress9 || lltv === 0n) {
6740
+ throw DefiError.invalidParam(
6741
+ `[${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.`
6742
+ );
6743
+ }
6744
+ return { loanToken, collateralToken, oracle, irm, lltv };
6745
+ }
6306
6746
  async buildSupply(params) {
6747
+ if (params.market_id) {
6748
+ const market = await this.resolveMarketParams(params.market_id);
6749
+ const data = encodeFunctionData19({
6750
+ abi: MORPHO_ABI,
6751
+ functionName: "supply",
6752
+ args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6753
+ });
6754
+ return {
6755
+ description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6756
+ to: this.morpho,
6757
+ data,
6758
+ value: 0n,
6759
+ gas_estimate: 35e4,
6760
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6761
+ };
6762
+ }
6307
6763
  const vault = await this.resolveVault(params.asset);
6308
6764
  if (vault) {
6309
- const data2 = encodeFunctionData19({
6765
+ const data = encodeFunctionData19({
6310
6766
  abi: ERC4626_ABI,
6311
6767
  functionName: "deposit",
6312
6768
  args: [params.amount, params.on_behalf_of]
@@ -6314,50 +6770,118 @@ var init_dist2 = __esm({
6314
6770
  return {
6315
6771
  description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
6316
6772
  to: vault,
6317
- data: data2,
6773
+ data,
6318
6774
  value: 0n,
6319
6775
  gas_estimate: 4e5,
6320
6776
  approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
6321
6777
  };
6322
6778
  }
6323
- const market = defaultMarketParams(params.asset);
6779
+ throw DefiError.invalidParam(
6780
+ `[${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).`
6781
+ );
6782
+ }
6783
+ async buildBorrow(params) {
6784
+ if (!params.market_id) {
6785
+ throw DefiError.invalidParam(
6786
+ `[${this.protocolName}] Morpho Blue borrow requires --market <marketId>. Find one via the Morpho API (https://blue-api.morpho.org/graphql).`
6787
+ );
6788
+ }
6789
+ const market = await this.resolveMarketParams(params.market_id);
6324
6790
  const data = encodeFunctionData19({
6325
6791
  abi: MORPHO_ABI,
6326
- functionName: "supply",
6792
+ functionName: "borrow",
6793
+ args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
6794
+ });
6795
+ return {
6796
+ description: `[${this.protocolName}] Borrow ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
6797
+ to: this.morpho,
6798
+ data,
6799
+ value: 0n,
6800
+ gas_estimate: 4e5
6801
+ };
6802
+ }
6803
+ async buildRepay(params) {
6804
+ if (!params.market_id) {
6805
+ throw DefiError.invalidParam(
6806
+ `[${this.protocolName}] Morpho Blue repay requires --market <marketId>.`
6807
+ );
6808
+ }
6809
+ const market = await this.resolveMarketParams(params.market_id);
6810
+ if (params.amount === MAX_UINT256) {
6811
+ if (!this.rpcUrl) {
6812
+ throw DefiError.rpcError(
6813
+ `[${this.protocolName}] max-repay requires an RPC URL to read borrowShares.`
6814
+ );
6815
+ }
6816
+ const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6817
+ const positionAbi = parseAbi20([
6818
+ "function position(bytes32 id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)"
6819
+ ]);
6820
+ const pos = await client.readContract({
6821
+ address: this.morpho,
6822
+ abi: positionAbi,
6823
+ functionName: "position",
6824
+ args: [params.market_id, params.on_behalf_of]
6825
+ });
6826
+ const [, borrowShares] = pos;
6827
+ if (borrowShares === 0n) {
6828
+ throw DefiError.invalidParam(
6829
+ `[${this.protocolName}] cannot repay max \u2014 user has no borrow position in market ${params.market_id}.`
6830
+ );
6831
+ }
6832
+ const data2 = encodeFunctionData19({
6833
+ abi: MORPHO_ABI,
6834
+ functionName: "repay",
6835
+ args: [market, 0n, borrowShares, params.on_behalf_of, "0x"]
6836
+ });
6837
+ return {
6838
+ description: `[${this.protocolName}] Repay max (${borrowShares} shares) to market ${params.market_id.slice(0, 10)}\u2026`,
6839
+ to: this.morpho,
6840
+ data: data2,
6841
+ value: 0n,
6842
+ gas_estimate: 35e4,
6843
+ approvals: [{ token: params.asset, spender: this.morpho, amount: MAX_UINT256 }]
6844
+ };
6845
+ }
6846
+ const data = encodeFunctionData19({
6847
+ abi: MORPHO_ABI,
6848
+ functionName: "repay",
6327
6849
  args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6328
6850
  });
6329
6851
  return {
6330
- description: `[${this.protocolName}] Supply ${params.amount} to Morpho market`,
6852
+ description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6331
6853
  to: this.morpho,
6332
6854
  data,
6333
6855
  value: 0n,
6334
- gas_estimate: 3e5
6856
+ gas_estimate: 35e4,
6857
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6335
6858
  };
6336
6859
  }
6337
- async buildBorrow(params) {
6338
- const market = defaultMarketParams(params.asset);
6860
+ async buildSupplyCollateral(params) {
6861
+ const market = await this.resolveMarketParams(params.market_id);
6339
6862
  const data = encodeFunctionData19({
6340
6863
  abi: MORPHO_ABI,
6341
- functionName: "borrow",
6342
- args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
6864
+ functionName: "supplyCollateral",
6865
+ args: [market, params.amount, params.on_behalf_of, "0x"]
6343
6866
  });
6344
6867
  return {
6345
- description: `[${this.protocolName}] Borrow ${params.amount} from Morpho market`,
6868
+ description: `[${this.protocolName}] Supply collateral ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
6346
6869
  to: this.morpho,
6347
6870
  data,
6348
6871
  value: 0n,
6349
- gas_estimate: 35e4
6872
+ gas_estimate: 35e4,
6873
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6350
6874
  };
6351
6875
  }
6352
- async buildRepay(params) {
6353
- const market = defaultMarketParams(params.asset);
6876
+ async buildWithdrawCollateral(params) {
6877
+ const market = await this.resolveMarketParams(params.market_id);
6354
6878
  const data = encodeFunctionData19({
6355
6879
  abi: MORPHO_ABI,
6356
- functionName: "repay",
6357
- args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6880
+ functionName: "withdrawCollateral",
6881
+ args: [market, params.amount, params.to, params.to]
6358
6882
  });
6359
6883
  return {
6360
- description: `[${this.protocolName}] Repay ${params.amount} to Morpho market`,
6884
+ description: `[${this.protocolName}] Withdraw collateral ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
6361
6885
  to: this.morpho,
6362
6886
  data,
6363
6887
  value: 0n,
@@ -6365,6 +6889,21 @@ var init_dist2 = __esm({
6365
6889
  };
6366
6890
  }
6367
6891
  async buildWithdraw(params) {
6892
+ if (params.market_id) {
6893
+ const market = await this.resolveMarketParams(params.market_id);
6894
+ const data = encodeFunctionData19({
6895
+ abi: MORPHO_ABI,
6896
+ functionName: "withdraw",
6897
+ args: [market, params.amount, 0n, params.to, params.to]
6898
+ });
6899
+ return {
6900
+ description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
6901
+ to: this.morpho,
6902
+ data,
6903
+ value: 0n,
6904
+ gas_estimate: 3e5
6905
+ };
6906
+ }
6368
6907
  const vault = await this.resolveVault(params.asset);
6369
6908
  if (vault) {
6370
6909
  if (params.amount === MAX_UINT256) {
@@ -6373,7 +6912,7 @@ var init_dist2 = __esm({
6373
6912
  [vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
6374
6913
  ]);
6375
6914
  const shares = decodeU256(balRaw ?? null);
6376
- const data3 = encodeFunctionData19({
6915
+ const data2 = encodeFunctionData19({
6377
6916
  abi: ERC4626_ABI,
6378
6917
  functionName: "redeem",
6379
6918
  args: [shares, params.to, params.to]
@@ -6381,12 +6920,12 @@ var init_dist2 = __esm({
6381
6920
  return {
6382
6921
  description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
6383
6922
  to: vault,
6384
- data: data3,
6923
+ data: data2,
6385
6924
  value: 0n,
6386
6925
  gas_estimate: 4e5
6387
6926
  };
6388
6927
  }
6389
- const data2 = encodeFunctionData19({
6928
+ const data = encodeFunctionData19({
6390
6929
  abi: ERC4626_ABI,
6391
6930
  functionName: "withdraw",
6392
6931
  args: [params.amount, params.to, params.to]
@@ -6394,24 +6933,14 @@ var init_dist2 = __esm({
6394
6933
  return {
6395
6934
  description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
6396
6935
  to: vault,
6397
- data: data2,
6936
+ data,
6398
6937
  value: 0n,
6399
6938
  gas_estimate: 4e5
6400
6939
  };
6401
6940
  }
6402
- const market = defaultMarketParams(params.asset);
6403
- const data = encodeFunctionData19({
6404
- abi: MORPHO_ABI,
6405
- functionName: "withdraw",
6406
- args: [market, params.amount, 0n, params.to, params.to]
6407
- });
6408
- return {
6409
- description: `[${this.protocolName}] Withdraw ${params.amount} from Morpho market`,
6410
- to: this.morpho,
6411
- data,
6412
- value: 0n,
6413
- gas_estimate: 25e4
6414
- };
6941
+ throw DefiError.invalidParam(
6942
+ `[${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).`
6943
+ );
6415
6944
  }
6416
6945
  async getRates(asset) {
6417
6946
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
@@ -6528,7 +7057,7 @@ var init_dist2 = __esm({
6528
7057
  if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
6529
7058
  return [0n, 0n];
6530
7059
  }
6531
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7060
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6532
7061
  const approxResult = await client.readContract({
6533
7062
  address: this.hintHelpers,
6534
7063
  abi: HINT_HELPERS_ABI,
@@ -6619,7 +7148,7 @@ var init_dist2 = __esm({
6619
7148
  async getCdpInfo(cdpId) {
6620
7149
  if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
6621
7150
  if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
6622
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
7151
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6623
7152
  const data = await client.readContract({
6624
7153
  address: this.troveManager,
6625
7154
  abi: TROVE_MANAGER_ABI,
@@ -6637,13 +7166,13 @@ var init_dist2 = __esm({
6637
7166
  protocol: this.protocolName,
6638
7167
  cdp_id: cdpId,
6639
7168
  collateral: {
6640
- token: zeroAddress11,
7169
+ token: zeroAddress10,
6641
7170
  symbol: "WHYPE",
6642
7171
  amount: entireColl,
6643
7172
  decimals: 18
6644
7173
  },
6645
7174
  debt: {
6646
- token: zeroAddress11,
7175
+ token: zeroAddress10,
6647
7176
  symbol: "feUSD",
6648
7177
  amount: entireDebt,
6649
7178
  decimals: 18
@@ -6678,7 +7207,7 @@ var init_dist2 = __esm({
6678
7207
  if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
6679
7208
  throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
6680
7209
  }
6681
- const client = createPublicClient17({ transport: http17(this.rpcUrl) });
7210
+ const client = createPublicClient18({ transport: http18(this.rpcUrl) });
6682
7211
  let priceVal;
6683
7212
  try {
6684
7213
  const result = await client.readContract({
@@ -6770,7 +7299,7 @@ var init_dist2 = __esm({
6770
7299
  }
6771
7300
  async totalAssets() {
6772
7301
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6773
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7302
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6774
7303
  return client.readContract({
6775
7304
  address: this.vaultAddress,
6776
7305
  abi: ERC4626_ABI2,
@@ -6781,7 +7310,7 @@ var init_dist2 = __esm({
6781
7310
  }
6782
7311
  async convertToShares(assets) {
6783
7312
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6784
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7313
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6785
7314
  return client.readContract({
6786
7315
  address: this.vaultAddress,
6787
7316
  abi: ERC4626_ABI2,
@@ -6793,7 +7322,7 @@ var init_dist2 = __esm({
6793
7322
  }
6794
7323
  async convertToAssets(shares) {
6795
7324
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6796
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7325
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6797
7326
  return client.readContract({
6798
7327
  address: this.vaultAddress,
6799
7328
  abi: ERC4626_ABI2,
@@ -6805,7 +7334,7 @@ var init_dist2 = __esm({
6805
7334
  }
6806
7335
  async getVaultInfo() {
6807
7336
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6808
- const client = createPublicClient18({ transport: http18(this.rpcUrl) });
7337
+ const client = createPublicClient19({ transport: http19(this.rpcUrl) });
6809
7338
  const [totalAssets, totalSupply, asset] = await Promise.all([
6810
7339
  client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
6811
7340
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
@@ -6897,7 +7426,7 @@ var init_dist2 = __esm({
6897
7426
  const data = encodeFunctionData23({
6898
7427
  abi: STHYPE_ABI,
6899
7428
  functionName: "submit",
6900
- args: [zeroAddress12]
7429
+ args: [zeroAddress11]
6901
7430
  });
6902
7431
  return {
6903
7432
  description: `[${this.protocolName}] Stake ${params.amount} HYPE for stHYPE`,
@@ -6923,7 +7452,7 @@ var init_dist2 = __esm({
6923
7452
  }
6924
7453
  async getInfo() {
6925
7454
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6926
- const client = createPublicClient19({ transport: http19(this.rpcUrl) });
7455
+ const client = createPublicClient20({ transport: http20(this.rpcUrl) });
6927
7456
  const tokenAddr = this.sthypeToken ?? this.staking;
6928
7457
  const totalSupply = await client.readContract({
6929
7458
  address: tokenAddr,
@@ -6934,7 +7463,7 @@ var init_dist2 = __esm({
6934
7463
  });
6935
7464
  return {
6936
7465
  protocol: this.protocolName,
6937
- staked_token: zeroAddress12,
7466
+ staked_token: zeroAddress11,
6938
7467
  liquid_token: tokenAddr,
6939
7468
  exchange_rate: 1,
6940
7469
  total_staked: totalSupply
@@ -6993,7 +7522,7 @@ var init_dist2 = __esm({
6993
7522
  }
6994
7523
  async getInfo() {
6995
7524
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
6996
- const client = createPublicClient20({ transport: http20(this.rpcUrl) });
7525
+ const client = createPublicClient21({ transport: http21(this.rpcUrl) });
6997
7526
  const totalStaked = await client.readContract({
6998
7527
  address: this.staking,
6999
7528
  abi: KINETIQ_ABI,
@@ -7008,7 +7537,7 @@ var init_dist2 = __esm({
7008
7537
  const rateF64 = hypePrice > 0n && khypePrice > 0n ? Number(khypePrice * 10n ** 18n / hypePrice) / 1e18 : 1;
7009
7538
  return {
7010
7539
  protocol: this.protocolName,
7011
- staked_token: zeroAddress13,
7540
+ staked_token: zeroAddress12,
7012
7541
  liquid_token: this.liquidToken,
7013
7542
  exchange_rate: rateF64,
7014
7543
  total_staked: totalStaked
@@ -7228,7 +7757,7 @@ var init_dist2 = __esm({
7228
7757
  }
7229
7758
  async getCollectionInfo(collection) {
7230
7759
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7231
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7760
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7232
7761
  const [collectionName, symbol, totalSupply] = await Promise.all([
7233
7762
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "name" }).catch((e) => {
7234
7763
  throw DefiError.rpcError(`[${this.protocolName}] name failed: ${e}`);
@@ -7247,7 +7776,7 @@ var init_dist2 = __esm({
7247
7776
  }
7248
7777
  async getTokenInfo(collection, tokenId) {
7249
7778
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7250
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7779
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7251
7780
  const [owner, tokenUri] = await Promise.all([
7252
7781
  client.readContract({ address: collection, abi: ERC721_ABI, functionName: "ownerOf", args: [tokenId] }).catch((e) => {
7253
7782
  throw DefiError.rpcError(`[${this.protocolName}] ownerOf failed: ${e}`);
@@ -7263,7 +7792,7 @@ var init_dist2 = __esm({
7263
7792
  }
7264
7793
  async getBalance(owner, collection) {
7265
7794
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
7266
- const client = createPublicClient21({ transport: http21(this.rpcUrl) });
7795
+ const client = createPublicClient222({ transport: http222(this.rpcUrl) });
7267
7796
  return client.readContract({ address: collection, abi: ERC721_ABI, functionName: "balanceOf", args: [owner] }).catch((e) => {
7268
7797
  throw DefiError.rpcError(`[${this.protocolName}] balanceOf failed: ${e}`);
7269
7798
  });
@@ -7332,10 +7861,22 @@ var Executor = class _Executor {
7332
7861
  dryRun;
7333
7862
  rpcUrl;
7334
7863
  explorerUrl;
7335
- constructor(broadcast, rpcUrl, explorerUrl) {
7864
+ /**
7865
+ * Optional viem Chain object. When set, all wallet/public clients built
7866
+ * inside this executor anchor to the explicit chainId at construction
7867
+ * time, defending against MITM RPCs that lie about eth_chainId and
7868
+ * keeping offline-signing safe under RPC drift (SSOT 7.4).
7869
+ */
7870
+ chain;
7871
+ constructor(broadcast, rpcUrl, explorerUrl, chain) {
7336
7872
  this.dryRun = !broadcast;
7337
7873
  this.rpcUrl = rpcUrl;
7338
7874
  this.explorerUrl = explorerUrl;
7875
+ this.chain = chain;
7876
+ }
7877
+ /** Returns the optional `{ chain }` spread for viem client constructors. */
7878
+ chainOpt() {
7879
+ return this.chain ? { chain: this.chain } : {};
7339
7880
  }
7340
7881
  /** Apply 20% buffer to a gas estimate */
7341
7882
  static applyGasBuffer(gas) {
@@ -7459,7 +8000,7 @@ var Executor = class _Executor {
7459
8000
  */
7460
8001
  async fetchEip1559Fees(rpcUrl) {
7461
8002
  try {
7462
- const client = createPublicClient2({ transport: http2(rpcUrl) });
8003
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7463
8004
  let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
7464
8005
  try {
7465
8006
  priorityFee = await client.estimateMaxPriorityFeePerGas();
@@ -7481,7 +8022,7 @@ var Executor = class _Executor {
7481
8022
  /** Estimate gas dynamically with buffer, falling back to a hardcoded estimate */
7482
8023
  async estimateGasWithBuffer(rpcUrl, tx, from) {
7483
8024
  try {
7484
- const client = createPublicClient2({ transport: http2(rpcUrl) });
8025
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7485
8026
  const estimated = await client.estimateGas({
7486
8027
  to: tx.to,
7487
8028
  data: tx.data,
@@ -7505,7 +8046,7 @@ var Executor = class _Executor {
7505
8046
  if (!rpcUrl) {
7506
8047
  throw DefiError.rpcError("No RPC URL \u2014 cannot simulate. Set HYPEREVM_RPC_URL.");
7507
8048
  }
7508
- const client = createPublicClient2({ transport: http2(rpcUrl) });
8049
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7509
8050
  const privateKey = process.env["DEFI_PRIVATE_KEY"];
7510
8051
  const from = privateKey ? privateKeyToAccount(privateKey).address : "0x0000000000000000000000000000000000000001";
7511
8052
  if (tx.approvals && tx.approvals.length > 0) {
@@ -7619,8 +8160,8 @@ var Executor = class _Executor {
7619
8160
  if (!rpcUrl) {
7620
8161
  throw DefiError.rpcError("No RPC URL configured for broadcasting");
7621
8162
  }
7622
- const publicClient = createPublicClient2({ transport: http2(rpcUrl) });
7623
- const walletClient = createWalletClient({ account, transport: http2(rpcUrl) });
8163
+ const publicClient = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
8164
+ const walletClient = createWalletClient({ account, transport: http2(rpcUrl), ...this.chainOpt() });
7624
8165
  if (tx.pre_txs && tx.pre_txs.length > 0) {
7625
8166
  for (const preTx of tx.pre_txs) {
7626
8167
  process.stderr.write(` Pre-tx: ${preTx.description}...
@@ -8302,7 +8843,7 @@ function registerSchema(parent, getOpts) {
8302
8843
  init_dist();
8303
8844
  init_dist();
8304
8845
  init_dist2();
8305
- import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as zeroAddress14 } from "viem";
8846
+ import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as zeroAddress13 } from "viem";
8306
8847
 
8307
8848
  // src/whitelist.ts
8308
8849
  import { readFileSync as readFileSync2 } from "fs";
@@ -8723,6 +9264,24 @@ function registerLP(parent, getOpts, makeExecutor2) {
8723
9264
  await Promise.allSettled(
8724
9265
  protocols.map(async (protocol) => {
8725
9266
  try {
9267
+ if (protocol.reward_strategy === "off_chain_api") {
9268
+ const adapter = createNestOffChain(protocol);
9269
+ const pools = await adapter.getLiquidityPools();
9270
+ for (const p of pools) {
9271
+ if (p.aprPercent === 0 && opts.emissionOnly) continue;
9272
+ results.push({
9273
+ protocol: protocol.slug,
9274
+ pool: p.pool,
9275
+ pair: `${p.token0.symbol}/${p.token1.symbol}`,
9276
+ type: p.aprPercent > 0 ? "EMISSION" : "FEE",
9277
+ source: "off_chain_api",
9278
+ aprPercent: p.aprPercent,
9279
+ poolTvlUsd: p.tvlUSD,
9280
+ emissionUsd: p.curEpochEmissionRewardsUSD
9281
+ });
9282
+ }
9283
+ return;
9284
+ }
8726
9285
  const isGaugeProtocol = ["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(protocol.interface) || protocol.interface === "uniswap_v3" && protocol.contracts?.["voter"];
8727
9286
  if (isGaugeProtocol) {
8728
9287
  const chainTokens = registry.tokens.get(chainName)?.map((t) => t.address);
@@ -8838,7 +9397,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8838
9397
  try {
8839
9398
  const decoded = decodeFunctionResult8({ abi: mcAbi, functionName: "poolInfo", data: raw });
8840
9399
  const [allocPoint, v3Pool, t0, t1, , totalLiq] = decoded;
8841
- if (allocPoint === 0n || v3Pool === zeroAddress14) continue;
9400
+ if (allocPoint === 0n || v3Pool === zeroAddress13) continue;
8842
9401
  const tokens = registry.tokens.get(chainName);
8843
9402
  const sym0 = tokens?.find((t) => t.address.toLowerCase() === t0?.toLowerCase())?.symbol ?? t0?.slice(0, 8) ?? "?";
8844
9403
  const sym1 = tokens?.find((t) => t.address.toLowerCase() === t1?.toLowerCase())?.symbol ?? t1?.slice(0, 8) ?? "?";
@@ -8905,7 +9464,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8905
9464
  if (opts.emissionOnly) {
8906
9465
  printOutput(
8907
9466
  results.filter(
8908
- (r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n)
9467
+ (r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n || (r.aprPercent ?? 0) > 0)
8909
9468
  ),
8910
9469
  getOpts()
8911
9470
  );
@@ -8913,7 +9472,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8913
9472
  printOutput(results, getOpts());
8914
9473
  }
8915
9474
  });
8916
- lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").option("--num-bins <n>", "Merchant Moe LB: bins on each side of active (default 5)").action(async (opts) => {
9475
+ lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").option("--num-bins <n>", "Merchant Moe LB: bins on each side of active (default 5)").option("--slippage <bps>", "Slippage tolerance in basis points (default 50 = 0.5%). Sets amount{0,1}Min per side via applyMinSlippage.").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on add (overrides --slippage for that side).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on add (overrides --slippage for that side).").action(async (opts) => {
8917
9476
  const executor = makeExecutor2();
8918
9477
  const chainName = parent.opts().chain;
8919
9478
  if (!chainName) {
@@ -8930,13 +9489,27 @@ function registerLP(parent, getOpts, makeExecutor2) {
8930
9489
  if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
8931
9490
  if (!poolAddr) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>; use \`lp discover --protocol ${protocol.slug}\` to list active pools)`);
8932
9491
  const lbAdapter = createMerchantMoeLB(protocol, chain.effectiveRpcUrl());
8933
- const [tokenX, tokenY, amountX, amountY] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB, BigInt(opts.amountA), BigInt(opts.amountB)] : [tokenB, tokenA, BigInt(opts.amountB), BigInt(opts.amountA)];
8934
9492
  const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
8935
- const binStep = await client.readContract({
8936
- address: poolAddr,
8937
- abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
8938
- functionName: "getBinStep"
8939
- });
9493
+ const lbPairOrderAbi = parseAbi30([
9494
+ "function getTokenX() view returns (address)",
9495
+ "function getTokenY() view returns (address)",
9496
+ "function getBinStep() view returns (uint16)"
9497
+ ]);
9498
+ const [poolTokenX, poolTokenY, binStep] = await Promise.all([
9499
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenX" }),
9500
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenY" }),
9501
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getBinStep" })
9502
+ ]);
9503
+ const aIsX = tokenA.toLowerCase() === poolTokenX.toLowerCase();
9504
+ const bIsX = tokenB.toLowerCase() === poolTokenX.toLowerCase();
9505
+ const aIsY = tokenA.toLowerCase() === poolTokenY.toLowerCase();
9506
+ const bIsY = tokenB.toLowerCase() === poolTokenY.toLowerCase();
9507
+ if (!(aIsX && bIsY || bIsX && aIsY)) {
9508
+ throw new Error(
9509
+ `[${protocol.name}] --token-a/--token-b (${tokenA}/${tokenB}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
9510
+ );
9511
+ }
9512
+ const [tokenX, tokenY, amountX, amountY] = aIsX ? [tokenA, tokenB, BigInt(opts.amountA), BigInt(opts.amountB)] : [tokenB, tokenA, BigInt(opts.amountB), BigInt(opts.amountA)];
8940
9513
  const tx2 = await lbAdapter.buildAddLiquidity({
8941
9514
  pool: poolAddr,
8942
9515
  tokenX,
@@ -8962,12 +9535,15 @@ function registerLP(parent, getOpts, makeExecutor2) {
8962
9535
  tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
8963
9536
  tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
8964
9537
  range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
8965
- pool: poolAddr
9538
+ pool: poolAddr,
9539
+ slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
9540
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
9541
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
8966
9542
  });
8967
9543
  const result = await executor.execute(tx);
8968
9544
  printOutput(result, getOpts());
8969
9545
  });
8970
- lp.command("farm").description("Add liquidity and auto-stake into gauge/farming for emissions").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--gauge <address>", "Gauge address (required for solidly/hybra if not resolved automatically)").option("--recipient <address>", "Recipient / owner address").option("--tick-lower <tick>", "Lower tick for concentrated LP").option("--tick-upper <tick>", "Upper tick for concentrated LP").option("--range <percent>", "\xB1N% concentrated range around current price").action(async (opts) => {
9546
+ lp.command("farm").description("Add liquidity and auto-stake into gauge/farming for emissions").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--gauge <address>", "Gauge address (required for solidly/hybra if not resolved automatically)").option("--recipient <address>", "Recipient / owner address").option("--tick-lower <tick>", "Lower tick for concentrated LP").option("--tick-upper <tick>", "Upper tick for concentrated LP").option("--range <percent>", "\xB1N% concentrated range around current price").option("--slippage <bps>", "Slippage tolerance in basis points (default 50 = 0.5%). Applied to the underlying mint step.").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on add (overrides --slippage for that side).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on add (overrides --slippage for that side).").action(async (opts) => {
8971
9547
  const executor = makeExecutor2();
8972
9548
  const chainName = parent.opts().chain;
8973
9549
  if (!chainName) {
@@ -8993,7 +9569,10 @@ function registerLP(parent, getOpts, makeExecutor2) {
8993
9569
  tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
8994
9570
  tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
8995
9571
  range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
8996
- pool: poolAddr
9572
+ pool: poolAddr,
9573
+ slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
9574
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
9575
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
8997
9576
  });
8998
9577
  process.stderr.write("Step 1/2: Adding liquidity...\n");
8999
9578
  const addResult = await executor.execute(addTx);
@@ -9233,7 +9812,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9233
9812
  note: "Plan output. Run each cli_command sequentially. After the mint step, broadcast mode prints `details.minted_token_id` \u2014 feed that into the next step's --token-id."
9234
9813
  }, getOpts());
9235
9814
  });
9236
- lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").option("--token-a <token>", "First token symbol or address (required for V2/Curve/LB)").option("--token-b <token>", "Second token symbol or address (required for V2/Curve/LB)").option("--liquidity <amount>", "Liquidity amount to remove in wei (required for V2/Curve/LB)").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
9815
+ lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").option("--token-a <token>", "First token symbol or address (required for V2/Curve/LB)").option("--token-b <token>", "Second token symbol or address (required for V2/Curve/LB)").option("--liquidity <amount>", "Liquidity amount to remove in wei (required for V2/Curve/LB)").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on remove (REQUIRED for V3/Algebra/Thena CL \u2014 caller must compute from positions(tokenId) + pool state and apply tolerance).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on remove (REQUIRED for V3/Algebra/Thena CL).").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
9237
9816
  const executor = makeExecutor2();
9238
9817
  const chainName = parent.opts().chain;
9239
9818
  if (!chainName) {
@@ -9255,14 +9834,28 @@ function registerLP(parent, getOpts, makeExecutor2) {
9255
9834
  const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
9256
9835
  const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9257
9836
  const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
9258
- const [tokenX, tokenY] = tokenA2.toLowerCase() < tokenB2.toLowerCase() ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
9259
9837
  const binIds = opts.bins.split(",").map((s) => parseInt(s.trim()));
9260
9838
  const client = createPublicClient23({ transport: http23(rpcUrl) });
9261
- const binStep = await client.readContract({
9262
- address: opts.pool,
9263
- abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
9264
- functionName: "getBinStep"
9265
- });
9839
+ const lbPairOrderAbi = parseAbi30([
9840
+ "function getTokenX() view returns (address)",
9841
+ "function getTokenY() view returns (address)",
9842
+ "function getBinStep() view returns (uint16)"
9843
+ ]);
9844
+ const [poolTokenX, poolTokenY, binStep] = await Promise.all([
9845
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenX" }),
9846
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenY" }),
9847
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getBinStep" })
9848
+ ]);
9849
+ const aIsX = tokenA2.toLowerCase() === poolTokenX.toLowerCase();
9850
+ const bIsX = tokenB2.toLowerCase() === poolTokenX.toLowerCase();
9851
+ const aIsY = tokenA2.toLowerCase() === poolTokenY.toLowerCase();
9852
+ const bIsY = tokenB2.toLowerCase() === poolTokenY.toLowerCase();
9853
+ if (!(aIsX && bIsY || bIsX && aIsY)) {
9854
+ throw new Error(
9855
+ `[${protocol.name}] --token-a/--token-b (${tokenA2}/${tokenB2}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
9856
+ );
9857
+ }
9858
+ const [tokenX, tokenY] = aIsX ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
9266
9859
  let amounts;
9267
9860
  if (opts.amounts) {
9268
9861
  amounts = opts.amounts.split(",").map((s) => BigInt(s.trim()));
@@ -9282,6 +9875,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9282
9875
  gas_estimate: 8e4
9283
9876
  };
9284
9877
  const tx = await lbAdapter.buildRemoveLiquidity({
9878
+ pool: opts.pool,
9285
9879
  tokenX,
9286
9880
  tokenY,
9287
9881
  binStep,
@@ -9394,13 +9988,17 @@ function registerLP(parent, getOpts, makeExecutor2) {
9394
9988
  return;
9395
9989
  }
9396
9990
  const ZERO = "0x0000000000000000000000000000000000000000";
9991
+ const removePoolAddr = opts.pool ? resolvePoolAddress(registry, opts.protocol, opts.pool) : void 0;
9397
9992
  const removeTx = await dexAdapter.buildRemoveLiquidity({
9398
9993
  protocol: protocol.name,
9399
9994
  token_a: tokenA ?? ZERO,
9400
9995
  token_b: tokenB ?? ZERO,
9401
9996
  liquidity: removeLiquidity,
9402
9997
  recipient,
9403
- token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
9998
+ token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0,
9999
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
10000
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0,
10001
+ pool: removePoolAddr
9404
10002
  });
9405
10003
  const removeResult = await executor.execute(removeTx);
9406
10004
  printOutput({ step: "lp_remove", ...removeResult }, getOpts());
@@ -9753,6 +10351,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9753
10351
  // src/commands/lending.ts
9754
10352
  init_dist();
9755
10353
  init_dist2();
10354
+ import { maxUint256 } from "viem";
9756
10355
 
9757
10356
  // src/utils.ts
9758
10357
  init_dist();
@@ -9802,6 +10401,26 @@ function parseBigIntValue(v) {
9802
10401
  }
9803
10402
 
9804
10403
  // src/commands/lending.ts
10404
+ function parseAmount(s) {
10405
+ const lower = s.toLowerCase();
10406
+ if (lower === "max" || lower === "all") return maxUint256;
10407
+ return BigInt(s);
10408
+ }
10409
+ function resolveMarketInput(adapter, raw) {
10410
+ if (!raw) return void 0;
10411
+ if (/^0x[0-9a-fA-F]{64}$/.test(raw)) return raw;
10412
+ if (typeof adapter.resolveMarketIdByName !== "function") {
10413
+ throw new Error(
10414
+ `--market must be a 32-byte hex value; got '${raw}'. This adapter does not support named-market lookup.`
10415
+ );
10416
+ }
10417
+ const id = adapter.resolveMarketIdByName(raw);
10418
+ if (id) return id;
10419
+ const known = adapter.listNamedMarkets?.()?.map((m) => m.name).join(", ") ?? "(none)";
10420
+ throw new Error(
10421
+ `--market '${raw}' is not a 32-byte hex and is not registered on this protocol. Known markets: ${known}.`
10422
+ );
10423
+ }
9805
10424
  function registerLending(parent, getOpts, makeExecutor2) {
9806
10425
  const lending = parent.command("lending").description("Lending operations: supply, borrow, repay, withdraw, rates, position");
9807
10426
  lending.command("rates").description("Show current lending rates").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").action(async (opts) => {
@@ -9824,18 +10443,24 @@ function registerLending(parent, getOpts, makeExecutor2) {
9824
10443
  const position = await adapter.getUserPosition(address);
9825
10444
  printOutput(position, getOpts());
9826
10445
  });
9827
- lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
10446
+ lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei (or 'max')").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
9828
10447
  const executor = makeExecutor2();
9829
10448
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9830
10449
  if (!ctx) return;
9831
10450
  const adapter = createLending(ctx.protocol, ctx.rpcUrl);
9832
10451
  const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
9833
10452
  const onBehalfOf = resolveWallet(opts.onBehalfOf);
9834
- const tx = await adapter.buildSupply({ protocol: ctx.protocol.name, asset, amount: BigInt(opts.amount), on_behalf_of: onBehalfOf });
10453
+ const tx = await adapter.buildSupply({
10454
+ protocol: ctx.protocol.name,
10455
+ asset,
10456
+ amount: parseAmount(opts.amount),
10457
+ on_behalf_of: onBehalfOf,
10458
+ market_id: resolveMarketInput(adapter, opts.market)
10459
+ });
9835
10460
  const result = await executor.execute(tx);
9836
10461
  printOutput(result, getOpts());
9837
10462
  });
9838
- lending.command("borrow").description("Borrow an asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
10463
+ lending.command("borrow").description("Borrow an asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--rate-mode <mode>", "variable or stable", "variable").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
9839
10464
  const executor = makeExecutor2();
9840
10465
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9841
10466
  if (!ctx) return;
@@ -9845,14 +10470,15 @@ function registerLending(parent, getOpts, makeExecutor2) {
9845
10470
  const tx = await adapter.buildBorrow({
9846
10471
  protocol: ctx.protocol.name,
9847
10472
  asset,
9848
- amount: BigInt(opts.amount),
10473
+ amount: parseAmount(opts.amount),
9849
10474
  interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
9850
- on_behalf_of: onBehalfOf
10475
+ on_behalf_of: onBehalfOf,
10476
+ market_id: resolveMarketInput(adapter, opts.market)
9851
10477
  });
9852
10478
  const result = await executor.execute(tx);
9853
10479
  printOutput(result, getOpts());
9854
10480
  });
9855
- lending.command("repay").description("Repay a borrowed asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
10481
+ lending.command("repay").description("Repay a borrowed asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--rate-mode <mode>", "variable or stable", "variable").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
9856
10482
  const executor = makeExecutor2();
9857
10483
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9858
10484
  if (!ctx) return;
@@ -9862,21 +10488,150 @@ function registerLending(parent, getOpts, makeExecutor2) {
9862
10488
  const tx = await adapter.buildRepay({
9863
10489
  protocol: ctx.protocol.name,
9864
10490
  asset,
9865
- amount: BigInt(opts.amount),
10491
+ amount: parseAmount(opts.amount),
9866
10492
  interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
9867
- on_behalf_of: onBehalfOf
10493
+ on_behalf_of: onBehalfOf,
10494
+ market_id: resolveMarketInput(adapter, opts.market)
9868
10495
  });
9869
10496
  const result = await executor.execute(tx);
9870
10497
  printOutput(result, getOpts());
9871
10498
  });
9872
- lending.command("withdraw").description("Withdraw a supplied asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--to <address>", "Recipient address").action(async (opts) => {
10499
+ lending.command("withdraw").description("Withdraw a supplied asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--to <address>", "Recipient address").action(async (opts) => {
9873
10500
  const executor = makeExecutor2();
9874
10501
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9875
10502
  if (!ctx) return;
9876
10503
  const adapter = createLending(ctx.protocol, ctx.rpcUrl);
9877
10504
  const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
9878
10505
  const to = resolveWallet(opts.to);
9879
- const tx = await adapter.buildWithdraw({ protocol: ctx.protocol.name, asset, amount: BigInt(opts.amount), to });
10506
+ const tx = await adapter.buildWithdraw({
10507
+ protocol: ctx.protocol.name,
10508
+ asset,
10509
+ amount: parseAmount(opts.amount),
10510
+ to,
10511
+ market_id: resolveMarketInput(adapter, opts.market)
10512
+ });
10513
+ const result = await executor.execute(tx);
10514
+ printOutput(result, getOpts());
10515
+ });
10516
+ lending.command("toggle-collateral").description("Enable or disable a supplied reserve as collateral (Aave V3 family)").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").option("--enable", "Enable as collateral").option("--disable", "Disable as collateral").action(async (opts) => {
10517
+ const executor = makeExecutor2();
10518
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10519
+ if (!ctx) return;
10520
+ if (!opts.enable && !opts.disable) {
10521
+ printOutput({ error: "must pass either --enable or --disable" }, getOpts());
10522
+ return;
10523
+ }
10524
+ if (opts.enable && opts.disable) {
10525
+ printOutput({ error: "--enable and --disable are mutually exclusive" }, getOpts());
10526
+ return;
10527
+ }
10528
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10529
+ if (typeof adapter.buildSetUseReserveAsCollateral !== "function") {
10530
+ printOutput({
10531
+ error: `[${ctx.protocol.name}] adapter does not implement buildSetUseReserveAsCollateral. Aave V3 forks support this; Compound V2/Morpho Blue use different flows.`
10532
+ }, getOpts());
10533
+ return;
10534
+ }
10535
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10536
+ const tx = await adapter.buildSetUseReserveAsCollateral(asset, !!opts.enable);
10537
+ const result = await executor.execute(tx);
10538
+ printOutput(result, getOpts());
10539
+ });
10540
+ lending.command("set-emode").description("Enroll the user in an Aave V3 efficiency-mode category (0 to opt out)").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--category-id <id>", "eMode category id (0 = opt out)").action(async (opts) => {
10541
+ const executor = makeExecutor2();
10542
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10543
+ if (!ctx) return;
10544
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10545
+ if (typeof adapter.buildSetEMode !== "function") {
10546
+ printOutput({
10547
+ error: `[${ctx.protocol.name}] adapter does not implement buildSetEMode (Aave V3 only)`
10548
+ }, getOpts());
10549
+ return;
10550
+ }
10551
+ const id = parseInt(opts.categoryId, 10);
10552
+ if (!Number.isInteger(id) || id < 0 || id > 255) {
10553
+ printOutput({ error: `--category-id must be an integer in [0, 255], got '${opts.categoryId}'` }, getOpts());
10554
+ return;
10555
+ }
10556
+ const tx = await adapter.buildSetEMode(id);
10557
+ const result = await executor.execute(tx);
10558
+ printOutput(result, getOpts());
10559
+ });
10560
+ lending.command("enter-markets").description("Compound V2 (Venus): enter supplied assets as collateral via Comptroller.enterMarkets").requiredOption("--protocol <protocol>", "Protocol slug (must be a Compound V2 fork)").requiredOption("--asset <token>", "Underlying asset symbol or address (resolved to its cToken)").action(async (opts) => {
10561
+ const executor = makeExecutor2();
10562
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10563
+ if (!ctx) return;
10564
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10565
+ if (typeof adapter.buildEnterMarkets !== "function") {
10566
+ printOutput({
10567
+ error: `[${ctx.protocol.name}] adapter does not implement buildEnterMarkets. This is a Compound V2 family operation; Aave V3 uses toggle-collateral instead.`
10568
+ }, getOpts());
10569
+ return;
10570
+ }
10571
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10572
+ const contracts = ctx.protocol.contracts ?? {};
10573
+ const vTokenEntries = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k));
10574
+ if (vTokenEntries.length === 0) {
10575
+ printOutput({ error: `[${ctx.protocol.name}] no vTokens registered in TOML` }, getOpts());
10576
+ return;
10577
+ }
10578
+ const symbol = opts.asset.toLowerCase();
10579
+ const matchedKey = vTokenEntries.find(([k]) => k.toLowerCase() === `v${symbol}`);
10580
+ const vToken = matchedKey ? matchedKey[1] : void 0;
10581
+ if (!vToken) {
10582
+ printOutput({
10583
+ error: `[${ctx.protocol.name}] could not resolve a vToken for '${opts.asset}'. Registered vTokens: ${vTokenEntries.map(([k]) => k).join(", ")}. Pass --asset matching the symbol after the 'v' prefix (e.g. USDT for vusdt).`
10584
+ }, getOpts());
10585
+ return;
10586
+ }
10587
+ void asset;
10588
+ const tx = await adapter.buildEnterMarkets([vToken]);
10589
+ const result = await executor.execute(tx);
10590
+ printOutput(result, getOpts());
10591
+ });
10592
+ lending.command("supply-collateral").description("Supply the collateral side of a Morpho Blue market (different selector from supply)").requiredOption("--protocol <protocol>", "Protocol slug (must be a Morpho Blue adapter)").requiredOption("--asset <token>", "Collateral token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").requiredOption("--market <marketId>", "32-byte Morpho marketId (find via Morpho API)").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
10593
+ const executor = makeExecutor2();
10594
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10595
+ if (!ctx) return;
10596
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10597
+ if (typeof adapter.buildSupplyCollateral !== "function") {
10598
+ printOutput({
10599
+ error: `[${ctx.protocol.name}] adapter does not implement buildSupplyCollateral. Only Morpho Blue forks expose this; Aave V3 / Compound use plain supply.`
10600
+ }, getOpts());
10601
+ return;
10602
+ }
10603
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10604
+ const onBehalfOf = resolveWallet(opts.onBehalfOf);
10605
+ const tx = await adapter.buildSupplyCollateral({
10606
+ protocol: ctx.protocol.name,
10607
+ asset,
10608
+ amount: parseAmount(opts.amount),
10609
+ on_behalf_of: onBehalfOf,
10610
+ market_id: resolveMarketInput(adapter, opts.market)
10611
+ });
10612
+ const result = await executor.execute(tx);
10613
+ printOutput(result, getOpts());
10614
+ });
10615
+ lending.command("withdraw-collateral").description("Withdraw the collateral side of a Morpho Blue market").requiredOption("--protocol <protocol>", "Protocol slug (must be a Morpho Blue adapter)").requiredOption("--asset <token>", "Collateral token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").requiredOption("--market <marketId>", "32-byte Morpho marketId").option("--to <address>", "Recipient address").action(async (opts) => {
10616
+ const executor = makeExecutor2();
10617
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10618
+ if (!ctx) return;
10619
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10620
+ if (typeof adapter.buildWithdrawCollateral !== "function") {
10621
+ printOutput({
10622
+ error: `[${ctx.protocol.name}] adapter does not implement buildWithdrawCollateral.`
10623
+ }, getOpts());
10624
+ return;
10625
+ }
10626
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10627
+ const to = resolveWallet(opts.to);
10628
+ const tx = await adapter.buildWithdrawCollateral({
10629
+ protocol: ctx.protocol.name,
10630
+ asset,
10631
+ amount: parseAmount(opts.amount),
10632
+ to,
10633
+ market_id: resolveMarketInput(adapter, opts.market)
10634
+ });
9880
10635
  const result = await executor.execute(tx);
9881
10636
  printOutput(result, getOpts());
9882
10637
  });
@@ -11246,7 +12001,7 @@ function registerWallet(parent, getOpts) {
11246
12001
 
11247
12002
  // src/commands/token.ts
11248
12003
  init_dist();
11249
- import { createPublicClient as createPublicClient27, http as http27, maxUint256 } from "viem";
12004
+ import { createPublicClient as createPublicClient27, encodeFunctionData as encodeFunctionData30, http as http27, maxUint256 as maxUint2562, parseAbi as parseAbi34 } from "viem";
11250
12005
  function registerToken(parent, getOpts, makeExecutor2) {
11251
12006
  const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
11252
12007
  token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").option("--owner <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
@@ -11280,7 +12035,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11280
12035
  if (!chainName) return;
11281
12036
  const registry = Registry.loadEmbedded();
11282
12037
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11283
- const amount = opts.amount === "max" ? maxUint256 : BigInt(opts.amount);
12038
+ const amount = opts.amount === "max" ? maxUint2562 : BigInt(opts.amount);
11284
12039
  const tx = buildApprove(tokenAddr, opts.spender, amount);
11285
12040
  const result = await executor.execute(tx);
11286
12041
  printOutput(result, getOpts());
@@ -11305,6 +12060,52 @@ function registerToken(parent, getOpts, makeExecutor2) {
11305
12060
  });
11306
12061
  printOutput({ token: tokenAddr, owner, spender: opts.spender, allowance }, getOpts());
11307
12062
  });
12063
+ const WETH9_ABI = parseAbi34([
12064
+ "function deposit() payable",
12065
+ "function withdraw(uint256 amount)"
12066
+ ]);
12067
+ token.command("wrap").description("Wrap native gas token into its ERC-20 wrapped form (WrappedNative.deposit())").requiredOption("--amount <amount>", "Amount of native token to wrap (in wei)").action(async (opts) => {
12068
+ const executor = makeExecutor2();
12069
+ const chainName = requireChain(parent, getOpts);
12070
+ if (!chainName) return;
12071
+ const registry = Registry.loadEmbedded();
12072
+ const chain = registry.getChain(chainName);
12073
+ if (!chain.wrapped_native) {
12074
+ printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
12075
+ return;
12076
+ }
12077
+ const amount = BigInt(opts.amount);
12078
+ const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "deposit" });
12079
+ const result = await executor.execute({
12080
+ description: `[${chainName}] Wrap ${opts.amount} ${chain.native_token} \u2192 W${chain.native_token}`,
12081
+ to: chain.wrapped_native,
12082
+ data,
12083
+ value: amount,
12084
+ gas_estimate: 8e4
12085
+ });
12086
+ printOutput(result, getOpts());
12087
+ });
12088
+ token.command("unwrap").description("Unwrap wrapped-native ERC-20 back into native gas token (WrappedNative.withdraw(amount))").requiredOption("--amount <amount>", "Amount of wrapped token to unwrap (in wei)").action(async (opts) => {
12089
+ const executor = makeExecutor2();
12090
+ const chainName = requireChain(parent, getOpts);
12091
+ if (!chainName) return;
12092
+ const registry = Registry.loadEmbedded();
12093
+ const chain = registry.getChain(chainName);
12094
+ if (!chain.wrapped_native) {
12095
+ printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
12096
+ return;
12097
+ }
12098
+ const amount = BigInt(opts.amount);
12099
+ const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "withdraw", args: [amount] });
12100
+ const result = await executor.execute({
12101
+ description: `[${chainName}] Unwrap ${opts.amount} W${chain.native_token} \u2192 ${chain.native_token}`,
12102
+ to: chain.wrapped_native,
12103
+ data,
12104
+ value: 0n,
12105
+ gas_estimate: 8e4
12106
+ });
12107
+ printOutput(result, getOpts());
12108
+ });
11308
12109
  token.command("transfer").description("Transfer tokens to an address").requiredOption("--token <token>", "Token symbol or address").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Amount to transfer (in wei)").action(async (opts) => {
11309
12110
  const executor = makeExecutor2();
11310
12111
  const chainName = requireChain(parent, getOpts);
@@ -11319,9 +12120,50 @@ function registerToken(parent, getOpts, makeExecutor2) {
11319
12120
 
11320
12121
  // src/commands/bridge.ts
11321
12122
  init_dist();
12123
+ function resolveDestExecutor(slug, broadcast) {
12124
+ const registry = Registry.loadEmbedded();
12125
+ try {
12126
+ const c = registry.getChain(slug);
12127
+ return new Executor(broadcast, c.effectiveRpcUrl(), c.explorer_url, c.viemChain());
12128
+ } catch {
12129
+ const envKey = `${slug.toUpperCase()}_RPC_URL`;
12130
+ const envVal = process.env[envKey];
12131
+ const meta = DEST_CHAIN_META[slug];
12132
+ if (!meta) throw new Error(`Cannot resolve destination chain: ${slug}`);
12133
+ const rpc = envVal ?? DEST_RPC_FALLBACKS[slug];
12134
+ if (!rpc) throw new Error(`No RPC URL for ${slug}. Set ${envKey} or use a registered chain.`);
12135
+ const minimal = {
12136
+ id: meta.chain_id,
12137
+ name: meta.name,
12138
+ nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
12139
+ rpcUrls: { default: { http: [rpc] } }
12140
+ };
12141
+ return new Executor(broadcast, rpc, void 0, minimal);
12142
+ }
12143
+ }
12144
+ async function pollCctpAttestation(srcDomain, burnTxHash, maxSeconds) {
12145
+ const intervalMs = 5e3;
12146
+ const deadline = Date.now() + maxSeconds * 1e3;
12147
+ while (Date.now() < deadline) {
12148
+ try {
12149
+ const res = await fetch(`https://iris-api.circle.com/v2/messages/${srcDomain}?transactionHash=${burnTxHash}`);
12150
+ if (res.ok) {
12151
+ const data = await res.json();
12152
+ const m = data.messages?.[0];
12153
+ if (m && m.status === "complete" && m.attestation && m.attestation !== "PENDING") {
12154
+ return { message: m.message, attestation: m.attestation };
12155
+ }
12156
+ }
12157
+ } catch {
12158
+ }
12159
+ await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
12160
+ }
12161
+ throw new Error(`CCTP attestation timeout after ${maxSeconds}s for tx ${burnTxHash}`);
12162
+ }
11322
12163
  var LIFI_API = "https://li.quest/v1";
11323
12164
  var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
11324
12165
  var CCTP_FEE_API = "https://iris-api.circle.com/v2/burn/USDC/fees";
12166
+ var RELAY_API = "https://api.relay.link";
11325
12167
  var DEST_CHAIN_META = {
11326
12168
  ethereum: { chain_id: 1, name: "Ethereum" },
11327
12169
  optimism: { chain_id: 10, name: "Optimism" },
@@ -11393,6 +12235,63 @@ async function getDebridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amou
11393
12235
  raw: createData
11394
12236
  };
11395
12237
  }
12238
+ async function getRelayBridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amountRaw, user, recipient) {
12239
+ const body = {
12240
+ user,
12241
+ recipient,
12242
+ originChainId: srcChainId,
12243
+ destinationChainId: dstChainId,
12244
+ originCurrency: srcToken,
12245
+ destinationCurrency: dstToken,
12246
+ tradeType: "EXACT_INPUT",
12247
+ amount: amountRaw
12248
+ };
12249
+ const res = await fetch(`${RELAY_API}/quote`, {
12250
+ method: "POST",
12251
+ headers: { "content-type": "application/json" },
12252
+ body: JSON.stringify(body)
12253
+ });
12254
+ if (!res.ok) {
12255
+ const text = await res.text();
12256
+ let code = "";
12257
+ let msg = text;
12258
+ try {
12259
+ const j = JSON.parse(text);
12260
+ code = String(j.errorCode ?? j.code ?? "");
12261
+ msg = String(j.message ?? text);
12262
+ } catch {
12263
+ }
12264
+ const hints = {
12265
+ AMOUNT_TOO_LOW: "Try a larger amount or use --provider lifi (Relay enforces a per-route fee floor)",
12266
+ INVALID_INPUT_CURRENCY: "Source token unsupported by Relay on this chain \u2014 try --provider lifi",
12267
+ INVALID_OUTPUT_CURRENCY: "Destination token unsupported by Relay on this chain \u2014 try --provider lifi or change --token",
12268
+ NO_QUOTES: "No Relay liquidity for this route right now \u2014 try --provider lifi or retry later",
12269
+ AMOUNT_TOO_HIGH: "Amount exceeds Relay per-tx cap \u2014 split into smaller amounts",
12270
+ UNSUPPORTED_CHAIN: "Relay does not support this origin/destination chain \u2014 use --provider lifi"
12271
+ };
12272
+ const hint = hints[code];
12273
+ const detail = code ? `${code}: ${msg}` : msg;
12274
+ throw new Error(`Relay quote failed (${res.status}): ${detail}${hint ? ` \u2014 ${hint}` : ""}`);
12275
+ }
12276
+ const json = await res.json();
12277
+ const steps = json.steps;
12278
+ const swapStep = steps?.find((s) => s.id !== "approve") ?? steps?.[steps?.length ?? 1 - 1];
12279
+ const items = swapStep?.items;
12280
+ const txData = items?.[0]?.data;
12281
+ if (!txData) throw new Error("Relay: no executable step in cross-chain quote");
12282
+ const details = json.details;
12283
+ const currencyOut = details?.currencyOut;
12284
+ const timeEst = details?.timeEstimate ?? 30;
12285
+ return {
12286
+ to: String(txData.to),
12287
+ data: String(txData.data),
12288
+ value: String(txData.value ?? "0x0"),
12289
+ amountOut: String(currencyOut?.amount ?? "0"),
12290
+ estimatedTime: timeEst,
12291
+ tool: String(swapStep?.id ?? "relay"),
12292
+ raw: json
12293
+ };
12294
+ }
11396
12295
  var CCTP_DOMAINS = {
11397
12296
  ethereum: 0,
11398
12297
  avalanche: 1,
@@ -11405,6 +12304,16 @@ var CCTP_DOMAINS = {
11405
12304
  aptos: 9
11406
12305
  };
11407
12306
  var CCTP_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
12307
+ var CCTP_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
12308
+ var DEST_RPC_FALLBACKS = {
12309
+ ethereum: "https://eth.merkle.io",
12310
+ arbitrum: "https://arbitrum.drpc.org",
12311
+ optimism: "https://optimism.drpc.org",
12312
+ polygon: "https://polygon.drpc.org",
12313
+ avalanche: "https://avalanche.drpc.org",
12314
+ linea: "https://linea.drpc.org",
12315
+ zksync: "https://zksync.drpc.org"
12316
+ };
11408
12317
  var CCTP_USDC_ADDRESSES = {
11409
12318
  ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
11410
12319
  avalanche: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
@@ -11448,8 +12357,8 @@ async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
11448
12357
  }
11449
12358
  return { fee: 0.25, maxFeeSubunits: 250000n };
11450
12359
  }
11451
- function registerBridge(parent, getOpts) {
11452
- parent.command("bridge").description("Cross-chain bridge: move assets between chains").requiredOption("--token <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").requiredOption("--to-chain <chain>", "Destination chain name").option("--recipient <address>", "Recipient address on destination chain").option("--slippage <bps>", "Slippage in bps (LI.FI only)", "50").option("--provider <name>", "Bridge provider: lifi, debridge, cctp", "lifi").action(async (opts) => {
12360
+ function registerBridge(parent, getOpts, makeExecutor2) {
12361
+ parent.command("bridge").description("Cross-chain bridge: move assets between chains").requiredOption("--token <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").requiredOption("--to-chain <chain>", "Destination chain name").option("--recipient <address>", "Recipient address on destination chain").option("--slippage <bps>", "Slippage in bps (LI.FI only)", "50").option("--provider <name>", "Bridge provider: lifi, relay, debridge, cctp", "lifi").option("--auto-receive", "(CCTP only) After burn, poll Circle attestation and auto-call MessageTransmitter.receiveMessage on destination", false).option("--receive-timeout <seconds>", "(CCTP --auto-receive) max seconds to wait for attestation", "1200").action(async (opts) => {
11453
12362
  const chainName = requireChain(parent, getOpts);
11454
12363
  if (!chainName) return;
11455
12364
  const registry = Registry.loadEmbedded();
@@ -11462,8 +12371,55 @@ function registerBridge(parent, getOpts) {
11462
12371
  return;
11463
12372
  }
11464
12373
  const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
12374
+ let dstTokenAddr = tokenAddr;
12375
+ if (!opts.token.startsWith("0x")) {
12376
+ try {
12377
+ dstTokenAddr = registry.resolveToken(opts.toChain, opts.token).address;
12378
+ } catch {
12379
+ }
12380
+ }
11465
12381
  const recipient = resolveWallet(opts.recipient);
11466
12382
  const provider = opts.provider.toLowerCase();
12383
+ if (provider === "relay") {
12384
+ try {
12385
+ const result = await getRelayBridgeQuote(
12386
+ fromChain.chain_id,
12387
+ toChain.chain_id,
12388
+ tokenAddr,
12389
+ dstTokenAddr,
12390
+ opts.amount,
12391
+ recipient,
12392
+ recipient
12393
+ );
12394
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12395
+ const executor = makeExecutor2();
12396
+ const approvals = isNative ? [] : [{
12397
+ token: tokenAddr,
12398
+ spender: result.to,
12399
+ amount: BigInt(opts.amount)
12400
+ }];
12401
+ const action = await executor.execute({
12402
+ to: result.to,
12403
+ data: result.data,
12404
+ value: BigInt(result.value || "0"),
12405
+ description: `Relay bridge ${fromChain.name} \u2192 ${toChain.name}`,
12406
+ approvals
12407
+ });
12408
+ printOutput({
12409
+ from_chain: fromChain.name,
12410
+ to_chain: toChain.name,
12411
+ token: tokenAddr,
12412
+ amount: opts.amount,
12413
+ bridge: `Relay (${result.tool})`,
12414
+ estimated_output: result.amountOut,
12415
+ estimated_time_seconds: result.estimatedTime,
12416
+ action
12417
+ }, getOpts());
12418
+ } catch (e) {
12419
+ printOutput({ error: `Relay API error: ${errMsg(e)}` }, getOpts());
12420
+ }
12421
+ return;
12422
+ }
11467
12423
  if (provider === "debridge") {
11468
12424
  try {
11469
12425
  const srcId = DLN_CHAIN_IDS[chainName] ?? fromChain.chain_id;
@@ -11472,11 +12428,29 @@ function registerBridge(parent, getOpts) {
11472
12428
  srcId,
11473
12429
  dstId,
11474
12430
  tokenAddr,
11475
- tokenAddr,
12431
+ dstTokenAddr,
11476
12432
  opts.amount,
11477
12433
  recipient
11478
12434
  );
11479
12435
  const tx = result.raw.tx;
12436
+ if (!tx?.to || !tx?.data) {
12437
+ printOutput({ error: "deBridge: API did not return a tx envelope" }, getOpts());
12438
+ return;
12439
+ }
12440
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12441
+ const executor = makeExecutor2();
12442
+ const approvals = isNative ? [] : [{
12443
+ token: tokenAddr,
12444
+ spender: tx.to,
12445
+ amount: BigInt(opts.amount)
12446
+ }];
12447
+ const action = await executor.execute({
12448
+ to: tx.to,
12449
+ data: tx.data,
12450
+ value: BigInt(tx.value ?? "0"),
12451
+ description: `deBridge DLN ${fromChain.name} \u2192 ${toChain.name}`,
12452
+ approvals
12453
+ });
11480
12454
  printOutput({
11481
12455
  from_chain: fromChain.name,
11482
12456
  to_chain: toChain.name,
@@ -11485,7 +12459,7 @@ function registerBridge(parent, getOpts) {
11485
12459
  bridge: "deBridge DLN",
11486
12460
  estimated_output: result.amountOut,
11487
12461
  estimated_time_seconds: result.estimatedTime,
11488
- tx: tx ? { to: tx.to, data: tx.data, value: tx.value } : void 0
12462
+ action
11489
12463
  }, getOpts());
11490
12464
  } catch (e) {
11491
12465
  printOutput({ error: `deBridge API error: ${errMsg(e)}` }, getOpts());
@@ -11518,11 +12492,11 @@ function registerBridge(parent, getOpts) {
11518
12492
  return;
11519
12493
  }
11520
12494
  const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
11521
- const { encodeFunctionData: encodeFunctionData31, parseAbi: parseAbi35 } = await import("viem");
11522
- const tokenMessengerAbi = parseAbi35([
12495
+ const { encodeFunctionData: encodeFunctionData33, parseAbi: parseAbi36 } = await import("viem");
12496
+ const tokenMessengerAbi = parseAbi36([
11523
12497
  "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) external returns (uint64 nonce)"
11524
12498
  ]);
11525
- const data = encodeFunctionData31({
12499
+ const data = encodeFunctionData33({
11526
12500
  abi: tokenMessengerAbi,
11527
12501
  functionName: "depositForBurn",
11528
12502
  args: [
@@ -11537,6 +12511,48 @@ function registerBridge(parent, getOpts) {
11537
12511
  // standard finality
11538
12512
  ]
11539
12513
  });
12514
+ const executor = makeExecutor2();
12515
+ const burnAction = await executor.execute({
12516
+ to: CCTP_TOKEN_MESSENGER_V2,
12517
+ data,
12518
+ value: 0n,
12519
+ description: `CCTP burn ${fromChain.name} \u2192 ${toChain.name}`,
12520
+ approvals: [{
12521
+ token: usdcSrc,
12522
+ spender: CCTP_TOKEN_MESSENGER_V2,
12523
+ amount: BigInt(opts.amount)
12524
+ }]
12525
+ });
12526
+ let receiveAction = void 0;
12527
+ if (opts.autoReceive && burnAction.tx_hash && burnAction.status === "confirmed") {
12528
+ try {
12529
+ const timeoutSec = parseInt(opts.receiveTimeout) || 1200;
12530
+ process.stderr.write(`Polling Circle attestation for ${burnAction.tx_hash} (max ${timeoutSec}s)...
12531
+ `);
12532
+ const { message, attestation } = await pollCctpAttestation(srcDomain, burnAction.tx_hash, timeoutSec);
12533
+ process.stderr.write(`Attestation ready. Calling receiveMessage on ${opts.toChain}...
12534
+ `);
12535
+ const { encodeFunctionData: encReceive, parseAbi: parseAbiReceive } = await import("viem");
12536
+ const receiveAbi = parseAbiReceive([
12537
+ "function receiveMessage(bytes message, bytes attestation) external"
12538
+ ]);
12539
+ const receiveData = encReceive({
12540
+ abi: receiveAbi,
12541
+ functionName: "receiveMessage",
12542
+ args: [message, attestation]
12543
+ });
12544
+ const destExecutor = resolveDestExecutor(opts.toChain, !!parent.opts().broadcast);
12545
+ receiveAction = await destExecutor.execute({
12546
+ to: CCTP_MESSAGE_TRANSMITTER_V2,
12547
+ data: receiveData,
12548
+ value: 0n,
12549
+ description: `CCTP receive on ${toChain.name}`,
12550
+ approvals: []
12551
+ });
12552
+ } catch (e) {
12553
+ receiveAction = { error: `auto-receive failed: ${errMsg(e)}` };
12554
+ }
12555
+ }
11540
12556
  printOutput({
11541
12557
  from_chain: fromChain.name,
11542
12558
  to_chain: toChain.name,
@@ -11546,12 +12562,9 @@ function registerBridge(parent, getOpts) {
11546
12562
  bridge: "Circle CCTP V2",
11547
12563
  estimated_fee_usdc: fee,
11548
12564
  estimated_output: String(BigInt(opts.amount) - maxFeeSubunits),
11549
- note: "After burn, poll https://iris-api.circle.com/v2/messages/{srcDomain} for attestation, then call MessageTransmitter.receiveMessage() on destination",
11550
- tx: {
11551
- to: CCTP_TOKEN_MESSENGER_V2,
11552
- data,
11553
- value: "0x0"
11554
- }
12565
+ burn: burnAction,
12566
+ receive: receiveAction,
12567
+ note: opts.autoReceive ? void 0 : "Pass --auto-receive to poll Circle attestation and auto-call MessageTransmitter.receiveMessage on the destination chain."
11555
12568
  }, getOpts());
11556
12569
  } catch (e) {
11557
12570
  printOutput({ error: `CCTP error: ${errMsg(e)}` }, getOpts());
@@ -11563,26 +12576,41 @@ function registerBridge(parent, getOpts) {
11563
12576
  fromChain: String(fromChain.chain_id),
11564
12577
  toChain: String(toChain.chain_id),
11565
12578
  fromToken: tokenAddr,
11566
- toToken: tokenAddr,
12579
+ toToken: dstTokenAddr,
11567
12580
  fromAmount: opts.amount,
11568
12581
  fromAddress: recipient,
11569
12582
  slippage: String(parseInt(opts.slippage) / 1e4)
11570
12583
  });
11571
12584
  const res = await fetch(`${LIFI_API}/quote?${params}`);
11572
12585
  const quote = await res.json();
11573
- if (quote.transactionRequest) {
11574
- printOutput({
11575
- from_chain: fromChain.name,
11576
- to_chain: toChain.name,
11577
- token: tokenAddr,
11578
- amount: opts.amount,
11579
- bridge: quote.toolDetails?.name ?? "LI.FI",
11580
- estimated_output: quote.estimate?.toAmount,
11581
- tx: { to: quote.transactionRequest.to, data: quote.transactionRequest.data, value: quote.transactionRequest.value }
11582
- }, getOpts());
11583
- } else {
12586
+ if (!quote.transactionRequest) {
11584
12587
  printOutput({ error: "No LI.FI route found", details: quote }, getOpts());
12588
+ return;
11585
12589
  }
12590
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12591
+ const spender = quote.estimate?.approvalAddress ?? quote.transactionRequest.to;
12592
+ const executor = makeExecutor2();
12593
+ const approvals = isNative ? [] : [{
12594
+ token: tokenAddr,
12595
+ spender,
12596
+ amount: BigInt(opts.amount)
12597
+ }];
12598
+ const action = await executor.execute({
12599
+ to: quote.transactionRequest.to,
12600
+ data: quote.transactionRequest.data,
12601
+ value: BigInt(quote.transactionRequest.value ?? "0"),
12602
+ description: `LI.FI ${fromChain.name} \u2192 ${toChain.name}`,
12603
+ approvals
12604
+ });
12605
+ printOutput({
12606
+ from_chain: fromChain.name,
12607
+ to_chain: toChain.name,
12608
+ token: tokenAddr,
12609
+ amount: opts.amount,
12610
+ bridge: quote.toolDetails?.name ?? "LI.FI",
12611
+ estimated_output: quote.estimate?.toAmount,
12612
+ action
12613
+ }, getOpts());
11586
12614
  } catch (e) {
11587
12615
  printOutput({ error: `LI.FI API error: ${errMsg(e)}` }, getOpts());
11588
12616
  }
@@ -11670,7 +12698,7 @@ async function lifiQuote(chainId, fromToken, toToken, fromAmount, fromAddress, s
11670
12698
  outAmount: String(estimate?.toAmount ?? "0")
11671
12699
  };
11672
12700
  }
11673
- var RELAY_API = "https://api.relay.link";
12701
+ var RELAY_API2 = "https://api.relay.link";
11674
12702
  async function relayQuote(chainId, fromToken, toToken, amount, user) {
11675
12703
  const body = {
11676
12704
  user,
@@ -11682,7 +12710,7 @@ async function relayQuote(chainId, fromToken, toToken, amount, user) {
11682
12710
  tradeType: "EXACT_INPUT",
11683
12711
  amount
11684
12712
  };
11685
- const res = await fetch(`${RELAY_API}/quote`, {
12713
+ const res = await fetch(`${RELAY_API2}/quote`, {
11686
12714
  method: "POST",
11687
12715
  headers: { "content-type": "application/json" },
11688
12716
  body: JSON.stringify(body)
@@ -11722,7 +12750,7 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
11722
12750
  };
11723
12751
  }
11724
12752
  function registerSwap(parent, getOpts, makeExecutor2) {
11725
- parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap, LI.FI, Relay)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid, lifi, relay", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "50").action(async (opts) => {
12753
+ parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap, LI.FI, Relay)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid, lifi, relay", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "100").action(async (opts) => {
11726
12754
  const executor = makeExecutor2();
11727
12755
  const chainName = requireChain(parent, getOpts);
11728
12756
  if (!chainName) return;
@@ -11751,12 +12779,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
11751
12779
  wallet,
11752
12780
  slippageBps
11753
12781
  );
12782
+ const fromLowerKyber = fromAddr.toLowerCase();
12783
+ const isNativeInputKyber = fromLowerKyber === "0x0000000000000000000000000000000000000000" || fromLowerKyber === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
11754
12784
  const tx = {
11755
12785
  description: `KyberSwap: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
11756
12786
  to: txData.to,
11757
12787
  data: txData.data,
11758
- value: parseBigIntValue(txData.value),
11759
- approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }]
12788
+ value: isNativeInputKyber ? BigInt(opts.amount) : parseBigIntValue(txData.value),
12789
+ ...isNativeInputKyber ? {} : { approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }] }
11760
12790
  };
11761
12791
  const result = await executor.execute(tx);
11762
12792
  printOutput({
@@ -12323,7 +13353,8 @@ function makeExecutor() {
12323
13353
  process.exit(1);
12324
13354
  }
12325
13355
  const chain = registry.getChain(opts.chain);
12326
- return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url);
13356
+ const viemChain = chain.viemChain();
13357
+ return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url, viemChain);
12327
13358
  }
12328
13359
  registerStatus(program, getOutputMode);
12329
13360
  registerSchema(program, getOutputMode);
@@ -12334,7 +13365,7 @@ registerPortfolio(program, getOutputMode);
12334
13365
  registerPrice(program, getOutputMode);
12335
13366
  registerWallet(program, getOutputMode);
12336
13367
  registerToken(program, getOutputMode, makeExecutor);
12337
- registerBridge(program, getOutputMode);
13368
+ registerBridge(program, getOutputMode, makeExecutor);
12338
13369
  registerSwap(program, getOutputMode, makeExecutor);
12339
13370
  registerSetup(program);
12340
13371
  registerOws(program, getOutputMode);
@@ -12342,7 +13373,7 @@ registerOws(program, getOutputMode);
12342
13373
  // src/landing.ts
12343
13374
  init_dist();
12344
13375
  import pc4 from "picocolors";
12345
- import { encodeFunctionData as encodeFunctionData30, parseAbi as parseAbi34, formatUnits } from "viem";
13376
+ import { encodeFunctionData as encodeFunctionData31, parseAbi as parseAbi35, formatUnits } from "viem";
12346
13377
  var DASHBOARD_CHAINS = [
12347
13378
  { slug: "hyperevm", tokens: ["HYPE", "WHYPE", "USDC", "USDT0", "USDe", "kHYPE", "wstHYPE"] },
12348
13379
  { slug: "mantle", tokens: ["MNT", "WMNT", "USDC", "USDT", "WETH", "mETH"] },
@@ -12350,10 +13381,10 @@ var DASHBOARD_CHAINS = [
12350
13381
  { slug: "bnb", tokens: ["BNB", "WBNB", "USDT", "USDC", "BUSD", "CAKE"] },
12351
13382
  { slug: "monad", tokens: ["MON", "WMON", "USDC", "USDT0", "WETH", "WBTC"] }
12352
13383
  ];
12353
- var balanceOfAbi = parseAbi34([
13384
+ var balanceOfAbi = parseAbi35([
12354
13385
  "function balanceOf(address account) view returns (uint256)"
12355
13386
  ]);
12356
- var getEthBalanceAbi = parseAbi34([
13387
+ var getEthBalanceAbi = parseAbi35([
12357
13388
  "function getEthBalance(address addr) view returns (uint256)"
12358
13389
  ]);
12359
13390
  async function fetchBalances(rpcUrl, wallet, tokens) {
@@ -12362,12 +13393,12 @@ async function fetchBalances(rpcUrl, wallet, tokens) {
12362
13393
  if (isNative) {
12363
13394
  return [
12364
13395
  MULTICALL3_ADDRESS,
12365
- encodeFunctionData30({ abi: getEthBalanceAbi, functionName: "getEthBalance", args: [wallet] })
13396
+ encodeFunctionData31({ abi: getEthBalanceAbi, functionName: "getEthBalance", args: [wallet] })
12366
13397
  ];
12367
13398
  }
12368
13399
  return [
12369
13400
  t.address,
12370
- encodeFunctionData30({ abi: balanceOfAbi, functionName: "balanceOf", args: [wallet] })
13401
+ encodeFunctionData31({ abi: balanceOfAbi, functionName: "balanceOf", args: [wallet] })
12371
13402
  ];
12372
13403
  });
12373
13404
  let results;