@hypurrquant/defi-cli 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -265
- package/config/pools.example.toml +1 -1
- package/dist/index.js +1489 -127
- package/dist/index.js.map +1 -1
- package/dist/main.js +1490 -128
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +917 -85
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/defi-cli/SKILL.md +0 -1
- package/skills/defi-cli/references/protocols.md +0 -1
- package/config/protocols/lending/lendle_mantle.toml +0 -14
package/dist/index.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
4
|
var __esm = (fn, res) => function __init() {
|
|
11
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
6
|
};
|
|
@@ -230,6 +224,7 @@ var init_dist = __esm({
|
|
|
230
224
|
native_token;
|
|
231
225
|
wrapped_native;
|
|
232
226
|
multicall3;
|
|
227
|
+
aggregators;
|
|
233
228
|
effectiveRpcUrl() {
|
|
234
229
|
const chainEnv = this.name.toUpperCase().replace(/ /g, "_") + "_RPC_URL";
|
|
235
230
|
return process.env[chainEnv] ?? process.env["HYPEREVM_RPC_URL"] ?? this.rpc_url;
|
|
@@ -332,7 +327,7 @@ var init_dist = __esm({
|
|
|
332
327
|
}
|
|
333
328
|
getProtocolsForChain(chain, includeUnverified = false) {
|
|
334
329
|
return this.protocols.filter(
|
|
335
|
-
(p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false)
|
|
330
|
+
(p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false) && p.is_active !== false
|
|
336
331
|
);
|
|
337
332
|
}
|
|
338
333
|
resolveToken(chain, symbol) {
|
|
@@ -394,6 +389,7 @@ __export(dist_exports, {
|
|
|
394
389
|
MasterChefAdapter: () => MasterChefAdapter,
|
|
395
390
|
MerchantMoeLBAdapter: () => MerchantMoeLBAdapter,
|
|
396
391
|
MorphoBlueAdapter: () => MorphoBlueAdapter,
|
|
392
|
+
NestOffChainAdapter: () => NestOffChainAdapter,
|
|
397
393
|
PendleAdapter: () => PendleAdapter,
|
|
398
394
|
RyskAdapter: () => RyskAdapter,
|
|
399
395
|
SolidlyAdapter: () => SolidlyAdapter,
|
|
@@ -412,10 +408,12 @@ __export(dist_exports, {
|
|
|
412
408
|
createLiquidStaking: () => createLiquidStaking,
|
|
413
409
|
createMasterChef: () => createMasterChef,
|
|
414
410
|
createMerchantMoeLB: () => createMerchantMoeLB,
|
|
411
|
+
createNestOffChain: () => createNestOffChain,
|
|
415
412
|
createNft: () => createNft,
|
|
416
413
|
createOptions: () => createOptions,
|
|
417
414
|
createOracleFromCdp: () => createOracleFromCdp,
|
|
418
415
|
createOracleFromLending: () => createOracleFromLending,
|
|
416
|
+
createRewardReader: () => createRewardReader,
|
|
419
417
|
createVault: () => createVault,
|
|
420
418
|
createYieldSource: () => createYieldSource
|
|
421
419
|
});
|
|
@@ -656,8 +654,8 @@ function encodeClaimReward(rewardToken, to) {
|
|
|
656
654
|
return encodeFunctionData13({
|
|
657
655
|
abi: farmingCenterAbi,
|
|
658
656
|
functionName: "claimReward",
|
|
659
|
-
args: [rewardToken, to, 2n **
|
|
660
|
-
// max uint128
|
|
657
|
+
args: [rewardToken, to, 2n ** 256n - 1n]
|
|
658
|
+
// max uint256 (KittenSwap variant uses uint256 amount, not uint128)
|
|
661
659
|
});
|
|
662
660
|
}
|
|
663
661
|
function encodeMulticall(calls) {
|
|
@@ -900,6 +898,46 @@ function createOracleFromCdp(entry, _asset, rpcUrl) {
|
|
|
900
898
|
function createMerchantMoeLB(entry, rpcUrl) {
|
|
901
899
|
return new MerchantMoeLBAdapter(entry, rpcUrl);
|
|
902
900
|
}
|
|
901
|
+
function createNestOffChain(entry) {
|
|
902
|
+
return new NestOffChainAdapter(entry);
|
|
903
|
+
}
|
|
904
|
+
function createRewardReader(entry, rpcUrl, tokens) {
|
|
905
|
+
const strategy = entry.reward_strategy ?? inferRewardStrategy(entry);
|
|
906
|
+
switch (strategy) {
|
|
907
|
+
case "off_chain_api":
|
|
908
|
+
return { kind: "off_chain_api", adapter: new NestOffChainAdapter(entry) };
|
|
909
|
+
case "on_chain_farming_center":
|
|
910
|
+
if (!rpcUrl) throw DefiError.invalidParam("createRewardReader: rpcUrl required for on_chain_farming_center");
|
|
911
|
+
return { kind: "on_chain_farming_center", adapter: createKittenSwapFarming(entry, rpcUrl) };
|
|
912
|
+
case "on_chain_gauge_tokenid":
|
|
913
|
+
return { kind: "on_chain_gauge_tokenid", adapter: new HybraGaugeAdapter(entry, rpcUrl) };
|
|
914
|
+
case "on_chain_gauge":
|
|
915
|
+
return { kind: "on_chain_gauge", adapter: new SolidlyGaugeAdapter(entry, rpcUrl, tokens) };
|
|
916
|
+
case "auto_stake":
|
|
917
|
+
return { kind: "auto_stake", adapter: new SolidlyGaugeAdapter(entry, rpcUrl, tokens) };
|
|
918
|
+
case "on_chain_masterchef":
|
|
919
|
+
return { kind: "on_chain_masterchef", adapter: new MasterChefAdapter(entry, rpcUrl) };
|
|
920
|
+
case "none":
|
|
921
|
+
return { kind: "none" };
|
|
922
|
+
default:
|
|
923
|
+
throw DefiError.unsupported(`Unknown reward_strategy '${strategy}' on '${entry.slug}'`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function inferRewardStrategy(entry) {
|
|
927
|
+
if (entry.interface === "hybra" || entry.contracts?.["gauge_manager"]) {
|
|
928
|
+
return "on_chain_gauge_tokenid";
|
|
929
|
+
}
|
|
930
|
+
if (entry.contracts?.["farming_center"] && entry.contracts?.["eternal_farming"]) {
|
|
931
|
+
return "on_chain_farming_center";
|
|
932
|
+
}
|
|
933
|
+
if (entry.contracts?.["voter"]) {
|
|
934
|
+
return "on_chain_gauge";
|
|
935
|
+
}
|
|
936
|
+
if (entry.contracts?.["master_chef"] || entry.contracts?.["masterChef"]) {
|
|
937
|
+
return "on_chain_masterchef";
|
|
938
|
+
}
|
|
939
|
+
return "none";
|
|
940
|
+
}
|
|
903
941
|
function createKittenSwapFarming(entry, rpcUrl) {
|
|
904
942
|
const farmingCenter = entry.contracts?.["farming_center"];
|
|
905
943
|
if (!farmingCenter) {
|
|
@@ -914,9 +952,11 @@ function createKittenSwapFarming(entry, rpcUrl) {
|
|
|
914
952
|
throw new DefiError("CONTRACT_ERROR", `[${entry.name}] Missing 'position_manager' contract address`);
|
|
915
953
|
}
|
|
916
954
|
const factory = entry.contracts?.["factory"];
|
|
917
|
-
|
|
955
|
+
const rewardToken = entry.contracts?.["reward_token"];
|
|
956
|
+
const bonusRewardToken = entry.contracts?.["bonus_reward_token"];
|
|
957
|
+
return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken, bonusRewardToken);
|
|
918
958
|
}
|
|
919
|
-
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_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter,
|
|
959
|
+
var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
|
|
920
960
|
var init_dist2 = __esm({
|
|
921
961
|
"../defi-protocols/dist/index.js"() {
|
|
922
962
|
"use strict";
|
|
@@ -954,6 +994,7 @@ var init_dist2 = __esm({
|
|
|
954
994
|
init_dist();
|
|
955
995
|
init_dist();
|
|
956
996
|
init_dist();
|
|
997
|
+
init_dist();
|
|
957
998
|
DEFAULT_FEE = 3e3;
|
|
958
999
|
swapRouterAbi = parseAbi4([
|
|
959
1000
|
"struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
|
|
@@ -969,7 +1010,19 @@ var init_dist2 = __esm({
|
|
|
969
1010
|
]);
|
|
970
1011
|
positionManagerAbi = parseAbi4([
|
|
971
1012
|
"struct MintParams { address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
|
|
972
|
-
"function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
1013
|
+
"function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)",
|
|
1014
|
+
"struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
|
|
1015
|
+
"function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
1016
|
+
"struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
|
|
1017
|
+
"function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
1018
|
+
"struct IncreaseLiquidityParams { uint256 tokenId; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
|
|
1019
|
+
"function increaseLiquidity(IncreaseLiquidityParams calldata params) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1)",
|
|
1020
|
+
"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)",
|
|
1021
|
+
"function multicall(bytes[] data) external payable returns (bytes[] memory results)"
|
|
1022
|
+
]);
|
|
1023
|
+
slipstreamMintAbi = parseAbi4([
|
|
1024
|
+
"struct SlipstreamMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; uint160 sqrtPriceX96; }",
|
|
1025
|
+
"function mint(SlipstreamMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
973
1026
|
]);
|
|
974
1027
|
UniswapV3Adapter = class {
|
|
975
1028
|
protocolName;
|
|
@@ -992,7 +1045,7 @@ var init_dist2 = __esm({
|
|
|
992
1045
|
this.factory = entry.contracts?.["factory"];
|
|
993
1046
|
this.fee = DEFAULT_FEE;
|
|
994
1047
|
this.rpcUrl = rpcUrl;
|
|
995
|
-
this.useTickSpacingQuoter = entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
|
|
1048
|
+
this.useTickSpacingQuoter = entry.cl_style === "slipstream" || entry.cl_style === "ramses" || entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
|
|
996
1049
|
}
|
|
997
1050
|
name() {
|
|
998
1051
|
return this.protocolName;
|
|
@@ -1168,16 +1221,64 @@ var init_dist2 = __esm({
|
|
|
1168
1221
|
const [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
|
|
1169
1222
|
const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
|
|
1170
1223
|
const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
|
|
1171
|
-
|
|
1224
|
+
let thirdField = this.fee;
|
|
1225
|
+
let tickLower = -887220;
|
|
1226
|
+
let tickUpper = 887220;
|
|
1227
|
+
if (params.pool && this.rpcUrl) {
|
|
1228
|
+
const poolAbi2 = parseAbi4([
|
|
1229
|
+
"function fee() view returns (uint24)",
|
|
1230
|
+
"function tickSpacing() view returns (int24)",
|
|
1231
|
+
"function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, bool)"
|
|
1232
|
+
]);
|
|
1233
|
+
const client = createPublicClient4({ transport: http4(this.rpcUrl) });
|
|
1234
|
+
const [poolFee, poolTs, slot0] = await Promise.all([
|
|
1235
|
+
client.readContract({ address: params.pool, abi: poolAbi2, functionName: "fee" }).catch(() => null),
|
|
1236
|
+
client.readContract({ address: params.pool, abi: poolAbi2, functionName: "tickSpacing" }).catch(() => null),
|
|
1237
|
+
client.readContract({ address: params.pool, abi: poolAbi2, functionName: "slot0" }).catch(() => null)
|
|
1238
|
+
]);
|
|
1239
|
+
if (this.useTickSpacingQuoter && poolTs !== null) {
|
|
1240
|
+
thirdField = poolTs;
|
|
1241
|
+
} else if (poolFee !== null) {
|
|
1242
|
+
thirdField = poolFee;
|
|
1243
|
+
}
|
|
1244
|
+
if (params.range_pct !== void 0 && slot0 && poolTs !== null) {
|
|
1245
|
+
const currentTick = slot0[1];
|
|
1246
|
+
const rangeTicks = Math.floor(params.range_pct * 100);
|
|
1247
|
+
tickLower = Math.floor((currentTick - rangeTicks) / poolTs) * poolTs;
|
|
1248
|
+
tickUpper = Math.ceil((currentTick + rangeTicks) / poolTs) * poolTs;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (params.tick_lower !== void 0) tickLower = params.tick_lower;
|
|
1252
|
+
if (params.tick_upper !== void 0) tickUpper = params.tick_upper;
|
|
1253
|
+
const data = this.useTickSpacingQuoter ? encodeFunctionData4({
|
|
1254
|
+
abi: slipstreamMintAbi,
|
|
1255
|
+
functionName: "mint",
|
|
1256
|
+
args: [
|
|
1257
|
+
{
|
|
1258
|
+
token0,
|
|
1259
|
+
token1,
|
|
1260
|
+
tickSpacing: thirdField,
|
|
1261
|
+
tickLower,
|
|
1262
|
+
tickUpper,
|
|
1263
|
+
amount0Desired: amount0,
|
|
1264
|
+
amount1Desired: amount1,
|
|
1265
|
+
amount0Min: 0n,
|
|
1266
|
+
amount1Min: 0n,
|
|
1267
|
+
recipient: params.recipient,
|
|
1268
|
+
deadline: BigInt("18446744073709551615"),
|
|
1269
|
+
sqrtPriceX96: 0n
|
|
1270
|
+
}
|
|
1271
|
+
]
|
|
1272
|
+
}) : encodeFunctionData4({
|
|
1172
1273
|
abi: positionManagerAbi,
|
|
1173
1274
|
functionName: "mint",
|
|
1174
1275
|
args: [
|
|
1175
1276
|
{
|
|
1176
1277
|
token0,
|
|
1177
1278
|
token1,
|
|
1178
|
-
fee:
|
|
1179
|
-
tickLower
|
|
1180
|
-
tickUpper
|
|
1279
|
+
fee: thirdField,
|
|
1280
|
+
tickLower,
|
|
1281
|
+
tickUpper,
|
|
1181
1282
|
amount0Desired: amount0,
|
|
1182
1283
|
amount1Desired: amount1,
|
|
1183
1284
|
amount0Min: 0n,
|
|
@@ -1199,10 +1300,125 @@ var init_dist2 = __esm({
|
|
|
1199
1300
|
]
|
|
1200
1301
|
};
|
|
1201
1302
|
}
|
|
1202
|
-
async buildRemoveLiquidity(
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1303
|
+
async buildRemoveLiquidity(params) {
|
|
1304
|
+
const pm = this.positionManager;
|
|
1305
|
+
if (!pm) {
|
|
1306
|
+
throw DefiError.contractError(
|
|
1307
|
+
`[${this.protocolName}] Missing 'position_manager' for liquidity removal`
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
if (!params.token_id) {
|
|
1311
|
+
throw DefiError.invalidParam(
|
|
1312
|
+
`[${this.protocolName}] V3 remove_liquidity requires --token-id (NFT positionId)`
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
const tokenId = params.token_id;
|
|
1316
|
+
const liquidity = params.liquidity;
|
|
1317
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1318
|
+
const deadline = BigInt("18446744073709551615");
|
|
1319
|
+
const decreaseData = encodeFunctionData4({
|
|
1320
|
+
abi: positionManagerAbi,
|
|
1321
|
+
functionName: "decreaseLiquidity",
|
|
1322
|
+
args: [{ tokenId, liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
|
|
1323
|
+
});
|
|
1324
|
+
const collectData = encodeFunctionData4({
|
|
1325
|
+
abi: positionManagerAbi,
|
|
1326
|
+
functionName: "collect",
|
|
1327
|
+
args: [{ tokenId, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
|
|
1328
|
+
});
|
|
1329
|
+
const data = encodeFunctionData4({
|
|
1330
|
+
abi: positionManagerAbi,
|
|
1331
|
+
functionName: "multicall",
|
|
1332
|
+
args: [[decreaseData, collectData]]
|
|
1333
|
+
});
|
|
1334
|
+
return {
|
|
1335
|
+
description: `[${this.protocolName}] Remove ${liquidity} liquidity from tokenId ${tokenId}`,
|
|
1336
|
+
to: pm,
|
|
1337
|
+
data,
|
|
1338
|
+
value: 0n,
|
|
1339
|
+
gas_estimate: 4e5
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Collect accrued LP trading fees for a CL position via NPM.collect().
|
|
1344
|
+
* Used as the reward path for V3 forks with reward_strategy = "lp_fee_only"
|
|
1345
|
+
* (e.g., HyperSwap V3, Project X — no gauge/emissions, fees are the only reward).
|
|
1346
|
+
*/
|
|
1347
|
+
async buildCollectFees(tokenId, recipient) {
|
|
1348
|
+
const pm = this.positionManager;
|
|
1349
|
+
if (!pm) {
|
|
1350
|
+
throw DefiError.contractError(
|
|
1351
|
+
`[${this.protocolName}] Missing 'position_manager' for fee collection`
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1355
|
+
const data = encodeFunctionData4({
|
|
1356
|
+
abi: positionManagerAbi,
|
|
1357
|
+
functionName: "collect",
|
|
1358
|
+
args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
|
|
1359
|
+
});
|
|
1360
|
+
return {
|
|
1361
|
+
description: `[${this.protocolName}] Collect LP fees for tokenId ${tokenId}`,
|
|
1362
|
+
to: pm,
|
|
1363
|
+
data,
|
|
1364
|
+
value: 0n,
|
|
1365
|
+
gas_estimate: 2e5
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Compound: collect accrued fees and immediately re-add them as liquidity to the same position.
|
|
1370
|
+
* Flow: static-call collect to learn fee amounts → multicall([collect, increaseLiquidity]) on NPM.
|
|
1371
|
+
* Requires existing token approvals on the NPM (set during initial mint).
|
|
1372
|
+
* v1: V3 fee-only protocols (Project X, HyperSwap V3). Gauge protocols need swap routing first.
|
|
1373
|
+
*/
|
|
1374
|
+
async buildCompound(tokenId, recipient, opts) {
|
|
1375
|
+
const pm = this.positionManager;
|
|
1376
|
+
if (!pm) {
|
|
1377
|
+
throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager' for compound`);
|
|
1378
|
+
}
|
|
1379
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC required to preview fees");
|
|
1380
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1381
|
+
const deadline = BigInt("18446744073709551615");
|
|
1382
|
+
const slippageBps = BigInt(opts?.slippageBps ?? 50);
|
|
1383
|
+
if (slippageBps > 10000n) {
|
|
1384
|
+
throw DefiError.invalidParam(`[${this.protocolName}] slippageBps must be <= 10000 (got ${slippageBps})`);
|
|
1385
|
+
}
|
|
1386
|
+
const client = createPublicClient4({ transport: http4(this.rpcUrl) });
|
|
1387
|
+
const sim = await client.simulateContract({
|
|
1388
|
+
address: pm,
|
|
1389
|
+
abi: positionManagerAbi,
|
|
1390
|
+
functionName: "collect",
|
|
1391
|
+
args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }],
|
|
1392
|
+
account: recipient
|
|
1393
|
+
});
|
|
1394
|
+
const [amount0, amount1] = sim.result;
|
|
1395
|
+
if (amount0 === 0n && amount1 === 0n) {
|
|
1396
|
+
throw DefiError.invalidParam(`[${this.protocolName}] No fees to compound for tokenId ${tokenId}`);
|
|
1397
|
+
}
|
|
1398
|
+
const amount0Min = amount0 * (10000n - slippageBps) / 10000n;
|
|
1399
|
+
const amount1Min = amount1 * (10000n - slippageBps) / 10000n;
|
|
1400
|
+
const collectData = encodeFunctionData4({
|
|
1401
|
+
abi: positionManagerAbi,
|
|
1402
|
+
functionName: "collect",
|
|
1403
|
+
args: [{ tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
|
|
1404
|
+
});
|
|
1405
|
+
const increaseData = encodeFunctionData4({
|
|
1406
|
+
abi: positionManagerAbi,
|
|
1407
|
+
functionName: "increaseLiquidity",
|
|
1408
|
+
args: [{ tokenId, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, deadline }]
|
|
1409
|
+
});
|
|
1410
|
+
const data = encodeFunctionData4({
|
|
1411
|
+
abi: positionManagerAbi,
|
|
1412
|
+
functionName: "multicall",
|
|
1413
|
+
args: [[collectData, increaseData]]
|
|
1414
|
+
});
|
|
1415
|
+
return {
|
|
1416
|
+
description: `[${this.protocolName}] Compound tokenId ${tokenId}: collect ${amount0}/${amount1} \u2192 increaseLiquidity (slippage ${slippageBps}bps)`,
|
|
1417
|
+
to: pm,
|
|
1418
|
+
data,
|
|
1419
|
+
value: 0n,
|
|
1420
|
+
gas_estimate: 5e5
|
|
1421
|
+
};
|
|
1206
1422
|
}
|
|
1207
1423
|
};
|
|
1208
1424
|
abi = parseAbi22([
|
|
@@ -1423,6 +1639,13 @@ var init_dist2 = __esm({
|
|
|
1423
1639
|
"struct MintParams { address token0; address token1; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
|
|
1424
1640
|
"function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
1425
1641
|
]);
|
|
1642
|
+
algebraSharedPmAbi = parseAbi32([
|
|
1643
|
+
"struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
|
|
1644
|
+
"function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
1645
|
+
"struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
|
|
1646
|
+
"function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
1647
|
+
"function multicall(bytes[] data) external payable returns (bytes[] memory results)"
|
|
1648
|
+
]);
|
|
1426
1649
|
AlgebraV3Adapter = class {
|
|
1427
1650
|
protocolName;
|
|
1428
1651
|
router;
|
|
@@ -1618,10 +1841,34 @@ var init_dist2 = __esm({
|
|
|
1618
1841
|
approvals
|
|
1619
1842
|
};
|
|
1620
1843
|
}
|
|
1621
|
-
async buildRemoveLiquidity(
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
);
|
|
1844
|
+
async buildRemoveLiquidity(params) {
|
|
1845
|
+
const pm = this.positionManager;
|
|
1846
|
+
if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
|
|
1847
|
+
if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
|
|
1848
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1849
|
+
const deadline = BigInt("18446744073709551615");
|
|
1850
|
+
const decreaseData = encodeFunctionData32({
|
|
1851
|
+
abi: algebraSharedPmAbi,
|
|
1852
|
+
functionName: "decreaseLiquidity",
|
|
1853
|
+
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
|
|
1854
|
+
});
|
|
1855
|
+
const collectData = encodeFunctionData32({
|
|
1856
|
+
abi: algebraSharedPmAbi,
|
|
1857
|
+
functionName: "collect",
|
|
1858
|
+
args: [{ tokenId: params.token_id, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
|
|
1859
|
+
});
|
|
1860
|
+
const data = encodeFunctionData32({
|
|
1861
|
+
abi: algebraSharedPmAbi,
|
|
1862
|
+
functionName: "multicall",
|
|
1863
|
+
args: [[decreaseData, collectData]]
|
|
1864
|
+
});
|
|
1865
|
+
return {
|
|
1866
|
+
description: `[${this.protocolName}] Remove ${params.liquidity} liquidity from tokenId ${params.token_id}`,
|
|
1867
|
+
to: pm,
|
|
1868
|
+
data,
|
|
1869
|
+
value: 0n,
|
|
1870
|
+
gas_estimate: 4e5
|
|
1871
|
+
};
|
|
1625
1872
|
}
|
|
1626
1873
|
};
|
|
1627
1874
|
abi3 = parseAbi42([
|
|
@@ -1909,7 +2156,12 @@ var init_dist2 = __esm({
|
|
|
1909
2156
|
};
|
|
1910
2157
|
thenaPmAbi = parseAbi7([
|
|
1911
2158
|
"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; }",
|
|
1912
|
-
"function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
2159
|
+
"function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)",
|
|
2160
|
+
"struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; }",
|
|
2161
|
+
"function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
2162
|
+
"struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }",
|
|
2163
|
+
"function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1)",
|
|
2164
|
+
"function multicall(bytes[] data) external payable returns (bytes[] memory results)"
|
|
1913
2165
|
]);
|
|
1914
2166
|
thenaRouterAbi = parseAbi7([
|
|
1915
2167
|
"struct ExactInputSingleParams { address tokenIn; address tokenOut; int24 tickSpacing; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }",
|
|
@@ -2052,8 +2304,34 @@ var init_dist2 = __esm({
|
|
|
2052
2304
|
approvals
|
|
2053
2305
|
};
|
|
2054
2306
|
}
|
|
2055
|
-
async buildRemoveLiquidity(
|
|
2056
|
-
|
|
2307
|
+
async buildRemoveLiquidity(params) {
|
|
2308
|
+
const pm = this.positionManager;
|
|
2309
|
+
if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
|
|
2310
|
+
if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
|
|
2311
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
2312
|
+
const deadline = BigInt("18446744073709551615");
|
|
2313
|
+
const decreaseData = encodeFunctionData7({
|
|
2314
|
+
abi: thenaPmAbi,
|
|
2315
|
+
functionName: "decreaseLiquidity",
|
|
2316
|
+
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min: 0n, amount1Min: 0n, deadline }]
|
|
2317
|
+
});
|
|
2318
|
+
const collectData = encodeFunctionData7({
|
|
2319
|
+
abi: thenaPmAbi,
|
|
2320
|
+
functionName: "collect",
|
|
2321
|
+
args: [{ tokenId: params.token_id, recipient: params.recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }]
|
|
2322
|
+
});
|
|
2323
|
+
const data = encodeFunctionData7({
|
|
2324
|
+
abi: thenaPmAbi,
|
|
2325
|
+
functionName: "multicall",
|
|
2326
|
+
args: [[decreaseData, collectData]]
|
|
2327
|
+
});
|
|
2328
|
+
return {
|
|
2329
|
+
description: `[${this.protocolName}] Remove ${params.liquidity} liquidity from tokenId ${params.token_id}`,
|
|
2330
|
+
to: pm,
|
|
2331
|
+
data,
|
|
2332
|
+
value: 0n,
|
|
2333
|
+
gas_estimate: 4e5
|
|
2334
|
+
};
|
|
2057
2335
|
}
|
|
2058
2336
|
};
|
|
2059
2337
|
_addressDecodeAbi = parseAbi8(["function f() external view returns (address)"]);
|
|
@@ -2232,12 +2510,14 @@ var init_dist2 = __esm({
|
|
|
2232
2510
|
pre_txs: [approveTx]
|
|
2233
2511
|
};
|
|
2234
2512
|
}
|
|
2235
|
-
async buildWithdraw(gauge, _amount, tokenId) {
|
|
2513
|
+
async buildWithdraw(gauge, _amount, tokenId, opts) {
|
|
2236
2514
|
if (tokenId === void 0) throw new DefiError("CONTRACT_ERROR", "tokenId required for CL gauge withdraw");
|
|
2515
|
+
const redeemType = opts?.redeemType ?? 1;
|
|
2516
|
+
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)." : "";
|
|
2237
2517
|
return {
|
|
2238
|
-
description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge`,
|
|
2518
|
+
description: `[${this.protocolName}] Withdraw NFT #${tokenId} from gauge (redeemType=${redeemType})${warning}`,
|
|
2239
2519
|
to: gauge,
|
|
2240
|
-
data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId,
|
|
2520
|
+
data: encodeFunctionData8({ abi: gaugeCLAbi, functionName: "withdraw", args: [tokenId, redeemType] }),
|
|
2241
2521
|
value: 0n,
|
|
2242
2522
|
gas_estimate: 1e6
|
|
2243
2523
|
};
|
|
@@ -2246,15 +2526,15 @@ var init_dist2 = __esm({
|
|
|
2246
2526
|
async buildClaimRewards(gauge, _account) {
|
|
2247
2527
|
throw DefiError.unsupported(`[${this.protocolName}] Use buildClaimRewardsByTokenId for CL gauges`);
|
|
2248
2528
|
}
|
|
2249
|
-
async buildClaimRewardsByTokenId(gauge, tokenId) {
|
|
2529
|
+
async buildClaimRewardsByTokenId(gauge, tokenId, opts) {
|
|
2530
|
+
const redeemType = opts?.redeemType ?? 1;
|
|
2250
2531
|
return {
|
|
2251
|
-
description: `[${this.protocolName}] Claim rewards for NFT #${tokenId}`,
|
|
2532
|
+
description: `[${this.protocolName}] Claim rewards for NFT #${tokenId} (redeemType=${redeemType})`,
|
|
2252
2533
|
to: this.gaugeManager,
|
|
2253
2534
|
data: encodeFunctionData8({
|
|
2254
2535
|
abi: gaugeManagerAbi,
|
|
2255
2536
|
functionName: "claimRewards",
|
|
2256
|
-
args: [gauge, [tokenId],
|
|
2257
|
-
// redeemType=1
|
|
2537
|
+
args: [gauge, [tokenId], redeemType]
|
|
2258
2538
|
}),
|
|
2259
2539
|
value: 0n,
|
|
2260
2540
|
gas_estimate: 1e6
|
|
@@ -2399,15 +2679,21 @@ var init_dist2 = __esm({
|
|
|
2399
2679
|
"function getReward(address account) external",
|
|
2400
2680
|
"function getReward(address account, address[] tokens) external",
|
|
2401
2681
|
"function getReward(uint256 tokenId) external",
|
|
2682
|
+
// Ramses CL gauge factory (delegate target): tokenId-keyed multi-token claim
|
|
2683
|
+
"function getReward(uint256 tokenId, address[] tokens) external",
|
|
2402
2684
|
"function earned(address account) external view returns (uint256)",
|
|
2403
2685
|
"function earned(address account, uint256 tokenId) external view returns (uint256)",
|
|
2404
2686
|
"function earned(address token, address account) external view returns (uint256)",
|
|
2405
2687
|
"function earned(uint256 tokenId) external view returns (uint256)",
|
|
2688
|
+
// Ramses CL: token-first + tokenId (no account; account is ownerOf(tokenId) inferred by gauge)
|
|
2689
|
+
"function earned(address token, uint256 tokenId) external view returns (uint256)",
|
|
2406
2690
|
"function rewardRate() external view returns (uint256)",
|
|
2407
2691
|
"function rewardToken() external view returns (address)",
|
|
2408
2692
|
"function totalSupply() external view returns (uint256)",
|
|
2409
2693
|
"function rewardsListLength() external view returns (uint256)",
|
|
2410
2694
|
"function rewardData(address token) external view returns (uint256 periodFinish, uint256 rewardRate, uint256 lastUpdateTime, uint256 rewardPerTokenStored)",
|
|
2695
|
+
// Ramses CL gauge factory exposes the canonical reward-token list
|
|
2696
|
+
"function getRewardTokens() external view returns (address[])",
|
|
2411
2697
|
"function nonfungiblePositionManager() external view returns (address)"
|
|
2412
2698
|
]);
|
|
2413
2699
|
veAbi2 = parseAbi10([
|
|
@@ -2452,6 +2738,10 @@ var init_dist2 = __esm({
|
|
|
2452
2738
|
clFactory;
|
|
2453
2739
|
v2Factory;
|
|
2454
2740
|
tokens;
|
|
2741
|
+
// CL gauges (Aerodrome Slipstream / Velodrome CL) take an LP NFT via deposit(tokenId);
|
|
2742
|
+
// V2 gauges take an LP token amount via deposit(amount). Detect via uniswap_v3 + position_manager.
|
|
2743
|
+
clNftMode;
|
|
2744
|
+
positionManager;
|
|
2455
2745
|
constructor(entry, rpcUrl, tokens) {
|
|
2456
2746
|
this.protocolName = entry.name;
|
|
2457
2747
|
const voter = entry.contracts?.["voter"];
|
|
@@ -2468,6 +2758,8 @@ var init_dist2 = __esm({
|
|
|
2468
2758
|
this.tokens = tokens;
|
|
2469
2759
|
this.clFactory = entry.contracts?.["cl_factory"] ?? entry.contracts?.["factory"];
|
|
2470
2760
|
this.v2Factory = entry.contracts?.["pair_factory"] ?? entry.contracts?.["factory"];
|
|
2761
|
+
this.positionManager = entry.contracts?.["position_manager"];
|
|
2762
|
+
this.clNftMode = entry.interface === "uniswap_v3" && this.positionManager !== void 0;
|
|
2471
2763
|
}
|
|
2472
2764
|
name() {
|
|
2473
2765
|
return this.protocolName;
|
|
@@ -2802,6 +3094,25 @@ var init_dist2 = __esm({
|
|
|
2802
3094
|
}
|
|
2803
3095
|
// IGauge
|
|
2804
3096
|
async buildDeposit(gauge, amount, tokenId, lpToken) {
|
|
3097
|
+
if (this.clNftMode && tokenId !== void 0 && this.positionManager) {
|
|
3098
|
+
const nftAbi = parseAbi10(["function approve(address to, uint256 tokenId) external"]);
|
|
3099
|
+
const clGaugeAbi = parseAbi10(["function deposit(uint256 tokenId) external"]);
|
|
3100
|
+
const approveTx = {
|
|
3101
|
+
description: `[${this.protocolName}] Approve LP NFT #${tokenId} to gauge`,
|
|
3102
|
+
to: this.positionManager,
|
|
3103
|
+
data: encodeFunctionData10({ abi: nftAbi, functionName: "approve", args: [gauge, tokenId] }),
|
|
3104
|
+
value: 0n,
|
|
3105
|
+
gas_estimate: 8e4
|
|
3106
|
+
};
|
|
3107
|
+
return {
|
|
3108
|
+
description: `[${this.protocolName}] Deposit LP NFT #${tokenId} to CL gauge`,
|
|
3109
|
+
to: gauge,
|
|
3110
|
+
data: encodeFunctionData10({ abi: clGaugeAbi, functionName: "deposit", args: [tokenId] }),
|
|
3111
|
+
value: 0n,
|
|
3112
|
+
gas_estimate: 9e5,
|
|
3113
|
+
pre_txs: [approveTx]
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
2805
3116
|
if (tokenId !== void 0) {
|
|
2806
3117
|
const data2 = encodeFunctionData10({
|
|
2807
3118
|
abi: gaugeAbi,
|
|
@@ -2831,7 +3142,17 @@ var init_dist2 = __esm({
|
|
|
2831
3142
|
approvals: lpToken ? [{ token: lpToken, spender: gauge, amount }] : void 0
|
|
2832
3143
|
};
|
|
2833
3144
|
}
|
|
2834
|
-
async buildWithdraw(gauge, amount) {
|
|
3145
|
+
async buildWithdraw(gauge, amount, tokenId) {
|
|
3146
|
+
if (this.clNftMode && tokenId !== void 0) {
|
|
3147
|
+
const clGaugeAbi = parseAbi10(["function withdraw(uint256 tokenId) external"]);
|
|
3148
|
+
return {
|
|
3149
|
+
description: `[${this.protocolName}] Withdraw LP NFT #${tokenId} from CL gauge`,
|
|
3150
|
+
to: gauge,
|
|
3151
|
+
data: encodeFunctionData10({ abi: clGaugeAbi, functionName: "withdraw", args: [tokenId] }),
|
|
3152
|
+
value: 0n,
|
|
3153
|
+
gas_estimate: 6e5
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
2835
3156
|
const data = encodeFunctionData10({
|
|
2836
3157
|
abi: gaugeAbi,
|
|
2837
3158
|
functionName: "withdraw",
|
|
@@ -2921,16 +3242,16 @@ var init_dist2 = __esm({
|
|
|
2921
3242
|
}
|
|
2922
3243
|
async buildClaimRewards(gauge, account) {
|
|
2923
3244
|
if (!this.rpcUrl || !account) {
|
|
2924
|
-
const
|
|
3245
|
+
const data = encodeFunctionData10({
|
|
2925
3246
|
abi: gaugeAbi,
|
|
2926
3247
|
functionName: "getReward",
|
|
2927
3248
|
args: [account ?? zeroAddress6]
|
|
2928
3249
|
});
|
|
2929
|
-
return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data
|
|
3250
|
+
return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
|
|
2930
3251
|
}
|
|
2931
3252
|
const { tokens, multiToken } = await this.discoverRewardTokens(gauge);
|
|
2932
3253
|
if (multiToken && tokens.length > 0) {
|
|
2933
|
-
const
|
|
3254
|
+
const data = encodeFunctionData10({
|
|
2934
3255
|
abi: gaugeAbi,
|
|
2935
3256
|
functionName: "getReward",
|
|
2936
3257
|
args: [account, tokens]
|
|
@@ -2938,41 +3259,179 @@ var init_dist2 = __esm({
|
|
|
2938
3259
|
return {
|
|
2939
3260
|
description: `[${this.protocolName}] Claim gauge rewards (${tokens.length} tokens)`,
|
|
2940
3261
|
to: gauge,
|
|
2941
|
-
data
|
|
3262
|
+
data,
|
|
2942
3263
|
value: 0n,
|
|
2943
3264
|
gas_estimate: 3e5
|
|
2944
3265
|
};
|
|
2945
3266
|
}
|
|
3267
|
+
const accountVariant = encodeFunctionData10({
|
|
3268
|
+
abi: gaugeAbi,
|
|
3269
|
+
functionName: "getReward",
|
|
3270
|
+
args: [account]
|
|
3271
|
+
});
|
|
3272
|
+
try {
|
|
3273
|
+
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
3274
|
+
await client.call({ account, to: gauge, data: accountVariant });
|
|
3275
|
+
return {
|
|
3276
|
+
description: `[${this.protocolName}] Claim gauge rewards (getReward(account))`,
|
|
3277
|
+
to: gauge,
|
|
3278
|
+
data: accountVariant,
|
|
3279
|
+
value: 0n,
|
|
3280
|
+
gas_estimate: 2e5
|
|
3281
|
+
};
|
|
3282
|
+
} catch {
|
|
3283
|
+
const noArg = encodeFunctionData10({ abi: gaugeAbi, functionName: "getReward", args: [] });
|
|
3284
|
+
return {
|
|
3285
|
+
description: `[${this.protocolName}] Claim gauge rewards (getReward())`,
|
|
3286
|
+
to: gauge,
|
|
3287
|
+
data: noArg,
|
|
3288
|
+
value: 0n,
|
|
3289
|
+
gas_estimate: 2e5
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Claim rewards for a CL gauge by NFT tokenId (Hybra V4 style — single-arg getReward(tokenId)).
|
|
3295
|
+
*/
|
|
3296
|
+
async buildClaimRewardsByTokenId(gauge, tokenId) {
|
|
2946
3297
|
const data = encodeFunctionData10({
|
|
2947
3298
|
abi: gaugeAbi,
|
|
2948
3299
|
functionName: "getReward",
|
|
2949
|
-
args: []
|
|
3300
|
+
args: [tokenId]
|
|
2950
3301
|
});
|
|
2951
3302
|
return {
|
|
2952
|
-
description: `[${this.protocolName}] Claim gauge rewards`,
|
|
3303
|
+
description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
|
|
2953
3304
|
to: gauge,
|
|
2954
3305
|
data,
|
|
2955
3306
|
value: 0n,
|
|
2956
|
-
gas_estimate:
|
|
3307
|
+
gas_estimate: 3e5
|
|
2957
3308
|
};
|
|
2958
3309
|
}
|
|
2959
3310
|
/**
|
|
2960
|
-
*
|
|
3311
|
+
* Ramses-CL claim via NPM.getPeriodReward — the user-facing claim path.
|
|
3312
|
+
* The gauge contract restricts `getReward*` to authorized claimers (voter + NPM only);
|
|
3313
|
+
* EOAs must route through NPM, which calls into the gauge with msg.sender = NPM.
|
|
3314
|
+
*
|
|
3315
|
+
* ABI: getPeriodReward(uint256 period, uint256 tokenId, address[] tokens, address receiver)
|
|
3316
|
+
* `period` defaults to current Solidly weekly epoch index (block.timestamp / 604800).
|
|
3317
|
+
* `tokens` defaults to gauge.getRewardTokens() when `gauge` is provided.
|
|
3318
|
+
*
|
|
3319
|
+
* Verified 2026-04-29 on anvil fork: NPM.getPeriodReward(2938, 177068, [..., xRAM], wallet)
|
|
3320
|
+
* delivered 71.11 xRAM after 1h emission warp; direct gauge.getReward(...) reverts
|
|
3321
|
+
* with NOT_AUTHORIZED_CLAIMER for the same EOA.
|
|
2961
3322
|
*/
|
|
2962
|
-
async
|
|
3323
|
+
async buildClaimRewardsViaNPMPeriodReward(npm, tokenId, receiver, opts) {
|
|
3324
|
+
let rewardTokens = opts?.tokens;
|
|
3325
|
+
if (!rewardTokens || rewardTokens.length === 0) {
|
|
3326
|
+
if (!opts?.gauge) {
|
|
3327
|
+
throw DefiError.invalidParam(
|
|
3328
|
+
"Ramses CL claim requires either `tokens` or `gauge` (for getRewardTokens lookup)"
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required to discover reward tokens");
|
|
3332
|
+
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
3333
|
+
try {
|
|
3334
|
+
rewardTokens = await client.readContract({
|
|
3335
|
+
address: opts.gauge,
|
|
3336
|
+
abi: gaugeAbi,
|
|
3337
|
+
functionName: "getRewardTokens"
|
|
3338
|
+
});
|
|
3339
|
+
} catch {
|
|
3340
|
+
throw DefiError.contractError(
|
|
3341
|
+
`[${this.protocolName}] gauge.getRewardTokens() reverted \u2014 pass tokens[] explicitly`
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
if (rewardTokens.length === 0) {
|
|
3346
|
+
throw DefiError.contractError(`[${this.protocolName}] no reward tokens to claim`);
|
|
3347
|
+
}
|
|
3348
|
+
const epochSeconds = 604800n;
|
|
3349
|
+
const period = opts?.period ?? BigInt(Math.floor(Date.now() / 1e3)) / epochSeconds;
|
|
3350
|
+
const npmClaimAbi = parseAbi10([
|
|
3351
|
+
"function getPeriodReward(uint256 period, uint256 tokenId, address[] tokens, address receiver) external"
|
|
3352
|
+
]);
|
|
3353
|
+
const data = encodeFunctionData10({
|
|
3354
|
+
abi: npmClaimAbi,
|
|
3355
|
+
functionName: "getPeriodReward",
|
|
3356
|
+
args: [period, tokenId, rewardTokens, receiver]
|
|
3357
|
+
});
|
|
3358
|
+
return {
|
|
3359
|
+
description: `[${this.protocolName}] Claim via NPM.getPeriodReward(period=${period}, tokenId=${tokenId}, ${rewardTokens.length} tokens)`,
|
|
3360
|
+
to: npm,
|
|
3361
|
+
data,
|
|
3362
|
+
value: 0n,
|
|
3363
|
+
gas_estimate: 6e5
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* @deprecated Direct gauge.getReward(tokenId, tokens[]) reverts with NOT_AUTHORIZED_CLAIMER
|
|
3368
|
+
* for EOAs on Ramses CL. Use buildClaimRewardsViaNPMPeriodReward instead.
|
|
3369
|
+
*/
|
|
3370
|
+
async buildClaimRewardsByCLTokenIdMulti(gauge, tokenId, tokens) {
|
|
3371
|
+
let rewardTokens = tokens;
|
|
3372
|
+
if (!rewardTokens || rewardTokens.length === 0) {
|
|
3373
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required to discover reward tokens");
|
|
3374
|
+
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
3375
|
+
try {
|
|
3376
|
+
rewardTokens = await client.readContract({
|
|
3377
|
+
address: gauge,
|
|
3378
|
+
abi: gaugeAbi,
|
|
3379
|
+
functionName: "getRewardTokens"
|
|
3380
|
+
});
|
|
3381
|
+
} catch {
|
|
3382
|
+
throw DefiError.contractError(
|
|
3383
|
+
`[${this.protocolName}] gauge.getRewardTokens() reverted \u2014 pass tokens[] explicitly`
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
if (rewardTokens.length === 0) {
|
|
3388
|
+
throw DefiError.contractError(`[${this.protocolName}] no reward tokens to claim`);
|
|
3389
|
+
}
|
|
2963
3390
|
const data = encodeFunctionData10({
|
|
2964
3391
|
abi: gaugeAbi,
|
|
2965
3392
|
functionName: "getReward",
|
|
2966
|
-
args: [tokenId]
|
|
3393
|
+
args: [tokenId, rewardTokens]
|
|
2967
3394
|
});
|
|
2968
3395
|
return {
|
|
2969
|
-
description: `[${this.protocolName}] Claim gauge rewards for NFT #${tokenId}`,
|
|
3396
|
+
description: `[${this.protocolName}] Claim CL gauge rewards for NFT #${tokenId} (${rewardTokens.length} tokens)`,
|
|
2970
3397
|
to: gauge,
|
|
2971
3398
|
data,
|
|
2972
3399
|
value: 0n,
|
|
2973
|
-
gas_estimate:
|
|
3400
|
+
gas_estimate: 4e5
|
|
2974
3401
|
};
|
|
2975
3402
|
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Ramses-CL-style pending rewards: earned(token, tokenId) per reward token from
|
|
3405
|
+
* gauge.getRewardTokens(). Returns raw amounts; caller resolves USD value.
|
|
3406
|
+
*/
|
|
3407
|
+
async getPendingRewardsByCLTokenIdMulti(gauge, tokenId) {
|
|
3408
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
|
|
3409
|
+
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
3410
|
+
let rewardTokens;
|
|
3411
|
+
try {
|
|
3412
|
+
rewardTokens = await client.readContract({
|
|
3413
|
+
address: gauge,
|
|
3414
|
+
abi: gaugeAbi,
|
|
3415
|
+
functionName: "getRewardTokens"
|
|
3416
|
+
});
|
|
3417
|
+
} catch {
|
|
3418
|
+
return [];
|
|
3419
|
+
}
|
|
3420
|
+
const out = [];
|
|
3421
|
+
for (const token of rewardTokens) {
|
|
3422
|
+
try {
|
|
3423
|
+
const amount = await client.readContract({
|
|
3424
|
+
address: gauge,
|
|
3425
|
+
abi: gaugeAbi,
|
|
3426
|
+
functionName: "earned",
|
|
3427
|
+
args: [token, tokenId]
|
|
3428
|
+
});
|
|
3429
|
+
out.push({ token, symbol: token.slice(0, 10), amount });
|
|
3430
|
+
} catch {
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
return out;
|
|
3434
|
+
}
|
|
2976
3435
|
async getPendingRewards(gauge, user) {
|
|
2977
3436
|
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
|
|
2978
3437
|
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
@@ -3539,9 +3998,44 @@ var init_dist2 = __esm({
|
|
|
3539
3998
|
}
|
|
3540
3999
|
];
|
|
3541
4000
|
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Scan ±scanRange bins around the active bin and return the user's non-zero balance bin IDs.
|
|
4003
|
+
* Critical: the rewarder may track pending rewards for bins OUTSIDE its current rewarded range
|
|
4004
|
+
* (e.g. when the rewarded range shifts after a position was already in place). Always claim
|
|
4005
|
+
* against the user's actual positions, not the rewarder's "current" range.
|
|
4006
|
+
*/
|
|
4007
|
+
async findUserBinsWithBalance(pool, user, scanRange = 50) {
|
|
4008
|
+
const rpcUrl = this.requireRpc();
|
|
4009
|
+
const client = createPublicClient8({ transport: http8(rpcUrl) });
|
|
4010
|
+
const activeId = await client.readContract({
|
|
4011
|
+
address: pool,
|
|
4012
|
+
abi: lbPairAbi,
|
|
4013
|
+
functionName: "getActiveId"
|
|
4014
|
+
});
|
|
4015
|
+
const calls = [];
|
|
4016
|
+
const binIds = [];
|
|
4017
|
+
for (let b = activeId - scanRange; b <= activeId + scanRange; b++) {
|
|
4018
|
+
binIds.push(b);
|
|
4019
|
+
calls.push([pool, encodeFunctionData12({
|
|
4020
|
+
abi: parseAbi12(["function balanceOf(address account, uint256 id) view returns (uint256)"]),
|
|
4021
|
+
functionName: "balanceOf",
|
|
4022
|
+
args: [user, BigInt(b)]
|
|
4023
|
+
})]);
|
|
4024
|
+
}
|
|
4025
|
+
const results = await multicallRead(rpcUrl, calls);
|
|
4026
|
+
const owned = [];
|
|
4027
|
+
for (let i = 0; i < binIds.length; i++) {
|
|
4028
|
+
const data = results[i];
|
|
4029
|
+
if (!data) continue;
|
|
4030
|
+
const hex = data.slice(2).padStart(64, "0");
|
|
4031
|
+
if (hex !== "0".repeat(64)) owned.push(binIds[i]);
|
|
4032
|
+
}
|
|
4033
|
+
return owned;
|
|
4034
|
+
}
|
|
3542
4035
|
/**
|
|
3543
4036
|
* Build a claim rewards transaction for specific LB bins.
|
|
3544
|
-
* If binIds is omitted, auto-detects from the
|
|
4037
|
+
* If binIds is omitted, auto-detects from the user's actual non-zero balance bins (active ±50 scan).
|
|
4038
|
+
* This catches rewards accumulated in bins outside the rewarder's current rewarded range.
|
|
3545
4039
|
*/
|
|
3546
4040
|
async buildClaimRewards(user, pool, binIds) {
|
|
3547
4041
|
const rpcUrl = this.requireRpc();
|
|
@@ -3557,15 +4051,18 @@ var init_dist2 = __esm({
|
|
|
3557
4051
|
}
|
|
3558
4052
|
let resolvedBinIds = binIds;
|
|
3559
4053
|
if (!resolvedBinIds || resolvedBinIds.length === 0) {
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
4054
|
+
resolvedBinIds = await this.findUserBinsWithBalance(pool, user);
|
|
4055
|
+
if (resolvedBinIds.length === 0) {
|
|
4056
|
+
const range = await client.readContract({
|
|
4057
|
+
address: rewarder,
|
|
4058
|
+
abi: lbRewarderAbi,
|
|
4059
|
+
functionName: "getRewardedRange"
|
|
4060
|
+
});
|
|
4061
|
+
const min = Number(range[0]);
|
|
4062
|
+
const max = Number(range[1]);
|
|
4063
|
+
resolvedBinIds = [];
|
|
4064
|
+
for (let b = min; b <= max; b++) resolvedBinIds.push(b);
|
|
4065
|
+
}
|
|
3569
4066
|
}
|
|
3570
4067
|
const data = encodeFunctionData12({
|
|
3571
4068
|
abi: lbRewarderAbi,
|
|
@@ -3976,7 +4473,7 @@ var init_dist2 = __esm({
|
|
|
3976
4473
|
"function enterFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
|
|
3977
4474
|
"function exitFarming((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
|
|
3978
4475
|
"function collectRewards((address rewardToken, address bonusRewardToken, address pool, uint256 nonce) key, uint256 tokenId) external",
|
|
3979
|
-
"function claimReward(address rewardToken, address to,
|
|
4476
|
+
"function claimReward(address rewardToken, address to, uint256 amountRequested) external returns (uint256 reward)"
|
|
3980
4477
|
]);
|
|
3981
4478
|
positionManagerAbi2 = parseAbi13([
|
|
3982
4479
|
"function approveForFarming(uint256 tokenId, bool approve, address farmingAddress) external",
|
|
@@ -3998,13 +4495,17 @@ var init_dist2 = __esm({
|
|
|
3998
4495
|
positionManager;
|
|
3999
4496
|
rpcUrl;
|
|
4000
4497
|
factory;
|
|
4001
|
-
|
|
4498
|
+
rewardToken;
|
|
4499
|
+
bonusRewardToken;
|
|
4500
|
+
constructor(protocolName, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken = KITTEN_TOKEN, bonusRewardToken = WHYPE_TOKEN) {
|
|
4002
4501
|
this.protocolName = protocolName;
|
|
4003
4502
|
this.farmingCenter = farmingCenter;
|
|
4004
4503
|
this.eternalFarming = eternalFarming;
|
|
4005
4504
|
this.positionManager = positionManager;
|
|
4006
4505
|
this.rpcUrl = rpcUrl;
|
|
4007
4506
|
this.factory = factory;
|
|
4507
|
+
this.rewardToken = rewardToken;
|
|
4508
|
+
this.bonusRewardToken = bonusRewardToken;
|
|
4008
4509
|
}
|
|
4009
4510
|
name() {
|
|
4010
4511
|
return this.protocolName;
|
|
@@ -4019,8 +4520,8 @@ var init_dist2 = __esm({
|
|
|
4019
4520
|
const poolLc = pool.toLowerCase();
|
|
4020
4521
|
if (nonceCache.has(poolLc)) {
|
|
4021
4522
|
return {
|
|
4022
|
-
rewardToken:
|
|
4023
|
-
bonusRewardToken:
|
|
4523
|
+
rewardToken: this.rewardToken,
|
|
4524
|
+
bonusRewardToken: this.bonusRewardToken,
|
|
4024
4525
|
pool,
|
|
4025
4526
|
nonce: nonceCache.get(poolLc)
|
|
4026
4527
|
};
|
|
@@ -4031,8 +4532,8 @@ var init_dist2 = __esm({
|
|
|
4031
4532
|
const nonce = BigInt(n);
|
|
4032
4533
|
nonces.push(nonce);
|
|
4033
4534
|
const key = {
|
|
4034
|
-
rewardToken:
|
|
4035
|
-
bonusRewardToken:
|
|
4535
|
+
rewardToken: this.rewardToken,
|
|
4536
|
+
bonusRewardToken: this.bonusRewardToken,
|
|
4036
4537
|
pool,
|
|
4037
4538
|
nonce
|
|
4038
4539
|
};
|
|
@@ -4067,8 +4568,8 @@ var init_dist2 = __esm({
|
|
|
4067
4568
|
const nonce = nonces[i];
|
|
4068
4569
|
nonceCache.set(poolLc, nonce);
|
|
4069
4570
|
return {
|
|
4070
|
-
rewardToken:
|
|
4071
|
-
bonusRewardToken:
|
|
4571
|
+
rewardToken: this.rewardToken,
|
|
4572
|
+
bonusRewardToken: this.bonusRewardToken,
|
|
4072
4573
|
pool,
|
|
4073
4574
|
nonce
|
|
4074
4575
|
};
|
|
@@ -4160,8 +4661,8 @@ var init_dist2 = __esm({
|
|
|
4160
4661
|
}
|
|
4161
4662
|
const calls = [
|
|
4162
4663
|
encodeCollectRewards(key, tokenId),
|
|
4163
|
-
encodeClaimReward(
|
|
4164
|
-
encodeClaimReward(
|
|
4664
|
+
encodeClaimReward(this.rewardToken, owner),
|
|
4665
|
+
encodeClaimReward(this.bonusRewardToken, owner)
|
|
4165
4666
|
];
|
|
4166
4667
|
return {
|
|
4167
4668
|
description: `[${this.protocolName}] Collect + claim rewards for NFT #${tokenId} in pool ${pool}`,
|
|
@@ -4176,8 +4677,8 @@ var init_dist2 = __esm({
|
|
|
4176
4677
|
*/
|
|
4177
4678
|
async buildClaimReward(owner) {
|
|
4178
4679
|
const calls = [
|
|
4179
|
-
encodeClaimReward(
|
|
4180
|
-
encodeClaimReward(
|
|
4680
|
+
encodeClaimReward(this.rewardToken, owner),
|
|
4681
|
+
encodeClaimReward(this.bonusRewardToken, owner)
|
|
4181
4682
|
];
|
|
4182
4683
|
return {
|
|
4183
4684
|
description: `[${this.protocolName}] Claim KITTEN + WHYPE farming rewards to ${owner}`,
|
|
@@ -4246,8 +4747,8 @@ var init_dist2 = __esm({
|
|
|
4246
4747
|
for (const pool of pools) {
|
|
4247
4748
|
for (let n = 0; n <= MAX_NONCE_SCAN; n++) {
|
|
4248
4749
|
const key = {
|
|
4249
|
-
rewardToken:
|
|
4250
|
-
bonusRewardToken:
|
|
4750
|
+
rewardToken: this.rewardToken,
|
|
4751
|
+
bonusRewardToken: this.bonusRewardToken,
|
|
4251
4752
|
pool,
|
|
4252
4753
|
nonce: BigInt(n)
|
|
4253
4754
|
};
|
|
@@ -4294,8 +4795,8 @@ var init_dist2 = __esm({
|
|
|
4294
4795
|
const isActive = !deactivated;
|
|
4295
4796
|
if (!bestKey || isActive && !bestActive || isActive === bestActive && nonce > bestKey.nonce) {
|
|
4296
4797
|
bestKey = {
|
|
4297
|
-
rewardToken:
|
|
4298
|
-
bonusRewardToken:
|
|
4798
|
+
rewardToken: this.rewardToken,
|
|
4799
|
+
bonusRewardToken: this.bonusRewardToken,
|
|
4299
4800
|
pool,
|
|
4300
4801
|
nonce
|
|
4301
4802
|
};
|
|
@@ -4321,6 +4822,235 @@ var init_dist2 = __esm({
|
|
|
4321
4822
|
return results;
|
|
4322
4823
|
}
|
|
4323
4824
|
};
|
|
4825
|
+
DEFAULT_BASE_URL = "https://app.usenest.xyz/api/blaze";
|
|
4826
|
+
FALLBACK_BASE_URL = "https://blaze.nest.aegas.it";
|
|
4827
|
+
NEST_TOKEN = "0x07c57E32a3C29D5659bda1d3EFC2E7BF004E3035";
|
|
4828
|
+
NEST_DECIMALS = 18;
|
|
4829
|
+
NestOffChainAdapter = class {
|
|
4830
|
+
baseUrl;
|
|
4831
|
+
fallbackUrl;
|
|
4832
|
+
voter;
|
|
4833
|
+
constructor(entry) {
|
|
4834
|
+
const voter = entry.contracts?.["voter"];
|
|
4835
|
+
if (!voter) {
|
|
4836
|
+
throw DefiError.contractError("Nest off-chain: missing 'voter' contract");
|
|
4837
|
+
}
|
|
4838
|
+
this.voter = voter;
|
|
4839
|
+
this.baseUrl = process.env["NEST_API_URL"] ?? DEFAULT_BASE_URL;
|
|
4840
|
+
this.fallbackUrl = FALLBACK_BASE_URL;
|
|
4841
|
+
}
|
|
4842
|
+
name() {
|
|
4843
|
+
return "Nest";
|
|
4844
|
+
}
|
|
4845
|
+
/** Cumulative claimed + available NEST emissions for a wallet */
|
|
4846
|
+
async getClaimStatus(wallet) {
|
|
4847
|
+
const data = await this.fetchJson(
|
|
4848
|
+
`/claim/claim-status?publicAddress=${wallet}`
|
|
4849
|
+
);
|
|
4850
|
+
const totalClaimedRaw = BigInt(data.totalClaimed);
|
|
4851
|
+
const totalAvailableRaw = BigInt(data.totalAvailable);
|
|
4852
|
+
const pendingRaw = totalAvailableRaw > totalClaimedRaw ? totalAvailableRaw - totalClaimedRaw : 0n;
|
|
4853
|
+
return {
|
|
4854
|
+
totalClaimedRaw,
|
|
4855
|
+
totalAvailableRaw,
|
|
4856
|
+
pendingRaw,
|
|
4857
|
+
pendingFormatted: Number(pendingRaw) / 10 ** NEST_DECIMALS
|
|
4858
|
+
};
|
|
4859
|
+
}
|
|
4860
|
+
/**
|
|
4861
|
+
* Backend-signed claim ticket (or null when nothing to claim).
|
|
4862
|
+
* Returns the raw ticket; `buildClaim()` is not yet implemented because the
|
|
4863
|
+
* voter contract source is unverified — function selector 0xd6d7a454 takes
|
|
4864
|
+
* 5 dynamic arrays we have not been able to disambiguate yet.
|
|
4865
|
+
*/
|
|
4866
|
+
async getClaimTicket(wallet) {
|
|
4867
|
+
const url = `${this.baseUrl}/claim/claim-data?publicAddress=${wallet}`;
|
|
4868
|
+
const res = await fetch(url, this.requestInit());
|
|
4869
|
+
const text = await res.text();
|
|
4870
|
+
if (text.includes("no points to claim")) return null;
|
|
4871
|
+
if (!res.ok) {
|
|
4872
|
+
throw DefiError.providerError(`Nest claim-data ${res.status}: ${text.slice(0, 200)}`);
|
|
4873
|
+
}
|
|
4874
|
+
let json;
|
|
4875
|
+
try {
|
|
4876
|
+
json = JSON.parse(text);
|
|
4877
|
+
} catch {
|
|
4878
|
+
throw DefiError.providerError(`Nest claim-data: non-JSON response: ${text.slice(0, 200)}`);
|
|
4879
|
+
}
|
|
4880
|
+
return {
|
|
4881
|
+
user: json.user,
|
|
4882
|
+
amount: BigInt(json.amount),
|
|
4883
|
+
timestamp: BigInt(json.timestamp),
|
|
4884
|
+
day: json.day === null ? null : BigInt(json.day),
|
|
4885
|
+
signature: json.signature.startsWith("0x") ? json.signature : `0x${json.signature}`
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4888
|
+
/** APR estimate (percent) for a CL position with given tick range and amounts */
|
|
4889
|
+
async estimateLpApr(params) {
|
|
4890
|
+
const qs = new URLSearchParams({
|
|
4891
|
+
poolAddress: params.poolAddress,
|
|
4892
|
+
minTick: String(params.minTick),
|
|
4893
|
+
maxTick: String(params.maxTick),
|
|
4894
|
+
token0Amount: params.token0Amount.toString(),
|
|
4895
|
+
token1Amount: params.token1Amount.toString()
|
|
4896
|
+
});
|
|
4897
|
+
const data = await this.fetchJson(`/liquidity/apr/estimate?${qs}`);
|
|
4898
|
+
const apr = Number(data.apr);
|
|
4899
|
+
if (!Number.isFinite(apr)) {
|
|
4900
|
+
throw DefiError.providerError(`Nest apr/estimate: invalid apr value '${data.apr}'`);
|
|
4901
|
+
}
|
|
4902
|
+
return apr;
|
|
4903
|
+
}
|
|
4904
|
+
/** Pending NEST emissions as IGauge-compatible RewardInfo[] */
|
|
4905
|
+
async getPendingRewards(user) {
|
|
4906
|
+
const status = await this.getClaimStatus(user);
|
|
4907
|
+
if (status.pendingRaw === 0n) return [];
|
|
4908
|
+
return [{
|
|
4909
|
+
token: NEST_TOKEN,
|
|
4910
|
+
symbol: "NEST",
|
|
4911
|
+
amount: status.pendingRaw
|
|
4912
|
+
}];
|
|
4913
|
+
}
|
|
4914
|
+
/** Voter address used by aggregateClaim() — exposed for callers that build the tx themselves */
|
|
4915
|
+
getVoterAddress() {
|
|
4916
|
+
return this.voter;
|
|
4917
|
+
}
|
|
4918
|
+
/**
|
|
4919
|
+
* Build a Nest voter claim transaction by reproducing the byte-level calldata
|
|
4920
|
+
* pattern observed in successful onchain claims, swapping in the ticket's
|
|
4921
|
+
* (amount, timestamp, signature) words.
|
|
4922
|
+
*
|
|
4923
|
+
* The voter implementation source is not verified, so we cannot derive a
|
|
4924
|
+
* Solidity ABI for selector 0xd6d7a454. Instead, two known-successful claim
|
|
4925
|
+
* transactions were diffed:
|
|
4926
|
+
*
|
|
4927
|
+
* tx1: 0x99f35cfdb6fc3885ebe046c4625acc083e42d5afe6ca6962c6c81cd9006b99ba
|
|
4928
|
+
* tx2: 0x3e120ab95e9e0a9148cb8964993dd066b8a36363353fe727462231857724e7bb
|
|
4929
|
+
*
|
|
4930
|
+
* 31 of 34 calldata words are identical between the two; only words 21, 22,
|
|
4931
|
+
* 25, 26, 27 differ — and those map exactly to the backend ticket's
|
|
4932
|
+
* (amount, timestamp, sigR, sigS, sigVPadded). msg.sender is not encoded in
|
|
4933
|
+
* calldata; voter binds the claim to the caller, so the ticket signature
|
|
4934
|
+
* authorizes the EOA holding the wallet.
|
|
4935
|
+
*
|
|
4936
|
+
* Throws if no claim ticket is available.
|
|
4937
|
+
*/
|
|
4938
|
+
async buildClaim(wallet) {
|
|
4939
|
+
const ticket = await this.getClaimTicket(wallet);
|
|
4940
|
+
if (!ticket) {
|
|
4941
|
+
throw DefiError.invalidParam(`Nest: no claim ticket available for ${wallet}`);
|
|
4942
|
+
}
|
|
4943
|
+
const sigHex = ticket.signature.startsWith("0x") ? ticket.signature.slice(2) : ticket.signature;
|
|
4944
|
+
if (sigHex.length !== 130) {
|
|
4945
|
+
throw DefiError.providerError(`Nest: signature must be 65 bytes (130 hex chars), got ${sigHex.length}`);
|
|
4946
|
+
}
|
|
4947
|
+
const r = sigHex.slice(0, 64);
|
|
4948
|
+
const s = sigHex.slice(64, 128);
|
|
4949
|
+
const v = sigHex.slice(128, 130);
|
|
4950
|
+
const vPadded = v + "0".repeat(62);
|
|
4951
|
+
const amountHex = ticket.amount.toString(16).padStart(64, "0");
|
|
4952
|
+
const timestampHex = ticket.timestamp.toString(16).padStart(64, "0");
|
|
4953
|
+
const words = [
|
|
4954
|
+
"0000000000000000000000000000000000000000000000000000000000000160",
|
|
4955
|
+
// 0
|
|
4956
|
+
"0000000000000000000000000000000000000000000000000000000000000180",
|
|
4957
|
+
// 1
|
|
4958
|
+
"0000000000000000000000000000000000000000000000000000000000000200",
|
|
4959
|
+
// 2
|
|
4960
|
+
"00000000000000000000000000000000000000000000000000000000000002a0",
|
|
4961
|
+
// 3
|
|
4962
|
+
"0000000000000000000000000000000000000000000000000000000000000380",
|
|
4963
|
+
// 4
|
|
4964
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4965
|
+
// 5
|
|
4966
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4967
|
+
// 6
|
|
4968
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4969
|
+
// 7
|
|
4970
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4971
|
+
// 8
|
|
4972
|
+
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
4973
|
+
// 9
|
|
4974
|
+
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
4975
|
+
// 10
|
|
4976
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4977
|
+
// 11 — empty array length
|
|
4978
|
+
"0000000000000000000000000000000000000000000000000000000000000040",
|
|
4979
|
+
// 12
|
|
4980
|
+
"0000000000000000000000000000000000000000000000000000000000000060",
|
|
4981
|
+
// 13
|
|
4982
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4983
|
+
// 14
|
|
4984
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4985
|
+
// 15
|
|
4986
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4987
|
+
// 16
|
|
4988
|
+
"0000000000000000000000000000000000000000000000000000000000000060",
|
|
4989
|
+
// 17
|
|
4990
|
+
"0000000000000000000000000000000000000000000000000000000000000080",
|
|
4991
|
+
// 18
|
|
4992
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4993
|
+
// 19
|
|
4994
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
4995
|
+
// 20
|
|
4996
|
+
amountHex,
|
|
4997
|
+
// 21 — ticket amount
|
|
4998
|
+
timestampHex,
|
|
4999
|
+
// 22 — ticket timestamp
|
|
5000
|
+
"0000000000000000000000000000000000000000000000000000000000000060",
|
|
5001
|
+
// 23 — sig offset
|
|
5002
|
+
"0000000000000000000000000000000000000000000000000000000000000041",
|
|
5003
|
+
// 24 — sig length (65)
|
|
5004
|
+
r,
|
|
5005
|
+
// 25 — sig r
|
|
5006
|
+
s,
|
|
5007
|
+
// 26 — sig s
|
|
5008
|
+
vPadded,
|
|
5009
|
+
// 27 — sig v + zero padding
|
|
5010
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
5011
|
+
// 28
|
|
5012
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
5013
|
+
// 29
|
|
5014
|
+
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
5015
|
+
// 30
|
|
5016
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
5017
|
+
// 31
|
|
5018
|
+
"00000000000000000000000000000000000000000000000000000000000000a0",
|
|
5019
|
+
// 32
|
|
5020
|
+
"0000000000000000000000000000000000000000000000000000000000000000"
|
|
5021
|
+
// 33
|
|
5022
|
+
];
|
|
5023
|
+
const data = "0xd6d7a454" + words.join("");
|
|
5024
|
+
return {
|
|
5025
|
+
description: `[${this.name()}] Claim NEST emissions (${(Number(ticket.amount) / 1e18).toFixed(2)} NEST cumulative; backend-signed ts=${ticket.timestamp})`,
|
|
5026
|
+
to: this.voter,
|
|
5027
|
+
data,
|
|
5028
|
+
value: 0n,
|
|
5029
|
+
gas_estimate: 6e5
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
// ── internal ──
|
|
5033
|
+
async fetchJson(path) {
|
|
5034
|
+
const primary = `${this.baseUrl}${path.startsWith("/claim") ? path : path}`;
|
|
5035
|
+
try {
|
|
5036
|
+
const res = await fetch(primary, this.requestInit());
|
|
5037
|
+
if (res.ok) return await res.json();
|
|
5038
|
+
if (res.status >= 500) throw new Error(`upstream ${res.status}`);
|
|
5039
|
+
throw DefiError.providerError(`Nest API ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
5040
|
+
} catch (e) {
|
|
5041
|
+
if (this.baseUrl === this.fallbackUrl) throw e;
|
|
5042
|
+
const fallback = `${this.fallbackUrl}${path}`;
|
|
5043
|
+
const res = await fetch(fallback, this.requestInit());
|
|
5044
|
+
if (!res.ok) {
|
|
5045
|
+
throw DefiError.providerError(`Nest fallback ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
5046
|
+
}
|
|
5047
|
+
return await res.json();
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
requestInit() {
|
|
5051
|
+
return { headers: { "User-Agent": "defi-cli/0.5", "Accept": "application/json" } };
|
|
5052
|
+
}
|
|
5053
|
+
};
|
|
4324
5054
|
POOL_ABI = parseAbi14([
|
|
4325
5055
|
"function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external",
|
|
4326
5056
|
"function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
|
|
@@ -4622,8 +5352,8 @@ var init_dist2 = __esm({
|
|
|
4622
5352
|
throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
|
|
4623
5353
|
});
|
|
4624
5354
|
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
|
|
4625
|
-
const
|
|
4626
|
-
const hf = healthFactor >=
|
|
5355
|
+
const MAX_UINT2562 = 2n ** 256n - 1n;
|
|
5356
|
+
const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
|
|
4627
5357
|
const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
|
|
4628
5358
|
const debtUsd = u256ToF64(totalDebtBase) / 1e8;
|
|
4629
5359
|
const ltvBps = u256ToF64(ltv);
|
|
@@ -4787,8 +5517,8 @@ var init_dist2 = __esm({
|
|
|
4787
5517
|
throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
|
|
4788
5518
|
});
|
|
4789
5519
|
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
|
|
4790
|
-
const
|
|
4791
|
-
const hf = healthFactor >=
|
|
5520
|
+
const MAX_UINT2562 = 2n ** 256n - 1n;
|
|
5521
|
+
const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
|
|
4792
5522
|
const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
|
|
4793
5523
|
const debtUsd = u256ToF642(totalDebtBase) / 1e18;
|
|
4794
5524
|
const ltvBps = u256ToF642(ltv);
|
|
@@ -5035,7 +5765,8 @@ var init_dist2 = __esm({
|
|
|
5035
5765
|
to: this.comet,
|
|
5036
5766
|
data,
|
|
5037
5767
|
value: 0n,
|
|
5038
|
-
gas_estimate: 3e5
|
|
5768
|
+
gas_estimate: 3e5,
|
|
5769
|
+
approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
|
|
5039
5770
|
};
|
|
5040
5771
|
}
|
|
5041
5772
|
async buildBorrow(params) {
|
|
@@ -5063,7 +5794,8 @@ var init_dist2 = __esm({
|
|
|
5063
5794
|
to: this.comet,
|
|
5064
5795
|
data,
|
|
5065
5796
|
value: 0n,
|
|
5066
|
-
gas_estimate: 3e5
|
|
5797
|
+
gas_estimate: 3e5,
|
|
5798
|
+
approvals: [{ token: params.asset, spender: this.comet, amount: params.amount }]
|
|
5067
5799
|
};
|
|
5068
5800
|
}
|
|
5069
5801
|
async buildWithdraw(params) {
|
|
@@ -5252,6 +5984,14 @@ var init_dist2 = __esm({
|
|
|
5252
5984
|
"function totalAssets() external view returns (uint256)",
|
|
5253
5985
|
"function totalSupply() external view returns (uint256)"
|
|
5254
5986
|
]);
|
|
5987
|
+
ERC4626_ABI = parseAbi20([
|
|
5988
|
+
"function asset() external view returns (address)",
|
|
5989
|
+
"function deposit(uint256 assets, address receiver) external returns (uint256 shares)",
|
|
5990
|
+
"function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets)",
|
|
5991
|
+
"function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares)",
|
|
5992
|
+
"function balanceOf(address owner) external view returns (uint256)"
|
|
5993
|
+
]);
|
|
5994
|
+
MAX_UINT256 = (1n << 256n) - 1n;
|
|
5255
5995
|
IRM_ABI = parseAbi20([
|
|
5256
5996
|
"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)"
|
|
5257
5997
|
]);
|
|
@@ -5261,6 +6001,9 @@ var init_dist2 = __esm({
|
|
|
5261
6001
|
morpho;
|
|
5262
6002
|
defaultVault;
|
|
5263
6003
|
rpcUrl;
|
|
6004
|
+
metaMorphoVaults;
|
|
6005
|
+
metaMorphoVaultEntries;
|
|
6006
|
+
vaultAssetMap = null;
|
|
5264
6007
|
constructor(entry, rpcUrl) {
|
|
5265
6008
|
this.protocolName = entry.name;
|
|
5266
6009
|
this.rpcUrl = rpcUrl;
|
|
@@ -5269,11 +6012,58 @@ var init_dist2 = __esm({
|
|
|
5269
6012
|
if (!morpho) throw DefiError.contractError("Missing 'morpho_blue' contract address");
|
|
5270
6013
|
this.morpho = morpho;
|
|
5271
6014
|
this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
|
|
6015
|
+
this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
|
|
6016
|
+
this.metaMorphoVaults = this.metaMorphoVaultEntries.map((e) => e.addr);
|
|
6017
|
+
}
|
|
6018
|
+
async resolveVault(asset, preferKey) {
|
|
6019
|
+
if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
|
|
6020
|
+
if (preferKey) {
|
|
6021
|
+
const direct = this.metaMorphoVaultEntries.find((e) => e.key === preferKey);
|
|
6022
|
+
if (direct) return direct.addr;
|
|
6023
|
+
}
|
|
6024
|
+
if (!this.vaultAssetMap) {
|
|
6025
|
+
const calls = this.metaMorphoVaultEntries.map((e) => [
|
|
6026
|
+
e.addr,
|
|
6027
|
+
encodeFunctionData19({ abi: ERC4626_ABI, functionName: "asset" })
|
|
6028
|
+
]);
|
|
6029
|
+
const results = await multicallRead(this.rpcUrl, calls).catch(() => []);
|
|
6030
|
+
const map = /* @__PURE__ */ new Map();
|
|
6031
|
+
for (let i = 0; i < results.length; i++) {
|
|
6032
|
+
const data = results[i];
|
|
6033
|
+
if (!data || data.length < 66) continue;
|
|
6034
|
+
const a = `0x${data.slice(26, 66)}`.toLowerCase();
|
|
6035
|
+
const entry = this.metaMorphoVaultEntries[i];
|
|
6036
|
+
const existing = map.get(a);
|
|
6037
|
+
if (!existing || entry.key.length < existing.key.length) {
|
|
6038
|
+
map.set(a, entry);
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
const flatMap = /* @__PURE__ */ new Map();
|
|
6042
|
+
for (const [k, v] of map) flatMap.set(k, v.addr);
|
|
6043
|
+
this.vaultAssetMap = flatMap;
|
|
6044
|
+
}
|
|
6045
|
+
return this.vaultAssetMap.get(asset.toLowerCase()) ?? null;
|
|
5272
6046
|
}
|
|
5273
6047
|
name() {
|
|
5274
6048
|
return this.protocolName;
|
|
5275
6049
|
}
|
|
5276
6050
|
async buildSupply(params) {
|
|
6051
|
+
const vault = await this.resolveVault(params.asset);
|
|
6052
|
+
if (vault) {
|
|
6053
|
+
const data2 = encodeFunctionData19({
|
|
6054
|
+
abi: ERC4626_ABI,
|
|
6055
|
+
functionName: "deposit",
|
|
6056
|
+
args: [params.amount, params.on_behalf_of]
|
|
6057
|
+
});
|
|
6058
|
+
return {
|
|
6059
|
+
description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
|
|
6060
|
+
to: vault,
|
|
6061
|
+
data: data2,
|
|
6062
|
+
value: 0n,
|
|
6063
|
+
gas_estimate: 4e5,
|
|
6064
|
+
approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
|
|
6065
|
+
};
|
|
6066
|
+
}
|
|
5277
6067
|
const market = defaultMarketParams(params.asset);
|
|
5278
6068
|
const data = encodeFunctionData19({
|
|
5279
6069
|
abi: MORPHO_ABI,
|
|
@@ -5319,6 +6109,40 @@ var init_dist2 = __esm({
|
|
|
5319
6109
|
};
|
|
5320
6110
|
}
|
|
5321
6111
|
async buildWithdraw(params) {
|
|
6112
|
+
const vault = await this.resolveVault(params.asset);
|
|
6113
|
+
if (vault) {
|
|
6114
|
+
if (params.amount === MAX_UINT256) {
|
|
6115
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC required to fetch vault shares");
|
|
6116
|
+
const [balRaw] = await multicallRead(this.rpcUrl, [
|
|
6117
|
+
[vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
|
|
6118
|
+
]);
|
|
6119
|
+
const shares = decodeU256(balRaw ?? null);
|
|
6120
|
+
const data3 = encodeFunctionData19({
|
|
6121
|
+
abi: ERC4626_ABI,
|
|
6122
|
+
functionName: "redeem",
|
|
6123
|
+
args: [shares, params.to, params.to]
|
|
6124
|
+
});
|
|
6125
|
+
return {
|
|
6126
|
+
description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
|
|
6127
|
+
to: vault,
|
|
6128
|
+
data: data3,
|
|
6129
|
+
value: 0n,
|
|
6130
|
+
gas_estimate: 4e5
|
|
6131
|
+
};
|
|
6132
|
+
}
|
|
6133
|
+
const data2 = encodeFunctionData19({
|
|
6134
|
+
abi: ERC4626_ABI,
|
|
6135
|
+
functionName: "withdraw",
|
|
6136
|
+
args: [params.amount, params.to, params.to]
|
|
6137
|
+
});
|
|
6138
|
+
return {
|
|
6139
|
+
description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
|
|
6140
|
+
to: vault,
|
|
6141
|
+
data: data2,
|
|
6142
|
+
value: 0n,
|
|
6143
|
+
gas_estimate: 4e5
|
|
6144
|
+
};
|
|
6145
|
+
}
|
|
5322
6146
|
const market = defaultMarketParams(params.asset);
|
|
5323
6147
|
const data = encodeFunctionData19({
|
|
5324
6148
|
abi: MORPHO_ABI,
|
|
@@ -5637,7 +6461,7 @@ var init_dist2 = __esm({
|
|
|
5637
6461
|
return results;
|
|
5638
6462
|
}
|
|
5639
6463
|
};
|
|
5640
|
-
|
|
6464
|
+
ERC4626_ABI2 = parseAbi23([
|
|
5641
6465
|
"function asset() external view returns (address)",
|
|
5642
6466
|
"function totalAssets() external view returns (uint256)",
|
|
5643
6467
|
"function totalSupply() external view returns (uint256)",
|
|
@@ -5662,7 +6486,7 @@ var init_dist2 = __esm({
|
|
|
5662
6486
|
}
|
|
5663
6487
|
async buildDeposit(assets, receiver) {
|
|
5664
6488
|
const data = encodeFunctionData21({
|
|
5665
|
-
abi:
|
|
6489
|
+
abi: ERC4626_ABI2,
|
|
5666
6490
|
functionName: "deposit",
|
|
5667
6491
|
args: [assets, receiver]
|
|
5668
6492
|
});
|
|
@@ -5676,7 +6500,7 @@ var init_dist2 = __esm({
|
|
|
5676
6500
|
}
|
|
5677
6501
|
async buildWithdraw(assets, receiver, owner) {
|
|
5678
6502
|
const data = encodeFunctionData21({
|
|
5679
|
-
abi:
|
|
6503
|
+
abi: ERC4626_ABI2,
|
|
5680
6504
|
functionName: "withdraw",
|
|
5681
6505
|
args: [assets, receiver, owner]
|
|
5682
6506
|
});
|
|
@@ -5693,7 +6517,7 @@ var init_dist2 = __esm({
|
|
|
5693
6517
|
const client = createPublicClient18({ transport: http18(this.rpcUrl) });
|
|
5694
6518
|
return client.readContract({
|
|
5695
6519
|
address: this.vaultAddress,
|
|
5696
|
-
abi:
|
|
6520
|
+
abi: ERC4626_ABI2,
|
|
5697
6521
|
functionName: "totalAssets"
|
|
5698
6522
|
}).catch((e) => {
|
|
5699
6523
|
throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
|
|
@@ -5704,7 +6528,7 @@ var init_dist2 = __esm({
|
|
|
5704
6528
|
const client = createPublicClient18({ transport: http18(this.rpcUrl) });
|
|
5705
6529
|
return client.readContract({
|
|
5706
6530
|
address: this.vaultAddress,
|
|
5707
|
-
abi:
|
|
6531
|
+
abi: ERC4626_ABI2,
|
|
5708
6532
|
functionName: "convertToShares",
|
|
5709
6533
|
args: [assets]
|
|
5710
6534
|
}).catch((e) => {
|
|
@@ -5716,7 +6540,7 @@ var init_dist2 = __esm({
|
|
|
5716
6540
|
const client = createPublicClient18({ transport: http18(this.rpcUrl) });
|
|
5717
6541
|
return client.readContract({
|
|
5718
6542
|
address: this.vaultAddress,
|
|
5719
|
-
abi:
|
|
6543
|
+
abi: ERC4626_ABI2,
|
|
5720
6544
|
functionName: "convertToAssets",
|
|
5721
6545
|
args: [shares]
|
|
5722
6546
|
}).catch((e) => {
|
|
@@ -5727,13 +6551,13 @@ var init_dist2 = __esm({
|
|
|
5727
6551
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5728
6552
|
const client = createPublicClient18({ transport: http18(this.rpcUrl) });
|
|
5729
6553
|
const [totalAssets, totalSupply, asset] = await Promise.all([
|
|
5730
|
-
client.readContract({ address: this.vaultAddress, abi:
|
|
6554
|
+
client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
|
|
5731
6555
|
throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
|
|
5732
6556
|
}),
|
|
5733
|
-
client.readContract({ address: this.vaultAddress, abi:
|
|
6557
|
+
client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalSupply" }).catch((e) => {
|
|
5734
6558
|
throw DefiError.rpcError(`[${this.protocolName}] totalSupply failed: ${e}`);
|
|
5735
6559
|
}),
|
|
5736
|
-
client.readContract({ address: this.vaultAddress, abi:
|
|
6560
|
+
client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "asset" }).catch((e) => {
|
|
5737
6561
|
throw DefiError.rpcError(`[${this.protocolName}] asset failed: ${e}`);
|
|
5738
6562
|
})
|
|
5739
6563
|
]);
|
|
@@ -7168,6 +7992,9 @@ function loadWhitelist() {
|
|
|
7168
7992
|
}
|
|
7169
7993
|
}
|
|
7170
7994
|
|
|
7995
|
+
// src/signer/resolve.ts
|
|
7996
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
7997
|
+
|
|
7171
7998
|
// src/signer/ows-loader.ts
|
|
7172
7999
|
import { createRequire } from "module";
|
|
7173
8000
|
var _require = createRequire(import.meta.url);
|
|
@@ -7273,7 +8100,6 @@ function resolveWalletWithSigner(opts) {
|
|
|
7273
8100
|
}
|
|
7274
8101
|
const pk = process.env["DEFI_PRIVATE_KEY"];
|
|
7275
8102
|
if (pk) {
|
|
7276
|
-
const { privateKeyToAccount: privateKeyToAccount2 } = __require("viem/accounts");
|
|
7277
8103
|
return { address: privateKeyToAccount2(pk).address, signer: null };
|
|
7278
8104
|
}
|
|
7279
8105
|
if (envWallet) {
|
|
@@ -7290,10 +8116,116 @@ function resolveAccount(optOwner, optWallet) {
|
|
|
7290
8116
|
const { address } = resolveWalletWithSigner(optWallet ? { wallet: optWallet } : void 0);
|
|
7291
8117
|
return address;
|
|
7292
8118
|
}
|
|
8119
|
+
function buildCmd(parts) {
|
|
8120
|
+
const segs = [];
|
|
8121
|
+
for (const [flag, value, placeholder] of parts) {
|
|
8122
|
+
segs.push(`${flag} ${value ?? `<${placeholder}>`}`);
|
|
8123
|
+
}
|
|
8124
|
+
return segs.join(" ");
|
|
8125
|
+
}
|
|
8126
|
+
function buildPipelineSteps(p, input = {}) {
|
|
8127
|
+
const slug = p.slug;
|
|
8128
|
+
const chainFlag = input.chain ? `--chain ${input.chain} ` : "";
|
|
8129
|
+
const baseAdd = `defi ${chainFlag}lp add ` + buildCmd([
|
|
8130
|
+
["--protocol", slug, "slug"],
|
|
8131
|
+
["--token-a", input.tokenA, "token-a"],
|
|
8132
|
+
["--token-b", input.tokenB, "token-b"],
|
|
8133
|
+
["--amount-a", input.amountA, "amount-a"],
|
|
8134
|
+
["--amount-b", input.amountB, "amount-b"],
|
|
8135
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : [],
|
|
8136
|
+
...input.tickLower ? [["--tick-lower", input.tickLower, "tick-lower"]] : [],
|
|
8137
|
+
...input.tickUpper ? [["--tick-upper", input.tickUpper, "tick-upper"]] : [],
|
|
8138
|
+
...input.range ? [["--range", input.range, "range"]] : []
|
|
8139
|
+
]);
|
|
8140
|
+
const baseFarm = `defi ${chainFlag}lp farm ` + buildCmd([
|
|
8141
|
+
["--protocol", slug, "slug"],
|
|
8142
|
+
["--token-a", input.tokenA, "token-a"],
|
|
8143
|
+
["--token-b", input.tokenB, "token-b"],
|
|
8144
|
+
["--amount-a", input.amountA, "amount-a"],
|
|
8145
|
+
["--amount-b", input.amountB, "amount-b"],
|
|
8146
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : []
|
|
8147
|
+
]);
|
|
8148
|
+
const claimWithTokenId = (extra = "") => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8149
|
+
["--protocol", slug, "slug"],
|
|
8150
|
+
["--token-id", input.tokenId, "token-id-from-mint-result"],
|
|
8151
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : [],
|
|
8152
|
+
...input.gauge ? [["--gauge", input.gauge, "gauge"]] : []
|
|
8153
|
+
]) + extra;
|
|
8154
|
+
const claimWithGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8155
|
+
["--protocol", slug, "slug"],
|
|
8156
|
+
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
|
|
8157
|
+
]);
|
|
8158
|
+
switch (p.reward_strategy) {
|
|
8159
|
+
case "lp_fee_only":
|
|
8160
|
+
return [
|
|
8161
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8162
|
+
{ step: "collect", function: "NPM.collect(tokenId, recipient)", note: "No emissions; collects accrued LP trading fees only", cli_command: claimWithTokenId() }
|
|
8163
|
+
];
|
|
8164
|
+
case "on_chain_farming_center":
|
|
8165
|
+
return [
|
|
8166
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8167
|
+
{ step: "stake", function: "farmingCenter.enterFarming(incentiveKey, tokenId)", cli_command: baseFarm, note: "lp farm chains mint+stake into one tx sequence" },
|
|
8168
|
+
{ step: "claim", function: "farmingCenter.collectRewards(incentiveKey, tokenId)", cli_command: claimWithTokenId() }
|
|
8169
|
+
];
|
|
8170
|
+
case "on_chain_gauge_tokenid":
|
|
8171
|
+
return [
|
|
8172
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8173
|
+
{ step: "stake", function: "gauge.deposit(tokenId)", cli_command: baseFarm, note: "lp farm chains mint+stake into one tx sequence" },
|
|
8174
|
+
{ step: "claim", function: "gauge.earned(tokenId) \u2192 gauge.getReward(tokenId)", cli_command: claimWithTokenId() }
|
|
8175
|
+
];
|
|
8176
|
+
case "on_chain_gauge":
|
|
8177
|
+
return [
|
|
8178
|
+
{ step: "mint", function: "Router.addLiquidity / NPM.mint", cli_command: baseAdd },
|
|
8179
|
+
{ step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
|
|
8180
|
+
{ step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
|
|
8181
|
+
];
|
|
8182
|
+
case "auto_stake":
|
|
8183
|
+
return [
|
|
8184
|
+
{ step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
|
|
8185
|
+
{ step: "claim", function: "gauge.getReward(account, tokens[])", note: "Multi-token reward (xRAM + WHYPE on Ramses HL)", cli_command: claimWithGauge() }
|
|
8186
|
+
];
|
|
8187
|
+
case "on_chain_masterchef":
|
|
8188
|
+
return [
|
|
8189
|
+
{ step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
|
|
8190
|
+
{ step: "stake", function: "MasterChef.deposit(pid, amount)", cli_command: baseFarm },
|
|
8191
|
+
{ step: "claim", function: "MasterChef.harvest(pid) or pendingCake(pid, user)", cli_command: claimWithTokenId() }
|
|
8192
|
+
];
|
|
8193
|
+
case "off_chain_api":
|
|
8194
|
+
return [
|
|
8195
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8196
|
+
{ step: "claim", function: "GET claim-data \u2192 voter.aggregateClaim(...)", note: "Read-only: backend-signed ticket only; broadcast ABI unresolved (selector 0xd6d7a454, 11 args, public registries miss). Use the printed `ticket` payload to submit through the Nest UI.", cli_command: `defi ${chainFlag}lp claim --protocol ${slug} --address <wallet>` }
|
|
8197
|
+
];
|
|
8198
|
+
case "none":
|
|
8199
|
+
return [{ step: "mint", function: "Router/NPM mint", note: "No reward path declared", cli_command: baseAdd }];
|
|
8200
|
+
default:
|
|
8201
|
+
return [{ step: "mint", function: "Router/NPM mint", note: "reward_strategy unset \u2014 pipeline cannot be inferred", cli_command: baseAdd }];
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
7293
8204
|
function resolvePoolAddress(registry, protocolSlug, pool) {
|
|
7294
8205
|
if (pool.startsWith("0x")) return pool;
|
|
7295
8206
|
return registry.resolvePool(protocolSlug, pool).address;
|
|
7296
8207
|
}
|
|
8208
|
+
async function detectV3Liquidity(client, npm, tokenId) {
|
|
8209
|
+
const ramsesAbi = parseAbi30([
|
|
8210
|
+
"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)"
|
|
8211
|
+
]);
|
|
8212
|
+
const standardAbi = parseAbi30([
|
|
8213
|
+
"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)"
|
|
8214
|
+
]);
|
|
8215
|
+
try {
|
|
8216
|
+
const r = await client.readContract({ address: npm, abi: ramsesAbi, functionName: "positions", args: [tokenId] });
|
|
8217
|
+
if (r[5] !== void 0) {
|
|
8218
|
+
return { token0: r[0], token1: r[1], tickLower: r[3], tickUpper: r[4], liquidity: r[5] };
|
|
8219
|
+
}
|
|
8220
|
+
} catch {
|
|
8221
|
+
}
|
|
8222
|
+
try {
|
|
8223
|
+
const r = await client.readContract({ address: npm, abi: standardAbi, functionName: "positions", args: [tokenId] });
|
|
8224
|
+
return { token0: r[2], token1: r[3], tickLower: r[5], tickUpper: r[6], liquidity: r[7] };
|
|
8225
|
+
} catch {
|
|
8226
|
+
return null;
|
|
8227
|
+
}
|
|
8228
|
+
}
|
|
7297
8229
|
var V2_PAIR_ABI = parseAbi30([
|
|
7298
8230
|
"function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)",
|
|
7299
8231
|
"function totalSupply() external view returns (uint256)"
|
|
@@ -7576,18 +8508,59 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7576
8508
|
} catch {
|
|
7577
8509
|
}
|
|
7578
8510
|
}
|
|
8511
|
+
if (protocol.interface === "curve_stableswap" && protocol.contracts?.["stableswap_factory"]) {
|
|
8512
|
+
const factory = protocol.contracts["stableswap_factory"];
|
|
8513
|
+
const factoryAbi = parseAbi30([
|
|
8514
|
+
"function pool_count() view returns (uint256)",
|
|
8515
|
+
"function pool_list(uint256) view returns (address)"
|
|
8516
|
+
]);
|
|
8517
|
+
const poolAbi2 = parseAbi30([
|
|
8518
|
+
"function coins(uint256) view returns (address)",
|
|
8519
|
+
"function name() view returns (string)"
|
|
8520
|
+
]);
|
|
8521
|
+
const cClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8522
|
+
try {
|
|
8523
|
+
const count = await cClient.readContract({ address: factory, abi: factoryAbi, functionName: "pool_count" });
|
|
8524
|
+
const MAX_SCAN = Math.min(Number(count), 50);
|
|
8525
|
+
for (let i = 0; i < MAX_SCAN; i++) {
|
|
8526
|
+
try {
|
|
8527
|
+
const pool = await cClient.readContract({ address: factory, abi: factoryAbi, functionName: "pool_list", args: [BigInt(i)] });
|
|
8528
|
+
const [c0, c1, name] = await Promise.all([
|
|
8529
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "coins", args: [0n] }).catch(() => null),
|
|
8530
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "coins", args: [1n] }).catch(() => null),
|
|
8531
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "name" }).catch(() => "")
|
|
8532
|
+
]);
|
|
8533
|
+
results.push({
|
|
8534
|
+
protocol: protocol.slug,
|
|
8535
|
+
pool,
|
|
8536
|
+
pair: name || `${c0 ?? "?"}/${c1 ?? "?"}`,
|
|
8537
|
+
type: "FEE",
|
|
8538
|
+
source: "curve_factory"
|
|
8539
|
+
});
|
|
8540
|
+
} catch {
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
8543
|
+
} catch {
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
7579
8546
|
} catch {
|
|
7580
8547
|
}
|
|
7581
8548
|
})
|
|
7582
8549
|
);
|
|
7583
8550
|
await _enrichGaugeAprs(results, rpcUrl, registry, chainName);
|
|
8551
|
+
results.sort((a, b) => (b.aprPercent ?? 0) - (a.aprPercent ?? 0));
|
|
7584
8552
|
if (opts.emissionOnly) {
|
|
7585
|
-
printOutput(
|
|
8553
|
+
printOutput(
|
|
8554
|
+
results.filter(
|
|
8555
|
+
(r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n)
|
|
8556
|
+
),
|
|
8557
|
+
getOpts()
|
|
8558
|
+
);
|
|
7586
8559
|
} else {
|
|
7587
8560
|
printOutput(results, getOpts());
|
|
7588
8561
|
}
|
|
7589
8562
|
});
|
|
7590
|
-
lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").action(async (opts) => {
|
|
8563
|
+
lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").option("--num-bins <n>", "Merchant Moe LB: bins on each side of active (default 5)").action(async (opts) => {
|
|
7591
8564
|
const executor = makeExecutor2();
|
|
7592
8565
|
const chainName = parent.opts().chain;
|
|
7593
8566
|
if (!chainName) {
|
|
@@ -7597,11 +8570,35 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7597
8570
|
const registry = Registry.loadEmbedded();
|
|
7598
8571
|
const chain = registry.getChain(chainName);
|
|
7599
8572
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7600
|
-
const adapter = createDex(protocol, chain.effectiveRpcUrl());
|
|
7601
8573
|
const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
7602
8574
|
const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
7603
8575
|
const recipient = opts.recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
|
|
7604
8576
|
const poolAddr = opts.pool ? resolvePoolAddress(registry, opts.protocol, opts.pool) : void 0;
|
|
8577
|
+
if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
8578
|
+
if (!poolAddr) throw new Error("--pool is required for Merchant Moe LB add");
|
|
8579
|
+
const lbAdapter = createMerchantMoeLB(protocol, chain.effectiveRpcUrl());
|
|
8580
|
+
const [tokenX, tokenY, amountX, amountY] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB, BigInt(opts.amountA), BigInt(opts.amountB)] : [tokenB, tokenA, BigInt(opts.amountB), BigInt(opts.amountA)];
|
|
8581
|
+
const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
|
|
8582
|
+
const binStep = await client.readContract({
|
|
8583
|
+
address: poolAddr,
|
|
8584
|
+
abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
|
|
8585
|
+
functionName: "getBinStep"
|
|
8586
|
+
});
|
|
8587
|
+
const tx2 = await lbAdapter.buildAddLiquidity({
|
|
8588
|
+
pool: poolAddr,
|
|
8589
|
+
tokenX,
|
|
8590
|
+
tokenY,
|
|
8591
|
+
binStep,
|
|
8592
|
+
amountX,
|
|
8593
|
+
amountY,
|
|
8594
|
+
recipient,
|
|
8595
|
+
numBins: opts.numBins !== void 0 ? parseInt(opts.numBins, 10) : 5
|
|
8596
|
+
});
|
|
8597
|
+
const result2 = await executor.execute(tx2);
|
|
8598
|
+
printOutput(result2, getOpts());
|
|
8599
|
+
return;
|
|
8600
|
+
}
|
|
8601
|
+
const adapter = createDex(protocol, chain.effectiveRpcUrl());
|
|
7605
8602
|
const tx = await adapter.buildAddLiquidity({
|
|
7606
8603
|
protocol: protocol.name,
|
|
7607
8604
|
token_a: tokenA,
|
|
@@ -7667,7 +8664,8 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7667
8664
|
printOutput({ step: "stake_farming", ...stakeResult }, getOpts());
|
|
7668
8665
|
return;
|
|
7669
8666
|
}
|
|
7670
|
-
|
|
8667
|
+
const isGaugeStakeable = ["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && protocol.contracts?.["voter"];
|
|
8668
|
+
if (isGaugeStakeable) {
|
|
7671
8669
|
if (!mintedTokenId && iface !== "solidly_v2") {
|
|
7672
8670
|
process.stderr.write("Step 2/2: Skipped staking (no tokenId \u2014 run in --broadcast mode to get minted NFT)\n");
|
|
7673
8671
|
return;
|
|
@@ -7686,7 +8684,21 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7686
8684
|
process.stderr.write("Step 2/2: Staking into gauge...\n");
|
|
7687
8685
|
const gaugeAdapter = createGauge(protocol, rpcUrl);
|
|
7688
8686
|
const tokenIdArg = mintedTokenId;
|
|
7689
|
-
|
|
8687
|
+
let amountArg = 0n;
|
|
8688
|
+
if (iface === "solidly_v2" && poolAddr) {
|
|
8689
|
+
const erc20Abi3 = parseAbi30(["function balanceOf(address) view returns (uint256)"]);
|
|
8690
|
+
const lpClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8691
|
+
amountArg = await lpClient.readContract({
|
|
8692
|
+
address: poolAddr,
|
|
8693
|
+
abi: erc20Abi3,
|
|
8694
|
+
functionName: "balanceOf",
|
|
8695
|
+
args: [recipient]
|
|
8696
|
+
});
|
|
8697
|
+
if (amountArg === 0n) {
|
|
8698
|
+
process.stderr.write("Step 2/2: Skipped staking \u2014 LP balance 0 (Step 1 add returned no LP).\n");
|
|
8699
|
+
return;
|
|
8700
|
+
}
|
|
8701
|
+
}
|
|
7690
8702
|
const lpTokenArg = iface === "solidly_v2" ? poolAddr : void 0;
|
|
7691
8703
|
const stakeTx = await gaugeAdapter.buildDeposit(gaugeAddr, amountArg, tokenIdArg, lpTokenArg);
|
|
7692
8704
|
const stakeResult = await executor.execute(stakeTx);
|
|
@@ -7699,7 +8711,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7699
8711
|
}
|
|
7700
8712
|
process.stderr.write("Step 2/2: No staking adapter found for this protocol interface \u2014 liquidity added only.\n");
|
|
7701
8713
|
});
|
|
7702
|
-
lp.command("claim").description("Claim rewards from a pool (fee or emission)").requiredOption("--protocol <protocol>", "Protocol slug").option("--pool <address>", "Pool address (required for farming/LB)").option("--gauge <address>", "Gauge contract address (required for solidly/hybra)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--bins <binIds>", "Comma-separated bin IDs (for Merchant Moe LB)").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
8714
|
+
lp.command("claim").description("Claim rewards from a pool (fee or emission)").requiredOption("--protocol <protocol>", "Protocol slug").option("--pool <address>", "Pool address (required for farming/LB)").option("--gauge <address>", "Gauge contract address (required for solidly/hybra)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--bins <binIds>", "Comma-separated bin IDs (for Merchant Moe LB)").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default)").action(async (opts) => {
|
|
7703
8715
|
const executor = makeExecutor2();
|
|
7704
8716
|
const chainName = parent.opts().chain;
|
|
7705
8717
|
if (!chainName) {
|
|
@@ -7712,6 +8724,86 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7712
8724
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7713
8725
|
const account = resolveAccount(opts.address, lp.opts().wallet);
|
|
7714
8726
|
const iface = protocol.interface;
|
|
8727
|
+
const isV3Fee = protocol.reward_strategy === "lp_fee_only" || iface === "uniswap_v3" && opts.tokenId && !opts.gauge;
|
|
8728
|
+
if (isV3Fee) {
|
|
8729
|
+
if (!opts.tokenId) throw new Error("--token-id is required for V3 LP fee collection");
|
|
8730
|
+
const adapter = createDex(protocol, rpcUrl);
|
|
8731
|
+
if (!("buildCollectFees" in adapter) || typeof adapter.buildCollectFees !== "function") {
|
|
8732
|
+
throw new Error(`[${protocol.name}] adapter does not support buildCollectFees`);
|
|
8733
|
+
}
|
|
8734
|
+
const tx = await adapter.buildCollectFees(BigInt(opts.tokenId), account);
|
|
8735
|
+
const result = await executor.execute(tx);
|
|
8736
|
+
printOutput(result, getOpts());
|
|
8737
|
+
return;
|
|
8738
|
+
}
|
|
8739
|
+
if (protocol.reward_strategy === "off_chain_api") {
|
|
8740
|
+
const nest = createNestOffChain(protocol);
|
|
8741
|
+
const status = await nest.getClaimStatus(account);
|
|
8742
|
+
const ticket = await nest.getClaimTicket(account);
|
|
8743
|
+
if (!ticket) {
|
|
8744
|
+
printOutput({
|
|
8745
|
+
protocol: protocol.slug,
|
|
8746
|
+
wallet: account,
|
|
8747
|
+
voter: nest.getVoterAddress(),
|
|
8748
|
+
reward_symbol: "NEST",
|
|
8749
|
+
pending_amount: 0,
|
|
8750
|
+
note: "No claim ticket available \u2014 backend reports no points to claim."
|
|
8751
|
+
}, getOpts());
|
|
8752
|
+
return;
|
|
8753
|
+
}
|
|
8754
|
+
const tx = await nest.buildClaim(account);
|
|
8755
|
+
const preflightClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8756
|
+
try {
|
|
8757
|
+
await preflightClient.call({
|
|
8758
|
+
account,
|
|
8759
|
+
to: tx.to,
|
|
8760
|
+
data: tx.data,
|
|
8761
|
+
value: tx.value
|
|
8762
|
+
});
|
|
8763
|
+
} catch (e) {
|
|
8764
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8765
|
+
printOutput({
|
|
8766
|
+
protocol: protocol.slug,
|
|
8767
|
+
wallet: account,
|
|
8768
|
+
voter: nest.getVoterAddress(),
|
|
8769
|
+
reward_symbol: "NEST",
|
|
8770
|
+
pending_amount: status.pendingFormatted,
|
|
8771
|
+
pending_raw: status.pendingRaw.toString(),
|
|
8772
|
+
ticket: {
|
|
8773
|
+
amount: ticket.amount.toString(),
|
|
8774
|
+
timestamp: ticket.timestamp.toString(),
|
|
8775
|
+
day: ticket.day === null ? null : ticket.day.toString(),
|
|
8776
|
+
signature: ticket.signature
|
|
8777
|
+
},
|
|
8778
|
+
preflight: "revert",
|
|
8779
|
+
preflight_error: msg.length > 400 ? msg.slice(0, 400) + "..." : msg,
|
|
8780
|
+
broadcast: "skipped",
|
|
8781
|
+
note: "Simulation reverted \u2014 broadcast aborted to avoid wasted gas. Common causes: ticket expired, caller != ticket signer, ticket already claimed."
|
|
8782
|
+
}, getOpts());
|
|
8783
|
+
return;
|
|
8784
|
+
}
|
|
8785
|
+
const result = await executor.execute(tx);
|
|
8786
|
+
printOutput({
|
|
8787
|
+
protocol: protocol.slug,
|
|
8788
|
+
wallet: account,
|
|
8789
|
+
voter: nest.getVoterAddress(),
|
|
8790
|
+
reward_symbol: "NEST",
|
|
8791
|
+
pending_amount: status.pendingFormatted,
|
|
8792
|
+
pending_raw: status.pendingRaw.toString(),
|
|
8793
|
+
total_claimed_raw: status.totalClaimedRaw.toString(),
|
|
8794
|
+
total_available_raw: status.totalAvailableRaw.toString(),
|
|
8795
|
+
ticket: {
|
|
8796
|
+
amount: ticket.amount.toString(),
|
|
8797
|
+
timestamp: ticket.timestamp.toString(),
|
|
8798
|
+
day: ticket.day === null ? null : ticket.day.toString(),
|
|
8799
|
+
signature: ticket.signature
|
|
8800
|
+
},
|
|
8801
|
+
preflight: "passed",
|
|
8802
|
+
claim_result: result,
|
|
8803
|
+
note: "Calldata derived from byte-level template (verified against two known-successful onchain claim txs). Pre-flight eth_call simulation passed before broadcast."
|
|
8804
|
+
}, getOpts());
|
|
8805
|
+
return;
|
|
8806
|
+
}
|
|
7715
8807
|
if (iface === "algebra_v3" && protocol.contracts?.["farming_center"]) {
|
|
7716
8808
|
if (!opts.pool) throw new Error("--pool is required for KittenSwap farming claim");
|
|
7717
8809
|
if (!opts.tokenId) throw new Error("--token-id is required for KittenSwap farming claim");
|
|
@@ -7738,9 +8830,19 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7738
8830
|
if (!opts.gauge) throw new Error("--gauge is required for gauge claim");
|
|
7739
8831
|
const adapter = createGauge(protocol, rpcUrl);
|
|
7740
8832
|
let tx;
|
|
7741
|
-
if (opts.tokenId) {
|
|
8833
|
+
if (opts.tokenId && iface === "uniswap_v3" && protocol.reward_strategy === "auto_stake" && "buildClaimRewardsViaNPMPeriodReward" in adapter && typeof adapter.buildClaimRewardsViaNPMPeriodReward === "function") {
|
|
8834
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
8835
|
+
if (!npm) throw new Error(`${protocol.name} requires contracts.position_manager for NPM-based claim`);
|
|
8836
|
+
tx = await adapter.buildClaimRewardsViaNPMPeriodReward(
|
|
8837
|
+
npm,
|
|
8838
|
+
BigInt(opts.tokenId),
|
|
8839
|
+
account,
|
|
8840
|
+
{ gauge: opts.gauge }
|
|
8841
|
+
);
|
|
8842
|
+
} else if (opts.tokenId) {
|
|
7742
8843
|
if (!adapter.buildClaimRewardsByTokenId) throw new Error(`${protocol.name} does not support NFT-based claim`);
|
|
7743
|
-
|
|
8844
|
+
const claimOpts = opts.redeemType !== void 0 ? { redeemType: parseInt(opts.redeemType, 10) } : void 0;
|
|
8845
|
+
tx = await adapter.buildClaimRewardsByTokenId(opts.gauge, BigInt(opts.tokenId), claimOpts);
|
|
7744
8846
|
} else {
|
|
7745
8847
|
tx = await adapter.buildClaimRewards(opts.gauge, account);
|
|
7746
8848
|
}
|
|
@@ -7750,7 +8852,35 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7750
8852
|
}
|
|
7751
8853
|
throw new Error(`No claim method found for protocol interface '${iface}'`);
|
|
7752
8854
|
});
|
|
7753
|
-
lp.command("
|
|
8855
|
+
lp.command("pipeline").description("Show the mint\u2192stake\u2192claim sequence implied by a protocol's reward_strategy. Pass --token-a/-b/--amount-a/-b etc. to get fully-resolved CLI commands you can copy-paste.").requiredOption("--protocol <protocol>", "Protocol slug").option("--pool <pool>", "Pool name or address (e.g. WHYPE/USDC)").option("--token-a <token>", "First token symbol or address").option("--token-b <token>", "Second token symbol or address").option("--amount-a <amount>", "Amount of token A in wei").option("--amount-b <amount>", "Amount of token B in wei").option("--tick-lower <tick>", "Lower tick (V3/CL)").option("--tick-upper <tick>", "Upper tick (V3/CL)").option("--range <percent>", "\xB1N% concentrated range").option("--token-id <id>", "NFT tokenId for already-minted positions (skips mint step)").option("--gauge <addr>", "Gauge address for stake/claim").action((opts) => {
|
|
8856
|
+
const registry = Registry.loadEmbedded();
|
|
8857
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
8858
|
+
const chainName = parent.opts().chain;
|
|
8859
|
+
const steps = buildPipelineSteps(protocol, {
|
|
8860
|
+
chain: chainName,
|
|
8861
|
+
pool: opts.pool,
|
|
8862
|
+
tokenA: opts.tokenA,
|
|
8863
|
+
tokenB: opts.tokenB,
|
|
8864
|
+
amountA: opts.amountA,
|
|
8865
|
+
amountB: opts.amountB,
|
|
8866
|
+
tickLower: opts.tickLower,
|
|
8867
|
+
tickUpper: opts.tickUpper,
|
|
8868
|
+
range: opts.range,
|
|
8869
|
+
tokenId: opts.tokenId,
|
|
8870
|
+
gauge: opts.gauge
|
|
8871
|
+
});
|
|
8872
|
+
printOutput({
|
|
8873
|
+
protocol: protocol.slug,
|
|
8874
|
+
chain: protocol.chain,
|
|
8875
|
+
interface: protocol.interface,
|
|
8876
|
+
is_active: protocol.is_active !== false,
|
|
8877
|
+
verified: protocol.verified === true,
|
|
8878
|
+
reward_strategy: protocol.reward_strategy ?? "(unset \u2014 falls back to interface inference)",
|
|
8879
|
+
steps,
|
|
8880
|
+
note: "Plan output. Run each cli_command sequentially. After the mint step, broadcast mode prints `details.minted_token_id` \u2014 feed that into the next step's --token-id."
|
|
8881
|
+
}, getOpts());
|
|
8882
|
+
});
|
|
8883
|
+
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--liquidity <amount>", "Liquidity amount to remove in wei").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
|
|
7754
8884
|
const executor = makeExecutor2();
|
|
7755
8885
|
const chainName = parent.opts().chain;
|
|
7756
8886
|
if (!chainName) {
|
|
@@ -7763,6 +8893,51 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7763
8893
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7764
8894
|
const iface = protocol.interface;
|
|
7765
8895
|
const recipient = opts.recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
|
|
8896
|
+
if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
8897
|
+
if (!opts.pool) throw new Error("--pool is required for Merchant Moe LB remove");
|
|
8898
|
+
if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
|
|
8899
|
+
const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
8900
|
+
const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
8901
|
+
const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
8902
|
+
const [tokenX, tokenY] = tokenA2.toLowerCase() < tokenB2.toLowerCase() ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
|
|
8903
|
+
const binIds = opts.bins.split(",").map((s) => parseInt(s.trim()));
|
|
8904
|
+
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8905
|
+
const binStep = await client.readContract({
|
|
8906
|
+
address: opts.pool,
|
|
8907
|
+
abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
|
|
8908
|
+
functionName: "getBinStep"
|
|
8909
|
+
});
|
|
8910
|
+
let amounts;
|
|
8911
|
+
if (opts.amounts) {
|
|
8912
|
+
amounts = opts.amounts.split(",").map((s) => BigInt(s.trim()));
|
|
8913
|
+
} else {
|
|
8914
|
+
const balanceAbi = parseAbi30(["function balanceOf(address account, uint256 id) view returns (uint256)"]);
|
|
8915
|
+
amounts = await Promise.all(binIds.map(
|
|
8916
|
+
(id) => client.readContract({ address: opts.pool, abi: balanceAbi, functionName: "balanceOf", args: [recipient, BigInt(id)] })
|
|
8917
|
+
));
|
|
8918
|
+
}
|
|
8919
|
+
const approveForAllAbi = parseAbi30(["function approveForAll(address operator, bool approved) external"]);
|
|
8920
|
+
const lbRouter = protocol.contracts["lb_router"];
|
|
8921
|
+
const approveTx = {
|
|
8922
|
+
description: `[${protocol.name}] approveForAll LBPair \u2192 LBRouter`,
|
|
8923
|
+
to: opts.pool,
|
|
8924
|
+
data: encodeFunctionData27({ abi: approveForAllAbi, functionName: "approveForAll", args: [lbRouter, true] }),
|
|
8925
|
+
value: 0n,
|
|
8926
|
+
gas_estimate: 8e4
|
|
8927
|
+
};
|
|
8928
|
+
const tx = await lbAdapter.buildRemoveLiquidity({
|
|
8929
|
+
tokenX,
|
|
8930
|
+
tokenY,
|
|
8931
|
+
binStep,
|
|
8932
|
+
binIds,
|
|
8933
|
+
amounts,
|
|
8934
|
+
recipient
|
|
8935
|
+
});
|
|
8936
|
+
tx.pre_txs = [approveTx, ...tx.pre_txs ?? []];
|
|
8937
|
+
const result = await executor.execute(tx);
|
|
8938
|
+
printOutput({ step: "lb_remove", ...result }, getOpts());
|
|
8939
|
+
return;
|
|
8940
|
+
}
|
|
7766
8941
|
const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
7767
8942
|
const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
7768
8943
|
const poolAddr = opts.pool ? opts.pool : void 0;
|
|
@@ -7778,7 +8953,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7778
8953
|
return;
|
|
7779
8954
|
}
|
|
7780
8955
|
didUnstake = true;
|
|
7781
|
-
} else if (["solidly_v2", "solidly_cl", "hybra"].includes(iface)) {
|
|
8956
|
+
} else if (["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && protocol.contracts?.["voter"]) {
|
|
7782
8957
|
let gaugeAddr = opts.gauge;
|
|
7783
8958
|
if (!gaugeAddr && poolAddr) {
|
|
7784
8959
|
try {
|
|
@@ -7789,11 +8964,32 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7789
8964
|
} catch {
|
|
7790
8965
|
}
|
|
7791
8966
|
}
|
|
8967
|
+
if (gaugeAddr) {
|
|
8968
|
+
if (iface === "solidly_v2" && !opts.tokenId) {
|
|
8969
|
+
const erc20Abi3 = parseAbi30(["function balanceOf(address) view returns (uint256)"]);
|
|
8970
|
+
const gClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8971
|
+
const gaugeBal = await gClient.readContract({
|
|
8972
|
+
address: gaugeAddr,
|
|
8973
|
+
abi: erc20Abi3,
|
|
8974
|
+
functionName: "balanceOf",
|
|
8975
|
+
args: [recipient]
|
|
8976
|
+
});
|
|
8977
|
+
if (gaugeBal === 0n) {
|
|
8978
|
+
process.stderr.write("Step 1/2: Skipped unstake \u2014 gauge balance 0 (already unstaked).\n");
|
|
8979
|
+
didUnstake = true;
|
|
8980
|
+
gaugeAddr = void 0;
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
}
|
|
7792
8984
|
if (gaugeAddr) {
|
|
7793
8985
|
process.stderr.write("Step 1/2: Withdrawing from gauge...\n");
|
|
7794
8986
|
const gaugeAdapter = createGauge(protocol, rpcUrl);
|
|
7795
8987
|
const tokenId = opts.tokenId ? BigInt(opts.tokenId) : void 0;
|
|
7796
|
-
const
|
|
8988
|
+
const wOpts = opts.redeemType !== void 0 ? { redeemType: parseInt(opts.redeemType, 10) } : void 0;
|
|
8989
|
+
if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
|
|
8990
|
+
process.stderr.write("WARNING: Hybra default redeemType=1 locks rewards into 2-year veHYBR NFT. Pass --redeem-type 0 for instant exit (with penalty).\n");
|
|
8991
|
+
}
|
|
8992
|
+
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
|
|
7797
8993
|
const withdrawResult = await executor.execute(withdrawTx);
|
|
7798
8994
|
printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
|
|
7799
8995
|
if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
|
|
@@ -7813,11 +9009,33 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7813
9009
|
token_a: tokenA,
|
|
7814
9010
|
token_b: tokenB,
|
|
7815
9011
|
liquidity: BigInt(opts.liquidity),
|
|
7816
|
-
recipient
|
|
9012
|
+
recipient,
|
|
9013
|
+
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
|
|
7817
9014
|
});
|
|
7818
9015
|
const removeResult = await executor.execute(removeTx);
|
|
7819
9016
|
printOutput({ step: "lp_remove", ...removeResult }, getOpts());
|
|
7820
9017
|
});
|
|
9018
|
+
lp.command("compound").description("Auto-compound: collect accrued LP fees and immediately re-add them as liquidity (V3 fee-only protocols).").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-id <id>", "NFT tokenId of the position to compound").option("--address <address>", "Wallet/recipient address (defaults to DEFI_WALLET_ADDRESS)").option("--slippage <bps>", "Slippage tolerance in basis points (default 50 = 0.5%). Sets amount0Min/amount1Min on increaseLiquidity to protect against MEV.").action(async (opts) => {
|
|
9019
|
+
const executor = makeExecutor2();
|
|
9020
|
+
const chainName = parent.opts().chain;
|
|
9021
|
+
if (!chainName) {
|
|
9022
|
+
printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
|
|
9023
|
+
return;
|
|
9024
|
+
}
|
|
9025
|
+
const registry = Registry.loadEmbedded();
|
|
9026
|
+
const chain = registry.getChain(chainName);
|
|
9027
|
+
const rpcUrl = chain.effectiveRpcUrl();
|
|
9028
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
9029
|
+
const recipient = resolveAccount(opts.address, lp.opts().wallet);
|
|
9030
|
+
const adapter = createDex(protocol, rpcUrl);
|
|
9031
|
+
if (!("buildCompound" in adapter) || typeof adapter.buildCompound !== "function") {
|
|
9032
|
+
throw new Error(`[${protocol.name}] adapter does not support compound (v1 supports V3 fee-only protocols)`);
|
|
9033
|
+
}
|
|
9034
|
+
const compoundOpts = opts.slippage !== void 0 ? { slippageBps: parseInt(opts.slippage, 10) } : void 0;
|
|
9035
|
+
const tx = await adapter.buildCompound(BigInt(opts.tokenId), recipient, compoundOpts);
|
|
9036
|
+
const result = await executor.execute(tx);
|
|
9037
|
+
printOutput(result, getOpts());
|
|
9038
|
+
});
|
|
7821
9039
|
lp.command("positions").description("Show all LP positions across protocols").option("--protocol <protocol>", "Filter to a single protocol slug").option("--pool <address>", "Filter to a specific pool address").option("--bins <binIds>", "Comma-separated bin IDs (for Merchant Moe LB, auto-detected if omitted)").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
7822
9040
|
const chainName = parent.opts().chain;
|
|
7823
9041
|
if (!chainName) {
|
|
@@ -7835,17 +9053,68 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7835
9053
|
protocols.map(async (protocol) => {
|
|
7836
9054
|
try {
|
|
7837
9055
|
if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
7838
|
-
if (!opts.pool) return;
|
|
7839
9056
|
const adapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
7840
9057
|
const binIds = opts.bins ? opts.bins.split(",").map((s) => parseInt(s.trim())) : void 0;
|
|
7841
|
-
const
|
|
7842
|
-
for (const
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
9058
|
+
const poolsToScan = opts.pool ? [opts.pool] : (await adapter.discoverRewardedPools()).map((p) => p.pool);
|
|
9059
|
+
for (const poolAddr of poolsToScan) {
|
|
9060
|
+
try {
|
|
9061
|
+
const userBins = binIds ?? await adapter.findUserBinsWithBalance(poolAddr, user);
|
|
9062
|
+
if (userBins.length === 0) continue;
|
|
9063
|
+
const positions = await adapter.getUserPositions(user, poolAddr, userBins);
|
|
9064
|
+
if (positions.length === 0) continue;
|
|
9065
|
+
const pending = await adapter.getPendingRewards(user, poolAddr, userBins).catch(() => []);
|
|
9066
|
+
const totalPending = pending.reduce((s, r) => s + (r.amount ?? 0n), 0n);
|
|
9067
|
+
for (const pos of positions) {
|
|
9068
|
+
results.push({
|
|
9069
|
+
protocol: protocol.slug,
|
|
9070
|
+
type: "lb",
|
|
9071
|
+
pool: poolAddr,
|
|
9072
|
+
...pos,
|
|
9073
|
+
pending_reward: totalPending.toString(),
|
|
9074
|
+
pending_reward_token: pending[0]?.token
|
|
9075
|
+
});
|
|
9076
|
+
}
|
|
9077
|
+
} catch {
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
9082
|
+
if (npm && ["uniswap_v3", "algebra_v3", "hybra"].includes(protocol.interface)) {
|
|
9083
|
+
const npmAbi = parseAbi30([
|
|
9084
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
9085
|
+
"function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)"
|
|
9086
|
+
]);
|
|
9087
|
+
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9088
|
+
let count;
|
|
9089
|
+
try {
|
|
9090
|
+
count = await client.readContract({ address: npm, abi: npmAbi, functionName: "balanceOf", args: [user] });
|
|
9091
|
+
} catch {
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
9094
|
+
const max = Math.min(Number(count), 50);
|
|
9095
|
+
for (let i = 0; i < max; i++) {
|
|
9096
|
+
try {
|
|
9097
|
+
const tokenId = await client.readContract({
|
|
9098
|
+
address: npm,
|
|
9099
|
+
abi: npmAbi,
|
|
9100
|
+
functionName: "tokenOfOwnerByIndex",
|
|
9101
|
+
args: [user, BigInt(i)]
|
|
9102
|
+
});
|
|
9103
|
+
const liq = await detectV3Liquidity(client, npm, tokenId);
|
|
9104
|
+
if (liq && liq.liquidity > 0n) {
|
|
9105
|
+
results.push({
|
|
9106
|
+
protocol: protocol.slug,
|
|
9107
|
+
type: "v3_nft",
|
|
9108
|
+
token_id: tokenId.toString(),
|
|
9109
|
+
token0: liq.token0,
|
|
9110
|
+
token1: liq.token1,
|
|
9111
|
+
liquidity: liq.liquidity.toString(),
|
|
9112
|
+
tickLower: liq.tickLower,
|
|
9113
|
+
tickUpper: liq.tickUpper
|
|
9114
|
+
});
|
|
9115
|
+
}
|
|
9116
|
+
} catch {
|
|
9117
|
+
}
|
|
7849
9118
|
}
|
|
7850
9119
|
}
|
|
7851
9120
|
} catch {
|
|
@@ -9749,10 +11018,9 @@ function registerBridge(parent, getOpts) {
|
|
|
9749
11018
|
|
|
9750
11019
|
// src/commands/swap.ts
|
|
9751
11020
|
init_dist();
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
};
|
|
11021
|
+
function getAggregatorSlugs(chainCfg) {
|
|
11022
|
+
return chainCfg.aggregators ?? {};
|
|
11023
|
+
}
|
|
9756
11024
|
var KYBER_API = "https://aggregator-api.kyberswap.com";
|
|
9757
11025
|
async function kyberGetQuote(chain, tokenIn, tokenOut, amountIn) {
|
|
9758
11026
|
const params = new URLSearchParams({ tokenIn, tokenOut, amountIn });
|
|
@@ -9804,6 +11072,64 @@ async function openoceanSwap(chain, inTokenAddress, outTokenAddress, amountIn, s
|
|
|
9804
11072
|
outAmount: String(data.outAmount ?? "0")
|
|
9805
11073
|
};
|
|
9806
11074
|
}
|
|
11075
|
+
var LIFI_API2 = "https://li.quest/v1";
|
|
11076
|
+
async function lifiQuote(chainId, fromToken, toToken, fromAmount, fromAddress, slippagePct) {
|
|
11077
|
+
const params = new URLSearchParams({
|
|
11078
|
+
fromChain: String(chainId),
|
|
11079
|
+
toChain: String(chainId),
|
|
11080
|
+
fromToken,
|
|
11081
|
+
toToken,
|
|
11082
|
+
fromAmount,
|
|
11083
|
+
fromAddress,
|
|
11084
|
+
slippage: (Number(slippagePct) / 100).toFixed(4)
|
|
11085
|
+
});
|
|
11086
|
+
const url = `${LIFI_API2}/quote?${params}`;
|
|
11087
|
+
const res = await fetch(url);
|
|
11088
|
+
if (!res.ok) throw new Error(`LI.FI quote failed: ${res.status} ${await res.text()}`);
|
|
11089
|
+
const json = await res.json();
|
|
11090
|
+
const txReq = json.transactionRequest;
|
|
11091
|
+
if (!txReq) throw new Error("LI.FI: no transactionRequest in response");
|
|
11092
|
+
const estimate = json.estimate;
|
|
11093
|
+
return {
|
|
11094
|
+
to: String(txReq.to),
|
|
11095
|
+
data: String(txReq.data),
|
|
11096
|
+
value: String(txReq.value ?? "0x0"),
|
|
11097
|
+
outAmount: String(estimate?.toAmount ?? "0")
|
|
11098
|
+
};
|
|
11099
|
+
}
|
|
11100
|
+
var RELAY_API = "https://api.relay.link";
|
|
11101
|
+
async function relayQuote(chainId, fromToken, toToken, amount, user) {
|
|
11102
|
+
const body = {
|
|
11103
|
+
user,
|
|
11104
|
+
originChainId: chainId,
|
|
11105
|
+
destinationChainId: chainId,
|
|
11106
|
+
originCurrency: fromToken,
|
|
11107
|
+
destinationCurrency: toToken,
|
|
11108
|
+
recipient: user,
|
|
11109
|
+
tradeType: "EXACT_INPUT",
|
|
11110
|
+
amount
|
|
11111
|
+
};
|
|
11112
|
+
const res = await fetch(`${RELAY_API}/quote`, {
|
|
11113
|
+
method: "POST",
|
|
11114
|
+
headers: { "content-type": "application/json" },
|
|
11115
|
+
body: JSON.stringify(body)
|
|
11116
|
+
});
|
|
11117
|
+
if (!res.ok) throw new Error(`Relay quote failed: ${res.status} ${await res.text()}`);
|
|
11118
|
+
const json = await res.json();
|
|
11119
|
+
const steps = json.steps;
|
|
11120
|
+
const swapStep = steps?.find((s) => s.id !== "approve") ?? steps?.[steps.length - 1];
|
|
11121
|
+
const items = swapStep?.items;
|
|
11122
|
+
const txData = items?.[0]?.data;
|
|
11123
|
+
if (!txData) throw new Error("Relay: no swap step in quote");
|
|
11124
|
+
const details = json.details;
|
|
11125
|
+
const currencyOut = details?.currencyOut;
|
|
11126
|
+
return {
|
|
11127
|
+
to: String(txData.to),
|
|
11128
|
+
data: String(txData.data),
|
|
11129
|
+
value: String(txData.value ?? "0x0"),
|
|
11130
|
+
outAmount: String(currencyOut?.amount ?? "0")
|
|
11131
|
+
};
|
|
11132
|
+
}
|
|
9807
11133
|
var LIQD_API = "https://api.liqd.ag/v2";
|
|
9808
11134
|
var LIQD_ROUTER = "0x744489ee3d540777a66f2cf297479745e0852f7a";
|
|
9809
11135
|
async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
|
|
@@ -9823,7 +11149,7 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
|
|
|
9823
11149
|
};
|
|
9824
11150
|
}
|
|
9825
11151
|
function registerSwap(parent, getOpts, makeExecutor2) {
|
|
9826
|
-
parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "50").action(async (opts) => {
|
|
11152
|
+
parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap, LI.FI, Relay)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid, lifi, relay", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "50").action(async (opts) => {
|
|
9827
11153
|
const executor = makeExecutor2();
|
|
9828
11154
|
const chainName = requireChain(parent, getOpts);
|
|
9829
11155
|
if (!chainName) return;
|
|
@@ -9834,12 +11160,13 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9834
11160
|
const toAddr = resolveTokenAddress(registry, chainName, opts.to);
|
|
9835
11161
|
const wallet = resolveWallet();
|
|
9836
11162
|
if (provider === "kyber") {
|
|
9837
|
-
const
|
|
9838
|
-
if (!
|
|
9839
|
-
|
|
11163
|
+
const aggCfg = getAggregatorSlugs(registry.getChain(chainName));
|
|
11164
|
+
if (!aggCfg.kyber) {
|
|
11165
|
+
const supported = Array.from(registry.chains.keys()).filter((c) => registry.getChain(c).aggregators?.kyber).join(", ");
|
|
11166
|
+
printOutput({ error: `KyberSwap: unsupported chain '${chainName}'. Supported: ${supported || "(none)"}` }, getOpts());
|
|
9840
11167
|
return;
|
|
9841
11168
|
}
|
|
9842
|
-
const kyberChain =
|
|
11169
|
+
const kyberChain = aggCfg.kyber;
|
|
9843
11170
|
try {
|
|
9844
11171
|
const quoteData = await kyberGetQuote(kyberChain, fromAddr, toAddr, opts.amount);
|
|
9845
11172
|
const routeSummary = quoteData.routeSummary;
|
|
@@ -9875,12 +11202,13 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9875
11202
|
return;
|
|
9876
11203
|
}
|
|
9877
11204
|
if (provider === "openocean") {
|
|
9878
|
-
const
|
|
9879
|
-
if (!
|
|
9880
|
-
|
|
11205
|
+
const aggCfg = getAggregatorSlugs(registry.getChain(chainName));
|
|
11206
|
+
if (!aggCfg.openocean) {
|
|
11207
|
+
const supported = Array.from(registry.chains.keys()).filter((c) => registry.getChain(c).aggregators?.openocean).join(", ");
|
|
11208
|
+
printOutput({ error: `OpenOcean: unsupported chain '${chainName}'. Supported: ${supported || "(none)"}` }, getOpts());
|
|
9881
11209
|
return;
|
|
9882
11210
|
}
|
|
9883
|
-
const ooChain =
|
|
11211
|
+
const ooChain = aggCfg.openocean;
|
|
9884
11212
|
const fromToken = opts.from.startsWith("0x") ? registry.tokens.get(chainName)?.find((t) => t.address.toLowerCase() === opts.from.toLowerCase()) : registry.tokens.get(chainName)?.find((t) => t.symbol.toLowerCase() === opts.from.toLowerCase());
|
|
9885
11213
|
const fromDecimals = fromToken?.decimals ?? 18;
|
|
9886
11214
|
const humanAmount = (Number(opts.amount) / 10 ** fromDecimals).toString();
|
|
@@ -9948,7 +11276,41 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9948
11276
|
}
|
|
9949
11277
|
return;
|
|
9950
11278
|
}
|
|
9951
|
-
|
|
11279
|
+
if (provider === "lifi" || provider === "relay") {
|
|
11280
|
+
const chainCfg = registry.getChain(chainName);
|
|
11281
|
+
const chainId = chainCfg.chain_id;
|
|
11282
|
+
if (!chainId) {
|
|
11283
|
+
printOutput({ error: `${provider}: chain '${chainName}' has no chain_id in registry` }, getOpts());
|
|
11284
|
+
return;
|
|
11285
|
+
}
|
|
11286
|
+
const slippagePct = (slippageBps / 100).toFixed(2);
|
|
11287
|
+
try {
|
|
11288
|
+
const route = provider === "lifi" ? await lifiQuote(chainId, fromAddr, toAddr, opts.amount, wallet, slippagePct) : await relayQuote(chainId, fromAddr, toAddr, opts.amount, wallet);
|
|
11289
|
+
const tx = {
|
|
11290
|
+
description: `${provider}: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11291
|
+
to: route.to,
|
|
11292
|
+
data: route.data,
|
|
11293
|
+
value: parseBigIntValue(route.value),
|
|
11294
|
+
approvals: [{ token: fromAddr, spender: route.to, amount: BigInt(opts.amount) }]
|
|
11295
|
+
};
|
|
11296
|
+
const result = await executor.execute(tx);
|
|
11297
|
+
printOutput({
|
|
11298
|
+
provider,
|
|
11299
|
+
chain: chainName,
|
|
11300
|
+
chain_id: chainId,
|
|
11301
|
+
from_token: fromAddr,
|
|
11302
|
+
to_token: toAddr,
|
|
11303
|
+
amount_in: opts.amount,
|
|
11304
|
+
amount_out: route.outAmount,
|
|
11305
|
+
router: route.to,
|
|
11306
|
+
...result
|
|
11307
|
+
}, getOpts());
|
|
11308
|
+
} catch (e) {
|
|
11309
|
+
printOutput({ error: `${provider} error: ${errMsg(e)}` }, getOpts());
|
|
11310
|
+
}
|
|
11311
|
+
return;
|
|
11312
|
+
}
|
|
11313
|
+
printOutput({ error: `Unknown provider '${opts.provider}'. Choose: kyber, openocean, liquid, lifi, relay` }, getOpts());
|
|
9952
11314
|
});
|
|
9953
11315
|
}
|
|
9954
11316
|
|
|
@@ -10000,8 +11362,8 @@ function isValidPrivateKey(s) {
|
|
|
10000
11362
|
}
|
|
10001
11363
|
async function deriveAddress(privateKey) {
|
|
10002
11364
|
try {
|
|
10003
|
-
const { privateKeyToAccount:
|
|
10004
|
-
const account =
|
|
11365
|
+
const { privateKeyToAccount: privateKeyToAccount3 } = await import("viem/accounts");
|
|
11366
|
+
const account = privateKeyToAccount3(privateKey);
|
|
10005
11367
|
return account.address;
|
|
10006
11368
|
} catch {
|
|
10007
11369
|
return null;
|