@hypurrquant/defi-cli 0.5.0 → 1.0.1

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;
@@ -438,7 +439,7 @@ var init_dist = __esm({
438
439
  }
439
440
  getProtocolsForChain(chain, includeUnverified = false) {
440
441
  return this.protocols.filter(
441
- (p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false)
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) {
@@ -1006,6 +1010,46 @@ function createOracleFromCdp(entry, _asset, rpcUrl) {
1006
1010
  function createMerchantMoeLB(entry, rpcUrl) {
1007
1011
  return new MerchantMoeLBAdapter(entry, rpcUrl);
1008
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
+ }
1009
1053
  function createKittenSwapFarming(entry, rpcUrl) {
1010
1054
  const farmingCenter = entry.contracts?.["farming_center"];
1011
1055
  if (!farmingCenter) {
@@ -1020,9 +1064,11 @@ function createKittenSwapFarming(entry, rpcUrl) {
1020
1064
  throw new DefiError("CONTRACT_ERROR", `[${entry.name}] Missing 'position_manager' contract address`);
1021
1065
  }
1022
1066
  const factory = entry.contracts?.["factory"];
1023
- 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);
1024
1070
  }
1025
- 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;
1026
1072
  var init_dist2 = __esm({
1027
1073
  "../defi-protocols/dist/index.js"() {
1028
1074
  "use strict";
@@ -1060,6 +1106,7 @@ var init_dist2 = __esm({
1060
1106
  init_dist();
1061
1107
  init_dist();
1062
1108
  init_dist();
1109
+ init_dist();
1063
1110
  DEFAULT_FEE = 3e3;
1064
1111
  swapRouterAbi = parseAbi3([
1065
1112
  "struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
@@ -1075,7 +1122,19 @@ var init_dist2 = __esm({
1075
1122
  ]);
1076
1123
  positionManagerAbi = parseAbi3([
1077
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; }",
1078
- "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)"
1079
1138
  ]);
1080
1139
  UniswapV3Adapter = class {
1081
1140
  protocolName;
@@ -1098,7 +1157,7 @@ var init_dist2 = __esm({
1098
1157
  this.factory = entry.contracts?.["factory"];
1099
1158
  this.fee = DEFAULT_FEE;
1100
1159
  this.rpcUrl = rpcUrl;
1101
- 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;
1102
1161
  }
1103
1162
  name() {
1104
1163
  return this.protocolName;
@@ -1274,16 +1333,64 @@ var init_dist2 = __esm({
1274
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];
1275
1334
  const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
1276
1335
  const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
1277
- 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({
1278
1385
  abi: positionManagerAbi,
1279
1386
  functionName: "mint",
1280
1387
  args: [
1281
1388
  {
1282
1389
  token0,
1283
1390
  token1,
1284
- fee: this.fee,
1285
- tickLower: -887220,
1286
- tickUpper: 887220,
1391
+ fee: thirdField,
1392
+ tickLower,
1393
+ tickUpper,
1287
1394
  amount0Desired: amount0,
1288
1395
  amount1Desired: amount1,
1289
1396
  amount0Min: 0n,
@@ -1305,10 +1412,125 @@ var init_dist2 = __esm({
1305
1412
  ]
1306
1413
  };
1307
1414
  }
1308
- async buildRemoveLiquidity(_params) {
1309
- throw DefiError.unsupported(
1310
- `[${this.protocolName}] remove_liquidity requires tokenId \u2014 use NFT position manager directly`
1311
- );
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
+ };
1312
1534
  }
1313
1535
  };
1314
1536
  abi = parseAbi22([
@@ -1529,6 +1751,13 @@ var init_dist2 = __esm({
1529
1751
  "struct MintParams { address token0; address token1; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
1530
1752
  "function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
1531
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
+ ]);
1532
1761
  AlgebraV3Adapter = class {
1533
1762
  protocolName;
1534
1763
  router;
@@ -1724,10 +1953,34 @@ var init_dist2 = __esm({
1724
1953
  approvals
1725
1954
  };
1726
1955
  }
1727
- async buildRemoveLiquidity(_params) {
1728
- throw DefiError.unsupported(
1729
- `[${this.protocolName}] remove_liquidity requires tokenId \u2014 use NFT position manager directly`
1730
- );
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
+ };
1731
1984
  }
1732
1985
  };
1733
1986
  abi3 = parseAbi4([
@@ -2015,7 +2268,12 @@ var init_dist2 = __esm({
2015
2268
  };
2016
2269
  thenaPmAbi = parseAbi7([
2017
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; }",
2018
- "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)"
2019
2277
  ]);
2020
2278
  thenaRouterAbi = parseAbi7([
2021
2279
  "struct ExactInputSingleParams { address tokenIn; address tokenOut; int24 tickSpacing; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
@@ -2158,8 +2416,34 @@ var init_dist2 = __esm({
2158
2416
  approvals
2159
2417
  };
2160
2418
  }
2161
- async buildRemoveLiquidity(_params) {
2162
- 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
+ };
2163
2447
  }
2164
2448
  };
2165
2449
  _addressDecodeAbi = parseAbi8(["function f() external view returns (address)"]);
@@ -2338,12 +2622,14 @@ var init_dist2 = __esm({
2338
2622
  pre_txs: [approveTx]
2339
2623
  };
2340
2624
  }
2341
- async buildWithdraw(gauge, _amount, tokenId) {
2625
+ async buildWithdraw(gauge, _amount, tokenId, opts) {
2342
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)." : "";
2343
2629
  return {
2344
- description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge`,
2630
+ description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge (redeemType=${redeemType})${warning}`,
2345
2631
  to: gauge,
2346
- data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId, 1] }),
2632
+ data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId, redeemType] }),
2347
2633
  value: 0n,
2348
2634
  gas_estimate: 1e6
2349
2635
  };
@@ -2352,15 +2638,15 @@ var init_dist2 = __esm({
2352
2638
  async buildClaimRewards(gauge, _account) {
2353
2639
  throw DefiError.unsupported(`[${this.protocolName}] Use buildClaimRewardsByTokenId for CL gauges`);
2354
2640
  }
2355
- async buildClaimRewardsByTokenId(gauge, tokenId) {
2641
+ async buildClaimRewardsByTokenId(gauge, tokenId, opts) {
2642
+ const redeemType = opts?.redeemType ?? 1;
2356
2643
  return {
2357
- description: `[${this.protocolName}] Claim rewards for NFT #${tokenId}`,
2644
+ description: `[${this.protocolName}] Claim rewards for NFT #${tokenId} (redeemType=${redeemType})`,
2358
2645
  to: this.gaugeManager,
2359
2646
  data: encodeFunctionData8({
2360
2647
  abi: gaugeManagerAbi,
2361
2648
  functionName: "claimRewards",
2362
- args: [gauge, [tokenId], 1]
2363
- // redeemType=1
2649
+ args: [gauge, [tokenId], redeemType]
2364
2650
  }),
2365
2651
  value: 0n,
2366
2652
  gas_estimate: 1e6
@@ -2505,15 +2791,21 @@ var init_dist2 = __esm({
2505
2791
  "function getReward(address account) external",
2506
2792
  "function getReward(address account, address[] tokens) external",
2507
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",
2508
2796
  "function earned(address account) external view returns (uint256)",
2509
2797
  "function earned(address account, uint256 tokenId) external view returns (uint256)",
2510
2798
  "function earned(address token, address account) external view returns (uint256)",
2511
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)",
2512
2802
  "function rewardRate() external view returns (uint256)",
2513
2803
  "function rewardToken() external view returns (address)",
2514
2804
  "function totalSupply() external view returns (uint256)",
2515
2805
  "function rewardsListLength() external view returns (uint256)",
2516
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[])",
2517
2809
  "function nonfungiblePositionManager() external view returns (address)"
2518
2810
  ]);
2519
2811
  veAbi2 = parseAbi10([
@@ -2558,6 +2850,10 @@ var init_dist2 = __esm({
2558
2850
  clFactory;
2559
2851
  v2Factory;
2560
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;
2561
2857
  constructor(entry, rpcUrl, tokens) {
2562
2858
  this.protocolName = entry.name;
2563
2859
  const voter = entry.contracts?.["voter"];
@@ -2574,6 +2870,8 @@ var init_dist2 = __esm({
2574
2870
  this.tokens = tokens;
2575
2871
  this.clFactory = entry.contracts?.["cl_factory"] ?? entry.contracts?.["factory"];
2576
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;
2577
2875
  }
2578
2876
  name() {
2579
2877
  return this.protocolName;
@@ -2908,6 +3206,25 @@ var init_dist2 = __esm({
2908
3206
  }
2909
3207
  // IGauge
2910
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
+ }
2911
3228
  if (tokenId !== void 0) {
2912
3229
  const data2 = encodeFunctionData10({
2913
3230
  abi: gaugeAbi,
@@ -2937,7 +3254,17 @@ var init_dist2 = __esm({
2937
3254
  approvals: lpToken ? [{ token: lpToken, spender: gauge, amount }] : void 0
2938
3255
  };
2939
3256
  }
2940
- 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
+ }
2941
3268
  const data = encodeFunctionData10({
2942
3269
  abi: gaugeAbi,
2943
3270
  functionName: "withdraw",
@@ -3027,16 +3354,16 @@ var init_dist2 = __esm({
3027
3354
  }
3028
3355
  async buildClaimRewards(gauge, account) {
3029
3356
  if (!this.rpcUrl || !account) {
3030
- const data2 = encodeFunctionData10({
3357
+ const data = encodeFunctionData10({
3031
3358
  abi: gaugeAbi,
3032
3359
  functionName: "getReward",
3033
3360
  args: [account ?? zeroAddress6]
3034
3361
  });
3035
- 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 };
3036
3363
  }
3037
3364
  const { tokens, multiToken } = await this.discoverRewardTokens(gauge);
3038
3365
  if (multiToken && tokens.length > 0) {
3039
- const data2 = encodeFunctionData10({
3366
+ const data = encodeFunctionData10({
3040
3367
  abi: gaugeAbi,
3041
3368
  functionName: "getReward",
3042
3369
  args: [account, tokens]
@@ -3044,41 +3371,179 @@ var init_dist2 = __esm({
3044
3371
  return {
3045
3372
  description: `[${this.protocolName}] Claim gauge rewards (${tokens.length} tokens)`,
3046
3373
  to: gauge,
3047
- data: data2,
3374
+ data,
3048
3375
  value: 0n,
3049
3376
  gas_estimate: 3e5
3050
3377
  };
3051
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) {
3052
3409
  const data = encodeFunctionData10({
3053
3410
  abi: gaugeAbi,
3054
3411
  functionName: "getReward",
3055
- args: []
3412
+ args: [tokenId]
3056
3413
  });
3057
3414
  return {
3058
- description: `[${this.protocolName}] Claim gauge rewards`,
3415
+ description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
3059
3416
  to: gauge,
3060
3417
  data,
3061
3418
  value: 0n,
3062
- gas_estimate: 2e5
3419
+ gas_estimate: 3e5
3063
3420
  };
3064
3421
  }
3065
3422
  /**
3066
- * 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.
3067
3434
  */
3068
- 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
+ }
3069
3502
  const data = encodeFunctionData10({
3070
3503
  abi: gaugeAbi,
3071
3504
  functionName: "getReward",
3072
- args: [tokenId]
3505
+ args: [tokenId, rewardTokens]
3073
3506
  });
3074
3507
  return {
3075
- description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
3508
+ description: `[${this.protocolName}] Claim CL gauge rewards for NFT #${tokenId} (${rewardTokens.length} tokens)`,
3076
3509
  to: gauge,
3077
3510
  data,
3078
3511
  value: 0n,
3079
- gas_estimate: 3e5
3512
+ gas_estimate: 4e5
3080
3513
  };
3081
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
+ }
3082
3547
  async getPendingRewards(gauge, user) {
3083
3548
  if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
3084
3549
  const client = createPublicClient6({ transport: http6(this.rpcUrl) });
@@ -3645,9 +4110,44 @@ var init_dist2 = __esm({
3645
4110
  }
3646
4111
  ];
3647
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
+ }
3648
4147
  /**
3649
4148
  * Build a claim rewards transaction for specific LB bins.
3650
- * 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.
3651
4151
  */
3652
4152
  async buildClaimRewards(user, pool, binIds) {
3653
4153
  const rpcUrl = this.requireRpc();
@@ -3663,15 +4163,18 @@ var init_dist2 = __esm({
3663
4163
  }
3664
4164
  let resolvedBinIds = binIds;
3665
4165
  if (!resolvedBinIds || resolvedBinIds.length === 0) {
3666
- const range = await client.readContract({
3667
- address: rewarder,
3668
- abi: lbRewarderAbi,
3669
- functionName: "getRewardedRange"
3670
- });
3671
- const min = Number(range[0]);
3672
- const max = Number(range[1]);
3673
- resolvedBinIds = [];
3674
- 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
+ }
3675
4178
  }
3676
4179
  const data = encodeFunctionData12({
3677
4180
  abi: lbRewarderAbi,
@@ -4082,7 +4585,7 @@ var init_dist2 = __esm({
4082
4585
  "function enterFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
4083
4586
  "function exitFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
4084
4587
  "function collectRewards((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
4085
- "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)"
4086
4589
  ]);
4087
4590
  positionManagerAbi2 = parseAbi13([
4088
4591
  "function approveForFarming(uint256 tokenId, bool approve, address farmingAddress) external",
@@ -4104,13 +4607,17 @@ var init_dist2 = __esm({
4104
4607
  positionManager;
4105
4608
  rpcUrl;
4106
4609
  factory;
4107
- 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) {
4108
4613
  this.protocolName = protocolName;
4109
4614
  this.farmingCenter = farmingCenter;
4110
4615
  this.eternalFarming = eternalFarming;
4111
4616
  this.positionManager = positionManager;
4112
4617
  this.rpcUrl = rpcUrl;
4113
4618
  this.factory = factory;
4619
+ this.rewardToken = rewardToken;
4620
+ this.bonusRewardToken = bonusRewardToken;
4114
4621
  }
4115
4622
  name() {
4116
4623
  return this.protocolName;
@@ -4125,8 +4632,8 @@ var init_dist2 = __esm({
4125
4632
  const poolLc = pool.toLowerCase();
4126
4633
  if (nonceCache.has(poolLc)) {
4127
4634
  return {
4128
- rewardToken: KITTEN_TOKEN,
4129
- bonusRewardToken: WHYPE_TOKEN,
4635
+ rewardToken: this.rewardToken,
4636
+ bonusRewardToken: this.bonusRewardToken,
4130
4637
  pool,
4131
4638
  nonce: nonceCache.get(poolLc)
4132
4639
  };
@@ -4137,8 +4644,8 @@ var init_dist2 = __esm({
4137
4644
  const nonce = BigInt(n);
4138
4645
  nonces.push(nonce);
4139
4646
  const key = {
4140
- rewardToken: KITTEN_TOKEN,
4141
- bonusRewardToken: WHYPE_TOKEN,
4647
+ rewardToken: this.rewardToken,
4648
+ bonusRewardToken: this.bonusRewardToken,
4142
4649
  pool,
4143
4650
  nonce
4144
4651
  };
@@ -4173,8 +4680,8 @@ var init_dist2 = __esm({
4173
4680
  const nonce = nonces[i];
4174
4681
  nonceCache.set(poolLc, nonce);
4175
4682
  return {
4176
- rewardToken: KITTEN_TOKEN,
4177
- bonusRewardToken: WHYPE_TOKEN,
4683
+ rewardToken: this.rewardToken,
4684
+ bonusRewardToken: this.bonusRewardToken,
4178
4685
  pool,
4179
4686
  nonce
4180
4687
  };
@@ -4266,8 +4773,8 @@ var init_dist2 = __esm({
4266
4773
  }
4267
4774
  const calls = [
4268
4775
  encodeCollectRewards(key, tokenId),
4269
- encodeClaimReward(KITTEN_TOKEN, owner),
4270
- encodeClaimReward(WHYPE_TOKEN, owner)
4776
+ encodeClaimReward(this.rewardToken, owner),
4777
+ encodeClaimReward(this.bonusRewardToken, owner)
4271
4778
  ];
4272
4779
  return {
4273
4780
  description: `[${this.protocolName}] Collect + claim rewards for NFT #${tokenId} in pool ${pool}`,
@@ -4282,8 +4789,8 @@ var init_dist2 = __esm({
4282
4789
  */
4283
4790
  async buildClaimReward(owner) {
4284
4791
  const calls = [
4285
- encodeClaimReward(KITTEN_TOKEN, owner),
4286
- encodeClaimReward(WHYPE_TOKEN, owner)
4792
+ encodeClaimReward(this.rewardToken, owner),
4793
+ encodeClaimReward(this.bonusRewardToken, owner)
4287
4794
  ];
4288
4795
  return {
4289
4796
  description: `[${this.protocolName}] Claim KITTEN + WHYPE farming rewards to ${owner}`,
@@ -4352,8 +4859,8 @@ var init_dist2 = __esm({
4352
4859
  for (const pool of pools) {
4353
4860
  for (let n = 0; n <= MAX_NONCE_SCAN; n++) {
4354
4861
  const key = {
4355
- rewardToken: KITTEN_TOKEN,
4356
- bonusRewardToken: WHYPE_TOKEN,
4862
+ rewardToken: this.rewardToken,
4863
+ bonusRewardToken: this.bonusRewardToken,
4357
4864
  pool,
4358
4865
  nonce: BigInt(n)
4359
4866
  };
@@ -4400,8 +4907,8 @@ var init_dist2 = __esm({
4400
4907
  const isActive = !deactivated;
4401
4908
  if (!bestKey || isActive && !bestActive || isActive === bestActive && nonce > bestKey.nonce) {
4402
4909
  bestKey = {
4403
- rewardToken: KITTEN_TOKEN,
4404
- bonusRewardToken: WHYPE_TOKEN,
4910
+ rewardToken: this.rewardToken,
4911
+ bonusRewardToken: this.bonusRewardToken,
4405
4912
  pool,
4406
4913
  nonce
4407
4914
  };
@@ -4427,6 +4934,235 @@ var init_dist2 = __esm({
4427
4934
  return results;
4428
4935
  }
4429
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
+ };
4430
5166
  POOL_ABI = parseAbi14([
4431
5167
  "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external",
4432
5168
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
@@ -4728,8 +5464,8 @@ var init_dist2 = __esm({
4728
5464
  throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
4729
5465
  });
4730
5466
  const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
4731
- const MAX_UINT256 = 2n ** 256n - 1n;
4732
- 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;
4733
5469
  const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
4734
5470
  const debtUsd = u256ToF64(totalDebtBase) / 1e8;
4735
5471
  const ltvBps = u256ToF64(ltv);
@@ -4893,8 +5629,8 @@ var init_dist2 = __esm({
4893
5629
  throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
4894
5630
  });
4895
5631
  const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
4896
- const MAX_UINT256 = 2n ** 256n - 1n;
4897
- 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;
4898
5634
  const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
4899
5635
  const debtUsd = u256ToF642(totalDebtBase) / 1e18;
4900
5636
  const ltvBps = u256ToF642(ltv);
@@ -5141,7 +5877,8 @@ var init_dist2 = __esm({
5141
5877
  to: this.comet,
5142
5878
  data,
5143
5879
  value: 0n,
5144
- gas_estimate: 3e5
5880
+ gas_estimate: 3e5,
5881
+ approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
5145
5882
  };
5146
5883
  }
5147
5884
  async buildBorrow(params) {
@@ -5169,7 +5906,8 @@ var init_dist2 = __esm({
5169
5906
  to: this.comet,
5170
5907
  data,
5171
5908
  value: 0n,
5172
- gas_estimate: 3e5
5909
+ gas_estimate: 3e5,
5910
+ approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
5173
5911
  };
5174
5912
  }
5175
5913
  async buildWithdraw(params) {
@@ -5358,6 +6096,14 @@ var init_dist2 = __esm({
5358
6096
  "function totalAssets() external view returns (uint256)",
5359
6097
  "function totalSupply() external view returns (uint256)"
5360
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;
5361
6107
  IRM_ABI = parseAbi20([
5362
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)"
5363
6109
  ]);
@@ -5367,6 +6113,9 @@ var init_dist2 = __esm({
5367
6113
  morpho;
5368
6114
  defaultVault;
5369
6115
  rpcUrl;
6116
+ metaMorphoVaults;
6117
+ metaMorphoVaultEntries;
6118
+ vaultAssetMap = null;
5370
6119
  constructor(entry, rpcUrl) {
5371
6120
  this.protocolName = entry.name;
5372
6121
  this.rpcUrl = rpcUrl;
@@ -5375,11 +6124,58 @@ var init_dist2 = __esm({
5375
6124
  if (!morpho) throw DefiError.contractError("Missing 'morpho_blue' contract address");
5376
6125
  this.morpho = morpho;
5377
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;
5378
6158
  }
5379
6159
  name() {
5380
6160
  return this.protocolName;
5381
6161
  }
5382
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
+ }
5383
6179
  const market = defaultMarketParams(params.asset);
5384
6180
  const data = encodeFunctionData19({
5385
6181
  abi: MORPHO_ABI,
@@ -5425,6 +6221,40 @@ var init_dist2 = __esm({
5425
6221
  };
5426
6222
  }
5427
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
+ }
5428
6258
  const market = defaultMarketParams(params.asset);
5429
6259
  const data = encodeFunctionData19({
5430
6260
  abi: MORPHO_ABI,
@@ -5743,7 +6573,7 @@ var init_dist2 = __esm({
5743
6573
  return results;
5744
6574
  }
5745
6575
  };
5746
- ERC4626_ABI = parseAbi23([
6576
+ ERC4626_ABI2 = parseAbi23([
5747
6577
  "function asset() external view returns (address)",
5748
6578
  "function totalAssets() external view returns (uint256)",
5749
6579
  "function totalSupply() external view returns (uint256)",
@@ -5768,7 +6598,7 @@ var init_dist2 = __esm({
5768
6598
  }
5769
6599
  async buildDeposit(assets, receiver) {
5770
6600
  const data = encodeFunctionData21({
5771
- abi: ERC4626_ABI,
6601
+ abi: ERC4626_ABI2,
5772
6602
  functionName: "deposit",
5773
6603
  args: [assets, receiver]
5774
6604
  });
@@ -5782,7 +6612,7 @@ var init_dist2 = __esm({
5782
6612
  }
5783
6613
  async buildWithdraw(assets, receiver, owner) {
5784
6614
  const data = encodeFunctionData21({
5785
- abi: ERC4626_ABI,
6615
+ abi: ERC4626_ABI2,
5786
6616
  functionName: "withdraw",
5787
6617
  args: [assets, receiver, owner]
5788
6618
  });
@@ -5799,7 +6629,7 @@ var init_dist2 = __esm({
5799
6629
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5800
6630
  return client.readContract({
5801
6631
  address: this.vaultAddress,
5802
- abi: ERC4626_ABI,
6632
+ abi: ERC4626_ABI2,
5803
6633
  functionName: "totalAssets"
5804
6634
  }).catch((e) => {
5805
6635
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
@@ -5810,7 +6640,7 @@ var init_dist2 = __esm({
5810
6640
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5811
6641
  return client.readContract({
5812
6642
  address: this.vaultAddress,
5813
- abi: ERC4626_ABI,
6643
+ abi: ERC4626_ABI2,
5814
6644
  functionName: "convertToShares",
5815
6645
  args: [assets]
5816
6646
  }).catch((e) => {
@@ -5822,7 +6652,7 @@ var init_dist2 = __esm({
5822
6652
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5823
6653
  return client.readContract({
5824
6654
  address: this.vaultAddress,
5825
- abi: ERC4626_ABI,
6655
+ abi: ERC4626_ABI2,
5826
6656
  functionName: "convertToAssets",
5827
6657
  args: [shares]
5828
6658
  }).catch((e) => {
@@ -5833,13 +6663,13 @@ var init_dist2 = __esm({
5833
6663
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5834
6664
  const client = createPublicClient18({ transport: http18(this.rpcUrl) });
5835
6665
  const [totalAssets, totalSupply, asset] = await Promise.all([
5836
- 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) => {
5837
6667
  throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
5838
6668
  }),
5839
- 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) => {
5840
6670
  throw DefiError.rpcError(`[${this.protocolName}] totalSupply failed: ${e}`);
5841
6671
  }),
5842
- 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) => {
5843
6673
  throw DefiError.rpcError(`[${this.protocolName}] asset failed: ${e}`);
5844
6674
  })
5845
6675
  ]);
@@ -6713,7 +7543,7 @@ server.tool(
6713
7543
  "defi_status",
6714
7544
  "Show chain and protocol status: lists all protocols deployed on a chain with contract addresses and categories",
6715
7545
  {
6716
- chain: z.string().optional().describe("Chain name (default: hyperevm). E.g. hyperevm, ethereum, arbitrum, base")
7546
+ chain: z.string().optional().describe("Chain name (default: hyperevm). One of: hyperevm, mantle, base, bnb, monad")
6717
7547
  },
6718
7548
  async ({ chain }) => {
6719
7549
  try {
@@ -6936,10 +7766,11 @@ server.tool(
6936
7766
  token_a: z.string().describe("First token symbol or address"),
6937
7767
  token_b: z.string().describe("Second token symbol or address"),
6938
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)"),
6939
7770
  recipient: z.string().optional().describe("Recipient address for returned tokens"),
6940
7771
  broadcast: z.boolean().optional().describe("Set true to broadcast (default: false)")
6941
7772
  },
6942
- async ({ chain, protocol, token_a, token_b, liquidity, recipient, broadcast }) => {
7773
+ async ({ chain, protocol, token_a, token_b, liquidity, token_id, recipient, broadcast }) => {
6943
7774
  try {
6944
7775
  const chainName = chain ?? "hyperevm";
6945
7776
  const registry = getRegistry();
@@ -6954,7 +7785,8 @@ server.tool(
6954
7785
  token_a: tokenA,
6955
7786
  token_b: tokenB,
6956
7787
  liquidity: BigInt(liquidity),
6957
- recipient: recipientAddr
7788
+ recipient: recipientAddr,
7789
+ token_id: token_id ? BigInt(token_id) : void 0
6958
7790
  });
6959
7791
  const executor = makeExecutor(broadcast ?? false, chainConfig.effectiveRpcUrl(), chainConfig.explorer_url);
6960
7792
  const result = await executor.execute(tx);
@@ -6968,8 +7800,8 @@ server.tool(
6968
7800
  "defi_bridge",
6969
7801
  "Get a cross-chain bridge quote via LI.FI, deBridge DLN, or Circle CCTP. Returns estimated output amount and fees",
6970
7802
  {
6971
- from_chain: z.string().describe("Source chain name, e.g. ethereum, arbitrum, base"),
6972
- to_chain: z.string().describe("Destination chain name, e.g. hyperevm, arbitrum"),
7803
+ from_chain: z.string().describe("Source chain name (supported source chains: hyperevm, mantle, base, bnb, monad)"),
7804
+ to_chain: z.string().describe("Destination chain name. CCTP V2 destinations include ethereum, arbitrum, optimism, polygon, avalanche; LI.FI/deBridge accept any chain"),
6973
7805
  token: z.string().optional().describe("Token symbol to bridge (default: USDC). Use native for native token"),
6974
7806
  amount: z.string().describe("Amount in human-readable units, e.g. '100' for 100 USDC"),
6975
7807
  recipient: z.string().optional().describe("Recipient address on destination chain")
@@ -7633,6 +8465,352 @@ server.tool(
7633
8465
  };
7634
8466
  }
7635
8467
  );
8468
+ server.tool(
8469
+ "defi_lp_compound",
8470
+ "V3 fee auto-compound: collect accrued LP fees and re-add them as liquidity in one tx. Requires NFT tokenId. Returns simulated/confirmed tx with collected fee amounts.",
8471
+ {
8472
+ chain: z.string().describe("Chain name (e.g. base, hyperevm, mantle)"),
8473
+ protocol: z.string().describe("Protocol slug (e.g. uniswap-v3-base, hyperswap, project-x)"),
8474
+ token_id: z.string().describe("NFT tokenId of the LP position to compound"),
8475
+ slippage_bps: z.number().optional().describe("Slippage tolerance in bps (default: 50 = 0.5%)"),
8476
+ address: z.string().optional().describe("Recipient address (defaults to DEFI_WALLET_ADDRESS)"),
8477
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8478
+ },
8479
+ async ({ chain, protocol, token_id, slippage_bps, address, broadcast }) => {
8480
+ try {
8481
+ const chainName = chain.toLowerCase();
8482
+ const registry = getRegistry();
8483
+ const chainConfig = registry.getChain(chainName);
8484
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8485
+ const proto = registry.getProtocol(protocol);
8486
+ const recipient = address ?? process.env["DEFI_WALLET_ADDRESS"];
8487
+ if (!recipient) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8488
+ const adapter = createDex(proto, rpcUrl);
8489
+ if (!("buildCompound" in adapter) || typeof adapter.buildCompound !== "function") {
8490
+ throw new Error(`[${proto.name}] adapter does not support compound (V3 fee-only protocols only)`);
8491
+ }
8492
+ const compoundOpts = slippage_bps !== void 0 ? { slippageBps: slippage_bps } : void 0;
8493
+ const tx = await adapter.buildCompound(BigInt(token_id), recipient, compoundOpts);
8494
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8495
+ const result = await executor.execute(tx);
8496
+ return {
8497
+ content: [{ type: "text", text: ok(result, { chain: chainName, protocol, token_id, broadcast: broadcast ?? false }) }]
8498
+ };
8499
+ } catch (e) {
8500
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol, token_id }) }], isError: true };
8501
+ }
8502
+ }
8503
+ );
8504
+ server.tool(
8505
+ "defi_lp_claim",
8506
+ "Claim LP rewards from a pool. Auto-dispatches by protocol interface: V3 fees (NPM.collect), gauge emission (Solidly/Hybra/Ramses CL), Merchant Moe LB hooks, KittenSwap eternal farming, off-chain Nest tickets. Requires either token-id (CL/NFT positions) or gauge (V2-style staking) plus pool for LB.",
8507
+ {
8508
+ chain: z.string().describe("Chain name"),
8509
+ protocol: z.string().describe("Protocol slug"),
8510
+ pool: z.string().optional().describe("Pool address (required for LB / KittenSwap farming)"),
8511
+ gauge: z.string().optional().describe("Gauge address (required for solidly/hybra)"),
8512
+ token_id: z.string().optional().describe("NFT tokenId (CL gauge or farming positions)"),
8513
+ bins: z.string().optional().describe("Comma-separated bin IDs (Merchant Moe LB; auto-detected if omitted)"),
8514
+ address: z.string().optional().describe("Wallet address (defaults to DEFI_WALLET_ADDRESS)"),
8515
+ redeem_type: z.number().optional().describe("Hybra: 0=instant exit (penalty), 1=2yr veHYBR lock (default)"),
8516
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8517
+ },
8518
+ async ({ chain, protocol, pool, gauge, token_id, bins, address, redeem_type, broadcast }) => {
8519
+ try {
8520
+ const chainName = chain.toLowerCase();
8521
+ const registry = getRegistry();
8522
+ const chainConfig = registry.getChain(chainName);
8523
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8524
+ const proto = registry.getProtocol(protocol);
8525
+ const account = address ?? process.env["DEFI_WALLET_ADDRESS"];
8526
+ if (!account) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8527
+ const iface = proto.interface;
8528
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8529
+ const { createKittenSwapFarming: createKittenSwapFarming2, createMerchantMoeLB: createMerchantMoeLB2, createGauge: createGauge2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8530
+ const isV3Fee = proto.reward_strategy === "lp_fee_only" || iface === "uniswap_v3" && token_id && !gauge;
8531
+ if (isV3Fee) {
8532
+ if (!token_id) throw new Error("token_id required for V3 fee collection");
8533
+ const adapter = createDex(proto, rpcUrl);
8534
+ if (!("buildCollectFees" in adapter) || typeof adapter.buildCollectFees !== "function") {
8535
+ throw new Error(`[${proto.name}] adapter does not support buildCollectFees`);
8536
+ }
8537
+ const tx = await adapter.buildCollectFees(BigInt(token_id), account);
8538
+ const result = await executor.execute(tx);
8539
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "v3_fee", broadcast: broadcast ?? false }) }] };
8540
+ }
8541
+ if (iface === "algebra_v3" && proto.contracts?.["farming_center"]) {
8542
+ if (!pool) throw new Error("pool required for KittenSwap farming claim");
8543
+ if (!token_id) throw new Error("token_id required for KittenSwap farming claim");
8544
+ const adapter = createKittenSwapFarming2(proto, rpcUrl);
8545
+ const tx = await adapter.buildCollectRewards(BigInt(token_id), pool, account);
8546
+ const result = await executor.execute(tx);
8547
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "kittenswap_farming", broadcast: broadcast ?? false }) }] };
8548
+ }
8549
+ if (iface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8550
+ if (!pool) throw new Error("pool required for Merchant Moe LB claim");
8551
+ const adapter = createMerchantMoeLB2(proto, rpcUrl);
8552
+ const binIds = bins ? bins.split(",").map((s) => parseInt(s.trim(), 10)) : void 0;
8553
+ const tx = await adapter.buildClaimRewards(account, pool, binIds);
8554
+ const result = await executor.execute(tx);
8555
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "lb", broadcast: broadcast ?? false }) }] };
8556
+ }
8557
+ if (["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(iface) || iface === "uniswap_v3" && proto.contracts?.["voter"]) {
8558
+ if (!gauge) throw new Error("gauge required for gauge-based claim");
8559
+ const adapter = createGauge2(proto, rpcUrl);
8560
+ let tx;
8561
+ if (token_id && iface === "uniswap_v3" && proto.reward_strategy === "auto_stake" && "buildClaimRewardsViaNPMPeriodReward" in adapter && typeof adapter.buildClaimRewardsViaNPMPeriodReward === "function") {
8562
+ const npm = proto.contracts?.["position_manager"];
8563
+ if (!npm) throw new Error(`${proto.name} requires contracts.position_manager for NPM-based claim`);
8564
+ tx = await adapter.buildClaimRewardsViaNPMPeriodReward(npm, BigInt(token_id), account, { gauge });
8565
+ } else if (token_id) {
8566
+ if (!adapter.buildClaimRewardsByTokenId) throw new Error(`${proto.name} does not support NFT-based claim`);
8567
+ const claimOpts = redeem_type !== void 0 ? { redeemType: redeem_type } : void 0;
8568
+ tx = await adapter.buildClaimRewardsByTokenId(gauge, BigInt(token_id), claimOpts);
8569
+ } else {
8570
+ tx = await adapter.buildClaimRewards(gauge, account);
8571
+ }
8572
+ const result = await executor.execute(tx);
8573
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "gauge", broadcast: broadcast ?? false }) }] };
8574
+ }
8575
+ throw new Error(`No claim method found for protocol interface '${iface}'`);
8576
+ } catch (e) {
8577
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol }) }], isError: true };
8578
+ }
8579
+ }
8580
+ );
8581
+ server.tool(
8582
+ "defi_lp_farm",
8583
+ "Add liquidity and auto-stake into gauge/farming for emissions. Two-step flow: mint LP NFT, then deposit into gauge (Solidly/Hybra) or enterFarming (KittenSwap eternal). Merchant Moe LB hooks need no staking.",
8584
+ {
8585
+ chain: z.string().describe("Chain name"),
8586
+ protocol: z.string().describe("Protocol slug"),
8587
+ token_a: z.string().describe("First token symbol or address"),
8588
+ token_b: z.string().describe("Second token symbol or address"),
8589
+ amount_a: z.string().describe("Amount of token A in wei"),
8590
+ amount_b: z.string().describe("Amount of token B in wei"),
8591
+ pool: z.string().optional().describe("Pool name (e.g. WHYPE/USDC) or address"),
8592
+ gauge: z.string().optional().describe("Gauge address (auto-resolved if omitted)"),
8593
+ recipient: z.string().optional().describe("Recipient/owner (defaults to DEFI_WALLET_ADDRESS)"),
8594
+ tick_lower: z.number().optional().describe("Lower tick (CL)"),
8595
+ tick_upper: z.number().optional().describe("Upper tick (CL)"),
8596
+ range_pct: z.number().optional().describe("\xB1N% concentrated range around current price"),
8597
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8598
+ },
8599
+ async ({ chain, protocol, token_a, token_b, amount_a, amount_b, pool, gauge, recipient, tick_lower, tick_upper, range_pct, broadcast }) => {
8600
+ try {
8601
+ const chainName = chain.toLowerCase();
8602
+ const registry = getRegistry();
8603
+ const chainConfig = registry.getChain(chainName);
8604
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8605
+ const proto = registry.getProtocol(protocol);
8606
+ const owner = recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
8607
+ const tokenA = resolveToken(registry, chainName, token_a);
8608
+ const tokenB = resolveToken(registry, chainName, token_b);
8609
+ let poolAddr;
8610
+ if (pool) {
8611
+ poolAddr = pool.startsWith("0x") ? pool : registry.resolvePool(protocol, pool).address;
8612
+ }
8613
+ const dexAdapter = createDex(proto, rpcUrl);
8614
+ const addTx = await dexAdapter.buildAddLiquidity({
8615
+ protocol: proto.name,
8616
+ token_a: tokenA,
8617
+ token_b: tokenB,
8618
+ amount_a: BigInt(amount_a),
8619
+ amount_b: BigInt(amount_b),
8620
+ recipient: owner,
8621
+ tick_lower,
8622
+ tick_upper,
8623
+ range_pct,
8624
+ pool: poolAddr
8625
+ });
8626
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8627
+ const addResult = await executor.execute(addTx);
8628
+ const steps = [{ step: "lp_add", ...addResult }];
8629
+ if (addResult.status !== "confirmed" && addResult.status !== "simulated") {
8630
+ return { content: [{ type: "text", text: ok({ steps, note: "LP add did not succeed; staking skipped." }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8631
+ }
8632
+ const mintedTokenId = addResult.details?.minted_token_id ? BigInt(addResult.details.minted_token_id) : void 0;
8633
+ const iface = proto.interface;
8634
+ const { createKittenSwapFarming: createKittenSwapFarming2, createGauge: createGauge2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8635
+ if (iface === "algebra_v3" && proto.contracts?.["farming_center"]) {
8636
+ if (!mintedTokenId) {
8637
+ steps.push({ step: "stake_farming", skipped: "no tokenId in dry-run mode" });
8638
+ } else if (!poolAddr) {
8639
+ throw new Error("pool required for KittenSwap farming");
8640
+ } else {
8641
+ const farmAdapter = createKittenSwapFarming2(proto, rpcUrl);
8642
+ const stakeTx = await farmAdapter.buildEnterFarming(mintedTokenId, poolAddr, owner);
8643
+ const stakeResult = await executor.execute(stakeTx);
8644
+ steps.push({ step: "stake_farming", ...stakeResult });
8645
+ }
8646
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8647
+ }
8648
+ const isGaugeStakeable = ["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && proto.contracts?.["voter"];
8649
+ if (isGaugeStakeable) {
8650
+ if (!mintedTokenId && iface !== "solidly_v2") {
8651
+ steps.push({ step: "stake_gauge", skipped: "no tokenId in dry-run mode" });
8652
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8653
+ }
8654
+ let gaugeAddr = gauge;
8655
+ const gaugeAdapter = createGauge2(proto, rpcUrl);
8656
+ if (!gaugeAddr && poolAddr && gaugeAdapter.resolveGauge) {
8657
+ try {
8658
+ gaugeAddr = await gaugeAdapter.resolveGauge(poolAddr);
8659
+ } catch {
8660
+ }
8661
+ }
8662
+ if (!gaugeAddr) throw new Error("gauge required (could not auto-resolve)");
8663
+ let amountArg = 0n;
8664
+ if (iface === "solidly_v2" && poolAddr) {
8665
+ const { createPublicClient: createPublicClient24, http: httpTransport, parseAbi: parseAbi31 } = await import("viem");
8666
+ const erc20Abi3 = parseAbi31(["function balanceOf(address) view returns (uint256)"]);
8667
+ const lpClient = createPublicClient24({ transport: httpTransport(rpcUrl) });
8668
+ amountArg = await lpClient.readContract({
8669
+ address: poolAddr,
8670
+ abi: erc20Abi3,
8671
+ functionName: "balanceOf",
8672
+ args: [owner]
8673
+ });
8674
+ if (amountArg === 0n) {
8675
+ steps.push({ step: "stake_gauge", skipped: "LP balance 0 after mint" });
8676
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8677
+ }
8678
+ }
8679
+ const lpTokenArg = iface === "solidly_v2" ? poolAddr : void 0;
8680
+ const stakeTx = await gaugeAdapter.buildDeposit(gaugeAddr, amountArg, mintedTokenId, lpTokenArg);
8681
+ const stakeResult = await executor.execute(stakeTx);
8682
+ steps.push({ step: "stake_gauge", ...stakeResult });
8683
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8684
+ }
8685
+ if (iface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8686
+ steps.push({ step: "stake_gauge", skipped: "Merchant Moe LB hooks auto-handle rewards" });
8687
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8688
+ }
8689
+ steps.push({ step: "stake_gauge", skipped: "No staking adapter for this interface" });
8690
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8691
+ } catch (e) {
8692
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol }) }], isError: true };
8693
+ }
8694
+ }
8695
+ );
8696
+ server.tool(
8697
+ "defi_lp_positions",
8698
+ "Show all LP positions for a wallet across protocols on a chain. Includes Merchant Moe LB bins (with pending MOE), V3/Algebra/Hybra NFT positions. Auto-detects user's actual bin IDs for LB pools.",
8699
+ {
8700
+ chain: z.string().describe("Chain name"),
8701
+ protocol: z.string().optional().describe("Filter to a single protocol slug"),
8702
+ pool: z.string().optional().describe("Filter to a specific pool address"),
8703
+ bins: z.string().optional().describe("Comma-separated bin IDs (LB; auto-detected if omitted)"),
8704
+ address: z.string().optional().describe("Wallet address (defaults to DEFI_WALLET_ADDRESS)")
8705
+ },
8706
+ async ({ chain, protocol, pool, bins, address }) => {
8707
+ try {
8708
+ const chainName = chain.toLowerCase();
8709
+ const registry = getRegistry();
8710
+ const chainConfig = registry.getChain(chainName);
8711
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8712
+ const user = address ?? process.env["DEFI_WALLET_ADDRESS"];
8713
+ if (!user) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8714
+ const { createMerchantMoeLB: createMerchantMoeLB2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8715
+ const { createPublicClient: createPublicClient24, http: httpTransport, parseAbi: parseAbi31 } = await import("viem");
8716
+ const allProtocols = registry.getProtocolsForChain(chainName);
8717
+ const protocols = protocol ? [registry.getProtocol(protocol)] : allProtocols;
8718
+ const results = [];
8719
+ await Promise.allSettled(
8720
+ protocols.map(async (proto) => {
8721
+ try {
8722
+ if (proto.interface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8723
+ const adapter = createMerchantMoeLB2(proto, rpcUrl);
8724
+ const binIds = bins ? bins.split(",").map((s) => parseInt(s.trim(), 10)) : void 0;
8725
+ const poolsToScan = pool ? [pool] : (await adapter.discoverRewardedPools()).map((p) => p.pool);
8726
+ for (const poolAddr of poolsToScan) {
8727
+ try {
8728
+ const userBins = binIds ?? await adapter.findUserBinsWithBalance(poolAddr, user);
8729
+ if (userBins.length === 0) continue;
8730
+ const positions = await adapter.getUserPositions(user, poolAddr, userBins);
8731
+ if (positions.length === 0) continue;
8732
+ const pending = await adapter.getPendingRewards(user, poolAddr, userBins).catch(() => []);
8733
+ const totalPending = pending.reduce((s, r) => s + (r.amount ?? 0n), 0n);
8734
+ for (const pos of positions) {
8735
+ results.push({
8736
+ protocol: proto.slug,
8737
+ type: "lb",
8738
+ pool: poolAddr,
8739
+ ...pos,
8740
+ pending_reward: totalPending.toString(),
8741
+ pending_reward_token: pending[0]?.token
8742
+ });
8743
+ }
8744
+ } catch {
8745
+ }
8746
+ }
8747
+ }
8748
+ const npm = proto.contracts?.["position_manager"];
8749
+ if (npm && ["uniswap_v3", "algebra_v3", "hybra"].includes(proto.interface)) {
8750
+ const npmAbi = parseAbi31([
8751
+ "function balanceOf(address) view returns (uint256)",
8752
+ "function tokenOfOwnerByIndex(address, uint256) view returns (uint256)",
8753
+ "function positions(uint256) view returns (uint96 nonce, address op, address t0, address t1, uint24 fee, int24 tl, int24 tu, uint128 liq, uint256 a, uint256 b, uint128 o0, uint128 o1)"
8754
+ ]);
8755
+ const ramsesAbi = parseAbi31([
8756
+ "function positions(uint256) view returns (address t0, address t1, int24 ts, int24 tl, int24 tu, uint128 liq, uint256 a, uint256 b, uint128 o0, uint128 o1)"
8757
+ ]);
8758
+ const client = createPublicClient24({ transport: httpTransport(rpcUrl) });
8759
+ let count;
8760
+ try {
8761
+ count = await client.readContract({ address: npm, abi: npmAbi, functionName: "balanceOf", args: [user] });
8762
+ } catch {
8763
+ return;
8764
+ }
8765
+ const max = Math.min(Number(count), 50);
8766
+ for (let i = 0; i < max; i++) {
8767
+ try {
8768
+ const tokenId = await client.readContract({
8769
+ address: npm,
8770
+ abi: npmAbi,
8771
+ functionName: "tokenOfOwnerByIndex",
8772
+ args: [user, BigInt(i)]
8773
+ });
8774
+ let liq = 0n;
8775
+ let token0;
8776
+ let token1;
8777
+ let tickLower;
8778
+ let tickUpper;
8779
+ try {
8780
+ const r = await client.readContract({ address: npm, abi: ramsesAbi, functionName: "positions", args: [tokenId] });
8781
+ [token0, token1, , tickLower, tickUpper, liq] = r;
8782
+ } catch {
8783
+ const r = await client.readContract({ address: npm, abi: npmAbi, functionName: "positions", args: [tokenId] });
8784
+ [, , token0, token1, , tickLower, tickUpper, liq] = r;
8785
+ }
8786
+ if (liq > 0n) {
8787
+ results.push({
8788
+ protocol: proto.slug,
8789
+ type: "v3_nft",
8790
+ token_id: tokenId.toString(),
8791
+ token0,
8792
+ token1,
8793
+ liquidity: liq.toString(),
8794
+ tick_lower: tickLower,
8795
+ tick_upper: tickUpper
8796
+ });
8797
+ }
8798
+ } catch {
8799
+ }
8800
+ }
8801
+ }
8802
+ } catch {
8803
+ }
8804
+ })
8805
+ );
8806
+ return {
8807
+ content: [{ type: "text", text: ok({ chain: chainName, positions: results, total: results.length }, { wallet: user, scanned_protocols: protocols.length }) }]
8808
+ };
8809
+ } catch (e) {
8810
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain }) }], isError: true };
8811
+ }
8812
+ }
8813
+ );
7636
8814
  var transport = new StdioServerTransport();
7637
8815
  await server.connect(transport);
7638
8816
  //# sourceMappingURL=mcp-server.js.map