@hypurrquant/defi-cli 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -336,6 +336,7 @@ var init_dist = __esm({
336
336
  native_token;
337
337
  wrapped_native;
338
338
  multicall3;
339
+ aggregators;
339
340
  effectiveRpcUrl() {
340
341
  const chainEnv = this.name.toUpperCase().replace(/ /g, "_") + "_RPC_URL";
341
342
  return process.env[chainEnv] ?? process.env["HYPEREVM_RPC_URL"] ?? this.rpc_url;
@@ -436,9 +437,9 @@ var init_dist = __esm({
436
437
  getProtocolsByCategory(category) {
437
438
  return this.protocols.filter((p) => p.category === category);
438
439
  }
439
- getProtocolsForChain(chain) {
440
+ getProtocolsForChain(chain, includeUnverified = false) {
440
441
  return this.protocols.filter(
441
- (p) => p.chain.toLowerCase() === chain.toLowerCase()
442
+ (p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false) && p.is_active !== false
442
443
  );
443
444
  }
444
445
  resolveToken(chain, symbol) {
@@ -500,6 +501,7 @@ __export(dist_exports2, {
500
501
  MasterChefAdapter: () => MasterChefAdapter,
501
502
  MerchantMoeLBAdapter: () => MerchantMoeLBAdapter,
502
503
  MorphoBlueAdapter: () => MorphoBlueAdapter,
504
+ NestOffChainAdapter: () => NestOffChainAdapter,
503
505
  PendleAdapter: () => PendleAdapter,
504
506
  RyskAdapter: () => RyskAdapter,
505
507
  SolidlyAdapter: () => SolidlyAdapter,
@@ -518,10 +520,12 @@ __export(dist_exports2, {
518
520
  createLiquidStaking: () => createLiquidStaking,
519
521
  createMasterChef: () => createMasterChef,
520
522
  createMerchantMoeLB: () => createMerchantMoeLB,
523
+ createNestOffChain: () => createNestOffChain,
521
524
  createNft: () => createNft,
522
525
  createOptions: () => createOptions,
523
526
  createOracleFromCdp: () => createOracleFromCdp,
524
527
  createOracleFromLending: () => createOracleFromLending,
528
+ createRewardReader: () => createRewardReader,
525
529
  createVault: () => createVault,
526
530
  createYieldSource: () => createYieldSource
527
531
  });
@@ -762,8 +766,8 @@ function encodeClaimReward(rewardToken, to) {
762
766
  return encodeFunctionData13({
763
767
  abi: farmingCenterAbi,
764
768
  functionName: "claimReward",
765
- args: [rewardToken, to, 2n ** 128n - 1n]
766
- // max uint128
769
+ args: [rewardToken, to, 2n ** 256n - 1n]
770
+ // max uint256 (KittenSwap variant uses uint256 amount, not uint128)
767
771
  });
768
772
  }
769
773
  function encodeMulticall(calls) {
@@ -932,7 +936,7 @@ function createLiquidStaking(entry, rpcUrl) {
932
936
  return new GenericLstAdapter(entry, rpcUrl);
933
937
  }
934
938
  }
935
- function createGauge(entry, rpcUrl) {
939
+ function createGauge(entry, rpcUrl, tokens) {
936
940
  if (entry.interface === "hybra" || entry.contracts?.["gauge_manager"]) {
937
941
  return new HybraGaugeAdapter(entry, rpcUrl);
938
942
  }
@@ -940,7 +944,11 @@ function createGauge(entry, rpcUrl) {
940
944
  case "solidly_v2":
941
945
  case "solidly_cl":
942
946
  case "algebra_v3":
943
- return new SolidlyGaugeAdapter(entry, rpcUrl);
947
+ return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
948
+ // uniswap_v3 with voter = ve(3,3) CL (e.g., Aerodrome Slipstream, Ramses CL)
949
+ case "uniswap_v3":
950
+ if (entry.contracts?.["voter"]) return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
951
+ throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported (no voter contract)`);
944
952
  default:
945
953
  throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported`);
946
954
  }
@@ -1002,6 +1010,46 @@ function createOracleFromCdp(entry, _asset, rpcUrl) {
1002
1010
  function createMerchantMoeLB(entry, rpcUrl) {
1003
1011
  return new MerchantMoeLBAdapter(entry, rpcUrl);
1004
1012
  }
1013
+ function createNestOffChain(entry) {
1014
+ return new NestOffChainAdapter(entry);
1015
+ }
1016
+ function createRewardReader(entry, rpcUrl, tokens) {
1017
+ const strategy = entry.reward_strategy ?? inferRewardStrategy(entry);
1018
+ switch (strategy) {
1019
+ case "off_chain_api":
1020
+ return { kind: "off_chain_api", adapter: new NestOffChainAdapter(entry) };
1021
+ case "on_chain_farming_center":
1022
+ if (!rpcUrl) throw DefiError.invalidParam("createRewardReader: rpcUrl required for on_chain_farming_center");
1023
+ return { kind: "on_chain_farming_center", adapter: createKittenSwapFarming(entry, rpcUrl) };
1024
+ case "on_chain_gauge_tokenid":
1025
+ return { kind: "on_chain_gauge_tokenid", adapter: new HybraGaugeAdapter(entry, rpcUrl) };
1026
+ case "on_chain_gauge":
1027
+ return { kind: "on_chain_gauge", adapter: new SolidlyGaugeAdapter(entry, rpcUrl, tokens) };
1028
+ case "auto_stake":
1029
+ return { kind: "auto_stake", adapter: new SolidlyGaugeAdapter(entry, rpcUrl, tokens) };
1030
+ case "on_chain_masterchef":
1031
+ return { kind: "on_chain_masterchef", adapter: new MasterChefAdapter(entry, rpcUrl) };
1032
+ case "none":
1033
+ return { kind: "none" };
1034
+ default:
1035
+ throw DefiError.unsupported(`Unknown reward_strategy '${strategy}' on '${entry.slug}'`);
1036
+ }
1037
+ }
1038
+ function inferRewardStrategy(entry) {
1039
+ if (entry.interface === "hybra" || entry.contracts?.["gauge_manager"]) {
1040
+ return "on_chain_gauge_tokenid";
1041
+ }
1042
+ if (entry.contracts?.["farming_center"] && entry.contracts?.["eternal_farming"]) {
1043
+ return "on_chain_farming_center";
1044
+ }
1045
+ if (entry.contracts?.["voter"]) {
1046
+ return "on_chain_gauge";
1047
+ }
1048
+ if (entry.contracts?.["master_chef"] || entry.contracts?.["masterChef"]) {
1049
+ return "on_chain_masterchef";
1050
+ }
1051
+ return "none";
1052
+ }
1005
1053
  function createKittenSwapFarming(entry, rpcUrl) {
1006
1054
  const farmingCenter = entry.contracts?.["farming_center"];
1007
1055
  if (!farmingCenter) {
@@ -1016,9 +1064,11 @@ function createKittenSwapFarming(entry, rpcUrl) {
1016
1064
  throw new DefiError("CONTRACT_ERROR", `[${entry.name}] Missing 'position_manager' contract address`);
1017
1065
  }
1018
1066
  const factory = entry.contracts?.["factory"];
1019
- return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory);
1067
+ const rewardToken = entry.contracts?.["reward_token"];
1068
+ const bonusRewardToken = entry.contracts?.["bonus_reward_token"];
1069
+ return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken, bonusRewardToken);
1020
1070
  }
1021
- var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, 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, POOL_ABI, ERC20_ABI, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI2, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI, 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;
1071
+ var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI2, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
1022
1072
  var init_dist2 = __esm({
1023
1073
  "../defi-protocols/dist/index.js"() {
1024
1074
  "use strict";
@@ -1056,6 +1106,7 @@ var init_dist2 = __esm({
1056
1106
  init_dist();
1057
1107
  init_dist();
1058
1108
  init_dist();
1109
+ init_dist();
1059
1110
  DEFAULT_FEE = 3e3;
1060
1111
  swapRouterAbi = parseAbi3([
1061
1112
  "struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
@@ -1071,7 +1122,19 @@ var init_dist2 = __esm({
1071
1122
  ]);
1072
1123
  positionManagerAbi = parseAbi3([
1073
1124
  "struct MintParams { address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
1074
- "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1125
+ "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)",
1126
+ "struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
1127
+ "function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
1128
+ "struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
1129
+ "function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
1130
+ "struct IncreaseLiquidityParams { uint256 tokenId; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
1131
+ "function increaseLiquidity(IncreaseLiquidityParams calldata params) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1)",
1132
+ "function positions(uint256 tokenId) external view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)",
1133
+ "function multicall(bytes[] data) external payable returns (bytes[] memory results)"
1134
+ ]);
1135
+ slipstreamMintAbi = parseAbi3([
1136
+ "struct SlipstreamMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; uint160 sqrtPriceX96; }",
1137
+ "function mint(SlipstreamMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1075
1138
  ]);
1076
1139
  UniswapV3Adapter = class {
1077
1140
  protocolName;
@@ -1094,7 +1157,7 @@ var init_dist2 = __esm({
1094
1157
  this.factory = entry.contracts?.["factory"];
1095
1158
  this.fee = DEFAULT_FEE;
1096
1159
  this.rpcUrl = rpcUrl;
1097
- this.useTickSpacingQuoter = entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
1160
+ this.useTickSpacingQuoter = entry.cl_style === "slipstream" || entry.cl_style === "ramses" || entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
1098
1161
  }
1099
1162
  name() {
1100
1163
  return this.protocolName;
@@ -1270,16 +1333,64 @@ var init_dist2 = __esm({
1270
1333
  const [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
1271
1334
  const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
1272
1335
  const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
1273
- const data = encodeFunctionData3({
1336
+ let thirdField = this.fee;
1337
+ let tickLower = -887220;
1338
+ let tickUpper = 887220;
1339
+ if (params.pool && this.rpcUrl) {
1340
+ const poolAbi2 = parseAbi3([
1341
+ "function fee() view returns (uint24)",
1342
+ "function tickSpacing() view returns (int24)",
1343
+ "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, bool)"
1344
+ ]);
1345
+ const client = createPublicClient2({ transport: http2(this.rpcUrl) });
1346
+ const [poolFee, poolTs, slot0] = await Promise.all([
1347
+ client.readContract({ address: params.pool, abi: poolAbi2, functionName: "fee" }).catch(() => null),
1348
+ client.readContract({ address: params.pool, abi: poolAbi2, functionName: "tickSpacing" }).catch(() => null),
1349
+ client.readContract({ address: params.pool, abi: poolAbi2, functionName: "slot0" }).catch(() => null)
1350
+ ]);
1351
+ if (this.useTickSpacingQuoter && poolTs !== null) {
1352
+ thirdField = poolTs;
1353
+ } else if (poolFee !== null) {
1354
+ thirdField = poolFee;
1355
+ }
1356
+ if (params.range_pct !== void 0 && slot0 && poolTs !== null) {
1357
+ const currentTick = slot0[1];
1358
+ const rangeTicks = Math.floor(params.range_pct * 100);
1359
+ tickLower = Math.floor((currentTick - rangeTicks) / poolTs) * poolTs;
1360
+ tickUpper = Math.ceil((currentTick + rangeTicks) / poolTs) * poolTs;
1361
+ }
1362
+ }
1363
+ if (params.tick_lower !== void 0) tickLower = params.tick_lower;
1364
+ if (params.tick_upper !== void 0) tickUpper = params.tick_upper;
1365
+ const data = this.useTickSpacingQuoter ? encodeFunctionData3({
1366
+ abi: slipstreamMintAbi,
1367
+ functionName: "mint",
1368
+ args: [
1369
+ {
1370
+ token0,
1371
+ token1,
1372
+ tickSpacing: thirdField,
1373
+ tickLower,
1374
+ tickUpper,
1375
+ amount0Desired: amount0,
1376
+ amount1Desired: amount1,
1377
+ amount0Min: 0n,
1378
+ amount1Min: 0n,
1379
+ recipient: params.recipient,
1380
+ deadline: BigInt("18446744073709551615"),
1381
+ sqrtPriceX96: 0n
1382
+ }
1383
+ ]
1384
+ }) : encodeFunctionData3({
1274
1385
  abi: positionManagerAbi,
1275
1386
  functionName: "mint",
1276
1387
  args: [
1277
1388
  {
1278
1389
  token0,
1279
1390
  token1,
1280
- fee: this.fee,
1281
- tickLower: -887220,
1282
- tickUpper: 887220,
1391
+ fee: thirdField,
1392
+ tickLower,
1393
+ tickUpper,
1283
1394
  amount0Desired: amount0,
1284
1395
  amount1Desired: amount1,
1285
1396
  amount0Min: 0n,
@@ -1301,10 +1412,125 @@ var init_dist2 = __esm({
1301
1412
  ]
1302
1413
  };
1303
1414
  }
1304
- async buildRemoveLiquidity(_params) {
1305
- throw DefiError.unsupported(
1306
- `[${this.protocolName}] remove_liquidity requires tokenId \u2014 use NFT position manager directly`
1307
- );
1415
+ async buildRemoveLiquidity(params) {
1416
+ const pm = this.positionManager;
1417
+ if (!pm) {
1418
+ throw DefiError.contractError(
1419
+ `[${this.protocolName}] Missing 'position_manager' for liquidity removal`
1420
+ );
1421
+ }
1422
+ if (!params.token_id) {
1423
+ throw DefiError.invalidParam(
1424
+ `[${this.protocolName}] V3 remove_liquidity requires --token-id (NFT positionId)`
1425
+ );
1426
+ }
1427
+ const tokenId = params.token_id;
1428
+ const liquidity = params.liquidity;
1429
+ const MAX_UINT128 = (1n << 128n) - 1n;
1430
+ const deadline = BigInt("18446744073709551615");
1431
+ const decreaseData = encodeFunctionData3({
1432
+ abi: positionManagerAbi,
1433
+ functionName: "decreaseLiquidity",
1434
+ args: [{ tokenId, liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
1435
+ });
1436
+ const collectData = encodeFunctionData3({
1437
+ abi: positionManagerAbi,
1438
+ functionName: "collect",
1439
+ args: [{ tokenId, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
1440
+ });
1441
+ const data = encodeFunctionData3({
1442
+ abi: positionManagerAbi,
1443
+ functionName: "multicall",
1444
+ args: [[decreaseData, collectData]]
1445
+ });
1446
+ return {
1447
+ description: `[${this.protocolName}] Remove ${liquidity} liquidity from tokenId ${tokenId}`,
1448
+ to: pm,
1449
+ data,
1450
+ value: 0n,
1451
+ gas_estimate: 4e5
1452
+ };
1453
+ }
1454
+ /**
1455
+ * Collect accrued LP trading fees for a CL position via NPM.collect().
1456
+ * Used as the reward path for V3 forks with reward_strategy = "lp_fee_only"
1457
+ * (e.g., HyperSwap V3, Project X — no gauge/emissions, fees are the only reward).
1458
+ */
1459
+ async buildCollectFees(tokenId, recipient) {
1460
+ const pm = this.positionManager;
1461
+ if (!pm) {
1462
+ throw DefiError.contractError(
1463
+ `[${this.protocolName}] Missing 'position_manager' for fee collection`
1464
+ );
1465
+ }
1466
+ const MAX_UINT128 = (1n << 128n) - 1n;
1467
+ const data = encodeFunctionData3({
1468
+ abi: positionManagerAbi,
1469
+ functionName: "collect",
1470
+ args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
1471
+ });
1472
+ return {
1473
+ description: `[${this.protocolName}] Collect LP fees for tokenId ${tokenId}`,
1474
+ to: pm,
1475
+ data,
1476
+ value: 0n,
1477
+ gas_estimate: 2e5
1478
+ };
1479
+ }
1480
+ /**
1481
+ * Compound: collect accrued fees and immediately re-add them as liquidity to the same position.
1482
+ * Flow: static-call collect to learn fee amounts → multicall([collect, increaseLiquidity]) on NPM.
1483
+ * Requires existing token approvals on the NPM (set during initial mint).
1484
+ * v1: V3 fee-only protocols (Project X, HyperSwap V3). Gauge protocols need swap routing first.
1485
+ */
1486
+ async buildCompound(tokenId, recipient, opts) {
1487
+ const pm = this.positionManager;
1488
+ if (!pm) {
1489
+ throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager' for compound`);
1490
+ }
1491
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC required to preview fees");
1492
+ const MAX_UINT128 = (1n << 128n) - 1n;
1493
+ const deadline = BigInt("18446744073709551615");
1494
+ const slippageBps = BigInt(opts?.slippageBps ?? 50);
1495
+ if (slippageBps > 10000n) {
1496
+ throw DefiError.invalidParam(`[${this.protocolName}] slippageBps must be <= 10000 (got ${slippageBps})`);
1497
+ }
1498
+ const client = createPublicClient2({ transport: http2(this.rpcUrl) });
1499
+ const sim = await client.simulateContract({
1500
+ address: pm,
1501
+ abi: positionManagerAbi,
1502
+ functionName: "collect",
1503
+ args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }],
1504
+ account: recipient
1505
+ });
1506
+ const [amount0, amount1] = sim.result;
1507
+ if (amount0 === 0n && amount1 === 0n) {
1508
+ throw DefiError.invalidParam(`[${this.protocolName}] No fees to compound for tokenId ${tokenId}`);
1509
+ }
1510
+ const amount0Min = amount0 * (10000n - slippageBps) / 10000n;
1511
+ const amount1Min = amount1 * (10000n - slippageBps) / 10000n;
1512
+ const collectData = encodeFunctionData3({
1513
+ abi: positionManagerAbi,
1514
+ functionName: "collect",
1515
+ args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
1516
+ });
1517
+ const increaseData = encodeFunctionData3({
1518
+ abi: positionManagerAbi,
1519
+ functionName: "increaseLiquidity",
1520
+ args: [{ tokenId, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, deadline }]
1521
+ });
1522
+ const data = encodeFunctionData3({
1523
+ abi: positionManagerAbi,
1524
+ functionName: "multicall",
1525
+ args: [[collectData, increaseData]]
1526
+ });
1527
+ return {
1528
+ description: `[${this.protocolName}] Compound tokenId ${tokenId}: collect ${amount0}/${amount1} \u2192 increaseLiquidity (slippage ${slippageBps}bps)`,
1529
+ to: pm,
1530
+ data,
1531
+ value: 0n,
1532
+ gas_estimate: 5e5
1533
+ };
1308
1534
  }
1309
1535
  };
1310
1536
  abi = parseAbi22([
@@ -1525,6 +1751,13 @@ var init_dist2 = __esm({
1525
1751
  "struct MintParams { address token0; address token1; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
1526
1752
  "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1527
1753
  ]);
1754
+ algebraSharedPmAbi = parseAbi32([
1755
+ "struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
1756
+ "function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
1757
+ "struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
1758
+ "function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
1759
+ "function multicall(bytes[] data) external payable returns (bytes[] memory results)"
1760
+ ]);
1528
1761
  AlgebraV3Adapter = class {
1529
1762
  protocolName;
1530
1763
  router;
@@ -1720,10 +1953,34 @@ var init_dist2 = __esm({
1720
1953
  approvals
1721
1954
  };
1722
1955
  }
1723
- async buildRemoveLiquidity(_params) {
1724
- throw DefiError.unsupported(
1725
- `[${this.protocolName}] remove_liquidity requires tokenId \u2014 use NFT position manager directly`
1726
- );
1956
+ async buildRemoveLiquidity(params) {
1957
+ const pm = this.positionManager;
1958
+ if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
1959
+ if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
1960
+ const MAX_UINT128 = (1n << 128n) - 1n;
1961
+ const deadline = BigInt("18446744073709551615");
1962
+ const decreaseData = encodeFunctionData32({
1963
+ abi: algebraSharedPmAbi,
1964
+ functionName: "decreaseLiquidity",
1965
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
1966
+ });
1967
+ const collectData = encodeFunctionData32({
1968
+ abi: algebraSharedPmAbi,
1969
+ functionName: "collect",
1970
+ args: [{ tokenId: params.token_id, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
1971
+ });
1972
+ const data = encodeFunctionData32({
1973
+ abi: algebraSharedPmAbi,
1974
+ functionName: "multicall",
1975
+ args: [[decreaseData, collectData]]
1976
+ });
1977
+ return {
1978
+ description: `[${this.protocolName}] Remove ${params.liquidity} liquidity from tokenId ${params.token_id}`,
1979
+ to: pm,
1980
+ data,
1981
+ value: 0n,
1982
+ gas_estimate: 4e5
1983
+ };
1727
1984
  }
1728
1985
  };
1729
1986
  abi3 = parseAbi4([
@@ -2011,7 +2268,12 @@ var init_dist2 = __esm({
2011
2268
  };
2012
2269
  thenaPmAbi = parseAbi7([
2013
2270
  "struct MintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; uint160 sqrtPriceX96; }",
2014
- "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
2271
+ "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)",
2272
+ "struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
2273
+ "function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
2274
+ "struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
2275
+ "function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
2276
+ "function multicall(bytes[] data) external payable returns (bytes[] memory results)"
2015
2277
  ]);
2016
2278
  thenaRouterAbi = parseAbi7([
2017
2279
  "struct ExactInputSingleParams { address tokenIn; address tokenOut; int24 tickSpacing; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
@@ -2154,8 +2416,34 @@ var init_dist2 = __esm({
2154
2416
  approvals
2155
2417
  };
2156
2418
  }
2157
- async buildRemoveLiquidity(_params) {
2158
- throw DefiError.unsupported(`[${this.protocolName}] remove_liquidity requires tokenId`);
2419
+ async buildRemoveLiquidity(params) {
2420
+ const pm = this.positionManager;
2421
+ if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
2422
+ if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
2423
+ const MAX_UINT128 = (1n << 128n) - 1n;
2424
+ const deadline = BigInt("18446744073709551615");
2425
+ const decreaseData = encodeFunctionData7({
2426
+ abi: thenaPmAbi,
2427
+ functionName: "decreaseLiquidity",
2428
+ args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
2429
+ });
2430
+ const collectData = encodeFunctionData7({
2431
+ abi: thenaPmAbi,
2432
+ functionName: "collect",
2433
+ args: [{ tokenId: params.token_id, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
2434
+ });
2435
+ const data = encodeFunctionData7({
2436
+ abi: thenaPmAbi,
2437
+ functionName: "multicall",
2438
+ args: [[decreaseData, collectData]]
2439
+ });
2440
+ return {
2441
+ description: `[${this.protocolName}] Remove ${params.liquidity} liquidity from tokenId ${params.token_id}`,
2442
+ to: pm,
2443
+ data,
2444
+ value: 0n,
2445
+ gas_estimate: 4e5
2446
+ };
2159
2447
  }
2160
2448
  };
2161
2449
  _addressDecodeAbi = parseAbi8(["function f() external view returns (address)"]);
@@ -2295,6 +2583,8 @@ var init_dist2 = __esm({
2295
2583
  gauge,
2296
2584
  token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
2297
2585
  token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
2586
+ token0Addr: t0 ?? void 0,
2587
+ token1Addr: t1 ?? void 0,
2298
2588
  type: "CL"
2299
2589
  });
2300
2590
  }
@@ -2332,12 +2622,14 @@ var init_dist2 = __esm({
2332
2622
  pre_txs: [approveTx]
2333
2623
  };
2334
2624
  }
2335
- async buildWithdraw(gauge, _amount, tokenId) {
2625
+ async buildWithdraw(gauge, _amount, tokenId, opts) {
2336
2626
  if (tokenId === void 0) throw new DefiError("CONTRACT_ERROR", "tokenId required for CL gauge withdraw");
2627
+ const redeemType = opts?.redeemType ?? 1;
2628
+ const warning = redeemType === 1 ? " \u2014 WARNING: redeemType=1 locks accumulated rewards into 2-year veHYBR NFT. Use --redeem-type 0 for instant exit (with penalty)." : "";
2337
2629
  return {
2338
- description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge`,
2630
+ description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge (redeemType=${redeemType})${warning}`,
2339
2631
  to: gauge,
2340
- data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId, 1] }),
2632
+ data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId, redeemType] }),
2341
2633
  value: 0n,
2342
2634
  gas_estimate: 1e6
2343
2635
  };
@@ -2346,15 +2638,15 @@ var init_dist2 = __esm({
2346
2638
  async buildClaimRewards(gauge, _account) {
2347
2639
  throw DefiError.unsupported(`[${this.protocolName}] Use buildClaimRewardsByTokenId for CL gauges`);
2348
2640
  }
2349
- async buildClaimRewardsByTokenId(gauge, tokenId) {
2641
+ async buildClaimRewardsByTokenId(gauge, tokenId, opts) {
2642
+ const redeemType = opts?.redeemType ?? 1;
2350
2643
  return {
2351
- description: `[${this.protocolName}] Claim rewards for NFT #${tokenId}`,
2644
+ description: `[${this.protocolName}] Claim rewards for NFT #${tokenId} (redeemType=${redeemType})`,
2352
2645
  to: this.gaugeManager,
2353
2646
  data: encodeFunctionData8({
2354
2647
  abi: gaugeManagerAbi,
2355
2648
  functionName: "claimRewards",
2356
- args: [gauge, [tokenId], 1]
2357
- // redeemType=1
2649
+ args: [gauge, [tokenId], redeemType]
2358
2650
  }),
2359
2651
  value: 0n,
2360
2652
  gas_estimate: 1e6
@@ -2499,14 +2791,21 @@ var init_dist2 = __esm({
2499
2791
  "function getReward(address account) external",
2500
2792
  "function getReward(address account, address[] tokens) external",
2501
2793
  "function getReward(uint256 tokenId) external",
2794
+ // Ramses CL gauge factory (delegate target): tokenId-keyed multi-token claim
2795
+ "function getReward(uint256 tokenId, address[] tokens) external",
2502
2796
  "function earned(address account) external view returns (uint256)",
2797
+ "function earned(address account, uint256 tokenId) external view returns (uint256)",
2503
2798
  "function earned(address token, address account) external view returns (uint256)",
2504
2799
  "function earned(uint256 tokenId) external view returns (uint256)",
2800
+ // Ramses CL: token-first + tokenId (no account; account is ownerOf(tokenId) inferred by gauge)
2801
+ "function earned(address token, uint256 tokenId) external view returns (uint256)",
2505
2802
  "function rewardRate() external view returns (uint256)",
2506
2803
  "function rewardToken() external view returns (address)",
2507
2804
  "function totalSupply() external view returns (uint256)",
2508
2805
  "function rewardsListLength() external view returns (uint256)",
2509
2806
  "function rewardData(address token) external view returns (uint256 periodFinish, uint256 rewardRate, uint256 lastUpdateTime, uint256 rewardPerTokenStored)",
2807
+ // Ramses CL gauge factory exposes the canonical reward-token list
2808
+ "function getRewardTokens() external view returns (address[])",
2510
2809
  "function nonfungiblePositionManager() external view returns (address)"
2511
2810
  ]);
2512
2811
  veAbi2 = parseAbi10([
@@ -2550,7 +2849,12 @@ var init_dist2 = __esm({
2550
2849
  rpcUrl;
2551
2850
  clFactory;
2552
2851
  v2Factory;
2553
- constructor(entry, rpcUrl) {
2852
+ tokens;
2853
+ // CL gauges (Aerodrome Slipstream / Velodrome CL) take an LP NFT via deposit(tokenId);
2854
+ // V2 gauges take an LP token amount via deposit(amount). Detect via uniswap_v3 + position_manager.
2855
+ clNftMode;
2856
+ positionManager;
2857
+ constructor(entry, rpcUrl, tokens) {
2554
2858
  this.protocolName = entry.name;
2555
2859
  const voter = entry.contracts?.["voter"];
2556
2860
  if (!voter) {
@@ -2563,8 +2867,11 @@ var init_dist2 = __esm({
2563
2867
  this.voter = voter;
2564
2868
  this.veToken = veToken;
2565
2869
  this.rpcUrl = rpcUrl;
2870
+ this.tokens = tokens;
2566
2871
  this.clFactory = entry.contracts?.["cl_factory"] ?? entry.contracts?.["factory"];
2567
2872
  this.v2Factory = entry.contracts?.["pair_factory"] ?? entry.contracts?.["factory"];
2873
+ this.positionManager = entry.contracts?.["position_manager"];
2874
+ this.clNftMode = entry.interface === "uniswap_v3" && this.positionManager !== void 0;
2568
2875
  }
2569
2876
  name() {
2570
2877
  return this.protocolName;
@@ -2577,57 +2884,148 @@ var init_dist2 = __esm({
2577
2884
  this._discoverV2GaugedPools(results),
2578
2885
  this._discoverCLGaugedPools(results)
2579
2886
  ]);
2887
+ await this._enrichGaugeMetrics(results);
2580
2888
  return results;
2581
2889
  }
2890
+ /**
2891
+ * Batch query rewardRate, totalSupply, rewardToken for all discovered gauges.
2892
+ * Handles both single-token (rewardRate) and multi-token (rewardData) gauges.
2893
+ */
2894
+ async _enrichGaugeMetrics(pools) {
2895
+ if (!this.rpcUrl || pools.length === 0) return;
2896
+ const _u256Abi = parseAbi10(["function f() view returns (uint256)"]);
2897
+ const calls = [];
2898
+ for (const p of pools) {
2899
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardRate" })]);
2900
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "totalSupply" })]);
2901
+ calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardToken" })]);
2902
+ }
2903
+ const results = await multicallRead(this.rpcUrl, calls).catch(() => []);
2904
+ for (let i = 0; i < pools.length; i++) {
2905
+ const base = i * 3;
2906
+ try {
2907
+ pools[i].rewardRate = results[base] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base] }) : 0n;
2908
+ } catch {
2909
+ pools[i].rewardRate = 0n;
2910
+ }
2911
+ try {
2912
+ pools[i].totalStaked = results[base + 1] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base + 1] }) : 0n;
2913
+ } catch {
2914
+ pools[i].totalStaked = 0n;
2915
+ }
2916
+ try {
2917
+ pools[i].rewardToken = results[base + 2] ? decodeAddress2(results[base + 2]) ?? void 0 : void 0;
2918
+ } catch {
2919
+ }
2920
+ }
2921
+ const KNOWN_REWARD_TOKENS = [
2922
+ "0x555570a286F15EbDFE42B66eDE2f724Aa1AB5555",
2923
+ // xRAM
2924
+ "0x5555555555555555555555555555555555555555",
2925
+ // WHYPE
2926
+ "0x067b0C72aa4C6Bd3BFEFfF443c536DCd6a25a9C8",
2927
+ // HYBR
2928
+ "0x07c57E32a3C29D5659bda1d3EFC2E7BF004E3035"
2929
+ // NEST
2930
+ ];
2931
+ const needsFallback = pools.filter((p) => (p.rewardRate ?? 0n) === 0n && (p.totalStaked ?? 0n) > 0n);
2932
+ if (needsFallback.length === 0) return;
2933
+ const rdCalls = [];
2934
+ const rdMeta = [];
2935
+ for (const p of needsFallback) {
2936
+ const poolIdx = pools.indexOf(p);
2937
+ for (const token of KNOWN_REWARD_TOKENS) {
2938
+ rdCalls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardData", args: [token] })]);
2939
+ rdMeta.push({ poolIdx, token });
2940
+ }
2941
+ }
2942
+ const rdResults = await multicallRead(this.rpcUrl, rdCalls).catch(() => []);
2943
+ const _rdAbi = parseAbi10(["function f() view returns (uint256, uint256, uint256, uint256)"]);
2944
+ for (let i = 0; i < rdMeta.length; i++) {
2945
+ const { poolIdx, token } = rdMeta[i];
2946
+ const pool = pools[poolIdx];
2947
+ if ((pool.rewardRate ?? 0n) > 0n) continue;
2948
+ try {
2949
+ if (!rdResults[i]) continue;
2950
+ const decoded = decodeFunctionResult3({ abi: _rdAbi, functionName: "f", data: rdResults[i] });
2951
+ const [periodFinish, rewardRate] = decoded;
2952
+ const now = BigInt(Math.floor(Date.now() / 1e3));
2953
+ if (rewardRate > 0n && periodFinish > now) {
2954
+ pool.rewardRate = rewardRate;
2955
+ pool.rewardToken = token;
2956
+ }
2957
+ } catch {
2958
+ }
2959
+ }
2960
+ }
2582
2961
  async _discoverV2GaugedPools(out) {
2583
2962
  if (!this.rpcUrl || !this.v2Factory) return;
2584
- const v2FactoryAbi = parseAbi10([
2585
- "function allPairsLength() external view returns (uint256)",
2586
- "function allPairs(uint256) external view returns (address)"
2587
- ]);
2588
2963
  const pairAbi = parseAbi10([
2589
2964
  "function token0() external view returns (address)",
2590
2965
  "function token1() external view returns (address)",
2591
2966
  "function stable() external view returns (bool)"
2592
2967
  ]);
2593
2968
  const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
2969
+ const voterLengthAbi = parseAbi10(["function length() external view returns (uint256)"]);
2970
+ const voterPoolsAbi = parseAbi10(["function pools(uint256) external view returns (address)"]);
2971
+ const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2972
+ const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2973
+ const gaugesAbi = parseAbi10(["function gauges(address) external view returns (address)"]);
2594
2974
  const client = createPublicClient6({ transport: http6(this.rpcUrl) });
2595
- let pairCount;
2975
+ let pairs = [];
2976
+ let voterPoolCount = 0;
2596
2977
  try {
2597
- pairCount = await client.readContract({
2598
- address: this.v2Factory,
2599
- abi: v2FactoryAbi,
2600
- functionName: "allPairsLength"
2601
- });
2978
+ const len = await client.readContract({ address: this.voter, abi: voterLengthAbi, functionName: "length" });
2979
+ voterPoolCount = Number(len);
2602
2980
  } catch {
2603
- return;
2604
2981
  }
2605
- const count = Number(pairCount);
2606
- if (count === 0) return;
2607
- const pairAddressCalls = [];
2608
- for (let i = 0; i < count; i++) {
2609
- pairAddressCalls.push([
2610
- this.v2Factory,
2611
- encodeFunctionData10({ abi: v2FactoryAbi, functionName: "allPairs", args: [BigInt(i)] })
2612
- ]);
2982
+ if (voterPoolCount > 0) {
2983
+ const MAX_SCAN = 500;
2984
+ const startIdx = Math.max(0, voterPoolCount - MAX_SCAN);
2985
+ const poolCalls = [];
2986
+ for (let i = startIdx; i < voterPoolCount; i++) {
2987
+ poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
2988
+ }
2989
+ const poolResults = await multicallRead(this.rpcUrl, poolCalls);
2990
+ pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2991
+ } else {
2992
+ const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
2993
+ const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
2994
+ let pairCount = 0n;
2995
+ let useSolidly = false;
2996
+ try {
2997
+ pairCount = await client.readContract({ address: this.v2Factory, abi: v2FactoryAbi, functionName: "allPairsLength" });
2998
+ } catch {
2999
+ try {
3000
+ pairCount = await client.readContract({ address: this.v2Factory, abi: solidlyFactoryAbi, functionName: "allPoolsLength" });
3001
+ useSolidly = true;
3002
+ } catch {
3003
+ return;
3004
+ }
3005
+ }
3006
+ const count = Number(pairCount);
3007
+ if (count === 0) return;
3008
+ const MAX_SCAN = 500;
3009
+ const startIdx = Math.max(0, count - MAX_SCAN);
3010
+ const fetchAbi = useSolidly ? solidlyFactoryAbi : v2FactoryAbi;
3011
+ const fetchFn = useSolidly ? "allPools" : "allPairs";
3012
+ const pairCalls = [];
3013
+ for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
3014
+ const pairResults = await multicallRead(this.rpcUrl, pairCalls);
3015
+ pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2613
3016
  }
2614
- const pairAddressResults = await multicallRead(this.rpcUrl, pairAddressCalls);
2615
- const pairs = pairAddressResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
2616
3017
  if (pairs.length === 0) return;
2617
- const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2618
- const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2619
- const gaugeCalls = pairs.map((pair) => [
2620
- this.voter,
2621
- encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pair] })
2622
- ]);
3018
+ const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
2623
3019
  let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
2624
- const allNullV2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
2625
- if (allNullV2) {
2626
- const fallbackCalls = pairs.map((pair) => [
2627
- this.voter,
2628
- encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [pair] })
2629
- ]);
2630
- gaugeResults = await multicallRead(this.rpcUrl, fallbackCalls);
3020
+ const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3021
+ if (allNull) {
3022
+ const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
3023
+ gaugeResults = await multicallRead(this.rpcUrl, fb1);
3024
+ }
3025
+ const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
3026
+ if (allNull2) {
3027
+ const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
3028
+ gaugeResults = await multicallRead(this.rpcUrl, fb2);
2631
3029
  }
2632
3030
  const gaugedPairs = [];
2633
3031
  for (let i = 0; i < pairs.length; i++) {
@@ -2672,6 +3070,8 @@ var init_dist2 = __esm({
2672
3070
  gauge,
2673
3071
  token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
2674
3072
  token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
3073
+ token0Addr: t0 ?? void 0,
3074
+ token1Addr: t1 ?? void 0,
2675
3075
  type: "V2",
2676
3076
  stable
2677
3077
  });
@@ -2692,8 +3092,7 @@ var init_dist2 = __esm({
2692
3092
  const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
2693
3093
  const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
2694
3094
  const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
2695
- const tokenEntries = Object.entries(HYPEREVM_TOKENS);
2696
- const tokenAddresses = tokenEntries.map(([, addr]) => addr);
3095
+ const tokenAddresses = this.tokens ?? Object.values(HYPEREVM_TOKENS);
2697
3096
  const pairs = [];
2698
3097
  for (let i = 0; i < tokenAddresses.length; i++) {
2699
3098
  for (let j = i + 1; j < tokenAddresses.length; j++) {
@@ -2798,6 +3197,8 @@ var init_dist2 = __esm({
2798
3197
  gauge,
2799
3198
  token0: symbolMap.get(t0) ?? t0.slice(0, 10),
2800
3199
  token1: symbolMap.get(t1) ?? t1.slice(0, 10),
3200
+ token0Addr: t0,
3201
+ token1Addr: t1,
2801
3202
  type: "CL",
2802
3203
  tickSpacing
2803
3204
  });
@@ -2805,6 +3206,25 @@ var init_dist2 = __esm({
2805
3206
  }
2806
3207
  // IGauge
2807
3208
  async buildDeposit(gauge, amount, tokenId, lpToken) {
3209
+ if (this.clNftMode && tokenId !== void 0 && this.positionManager) {
3210
+ const nftAbi = parseAbi10(["function approve(address to, uint256 tokenId) external"]);
3211
+ const clGaugeAbi = parseAbi10(["function deposit(uint256 tokenId) external"]);
3212
+ const approveTx = {
3213
+ description: `[${this.protocolName}] Approve LP NFT #${tokenId} to gauge`,
3214
+ to: this.positionManager,
3215
+ data: encodeFunctionData10({ abi: nftAbi, functionName: "approve", args: [gauge, tokenId] }),
3216
+ value: 0n,
3217
+ gas_estimate: 8e4
3218
+ };
3219
+ return {
3220
+ description: `[${this.protocolName}] Deposit LP NFT #${tokenId} to CL gauge`,
3221
+ to: gauge,
3222
+ data: encodeFunctionData10({ abi: clGaugeAbi, functionName: "deposit", args: [tokenId] }),
3223
+ value: 0n,
3224
+ gas_estimate: 9e5,
3225
+ pre_txs: [approveTx]
3226
+ };
3227
+ }
2808
3228
  if (tokenId !== void 0) {
2809
3229
  const data2 = encodeFunctionData10({
2810
3230
  abi: gaugeAbi,
@@ -2834,7 +3254,17 @@ var init_dist2 = __esm({
2834
3254
  approvals: lpToken ? [{ token: lpToken, spender: gauge, amount }] : void 0
2835
3255
  };
2836
3256
  }
2837
- async buildWithdraw(gauge, amount) {
3257
+ async buildWithdraw(gauge, amount, tokenId) {
3258
+ if (this.clNftMode && tokenId !== void 0) {
3259
+ const clGaugeAbi = parseAbi10(["function withdraw(uint256 tokenId) external"]);
3260
+ return {
3261
+ description: `[${this.protocolName}] Withdraw LP NFT #${tokenId} from CL gauge`,
3262
+ to: gauge,
3263
+ data: encodeFunctionData10({ abi: clGaugeAbi, functionName: "withdraw", args: [tokenId] }),
3264
+ value: 0n,
3265
+ gas_estimate: 6e5
3266
+ };
3267
+ }
2838
3268
  const data = encodeFunctionData10({
2839
3269
  abi: gaugeAbi,
2840
3270
  functionName: "withdraw",
@@ -2924,16 +3354,16 @@ var init_dist2 = __esm({
2924
3354
  }
2925
3355
  async buildClaimRewards(gauge, account) {
2926
3356
  if (!this.rpcUrl || !account) {
2927
- const data2 = encodeFunctionData10({
3357
+ const data = encodeFunctionData10({
2928
3358
  abi: gaugeAbi,
2929
3359
  functionName: "getReward",
2930
3360
  args: [account ?? zeroAddress6]
2931
3361
  });
2932
- return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data: data2, value: 0n, gas_estimate: 2e5 };
3362
+ return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
2933
3363
  }
2934
3364
  const { tokens, multiToken } = await this.discoverRewardTokens(gauge);
2935
3365
  if (multiToken && tokens.length > 0) {
2936
- const data2 = encodeFunctionData10({
3366
+ const data = encodeFunctionData10({
2937
3367
  abi: gaugeAbi,
2938
3368
  functionName: "getReward",
2939
3369
  args: [account, tokens]
@@ -2941,41 +3371,179 @@ var init_dist2 = __esm({
2941
3371
  return {
2942
3372
  description: `[${this.protocolName}] Claim gauge rewards (${tokens.length} tokens)`,
2943
3373
  to: gauge,
2944
- data: data2,
3374
+ data,
2945
3375
  value: 0n,
2946
3376
  gas_estimate: 3e5
2947
3377
  };
2948
3378
  }
3379
+ const accountVariant = encodeFunctionData10({
3380
+ abi: gaugeAbi,
3381
+ functionName: "getReward",
3382
+ args: [account]
3383
+ });
3384
+ try {
3385
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3386
+ await client.call({ account, to: gauge, data: accountVariant });
3387
+ return {
3388
+ description: `[${this.protocolName}] Claim gauge rewards (getReward(account))`,
3389
+ to: gauge,
3390
+ data: accountVariant,
3391
+ value: 0n,
3392
+ gas_estimate: 2e5
3393
+ };
3394
+ } catch {
3395
+ const noArg = encodeFunctionData10({ abi: gaugeAbi, functionName: "getReward", args: [] });
3396
+ return {
3397
+ description: `[${this.protocolName}] Claim gauge rewards (getReward())`,
3398
+ to: gauge,
3399
+ data: noArg,
3400
+ value: 0n,
3401
+ gas_estimate: 2e5
3402
+ };
3403
+ }
3404
+ }
3405
+ /**
3406
+ * Claim rewards for a CL gauge by NFT tokenId (Hybra V4 style — single-arg getReward(tokenId)).
3407
+ */
3408
+ async buildClaimRewardsByTokenId(gauge, tokenId) {
2949
3409
  const data = encodeFunctionData10({
2950
3410
  abi: gaugeAbi,
2951
3411
  functionName: "getReward",
2952
- args: []
3412
+ args: [tokenId]
2953
3413
  });
2954
3414
  return {
2955
- description: `[${this.protocolName}] Claim gauge rewards`,
3415
+ description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
2956
3416
  to: gauge,
2957
3417
  data,
2958
3418
  value: 0n,
2959
- gas_estimate: 2e5
3419
+ gas_estimate: 3e5
2960
3420
  };
2961
3421
  }
2962
3422
  /**
2963
- * Claim rewards for a CL gauge by NFT tokenId (Hybra V4 style).
3423
+ * Ramses-CL claim via NPM.getPeriodReward the user-facing claim path.
3424
+ * The gauge contract restricts `getReward*` to authorized claimers (voter + NPM only);
3425
+ * EOAs must route through NPM, which calls into the gauge with msg.sender = NPM.
3426
+ *
3427
+ * ABI: getPeriodReward(uint256 period, uint256 tokenId, address[] tokens, address receiver)
3428
+ * `period` defaults to current Solidly weekly epoch index (block.timestamp / 604800).
3429
+ * `tokens` defaults to gauge.getRewardTokens() when `gauge` is provided.
3430
+ *
3431
+ * Verified 2026-04-29 on anvil fork: NPM.getPeriodReward(2938, 177068, [..., xRAM], wallet)
3432
+ * delivered 71.11 xRAM after 1h emission warp; direct gauge.getReward(...) reverts
3433
+ * with NOT_AUTHORIZED_CLAIMER for the same EOA.
2964
3434
  */
2965
- async buildClaimRewardsByTokenId(gauge, tokenId) {
3435
+ async buildClaimRewardsViaNPMPeriodReward(npm, tokenId, receiver, opts) {
3436
+ let rewardTokens = opts?.tokens;
3437
+ if (!rewardTokens || rewardTokens.length === 0) {
3438
+ if (!opts?.gauge) {
3439
+ throw DefiError.invalidParam(
3440
+ "Ramses CL claim requires either `tokens` or `gauge` (for getRewardTokens lookup)"
3441
+ );
3442
+ }
3443
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required to discover reward tokens");
3444
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3445
+ try {
3446
+ rewardTokens = await client.readContract({
3447
+ address: opts.gauge,
3448
+ abi: gaugeAbi,
3449
+ functionName: "getRewardTokens"
3450
+ });
3451
+ } catch {
3452
+ throw DefiError.contractError(
3453
+ `[${this.protocolName}] gauge.getRewardTokens() reverted \u2014 pass tokens[] explicitly`
3454
+ );
3455
+ }
3456
+ }
3457
+ if (rewardTokens.length === 0) {
3458
+ throw DefiError.contractError(`[${this.protocolName}] no reward tokens to claim`);
3459
+ }
3460
+ const epochSeconds = 604800n;
3461
+ const period = opts?.period ?? BigInt(Math.floor(Date.now() / 1e3)) / epochSeconds;
3462
+ const npmClaimAbi = parseAbi10([
3463
+ "function getPeriodReward(uint256 period, uint256 tokenId, address[] tokens, address receiver) external"
3464
+ ]);
3465
+ const data = encodeFunctionData10({
3466
+ abi: npmClaimAbi,
3467
+ functionName: "getPeriodReward",
3468
+ args: [period, tokenId, rewardTokens, receiver]
3469
+ });
3470
+ return {
3471
+ description: `[${this.protocolName}] Claim via NPM.getPeriodReward(period=${period}, tokenId=${tokenId}, ${rewardTokens.length} tokens)`,
3472
+ to: npm,
3473
+ data,
3474
+ value: 0n,
3475
+ gas_estimate: 6e5
3476
+ };
3477
+ }
3478
+ /**
3479
+ * @deprecated Direct gauge.getReward(tokenId, tokens[]) reverts with NOT_AUTHORIZED_CLAIMER
3480
+ * for EOAs on Ramses CL. Use buildClaimRewardsViaNPMPeriodReward instead.
3481
+ */
3482
+ async buildClaimRewardsByCLTokenIdMulti(gauge, tokenId, tokens) {
3483
+ let rewardTokens = tokens;
3484
+ if (!rewardTokens || rewardTokens.length === 0) {
3485
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required to discover reward tokens");
3486
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3487
+ try {
3488
+ rewardTokens = await client.readContract({
3489
+ address: gauge,
3490
+ abi: gaugeAbi,
3491
+ functionName: "getRewardTokens"
3492
+ });
3493
+ } catch {
3494
+ throw DefiError.contractError(
3495
+ `[${this.protocolName}] gauge.getRewardTokens() reverted \u2014 pass tokens[] explicitly`
3496
+ );
3497
+ }
3498
+ }
3499
+ if (rewardTokens.length === 0) {
3500
+ throw DefiError.contractError(`[${this.protocolName}] no reward tokens to claim`);
3501
+ }
2966
3502
  const data = encodeFunctionData10({
2967
3503
  abi: gaugeAbi,
2968
3504
  functionName: "getReward",
2969
- args: [tokenId]
3505
+ args: [tokenId, rewardTokens]
2970
3506
  });
2971
3507
  return {
2972
- description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
3508
+ description: `[${this.protocolName}] Claim CL gauge rewards for NFT #${tokenId} (${rewardTokens.length} tokens)`,
2973
3509
  to: gauge,
2974
3510
  data,
2975
3511
  value: 0n,
2976
- gas_estimate: 3e5
3512
+ gas_estimate: 4e5
2977
3513
  };
2978
3514
  }
3515
+ /**
3516
+ * Ramses-CL-style pending rewards: earned(token, tokenId) per reward token from
3517
+ * gauge.getRewardTokens(). Returns raw amounts; caller resolves USD value.
3518
+ */
3519
+ async getPendingRewardsByCLTokenIdMulti(gauge, tokenId) {
3520
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
3521
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3522
+ let rewardTokens;
3523
+ try {
3524
+ rewardTokens = await client.readContract({
3525
+ address: gauge,
3526
+ abi: gaugeAbi,
3527
+ functionName: "getRewardTokens"
3528
+ });
3529
+ } catch {
3530
+ return [];
3531
+ }
3532
+ const out = [];
3533
+ for (const token of rewardTokens) {
3534
+ try {
3535
+ const amount = await client.readContract({
3536
+ address: gauge,
3537
+ abi: gaugeAbi,
3538
+ functionName: "earned",
3539
+ args: [token, tokenId]
3540
+ });
3541
+ out.push({ token, symbol: token.slice(0, 10), amount });
3542
+ } catch {
3543
+ }
3544
+ }
3545
+ return out;
3546
+ }
2979
3547
  async getPendingRewards(gauge, user) {
2980
3548
  if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
2981
3549
  const client = createPublicClient6({ transport: http6(this.rpcUrl) });
@@ -3032,6 +3600,21 @@ var init_dist2 = __esm({
3032
3600
  args: [tokenId]
3033
3601
  });
3034
3602
  }
3603
+ /**
3604
+ * Get pending rewards for an Aerodrome Slipstream CL gauge NFT position.
3605
+ * Uses the earned(address account, uint256 tokenId) overload, which is required
3606
+ * for CL gauges — the single-param earned(address) reverts on these contracts.
3607
+ */
3608
+ async getPendingRewardsByCLTokenId(gauge, user, tokenId) {
3609
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
3610
+ const client = createPublicClient6({ transport: http6(this.rpcUrl) });
3611
+ return await client.readContract({
3612
+ address: gauge,
3613
+ abi: gaugeAbi,
3614
+ functionName: "earned",
3615
+ args: [user, tokenId]
3616
+ });
3617
+ }
3035
3618
  // IVoteEscrow
3036
3619
  async buildCreateLock(amount, lockDuration) {
3037
3620
  const data = encodeFunctionData10({
@@ -3527,9 +4110,44 @@ var init_dist2 = __esm({
3527
4110
  }
3528
4111
  ];
3529
4112
  }
4113
+ /**
4114
+ * Scan ±scanRange bins around the active bin and return the user's non-zero balance bin IDs.
4115
+ * Critical: the rewarder may track pending rewards for bins OUTSIDE its current rewarded range
4116
+ * (e.g. when the rewarded range shifts after a position was already in place). Always claim
4117
+ * against the user's actual positions, not the rewarder's "current" range.
4118
+ */
4119
+ async findUserBinsWithBalance(pool, user, scanRange = 50) {
4120
+ const rpcUrl = this.requireRpc();
4121
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
4122
+ const activeId = await client.readContract({
4123
+ address: pool,
4124
+ abi: lbPairAbi,
4125
+ functionName: "getActiveId"
4126
+ });
4127
+ const calls = [];
4128
+ const binIds = [];
4129
+ for (let b = activeId - scanRange; b <= activeId + scanRange; b++) {
4130
+ binIds.push(b);
4131
+ calls.push([pool, encodeFunctionData12({
4132
+ abi: parseAbi12(["function balanceOf(address account, uint256 id) view returns (uint256)"]),
4133
+ functionName: "balanceOf",
4134
+ args: [user, BigInt(b)]
4135
+ })]);
4136
+ }
4137
+ const results = await multicallRead(rpcUrl, calls);
4138
+ const owned = [];
4139
+ for (let i = 0; i < binIds.length; i++) {
4140
+ const data = results[i];
4141
+ if (!data) continue;
4142
+ const hex = data.slice(2).padStart(64, "0");
4143
+ if (hex !== "0".repeat(64)) owned.push(binIds[i]);
4144
+ }
4145
+ return owned;
4146
+ }
3530
4147
  /**
3531
4148
  * Build a claim rewards transaction for specific LB bins.
3532
- * If binIds is omitted, auto-detects from the rewarder's rewarded range.
4149
+ * If binIds is omitted, auto-detects from the user's actual non-zero balance bins (active ±50 scan).
4150
+ * This catches rewards accumulated in bins outside the rewarder's current rewarded range.
3533
4151
  */
3534
4152
  async buildClaimRewards(user, pool, binIds) {
3535
4153
  const rpcUrl = this.requireRpc();
@@ -3545,15 +4163,18 @@ var init_dist2 = __esm({
3545
4163
  }
3546
4164
  let resolvedBinIds = binIds;
3547
4165
  if (!resolvedBinIds || resolvedBinIds.length === 0) {
3548
- const range = await client.readContract({
3549
- address: rewarder,
3550
- abi: lbRewarderAbi,
3551
- functionName: "getRewardedRange"
3552
- });
3553
- const min = Number(range[0]);
3554
- const max = Number(range[1]);
3555
- resolvedBinIds = [];
3556
- for (let b = min; b <= max; b++) resolvedBinIds.push(b);
4166
+ resolvedBinIds = await this.findUserBinsWithBalance(pool, user);
4167
+ if (resolvedBinIds.length === 0) {
4168
+ const range = await client.readContract({
4169
+ address: rewarder,
4170
+ abi: lbRewarderAbi,
4171
+ functionName: "getRewardedRange"
4172
+ });
4173
+ const min = Number(range[0]);
4174
+ const max = Number(range[1]);
4175
+ resolvedBinIds = [];
4176
+ for (let b = min; b <= max; b++) resolvedBinIds.push(b);
4177
+ }
3557
4178
  }
3558
4179
  const data = encodeFunctionData12({
3559
4180
  abi: lbRewarderAbi,
@@ -3964,7 +4585,7 @@ var init_dist2 = __esm({
3964
4585
  "function enterFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
3965
4586
  "function exitFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
3966
4587
  "function collectRewards((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
3967
- "function claimReward(address rewardToken, address to, uint128 amountRequested) external returns (uint256 reward)"
4588
+ "function claimReward(address rewardToken, address to, uint256 amountRequested) external returns (uint256 reward)"
3968
4589
  ]);
3969
4590
  positionManagerAbi2 = parseAbi13([
3970
4591
  "function approveForFarming(uint256 tokenId, bool approve, address farmingAddress) external",
@@ -3986,13 +4607,17 @@ var init_dist2 = __esm({
3986
4607
  positionManager;
3987
4608
  rpcUrl;
3988
4609
  factory;
3989
- constructor(protocolName, farmingCenter, eternalFarming, positionManager, rpcUrl, factory) {
4610
+ rewardToken;
4611
+ bonusRewardToken;
4612
+ constructor(protocolName, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken = KITTEN_TOKEN, bonusRewardToken = WHYPE_TOKEN) {
3990
4613
  this.protocolName = protocolName;
3991
4614
  this.farmingCenter = farmingCenter;
3992
4615
  this.eternalFarming = eternalFarming;
3993
4616
  this.positionManager = positionManager;
3994
4617
  this.rpcUrl = rpcUrl;
3995
4618
  this.factory = factory;
4619
+ this.rewardToken = rewardToken;
4620
+ this.bonusRewardToken = bonusRewardToken;
3996
4621
  }
3997
4622
  name() {
3998
4623
  return this.protocolName;
@@ -4007,8 +4632,8 @@ var init_dist2 = __esm({
4007
4632
  const poolLc = pool.toLowerCase();
4008
4633
  if (nonceCache.has(poolLc)) {
4009
4634
  return {
4010
- rewardToken: KITTEN_TOKEN,
4011
- bonusRewardToken: WHYPE_TOKEN,
4635
+ rewardToken: this.rewardToken,
4636
+ bonusRewardToken: this.bonusRewardToken,
4012
4637
  pool,
4013
4638
  nonce: nonceCache.get(poolLc)
4014
4639
  };
@@ -4019,8 +4644,8 @@ var init_dist2 = __esm({
4019
4644
  const nonce = BigInt(n);
4020
4645
  nonces.push(nonce);
4021
4646
  const key = {
4022
- rewardToken: KITTEN_TOKEN,
4023
- bonusRewardToken: WHYPE_TOKEN,
4647
+ rewardToken: this.rewardToken,
4648
+ bonusRewardToken: this.bonusRewardToken,
4024
4649
  pool,
4025
4650
  nonce
4026
4651
  };
@@ -4055,8 +4680,8 @@ var init_dist2 = __esm({
4055
4680
  const nonce = nonces[i];
4056
4681
  nonceCache.set(poolLc, nonce);
4057
4682
  return {
4058
- rewardToken: KITTEN_TOKEN,
4059
- bonusRewardToken: WHYPE_TOKEN,
4683
+ rewardToken: this.rewardToken,
4684
+ bonusRewardToken: this.bonusRewardToken,
4060
4685
  pool,
4061
4686
  nonce
4062
4687
  };
@@ -4148,8 +4773,8 @@ var init_dist2 = __esm({
4148
4773
  }
4149
4774
  const calls = [
4150
4775
  encodeCollectRewards(key, tokenId),
4151
- encodeClaimReward(KITTEN_TOKEN, owner),
4152
- encodeClaimReward(WHYPE_TOKEN, owner)
4776
+ encodeClaimReward(this.rewardToken, owner),
4777
+ encodeClaimReward(this.bonusRewardToken, owner)
4153
4778
  ];
4154
4779
  return {
4155
4780
  description: `[${this.protocolName}] Collect + claim rewards for NFT #${tokenId} in pool ${pool}`,
@@ -4164,8 +4789,8 @@ var init_dist2 = __esm({
4164
4789
  */
4165
4790
  async buildClaimReward(owner) {
4166
4791
  const calls = [
4167
- encodeClaimReward(KITTEN_TOKEN, owner),
4168
- encodeClaimReward(WHYPE_TOKEN, owner)
4792
+ encodeClaimReward(this.rewardToken, owner),
4793
+ encodeClaimReward(this.bonusRewardToken, owner)
4169
4794
  ];
4170
4795
  return {
4171
4796
  description: `[${this.protocolName}] Claim KITTEN + WHYPE farming rewards to ${owner}`,
@@ -4234,8 +4859,8 @@ var init_dist2 = __esm({
4234
4859
  for (const pool of pools) {
4235
4860
  for (let n = 0; n <= MAX_NONCE_SCAN; n++) {
4236
4861
  const key = {
4237
- rewardToken: KITTEN_TOKEN,
4238
- bonusRewardToken: WHYPE_TOKEN,
4862
+ rewardToken: this.rewardToken,
4863
+ bonusRewardToken: this.bonusRewardToken,
4239
4864
  pool,
4240
4865
  nonce: BigInt(n)
4241
4866
  };
@@ -4282,8 +4907,8 @@ var init_dist2 = __esm({
4282
4907
  const isActive = !deactivated;
4283
4908
  if (!bestKey || isActive && !bestActive || isActive === bestActive && nonce > bestKey.nonce) {
4284
4909
  bestKey = {
4285
- rewardToken: KITTEN_TOKEN,
4286
- bonusRewardToken: WHYPE_TOKEN,
4910
+ rewardToken: this.rewardToken,
4911
+ bonusRewardToken: this.bonusRewardToken,
4287
4912
  pool,
4288
4913
  nonce
4289
4914
  };
@@ -4309,6 +4934,235 @@ var init_dist2 = __esm({
4309
4934
  return results;
4310
4935
  }
4311
4936
  };
4937
+ DEFAULT_BASE_URL = "https://app.usenest.xyz/api/blaze";
4938
+ FALLBACK_BASE_URL = "https://blaze.nest.aegas.it";
4939
+ NEST_TOKEN = "0x07c57E32a3C29D5659bda1d3EFC2E7BF004E3035";
4940
+ NEST_DECIMALS = 18;
4941
+ NestOffChainAdapter = class {
4942
+ baseUrl;
4943
+ fallbackUrl;
4944
+ voter;
4945
+ constructor(entry) {
4946
+ const voter = entry.contracts?.["voter"];
4947
+ if (!voter) {
4948
+ throw DefiError.contractError("Nest off-chain: missing 'voter' contract");
4949
+ }
4950
+ this.voter = voter;
4951
+ this.baseUrl = process.env["NEST_API_URL"] ?? DEFAULT_BASE_URL;
4952
+ this.fallbackUrl = FALLBACK_BASE_URL;
4953
+ }
4954
+ name() {
4955
+ return "Nest";
4956
+ }
4957
+ /** Cumulative claimed + available NEST emissions for a wallet */
4958
+ async getClaimStatus(wallet) {
4959
+ const data = await this.fetchJson(
4960
+ `/claim/claim-status?publicAddress=${wallet}`
4961
+ );
4962
+ const totalClaimedRaw = BigInt(data.totalClaimed);
4963
+ const totalAvailableRaw = BigInt(data.totalAvailable);
4964
+ const pendingRaw = totalAvailableRaw > totalClaimedRaw ? totalAvailableRaw - totalClaimedRaw : 0n;
4965
+ return {
4966
+ totalClaimedRaw,
4967
+ totalAvailableRaw,
4968
+ pendingRaw,
4969
+ pendingFormatted: Number(pendingRaw) / 10 ** NEST_DECIMALS
4970
+ };
4971
+ }
4972
+ /**
4973
+ * Backend-signed claim ticket (or null when nothing to claim).
4974
+ * Returns the raw ticket; `buildClaim()` is not yet implemented because the
4975
+ * voter contract source is unverified — function selector 0xd6d7a454 takes
4976
+ * 5 dynamic arrays we have not been able to disambiguate yet.
4977
+ */
4978
+ async getClaimTicket(wallet) {
4979
+ const url = `${this.baseUrl}/claim/claim-data?publicAddress=${wallet}`;
4980
+ const res = await fetch(url, this.requestInit());
4981
+ const text = await res.text();
4982
+ if (text.includes("no points to claim")) return null;
4983
+ if (!res.ok) {
4984
+ throw DefiError.providerError(`Nest claim-data ${res.status}: ${text.slice(0, 200)}`);
4985
+ }
4986
+ let json;
4987
+ try {
4988
+ json = JSON.parse(text);
4989
+ } catch {
4990
+ throw DefiError.providerError(`Nest claim-data: non-JSON response: ${text.slice(0, 200)}`);
4991
+ }
4992
+ return {
4993
+ user: json.user,
4994
+ amount: BigInt(json.amount),
4995
+ timestamp: BigInt(json.timestamp),
4996
+ day: json.day === null ? null : BigInt(json.day),
4997
+ signature: json.signature.startsWith("0x") ? json.signature : `0x${json.signature}`
4998
+ };
4999
+ }
5000
+ /** APR estimate (percent) for a CL position with given tick range and amounts */
5001
+ async estimateLpApr(params) {
5002
+ const qs = new URLSearchParams({
5003
+ poolAddress: params.poolAddress,
5004
+ minTick: String(params.minTick),
5005
+ maxTick: String(params.maxTick),
5006
+ token0Amount: params.token0Amount.toString(),
5007
+ token1Amount: params.token1Amount.toString()
5008
+ });
5009
+ const data = await this.fetchJson(`/liquidity/apr/estimate?${qs}`);
5010
+ const apr = Number(data.apr);
5011
+ if (!Number.isFinite(apr)) {
5012
+ throw DefiError.providerError(`Nest apr/estimate: invalid apr value '${data.apr}'`);
5013
+ }
5014
+ return apr;
5015
+ }
5016
+ /** Pending NEST emissions as IGauge-compatible RewardInfo[] */
5017
+ async getPendingRewards(user) {
5018
+ const status = await this.getClaimStatus(user);
5019
+ if (status.pendingRaw === 0n) return [];
5020
+ return [{
5021
+ token: NEST_TOKEN,
5022
+ symbol: "NEST",
5023
+ amount: status.pendingRaw
5024
+ }];
5025
+ }
5026
+ /** Voter address used by aggregateClaim() — exposed for callers that build the tx themselves */
5027
+ getVoterAddress() {
5028
+ return this.voter;
5029
+ }
5030
+ /**
5031
+ * Build a Nest voter claim transaction by reproducing the byte-level calldata
5032
+ * pattern observed in successful onchain claims, swapping in the ticket's
5033
+ * (amount, timestamp, signature) words.
5034
+ *
5035
+ * The voter implementation source is not verified, so we cannot derive a
5036
+ * Solidity ABI for selector 0xd6d7a454. Instead, two known-successful claim
5037
+ * transactions were diffed:
5038
+ *
5039
+ * tx1: 0x99f35cfdb6fc3885ebe046c4625acc083e42d5afe6ca6962c6c81cd9006b99ba
5040
+ * tx2: 0x3e120ab95e9e0a9148cb8964993dd066b8a36363353fe727462231857724e7bb
5041
+ *
5042
+ * 31 of 34 calldata words are identical between the two; only words 21, 22,
5043
+ * 25, 26, 27 differ — and those map exactly to the backend ticket's
5044
+ * (amount, timestamp, sigR, sigS, sigVPadded). msg.sender is not encoded in
5045
+ * calldata; voter binds the claim to the caller, so the ticket signature
5046
+ * authorizes the EOA holding the wallet.
5047
+ *
5048
+ * Throws if no claim ticket is available.
5049
+ */
5050
+ async buildClaim(wallet) {
5051
+ const ticket = await this.getClaimTicket(wallet);
5052
+ if (!ticket) {
5053
+ throw DefiError.invalidParam(`Nest: no claim ticket available for ${wallet}`);
5054
+ }
5055
+ const sigHex = ticket.signature.startsWith("0x") ? ticket.signature.slice(2) : ticket.signature;
5056
+ if (sigHex.length !== 130) {
5057
+ throw DefiError.providerError(`Nest: signature must be 65 bytes (130 hex chars), got ${sigHex.length}`);
5058
+ }
5059
+ const r = sigHex.slice(0, 64);
5060
+ const s = sigHex.slice(64, 128);
5061
+ const v = sigHex.slice(128, 130);
5062
+ const vPadded = v + "0".repeat(62);
5063
+ const amountHex = ticket.amount.toString(16).padStart(64, "0");
5064
+ const timestampHex = ticket.timestamp.toString(16).padStart(64, "0");
5065
+ const words = [
5066
+ "0000000000000000000000000000000000000000000000000000000000000160",
5067
+ // 0
5068
+ "0000000000000000000000000000000000000000000000000000000000000180",
5069
+ // 1
5070
+ "0000000000000000000000000000000000000000000000000000000000000200",
5071
+ // 2
5072
+ "00000000000000000000000000000000000000000000000000000000000002a0",
5073
+ // 3
5074
+ "0000000000000000000000000000000000000000000000000000000000000380",
5075
+ // 4
5076
+ "0000000000000000000000000000000000000000000000000000000000000000",
5077
+ // 5
5078
+ "0000000000000000000000000000000000000000000000000000000000000000",
5079
+ // 6
5080
+ "0000000000000000000000000000000000000000000000000000000000000000",
5081
+ // 7
5082
+ "0000000000000000000000000000000000000000000000000000000000000000",
5083
+ // 8
5084
+ "0000000000000000000000000000000000000000000000000000000000000001",
5085
+ // 9
5086
+ "0000000000000000000000000000000000000000000000000000000000000001",
5087
+ // 10
5088
+ "0000000000000000000000000000000000000000000000000000000000000000",
5089
+ // 11 — empty array length
5090
+ "0000000000000000000000000000000000000000000000000000000000000040",
5091
+ // 12
5092
+ "0000000000000000000000000000000000000000000000000000000000000060",
5093
+ // 13
5094
+ "0000000000000000000000000000000000000000000000000000000000000000",
5095
+ // 14
5096
+ "0000000000000000000000000000000000000000000000000000000000000000",
5097
+ // 15
5098
+ "0000000000000000000000000000000000000000000000000000000000000000",
5099
+ // 16
5100
+ "0000000000000000000000000000000000000000000000000000000000000060",
5101
+ // 17
5102
+ "0000000000000000000000000000000000000000000000000000000000000080",
5103
+ // 18
5104
+ "0000000000000000000000000000000000000000000000000000000000000000",
5105
+ // 19
5106
+ "0000000000000000000000000000000000000000000000000000000000000000",
5107
+ // 20
5108
+ amountHex,
5109
+ // 21 — ticket amount
5110
+ timestampHex,
5111
+ // 22 — ticket timestamp
5112
+ "0000000000000000000000000000000000000000000000000000000000000060",
5113
+ // 23 — sig offset
5114
+ "0000000000000000000000000000000000000000000000000000000000000041",
5115
+ // 24 — sig length (65)
5116
+ r,
5117
+ // 25 — sig r
5118
+ s,
5119
+ // 26 — sig s
5120
+ vPadded,
5121
+ // 27 — sig v + zero padding
5122
+ "0000000000000000000000000000000000000000000000000000000000000000",
5123
+ // 28
5124
+ "0000000000000000000000000000000000000000000000000000000000000000",
5125
+ // 29
5126
+ "0000000000000000000000000000000000000000000000000000000000000001",
5127
+ // 30
5128
+ "0000000000000000000000000000000000000000000000000000000000000000",
5129
+ // 31
5130
+ "00000000000000000000000000000000000000000000000000000000000000a0",
5131
+ // 32
5132
+ "0000000000000000000000000000000000000000000000000000000000000000"
5133
+ // 33
5134
+ ];
5135
+ const data = "0xd6d7a454" + words.join("");
5136
+ return {
5137
+ description: `[${this.name()}] Claim NEST emissions (${(Number(ticket.amount) / 1e18).toFixed(2)} NEST cumulative; backend-signed ts=${ticket.timestamp})`,
5138
+ to: this.voter,
5139
+ data,
5140
+ value: 0n,
5141
+ gas_estimate: 6e5
5142
+ };
5143
+ }
5144
+ // ── internal ──
5145
+ async fetchJson(path) {
5146
+ const primary = `${this.baseUrl}${path.startsWith("/claim") ? path : path}`;
5147
+ try {
5148
+ const res = await fetch(primary, this.requestInit());
5149
+ if (res.ok) return await res.json();
5150
+ if (res.status >= 500) throw new Error(`upstream ${res.status}`);
5151
+ throw DefiError.providerError(`Nest API ${res.status}: ${(await res.text()).slice(0, 200)}`);
5152
+ } catch (e) {
5153
+ if (this.baseUrl === this.fallbackUrl) throw e;
5154
+ const fallback = `${this.fallbackUrl}${path}`;
5155
+ const res = await fetch(fallback, this.requestInit());
5156
+ if (!res.ok) {
5157
+ throw DefiError.providerError(`Nest fallback ${res.status}: ${(await res.text()).slice(0, 200)}`);
5158
+ }
5159
+ return await res.json();
5160
+ }
5161
+ }
5162
+ requestInit() {
5163
+ return { headers: { "User-Agent": "defi-cli/0.5", "Accept": "application/json" } };
5164
+ }
5165
+ };
4312
5166
  POOL_ABI = parseAbi14([
4313
5167
  "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external",
4314
5168
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
@@ -4610,8 +5464,8 @@ var init_dist2 = __esm({
4610
5464
  throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
4611
5465
  });
4612
5466
  const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
4613
- const MAX_UINT256 = 2n ** 256n - 1n;
4614
- const hf = healthFactor >= MAX_UINT256 ? Infinity : Number(healthFactor) / 1e18;
5467
+ const MAX_UINT2562 = 2n ** 256n - 1n;
5468
+ const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
4615
5469
  const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
4616
5470
  const debtUsd = u256ToF64(totalDebtBase) / 1e8;
4617
5471
  const ltvBps = u256ToF64(ltv);
@@ -4775,8 +5629,8 @@ var init_dist2 = __esm({
4775
5629
  throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
4776
5630
  });
4777
5631
  const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
4778
- const MAX_UINT256 = 2n ** 256n - 1n;
4779
- const hf = healthFactor >= MAX_UINT256 ? Infinity : Number(healthFactor) / 1e18;
5632
+ const MAX_UINT2562 = 2n ** 256n - 1n;
5633
+ const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
4780
5634
  const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
4781
5635
  const debtUsd = u256ToF642(totalDebtBase) / 1e18;
4782
5636
  const ltvBps = u256ToF642(ltv);
@@ -5023,7 +5877,8 @@ var init_dist2 = __esm({
5023
5877
  to: this.comet,
5024
5878
  data,
5025
5879
  value: 0n,
5026
- gas_estimate: 3e5
5880
+ gas_estimate: 3e5,
5881
+ approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
5027
5882
  };
5028
5883
  }
5029
5884
  async buildBorrow(params) {
@@ -5051,7 +5906,8 @@ var init_dist2 = __esm({
5051
5906
  to: this.comet,
5052
5907
  data,
5053
5908
  value: 0n,
5054
- gas_estimate: 3e5
5909
+ gas_estimate: 3e5,
5910
+ approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
5055
5911
  };
5056
5912
  }
5057
5913
  async buildWithdraw(params) {
@@ -5240,6 +6096,14 @@ var init_dist2 = __esm({
5240
6096
  "function totalAssets() external view returns (uint256)",
5241
6097
  "function totalSupply() external view returns (uint256)"
5242
6098
  ]);
6099
+ ERC4626_ABI = parseAbi20([
6100
+ "function asset() external view returns (address)",
6101
+ "function deposit(uint256 assets, address receiver) external returns (uint256 shares)",
6102
+ "function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets)",
6103
+ "function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares)",
6104
+ "function balanceOf(address owner) external view returns (uint256)"
6105
+ ]);
6106
+ MAX_UINT256 = (1n << 256n) - 1n;
5243
6107
  IRM_ABI = parseAbi20([
5244
6108
  "function borrowRateView((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee) market) external view returns (uint256)"
5245
6109
  ]);
@@ -5249,6 +6113,9 @@ var init_dist2 = __esm({
5249
6113
  morpho;
5250
6114
  defaultVault;
5251
6115
  rpcUrl;
6116
+ metaMorphoVaults;
6117
+ metaMorphoVaultEntries;
6118
+ vaultAssetMap = null;
5252
6119
  constructor(entry, rpcUrl) {
5253
6120
  this.protocolName = entry.name;
5254
6121
  this.rpcUrl = rpcUrl;
@@ -5257,11 +6124,58 @@ var init_dist2 = __esm({
5257
6124
  if (!morpho) throw DefiError.contractError("Missing 'morpho_blue' contract address");
5258
6125
  this.morpho = morpho;
5259
6126
  this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
6127
+ this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
6128
+ this.metaMorphoVaults = this.metaMorphoVaultEntries.map((e) => e.addr);
6129
+ }
6130
+ async resolveVault(asset, preferKey) {
6131
+ if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
6132
+ if (preferKey) {
6133
+ const direct = this.metaMorphoVaultEntries.find((e) => e.key === preferKey);
6134
+ if (direct) return direct.addr;
6135
+ }
6136
+ if (!this.vaultAssetMap) {
6137
+ const calls = this.metaMorphoVaultEntries.map((e) => [
6138
+ e.addr,
6139
+ encodeFunctionData19({ abi: ERC4626_ABI, functionName: "asset" })
6140
+ ]);
6141
+ const results = await multicallRead(this.rpcUrl, calls).catch(() => []);
6142
+ const map = /* @__PURE__ */ new Map();
6143
+ for (let i = 0; i < results.length; i++) {
6144
+ const data = results[i];
6145
+ if (!data || data.length < 66) continue;
6146
+ const a = `0x${data.slice(26, 66)}`.toLowerCase();
6147
+ const entry = this.metaMorphoVaultEntries[i];
6148
+ const existing = map.get(a);
6149
+ if (!existing || entry.key.length < existing.key.length) {
6150
+ map.set(a, entry);
6151
+ }
6152
+ }
6153
+ const flatMap = /* @__PURE__ */ new Map();
6154
+ for (const [k, v] of map) flatMap.set(k, v.addr);
6155
+ this.vaultAssetMap = flatMap;
6156
+ }
6157
+ return this.vaultAssetMap.get(asset.toLowerCase()) ?? null;
5260
6158
  }
5261
6159
  name() {
5262
6160
  return this.protocolName;
5263
6161
  }
5264
6162
  async buildSupply(params) {
6163
+ const vault = await this.resolveVault(params.asset);
6164
+ if (vault) {
6165
+ const data2 = encodeFunctionData19({
6166
+ abi: ERC4626_ABI,
6167
+ functionName: "deposit",
6168
+ args: [params.amount, params.on_behalf_of]
6169
+ });
6170
+ return {
6171
+ description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
6172
+ to: vault,
6173
+ data: data2,
6174
+ value: 0n,
6175
+ gas_estimate: 4e5,
6176
+ approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
6177
+ };
6178
+ }
5265
6179
  const market = defaultMarketParams(params.asset);
5266
6180
  const data = encodeFunctionData19({
5267
6181
  abi: MORPHO_ABI,
@@ -5307,6 +6221,40 @@ var init_dist2 = __esm({
5307
6221
  };
5308
6222
  }
5309
6223
  async buildWithdraw(params) {
6224
+ const vault = await this.resolveVault(params.asset);
6225
+ if (vault) {
6226
+ if (params.amount === MAX_UINT256) {
6227
+ if (!this.rpcUrl) throw DefiError.rpcError("RPC required to fetch vault shares");
6228
+ const [balRaw] = await multicallRead(this.rpcUrl, [
6229
+ [vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
6230
+ ]);
6231
+ const shares = decodeU256(balRaw ?? null);
6232
+ const data3 = encodeFunctionData19({
6233
+ abi: ERC4626_ABI,
6234
+ functionName: "redeem",
6235
+ args: [shares, params.to, params.to]
6236
+ });
6237
+ return {
6238
+ description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
6239
+ to: vault,
6240
+ data: data3,
6241
+ value: 0n,
6242
+ gas_estimate: 4e5
6243
+ };
6244
+ }
6245
+ const data2 = encodeFunctionData19({
6246
+ abi: ERC4626_ABI,
6247
+ functionName: "withdraw",
6248
+ args: [params.amount, params.to, params.to]
6249
+ });
6250
+ return {
6251
+ description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
6252
+ to: vault,
6253
+ data: data2,
6254
+ value: 0n,
6255
+ gas_estimate: 4e5
6256
+ };
6257
+ }
5310
6258
  const market = defaultMarketParams(params.asset);
5311
6259
  const data = encodeFunctionData19({
5312
6260
  abi: MORPHO_ABI,
@@ -5324,7 +6272,7 @@ var init_dist2 = __esm({
5324
6272
  async getRates(asset) {
5325
6273
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5326
6274
  if (!this.defaultVault) {
5327
- throw DefiError.contractError(`[${this.protocolName}] No MetaMorpho vault configured for rate query`);
6275
+ return { protocol: this.protocolName, asset, supply_apy: 0, borrow_variable_apy: 0, borrow_stable_apy: 0, utilization: 0, total_supply: 0n, total_borrow: 0n };
5328
6276
  }
5329
6277
  const [queueLenRaw] = await multicallRead(this.rpcUrl, [
5330
6278
  [this.defaultVault, encodeFunctionData19({ abi: META_MORPHO_ABI, functionName: "supplyQueueLength" })]
@@ -5625,7 +6573,7 @@ var init_dist2 = __esm({
5625
6573
  return results;
5626
6574
  }
5627
6575
  };
5628
- ERC4626_ABI = parseAbi23([
6576
+ ERC4626_ABI2 = parseAbi23([
5629
6577
  "function asset() external view returns (address)",
5630
6578
  "function totalAssets() external view returns (uint256)",
5631
6579
  "function totalSupply() external view returns (uint256)",
@@ -5650,7 +6598,7 @@ var init_dist2 = __esm({
5650
6598
  }
5651
6599
  async buildDeposit(assets, receiver) {
5652
6600
  const data = encodeFunctionData21({
5653
- abi: ERC4626_ABI,
6601
+ abi: ERC4626_ABI2,
5654
6602
  functionName: "deposit",
5655
6603
  args: [assets, receiver]
5656
6604
  });
@@ -5664,7 +6612,7 @@ var init_dist2 = __esm({
5664
6612
  }
5665
6613
  async buildWithdraw(assets, receiver, owner) {
5666
6614
  const data = encodeFunctionData21({
5667
- abi: ERC4626_ABI,
6615
+ abi: ERC4626_ABI2,
5668
6616
  functionName: "withdraw",
5669
6617
  args: [assets, receiver, owner]
5670
6618
  });
@@ -5681,7 +6629,7 @@ var init_dist2 = __esm({
5681
6629
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5682
6630
  return client.readContract({
5683
6631
  address: this.vaultAddress,
5684
- abi: ERC4626_ABI,
6632
+ abi: ERC4626_ABI2,
5685
6633
  functionName: "totalAssets"
5686
6634
  }).catch((e) => {
5687
6635
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
@@ -5692,7 +6640,7 @@ var init_dist2 = __esm({
5692
6640
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5693
6641
  return client.readContract({
5694
6642
  address: this.vaultAddress,
5695
- abi: ERC4626_ABI,
6643
+ abi: ERC4626_ABI2,
5696
6644
  functionName: "convertToShares",
5697
6645
  args: [assets]
5698
6646
  }).catch((e) => {
@@ -5704,7 +6652,7 @@ var init_dist2 = __esm({
5704
6652
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5705
6653
  return client.readContract({
5706
6654
  address: this.vaultAddress,
5707
- abi: ERC4626_ABI,
6655
+ abi: ERC4626_ABI2,
5708
6656
  functionName: "convertToAssets",
5709
6657
  args: [shares]
5710
6658
  }).catch((e) => {
@@ -5715,13 +6663,13 @@ var init_dist2 = __esm({
5715
6663
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5716
6664
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5717
6665
  const [totalAssets, totalSupply, asset] = await Promise.all([
5718
- client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI, functionName: "totalAssets" }).catch((e) => {
6666
+ client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
5719
6667
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
5720
6668
  }),
5721
- client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI, functionName: "totalSupply" }).catch((e) => {
6669
+ client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalSupply" }).catch((e) => {
5722
6670
  throw DefiError.rpcError(`[${this.protocolName}] totalSupply failed: ${e}`);
5723
6671
  }),
5724
- client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI, functionName: "asset" }).catch((e) => {
6672
+ client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "asset" }).catch((e) => {
5725
6673
  throw DefiError.rpcError(`[${this.protocolName}] asset failed: ${e}`);
5726
6674
  })
5727
6675
  ]);
@@ -6818,10 +7766,11 @@ server.tool(
6818
7766
  token_a: z.string().describe("First token symbol or address"),
6819
7767
  token_b: z.string().describe("Second token symbol or address"),
6820
7768
  liquidity: z.string().describe("Liquidity amount to remove in wei"),
7769
+ token_id: z.string().optional().describe("NFT tokenId \u2014 required for V3/CL position-manager based protocols (uniswap_v3, algebra_v3, hybra)"),
6821
7770
  recipient: z.string().optional().describe("Recipient address for returned tokens"),
6822
7771
  broadcast: z.boolean().optional().describe("Set true to broadcast (default: false)")
6823
7772
  },
6824
- async ({ chain, protocol, token_a, token_b, liquidity, recipient, broadcast }) => {
7773
+ async ({ chain, protocol, token_a, token_b, liquidity, token_id, recipient, broadcast }) => {
6825
7774
  try {
6826
7775
  const chainName = chain ?? "hyperevm";
6827
7776
  const registry = getRegistry();
@@ -6836,7 +7785,8 @@ server.tool(
6836
7785
  token_a: tokenA,
6837
7786
  token_b: tokenB,
6838
7787
  liquidity: BigInt(liquidity),
6839
- recipient: recipientAddr
7788
+ recipient: recipientAddr,
7789
+ token_id: token_id ? BigInt(token_id) : void 0
6840
7790
  });
6841
7791
  const executor = makeExecutor(broadcast ?? false, chainConfig.effectiveRpcUrl(), chainConfig.explorer_url);
6842
7792
  const result = await executor.execute(tx);
@@ -7469,6 +8419,52 @@ server.tool(
7469
8419
  }
7470
8420
  }
7471
8421
  );
8422
+ server.tool(
8423
+ "defi_yield_scan",
8424
+ "Scan all configured chains for the best lending yield opportunities for a given asset. Compares supply APY across Aave V3, Compound, Venus, Morpho, etc. Returns ranked results with chain, protocol, supply_apy, borrow_apy, utilization.",
8425
+ {
8426
+ asset: z.string().optional().describe("Token symbol to scan (default: USDC)")
8427
+ },
8428
+ async ({ asset }) => {
8429
+ const registry = Registry.loadEmbedded();
8430
+ const symbol = asset ?? "USDC";
8431
+ const results = [];
8432
+ const chains = Array.from(registry.chains.keys());
8433
+ await Promise.allSettled(
8434
+ chains.map(async (chainName) => {
8435
+ const chain = registry.getChain(chainName);
8436
+ const rpcUrl = chain.effectiveRpcUrl();
8437
+ const lendingProtos = registry.getProtocolsForChain(chainName).filter((p) => p.category === "lending");
8438
+ for (const proto of lendingProtos) {
8439
+ try {
8440
+ const adapter = createLending(proto, rpcUrl);
8441
+ const tokens = registry.tokens.get(chainName);
8442
+ const token = tokens?.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());
8443
+ if (!token) continue;
8444
+ const rates = await adapter.getRates(token.address);
8445
+ results.push({
8446
+ chain: chainName,
8447
+ protocol: proto.name,
8448
+ slug: proto.slug,
8449
+ asset: token.symbol,
8450
+ asset_address: token.address,
8451
+ supply_apy: rates.supply_apy,
8452
+ borrow_variable_apy: rates.borrow_variable_apy,
8453
+ utilization: rates.utilization,
8454
+ total_supply: rates.total_supply?.toString(),
8455
+ total_borrow: rates.total_borrow?.toString()
8456
+ });
8457
+ } catch {
8458
+ }
8459
+ }
8460
+ })
8461
+ );
8462
+ results.sort((a, b) => b.supply_apy - a.supply_apy);
8463
+ return {
8464
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
8465
+ };
8466
+ }
8467
+ );
7472
8468
  var transport = new StdioServerTransport();
7473
8469
  await server.connect(transport);
7474
8470
  //# sourceMappingURL=mcp-server.js.map