@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/main.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
|
};
|
|
@@ -4318,7 +4819,236 @@ var init_dist2 = __esm({
|
|
|
4318
4819
|
});
|
|
4319
4820
|
}
|
|
4320
4821
|
}
|
|
4321
|
-
return results;
|
|
4822
|
+
return results;
|
|
4823
|
+
}
|
|
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" } };
|
|
4322
5052
|
}
|
|
4323
5053
|
};
|
|
4324
5054
|
POOL_ABI = parseAbi14([
|
|
@@ -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
|
]);
|
|
@@ -7172,6 +7996,9 @@ function loadWhitelist() {
|
|
|
7172
7996
|
}
|
|
7173
7997
|
}
|
|
7174
7998
|
|
|
7999
|
+
// src/signer/resolve.ts
|
|
8000
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
8001
|
+
|
|
7175
8002
|
// src/signer/ows-loader.ts
|
|
7176
8003
|
import { createRequire } from "module";
|
|
7177
8004
|
var _require = createRequire(import.meta.url);
|
|
@@ -7277,7 +8104,6 @@ function resolveWalletWithSigner(opts) {
|
|
|
7277
8104
|
}
|
|
7278
8105
|
const pk = process.env["DEFI_PRIVATE_KEY"];
|
|
7279
8106
|
if (pk) {
|
|
7280
|
-
const { privateKeyToAccount: privateKeyToAccount2 } = __require("viem/accounts");
|
|
7281
8107
|
return { address: privateKeyToAccount2(pk).address, signer: null };
|
|
7282
8108
|
}
|
|
7283
8109
|
if (envWallet) {
|
|
@@ -7294,10 +8120,116 @@ function resolveAccount(optOwner, optWallet) {
|
|
|
7294
8120
|
const { address } = resolveWalletWithSigner(optWallet ? { wallet: optWallet } : void 0);
|
|
7295
8121
|
return address;
|
|
7296
8122
|
}
|
|
8123
|
+
function buildCmd(parts) {
|
|
8124
|
+
const segs = [];
|
|
8125
|
+
for (const [flag, value, placeholder] of parts) {
|
|
8126
|
+
segs.push(`${flag} ${value ?? `<${placeholder}>`}`);
|
|
8127
|
+
}
|
|
8128
|
+
return segs.join(" ");
|
|
8129
|
+
}
|
|
8130
|
+
function buildPipelineSteps(p, input = {}) {
|
|
8131
|
+
const slug = p.slug;
|
|
8132
|
+
const chainFlag = input.chain ? `--chain ${input.chain} ` : "";
|
|
8133
|
+
const baseAdd = `defi ${chainFlag}lp add ` + buildCmd([
|
|
8134
|
+
["--protocol", slug, "slug"],
|
|
8135
|
+
["--token-a", input.tokenA, "token-a"],
|
|
8136
|
+
["--token-b", input.tokenB, "token-b"],
|
|
8137
|
+
["--amount-a", input.amountA, "amount-a"],
|
|
8138
|
+
["--amount-b", input.amountB, "amount-b"],
|
|
8139
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : [],
|
|
8140
|
+
...input.tickLower ? [["--tick-lower", input.tickLower, "tick-lower"]] : [],
|
|
8141
|
+
...input.tickUpper ? [["--tick-upper", input.tickUpper, "tick-upper"]] : [],
|
|
8142
|
+
...input.range ? [["--range", input.range, "range"]] : []
|
|
8143
|
+
]);
|
|
8144
|
+
const baseFarm = `defi ${chainFlag}lp farm ` + buildCmd([
|
|
8145
|
+
["--protocol", slug, "slug"],
|
|
8146
|
+
["--token-a", input.tokenA, "token-a"],
|
|
8147
|
+
["--token-b", input.tokenB, "token-b"],
|
|
8148
|
+
["--amount-a", input.amountA, "amount-a"],
|
|
8149
|
+
["--amount-b", input.amountB, "amount-b"],
|
|
8150
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : []
|
|
8151
|
+
]);
|
|
8152
|
+
const claimWithTokenId = (extra = "") => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8153
|
+
["--protocol", slug, "slug"],
|
|
8154
|
+
["--token-id", input.tokenId, "token-id-from-mint-result"],
|
|
8155
|
+
...input.pool ? [["--pool", input.pool, "pool"]] : [],
|
|
8156
|
+
...input.gauge ? [["--gauge", input.gauge, "gauge"]] : []
|
|
8157
|
+
]) + extra;
|
|
8158
|
+
const claimWithGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
8159
|
+
["--protocol", slug, "slug"],
|
|
8160
|
+
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
|
|
8161
|
+
]);
|
|
8162
|
+
switch (p.reward_strategy) {
|
|
8163
|
+
case "lp_fee_only":
|
|
8164
|
+
return [
|
|
8165
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8166
|
+
{ step: "collect", function: "NPM.collect(tokenId, recipient)", note: "No emissions; collects accrued LP trading fees only", cli_command: claimWithTokenId() }
|
|
8167
|
+
];
|
|
8168
|
+
case "on_chain_farming_center":
|
|
8169
|
+
return [
|
|
8170
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8171
|
+
{ step: "stake", function: "farmingCenter.enterFarming(incentiveKey, tokenId)", cli_command: baseFarm, note: "lp farm chains mint+stake into one tx sequence" },
|
|
8172
|
+
{ step: "claim", function: "farmingCenter.collectRewards(incentiveKey, tokenId)", cli_command: claimWithTokenId() }
|
|
8173
|
+
];
|
|
8174
|
+
case "on_chain_gauge_tokenid":
|
|
8175
|
+
return [
|
|
8176
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8177
|
+
{ step: "stake", function: "gauge.deposit(tokenId)", cli_command: baseFarm, note: "lp farm chains mint+stake into one tx sequence" },
|
|
8178
|
+
{ step: "claim", function: "gauge.earned(tokenId) \u2192 gauge.getReward(tokenId)", cli_command: claimWithTokenId() }
|
|
8179
|
+
];
|
|
8180
|
+
case "on_chain_gauge":
|
|
8181
|
+
return [
|
|
8182
|
+
{ step: "mint", function: "Router.addLiquidity / NPM.mint", cli_command: baseAdd },
|
|
8183
|
+
{ step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
|
|
8184
|
+
{ step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
|
|
8185
|
+
];
|
|
8186
|
+
case "auto_stake":
|
|
8187
|
+
return [
|
|
8188
|
+
{ step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
|
|
8189
|
+
{ step: "claim", function: "gauge.getReward(account, tokens[])", note: "Multi-token reward (xRAM + WHYPE on Ramses HL)", cli_command: claimWithGauge() }
|
|
8190
|
+
];
|
|
8191
|
+
case "on_chain_masterchef":
|
|
8192
|
+
return [
|
|
8193
|
+
{ step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
|
|
8194
|
+
{ step: "stake", function: "MasterChef.deposit(pid, amount)", cli_command: baseFarm },
|
|
8195
|
+
{ step: "claim", function: "MasterChef.harvest(pid) or pendingCake(pid, user)", cli_command: claimWithTokenId() }
|
|
8196
|
+
];
|
|
8197
|
+
case "off_chain_api":
|
|
8198
|
+
return [
|
|
8199
|
+
{ step: "mint", function: "NPM.mint(MintParams)", cli_command: baseAdd },
|
|
8200
|
+
{ 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>` }
|
|
8201
|
+
];
|
|
8202
|
+
case "none":
|
|
8203
|
+
return [{ step: "mint", function: "Router/NPM mint", note: "No reward path declared", cli_command: baseAdd }];
|
|
8204
|
+
default:
|
|
8205
|
+
return [{ step: "mint", function: "Router/NPM mint", note: "reward_strategy unset \u2014 pipeline cannot be inferred", cli_command: baseAdd }];
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
7297
8208
|
function resolvePoolAddress(registry, protocolSlug, pool) {
|
|
7298
8209
|
if (pool.startsWith("0x")) return pool;
|
|
7299
8210
|
return registry.resolvePool(protocolSlug, pool).address;
|
|
7300
8211
|
}
|
|
8212
|
+
async function detectV3Liquidity(client, npm, tokenId) {
|
|
8213
|
+
const ramsesAbi = parseAbi30([
|
|
8214
|
+
"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)"
|
|
8215
|
+
]);
|
|
8216
|
+
const standardAbi = parseAbi30([
|
|
8217
|
+
"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)"
|
|
8218
|
+
]);
|
|
8219
|
+
try {
|
|
8220
|
+
const r = await client.readContract({ address: npm, abi: ramsesAbi, functionName: "positions", args: [tokenId] });
|
|
8221
|
+
if (r[5] !== void 0) {
|
|
8222
|
+
return { token0: r[0], token1: r[1], tickLower: r[3], tickUpper: r[4], liquidity: r[5] };
|
|
8223
|
+
}
|
|
8224
|
+
} catch {
|
|
8225
|
+
}
|
|
8226
|
+
try {
|
|
8227
|
+
const r = await client.readContract({ address: npm, abi: standardAbi, functionName: "positions", args: [tokenId] });
|
|
8228
|
+
return { token0: r[2], token1: r[3], tickLower: r[5], tickUpper: r[6], liquidity: r[7] };
|
|
8229
|
+
} catch {
|
|
8230
|
+
return null;
|
|
8231
|
+
}
|
|
8232
|
+
}
|
|
7301
8233
|
var V2_PAIR_ABI = parseAbi30([
|
|
7302
8234
|
"function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)",
|
|
7303
8235
|
"function totalSupply() external view returns (uint256)"
|
|
@@ -7580,18 +8512,59 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7580
8512
|
} catch {
|
|
7581
8513
|
}
|
|
7582
8514
|
}
|
|
8515
|
+
if (protocol.interface === "curve_stableswap" && protocol.contracts?.["stableswap_factory"]) {
|
|
8516
|
+
const factory = protocol.contracts["stableswap_factory"];
|
|
8517
|
+
const factoryAbi = parseAbi30([
|
|
8518
|
+
"function pool_count() view returns (uint256)",
|
|
8519
|
+
"function pool_list(uint256) view returns (address)"
|
|
8520
|
+
]);
|
|
8521
|
+
const poolAbi2 = parseAbi30([
|
|
8522
|
+
"function coins(uint256) view returns (address)",
|
|
8523
|
+
"function name() view returns (string)"
|
|
8524
|
+
]);
|
|
8525
|
+
const cClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8526
|
+
try {
|
|
8527
|
+
const count = await cClient.readContract({ address: factory, abi: factoryAbi, functionName: "pool_count" });
|
|
8528
|
+
const MAX_SCAN = Math.min(Number(count), 50);
|
|
8529
|
+
for (let i = 0; i < MAX_SCAN; i++) {
|
|
8530
|
+
try {
|
|
8531
|
+
const pool = await cClient.readContract({ address: factory, abi: factoryAbi, functionName: "pool_list", args: [BigInt(i)] });
|
|
8532
|
+
const [c0, c1, name] = await Promise.all([
|
|
8533
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "coins", args: [0n] }).catch(() => null),
|
|
8534
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "coins", args: [1n] }).catch(() => null),
|
|
8535
|
+
cClient.readContract({ address: pool, abi: poolAbi2, functionName: "name" }).catch(() => "")
|
|
8536
|
+
]);
|
|
8537
|
+
results.push({
|
|
8538
|
+
protocol: protocol.slug,
|
|
8539
|
+
pool,
|
|
8540
|
+
pair: name || `${c0 ?? "?"}/${c1 ?? "?"}`,
|
|
8541
|
+
type: "FEE",
|
|
8542
|
+
source: "curve_factory"
|
|
8543
|
+
});
|
|
8544
|
+
} catch {
|
|
8545
|
+
}
|
|
8546
|
+
}
|
|
8547
|
+
} catch {
|
|
8548
|
+
}
|
|
8549
|
+
}
|
|
7583
8550
|
} catch {
|
|
7584
8551
|
}
|
|
7585
8552
|
})
|
|
7586
8553
|
);
|
|
7587
8554
|
await _enrichGaugeAprs(results, rpcUrl, registry, chainName);
|
|
8555
|
+
results.sort((a, b) => (b.aprPercent ?? 0) - (a.aprPercent ?? 0));
|
|
7588
8556
|
if (opts.emissionOnly) {
|
|
7589
|
-
printOutput(
|
|
8557
|
+
printOutput(
|
|
8558
|
+
results.filter(
|
|
8559
|
+
(r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n)
|
|
8560
|
+
),
|
|
8561
|
+
getOpts()
|
|
8562
|
+
);
|
|
7590
8563
|
} else {
|
|
7591
8564
|
printOutput(results, getOpts());
|
|
7592
8565
|
}
|
|
7593
8566
|
});
|
|
7594
|
-
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) => {
|
|
8567
|
+
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) => {
|
|
7595
8568
|
const executor = makeExecutor2();
|
|
7596
8569
|
const chainName = parent.opts().chain;
|
|
7597
8570
|
if (!chainName) {
|
|
@@ -7601,11 +8574,35 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7601
8574
|
const registry = Registry.loadEmbedded();
|
|
7602
8575
|
const chain = registry.getChain(chainName);
|
|
7603
8576
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7604
|
-
const adapter = createDex(protocol, chain.effectiveRpcUrl());
|
|
7605
8577
|
const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
7606
8578
|
const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
7607
8579
|
const recipient = opts.recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
|
|
7608
8580
|
const poolAddr = opts.pool ? resolvePoolAddress(registry, opts.protocol, opts.pool) : void 0;
|
|
8581
|
+
if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
8582
|
+
if (!poolAddr) throw new Error("--pool is required for Merchant Moe LB add");
|
|
8583
|
+
const lbAdapter = createMerchantMoeLB(protocol, chain.effectiveRpcUrl());
|
|
8584
|
+
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)];
|
|
8585
|
+
const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
|
|
8586
|
+
const binStep = await client.readContract({
|
|
8587
|
+
address: poolAddr,
|
|
8588
|
+
abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
|
|
8589
|
+
functionName: "getBinStep"
|
|
8590
|
+
});
|
|
8591
|
+
const tx2 = await lbAdapter.buildAddLiquidity({
|
|
8592
|
+
pool: poolAddr,
|
|
8593
|
+
tokenX,
|
|
8594
|
+
tokenY,
|
|
8595
|
+
binStep,
|
|
8596
|
+
amountX,
|
|
8597
|
+
amountY,
|
|
8598
|
+
recipient,
|
|
8599
|
+
numBins: opts.numBins !== void 0 ? parseInt(opts.numBins, 10) : 5
|
|
8600
|
+
});
|
|
8601
|
+
const result2 = await executor.execute(tx2);
|
|
8602
|
+
printOutput(result2, getOpts());
|
|
8603
|
+
return;
|
|
8604
|
+
}
|
|
8605
|
+
const adapter = createDex(protocol, chain.effectiveRpcUrl());
|
|
7609
8606
|
const tx = await adapter.buildAddLiquidity({
|
|
7610
8607
|
protocol: protocol.name,
|
|
7611
8608
|
token_a: tokenA,
|
|
@@ -7671,7 +8668,8 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7671
8668
|
printOutput({ step: "stake_farming", ...stakeResult }, getOpts());
|
|
7672
8669
|
return;
|
|
7673
8670
|
}
|
|
7674
|
-
|
|
8671
|
+
const isGaugeStakeable = ["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && protocol.contracts?.["voter"];
|
|
8672
|
+
if (isGaugeStakeable) {
|
|
7675
8673
|
if (!mintedTokenId && iface !== "solidly_v2") {
|
|
7676
8674
|
process.stderr.write("Step 2/2: Skipped staking (no tokenId \u2014 run in --broadcast mode to get minted NFT)\n");
|
|
7677
8675
|
return;
|
|
@@ -7690,7 +8688,21 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7690
8688
|
process.stderr.write("Step 2/2: Staking into gauge...\n");
|
|
7691
8689
|
const gaugeAdapter = createGauge(protocol, rpcUrl);
|
|
7692
8690
|
const tokenIdArg = mintedTokenId;
|
|
7693
|
-
|
|
8691
|
+
let amountArg = 0n;
|
|
8692
|
+
if (iface === "solidly_v2" && poolAddr) {
|
|
8693
|
+
const erc20Abi3 = parseAbi30(["function balanceOf(address) view returns (uint256)"]);
|
|
8694
|
+
const lpClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8695
|
+
amountArg = await lpClient.readContract({
|
|
8696
|
+
address: poolAddr,
|
|
8697
|
+
abi: erc20Abi3,
|
|
8698
|
+
functionName: "balanceOf",
|
|
8699
|
+
args: [recipient]
|
|
8700
|
+
});
|
|
8701
|
+
if (amountArg === 0n) {
|
|
8702
|
+
process.stderr.write("Step 2/2: Skipped staking \u2014 LP balance 0 (Step 1 add returned no LP).\n");
|
|
8703
|
+
return;
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
7694
8706
|
const lpTokenArg = iface === "solidly_v2" ? poolAddr : void 0;
|
|
7695
8707
|
const stakeTx = await gaugeAdapter.buildDeposit(gaugeAddr, amountArg, tokenIdArg, lpTokenArg);
|
|
7696
8708
|
const stakeResult = await executor.execute(stakeTx);
|
|
@@ -7703,7 +8715,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7703
8715
|
}
|
|
7704
8716
|
process.stderr.write("Step 2/2: No staking adapter found for this protocol interface \u2014 liquidity added only.\n");
|
|
7705
8717
|
});
|
|
7706
|
-
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) => {
|
|
8718
|
+
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) => {
|
|
7707
8719
|
const executor = makeExecutor2();
|
|
7708
8720
|
const chainName = parent.opts().chain;
|
|
7709
8721
|
if (!chainName) {
|
|
@@ -7716,6 +8728,86 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7716
8728
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7717
8729
|
const account = resolveAccount(opts.address, lp.opts().wallet);
|
|
7718
8730
|
const iface = protocol.interface;
|
|
8731
|
+
const isV3Fee = protocol.reward_strategy === "lp_fee_only" || iface === "uniswap_v3" && opts.tokenId && !opts.gauge;
|
|
8732
|
+
if (isV3Fee) {
|
|
8733
|
+
if (!opts.tokenId) throw new Error("--token-id is required for V3 LP fee collection");
|
|
8734
|
+
const adapter = createDex(protocol, rpcUrl);
|
|
8735
|
+
if (!("buildCollectFees" in adapter) || typeof adapter.buildCollectFees !== "function") {
|
|
8736
|
+
throw new Error(`[${protocol.name}] adapter does not support buildCollectFees`);
|
|
8737
|
+
}
|
|
8738
|
+
const tx = await adapter.buildCollectFees(BigInt(opts.tokenId), account);
|
|
8739
|
+
const result = await executor.execute(tx);
|
|
8740
|
+
printOutput(result, getOpts());
|
|
8741
|
+
return;
|
|
8742
|
+
}
|
|
8743
|
+
if (protocol.reward_strategy === "off_chain_api") {
|
|
8744
|
+
const nest = createNestOffChain(protocol);
|
|
8745
|
+
const status = await nest.getClaimStatus(account);
|
|
8746
|
+
const ticket = await nest.getClaimTicket(account);
|
|
8747
|
+
if (!ticket) {
|
|
8748
|
+
printOutput({
|
|
8749
|
+
protocol: protocol.slug,
|
|
8750
|
+
wallet: account,
|
|
8751
|
+
voter: nest.getVoterAddress(),
|
|
8752
|
+
reward_symbol: "NEST",
|
|
8753
|
+
pending_amount: 0,
|
|
8754
|
+
note: "No claim ticket available \u2014 backend reports no points to claim."
|
|
8755
|
+
}, getOpts());
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
const tx = await nest.buildClaim(account);
|
|
8759
|
+
const preflightClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8760
|
+
try {
|
|
8761
|
+
await preflightClient.call({
|
|
8762
|
+
account,
|
|
8763
|
+
to: tx.to,
|
|
8764
|
+
data: tx.data,
|
|
8765
|
+
value: tx.value
|
|
8766
|
+
});
|
|
8767
|
+
} catch (e) {
|
|
8768
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8769
|
+
printOutput({
|
|
8770
|
+
protocol: protocol.slug,
|
|
8771
|
+
wallet: account,
|
|
8772
|
+
voter: nest.getVoterAddress(),
|
|
8773
|
+
reward_symbol: "NEST",
|
|
8774
|
+
pending_amount: status.pendingFormatted,
|
|
8775
|
+
pending_raw: status.pendingRaw.toString(),
|
|
8776
|
+
ticket: {
|
|
8777
|
+
amount: ticket.amount.toString(),
|
|
8778
|
+
timestamp: ticket.timestamp.toString(),
|
|
8779
|
+
day: ticket.day === null ? null : ticket.day.toString(),
|
|
8780
|
+
signature: ticket.signature
|
|
8781
|
+
},
|
|
8782
|
+
preflight: "revert",
|
|
8783
|
+
preflight_error: msg.length > 400 ? msg.slice(0, 400) + "..." : msg,
|
|
8784
|
+
broadcast: "skipped",
|
|
8785
|
+
note: "Simulation reverted \u2014 broadcast aborted to avoid wasted gas. Common causes: ticket expired, caller != ticket signer, ticket already claimed."
|
|
8786
|
+
}, getOpts());
|
|
8787
|
+
return;
|
|
8788
|
+
}
|
|
8789
|
+
const result = await executor.execute(tx);
|
|
8790
|
+
printOutput({
|
|
8791
|
+
protocol: protocol.slug,
|
|
8792
|
+
wallet: account,
|
|
8793
|
+
voter: nest.getVoterAddress(),
|
|
8794
|
+
reward_symbol: "NEST",
|
|
8795
|
+
pending_amount: status.pendingFormatted,
|
|
8796
|
+
pending_raw: status.pendingRaw.toString(),
|
|
8797
|
+
total_claimed_raw: status.totalClaimedRaw.toString(),
|
|
8798
|
+
total_available_raw: status.totalAvailableRaw.toString(),
|
|
8799
|
+
ticket: {
|
|
8800
|
+
amount: ticket.amount.toString(),
|
|
8801
|
+
timestamp: ticket.timestamp.toString(),
|
|
8802
|
+
day: ticket.day === null ? null : ticket.day.toString(),
|
|
8803
|
+
signature: ticket.signature
|
|
8804
|
+
},
|
|
8805
|
+
preflight: "passed",
|
|
8806
|
+
claim_result: result,
|
|
8807
|
+
note: "Calldata derived from byte-level template (verified against two known-successful onchain claim txs). Pre-flight eth_call simulation passed before broadcast."
|
|
8808
|
+
}, getOpts());
|
|
8809
|
+
return;
|
|
8810
|
+
}
|
|
7719
8811
|
if (iface === "algebra_v3" && protocol.contracts?.["farming_center"]) {
|
|
7720
8812
|
if (!opts.pool) throw new Error("--pool is required for KittenSwap farming claim");
|
|
7721
8813
|
if (!opts.tokenId) throw new Error("--token-id is required for KittenSwap farming claim");
|
|
@@ -7742,9 +8834,19 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7742
8834
|
if (!opts.gauge) throw new Error("--gauge is required for gauge claim");
|
|
7743
8835
|
const adapter = createGauge(protocol, rpcUrl);
|
|
7744
8836
|
let tx;
|
|
7745
|
-
if (opts.tokenId) {
|
|
8837
|
+
if (opts.tokenId && iface === "uniswap_v3" && protocol.reward_strategy === "auto_stake" && "buildClaimRewardsViaNPMPeriodReward" in adapter && typeof adapter.buildClaimRewardsViaNPMPeriodReward === "function") {
|
|
8838
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
8839
|
+
if (!npm) throw new Error(`${protocol.name} requires contracts.position_manager for NPM-based claim`);
|
|
8840
|
+
tx = await adapter.buildClaimRewardsViaNPMPeriodReward(
|
|
8841
|
+
npm,
|
|
8842
|
+
BigInt(opts.tokenId),
|
|
8843
|
+
account,
|
|
8844
|
+
{ gauge: opts.gauge }
|
|
8845
|
+
);
|
|
8846
|
+
} else if (opts.tokenId) {
|
|
7746
8847
|
if (!adapter.buildClaimRewardsByTokenId) throw new Error(`${protocol.name} does not support NFT-based claim`);
|
|
7747
|
-
|
|
8848
|
+
const claimOpts = opts.redeemType !== void 0 ? { redeemType: parseInt(opts.redeemType, 10) } : void 0;
|
|
8849
|
+
tx = await adapter.buildClaimRewardsByTokenId(opts.gauge, BigInt(opts.tokenId), claimOpts);
|
|
7748
8850
|
} else {
|
|
7749
8851
|
tx = await adapter.buildClaimRewards(opts.gauge, account);
|
|
7750
8852
|
}
|
|
@@ -7754,7 +8856,35 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7754
8856
|
}
|
|
7755
8857
|
throw new Error(`No claim method found for protocol interface '${iface}'`);
|
|
7756
8858
|
});
|
|
7757
|
-
lp.command("
|
|
8859
|
+
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) => {
|
|
8860
|
+
const registry = Registry.loadEmbedded();
|
|
8861
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
8862
|
+
const chainName = parent.opts().chain;
|
|
8863
|
+
const steps = buildPipelineSteps(protocol, {
|
|
8864
|
+
chain: chainName,
|
|
8865
|
+
pool: opts.pool,
|
|
8866
|
+
tokenA: opts.tokenA,
|
|
8867
|
+
tokenB: opts.tokenB,
|
|
8868
|
+
amountA: opts.amountA,
|
|
8869
|
+
amountB: opts.amountB,
|
|
8870
|
+
tickLower: opts.tickLower,
|
|
8871
|
+
tickUpper: opts.tickUpper,
|
|
8872
|
+
range: opts.range,
|
|
8873
|
+
tokenId: opts.tokenId,
|
|
8874
|
+
gauge: opts.gauge
|
|
8875
|
+
});
|
|
8876
|
+
printOutput({
|
|
8877
|
+
protocol: protocol.slug,
|
|
8878
|
+
chain: protocol.chain,
|
|
8879
|
+
interface: protocol.interface,
|
|
8880
|
+
is_active: protocol.is_active !== false,
|
|
8881
|
+
verified: protocol.verified === true,
|
|
8882
|
+
reward_strategy: protocol.reward_strategy ?? "(unset \u2014 falls back to interface inference)",
|
|
8883
|
+
steps,
|
|
8884
|
+
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."
|
|
8885
|
+
}, getOpts());
|
|
8886
|
+
});
|
|
8887
|
+
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) => {
|
|
7758
8888
|
const executor = makeExecutor2();
|
|
7759
8889
|
const chainName = parent.opts().chain;
|
|
7760
8890
|
if (!chainName) {
|
|
@@ -7767,6 +8897,51 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7767
8897
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7768
8898
|
const iface = protocol.interface;
|
|
7769
8899
|
const recipient = opts.recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
|
|
8900
|
+
if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
8901
|
+
if (!opts.pool) throw new Error("--pool is required for Merchant Moe LB remove");
|
|
8902
|
+
if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
|
|
8903
|
+
const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
8904
|
+
const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
8905
|
+
const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
8906
|
+
const [tokenX, tokenY] = tokenA2.toLowerCase() < tokenB2.toLowerCase() ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
|
|
8907
|
+
const binIds = opts.bins.split(",").map((s) => parseInt(s.trim()));
|
|
8908
|
+
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8909
|
+
const binStep = await client.readContract({
|
|
8910
|
+
address: opts.pool,
|
|
8911
|
+
abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
|
|
8912
|
+
functionName: "getBinStep"
|
|
8913
|
+
});
|
|
8914
|
+
let amounts;
|
|
8915
|
+
if (opts.amounts) {
|
|
8916
|
+
amounts = opts.amounts.split(",").map((s) => BigInt(s.trim()));
|
|
8917
|
+
} else {
|
|
8918
|
+
const balanceAbi = parseAbi30(["function balanceOf(address account, uint256 id) view returns (uint256)"]);
|
|
8919
|
+
amounts = await Promise.all(binIds.map(
|
|
8920
|
+
(id) => client.readContract({ address: opts.pool, abi: balanceAbi, functionName: "balanceOf", args: [recipient, BigInt(id)] })
|
|
8921
|
+
));
|
|
8922
|
+
}
|
|
8923
|
+
const approveForAllAbi = parseAbi30(["function approveForAll(address operator, bool approved) external"]);
|
|
8924
|
+
const lbRouter = protocol.contracts["lb_router"];
|
|
8925
|
+
const approveTx = {
|
|
8926
|
+
description: `[${protocol.name}] approveForAll LBPair \u2192 LBRouter`,
|
|
8927
|
+
to: opts.pool,
|
|
8928
|
+
data: encodeFunctionData27({ abi: approveForAllAbi, functionName: "approveForAll", args: [lbRouter, true] }),
|
|
8929
|
+
value: 0n,
|
|
8930
|
+
gas_estimate: 8e4
|
|
8931
|
+
};
|
|
8932
|
+
const tx = await lbAdapter.buildRemoveLiquidity({
|
|
8933
|
+
tokenX,
|
|
8934
|
+
tokenY,
|
|
8935
|
+
binStep,
|
|
8936
|
+
binIds,
|
|
8937
|
+
amounts,
|
|
8938
|
+
recipient
|
|
8939
|
+
});
|
|
8940
|
+
tx.pre_txs = [approveTx, ...tx.pre_txs ?? []];
|
|
8941
|
+
const result = await executor.execute(tx);
|
|
8942
|
+
printOutput({ step: "lb_remove", ...result }, getOpts());
|
|
8943
|
+
return;
|
|
8944
|
+
}
|
|
7770
8945
|
const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
7771
8946
|
const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
7772
8947
|
const poolAddr = opts.pool ? opts.pool : void 0;
|
|
@@ -7782,7 +8957,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7782
8957
|
return;
|
|
7783
8958
|
}
|
|
7784
8959
|
didUnstake = true;
|
|
7785
|
-
} else if (["solidly_v2", "solidly_cl", "hybra"].includes(iface)) {
|
|
8960
|
+
} else if (["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && protocol.contracts?.["voter"]) {
|
|
7786
8961
|
let gaugeAddr = opts.gauge;
|
|
7787
8962
|
if (!gaugeAddr && poolAddr) {
|
|
7788
8963
|
try {
|
|
@@ -7793,11 +8968,32 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7793
8968
|
} catch {
|
|
7794
8969
|
}
|
|
7795
8970
|
}
|
|
8971
|
+
if (gaugeAddr) {
|
|
8972
|
+
if (iface === "solidly_v2" && !opts.tokenId) {
|
|
8973
|
+
const erc20Abi3 = parseAbi30(["function balanceOf(address) view returns (uint256)"]);
|
|
8974
|
+
const gClient = createPublicClient23({ transport: http23(rpcUrl) });
|
|
8975
|
+
const gaugeBal = await gClient.readContract({
|
|
8976
|
+
address: gaugeAddr,
|
|
8977
|
+
abi: erc20Abi3,
|
|
8978
|
+
functionName: "balanceOf",
|
|
8979
|
+
args: [recipient]
|
|
8980
|
+
});
|
|
8981
|
+
if (gaugeBal === 0n) {
|
|
8982
|
+
process.stderr.write("Step 1/2: Skipped unstake \u2014 gauge balance 0 (already unstaked).\n");
|
|
8983
|
+
didUnstake = true;
|
|
8984
|
+
gaugeAddr = void 0;
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
7796
8988
|
if (gaugeAddr) {
|
|
7797
8989
|
process.stderr.write("Step 1/2: Withdrawing from gauge...\n");
|
|
7798
8990
|
const gaugeAdapter = createGauge(protocol, rpcUrl);
|
|
7799
8991
|
const tokenId = opts.tokenId ? BigInt(opts.tokenId) : void 0;
|
|
7800
|
-
const
|
|
8992
|
+
const wOpts = opts.redeemType !== void 0 ? { redeemType: parseInt(opts.redeemType, 10) } : void 0;
|
|
8993
|
+
if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
|
|
8994
|
+
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");
|
|
8995
|
+
}
|
|
8996
|
+
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
|
|
7801
8997
|
const withdrawResult = await executor.execute(withdrawTx);
|
|
7802
8998
|
printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
|
|
7803
8999
|
if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
|
|
@@ -7817,11 +9013,33 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7817
9013
|
token_a: tokenA,
|
|
7818
9014
|
token_b: tokenB,
|
|
7819
9015
|
liquidity: BigInt(opts.liquidity),
|
|
7820
|
-
recipient
|
|
9016
|
+
recipient,
|
|
9017
|
+
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
|
|
7821
9018
|
});
|
|
7822
9019
|
const removeResult = await executor.execute(removeTx);
|
|
7823
9020
|
printOutput({ step: "lp_remove", ...removeResult }, getOpts());
|
|
7824
9021
|
});
|
|
9022
|
+
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) => {
|
|
9023
|
+
const executor = makeExecutor2();
|
|
9024
|
+
const chainName = parent.opts().chain;
|
|
9025
|
+
if (!chainName) {
|
|
9026
|
+
printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
|
|
9027
|
+
return;
|
|
9028
|
+
}
|
|
9029
|
+
const registry = Registry.loadEmbedded();
|
|
9030
|
+
const chain = registry.getChain(chainName);
|
|
9031
|
+
const rpcUrl = chain.effectiveRpcUrl();
|
|
9032
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
9033
|
+
const recipient = resolveAccount(opts.address, lp.opts().wallet);
|
|
9034
|
+
const adapter = createDex(protocol, rpcUrl);
|
|
9035
|
+
if (!("buildCompound" in adapter) || typeof adapter.buildCompound !== "function") {
|
|
9036
|
+
throw new Error(`[${protocol.name}] adapter does not support compound (v1 supports V3 fee-only protocols)`);
|
|
9037
|
+
}
|
|
9038
|
+
const compoundOpts = opts.slippage !== void 0 ? { slippageBps: parseInt(opts.slippage, 10) } : void 0;
|
|
9039
|
+
const tx = await adapter.buildCompound(BigInt(opts.tokenId), recipient, compoundOpts);
|
|
9040
|
+
const result = await executor.execute(tx);
|
|
9041
|
+
printOutput(result, getOpts());
|
|
9042
|
+
});
|
|
7825
9043
|
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) => {
|
|
7826
9044
|
const chainName = parent.opts().chain;
|
|
7827
9045
|
if (!chainName) {
|
|
@@ -7839,17 +9057,68 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7839
9057
|
protocols.map(async (protocol) => {
|
|
7840
9058
|
try {
|
|
7841
9059
|
if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
7842
|
-
if (!opts.pool) return;
|
|
7843
9060
|
const adapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
7844
9061
|
const binIds = opts.bins ? opts.bins.split(",").map((s) => parseInt(s.trim())) : void 0;
|
|
7845
|
-
const
|
|
7846
|
-
for (const
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
9062
|
+
const poolsToScan = opts.pool ? [opts.pool] : (await adapter.discoverRewardedPools()).map((p) => p.pool);
|
|
9063
|
+
for (const poolAddr of poolsToScan) {
|
|
9064
|
+
try {
|
|
9065
|
+
const userBins = binIds ?? await adapter.findUserBinsWithBalance(poolAddr, user);
|
|
9066
|
+
if (userBins.length === 0) continue;
|
|
9067
|
+
const positions = await adapter.getUserPositions(user, poolAddr, userBins);
|
|
9068
|
+
if (positions.length === 0) continue;
|
|
9069
|
+
const pending = await adapter.getPendingRewards(user, poolAddr, userBins).catch(() => []);
|
|
9070
|
+
const totalPending = pending.reduce((s, r) => s + (r.amount ?? 0n), 0n);
|
|
9071
|
+
for (const pos of positions) {
|
|
9072
|
+
results.push({
|
|
9073
|
+
protocol: protocol.slug,
|
|
9074
|
+
type: "lb",
|
|
9075
|
+
pool: poolAddr,
|
|
9076
|
+
...pos,
|
|
9077
|
+
pending_reward: totalPending.toString(),
|
|
9078
|
+
pending_reward_token: pending[0]?.token
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
9081
|
+
} catch {
|
|
9082
|
+
}
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
9086
|
+
if (npm && ["uniswap_v3", "algebra_v3", "hybra"].includes(protocol.interface)) {
|
|
9087
|
+
const npmAbi = parseAbi30([
|
|
9088
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
9089
|
+
"function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)"
|
|
9090
|
+
]);
|
|
9091
|
+
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9092
|
+
let count;
|
|
9093
|
+
try {
|
|
9094
|
+
count = await client.readContract({ address: npm, abi: npmAbi, functionName: "balanceOf", args: [user] });
|
|
9095
|
+
} catch {
|
|
9096
|
+
return;
|
|
9097
|
+
}
|
|
9098
|
+
const max = Math.min(Number(count), 50);
|
|
9099
|
+
for (let i = 0; i < max; i++) {
|
|
9100
|
+
try {
|
|
9101
|
+
const tokenId = await client.readContract({
|
|
9102
|
+
address: npm,
|
|
9103
|
+
abi: npmAbi,
|
|
9104
|
+
functionName: "tokenOfOwnerByIndex",
|
|
9105
|
+
args: [user, BigInt(i)]
|
|
9106
|
+
});
|
|
9107
|
+
const liq = await detectV3Liquidity(client, npm, tokenId);
|
|
9108
|
+
if (liq && liq.liquidity > 0n) {
|
|
9109
|
+
results.push({
|
|
9110
|
+
protocol: protocol.slug,
|
|
9111
|
+
type: "v3_nft",
|
|
9112
|
+
token_id: tokenId.toString(),
|
|
9113
|
+
token0: liq.token0,
|
|
9114
|
+
token1: liq.token1,
|
|
9115
|
+
liquidity: liq.liquidity.toString(),
|
|
9116
|
+
tickLower: liq.tickLower,
|
|
9117
|
+
tickUpper: liq.tickUpper
|
|
9118
|
+
});
|
|
9119
|
+
}
|
|
9120
|
+
} catch {
|
|
9121
|
+
}
|
|
7853
9122
|
}
|
|
7854
9123
|
}
|
|
7855
9124
|
} catch {
|
|
@@ -9753,10 +11022,9 @@ function registerBridge(parent, getOpts) {
|
|
|
9753
11022
|
|
|
9754
11023
|
// src/commands/swap.ts
|
|
9755
11024
|
init_dist();
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
};
|
|
11025
|
+
function getAggregatorSlugs(chainCfg) {
|
|
11026
|
+
return chainCfg.aggregators ?? {};
|
|
11027
|
+
}
|
|
9760
11028
|
var KYBER_API = "https://aggregator-api.kyberswap.com";
|
|
9761
11029
|
async function kyberGetQuote(chain, tokenIn, tokenOut, amountIn) {
|
|
9762
11030
|
const params = new URLSearchParams({ tokenIn, tokenOut, amountIn });
|
|
@@ -9808,6 +11076,64 @@ async function openoceanSwap(chain, inTokenAddress, outTokenAddress, amountIn, s
|
|
|
9808
11076
|
outAmount: String(data.outAmount ?? "0")
|
|
9809
11077
|
};
|
|
9810
11078
|
}
|
|
11079
|
+
var LIFI_API2 = "https://li.quest/v1";
|
|
11080
|
+
async function lifiQuote(chainId, fromToken, toToken, fromAmount, fromAddress, slippagePct) {
|
|
11081
|
+
const params = new URLSearchParams({
|
|
11082
|
+
fromChain: String(chainId),
|
|
11083
|
+
toChain: String(chainId),
|
|
11084
|
+
fromToken,
|
|
11085
|
+
toToken,
|
|
11086
|
+
fromAmount,
|
|
11087
|
+
fromAddress,
|
|
11088
|
+
slippage: (Number(slippagePct) / 100).toFixed(4)
|
|
11089
|
+
});
|
|
11090
|
+
const url = `${LIFI_API2}/quote?${params}`;
|
|
11091
|
+
const res = await fetch(url);
|
|
11092
|
+
if (!res.ok) throw new Error(`LI.FI quote failed: ${res.status} ${await res.text()}`);
|
|
11093
|
+
const json = await res.json();
|
|
11094
|
+
const txReq = json.transactionRequest;
|
|
11095
|
+
if (!txReq) throw new Error("LI.FI: no transactionRequest in response");
|
|
11096
|
+
const estimate = json.estimate;
|
|
11097
|
+
return {
|
|
11098
|
+
to: String(txReq.to),
|
|
11099
|
+
data: String(txReq.data),
|
|
11100
|
+
value: String(txReq.value ?? "0x0"),
|
|
11101
|
+
outAmount: String(estimate?.toAmount ?? "0")
|
|
11102
|
+
};
|
|
11103
|
+
}
|
|
11104
|
+
var RELAY_API = "https://api.relay.link";
|
|
11105
|
+
async function relayQuote(chainId, fromToken, toToken, amount, user) {
|
|
11106
|
+
const body = {
|
|
11107
|
+
user,
|
|
11108
|
+
originChainId: chainId,
|
|
11109
|
+
destinationChainId: chainId,
|
|
11110
|
+
originCurrency: fromToken,
|
|
11111
|
+
destinationCurrency: toToken,
|
|
11112
|
+
recipient: user,
|
|
11113
|
+
tradeType: "EXACT_INPUT",
|
|
11114
|
+
amount
|
|
11115
|
+
};
|
|
11116
|
+
const res = await fetch(`${RELAY_API}/quote`, {
|
|
11117
|
+
method: "POST",
|
|
11118
|
+
headers: { "content-type": "application/json" },
|
|
11119
|
+
body: JSON.stringify(body)
|
|
11120
|
+
});
|
|
11121
|
+
if (!res.ok) throw new Error(`Relay quote failed: ${res.status} ${await res.text()}`);
|
|
11122
|
+
const json = await res.json();
|
|
11123
|
+
const steps = json.steps;
|
|
11124
|
+
const swapStep = steps?.find((s) => s.id !== "approve") ?? steps?.[steps.length - 1];
|
|
11125
|
+
const items = swapStep?.items;
|
|
11126
|
+
const txData = items?.[0]?.data;
|
|
11127
|
+
if (!txData) throw new Error("Relay: no swap step in quote");
|
|
11128
|
+
const details = json.details;
|
|
11129
|
+
const currencyOut = details?.currencyOut;
|
|
11130
|
+
return {
|
|
11131
|
+
to: String(txData.to),
|
|
11132
|
+
data: String(txData.data),
|
|
11133
|
+
value: String(txData.value ?? "0x0"),
|
|
11134
|
+
outAmount: String(currencyOut?.amount ?? "0")
|
|
11135
|
+
};
|
|
11136
|
+
}
|
|
9811
11137
|
var LIQD_API = "https://api.liqd.ag/v2";
|
|
9812
11138
|
var LIQD_ROUTER = "0x744489ee3d540777a66f2cf297479745e0852f7a";
|
|
9813
11139
|
async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
|
|
@@ -9827,7 +11153,7 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
|
|
|
9827
11153
|
};
|
|
9828
11154
|
}
|
|
9829
11155
|
function registerSwap(parent, getOpts, makeExecutor2) {
|
|
9830
|
-
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) => {
|
|
11156
|
+
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) => {
|
|
9831
11157
|
const executor = makeExecutor2();
|
|
9832
11158
|
const chainName = requireChain(parent, getOpts);
|
|
9833
11159
|
if (!chainName) return;
|
|
@@ -9838,12 +11164,13 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9838
11164
|
const toAddr = resolveTokenAddress(registry, chainName, opts.to);
|
|
9839
11165
|
const wallet = resolveWallet();
|
|
9840
11166
|
if (provider === "kyber") {
|
|
9841
|
-
const
|
|
9842
|
-
if (!
|
|
9843
|
-
|
|
11167
|
+
const aggCfg = getAggregatorSlugs(registry.getChain(chainName));
|
|
11168
|
+
if (!aggCfg.kyber) {
|
|
11169
|
+
const supported = Array.from(registry.chains.keys()).filter((c) => registry.getChain(c).aggregators?.kyber).join(", ");
|
|
11170
|
+
printOutput({ error: `KyberSwap: unsupported chain '${chainName}'. Supported: ${supported || "(none)"}` }, getOpts());
|
|
9844
11171
|
return;
|
|
9845
11172
|
}
|
|
9846
|
-
const kyberChain =
|
|
11173
|
+
const kyberChain = aggCfg.kyber;
|
|
9847
11174
|
try {
|
|
9848
11175
|
const quoteData = await kyberGetQuote(kyberChain, fromAddr, toAddr, opts.amount);
|
|
9849
11176
|
const routeSummary = quoteData.routeSummary;
|
|
@@ -9879,12 +11206,13 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9879
11206
|
return;
|
|
9880
11207
|
}
|
|
9881
11208
|
if (provider === "openocean") {
|
|
9882
|
-
const
|
|
9883
|
-
if (!
|
|
9884
|
-
|
|
11209
|
+
const aggCfg = getAggregatorSlugs(registry.getChain(chainName));
|
|
11210
|
+
if (!aggCfg.openocean) {
|
|
11211
|
+
const supported = Array.from(registry.chains.keys()).filter((c) => registry.getChain(c).aggregators?.openocean).join(", ");
|
|
11212
|
+
printOutput({ error: `OpenOcean: unsupported chain '${chainName}'. Supported: ${supported || "(none)"}` }, getOpts());
|
|
9885
11213
|
return;
|
|
9886
11214
|
}
|
|
9887
|
-
const ooChain =
|
|
11215
|
+
const ooChain = aggCfg.openocean;
|
|
9888
11216
|
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());
|
|
9889
11217
|
const fromDecimals = fromToken?.decimals ?? 18;
|
|
9890
11218
|
const humanAmount = (Number(opts.amount) / 10 ** fromDecimals).toString();
|
|
@@ -9952,7 +11280,41 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
9952
11280
|
}
|
|
9953
11281
|
return;
|
|
9954
11282
|
}
|
|
9955
|
-
|
|
11283
|
+
if (provider === "lifi" || provider === "relay") {
|
|
11284
|
+
const chainCfg = registry.getChain(chainName);
|
|
11285
|
+
const chainId = chainCfg.chain_id;
|
|
11286
|
+
if (!chainId) {
|
|
11287
|
+
printOutput({ error: `${provider}: chain '${chainName}' has no chain_id in registry` }, getOpts());
|
|
11288
|
+
return;
|
|
11289
|
+
}
|
|
11290
|
+
const slippagePct = (slippageBps / 100).toFixed(2);
|
|
11291
|
+
try {
|
|
11292
|
+
const route = provider === "lifi" ? await lifiQuote(chainId, fromAddr, toAddr, opts.amount, wallet, slippagePct) : await relayQuote(chainId, fromAddr, toAddr, opts.amount, wallet);
|
|
11293
|
+
const tx = {
|
|
11294
|
+
description: `${provider}: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11295
|
+
to: route.to,
|
|
11296
|
+
data: route.data,
|
|
11297
|
+
value: parseBigIntValue(route.value),
|
|
11298
|
+
approvals: [{ token: fromAddr, spender: route.to, amount: BigInt(opts.amount) }]
|
|
11299
|
+
};
|
|
11300
|
+
const result = await executor.execute(tx);
|
|
11301
|
+
printOutput({
|
|
11302
|
+
provider,
|
|
11303
|
+
chain: chainName,
|
|
11304
|
+
chain_id: chainId,
|
|
11305
|
+
from_token: fromAddr,
|
|
11306
|
+
to_token: toAddr,
|
|
11307
|
+
amount_in: opts.amount,
|
|
11308
|
+
amount_out: route.outAmount,
|
|
11309
|
+
router: route.to,
|
|
11310
|
+
...result
|
|
11311
|
+
}, getOpts());
|
|
11312
|
+
} catch (e) {
|
|
11313
|
+
printOutput({ error: `${provider} error: ${errMsg(e)}` }, getOpts());
|
|
11314
|
+
}
|
|
11315
|
+
return;
|
|
11316
|
+
}
|
|
11317
|
+
printOutput({ error: `Unknown provider '${opts.provider}'. Choose: kyber, openocean, liquid, lifi, relay` }, getOpts());
|
|
9956
11318
|
});
|
|
9957
11319
|
}
|
|
9958
11320
|
|
|
@@ -10004,8 +11366,8 @@ function isValidPrivateKey(s) {
|
|
|
10004
11366
|
}
|
|
10005
11367
|
async function deriveAddress(privateKey) {
|
|
10006
11368
|
try {
|
|
10007
|
-
const { privateKeyToAccount:
|
|
10008
|
-
const account =
|
|
11369
|
+
const { privateKeyToAccount: privateKeyToAccount3 } = await import("viem/accounts");
|
|
11370
|
+
const account = privateKeyToAccount3(privateKey);
|
|
10009
11371
|
return account.address;
|
|
10010
11372
|
} catch {
|
|
10011
11373
|
return null;
|