@hypurrquant/defi-cli 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/config/chains.toml +6 -1
- package/config/protocols/dex/aerodrome_base.toml +2 -1
- package/config/protocols/dex/aerodrome_cl.toml +2 -1
- package/config/protocols/dex/apeswap_bnb.toml +1 -1
- package/config/protocols/dex/babydogeswap_bnb.toml +1 -1
- package/config/protocols/dex/bakeryswap_bnb.toml +2 -2
- package/config/protocols/dex/biswap_bnb.toml +1 -1
- package/config/protocols/dex/bscswap_bnb.toml +1 -1
- package/config/protocols/dex/curve_hyperevm.toml +1 -1
- package/config/protocols/dex/fstswap_bnb.toml +1 -1
- package/config/protocols/dex/hybra.toml +4 -2
- package/config/protocols/dex/hyperswap.toml +1 -1
- package/config/protocols/dex/kittenswap.toml +1 -1
- package/config/protocols/dex/merchantmoe_mantle.toml +2 -1
- package/config/protocols/dex/nest.toml +6 -5
- package/config/protocols/dex/pancakeswap_v2_bnb.toml +1 -1
- package/config/protocols/dex/pancakeswap_v3_bnb.toml +2 -1
- package/config/protocols/dex/project_x.toml +1 -1
- package/config/protocols/dex/ramses_cl.toml +1 -1
- package/config/protocols/dex/ramses_hl.toml +1 -1
- package/config/protocols/dex/thena_v1_bnb.toml +2 -1
- package/config/protocols/dex/traderjoe_monad.toml +2 -1
- package/config/protocols/dex/uniswap_v2_monad.toml +2 -1
- package/config/protocols/dex/uniswap_v3_base.toml +2 -1
- package/config/protocols/dex/uniswap_v3_bnb.toml +3 -1
- package/config/protocols/dex/uniswap_v3_mantle.toml +2 -1
- package/config/protocols/dex/uniswap_v3_monad.toml +2 -1
- package/config/protocols/lending/felix_morpho.toml +16 -0
- package/config/protocols/lending/hyperlend.toml +1 -1
- package/config/protocols/lending/hypurrfi.toml +1 -1
- package/config/protocols/lending/kinza_bnb.toml +1 -1
- package/config/protocols/lending/morpho_blue_monad.toml +17 -0
- package/config/protocols/lending/venus_flux_bnb.toml +5 -1
- package/dist/index.js +1590 -309
- package/dist/index.js.map +1 -1
- package/dist/main.js +1595 -314
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +810 -196
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/defi-cli/SKILL.md +54 -5
- package/skills/defi-cli/references/commands.md +41 -8
- package/skills/defi-cli/references/protocols.md +2 -2
- package/skills/defi-cli/scripts/bridge-quote.sh +34 -0
- package/skills/defi-cli/scripts/lending-supply-flow.sh +28 -0
- package/skills/defi-cli/scripts/lp-claim-all.sh +25 -0
- package/skills/defi-cli/scripts/lp-emission-discover.sh +20 -0
- package/skills/defi-cli/scripts/portfolio-snapshot.sh +0 -0
- package/skills/defi-cli/scripts/preflight.sh +0 -0
- package/skills/defi-cli/scripts/swap-quote.sh +37 -0
- package/skills/defi-cli/scripts/wallet-status.sh +30 -0
- package/skills/defi-cli/scripts/yield-scan.sh +0 -0
- package/skills/defi-cli/scripts/exploit-scan.sh +0 -10
package/dist/main.js
CHANGED
|
@@ -18,6 +18,12 @@ import { resolve } from "path";
|
|
|
18
18
|
import { fileURLToPath } from "url";
|
|
19
19
|
import { parse } from "smol-toml";
|
|
20
20
|
import { existsSync } from "fs";
|
|
21
|
+
function defaultSwapSlippage() {
|
|
22
|
+
return { bps: 50 };
|
|
23
|
+
}
|
|
24
|
+
function applyMinSlippage(slippage, amount) {
|
|
25
|
+
return amount * BigInt(1e4 - slippage.bps) / 10000n;
|
|
26
|
+
}
|
|
21
27
|
function jsonReplacerDecimal(_key, value) {
|
|
22
28
|
if (typeof value === "bigint") {
|
|
23
29
|
return value.toString();
|
|
@@ -55,11 +61,15 @@ function buildTransfer(token, to, amount) {
|
|
|
55
61
|
gas_estimate: 65e3
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
|
-
function getProvider(rpcUrl) {
|
|
59
|
-
const
|
|
64
|
+
function getProvider(rpcUrl, chain) {
|
|
65
|
+
const key = chain ? `${rpcUrl}@${chain.id}` : rpcUrl;
|
|
66
|
+
const cached = providerCache.get(key);
|
|
60
67
|
if (cached) return cached;
|
|
61
|
-
const client = createPublicClient({
|
|
62
|
-
|
|
68
|
+
const client = createPublicClient({
|
|
69
|
+
transport: http(rpcUrl),
|
|
70
|
+
...chain ? { chain } : {}
|
|
71
|
+
});
|
|
72
|
+
providerCache.set(key, client);
|
|
63
73
|
return client;
|
|
64
74
|
}
|
|
65
75
|
async function multicallRead(rpcUrl, calls) {
|
|
@@ -229,6 +239,28 @@ var init_dist = __esm({
|
|
|
229
239
|
const chainEnv = this.name.toUpperCase().replace(/ /g, "_") + "_RPC_URL";
|
|
230
240
|
return process.env[chainEnv] ?? this.rpc_url;
|
|
231
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Build a viem Chain object pinned to this config so wallet/public clients
|
|
244
|
+
* can sign with an explicit chainId rather than auto-fetching it from the
|
|
245
|
+
* RPC. SSOT 7.4: anchoring chainId at client-construction time defends
|
|
246
|
+
* against an MITM RPC that returns the wrong eth_chainId, and keeps
|
|
247
|
+
* offline signing safe against RPC drift.
|
|
248
|
+
*/
|
|
249
|
+
viemChain() {
|
|
250
|
+
const rpcUrl = this.effectiveRpcUrl();
|
|
251
|
+
return {
|
|
252
|
+
id: this.chain_id,
|
|
253
|
+
name: this.name,
|
|
254
|
+
nativeCurrency: {
|
|
255
|
+
name: this.native_token,
|
|
256
|
+
symbol: this.native_token,
|
|
257
|
+
decimals: 18
|
|
258
|
+
},
|
|
259
|
+
rpcUrls: { default: { http: [rpcUrl] } },
|
|
260
|
+
...this.explorer_url ? { blockExplorers: { default: { name: this.name, url: this.explorer_url } } } : {},
|
|
261
|
+
...this.multicall3 ? { contracts: { multicall3: { address: this.multicall3 } } } : {}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
232
264
|
};
|
|
233
265
|
ProtocolCategory = /* @__PURE__ */ ((ProtocolCategory2) => {
|
|
234
266
|
ProtocolCategory2["Dex"] = "dex";
|
|
@@ -420,13 +452,13 @@ __export(dist_exports, {
|
|
|
420
452
|
import { encodeFunctionData as encodeFunctionData4, parseAbi as parseAbi4, createPublicClient as createPublicClient4, http as http4, decodeAbiParameters } from "viem";
|
|
421
453
|
import { encodeFunctionData as encodeFunctionData22, parseAbi as parseAbi22, createPublicClient as createPublicClient22, http as http22, decodeFunctionResult as decodeFunctionResult2, decodeAbiParameters as decodeAbiParameters2 } from "viem";
|
|
422
454
|
import { encodeFunctionData as encodeFunctionData32, parseAbi as parseAbi32, createPublicClient as createPublicClient32, http as http32, decodeAbiParameters as decodeAbiParameters3, concatHex, zeroAddress } from "viem";
|
|
423
|
-
import { encodeFunctionData as encodeFunctionData42, parseAbi as parseAbi42
|
|
455
|
+
import { encodeFunctionData as encodeFunctionData42, parseAbi as parseAbi42 } from "viem";
|
|
424
456
|
import { encodeFunctionData as encodeFunctionData5, parseAbi as parseAbi5 } from "viem";
|
|
425
457
|
import { encodeFunctionData as encodeFunctionData6, parseAbi as parseAbi6, decodeAbiParameters as decodeAbiParameters4 } from "viem";
|
|
426
|
-
import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient42, http as http42, zeroAddress as
|
|
427
|
-
import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as
|
|
428
|
-
import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as
|
|
429
|
-
import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as
|
|
458
|
+
import { encodeFunctionData as encodeFunctionData7, parseAbi as parseAbi7, createPublicClient as createPublicClient42, http as http42, zeroAddress as zeroAddress2 } from "viem";
|
|
459
|
+
import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress3 } from "viem";
|
|
460
|
+
import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress4 } from "viem";
|
|
461
|
+
import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress5 } from "viem";
|
|
430
462
|
import { encodeFunctionData as encodeFunctionData11, parseAbi as parseAbi11, createPublicClient as createPublicClient7, http as http7 } from "viem";
|
|
431
463
|
import {
|
|
432
464
|
encodeFunctionData as encodeFunctionData12,
|
|
@@ -444,24 +476,24 @@ import {
|
|
|
444
476
|
keccak256,
|
|
445
477
|
parseAbi as parseAbi13,
|
|
446
478
|
decodeFunctionResult as decodeFunctionResult5,
|
|
447
|
-
zeroAddress as
|
|
479
|
+
zeroAddress as zeroAddress6
|
|
448
480
|
} from "viem";
|
|
449
|
-
import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as
|
|
450
|
-
import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as
|
|
481
|
+
import { createPublicClient as createPublicClient10, http as http10, parseAbi as parseAbi14, encodeFunctionData as encodeFunctionData14, decodeFunctionResult as decodeFunctionResult6, zeroAddress as zeroAddress7 } from "viem";
|
|
482
|
+
import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress8 } from "viem";
|
|
451
483
|
import { createPublicClient as createPublicClient12, http as http12, parseAbi as parseAbi16 } from "viem";
|
|
452
484
|
import { createPublicClient as createPublicClient13, http as http13, parseAbi as parseAbi17, encodeFunctionData as encodeFunctionData16 } from "viem";
|
|
453
485
|
import { createPublicClient as createPublicClient14, http as http14, parseAbi as parseAbi18, encodeFunctionData as encodeFunctionData17 } from "viem";
|
|
454
486
|
import { createPublicClient as createPublicClient15, http as http15, parseAbi as parseAbi19, encodeFunctionData as encodeFunctionData18 } from "viem";
|
|
455
|
-
import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as
|
|
456
|
-
import { createPublicClient as
|
|
457
|
-
import { createPublicClient as
|
|
458
|
-
import { createPublicClient as
|
|
487
|
+
import { parseAbi as parseAbi20, encodeFunctionData as encodeFunctionData19, decodeFunctionResult as decodeFunctionResult7, zeroAddress as zeroAddress9, createPublicClient as createPublicClient16, http as http16 } from "viem";
|
|
488
|
+
import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress10 } from "viem";
|
|
489
|
+
import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi222 } from "viem";
|
|
490
|
+
import { createPublicClient as createPublicClient19, http as http19, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
|
|
459
491
|
import { parseAbi as parseAbi24, encodeFunctionData as encodeFunctionData222 } from "viem";
|
|
460
|
-
import { createPublicClient as
|
|
461
|
-
import { createPublicClient as
|
|
492
|
+
import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress11 } from "viem";
|
|
493
|
+
import { createPublicClient as createPublicClient21, http as http21, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress12 } from "viem";
|
|
462
494
|
import { parseAbi as parseAbi27, encodeFunctionData as encodeFunctionData25 } from "viem";
|
|
463
495
|
import { parseAbi as parseAbi28, encodeFunctionData as encodeFunctionData26 } from "viem";
|
|
464
|
-
import { createPublicClient as
|
|
496
|
+
import { createPublicClient as createPublicClient222, http as http222, parseAbi as parseAbi29 } from "viem";
|
|
465
497
|
function pctToTickDelta(pct) {
|
|
466
498
|
return Math.round(Math.log(1 + pct / 100) / Math.log(1.0001));
|
|
467
499
|
}
|
|
@@ -715,15 +747,6 @@ function u256ToF642(v) {
|
|
|
715
747
|
if (v > MAX_U128) return Infinity;
|
|
716
748
|
return Number(v);
|
|
717
749
|
}
|
|
718
|
-
function defaultMarketParams(loanToken = zeroAddress10) {
|
|
719
|
-
return {
|
|
720
|
-
loanToken,
|
|
721
|
-
collateralToken: zeroAddress10,
|
|
722
|
-
oracle: zeroAddress10,
|
|
723
|
-
irm: zeroAddress10,
|
|
724
|
-
lltv: 0n
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
750
|
function decodeMarket(data) {
|
|
728
751
|
if (!data) return null;
|
|
729
752
|
try {
|
|
@@ -956,7 +979,7 @@ function createKittenSwapFarming(entry, rpcUrl) {
|
|
|
956
979
|
const bonusRewardToken = entry.contracts?.["bonus_reward_token"];
|
|
957
980
|
return new KittenSwapFarmingAdapter(entry.name, farmingCenter, eternalFarming, positionManager, rpcUrl, factory, rewardToken, bonusRewardToken);
|
|
958
981
|
}
|
|
959
|
-
var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
|
|
982
|
+
var DEFAULT_FEE, swapRouterAbi, quoterAbi, ramsesQuoterAbi, positionManagerAbi, slipstreamMintAbi, ramsesMintAbi, UniswapV3Adapter, abi, lbQuoterAbi, UniswapV2Adapter, abi2, algebraQuoterAbi, algebraSingleQuoterAbi, algebraIntegralPmAbi, algebraV2PmAbi, algebraSharedPmAbi, AlgebraV3Adapter, abi3, BalancerV3Adapter, poolAbi, CurveStableSwapAdapter, abi4, abiV2, SolidlyAdapter, thenaPmAbi, thenaRouterAbi, thenaPoolAbi, thenaFactoryAbi, ThenaCLAdapter, _addressDecodeAbi, _symbolDecodeAbi, gaugeManagerAbi, gaugeCLAbi, nfpmAbi, veAbi, voterAbi, HybraGaugeAdapter, abi5, WooFiAdapter, gaugeAbi, veAbi2, voterAbi2, _addressDecodeAbi2, _symbolDecodeAbi2, _boolDecodeAbi, HYPEREVM_TOKENS, CL_TICK_SPACINGS, SolidlyGaugeAdapter, masterchefAbi, MasterChefAdapter, lbRouterAbi, lbFactoryAbi, lbPairAbi, lbRewarderAbi, masterChefAbi, veMoeAbi, lbPairBinAbi, lbQuoterAbi2, erc20Abi2, _addressAbi, _uint256Abi, _boolAbi, _rangeAbi, _binAbi, _uint256ArrayAbi, MerchantMoeLBAdapter, KITTEN_TOKEN, WHYPE_TOKEN, MAX_NONCE_SCAN, HYPEREVM_TOKENS2, farmingCenterAbi, positionManagerAbi2, eternalFarmingAbi, algebraFactoryAbi, _addressDecodeAbi3, nonceCache, KittenSwapFarmingAdapter, DEFAULT_BASE_URL, FALLBACK_BASE_URL, NEST_TOKEN, NEST_DECIMALS, NestOffChainAdapter, POOL_ABI, ERC20_ABI2, INCENTIVES_ABI, REWARDS_CONTROLLER_ABI, POOL_PROVIDER_ABI, ADDRESSES_PROVIDER_ABI, ORACLE_ABI, ERC20_DECIMALS_ABI, AaveV3Adapter, POOL_ABI2, ERC20_ABI22, AaveV2Adapter, ORACLE_ABI2, AaveOracleAdapter, CTOKEN_ABI, NATIVE_CTOKEN_ABI, COMPTROLLER_ABI, NATIVE_SENTINEL, BSC_BLOCKS_PER_YEAR, CompoundV2Adapter, COMET_ABI, SECONDS_PER_YEAR, CompoundV3Adapter, EULER_VAULT_ABI, SECONDS_PER_YEAR2, EulerV2Adapter, MORPHO_ABI, META_MORPHO_ABI, ERC4626_ABI, MAX_UINT256, IRM_ABI, SECONDS_PER_YEAR3, MorphoBlueAdapter, BORROWER_OPS_ABI, TROVE_MANAGER_ABI, HINT_HELPERS_ABI, SORTED_TROVES_ABI, FelixCdpAdapter, PRICE_FEED_ABI, FelixOracleAdapter, ERC4626_ABI2, ERC4626VaultAdapter, GENERIC_LST_ABI, GenericLstAdapter, STHYPE_ABI, ERC20_ABI3, StHypeAdapter, KINETIQ_ABI, ORACLE_ABI3, WHYPE, HYPERLEND_ORACLE, KinetiqAdapter, PendleAdapter, GenericYieldAdapter, HLP_ABI, HlpVaultAdapter, GenericDerivativesAdapter, RYSK_ABI, RyskAdapter, GenericOptionsAdapter, ERC721_ABI, ERC721Adapter, DexSpotPrice;
|
|
960
983
|
var init_dist2 = __esm({
|
|
961
984
|
"../defi-protocols/dist/index.js"() {
|
|
962
985
|
"use strict";
|
|
@@ -1024,6 +1047,10 @@ var init_dist2 = __esm({
|
|
|
1024
1047
|
"struct SlipstreamMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; uint160 sqrtPriceX96; }",
|
|
1025
1048
|
"function mint(SlipstreamMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
1026
1049
|
]);
|
|
1050
|
+
ramsesMintAbi = parseAbi4([
|
|
1051
|
+
"struct RamsesMintParams { address token0; address token1; int24 tickSpacing; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; }",
|
|
1052
|
+
"function mint(RamsesMintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)"
|
|
1053
|
+
]);
|
|
1027
1054
|
UniswapV3Adapter = class {
|
|
1028
1055
|
protocolName;
|
|
1029
1056
|
router;
|
|
@@ -1033,6 +1060,7 @@ var init_dist2 = __esm({
|
|
|
1033
1060
|
fee;
|
|
1034
1061
|
rpcUrl;
|
|
1035
1062
|
useTickSpacingQuoter;
|
|
1063
|
+
clStyle;
|
|
1036
1064
|
constructor(entry, rpcUrl) {
|
|
1037
1065
|
this.protocolName = entry.name;
|
|
1038
1066
|
const router = entry.contracts?.["router"];
|
|
@@ -1045,6 +1073,7 @@ var init_dist2 = __esm({
|
|
|
1045
1073
|
this.factory = entry.contracts?.["factory"];
|
|
1046
1074
|
this.fee = DEFAULT_FEE;
|
|
1047
1075
|
this.rpcUrl = rpcUrl;
|
|
1076
|
+
this.clStyle = entry.cl_style;
|
|
1048
1077
|
this.useTickSpacingQuoter = entry.cl_style === "slipstream" || entry.cl_style === "ramses" || entry.contracts?.["pool_deployer"] !== void 0 || entry.contracts?.["gauge_factory"] !== void 0;
|
|
1049
1078
|
}
|
|
1050
1079
|
name() {
|
|
@@ -1052,7 +1081,15 @@ var init_dist2 = __esm({
|
|
|
1052
1081
|
}
|
|
1053
1082
|
async buildSwap(params) {
|
|
1054
1083
|
const deadline = BigInt(params.deadline ?? 18446744073709551615n);
|
|
1055
|
-
const amountOutMinimum =
|
|
1084
|
+
const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
|
|
1085
|
+
params.slippage,
|
|
1086
|
+
(await this.quote({
|
|
1087
|
+
protocol: this.protocolName,
|
|
1088
|
+
token_in: params.token_in,
|
|
1089
|
+
token_out: params.token_out,
|
|
1090
|
+
amount_in: params.amount_in
|
|
1091
|
+
})).amount_out
|
|
1092
|
+
);
|
|
1056
1093
|
const data = encodeFunctionData4({
|
|
1057
1094
|
abi: swapRouterAbi,
|
|
1058
1095
|
functionName: "exactInputSingle",
|
|
@@ -1218,31 +1255,37 @@ var init_dist2 = __esm({
|
|
|
1218
1255
|
if (!pm) {
|
|
1219
1256
|
throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
|
|
1220
1257
|
}
|
|
1221
|
-
const
|
|
1258
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
1259
|
+
const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
|
|
1222
1260
|
const amount0 = rawAmount0 === 0n && rawAmount1 > 0n ? 1n : rawAmount0;
|
|
1223
1261
|
const amount1 = rawAmount1 === 0n && rawAmount0 > 0n ? 1n : rawAmount1;
|
|
1262
|
+
const slippage = params.slippage ?? defaultSwapSlippage();
|
|
1263
|
+
const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
|
|
1264
|
+
const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
|
|
1265
|
+
const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
|
|
1224
1266
|
let thirdField = this.fee;
|
|
1225
1267
|
let tickLower = -887220;
|
|
1226
1268
|
let tickUpper = 887220;
|
|
1227
1269
|
if (params.pool && this.rpcUrl) {
|
|
1228
1270
|
const poolAbi2 = parseAbi4([
|
|
1229
1271
|
"function fee() view returns (uint24)",
|
|
1230
|
-
"function tickSpacing() view returns (int24)"
|
|
1231
|
-
"function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, bool)"
|
|
1272
|
+
"function tickSpacing() view returns (int24)"
|
|
1232
1273
|
]);
|
|
1233
1274
|
const client = createPublicClient4({ transport: http4(this.rpcUrl) });
|
|
1234
|
-
const [poolFee, poolTs,
|
|
1275
|
+
const [poolFee, poolTs, slot0Raw] = await Promise.all([
|
|
1235
1276
|
client.readContract({ address: params.pool, abi: poolAbi2, functionName: "fee" }).catch(() => null),
|
|
1236
1277
|
client.readContract({ address: params.pool, abi: poolAbi2, functionName: "tickSpacing" }).catch(() => null),
|
|
1237
|
-
client.
|
|
1278
|
+
client.call({ to: params.pool, data: "0x3850c7bd" }).then((r) => r.data ?? null).catch(() => null)
|
|
1238
1279
|
]);
|
|
1239
1280
|
if (this.useTickSpacingQuoter && poolTs !== null) {
|
|
1240
1281
|
thirdField = poolTs;
|
|
1241
1282
|
} else if (poolFee !== null) {
|
|
1242
1283
|
thirdField = poolFee;
|
|
1243
1284
|
}
|
|
1244
|
-
if (params.range_pct !== void 0 &&
|
|
1245
|
-
const
|
|
1285
|
+
if (params.range_pct !== void 0 && slot0Raw && poolTs !== null) {
|
|
1286
|
+
const tickWord = slot0Raw.slice(2 + 64, 2 + 128);
|
|
1287
|
+
const tickU = BigInt("0x" + tickWord);
|
|
1288
|
+
const currentTick = tickU >= 1n << 255n ? Number(tickU - (1n << 256n)) : Number(tickU);
|
|
1246
1289
|
const rangeTicks = Math.floor(params.range_pct * 100);
|
|
1247
1290
|
tickLower = Math.floor((currentTick - rangeTicks) / poolTs) * poolTs;
|
|
1248
1291
|
tickUpper = Math.ceil((currentTick + rangeTicks) / poolTs) * poolTs;
|
|
@@ -1250,7 +1293,25 @@ var init_dist2 = __esm({
|
|
|
1250
1293
|
}
|
|
1251
1294
|
if (params.tick_lower !== void 0) tickLower = params.tick_lower;
|
|
1252
1295
|
if (params.tick_upper !== void 0) tickUpper = params.tick_upper;
|
|
1253
|
-
const data = this.
|
|
1296
|
+
const data = this.clStyle === "ramses" ? encodeFunctionData4({
|
|
1297
|
+
abi: ramsesMintAbi,
|
|
1298
|
+
functionName: "mint",
|
|
1299
|
+
args: [
|
|
1300
|
+
{
|
|
1301
|
+
token0,
|
|
1302
|
+
token1,
|
|
1303
|
+
tickSpacing: thirdField,
|
|
1304
|
+
tickLower,
|
|
1305
|
+
tickUpper,
|
|
1306
|
+
amount0Desired: amount0,
|
|
1307
|
+
amount1Desired: amount1,
|
|
1308
|
+
amount0Min,
|
|
1309
|
+
amount1Min,
|
|
1310
|
+
recipient: params.recipient,
|
|
1311
|
+
deadline: BigInt("18446744073709551615")
|
|
1312
|
+
}
|
|
1313
|
+
]
|
|
1314
|
+
}) : this.useTickSpacingQuoter ? encodeFunctionData4({
|
|
1254
1315
|
abi: slipstreamMintAbi,
|
|
1255
1316
|
functionName: "mint",
|
|
1256
1317
|
args: [
|
|
@@ -1262,8 +1323,8 @@ var init_dist2 = __esm({
|
|
|
1262
1323
|
tickUpper,
|
|
1263
1324
|
amount0Desired: amount0,
|
|
1264
1325
|
amount1Desired: amount1,
|
|
1265
|
-
amount0Min
|
|
1266
|
-
amount1Min
|
|
1326
|
+
amount0Min,
|
|
1327
|
+
amount1Min,
|
|
1267
1328
|
recipient: params.recipient,
|
|
1268
1329
|
deadline: BigInt("18446744073709551615"),
|
|
1269
1330
|
sqrtPriceX96: 0n
|
|
@@ -1281,8 +1342,8 @@ var init_dist2 = __esm({
|
|
|
1281
1342
|
tickUpper,
|
|
1282
1343
|
amount0Desired: amount0,
|
|
1283
1344
|
amount1Desired: amount1,
|
|
1284
|
-
amount0Min
|
|
1285
|
-
amount1Min
|
|
1345
|
+
amount0Min,
|
|
1346
|
+
amount1Min,
|
|
1286
1347
|
recipient: params.recipient,
|
|
1287
1348
|
deadline: BigInt("18446744073709551615")
|
|
1288
1349
|
}
|
|
@@ -1316,10 +1377,17 @@ var init_dist2 = __esm({
|
|
|
1316
1377
|
const liquidity = params.liquidity;
|
|
1317
1378
|
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1318
1379
|
const deadline = BigInt("18446744073709551615");
|
|
1380
|
+
if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
|
|
1381
|
+
throw DefiError.invalidParam(
|
|
1382
|
+
`[${this.protocolName}] V3 remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Caller must compute expected token outputs from the pool state and apply a slippage tolerance before calling buildRemoveLiquidity.`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
1386
|
+
const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
|
|
1319
1387
|
const decreaseData = encodeFunctionData4({
|
|
1320
1388
|
abi: positionManagerAbi,
|
|
1321
1389
|
functionName: "decreaseLiquidity",
|
|
1322
|
-
args: [{ tokenId, liquidity, amount0Min
|
|
1390
|
+
args: [{ tokenId, liquidity, amount0Min, amount1Min, deadline }]
|
|
1323
1391
|
});
|
|
1324
1392
|
const collectData = encodeFunctionData4({
|
|
1325
1393
|
abi: positionManagerAbi,
|
|
@@ -1617,7 +1685,13 @@ var init_dist2 = __esm({
|
|
|
1617
1685
|
to: this.router,
|
|
1618
1686
|
data,
|
|
1619
1687
|
value: 0n,
|
|
1620
|
-
gas_estimate: 25e4
|
|
1688
|
+
gas_estimate: 25e4,
|
|
1689
|
+
// Same LP-approval requirement as Solidly forks: the V2 router pulls the
|
|
1690
|
+
// LP pair token via transferFrom and reverts at gas ~42k otherwise.
|
|
1691
|
+
// Caller passes --pool so we can target the pair contract for approval.
|
|
1692
|
+
// Discovered live on Uniswap V2 Monad WMON/AUSD 2026-05-08
|
|
1693
|
+
// (failed tx 0x2268659a…09ae → recovered with this fix).
|
|
1694
|
+
...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
|
|
1621
1695
|
};
|
|
1622
1696
|
}
|
|
1623
1697
|
};
|
|
@@ -1672,7 +1746,15 @@ var init_dist2 = __esm({
|
|
|
1672
1746
|
}
|
|
1673
1747
|
async buildSwap(params) {
|
|
1674
1748
|
const deadline = BigInt(params.deadline ?? 18446744073709551615n);
|
|
1675
|
-
const amountOutMinimum =
|
|
1749
|
+
const amountOutMinimum = params.amount_out_min ?? applyMinSlippage(
|
|
1750
|
+
params.slippage,
|
|
1751
|
+
(await this.quote({
|
|
1752
|
+
protocol: this.protocolName,
|
|
1753
|
+
token_in: params.token_in,
|
|
1754
|
+
token_out: params.token_out,
|
|
1755
|
+
amount_in: params.amount_in
|
|
1756
|
+
})).amount_out
|
|
1757
|
+
);
|
|
1676
1758
|
const data = encodeFunctionData32({
|
|
1677
1759
|
abi: abi2,
|
|
1678
1760
|
functionName: "exactInputSingle",
|
|
@@ -1786,7 +1868,12 @@ var init_dist2 = __esm({
|
|
|
1786
1868
|
if (!pm) {
|
|
1787
1869
|
throw new DefiError("CONTRACT_ERROR", "Position manager address not configured");
|
|
1788
1870
|
}
|
|
1789
|
-
const
|
|
1871
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
1872
|
+
const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
|
|
1873
|
+
const slippage = params.slippage ?? defaultSwapSlippage();
|
|
1874
|
+
const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
|
|
1875
|
+
const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
|
|
1876
|
+
const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
|
|
1790
1877
|
let tickLower = params.tick_lower ?? -887220;
|
|
1791
1878
|
let tickUpper = params.tick_upper ?? 887220;
|
|
1792
1879
|
const isSingleSide = rawAmount0 === 0n || rawAmount1 === 0n;
|
|
@@ -1823,11 +1910,11 @@ var init_dist2 = __esm({
|
|
|
1823
1910
|
const data = this.useSingleQuoter ? encodeFunctionData32({
|
|
1824
1911
|
abi: algebraV2PmAbi,
|
|
1825
1912
|
functionName: "mint",
|
|
1826
|
-
args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min
|
|
1913
|
+
args: [{ token0, token1, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
|
|
1827
1914
|
}) : encodeFunctionData32({
|
|
1828
1915
|
abi: algebraIntegralPmAbi,
|
|
1829
1916
|
functionName: "mint",
|
|
1830
|
-
args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min
|
|
1917
|
+
args: [{ token0, token1, deployer: zeroAddress, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min, amount1Min, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
|
|
1831
1918
|
});
|
|
1832
1919
|
const approvals = [];
|
|
1833
1920
|
if (amount0 > 0n) approvals.push({ token: token0, spender: pm, amount: amount0 });
|
|
@@ -1845,12 +1932,19 @@ var init_dist2 = __esm({
|
|
|
1845
1932
|
const pm = this.positionManager;
|
|
1846
1933
|
if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
|
|
1847
1934
|
if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
|
|
1935
|
+
if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
|
|
1936
|
+
throw DefiError.invalidParam(
|
|
1937
|
+
`[${this.protocolName}] remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Compute expected outputs from positions(tokenId) + pool.globalState off-chain and apply tolerance.`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
1941
|
+
const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
|
|
1848
1942
|
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
1849
1943
|
const deadline = BigInt("18446744073709551615");
|
|
1850
1944
|
const decreaseData = encodeFunctionData32({
|
|
1851
1945
|
abi: algebraSharedPmAbi,
|
|
1852
1946
|
functionName: "decreaseLiquidity",
|
|
1853
|
-
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min
|
|
1947
|
+
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
|
|
1854
1948
|
});
|
|
1855
1949
|
const collectData = encodeFunctionData32({
|
|
1856
1950
|
abi: algebraSharedPmAbi,
|
|
@@ -1877,6 +1971,7 @@ var init_dist2 = __esm({
|
|
|
1877
1971
|
BalancerV3Adapter = class {
|
|
1878
1972
|
protocolName;
|
|
1879
1973
|
router;
|
|
1974
|
+
pool;
|
|
1880
1975
|
constructor(entry, _rpcUrl) {
|
|
1881
1976
|
this.protocolName = entry.name;
|
|
1882
1977
|
const router = entry.contracts?.["router"];
|
|
@@ -1884,19 +1979,29 @@ var init_dist2 = __esm({
|
|
|
1884
1979
|
throw new DefiError("CONTRACT_ERROR", "Missing 'router' contract");
|
|
1885
1980
|
}
|
|
1886
1981
|
this.router = router;
|
|
1982
|
+
this.pool = entry.contracts?.["pool"];
|
|
1887
1983
|
}
|
|
1888
1984
|
name() {
|
|
1889
1985
|
return this.protocolName;
|
|
1890
1986
|
}
|
|
1891
1987
|
async buildSwap(params) {
|
|
1892
|
-
|
|
1988
|
+
if (params.amount_out_min === void 0) {
|
|
1989
|
+
throw DefiError.invalidParam(
|
|
1990
|
+
`[${this.protocolName}] buildSwap requires amount_out_min for slippage protection (SSOT 7.3) \u2014 Balancer V3 has no on-chain quoter wired in this adapter. Compute the floor off-chain (e.g. via the Vault's static-call simulation) and pass it explicitly.`
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
if (!this.pool) {
|
|
1994
|
+
throw DefiError.invalidParam(
|
|
1995
|
+
`[${this.protocolName}] buildSwap requires a pool address. Register the pool under [protocol.contracts] as \`pool = "0x..."\` in the protocol's TOML config. Multi-pool routing is intentionally not implemented in this adapter \u2014 for that, quote against the Balancer V3 BatchRouter off-chain and route via a different surface.`
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
const minAmountOut = params.amount_out_min;
|
|
1893
1999
|
const deadline = BigInt(params.deadline ?? 18446744073709551615n);
|
|
1894
2000
|
const data = encodeFunctionData42({
|
|
1895
2001
|
abi: abi3,
|
|
1896
2002
|
functionName: "swapSingleTokenExactIn",
|
|
1897
2003
|
args: [
|
|
1898
|
-
|
|
1899
|
-
// TODO: resolve pool from registry
|
|
2004
|
+
this.pool,
|
|
1900
2005
|
params.token_in,
|
|
1901
2006
|
params.token_out,
|
|
1902
2007
|
params.amount_in,
|
|
@@ -1907,7 +2012,7 @@ var init_dist2 = __esm({
|
|
|
1907
2012
|
]
|
|
1908
2013
|
});
|
|
1909
2014
|
return {
|
|
1910
|
-
description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3`,
|
|
2015
|
+
description: `[${this.protocolName}] Swap ${params.amount_in} via Balancer V3 pool ${this.pool}`,
|
|
1911
2016
|
to: this.router,
|
|
1912
2017
|
data,
|
|
1913
2018
|
value: 0n,
|
|
@@ -1927,8 +2032,8 @@ var init_dist2 = __esm({
|
|
|
1927
2032
|
poolAbi = parseAbi5([
|
|
1928
2033
|
"function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256)",
|
|
1929
2034
|
"function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256)",
|
|
1930
|
-
"function add_liquidity(uint256[
|
|
1931
|
-
"function remove_liquidity(uint256 amount, uint256[
|
|
2035
|
+
"function add_liquidity(uint256[] amounts, uint256 min_mint_amount) external returns (uint256)",
|
|
2036
|
+
"function remove_liquidity(uint256 amount, uint256[] min_amounts) external returns (uint256[])"
|
|
1932
2037
|
]);
|
|
1933
2038
|
CurveStableSwapAdapter = class {
|
|
1934
2039
|
protocolName;
|
|
@@ -1963,28 +2068,42 @@ var init_dist2 = __esm({
|
|
|
1963
2068
|
throw DefiError.unsupported(`[${this.protocolName}] quote requires RPC connection`);
|
|
1964
2069
|
}
|
|
1965
2070
|
async buildAddLiquidity(params) {
|
|
2071
|
+
if (!params.pool) {
|
|
2072
|
+
throw DefiError.invalidParam(
|
|
2073
|
+
`[${this.protocolName}] Curve add_liquidity needs --pool <address>. The router does not proxy this call; it lives on the pool itself.`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
1966
2076
|
const data = encodeFunctionData5({
|
|
1967
2077
|
abi: poolAbi,
|
|
1968
2078
|
functionName: "add_liquidity",
|
|
1969
2079
|
args: [[params.amount_a, params.amount_b], 0n]
|
|
1970
2080
|
});
|
|
2081
|
+
const approvals = [];
|
|
2082
|
+
if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: params.pool, amount: params.amount_a });
|
|
2083
|
+
if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: params.pool, amount: params.amount_b });
|
|
1971
2084
|
return {
|
|
1972
|
-
description: `[${this.protocolName}] Curve add liquidity`,
|
|
1973
|
-
to:
|
|
2085
|
+
description: `[${this.protocolName}] Curve add liquidity to ${params.pool}`,
|
|
2086
|
+
to: params.pool,
|
|
1974
2087
|
data,
|
|
1975
2088
|
value: 0n,
|
|
1976
|
-
gas_estimate: 4e5
|
|
2089
|
+
gas_estimate: 4e5,
|
|
2090
|
+
approvals
|
|
1977
2091
|
};
|
|
1978
2092
|
}
|
|
1979
2093
|
async buildRemoveLiquidity(params) {
|
|
2094
|
+
if (!params.pool) {
|
|
2095
|
+
throw DefiError.invalidParam(
|
|
2096
|
+
`[${this.protocolName}] Curve remove_liquidity needs --pool <address>. The router does not proxy this call.`
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
1980
2099
|
const data = encodeFunctionData5({
|
|
1981
2100
|
abi: poolAbi,
|
|
1982
2101
|
functionName: "remove_liquidity",
|
|
1983
2102
|
args: [params.liquidity, [0n, 0n]]
|
|
1984
2103
|
});
|
|
1985
2104
|
return {
|
|
1986
|
-
description: `[${this.protocolName}] Curve remove liquidity`,
|
|
1987
|
-
to:
|
|
2105
|
+
description: `[${this.protocolName}] Curve remove liquidity from ${params.pool}`,
|
|
2106
|
+
to: params.pool,
|
|
1988
2107
|
data,
|
|
1989
2108
|
value: 0n,
|
|
1990
2109
|
gas_estimate: 35e4
|
|
@@ -2150,7 +2269,12 @@ var init_dist2 = __esm({
|
|
|
2150
2269
|
to: this.router,
|
|
2151
2270
|
data,
|
|
2152
2271
|
value: 0n,
|
|
2153
|
-
gas_estimate: 3e5
|
|
2272
|
+
gas_estimate: 3e5,
|
|
2273
|
+
// The router pulls the LP token via transferFrom; without this approval
|
|
2274
|
+
// the tx reverts at gas ~42k. Caller must pass --pool so we know which
|
|
2275
|
+
// LP pair to approve. Discovered live on Aerodrome USDC/USDT 2026-05-08
|
|
2276
|
+
// (failed tx 0x6d052e0a…3298 → recovered with manual approve 0xa126fc3a).
|
|
2277
|
+
...params.pool ? { approvals: [{ token: params.pool, spender: this.router, amount: params.liquidity }] } : {}
|
|
2154
2278
|
};
|
|
2155
2279
|
}
|
|
2156
2280
|
};
|
|
@@ -2197,6 +2321,11 @@ var init_dist2 = __esm({
|
|
|
2197
2321
|
return this.protocolName;
|
|
2198
2322
|
}
|
|
2199
2323
|
async buildSwap(params) {
|
|
2324
|
+
if (params.amount_out_min === void 0) {
|
|
2325
|
+
throw DefiError.invalidParam(
|
|
2326
|
+
`[${this.protocolName}] buildSwap requires amount_out_min for slippage protection (SSOT 7.3) \u2014 Thena CL has no on-chain quoter. Compute the floor off-chain (e.g. via the router's static-call simulation) and pass it explicitly.`
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2200
2329
|
const data = encodeFunctionData7({
|
|
2201
2330
|
abi: thenaRouterAbi,
|
|
2202
2331
|
functionName: "exactInputSingle",
|
|
@@ -2207,7 +2336,7 @@ var init_dist2 = __esm({
|
|
|
2207
2336
|
recipient: params.recipient,
|
|
2208
2337
|
deadline: BigInt(params.deadline ?? 18446744073709551615n),
|
|
2209
2338
|
amountIn: params.amount_in,
|
|
2210
|
-
amountOutMinimum:
|
|
2339
|
+
amountOutMinimum: params.amount_out_min,
|
|
2211
2340
|
sqrtPriceLimitX96: 0n
|
|
2212
2341
|
}]
|
|
2213
2342
|
});
|
|
@@ -2227,7 +2356,12 @@ var init_dist2 = __esm({
|
|
|
2227
2356
|
const pm = this.positionManager;
|
|
2228
2357
|
if (!pm) throw new DefiError("CONTRACT_ERROR", "Position manager not configured");
|
|
2229
2358
|
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
|
|
2230
|
-
const
|
|
2359
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
2360
|
+
const [token0, token1, rawAmount0, rawAmount1] = isAFirst ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
|
|
2361
|
+
const slippage = params.slippage ?? defaultSwapSlippage();
|
|
2362
|
+
const minA = params.amount_a_min ?? applyMinSlippage(slippage, params.amount_a);
|
|
2363
|
+
const minB = params.amount_b_min ?? applyMinSlippage(slippage, params.amount_b);
|
|
2364
|
+
const [amount0Min, amount1Min] = isAFirst ? [minA, minB] : [minB, minA];
|
|
2231
2365
|
const client = createPublicClient42({ transport: http42(this.rpcUrl) });
|
|
2232
2366
|
const poolAddr = params.pool;
|
|
2233
2367
|
let tickSpacing = this.defaultTickSpacing;
|
|
@@ -2242,7 +2376,7 @@ var init_dist2 = __esm({
|
|
|
2242
2376
|
functionName: "getPool",
|
|
2243
2377
|
args: [token0, token1, tickSpacing]
|
|
2244
2378
|
});
|
|
2245
|
-
if (pool ===
|
|
2379
|
+
if (pool === zeroAddress2) throw new DefiError("CONTRACT_ERROR", "Pool not found");
|
|
2246
2380
|
}
|
|
2247
2381
|
if (pool) {
|
|
2248
2382
|
const [slot0, ts] = await Promise.all([
|
|
@@ -2285,8 +2419,8 @@ var init_dist2 = __esm({
|
|
|
2285
2419
|
tickUpper,
|
|
2286
2420
|
amount0Desired: rawAmount0,
|
|
2287
2421
|
amount1Desired: rawAmount1,
|
|
2288
|
-
amount0Min
|
|
2289
|
-
amount1Min
|
|
2422
|
+
amount0Min,
|
|
2423
|
+
amount1Min,
|
|
2290
2424
|
recipient: params.recipient,
|
|
2291
2425
|
deadline: BigInt("18446744073709551615"),
|
|
2292
2426
|
sqrtPriceX96: 0n
|
|
@@ -2308,12 +2442,19 @@ var init_dist2 = __esm({
|
|
|
2308
2442
|
const pm = this.positionManager;
|
|
2309
2443
|
if (!pm) throw DefiError.contractError(`[${this.protocolName}] Missing 'position_manager'`);
|
|
2310
2444
|
if (!params.token_id) throw DefiError.invalidParam(`[${this.protocolName}] V3 remove_liquidity requires --token-id`);
|
|
2445
|
+
if (params.amount_a_min === void 0 || params.amount_b_min === void 0) {
|
|
2446
|
+
throw DefiError.invalidParam(
|
|
2447
|
+
`[${this.protocolName}] remove_liquidity requires amount_a_min and amount_b_min for slippage protection (SSOT 7.3). Compute expected outputs from positions(tokenId) + pool.slot0 off-chain and apply tolerance.`
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
const isAFirst = params.token_a.toLowerCase() < params.token_b.toLowerCase();
|
|
2451
|
+
const [amount0Min, amount1Min] = isAFirst ? [params.amount_a_min, params.amount_b_min] : [params.amount_b_min, params.amount_a_min];
|
|
2311
2452
|
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
2312
2453
|
const deadline = BigInt("18446744073709551615");
|
|
2313
2454
|
const decreaseData = encodeFunctionData7({
|
|
2314
2455
|
abi: thenaPmAbi,
|
|
2315
2456
|
functionName: "decreaseLiquidity",
|
|
2316
|
-
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min
|
|
2457
|
+
args: [{ tokenId: params.token_id, liquidity: params.liquidity, amount0Min, amount1Min, deadline }]
|
|
2317
2458
|
});
|
|
2318
2459
|
const collectData = encodeFunctionData7({
|
|
2319
2460
|
abi: thenaPmAbi,
|
|
@@ -2380,8 +2521,8 @@ var init_dist2 = __esm({
|
|
|
2380
2521
|
const ve = entry.contracts?.["ve_token"];
|
|
2381
2522
|
if (!ve) throw new DefiError("CONTRACT_ERROR", "Missing 've_token' contract");
|
|
2382
2523
|
this.veToken = ve;
|
|
2383
|
-
this.voter = entry.contracts?.["voter"] ??
|
|
2384
|
-
this.positionManager = entry.contracts?.["position_manager"] ??
|
|
2524
|
+
this.voter = entry.contracts?.["voter"] ?? zeroAddress3;
|
|
2525
|
+
this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress3;
|
|
2385
2526
|
this.poolFactory = entry.contracts?.["pool_factory"];
|
|
2386
2527
|
this.rpcUrl = rpcUrl;
|
|
2387
2528
|
}
|
|
@@ -2423,7 +2564,7 @@ var init_dist2 = __esm({
|
|
|
2423
2564
|
]);
|
|
2424
2565
|
}
|
|
2425
2566
|
const poolAddressResults = await multicallRead(this.rpcUrl, poolAddressCalls);
|
|
2426
|
-
const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !==
|
|
2567
|
+
const pools = poolAddressResults.map((r) => decodeAddress(r)).filter((a) => a !== null && a !== zeroAddress3);
|
|
2427
2568
|
if (pools.length === 0) return [];
|
|
2428
2569
|
const gaugeCalls = pools.map((pool) => [
|
|
2429
2570
|
this.gaugeManager,
|
|
@@ -2433,7 +2574,7 @@ var init_dist2 = __esm({
|
|
|
2433
2574
|
const gaugedPools = [];
|
|
2434
2575
|
for (let i = 0; i < pools.length; i++) {
|
|
2435
2576
|
const gauge = decodeAddress(gaugeResults[i] ?? null);
|
|
2436
|
-
if (gauge && gauge !==
|
|
2577
|
+
if (gauge && gauge !== zeroAddress3) {
|
|
2437
2578
|
gaugedPools.push({ pool: pools[i], gauge });
|
|
2438
2579
|
}
|
|
2439
2580
|
}
|
|
@@ -2448,8 +2589,8 @@ var init_dist2 = __esm({
|
|
|
2448
2589
|
for (let i = 0; i < gaugedPools.length; i++) {
|
|
2449
2590
|
const t0 = decodeAddress(tokenResults[i * 2] ?? null);
|
|
2450
2591
|
const t1 = decodeAddress(tokenResults[i * 2 + 1] ?? null);
|
|
2451
|
-
if (t0 && t0 !==
|
|
2452
|
-
if (t1 && t1 !==
|
|
2592
|
+
if (t0 && t0 !== zeroAddress3) tokenAddrs.add(t0);
|
|
2593
|
+
if (t1 && t1 !== zeroAddress3) tokenAddrs.add(t1);
|
|
2453
2594
|
}
|
|
2454
2595
|
const uniqueTokens = Array.from(tokenAddrs);
|
|
2455
2596
|
const symbolCalls = uniqueTokens.map((t) => [
|
|
@@ -2488,7 +2629,7 @@ var init_dist2 = __esm({
|
|
|
2488
2629
|
functionName: "gauges",
|
|
2489
2630
|
args: [pool]
|
|
2490
2631
|
});
|
|
2491
|
-
if (gauge ===
|
|
2632
|
+
if (gauge === zeroAddress3) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
|
|
2492
2633
|
return gauge;
|
|
2493
2634
|
}
|
|
2494
2635
|
// ─── CL Gauge: NFT Deposit/Withdraw ──────────────────────────
|
|
@@ -2650,7 +2791,7 @@ var init_dist2 = __esm({
|
|
|
2650
2791
|
params.amount_in,
|
|
2651
2792
|
minToAmount,
|
|
2652
2793
|
params.recipient,
|
|
2653
|
-
|
|
2794
|
+
zeroAddress4
|
|
2654
2795
|
]
|
|
2655
2796
|
});
|
|
2656
2797
|
return {
|
|
@@ -2875,7 +3016,7 @@ var init_dist2 = __esm({
|
|
|
2875
3016
|
poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
|
|
2876
3017
|
}
|
|
2877
3018
|
const poolResults = await multicallRead(this.rpcUrl, poolCalls);
|
|
2878
|
-
pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !==
|
|
3019
|
+
pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
|
|
2879
3020
|
} else {
|
|
2880
3021
|
const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
|
|
2881
3022
|
const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
|
|
@@ -2900,17 +3041,17 @@ var init_dist2 = __esm({
|
|
|
2900
3041
|
const pairCalls = [];
|
|
2901
3042
|
for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
|
|
2902
3043
|
const pairResults = await multicallRead(this.rpcUrl, pairCalls);
|
|
2903
|
-
pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !==
|
|
3044
|
+
pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress5);
|
|
2904
3045
|
}
|
|
2905
3046
|
if (pairs.length === 0) return;
|
|
2906
3047
|
const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
|
|
2907
3048
|
let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
|
|
2908
|
-
const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) ===
|
|
3049
|
+
const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
|
|
2909
3050
|
if (allNull) {
|
|
2910
3051
|
const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
|
|
2911
3052
|
gaugeResults = await multicallRead(this.rpcUrl, fb1);
|
|
2912
3053
|
}
|
|
2913
|
-
const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) ===
|
|
3054
|
+
const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
|
|
2914
3055
|
if (allNull2) {
|
|
2915
3056
|
const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
|
|
2916
3057
|
gaugeResults = await multicallRead(this.rpcUrl, fb2);
|
|
@@ -2918,7 +3059,7 @@ var init_dist2 = __esm({
|
|
|
2918
3059
|
const gaugedPairs = [];
|
|
2919
3060
|
for (let i = 0; i < pairs.length; i++) {
|
|
2920
3061
|
const gauge = decodeAddress2(gaugeResults[i] ?? null);
|
|
2921
|
-
if (gauge && gauge !==
|
|
3062
|
+
if (gauge && gauge !== zeroAddress5) {
|
|
2922
3063
|
gaugedPairs.push({ pair: pairs[i], gauge });
|
|
2923
3064
|
}
|
|
2924
3065
|
}
|
|
@@ -2934,8 +3075,8 @@ var init_dist2 = __esm({
|
|
|
2934
3075
|
for (let i = 0; i < gaugedPairs.length; i++) {
|
|
2935
3076
|
const t0 = decodeAddress2(metaResults[i * 3] ?? null);
|
|
2936
3077
|
const t1 = decodeAddress2(metaResults[i * 3 + 1] ?? null);
|
|
2937
|
-
if (t0 && t0 !==
|
|
2938
|
-
if (t1 && t1 !==
|
|
3078
|
+
if (t0 && t0 !== zeroAddress5) tokenAddrs.add(t0);
|
|
3079
|
+
if (t1 && t1 !== zeroAddress5) tokenAddrs.add(t1);
|
|
2939
3080
|
}
|
|
2940
3081
|
const uniqueTokens = Array.from(tokenAddrs);
|
|
2941
3082
|
const symbolCalls = uniqueTokens.map((t) => [
|
|
@@ -3025,7 +3166,7 @@ var init_dist2 = __esm({
|
|
|
3025
3166
|
const candidatePools = [];
|
|
3026
3167
|
for (let i = 0; i < getPoolCalls.length; i++) {
|
|
3027
3168
|
const pool = decodeAddress2(getPoolResults[i] ?? null);
|
|
3028
|
-
if (pool && pool !==
|
|
3169
|
+
if (pool && pool !== zeroAddress5) {
|
|
3029
3170
|
const { pairIdx, tickSpacing } = callMeta[i];
|
|
3030
3171
|
const [tokenA, tokenB] = pairs[pairIdx];
|
|
3031
3172
|
candidatePools.push({ pool, tokenA, tokenB, tickSpacing });
|
|
@@ -3037,7 +3178,7 @@ var init_dist2 = __esm({
|
|
|
3037
3178
|
encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pool] })
|
|
3038
3179
|
]);
|
|
3039
3180
|
let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
|
|
3040
|
-
const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) ===
|
|
3181
|
+
const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress5 || decodeAddress2(r) === null);
|
|
3041
3182
|
if (allNull) {
|
|
3042
3183
|
const fallbackCalls = candidatePools.map(({ pool }) => [
|
|
3043
3184
|
this.voter,
|
|
@@ -3048,7 +3189,7 @@ var init_dist2 = __esm({
|
|
|
3048
3189
|
const gaugedCL = [];
|
|
3049
3190
|
for (let i = 0; i < candidatePools.length; i++) {
|
|
3050
3191
|
const gauge = decodeAddress2(gaugeResults[i] ?? null);
|
|
3051
|
-
if (gauge && gauge !==
|
|
3192
|
+
if (gauge && gauge !== zeroAddress5) {
|
|
3052
3193
|
gaugedCL.push({ ...candidatePools[i], gauge });
|
|
3053
3194
|
}
|
|
3054
3195
|
}
|
|
@@ -3078,8 +3219,8 @@ var init_dist2 = __esm({
|
|
|
3078
3219
|
const { pool, gauge, tokenA, tokenB, tickSpacing } = gaugedCL[i];
|
|
3079
3220
|
const rawT0 = decodeAddress2(poolTokenResults[i * 2] ?? null);
|
|
3080
3221
|
const rawT1 = decodeAddress2(poolTokenResults[i * 2 + 1] ?? null);
|
|
3081
|
-
const t0 = rawT0 && rawT0 !==
|
|
3082
|
-
const t1 = rawT1 && rawT1 !==
|
|
3222
|
+
const t0 = rawT0 && rawT0 !== zeroAddress5 ? rawT0 : tokenA;
|
|
3223
|
+
const t1 = rawT1 && rawT1 !== zeroAddress5 ? rawT1 : tokenB;
|
|
3083
3224
|
out.push({
|
|
3084
3225
|
pool,
|
|
3085
3226
|
gauge,
|
|
@@ -3181,7 +3322,7 @@ var init_dist2 = __esm({
|
|
|
3181
3322
|
functionName: fn,
|
|
3182
3323
|
args: [pool]
|
|
3183
3324
|
});
|
|
3184
|
-
if (gauge !==
|
|
3325
|
+
if (gauge !== zeroAddress5) return gauge;
|
|
3185
3326
|
} catch {
|
|
3186
3327
|
}
|
|
3187
3328
|
}
|
|
@@ -3235,7 +3376,7 @@ var init_dist2 = __esm({
|
|
|
3235
3376
|
abi: gaugeAbi,
|
|
3236
3377
|
functionName: "rewardToken"
|
|
3237
3378
|
});
|
|
3238
|
-
if (rt !==
|
|
3379
|
+
if (rt !== zeroAddress5) return { tokens: [rt], multiToken: false };
|
|
3239
3380
|
} catch {
|
|
3240
3381
|
}
|
|
3241
3382
|
return { tokens: [], multiToken: false };
|
|
@@ -3245,7 +3386,7 @@ var init_dist2 = __esm({
|
|
|
3245
3386
|
const data = encodeFunctionData10({
|
|
3246
3387
|
abi: gaugeAbi,
|
|
3247
3388
|
functionName: "getReward",
|
|
3248
|
-
args: [account ??
|
|
3389
|
+
args: [account ?? zeroAddress5]
|
|
3249
3390
|
});
|
|
3250
3391
|
return { description: `[${this.protocolName}] Claim gauge rewards`, to: gauge, data, value: 0n, gas_estimate: 2e5 };
|
|
3251
3392
|
}
|
|
@@ -3469,7 +3610,7 @@ var init_dist2 = __esm({
|
|
|
3469
3610
|
functionName: "earned",
|
|
3470
3611
|
args: [user]
|
|
3471
3612
|
});
|
|
3472
|
-
results.push({ token:
|
|
3613
|
+
results.push({ token: zeroAddress5, symbol: "unknown", amount: earned });
|
|
3473
3614
|
} catch {
|
|
3474
3615
|
}
|
|
3475
3616
|
}
|
|
@@ -3831,20 +3972,47 @@ var init_dist2 = __esm({
|
|
|
3831
3972
|
/**
|
|
3832
3973
|
* Build an addLiquidity transaction for a Liquidity Book pair.
|
|
3833
3974
|
* Distributes tokenX/tokenY uniformly across active bin ± numBins.
|
|
3975
|
+
*
|
|
3976
|
+
* The LB pair stores tokenX/tokenY in the order set at factory creation,
|
|
3977
|
+
* which is **not** address-sorted. Callers may pass tokens in either order;
|
|
3978
|
+
* we always re-align with the pool's `getTokenX/getTokenY` (and swap amounts
|
|
3979
|
+
* accordingly) so the router's `if (tokenX != pair.tokenX) revert` path is
|
|
3980
|
+
* never hit.
|
|
3834
3981
|
*/
|
|
3835
3982
|
async buildAddLiquidity(params) {
|
|
3836
3983
|
const numBins = params.numBins ?? 5;
|
|
3837
3984
|
const deadline = params.deadline ?? BigInt("18446744073709551615");
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3985
|
+
const rpcUrl = this.requireRpc();
|
|
3986
|
+
const client = createPublicClient8({ transport: http8(rpcUrl) });
|
|
3987
|
+
const [poolTokenX, poolTokenY, onChainActiveId] = await Promise.all([
|
|
3988
|
+
client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
|
|
3989
|
+
client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" }),
|
|
3990
|
+
params.activeIdDesired === void 0 ? client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getActiveId" }) : Promise.resolve(params.activeIdDesired)
|
|
3991
|
+
]);
|
|
3992
|
+
const activeIdDesired = onChainActiveId;
|
|
3993
|
+
const inX = params.tokenX.toLowerCase();
|
|
3994
|
+
const inY = params.tokenY.toLowerCase();
|
|
3995
|
+
const poolX = poolTokenX.toLowerCase();
|
|
3996
|
+
const poolY = poolTokenY.toLowerCase();
|
|
3997
|
+
let tokenX;
|
|
3998
|
+
let tokenY;
|
|
3999
|
+
let amountX;
|
|
4000
|
+
let amountY;
|
|
4001
|
+
if (inX === poolX && inY === poolY) {
|
|
4002
|
+
tokenX = params.tokenX;
|
|
4003
|
+
tokenY = params.tokenY;
|
|
4004
|
+
amountX = params.amountX;
|
|
4005
|
+
amountY = params.amountY;
|
|
4006
|
+
} else if (inX === poolY && inY === poolX) {
|
|
4007
|
+
tokenX = poolTokenX;
|
|
4008
|
+
tokenY = poolTokenY;
|
|
4009
|
+
amountX = params.amountY;
|
|
4010
|
+
amountY = params.amountX;
|
|
4011
|
+
} else {
|
|
4012
|
+
throw new DefiError(
|
|
4013
|
+
"CONTRACT_ERROR",
|
|
4014
|
+
`[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
|
|
4015
|
+
);
|
|
3848
4016
|
}
|
|
3849
4017
|
const deltaIds = [];
|
|
3850
4018
|
for (let d = -numBins; d <= numBins; d++) {
|
|
@@ -3856,11 +4024,11 @@ var init_dist2 = __esm({
|
|
|
3856
4024
|
functionName: "addLiquidity",
|
|
3857
4025
|
args: [
|
|
3858
4026
|
{
|
|
3859
|
-
tokenX
|
|
3860
|
-
tokenY
|
|
4027
|
+
tokenX,
|
|
4028
|
+
tokenY,
|
|
3861
4029
|
binStep: BigInt(params.binStep),
|
|
3862
|
-
amountX
|
|
3863
|
-
amountY
|
|
4030
|
+
amountX,
|
|
4031
|
+
amountY,
|
|
3864
4032
|
amountXMin: 0n,
|
|
3865
4033
|
amountYMin: 0n,
|
|
3866
4034
|
activeIdDesired: BigInt(activeIdDesired),
|
|
@@ -3875,31 +4043,63 @@ var init_dist2 = __esm({
|
|
|
3875
4043
|
]
|
|
3876
4044
|
});
|
|
3877
4045
|
return {
|
|
3878
|
-
description: `[${this.protocolName}] LB addLiquidity ${
|
|
4046
|
+
description: `[${this.protocolName}] LB addLiquidity ${amountX} tokenX + ${amountY} tokenY across ${deltaIds.length} bins`,
|
|
3879
4047
|
to: this.lbRouter,
|
|
3880
4048
|
data,
|
|
3881
4049
|
value: 0n,
|
|
3882
4050
|
gas_estimate: 8e5,
|
|
3883
4051
|
approvals: [
|
|
3884
|
-
{ token:
|
|
3885
|
-
{ token:
|
|
4052
|
+
{ token: tokenX, spender: this.lbRouter, amount: amountX },
|
|
4053
|
+
{ token: tokenY, spender: this.lbRouter, amount: amountY }
|
|
3886
4054
|
]
|
|
3887
4055
|
};
|
|
3888
4056
|
}
|
|
3889
4057
|
/**
|
|
3890
4058
|
* Build a removeLiquidity transaction for specific LB bins.
|
|
4059
|
+
*
|
|
4060
|
+
* When `pool` is supplied, the adapter realigns tokenX/tokenY (and
|
|
4061
|
+
* amountXMin/amountYMin) to the pool's actual ordering. Otherwise it trusts
|
|
4062
|
+
* the caller — the router will revert if the order is wrong.
|
|
3891
4063
|
*/
|
|
3892
4064
|
async buildRemoveLiquidity(params) {
|
|
3893
4065
|
const deadline = params.deadline ?? BigInt("18446744073709551615");
|
|
4066
|
+
let tokenX = params.tokenX;
|
|
4067
|
+
let tokenY = params.tokenY;
|
|
4068
|
+
let amountXMin = params.amountXMin ?? 0n;
|
|
4069
|
+
let amountYMin = params.amountYMin ?? 0n;
|
|
4070
|
+
if (params.pool) {
|
|
4071
|
+
const rpcUrl = this.requireRpc();
|
|
4072
|
+
const client = createPublicClient8({ transport: http8(rpcUrl) });
|
|
4073
|
+
const [poolTokenX, poolTokenY] = await Promise.all([
|
|
4074
|
+
client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenX" }),
|
|
4075
|
+
client.readContract({ address: params.pool, abi: lbPairAbi, functionName: "getTokenY" })
|
|
4076
|
+
]);
|
|
4077
|
+
const inX = params.tokenX.toLowerCase();
|
|
4078
|
+
const inY = params.tokenY.toLowerCase();
|
|
4079
|
+
const poolX = poolTokenX.toLowerCase();
|
|
4080
|
+
const poolY = poolTokenY.toLowerCase();
|
|
4081
|
+
if (inX === poolY && inY === poolX) {
|
|
4082
|
+
tokenX = poolTokenX;
|
|
4083
|
+
tokenY = poolTokenY;
|
|
4084
|
+
const x = amountXMin;
|
|
4085
|
+
amountXMin = amountYMin;
|
|
4086
|
+
amountYMin = x;
|
|
4087
|
+
} else if (!(inX === poolX && inY === poolY)) {
|
|
4088
|
+
throw new DefiError(
|
|
4089
|
+
"CONTRACT_ERROR",
|
|
4090
|
+
`[${this.protocolName}] tokenX/tokenY ${params.tokenX}/${params.tokenY} do not match pool ${params.pool} (${poolTokenX}/${poolTokenY})`
|
|
4091
|
+
);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
3894
4094
|
const data = encodeFunctionData12({
|
|
3895
4095
|
abi: lbRouterAbi,
|
|
3896
4096
|
functionName: "removeLiquidity",
|
|
3897
4097
|
args: [
|
|
3898
|
-
|
|
3899
|
-
|
|
4098
|
+
tokenX,
|
|
4099
|
+
tokenY,
|
|
3900
4100
|
params.binStep,
|
|
3901
|
-
|
|
3902
|
-
|
|
4101
|
+
amountXMin,
|
|
4102
|
+
amountYMin,
|
|
3903
4103
|
params.binIds.map(BigInt),
|
|
3904
4104
|
params.amounts,
|
|
3905
4105
|
params.recipient,
|
|
@@ -4809,7 +5009,7 @@ var init_dist2 = __esm({
|
|
|
4809
5009
|
const poolSet = /* @__PURE__ */ new Set();
|
|
4810
5010
|
for (const data of poolResults) {
|
|
4811
5011
|
const addr = decodeAddress3(data);
|
|
4812
|
-
if (addr && addr !==
|
|
5012
|
+
if (addr && addr !== zeroAddress6) {
|
|
4813
5013
|
poolSet.add(addr.toLowerCase());
|
|
4814
5014
|
}
|
|
4815
5015
|
}
|
|
@@ -4974,6 +5174,34 @@ var init_dist2 = __esm({
|
|
|
4974
5174
|
}
|
|
4975
5175
|
return apr;
|
|
4976
5176
|
}
|
|
5177
|
+
/**
|
|
5178
|
+
* Snapshot of all NEST gauged pools from the off-chain blaze API. The
|
|
5179
|
+
* on-chain ve(3,3) gauges return `rewardRate=0` (emissions are credited
|
|
5180
|
+
* off-chain via signed claim tickets), so the on-chain Solidly gauge
|
|
5181
|
+
* reader cannot surface real emission APR. This API is the canonical
|
|
5182
|
+
* read source — used by `lp discover` to expose non-zero NEST yields.
|
|
5183
|
+
*/
|
|
5184
|
+
async getLiquidityPools() {
|
|
5185
|
+
const raw = await this.fetchJson("/liquidity-pools");
|
|
5186
|
+
const out = [];
|
|
5187
|
+
for (const p of raw) {
|
|
5188
|
+
const t0 = p.token0?.basetoken;
|
|
5189
|
+
const t1 = p.token1?.basetoken;
|
|
5190
|
+
if (!t0?.address || !t1?.address) continue;
|
|
5191
|
+
out.push({
|
|
5192
|
+
pool: p.id,
|
|
5193
|
+
gauge: p.gauge ?? null,
|
|
5194
|
+
token0: { address: t0.address, symbol: t0.symbol ?? "?" },
|
|
5195
|
+
token1: { address: t1.address, symbol: t1.symbol ?? "?" },
|
|
5196
|
+
tvlUSD: Number(p.tvlUSD ?? 0),
|
|
5197
|
+
aprPercent: Number(p.apr ?? 0),
|
|
5198
|
+
curEpochEmissionRewardsUSD: Number(p.curEpochEmissionRewardsUSD ?? 0),
|
|
5199
|
+
poolType: p.poolType,
|
|
5200
|
+
isStable: p.isStable
|
|
5201
|
+
});
|
|
5202
|
+
}
|
|
5203
|
+
return out;
|
|
5204
|
+
}
|
|
4977
5205
|
/** Pending NEST emissions as IGauge-compatible RewardInfo[] */
|
|
4978
5206
|
async getPendingRewards(user) {
|
|
4979
5207
|
const status = await this.getClaimStatus(user);
|
|
@@ -5129,6 +5357,16 @@ var init_dist2 = __esm({
|
|
|
5129
5357
|
"function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external",
|
|
5130
5358
|
"function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
|
|
5131
5359
|
"function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
|
|
5360
|
+
// Toggles required to actually borrow against an isolation-mode reserve
|
|
5361
|
+
// (https://aave.com/docs/aave-v3/smart-contracts/pool):
|
|
5362
|
+
// - setUserUseReserveAsCollateral: an isolation reserve can be enabled
|
|
5363
|
+
// only if no other asset is enabled; LTV=0 reserves can never be
|
|
5364
|
+
// enabled; disable reverts if the resulting HF would drop below the
|
|
5365
|
+
// liquidation threshold.
|
|
5366
|
+
// - setUserEMode: reverts if the user is borrowing a non-eMode-
|
|
5367
|
+
// compatible asset, or if the change would push HF below threshold.
|
|
5368
|
+
"function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external",
|
|
5369
|
+
"function setUserEMode(uint8 categoryId) external",
|
|
5132
5370
|
"function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
|
|
5133
5371
|
"function getReservesList() external view returns (address[])",
|
|
5134
5372
|
"function getReserveData(address asset) external view returns (uint256 configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt)"
|
|
@@ -5254,6 +5492,34 @@ var init_dist2 = __esm({
|
|
|
5254
5492
|
gas_estimate: 25e4
|
|
5255
5493
|
};
|
|
5256
5494
|
}
|
|
5495
|
+
async buildSetUseReserveAsCollateral(asset, useAsCollateral) {
|
|
5496
|
+
const data = encodeFunctionData14({
|
|
5497
|
+
abi: POOL_ABI,
|
|
5498
|
+
functionName: "setUserUseReserveAsCollateral",
|
|
5499
|
+
args: [asset, useAsCollateral]
|
|
5500
|
+
});
|
|
5501
|
+
return {
|
|
5502
|
+
description: `[${this.protocolName}] ${useAsCollateral ? "Enable" : "Disable"} ${asset} as collateral`,
|
|
5503
|
+
to: this.pool,
|
|
5504
|
+
data,
|
|
5505
|
+
value: 0n,
|
|
5506
|
+
gas_estimate: 1e5
|
|
5507
|
+
};
|
|
5508
|
+
}
|
|
5509
|
+
async buildSetEMode(categoryId) {
|
|
5510
|
+
const data = encodeFunctionData14({
|
|
5511
|
+
abi: POOL_ABI,
|
|
5512
|
+
functionName: "setUserEMode",
|
|
5513
|
+
args: [categoryId]
|
|
5514
|
+
});
|
|
5515
|
+
return {
|
|
5516
|
+
description: `[${this.protocolName}] Set eMode category to ${categoryId}${categoryId === 0 ? " (opt out)" : ""}`,
|
|
5517
|
+
to: this.pool,
|
|
5518
|
+
data,
|
|
5519
|
+
value: 0n,
|
|
5520
|
+
gas_estimate: 15e4
|
|
5521
|
+
};
|
|
5522
|
+
}
|
|
5257
5523
|
async getRates(asset) {
|
|
5258
5524
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5259
5525
|
const reserveCallData = encodeFunctionData14({
|
|
@@ -5298,7 +5564,7 @@ var init_dist2 = __esm({
|
|
|
5298
5564
|
[aTokenAddress, encodeFunctionData14({ abi: INCENTIVES_ABI, functionName: "getIncentivesController" })]
|
|
5299
5565
|
]);
|
|
5300
5566
|
const controllerAddr = decodeAddress4(controllerRaw ?? null);
|
|
5301
|
-
if (controllerAddr && controllerAddr !==
|
|
5567
|
+
if (controllerAddr && controllerAddr !== zeroAddress7) {
|
|
5302
5568
|
const [supplyRewardsRaw, borrowRewardsRaw] = await multicallRead(this.rpcUrl, [
|
|
5303
5569
|
[controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [aTokenAddress] })],
|
|
5304
5570
|
[controllerAddr, encodeFunctionData14({ abi: REWARDS_CONTROLLER_ABI, functionName: "getRewardsByAsset", args: [variableDebtTokenAddress] })]
|
|
@@ -5494,8 +5760,8 @@ var init_dist2 = __esm({
|
|
|
5494
5760
|
if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
|
|
5495
5761
|
}
|
|
5496
5762
|
} catch {
|
|
5497
|
-
if (collateralUsd > 0) supplies.push({ asset:
|
|
5498
|
-
if (debtUsd > 0) borrows.push({ asset:
|
|
5763
|
+
if (collateralUsd > 0) supplies.push({ asset: zeroAddress7, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
|
|
5764
|
+
if (debtUsd > 0) borrows.push({ asset: zeroAddress7, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
|
|
5499
5765
|
}
|
|
5500
5766
|
return {
|
|
5501
5767
|
protocol: this.protocolName,
|
|
@@ -5662,8 +5928,8 @@ var init_dist2 = __esm({
|
|
|
5662
5928
|
const collateralUsd = u256ToF642(totalCollateralBase) / 1e18;
|
|
5663
5929
|
const debtUsd = u256ToF642(totalDebtBase) / 1e18;
|
|
5664
5930
|
const ltvBps = u256ToF642(ltv);
|
|
5665
|
-
const supplies = collateralUsd > 0 ? [{ asset:
|
|
5666
|
-
const borrows = debtUsd > 0 ? [{ asset:
|
|
5931
|
+
const supplies = collateralUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
|
|
5932
|
+
const borrows = debtUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
|
|
5667
5933
|
return {
|
|
5668
5934
|
protocol: this.protocolName,
|
|
5669
5935
|
user,
|
|
@@ -5766,14 +6032,31 @@ var init_dist2 = __esm({
|
|
|
5766
6032
|
"function borrow(uint256 borrowAmount) external returns (uint256)",
|
|
5767
6033
|
"function repayBorrow(uint256 repayAmount) external returns (uint256)"
|
|
5768
6034
|
]);
|
|
6035
|
+
NATIVE_CTOKEN_ABI = parseAbi17([
|
|
6036
|
+
"function mint() external payable",
|
|
6037
|
+
"function repayBorrow() external payable"
|
|
6038
|
+
]);
|
|
6039
|
+
COMPTROLLER_ABI = parseAbi17([
|
|
6040
|
+
"function enterMarkets(address[] cTokens) external returns (uint256[])",
|
|
6041
|
+
"function exitMarket(address cToken) external returns (uint256)"
|
|
6042
|
+
]);
|
|
6043
|
+
NATIVE_SENTINEL = "0x0000000000000000000000000000000000000000";
|
|
5769
6044
|
BSC_BLOCKS_PER_YEAR = 10512e3;
|
|
5770
6045
|
CompoundV2Adapter = class {
|
|
5771
6046
|
protocolName;
|
|
5772
6047
|
defaultVtoken;
|
|
5773
6048
|
vTokenCandidates;
|
|
6049
|
+
comptroller;
|
|
5774
6050
|
rpcUrl;
|
|
5775
|
-
// Lazy cache: underlying asset address (lowercased) → vToken address
|
|
6051
|
+
// Lazy cache: underlying asset address (lowercased) → vToken address.
|
|
6052
|
+
// The native sentinel (0x0…) is mapped to the cETH/vBNB-style vToken
|
|
6053
|
+
// when one is detected during resolveVtoken().
|
|
5776
6054
|
vTokenByAsset = null;
|
|
6055
|
+
// The cETH/vBNB-style vToken whose underlying() reverts (it has no
|
|
6056
|
+
// ERC20 underlying — the underlying is the chain's native gas token).
|
|
6057
|
+
// Set lazily by resolveVtoken() and consulted by buildSupply/buildRepay
|
|
6058
|
+
// to switch to the payable mint() / repayBorrow() variants.
|
|
6059
|
+
nativeVtoken = null;
|
|
5777
6060
|
constructor(entry, rpcUrl) {
|
|
5778
6061
|
this.protocolName = entry.name;
|
|
5779
6062
|
this.rpcUrl = rpcUrl;
|
|
@@ -5781,6 +6064,7 @@ var init_dist2 = __esm({
|
|
|
5781
6064
|
const vtoken = contracts["vusdt"] ?? contracts["vusdc"] ?? contracts["vbnb"] ?? contracts["comptroller"];
|
|
5782
6065
|
if (!vtoken) throw DefiError.contractError("Missing vToken or comptroller address");
|
|
5783
6066
|
this.defaultVtoken = vtoken;
|
|
6067
|
+
this.comptroller = contracts["comptroller"];
|
|
5784
6068
|
this.vTokenCandidates = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k)).map(([, v]) => v);
|
|
5785
6069
|
if (this.vTokenCandidates.length === 0) this.vTokenCandidates = [vtoken];
|
|
5786
6070
|
}
|
|
@@ -5789,19 +6073,38 @@ var init_dist2 = __esm({
|
|
|
5789
6073
|
if (!this.vTokenByAsset) {
|
|
5790
6074
|
const client = createPublicClient13({ transport: http13(this.rpcUrl) });
|
|
5791
6075
|
const map = /* @__PURE__ */ new Map();
|
|
6076
|
+
let nativeVtoken = null;
|
|
5792
6077
|
const lookups = await Promise.allSettled(
|
|
5793
6078
|
this.vTokenCandidates.map(async (v) => {
|
|
5794
|
-
|
|
5795
|
-
|
|
6079
|
+
try {
|
|
6080
|
+
const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
|
|
6081
|
+
return { vtoken: v, underlying: u };
|
|
6082
|
+
} catch {
|
|
6083
|
+
return { vtoken: v, underlying: null };
|
|
6084
|
+
}
|
|
5796
6085
|
})
|
|
5797
6086
|
);
|
|
5798
6087
|
for (const r of lookups) {
|
|
5799
|
-
if (r.status
|
|
6088
|
+
if (r.status !== "fulfilled") continue;
|
|
6089
|
+
const { vtoken, underlying } = r.value;
|
|
6090
|
+
if (underlying) {
|
|
6091
|
+
map.set(underlying.toLowerCase(), vtoken);
|
|
6092
|
+
} else if (!nativeVtoken) {
|
|
6093
|
+
nativeVtoken = vtoken;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
if (nativeVtoken) {
|
|
6097
|
+
map.set(NATIVE_SENTINEL, nativeVtoken);
|
|
5800
6098
|
}
|
|
5801
6099
|
this.vTokenByAsset = map;
|
|
6100
|
+
this.nativeVtoken = nativeVtoken;
|
|
5802
6101
|
}
|
|
5803
6102
|
return this.vTokenByAsset.get(asset.toLowerCase()) ?? null;
|
|
5804
6103
|
}
|
|
6104
|
+
/** True iff `vtoken` is the cETH/vBNB-style native cToken for this protocol. */
|
|
6105
|
+
isNativeVtoken(vtoken) {
|
|
6106
|
+
return this.nativeVtoken !== null && vtoken.toLowerCase() === this.nativeVtoken.toLowerCase();
|
|
6107
|
+
}
|
|
5805
6108
|
name() {
|
|
5806
6109
|
return this.protocolName;
|
|
5807
6110
|
}
|
|
@@ -5815,6 +6118,16 @@ var init_dist2 = __esm({
|
|
|
5815
6118
|
}
|
|
5816
6119
|
async buildSupply(params) {
|
|
5817
6120
|
const vtoken = await this.vtokenFor(params.asset);
|
|
6121
|
+
if (this.isNativeVtoken(vtoken)) {
|
|
6122
|
+
const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "mint" });
|
|
6123
|
+
return {
|
|
6124
|
+
description: `[${this.protocolName}] Supply ${params.amount} (native) to Venus`,
|
|
6125
|
+
to: vtoken,
|
|
6126
|
+
data: data2,
|
|
6127
|
+
value: params.amount,
|
|
6128
|
+
gas_estimate: 3e5
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
5818
6131
|
const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "mint", args: [params.amount] });
|
|
5819
6132
|
return {
|
|
5820
6133
|
description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to Venus`,
|
|
@@ -5838,6 +6151,16 @@ var init_dist2 = __esm({
|
|
|
5838
6151
|
}
|
|
5839
6152
|
async buildRepay(params) {
|
|
5840
6153
|
const vtoken = await this.vtokenFor(params.asset);
|
|
6154
|
+
if (this.isNativeVtoken(vtoken)) {
|
|
6155
|
+
const data2 = encodeFunctionData16({ abi: NATIVE_CTOKEN_ABI, functionName: "repayBorrow" });
|
|
6156
|
+
return {
|
|
6157
|
+
description: `[${this.protocolName}] Repay ${params.amount} (native) to Venus`,
|
|
6158
|
+
to: vtoken,
|
|
6159
|
+
data: data2,
|
|
6160
|
+
value: params.amount,
|
|
6161
|
+
gas_estimate: 3e5
|
|
6162
|
+
};
|
|
6163
|
+
}
|
|
5841
6164
|
const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "repayBorrow", args: [params.amount] });
|
|
5842
6165
|
return {
|
|
5843
6166
|
description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to Venus`,
|
|
@@ -5850,6 +6173,37 @@ var init_dist2 = __esm({
|
|
|
5850
6173
|
}
|
|
5851
6174
|
async buildWithdraw(params) {
|
|
5852
6175
|
const vtoken = await this.vtokenFor(params.asset);
|
|
6176
|
+
const MAX_UINT2562 = (1n << 256n) - 1n;
|
|
6177
|
+
if (params.amount === MAX_UINT2562 && this.rpcUrl) {
|
|
6178
|
+
const client = createPublicClient13({ transport: http13(this.rpcUrl) });
|
|
6179
|
+
const [vtokenBalance, borrowBalance] = await Promise.all([
|
|
6180
|
+
client.readContract({
|
|
6181
|
+
address: vtoken,
|
|
6182
|
+
abi: CTOKEN_ABI,
|
|
6183
|
+
functionName: "balanceOf",
|
|
6184
|
+
args: [params.to]
|
|
6185
|
+
}),
|
|
6186
|
+
client.readContract({
|
|
6187
|
+
address: vtoken,
|
|
6188
|
+
abi: CTOKEN_ABI,
|
|
6189
|
+
functionName: "borrowBalanceStored",
|
|
6190
|
+
args: [params.to]
|
|
6191
|
+
}).catch(() => 0n)
|
|
6192
|
+
]);
|
|
6193
|
+
if (borrowBalance > 0n) {
|
|
6194
|
+
throw DefiError.contractError(
|
|
6195
|
+
`[${this.protocolName}] Cannot withdraw all (uint256.max) \u2014 wallet has an outstanding borrow of ${borrowBalance} on this market. Repay the borrow first, or pass an explicit --amount that leaves enough collateral.`
|
|
6196
|
+
);
|
|
6197
|
+
}
|
|
6198
|
+
const redeemData = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeem", args: [vtokenBalance] });
|
|
6199
|
+
return {
|
|
6200
|
+
description: `[${this.protocolName}] Withdraw all (auto-max, ${vtokenBalance} vTokens) of ${params.asset} from Venus`,
|
|
6201
|
+
to: vtoken,
|
|
6202
|
+
data: redeemData,
|
|
6203
|
+
value: 0n,
|
|
6204
|
+
gas_estimate: 35e4
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
5853
6207
|
const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeemUnderlying", args: [params.amount] });
|
|
5854
6208
|
return {
|
|
5855
6209
|
description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from Venus`,
|
|
@@ -5859,6 +6213,37 @@ var init_dist2 = __esm({
|
|
|
5859
6213
|
gas_estimate: 25e4
|
|
5860
6214
|
};
|
|
5861
6215
|
}
|
|
6216
|
+
/**
|
|
6217
|
+
* Compound V2 family: enter cTokens as collateral via Comptroller.
|
|
6218
|
+
* Without this call, supplied assets sit dormant in the Comptroller's
|
|
6219
|
+
* accountAssets[] and `getAccountLiquidity` reports zero collateral —
|
|
6220
|
+
* any borrow then reverts. Mirrors the role of Aave V3's
|
|
6221
|
+
* setUserUseReserveAsCollateral, but the API is batch-by-cToken.
|
|
6222
|
+
*/
|
|
6223
|
+
async buildEnterMarkets(cTokens) {
|
|
6224
|
+
if (!this.comptroller) {
|
|
6225
|
+
throw DefiError.contractError(
|
|
6226
|
+
`[${this.protocolName}] enterMarkets requires the Comptroller address to be registered under [protocol.contracts] as 'comptroller'.`
|
|
6227
|
+
);
|
|
6228
|
+
}
|
|
6229
|
+
if (cTokens.length === 0) {
|
|
6230
|
+
throw DefiError.invalidParam(
|
|
6231
|
+
`[${this.protocolName}] enterMarkets requires at least one cToken address.`
|
|
6232
|
+
);
|
|
6233
|
+
}
|
|
6234
|
+
const data = encodeFunctionData16({
|
|
6235
|
+
abi: COMPTROLLER_ABI,
|
|
6236
|
+
functionName: "enterMarkets",
|
|
6237
|
+
args: [cTokens]
|
|
6238
|
+
});
|
|
6239
|
+
return {
|
|
6240
|
+
description: `[${this.protocolName}] Enter ${cTokens.length} market(s) as collateral`,
|
|
6241
|
+
to: this.comptroller,
|
|
6242
|
+
data,
|
|
6243
|
+
value: 0n,
|
|
6244
|
+
gas_estimate: 2e5
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
5862
6247
|
async getRates(asset) {
|
|
5863
6248
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5864
6249
|
const client = createPublicClient13({ transport: http13(this.rpcUrl) });
|
|
@@ -6226,9 +6611,11 @@ var init_dist2 = __esm({
|
|
|
6226
6611
|
"function market(bytes32 id) external view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
|
|
6227
6612
|
"function idToMarketParams(bytes32 id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)",
|
|
6228
6613
|
"function supply((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) external returns (uint256 assetsSupplied, uint256 sharesSupplied)",
|
|
6614
|
+
"function supplyCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, bytes data) external",
|
|
6229
6615
|
"function borrow((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed)",
|
|
6230
6616
|
"function repay((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) external returns (uint256 assetsRepaid, uint256 sharesRepaid)",
|
|
6231
|
-
"function withdraw((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn)"
|
|
6617
|
+
"function withdraw((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn)",
|
|
6618
|
+
"function withdrawCollateral((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, address receiver) external"
|
|
6232
6619
|
]);
|
|
6233
6620
|
META_MORPHO_ABI = parseAbi20([
|
|
6234
6621
|
"function supplyQueueLength() external view returns (uint256)",
|
|
@@ -6255,6 +6642,8 @@ var init_dist2 = __esm({
|
|
|
6255
6642
|
rpcUrl;
|
|
6256
6643
|
metaMorphoVaults;
|
|
6257
6644
|
metaMorphoVaultEntries;
|
|
6645
|
+
namedMarkets;
|
|
6646
|
+
namedMarketByName;
|
|
6258
6647
|
vaultAssetMap = null;
|
|
6259
6648
|
constructor(entry, rpcUrl) {
|
|
6260
6649
|
this.protocolName = entry.name;
|
|
@@ -6266,6 +6655,26 @@ var init_dist2 = __esm({
|
|
|
6266
6655
|
this.defaultVault = contracts["fehype"] ?? contracts["vault"] ?? contracts["feusdc"];
|
|
6267
6656
|
this.metaMorphoVaultEntries = Object.entries(contracts).filter(([key]) => /^fe[a-z0-9_]+$/i.test(key) || key === "vault").map(([key, addr]) => ({ key, addr }));
|
|
6268
6657
|
this.metaMorphoVaults = this.metaMorphoVaultEntries.map((e) => e.addr);
|
|
6658
|
+
this.namedMarkets = entry.markets ?? [];
|
|
6659
|
+
const byName = /* @__PURE__ */ new Map();
|
|
6660
|
+
for (const m of this.namedMarkets) byName.set(m.name.toLowerCase(), m.id);
|
|
6661
|
+
this.namedMarketByName = byName;
|
|
6662
|
+
}
|
|
6663
|
+
/**
|
|
6664
|
+
* Resolve a friendly market name (e.g. `WMON-AUSD`) to its 32-byte
|
|
6665
|
+
* marketId via the per-protocol TOML registry. Returns null when the
|
|
6666
|
+
* adapter has no markets[] block or the name doesn't match any entry —
|
|
6667
|
+
* callers fall back to treating the input as a raw hex marketId.
|
|
6668
|
+
*/
|
|
6669
|
+
resolveMarketIdByName(name) {
|
|
6670
|
+
return this.namedMarketByName.get(name.toLowerCase()) ?? null;
|
|
6671
|
+
}
|
|
6672
|
+
/**
|
|
6673
|
+
* Returns the registered named markets for diagnostics (e.g. CLI error
|
|
6674
|
+
* messages listing valid choices when the user passes an unknown name).
|
|
6675
|
+
*/
|
|
6676
|
+
listNamedMarkets() {
|
|
6677
|
+
return this.namedMarkets;
|
|
6269
6678
|
}
|
|
6270
6679
|
async resolveVault(asset, preferKey) {
|
|
6271
6680
|
if (this.metaMorphoVaultEntries.length === 0 || !this.rpcUrl) return null;
|
|
@@ -6299,10 +6708,61 @@ var init_dist2 = __esm({
|
|
|
6299
6708
|
name() {
|
|
6300
6709
|
return this.protocolName;
|
|
6301
6710
|
}
|
|
6711
|
+
/**
|
|
6712
|
+
* Resolve a Morpho Blue marketId into the full MarketParams tuple by
|
|
6713
|
+
* calling Morpho.idToMarketParams(id). Used by every direct-market
|
|
6714
|
+
* method (supply / borrow / repay / withdraw / supplyCollateral /
|
|
6715
|
+
* withdrawCollateral) so the caller only has to pass the 32-byte
|
|
6716
|
+
* marketId — same shape as the Morpho UI / API.
|
|
6717
|
+
*/
|
|
6718
|
+
async resolveMarketParams(marketId) {
|
|
6719
|
+
if (!this.rpcUrl) {
|
|
6720
|
+
throw DefiError.rpcError(
|
|
6721
|
+
`[${this.protocolName}] No RPC URL configured \u2014 cannot resolve marketId ${marketId}`
|
|
6722
|
+
);
|
|
6723
|
+
}
|
|
6724
|
+
const client = createPublicClient16({ transport: http16(this.rpcUrl) });
|
|
6725
|
+
let result;
|
|
6726
|
+
try {
|
|
6727
|
+
result = await client.readContract({
|
|
6728
|
+
address: this.morpho,
|
|
6729
|
+
abi: MORPHO_ABI,
|
|
6730
|
+
functionName: "idToMarketParams",
|
|
6731
|
+
args: [marketId]
|
|
6732
|
+
});
|
|
6733
|
+
} catch (e) {
|
|
6734
|
+
throw DefiError.rpcError(
|
|
6735
|
+
`[${this.protocolName}] idToMarketParams(${marketId}) failed: ${e}`
|
|
6736
|
+
);
|
|
6737
|
+
}
|
|
6738
|
+
const [loanToken, collateralToken, oracle, irm, lltv] = result;
|
|
6739
|
+
if (loanToken === zeroAddress9 || collateralToken === zeroAddress9 || lltv === 0n) {
|
|
6740
|
+
throw DefiError.invalidParam(
|
|
6741
|
+
`[${this.protocolName}] marketId ${marketId} resolves to an empty MarketParams (loan=${loanToken}, collateral=${collateralToken}, lltv=${lltv}). Verify the id matches a registered market on this chain.`
|
|
6742
|
+
);
|
|
6743
|
+
}
|
|
6744
|
+
return { loanToken, collateralToken, oracle, irm, lltv };
|
|
6745
|
+
}
|
|
6302
6746
|
async buildSupply(params) {
|
|
6747
|
+
if (params.market_id) {
|
|
6748
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6749
|
+
const data = encodeFunctionData19({
|
|
6750
|
+
abi: MORPHO_ABI,
|
|
6751
|
+
functionName: "supply",
|
|
6752
|
+
args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
|
|
6753
|
+
});
|
|
6754
|
+
return {
|
|
6755
|
+
description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6756
|
+
to: this.morpho,
|
|
6757
|
+
data,
|
|
6758
|
+
value: 0n,
|
|
6759
|
+
gas_estimate: 35e4,
|
|
6760
|
+
approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
|
|
6761
|
+
};
|
|
6762
|
+
}
|
|
6303
6763
|
const vault = await this.resolveVault(params.asset);
|
|
6304
6764
|
if (vault) {
|
|
6305
|
-
const
|
|
6765
|
+
const data = encodeFunctionData19({
|
|
6306
6766
|
abi: ERC4626_ABI,
|
|
6307
6767
|
functionName: "deposit",
|
|
6308
6768
|
args: [params.amount, params.on_behalf_of]
|
|
@@ -6310,50 +6770,118 @@ var init_dist2 = __esm({
|
|
|
6310
6770
|
return {
|
|
6311
6771
|
description: `[${this.protocolName}] Deposit ${params.amount} into MetaMorpho vault`,
|
|
6312
6772
|
to: vault,
|
|
6313
|
-
data
|
|
6773
|
+
data,
|
|
6314
6774
|
value: 0n,
|
|
6315
6775
|
gas_estimate: 4e5,
|
|
6316
6776
|
approvals: [{ token: params.asset, spender: vault, amount: params.amount }]
|
|
6317
6777
|
};
|
|
6318
6778
|
}
|
|
6319
|
-
|
|
6779
|
+
throw DefiError.invalidParam(
|
|
6780
|
+
`[${this.protocolName}] supply requires either a registered MetaMorpho vault for ${params.asset} or an explicit --market <marketId>. The legacy zero-MarketParams stub was removed (it always reverted on-chain).`
|
|
6781
|
+
);
|
|
6782
|
+
}
|
|
6783
|
+
async buildBorrow(params) {
|
|
6784
|
+
if (!params.market_id) {
|
|
6785
|
+
throw DefiError.invalidParam(
|
|
6786
|
+
`[${this.protocolName}] Morpho Blue borrow requires --market <marketId>. Find one via the Morpho API (https://blue-api.morpho.org/graphql).`
|
|
6787
|
+
);
|
|
6788
|
+
}
|
|
6789
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6790
|
+
const data = encodeFunctionData19({
|
|
6791
|
+
abi: MORPHO_ABI,
|
|
6792
|
+
functionName: "borrow",
|
|
6793
|
+
args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
|
|
6794
|
+
});
|
|
6795
|
+
return {
|
|
6796
|
+
description: `[${this.protocolName}] Borrow ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6797
|
+
to: this.morpho,
|
|
6798
|
+
data,
|
|
6799
|
+
value: 0n,
|
|
6800
|
+
gas_estimate: 4e5
|
|
6801
|
+
};
|
|
6802
|
+
}
|
|
6803
|
+
async buildRepay(params) {
|
|
6804
|
+
if (!params.market_id) {
|
|
6805
|
+
throw DefiError.invalidParam(
|
|
6806
|
+
`[${this.protocolName}] Morpho Blue repay requires --market <marketId>.`
|
|
6807
|
+
);
|
|
6808
|
+
}
|
|
6809
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6810
|
+
if (params.amount === MAX_UINT256) {
|
|
6811
|
+
if (!this.rpcUrl) {
|
|
6812
|
+
throw DefiError.rpcError(
|
|
6813
|
+
`[${this.protocolName}] max-repay requires an RPC URL to read borrowShares.`
|
|
6814
|
+
);
|
|
6815
|
+
}
|
|
6816
|
+
const client = createPublicClient16({ transport: http16(this.rpcUrl) });
|
|
6817
|
+
const positionAbi = parseAbi20([
|
|
6818
|
+
"function position(bytes32 id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)"
|
|
6819
|
+
]);
|
|
6820
|
+
const pos = await client.readContract({
|
|
6821
|
+
address: this.morpho,
|
|
6822
|
+
abi: positionAbi,
|
|
6823
|
+
functionName: "position",
|
|
6824
|
+
args: [params.market_id, params.on_behalf_of]
|
|
6825
|
+
});
|
|
6826
|
+
const [, borrowShares] = pos;
|
|
6827
|
+
if (borrowShares === 0n) {
|
|
6828
|
+
throw DefiError.invalidParam(
|
|
6829
|
+
`[${this.protocolName}] cannot repay max \u2014 user has no borrow position in market ${params.market_id}.`
|
|
6830
|
+
);
|
|
6831
|
+
}
|
|
6832
|
+
const data2 = encodeFunctionData19({
|
|
6833
|
+
abi: MORPHO_ABI,
|
|
6834
|
+
functionName: "repay",
|
|
6835
|
+
args: [market, 0n, borrowShares, params.on_behalf_of, "0x"]
|
|
6836
|
+
});
|
|
6837
|
+
return {
|
|
6838
|
+
description: `[${this.protocolName}] Repay max (${borrowShares} shares) to market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6839
|
+
to: this.morpho,
|
|
6840
|
+
data: data2,
|
|
6841
|
+
value: 0n,
|
|
6842
|
+
gas_estimate: 35e4,
|
|
6843
|
+
approvals: [{ token: params.asset, spender: this.morpho, amount: MAX_UINT256 }]
|
|
6844
|
+
};
|
|
6845
|
+
}
|
|
6320
6846
|
const data = encodeFunctionData19({
|
|
6321
6847
|
abi: MORPHO_ABI,
|
|
6322
|
-
functionName: "
|
|
6848
|
+
functionName: "repay",
|
|
6323
6849
|
args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
|
|
6324
6850
|
});
|
|
6325
6851
|
return {
|
|
6326
|
-
description: `[${this.protocolName}]
|
|
6852
|
+
description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6327
6853
|
to: this.morpho,
|
|
6328
6854
|
data,
|
|
6329
6855
|
value: 0n,
|
|
6330
|
-
gas_estimate:
|
|
6856
|
+
gas_estimate: 35e4,
|
|
6857
|
+
approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
|
|
6331
6858
|
};
|
|
6332
6859
|
}
|
|
6333
|
-
async
|
|
6334
|
-
const market =
|
|
6860
|
+
async buildSupplyCollateral(params) {
|
|
6861
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6335
6862
|
const data = encodeFunctionData19({
|
|
6336
6863
|
abi: MORPHO_ABI,
|
|
6337
|
-
functionName: "
|
|
6338
|
-
args: [market, params.amount,
|
|
6864
|
+
functionName: "supplyCollateral",
|
|
6865
|
+
args: [market, params.amount, params.on_behalf_of, "0x"]
|
|
6339
6866
|
});
|
|
6340
6867
|
return {
|
|
6341
|
-
description: `[${this.protocolName}]
|
|
6868
|
+
description: `[${this.protocolName}] Supply collateral ${params.amount} of ${params.asset} to market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6342
6869
|
to: this.morpho,
|
|
6343
6870
|
data,
|
|
6344
6871
|
value: 0n,
|
|
6345
|
-
gas_estimate: 35e4
|
|
6872
|
+
gas_estimate: 35e4,
|
|
6873
|
+
approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
|
|
6346
6874
|
};
|
|
6347
6875
|
}
|
|
6348
|
-
async
|
|
6349
|
-
const market =
|
|
6876
|
+
async buildWithdrawCollateral(params) {
|
|
6877
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6350
6878
|
const data = encodeFunctionData19({
|
|
6351
6879
|
abi: MORPHO_ABI,
|
|
6352
|
-
functionName: "
|
|
6353
|
-
args: [market, params.amount,
|
|
6880
|
+
functionName: "withdrawCollateral",
|
|
6881
|
+
args: [market, params.amount, params.to, params.to]
|
|
6354
6882
|
});
|
|
6355
6883
|
return {
|
|
6356
|
-
description: `[${this.protocolName}]
|
|
6884
|
+
description: `[${this.protocolName}] Withdraw collateral ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6357
6885
|
to: this.morpho,
|
|
6358
6886
|
data,
|
|
6359
6887
|
value: 0n,
|
|
@@ -6361,6 +6889,21 @@ var init_dist2 = __esm({
|
|
|
6361
6889
|
};
|
|
6362
6890
|
}
|
|
6363
6891
|
async buildWithdraw(params) {
|
|
6892
|
+
if (params.market_id) {
|
|
6893
|
+
const market = await this.resolveMarketParams(params.market_id);
|
|
6894
|
+
const data = encodeFunctionData19({
|
|
6895
|
+
abi: MORPHO_ABI,
|
|
6896
|
+
functionName: "withdraw",
|
|
6897
|
+
args: [market, params.amount, 0n, params.to, params.to]
|
|
6898
|
+
});
|
|
6899
|
+
return {
|
|
6900
|
+
description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from market ${params.market_id.slice(0, 10)}\u2026`,
|
|
6901
|
+
to: this.morpho,
|
|
6902
|
+
data,
|
|
6903
|
+
value: 0n,
|
|
6904
|
+
gas_estimate: 3e5
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6364
6907
|
const vault = await this.resolveVault(params.asset);
|
|
6365
6908
|
if (vault) {
|
|
6366
6909
|
if (params.amount === MAX_UINT256) {
|
|
@@ -6369,7 +6912,7 @@ var init_dist2 = __esm({
|
|
|
6369
6912
|
[vault, encodeFunctionData19({ abi: ERC4626_ABI, functionName: "balanceOf", args: [params.to] })]
|
|
6370
6913
|
]);
|
|
6371
6914
|
const shares = decodeU256(balRaw ?? null);
|
|
6372
|
-
const
|
|
6915
|
+
const data2 = encodeFunctionData19({
|
|
6373
6916
|
abi: ERC4626_ABI,
|
|
6374
6917
|
functionName: "redeem",
|
|
6375
6918
|
args: [shares, params.to, params.to]
|
|
@@ -6377,12 +6920,12 @@ var init_dist2 = __esm({
|
|
|
6377
6920
|
return {
|
|
6378
6921
|
description: `[${this.protocolName}] Redeem all shares (${shares}) from MetaMorpho vault`,
|
|
6379
6922
|
to: vault,
|
|
6380
|
-
data:
|
|
6923
|
+
data: data2,
|
|
6381
6924
|
value: 0n,
|
|
6382
6925
|
gas_estimate: 4e5
|
|
6383
6926
|
};
|
|
6384
6927
|
}
|
|
6385
|
-
const
|
|
6928
|
+
const data = encodeFunctionData19({
|
|
6386
6929
|
abi: ERC4626_ABI,
|
|
6387
6930
|
functionName: "withdraw",
|
|
6388
6931
|
args: [params.amount, params.to, params.to]
|
|
@@ -6390,24 +6933,14 @@ var init_dist2 = __esm({
|
|
|
6390
6933
|
return {
|
|
6391
6934
|
description: `[${this.protocolName}] Withdraw ${params.amount} assets from MetaMorpho vault`,
|
|
6392
6935
|
to: vault,
|
|
6393
|
-
data
|
|
6936
|
+
data,
|
|
6394
6937
|
value: 0n,
|
|
6395
6938
|
gas_estimate: 4e5
|
|
6396
6939
|
};
|
|
6397
6940
|
}
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
functionName: "withdraw",
|
|
6402
|
-
args: [market, params.amount, 0n, params.to, params.to]
|
|
6403
|
-
});
|
|
6404
|
-
return {
|
|
6405
|
-
description: `[${this.protocolName}] Withdraw ${params.amount} from Morpho market`,
|
|
6406
|
-
to: this.morpho,
|
|
6407
|
-
data,
|
|
6408
|
-
value: 0n,
|
|
6409
|
-
gas_estimate: 25e4
|
|
6410
|
-
};
|
|
6941
|
+
throw DefiError.invalidParam(
|
|
6942
|
+
`[${this.protocolName}] withdraw requires either a registered MetaMorpho vault for ${params.asset} or an explicit --market <marketId>. The legacy zero-MarketParams stub was removed (it always reverted on-chain).`
|
|
6943
|
+
);
|
|
6411
6944
|
}
|
|
6412
6945
|
async getRates(asset) {
|
|
6413
6946
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
@@ -6524,7 +7057,7 @@ var init_dist2 = __esm({
|
|
|
6524
7057
|
if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
|
|
6525
7058
|
return [0n, 0n];
|
|
6526
7059
|
}
|
|
6527
|
-
const client =
|
|
7060
|
+
const client = createPublicClient17({ transport: http17(this.rpcUrl) });
|
|
6528
7061
|
const approxResult = await client.readContract({
|
|
6529
7062
|
address: this.hintHelpers,
|
|
6530
7063
|
abi: HINT_HELPERS_ABI,
|
|
@@ -6615,7 +7148,7 @@ var init_dist2 = __esm({
|
|
|
6615
7148
|
async getCdpInfo(cdpId) {
|
|
6616
7149
|
if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
|
|
6617
7150
|
if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
|
|
6618
|
-
const client =
|
|
7151
|
+
const client = createPublicClient17({ transport: http17(this.rpcUrl) });
|
|
6619
7152
|
const data = await client.readContract({
|
|
6620
7153
|
address: this.troveManager,
|
|
6621
7154
|
abi: TROVE_MANAGER_ABI,
|
|
@@ -6633,13 +7166,13 @@ var init_dist2 = __esm({
|
|
|
6633
7166
|
protocol: this.protocolName,
|
|
6634
7167
|
cdp_id: cdpId,
|
|
6635
7168
|
collateral: {
|
|
6636
|
-
token:
|
|
7169
|
+
token: zeroAddress10,
|
|
6637
7170
|
symbol: "WHYPE",
|
|
6638
7171
|
amount: entireColl,
|
|
6639
7172
|
decimals: 18
|
|
6640
7173
|
},
|
|
6641
7174
|
debt: {
|
|
6642
|
-
token:
|
|
7175
|
+
token: zeroAddress10,
|
|
6643
7176
|
symbol: "feUSD",
|
|
6644
7177
|
amount: entireDebt,
|
|
6645
7178
|
decimals: 18
|
|
@@ -6674,7 +7207,7 @@ var init_dist2 = __esm({
|
|
|
6674
7207
|
if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
|
|
6675
7208
|
throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
|
|
6676
7209
|
}
|
|
6677
|
-
const client =
|
|
7210
|
+
const client = createPublicClient18({ transport: http18(this.rpcUrl) });
|
|
6678
7211
|
let priceVal;
|
|
6679
7212
|
try {
|
|
6680
7213
|
const result = await client.readContract({
|
|
@@ -6766,7 +7299,7 @@ var init_dist2 = __esm({
|
|
|
6766
7299
|
}
|
|
6767
7300
|
async totalAssets() {
|
|
6768
7301
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6769
|
-
const client =
|
|
7302
|
+
const client = createPublicClient19({ transport: http19(this.rpcUrl) });
|
|
6770
7303
|
return client.readContract({
|
|
6771
7304
|
address: this.vaultAddress,
|
|
6772
7305
|
abi: ERC4626_ABI2,
|
|
@@ -6777,7 +7310,7 @@ var init_dist2 = __esm({
|
|
|
6777
7310
|
}
|
|
6778
7311
|
async convertToShares(assets) {
|
|
6779
7312
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6780
|
-
const client =
|
|
7313
|
+
const client = createPublicClient19({ transport: http19(this.rpcUrl) });
|
|
6781
7314
|
return client.readContract({
|
|
6782
7315
|
address: this.vaultAddress,
|
|
6783
7316
|
abi: ERC4626_ABI2,
|
|
@@ -6789,7 +7322,7 @@ var init_dist2 = __esm({
|
|
|
6789
7322
|
}
|
|
6790
7323
|
async convertToAssets(shares) {
|
|
6791
7324
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6792
|
-
const client =
|
|
7325
|
+
const client = createPublicClient19({ transport: http19(this.rpcUrl) });
|
|
6793
7326
|
return client.readContract({
|
|
6794
7327
|
address: this.vaultAddress,
|
|
6795
7328
|
abi: ERC4626_ABI2,
|
|
@@ -6801,7 +7334,7 @@ var init_dist2 = __esm({
|
|
|
6801
7334
|
}
|
|
6802
7335
|
async getVaultInfo() {
|
|
6803
7336
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6804
|
-
const client =
|
|
7337
|
+
const client = createPublicClient19({ transport: http19(this.rpcUrl) });
|
|
6805
7338
|
const [totalAssets, totalSupply, asset] = await Promise.all([
|
|
6806
7339
|
client.readContract({ address: this.vaultAddress, abi: ERC4626_ABI2, functionName: "totalAssets" }).catch((e) => {
|
|
6807
7340
|
throw DefiError.rpcError(`[${this.protocolName}] totalAssets failed: ${e}`);
|
|
@@ -6893,7 +7426,7 @@ var init_dist2 = __esm({
|
|
|
6893
7426
|
const data = encodeFunctionData23({
|
|
6894
7427
|
abi: STHYPE_ABI,
|
|
6895
7428
|
functionName: "submit",
|
|
6896
|
-
args: [
|
|
7429
|
+
args: [zeroAddress11]
|
|
6897
7430
|
});
|
|
6898
7431
|
return {
|
|
6899
7432
|
description: `[${this.protocolName}] Stake ${params.amount} HYPE for stHYPE`,
|
|
@@ -6919,7 +7452,7 @@ var init_dist2 = __esm({
|
|
|
6919
7452
|
}
|
|
6920
7453
|
async getInfo() {
|
|
6921
7454
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6922
|
-
const client =
|
|
7455
|
+
const client = createPublicClient20({ transport: http20(this.rpcUrl) });
|
|
6923
7456
|
const tokenAddr = this.sthypeToken ?? this.staking;
|
|
6924
7457
|
const totalSupply = await client.readContract({
|
|
6925
7458
|
address: tokenAddr,
|
|
@@ -6930,7 +7463,7 @@ var init_dist2 = __esm({
|
|
|
6930
7463
|
});
|
|
6931
7464
|
return {
|
|
6932
7465
|
protocol: this.protocolName,
|
|
6933
|
-
staked_token:
|
|
7466
|
+
staked_token: zeroAddress11,
|
|
6934
7467
|
liquid_token: tokenAddr,
|
|
6935
7468
|
exchange_rate: 1,
|
|
6936
7469
|
total_staked: totalSupply
|
|
@@ -6989,7 +7522,7 @@ var init_dist2 = __esm({
|
|
|
6989
7522
|
}
|
|
6990
7523
|
async getInfo() {
|
|
6991
7524
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
6992
|
-
const client =
|
|
7525
|
+
const client = createPublicClient21({ transport: http21(this.rpcUrl) });
|
|
6993
7526
|
const totalStaked = await client.readContract({
|
|
6994
7527
|
address: this.staking,
|
|
6995
7528
|
abi: KINETIQ_ABI,
|
|
@@ -7004,7 +7537,7 @@ var init_dist2 = __esm({
|
|
|
7004
7537
|
const rateF64 = hypePrice > 0n && khypePrice > 0n ? Number(khypePrice * 10n ** 18n / hypePrice) / 1e18 : 1;
|
|
7005
7538
|
return {
|
|
7006
7539
|
protocol: this.protocolName,
|
|
7007
|
-
staked_token:
|
|
7540
|
+
staked_token: zeroAddress12,
|
|
7008
7541
|
liquid_token: this.liquidToken,
|
|
7009
7542
|
exchange_rate: rateF64,
|
|
7010
7543
|
total_staked: totalStaked
|
|
@@ -7224,7 +7757,7 @@ var init_dist2 = __esm({
|
|
|
7224
7757
|
}
|
|
7225
7758
|
async getCollectionInfo(collection) {
|
|
7226
7759
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
7227
|
-
const client =
|
|
7760
|
+
const client = createPublicClient222({ transport: http222(this.rpcUrl) });
|
|
7228
7761
|
const [collectionName, symbol, totalSupply] = await Promise.all([
|
|
7229
7762
|
client.readContract({ address: collection, abi: ERC721_ABI, functionName: "name" }).catch((e) => {
|
|
7230
7763
|
throw DefiError.rpcError(`[${this.protocolName}] name failed: ${e}`);
|
|
@@ -7243,7 +7776,7 @@ var init_dist2 = __esm({
|
|
|
7243
7776
|
}
|
|
7244
7777
|
async getTokenInfo(collection, tokenId) {
|
|
7245
7778
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
7246
|
-
const client =
|
|
7779
|
+
const client = createPublicClient222({ transport: http222(this.rpcUrl) });
|
|
7247
7780
|
const [owner, tokenUri] = await Promise.all([
|
|
7248
7781
|
client.readContract({ address: collection, abi: ERC721_ABI, functionName: "ownerOf", args: [tokenId] }).catch((e) => {
|
|
7249
7782
|
throw DefiError.rpcError(`[${this.protocolName}] ownerOf failed: ${e}`);
|
|
@@ -7259,7 +7792,7 @@ var init_dist2 = __esm({
|
|
|
7259
7792
|
}
|
|
7260
7793
|
async getBalance(owner, collection) {
|
|
7261
7794
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
7262
|
-
const client =
|
|
7795
|
+
const client = createPublicClient222({ transport: http222(this.rpcUrl) });
|
|
7263
7796
|
return client.readContract({ address: collection, abi: ERC721_ABI, functionName: "balanceOf", args: [owner] }).catch((e) => {
|
|
7264
7797
|
throw DefiError.rpcError(`[${this.protocolName}] balanceOf failed: ${e}`);
|
|
7265
7798
|
});
|
|
@@ -7328,15 +7861,34 @@ var Executor = class _Executor {
|
|
|
7328
7861
|
dryRun;
|
|
7329
7862
|
rpcUrl;
|
|
7330
7863
|
explorerUrl;
|
|
7331
|
-
|
|
7864
|
+
/**
|
|
7865
|
+
* Optional viem Chain object. When set, all wallet/public clients built
|
|
7866
|
+
* inside this executor anchor to the explicit chainId at construction
|
|
7867
|
+
* time, defending against MITM RPCs that lie about eth_chainId and
|
|
7868
|
+
* keeping offline-signing safe under RPC drift (SSOT 7.4).
|
|
7869
|
+
*/
|
|
7870
|
+
chain;
|
|
7871
|
+
constructor(broadcast, rpcUrl, explorerUrl, chain) {
|
|
7332
7872
|
this.dryRun = !broadcast;
|
|
7333
7873
|
this.rpcUrl = rpcUrl;
|
|
7334
7874
|
this.explorerUrl = explorerUrl;
|
|
7875
|
+
this.chain = chain;
|
|
7876
|
+
}
|
|
7877
|
+
/** Returns the optional `{ chain }` spread for viem client constructors. */
|
|
7878
|
+
chainOpt() {
|
|
7879
|
+
return this.chain ? { chain: this.chain } : {};
|
|
7335
7880
|
}
|
|
7336
7881
|
/** Apply 20% buffer to a gas estimate */
|
|
7337
7882
|
static applyGasBuffer(gas) {
|
|
7338
7883
|
return gas * GAS_BUFFER_BPS / 10000n;
|
|
7339
7884
|
}
|
|
7885
|
+
/**
|
|
7886
|
+
* EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
|
|
7887
|
+
* Extracted as a static so the math is unit-testable without mocking viem.
|
|
7888
|
+
*/
|
|
7889
|
+
static computeMaxFee(baseFeePerGas, priorityFee) {
|
|
7890
|
+
return baseFeePerGas * 125n / 100n + priorityFee;
|
|
7891
|
+
}
|
|
7340
7892
|
/**
|
|
7341
7893
|
* Check allowance for a single token/spender pair and send an approve tx if needed.
|
|
7342
7894
|
* Only called in broadcast mode (not dry-run).
|
|
@@ -7448,7 +8000,7 @@ var Executor = class _Executor {
|
|
|
7448
8000
|
*/
|
|
7449
8001
|
async fetchEip1559Fees(rpcUrl) {
|
|
7450
8002
|
try {
|
|
7451
|
-
const client = createPublicClient2({ transport: http2(rpcUrl) });
|
|
8003
|
+
const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
|
|
7452
8004
|
let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
|
|
7453
8005
|
try {
|
|
7454
8006
|
priorityFee = await client.estimateMaxPriorityFeePerGas();
|
|
@@ -7457,8 +8009,7 @@ var Executor = class _Executor {
|
|
|
7457
8009
|
try {
|
|
7458
8010
|
const block = await client.getBlock({ blockTag: "latest" });
|
|
7459
8011
|
if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
|
|
7460
|
-
|
|
7461
|
-
return [maxFee, priorityFee];
|
|
8012
|
+
return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
|
|
7462
8013
|
}
|
|
7463
8014
|
} catch {
|
|
7464
8015
|
}
|
|
@@ -7471,7 +8022,7 @@ var Executor = class _Executor {
|
|
|
7471
8022
|
/** Estimate gas dynamically with buffer, falling back to a hardcoded estimate */
|
|
7472
8023
|
async estimateGasWithBuffer(rpcUrl, tx, from) {
|
|
7473
8024
|
try {
|
|
7474
|
-
const client = createPublicClient2({ transport: http2(rpcUrl) });
|
|
8025
|
+
const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
|
|
7475
8026
|
const estimated = await client.estimateGas({
|
|
7476
8027
|
to: tx.to,
|
|
7477
8028
|
data: tx.data,
|
|
@@ -7495,7 +8046,7 @@ var Executor = class _Executor {
|
|
|
7495
8046
|
if (!rpcUrl) {
|
|
7496
8047
|
throw DefiError.rpcError("No RPC URL \u2014 cannot simulate. Set HYPEREVM_RPC_URL.");
|
|
7497
8048
|
}
|
|
7498
|
-
const client = createPublicClient2({ transport: http2(rpcUrl) });
|
|
8049
|
+
const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
|
|
7499
8050
|
const privateKey = process.env["DEFI_PRIVATE_KEY"];
|
|
7500
8051
|
const from = privateKey ? privateKeyToAccount(privateKey).address : "0x0000000000000000000000000000000000000001";
|
|
7501
8052
|
if (tx.approvals && tx.approvals.length > 0) {
|
|
@@ -7609,8 +8160,8 @@ var Executor = class _Executor {
|
|
|
7609
8160
|
if (!rpcUrl) {
|
|
7610
8161
|
throw DefiError.rpcError("No RPC URL configured for broadcasting");
|
|
7611
8162
|
}
|
|
7612
|
-
const publicClient = createPublicClient2({ transport: http2(rpcUrl) });
|
|
7613
|
-
const walletClient = createWalletClient({ account, transport: http2(rpcUrl) });
|
|
8163
|
+
const publicClient = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
|
|
8164
|
+
const walletClient = createWalletClient({ account, transport: http2(rpcUrl), ...this.chainOpt() });
|
|
7614
8165
|
if (tx.pre_txs && tx.pre_txs.length > 0) {
|
|
7615
8166
|
for (const preTx of tx.pre_txs) {
|
|
7616
8167
|
process.stderr.write(` Pre-tx: ${preTx.description}...
|
|
@@ -8280,7 +8831,8 @@ function handleSchema(params) {
|
|
|
8280
8831
|
function registerSchema(parent, getOpts) {
|
|
8281
8832
|
parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
|
|
8282
8833
|
const mode = getOpts();
|
|
8283
|
-
const
|
|
8834
|
+
const raw = opts.all ? "all" : command ?? "all";
|
|
8835
|
+
const action = raw.replace(/-/g, ".");
|
|
8284
8836
|
const params = { action };
|
|
8285
8837
|
const schema = handleSchema(params);
|
|
8286
8838
|
printOutput(schema, mode);
|
|
@@ -8291,7 +8843,7 @@ function registerSchema(parent, getOpts) {
|
|
|
8291
8843
|
init_dist();
|
|
8292
8844
|
init_dist();
|
|
8293
8845
|
init_dist2();
|
|
8294
|
-
import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as
|
|
8846
|
+
import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as zeroAddress13 } from "viem";
|
|
8295
8847
|
|
|
8296
8848
|
// src/whitelist.ts
|
|
8297
8849
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -8471,6 +9023,11 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8471
9023
|
["--protocol", slug, "slug"],
|
|
8472
9024
|
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
|
|
8473
9025
|
]);
|
|
9026
|
+
const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
|
|
9027
|
+
["--protocol", slug, "slug"],
|
|
9028
|
+
["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
|
|
9029
|
+
["--token-id", input.tokenId, "token-id-from-mint-result"]
|
|
9030
|
+
]);
|
|
8474
9031
|
switch (p.reward_strategy) {
|
|
8475
9032
|
case "lp_fee_only":
|
|
8476
9033
|
return [
|
|
@@ -8495,11 +9052,18 @@ function buildPipelineSteps(p, input = {}) {
|
|
|
8495
9052
|
{ step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
|
|
8496
9053
|
{ step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
|
|
8497
9054
|
];
|
|
8498
|
-
case "auto_stake":
|
|
9055
|
+
case "auto_stake": {
|
|
9056
|
+
const isNftAutoStake = p.interface === "uniswap_v3";
|
|
8499
9057
|
return [
|
|
8500
9058
|
{ step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
|
|
8501
|
-
{
|
|
9059
|
+
{
|
|
9060
|
+
step: "claim",
|
|
9061
|
+
function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
|
|
9062
|
+
note: isNftAutoStake ? "Ramses CL: claim via NPM with --token-id; gauge.getReward* reverts NOT_AUTHORIZED_CLAIMER for EOAs" : "Multi-token reward (xRAM + WHYPE on Ramses HL)",
|
|
9063
|
+
cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
|
|
9064
|
+
}
|
|
8502
9065
|
];
|
|
9066
|
+
}
|
|
8503
9067
|
case "on_chain_masterchef":
|
|
8504
9068
|
return [
|
|
8505
9069
|
{ step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
|
|
@@ -8700,6 +9264,24 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8700
9264
|
await Promise.allSettled(
|
|
8701
9265
|
protocols.map(async (protocol) => {
|
|
8702
9266
|
try {
|
|
9267
|
+
if (protocol.reward_strategy === "off_chain_api") {
|
|
9268
|
+
const adapter = createNestOffChain(protocol);
|
|
9269
|
+
const pools = await adapter.getLiquidityPools();
|
|
9270
|
+
for (const p of pools) {
|
|
9271
|
+
if (p.aprPercent === 0 && opts.emissionOnly) continue;
|
|
9272
|
+
results.push({
|
|
9273
|
+
protocol: protocol.slug,
|
|
9274
|
+
pool: p.pool,
|
|
9275
|
+
pair: `${p.token0.symbol}/${p.token1.symbol}`,
|
|
9276
|
+
type: p.aprPercent > 0 ? "EMISSION" : "FEE",
|
|
9277
|
+
source: "off_chain_api",
|
|
9278
|
+
aprPercent: p.aprPercent,
|
|
9279
|
+
poolTvlUsd: p.tvlUSD,
|
|
9280
|
+
emissionUsd: p.curEpochEmissionRewardsUSD
|
|
9281
|
+
});
|
|
9282
|
+
}
|
|
9283
|
+
return;
|
|
9284
|
+
}
|
|
8703
9285
|
const isGaugeProtocol = ["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(protocol.interface) || protocol.interface === "uniswap_v3" && protocol.contracts?.["voter"];
|
|
8704
9286
|
if (isGaugeProtocol) {
|
|
8705
9287
|
const chainTokens = registry.tokens.get(chainName)?.map((t) => t.address);
|
|
@@ -8815,7 +9397,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8815
9397
|
try {
|
|
8816
9398
|
const decoded = decodeFunctionResult8({ abi: mcAbi, functionName: "poolInfo", data: raw });
|
|
8817
9399
|
const [allocPoint, v3Pool, t0, t1, , totalLiq] = decoded;
|
|
8818
|
-
if (allocPoint === 0n || v3Pool ===
|
|
9400
|
+
if (allocPoint === 0n || v3Pool === zeroAddress13) continue;
|
|
8819
9401
|
const tokens = registry.tokens.get(chainName);
|
|
8820
9402
|
const sym0 = tokens?.find((t) => t.address.toLowerCase() === t0?.toLowerCase())?.symbol ?? t0?.slice(0, 8) ?? "?";
|
|
8821
9403
|
const sym1 = tokens?.find((t) => t.address.toLowerCase() === t1?.toLowerCase())?.symbol ?? t1?.slice(0, 8) ?? "?";
|
|
@@ -8882,7 +9464,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8882
9464
|
if (opts.emissionOnly) {
|
|
8883
9465
|
printOutput(
|
|
8884
9466
|
results.filter(
|
|
8885
|
-
(r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n)
|
|
9467
|
+
(r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n || (r.aprPercent ?? 0) > 0)
|
|
8886
9468
|
),
|
|
8887
9469
|
getOpts()
|
|
8888
9470
|
);
|
|
@@ -8890,7 +9472,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8890
9472
|
printOutput(results, getOpts());
|
|
8891
9473
|
}
|
|
8892
9474
|
});
|
|
8893
|
-
lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").option("--num-bins <n>", "Merchant Moe LB: bins on each side of active (default 5)").action(async (opts) => {
|
|
9475
|
+
lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").option("--num-bins <n>", "Merchant Moe LB: bins on each side of active (default 5)").option("--slippage <bps>", "Slippage tolerance in basis points (default 50 = 0.5%). Sets amount{0,1}Min per side via applyMinSlippage.").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on add (overrides --slippage for that side).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on add (overrides --slippage for that side).").action(async (opts) => {
|
|
8894
9476
|
const executor = makeExecutor2();
|
|
8895
9477
|
const chainName = parent.opts().chain;
|
|
8896
9478
|
if (!chainName) {
|
|
@@ -8907,13 +9489,27 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8907
9489
|
if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
8908
9490
|
if (!poolAddr) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>; use \`lp discover --protocol ${protocol.slug}\` to list active pools)`);
|
|
8909
9491
|
const lbAdapter = createMerchantMoeLB(protocol, chain.effectiveRpcUrl());
|
|
8910
|
-
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)];
|
|
8911
9492
|
const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
|
|
8912
|
-
const
|
|
8913
|
-
address
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
9493
|
+
const lbPairOrderAbi = parseAbi30([
|
|
9494
|
+
"function getTokenX() view returns (address)",
|
|
9495
|
+
"function getTokenY() view returns (address)",
|
|
9496
|
+
"function getBinStep() view returns (uint16)"
|
|
9497
|
+
]);
|
|
9498
|
+
const [poolTokenX, poolTokenY, binStep] = await Promise.all([
|
|
9499
|
+
client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenX" }),
|
|
9500
|
+
client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenY" }),
|
|
9501
|
+
client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getBinStep" })
|
|
9502
|
+
]);
|
|
9503
|
+
const aIsX = tokenA.toLowerCase() === poolTokenX.toLowerCase();
|
|
9504
|
+
const bIsX = tokenB.toLowerCase() === poolTokenX.toLowerCase();
|
|
9505
|
+
const aIsY = tokenA.toLowerCase() === poolTokenY.toLowerCase();
|
|
9506
|
+
const bIsY = tokenB.toLowerCase() === poolTokenY.toLowerCase();
|
|
9507
|
+
if (!(aIsX && bIsY || bIsX && aIsY)) {
|
|
9508
|
+
throw new Error(
|
|
9509
|
+
`[${protocol.name}] --token-a/--token-b (${tokenA}/${tokenB}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
|
|
9510
|
+
);
|
|
9511
|
+
}
|
|
9512
|
+
const [tokenX, tokenY, amountX, amountY] = aIsX ? [tokenA, tokenB, BigInt(opts.amountA), BigInt(opts.amountB)] : [tokenB, tokenA, BigInt(opts.amountB), BigInt(opts.amountA)];
|
|
8917
9513
|
const tx2 = await lbAdapter.buildAddLiquidity({
|
|
8918
9514
|
pool: poolAddr,
|
|
8919
9515
|
tokenX,
|
|
@@ -8939,12 +9535,15 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8939
9535
|
tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
|
|
8940
9536
|
tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
|
|
8941
9537
|
range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
|
|
8942
|
-
pool: poolAddr
|
|
9538
|
+
pool: poolAddr,
|
|
9539
|
+
slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
|
|
9540
|
+
amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
|
|
9541
|
+
amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
|
|
8943
9542
|
});
|
|
8944
9543
|
const result = await executor.execute(tx);
|
|
8945
9544
|
printOutput(result, getOpts());
|
|
8946
9545
|
});
|
|
8947
|
-
lp.command("farm").description("Add liquidity and auto-stake into gauge/farming for emissions").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--gauge <address>", "Gauge address (required for solidly/hybra if not resolved automatically)").option("--recipient <address>", "Recipient / owner address").option("--tick-lower <tick>", "Lower tick for concentrated LP").option("--tick-upper <tick>", "Upper tick for concentrated LP").option("--range <percent>", "\xB1N% concentrated range around current price").action(async (opts) => {
|
|
9546
|
+
lp.command("farm").description("Add liquidity and auto-stake into gauge/farming for emissions").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--gauge <address>", "Gauge address (required for solidly/hybra if not resolved automatically)").option("--recipient <address>", "Recipient / owner address").option("--tick-lower <tick>", "Lower tick for concentrated LP").option("--tick-upper <tick>", "Upper tick for concentrated LP").option("--range <percent>", "\xB1N% concentrated range around current price").option("--slippage <bps>", "Slippage tolerance in basis points (default 50 = 0.5%). Applied to the underlying mint step.").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on add (overrides --slippage for that side).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on add (overrides --slippage for that side).").action(async (opts) => {
|
|
8948
9547
|
const executor = makeExecutor2();
|
|
8949
9548
|
const chainName = parent.opts().chain;
|
|
8950
9549
|
if (!chainName) {
|
|
@@ -8970,7 +9569,10 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
8970
9569
|
tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
|
|
8971
9570
|
tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
|
|
8972
9571
|
range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
|
|
8973
|
-
pool: poolAddr
|
|
9572
|
+
pool: poolAddr,
|
|
9573
|
+
slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
|
|
9574
|
+
amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
|
|
9575
|
+
amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
|
|
8974
9576
|
});
|
|
8975
9577
|
process.stderr.write("Step 1/2: Adding liquidity...\n");
|
|
8976
9578
|
const addResult = await executor.execute(addTx);
|
|
@@ -9210,7 +9812,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9210
9812
|
note: "Plan output. Run each cli_command sequentially. After the mint step, broadcast mode prints `details.minted_token_id` \u2014 feed that into the next step's --token-id."
|
|
9211
9813
|
}, getOpts());
|
|
9212
9814
|
});
|
|
9213
|
-
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").
|
|
9815
|
+
lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").option("--token-a <token>", "First token symbol or address (required for V2/Curve/LB)").option("--token-b <token>", "Second token symbol or address (required for V2/Curve/LB)").option("--liquidity <amount>", "Liquidity amount to remove in wei (required for V2/Curve/LB)").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amount-a-min <wei>", "Explicit minimum of token_a accepted on remove (REQUIRED for V3/Algebra/Thena CL \u2014 caller must compute from positions(tokenId) + pool state and apply tolerance).").option("--amount-b-min <wei>", "Explicit minimum of token_b accepted on remove (REQUIRED for V3/Algebra/Thena CL).").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
|
|
9214
9816
|
const executor = makeExecutor2();
|
|
9215
9817
|
const chainName = parent.opts().chain;
|
|
9216
9818
|
if (!chainName) {
|
|
@@ -9226,17 +9828,34 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9226
9828
|
if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
|
|
9227
9829
|
if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
|
|
9228
9830
|
if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
|
|
9831
|
+
if (!opts.tokenA || !opts.tokenB) {
|
|
9832
|
+
throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
|
|
9833
|
+
}
|
|
9229
9834
|
const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
|
|
9230
9835
|
const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
|
|
9231
9836
|
const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
|
|
9232
|
-
const [tokenX, tokenY] = tokenA2.toLowerCase() < tokenB2.toLowerCase() ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
|
|
9233
9837
|
const binIds = opts.bins.split(",").map((s) => parseInt(s.trim()));
|
|
9234
9838
|
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9235
|
-
const
|
|
9236
|
-
address
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9839
|
+
const lbPairOrderAbi = parseAbi30([
|
|
9840
|
+
"function getTokenX() view returns (address)",
|
|
9841
|
+
"function getTokenY() view returns (address)",
|
|
9842
|
+
"function getBinStep() view returns (uint16)"
|
|
9843
|
+
]);
|
|
9844
|
+
const [poolTokenX, poolTokenY, binStep] = await Promise.all([
|
|
9845
|
+
client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenX" }),
|
|
9846
|
+
client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenY" }),
|
|
9847
|
+
client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getBinStep" })
|
|
9848
|
+
]);
|
|
9849
|
+
const aIsX = tokenA2.toLowerCase() === poolTokenX.toLowerCase();
|
|
9850
|
+
const bIsX = tokenB2.toLowerCase() === poolTokenX.toLowerCase();
|
|
9851
|
+
const aIsY = tokenA2.toLowerCase() === poolTokenY.toLowerCase();
|
|
9852
|
+
const bIsY = tokenB2.toLowerCase() === poolTokenY.toLowerCase();
|
|
9853
|
+
if (!(aIsX && bIsY || bIsX && aIsY)) {
|
|
9854
|
+
throw new Error(
|
|
9855
|
+
`[${protocol.name}] --token-a/--token-b (${tokenA2}/${tokenB2}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
|
|
9856
|
+
);
|
|
9857
|
+
}
|
|
9858
|
+
const [tokenX, tokenY] = aIsX ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
|
|
9240
9859
|
let amounts;
|
|
9241
9860
|
if (opts.amounts) {
|
|
9242
9861
|
amounts = opts.amounts.split(",").map((s) => BigInt(s.trim()));
|
|
@@ -9256,6 +9875,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9256
9875
|
gas_estimate: 8e4
|
|
9257
9876
|
};
|
|
9258
9877
|
const tx = await lbAdapter.buildRemoveLiquidity({
|
|
9878
|
+
pool: opts.pool,
|
|
9259
9879
|
tokenX,
|
|
9260
9880
|
tokenY,
|
|
9261
9881
|
binStep,
|
|
@@ -9268,8 +9888,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9268
9888
|
printOutput({ step: "lb_remove", ...result }, getOpts());
|
|
9269
9889
|
return;
|
|
9270
9890
|
}
|
|
9271
|
-
const
|
|
9272
|
-
const
|
|
9891
|
+
const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
|
|
9892
|
+
const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
|
|
9893
|
+
if (!isNftRemove) {
|
|
9894
|
+
const missing = [];
|
|
9895
|
+
if (!opts.tokenA) missing.push("--token-a");
|
|
9896
|
+
if (!opts.tokenB) missing.push("--token-b");
|
|
9897
|
+
if (!opts.liquidity) missing.push("--liquidity");
|
|
9898
|
+
if (missing.length > 0) {
|
|
9899
|
+
printOutput({
|
|
9900
|
+
error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
|
|
9901
|
+
}, getOpts());
|
|
9902
|
+
return;
|
|
9903
|
+
}
|
|
9904
|
+
}
|
|
9905
|
+
const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
|
|
9906
|
+
const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
|
|
9273
9907
|
const poolAddr = opts.pool ? opts.pool : void 0;
|
|
9274
9908
|
let didUnstake = false;
|
|
9275
9909
|
if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
|
|
@@ -9319,7 +9953,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9319
9953
|
if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
|
|
9320
9954
|
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");
|
|
9321
9955
|
}
|
|
9322
|
-
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
|
|
9956
|
+
const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
|
|
9323
9957
|
const withdrawResult = await executor.execute(withdrawTx);
|
|
9324
9958
|
printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
|
|
9325
9959
|
if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
|
|
@@ -9334,13 +9968,37 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9334
9968
|
}
|
|
9335
9969
|
process.stderr.write("Step 2/2: Removing liquidity...\n");
|
|
9336
9970
|
const dexAdapter = createDex(protocol, rpcUrl);
|
|
9971
|
+
let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
|
|
9972
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9973
|
+
const npm = protocol.contracts?.["position_manager"];
|
|
9974
|
+
if (npm) {
|
|
9975
|
+
const c = createPublicClient23({ transport: http23(rpcUrl) });
|
|
9976
|
+
const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
|
|
9977
|
+
if (pos) {
|
|
9978
|
+
removeLiquidity = pos.liquidity;
|
|
9979
|
+
process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
|
|
9980
|
+
`);
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
if (isNftRemove && removeLiquidity === 0n) {
|
|
9985
|
+
printOutput({
|
|
9986
|
+
error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
|
|
9987
|
+
}, getOpts());
|
|
9988
|
+
return;
|
|
9989
|
+
}
|
|
9990
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
9991
|
+
const removePoolAddr = opts.pool ? resolvePoolAddress(registry, opts.protocol, opts.pool) : void 0;
|
|
9337
9992
|
const removeTx = await dexAdapter.buildRemoveLiquidity({
|
|
9338
9993
|
protocol: protocol.name,
|
|
9339
|
-
token_a: tokenA,
|
|
9340
|
-
token_b: tokenB,
|
|
9341
|
-
liquidity:
|
|
9994
|
+
token_a: tokenA ?? ZERO,
|
|
9995
|
+
token_b: tokenB ?? ZERO,
|
|
9996
|
+
liquidity: removeLiquidity,
|
|
9342
9997
|
recipient,
|
|
9343
|
-
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
|
|
9998
|
+
token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0,
|
|
9999
|
+
amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
|
|
10000
|
+
amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0,
|
|
10001
|
+
pool: removePoolAddr
|
|
9344
10002
|
});
|
|
9345
10003
|
const removeResult = await executor.execute(removeTx);
|
|
9346
10004
|
printOutput({ step: "lp_remove", ...removeResult }, getOpts());
|
|
@@ -9693,6 +10351,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
9693
10351
|
// src/commands/lending.ts
|
|
9694
10352
|
init_dist();
|
|
9695
10353
|
init_dist2();
|
|
10354
|
+
import { maxUint256 } from "viem";
|
|
9696
10355
|
|
|
9697
10356
|
// src/utils.ts
|
|
9698
10357
|
init_dist();
|
|
@@ -9717,12 +10376,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
|
|
|
9717
10376
|
return registry.resolveToken(chainName, tokenOrAddress).address;
|
|
9718
10377
|
}
|
|
9719
10378
|
var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
|
|
10379
|
+
var warnedFallback = false;
|
|
9720
10380
|
function resolveWallet(override) {
|
|
9721
10381
|
if (override) return override;
|
|
9722
10382
|
try {
|
|
9723
10383
|
const { address } = resolveWalletWithSigner();
|
|
9724
10384
|
return address;
|
|
9725
10385
|
} catch {
|
|
10386
|
+
if (!warnedFallback) {
|
|
10387
|
+
process.stderr.write(
|
|
10388
|
+
`WARNING: no wallet configured (set DEFI_WALLET_ADDRESS or DEFI_PRIVATE_KEY, or use --wallet <name>). Using placeholder ${FALLBACK_ADDRESS} for dry-run preview ONLY \u2014 do NOT pass --broadcast with this address.
|
|
10389
|
+
`
|
|
10390
|
+
);
|
|
10391
|
+
warnedFallback = true;
|
|
10392
|
+
}
|
|
9726
10393
|
return FALLBACK_ADDRESS;
|
|
9727
10394
|
}
|
|
9728
10395
|
}
|
|
@@ -9734,6 +10401,26 @@ function parseBigIntValue(v) {
|
|
|
9734
10401
|
}
|
|
9735
10402
|
|
|
9736
10403
|
// src/commands/lending.ts
|
|
10404
|
+
function parseAmount(s) {
|
|
10405
|
+
const lower = s.toLowerCase();
|
|
10406
|
+
if (lower === "max" || lower === "all") return maxUint256;
|
|
10407
|
+
return BigInt(s);
|
|
10408
|
+
}
|
|
10409
|
+
function resolveMarketInput(adapter, raw) {
|
|
10410
|
+
if (!raw) return void 0;
|
|
10411
|
+
if (/^0x[0-9a-fA-F]{64}$/.test(raw)) return raw;
|
|
10412
|
+
if (typeof adapter.resolveMarketIdByName !== "function") {
|
|
10413
|
+
throw new Error(
|
|
10414
|
+
`--market must be a 32-byte hex value; got '${raw}'. This adapter does not support named-market lookup.`
|
|
10415
|
+
);
|
|
10416
|
+
}
|
|
10417
|
+
const id = adapter.resolveMarketIdByName(raw);
|
|
10418
|
+
if (id) return id;
|
|
10419
|
+
const known = adapter.listNamedMarkets?.()?.map((m) => m.name).join(", ") ?? "(none)";
|
|
10420
|
+
throw new Error(
|
|
10421
|
+
`--market '${raw}' is not a 32-byte hex and is not registered on this protocol. Known markets: ${known}.`
|
|
10422
|
+
);
|
|
10423
|
+
}
|
|
9737
10424
|
function registerLending(parent, getOpts, makeExecutor2) {
|
|
9738
10425
|
const lending = parent.command("lending").description("Lending operations: supply, borrow, repay, withdraw, rates, position");
|
|
9739
10426
|
lending.command("rates").description("Show current lending rates").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").action(async (opts) => {
|
|
@@ -9756,18 +10443,24 @@ function registerLending(parent, getOpts, makeExecutor2) {
|
|
|
9756
10443
|
const position = await adapter.getUserPosition(address);
|
|
9757
10444
|
printOutput(position, getOpts());
|
|
9758
10445
|
});
|
|
9759
|
-
lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
10446
|
+
lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei (or 'max')").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
9760
10447
|
const executor = makeExecutor2();
|
|
9761
10448
|
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
9762
10449
|
if (!ctx) return;
|
|
9763
10450
|
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
9764
10451
|
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
9765
10452
|
const onBehalfOf = resolveWallet(opts.onBehalfOf);
|
|
9766
|
-
const tx = await adapter.buildSupply({
|
|
10453
|
+
const tx = await adapter.buildSupply({
|
|
10454
|
+
protocol: ctx.protocol.name,
|
|
10455
|
+
asset,
|
|
10456
|
+
amount: parseAmount(opts.amount),
|
|
10457
|
+
on_behalf_of: onBehalfOf,
|
|
10458
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
10459
|
+
});
|
|
9767
10460
|
const result = await executor.execute(tx);
|
|
9768
10461
|
printOutput(result, getOpts());
|
|
9769
10462
|
});
|
|
9770
|
-
lending.command("borrow").description("Borrow an asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
10463
|
+
lending.command("borrow").description("Borrow an asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--rate-mode <mode>", "variable or stable", "variable").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
9771
10464
|
const executor = makeExecutor2();
|
|
9772
10465
|
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
9773
10466
|
if (!ctx) return;
|
|
@@ -9777,14 +10470,15 @@ function registerLending(parent, getOpts, makeExecutor2) {
|
|
|
9777
10470
|
const tx = await adapter.buildBorrow({
|
|
9778
10471
|
protocol: ctx.protocol.name,
|
|
9779
10472
|
asset,
|
|
9780
|
-
amount:
|
|
10473
|
+
amount: parseAmount(opts.amount),
|
|
9781
10474
|
interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
|
|
9782
|
-
on_behalf_of: onBehalfOf
|
|
10475
|
+
on_behalf_of: onBehalfOf,
|
|
10476
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
9783
10477
|
});
|
|
9784
10478
|
const result = await executor.execute(tx);
|
|
9785
10479
|
printOutput(result, getOpts());
|
|
9786
10480
|
});
|
|
9787
|
-
lending.command("repay").description("Repay a borrowed asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
10481
|
+
lending.command("repay").description("Repay a borrowed asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--rate-mode <mode>", "variable or stable", "variable").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
9788
10482
|
const executor = makeExecutor2();
|
|
9789
10483
|
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
9790
10484
|
if (!ctx) return;
|
|
@@ -9794,21 +10488,150 @@ function registerLending(parent, getOpts, makeExecutor2) {
|
|
|
9794
10488
|
const tx = await adapter.buildRepay({
|
|
9795
10489
|
protocol: ctx.protocol.name,
|
|
9796
10490
|
asset,
|
|
9797
|
-
amount:
|
|
10491
|
+
amount: parseAmount(opts.amount),
|
|
9798
10492
|
interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
|
|
9799
|
-
on_behalf_of: onBehalfOf
|
|
10493
|
+
on_behalf_of: onBehalfOf,
|
|
10494
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
9800
10495
|
});
|
|
9801
10496
|
const result = await executor.execute(tx);
|
|
9802
10497
|
printOutput(result, getOpts());
|
|
9803
10498
|
});
|
|
9804
|
-
lending.command("withdraw").description("Withdraw a supplied asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--to <address>", "Recipient address").action(async (opts) => {
|
|
10499
|
+
lending.command("withdraw").description("Withdraw a supplied asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").option("--market <marketId>", "Morpho Blue marketId (32-byte hex) \u2014 required for direct Morpho markets, ignored elsewhere").option("--to <address>", "Recipient address").action(async (opts) => {
|
|
9805
10500
|
const executor = makeExecutor2();
|
|
9806
10501
|
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
9807
10502
|
if (!ctx) return;
|
|
9808
10503
|
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
9809
10504
|
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
9810
10505
|
const to = resolveWallet(opts.to);
|
|
9811
|
-
const tx = await adapter.buildWithdraw({
|
|
10506
|
+
const tx = await adapter.buildWithdraw({
|
|
10507
|
+
protocol: ctx.protocol.name,
|
|
10508
|
+
asset,
|
|
10509
|
+
amount: parseAmount(opts.amount),
|
|
10510
|
+
to,
|
|
10511
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
10512
|
+
});
|
|
10513
|
+
const result = await executor.execute(tx);
|
|
10514
|
+
printOutput(result, getOpts());
|
|
10515
|
+
});
|
|
10516
|
+
lending.command("toggle-collateral").description("Enable or disable a supplied reserve as collateral (Aave V3 family)").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").option("--enable", "Enable as collateral").option("--disable", "Disable as collateral").action(async (opts) => {
|
|
10517
|
+
const executor = makeExecutor2();
|
|
10518
|
+
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
10519
|
+
if (!ctx) return;
|
|
10520
|
+
if (!opts.enable && !opts.disable) {
|
|
10521
|
+
printOutput({ error: "must pass either --enable or --disable" }, getOpts());
|
|
10522
|
+
return;
|
|
10523
|
+
}
|
|
10524
|
+
if (opts.enable && opts.disable) {
|
|
10525
|
+
printOutput({ error: "--enable and --disable are mutually exclusive" }, getOpts());
|
|
10526
|
+
return;
|
|
10527
|
+
}
|
|
10528
|
+
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
10529
|
+
if (typeof adapter.buildSetUseReserveAsCollateral !== "function") {
|
|
10530
|
+
printOutput({
|
|
10531
|
+
error: `[${ctx.protocol.name}] adapter does not implement buildSetUseReserveAsCollateral. Aave V3 forks support this; Compound V2/Morpho Blue use different flows.`
|
|
10532
|
+
}, getOpts());
|
|
10533
|
+
return;
|
|
10534
|
+
}
|
|
10535
|
+
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
10536
|
+
const tx = await adapter.buildSetUseReserveAsCollateral(asset, !!opts.enable);
|
|
10537
|
+
const result = await executor.execute(tx);
|
|
10538
|
+
printOutput(result, getOpts());
|
|
10539
|
+
});
|
|
10540
|
+
lending.command("set-emode").description("Enroll the user in an Aave V3 efficiency-mode category (0 to opt out)").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--category-id <id>", "eMode category id (0 = opt out)").action(async (opts) => {
|
|
10541
|
+
const executor = makeExecutor2();
|
|
10542
|
+
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
10543
|
+
if (!ctx) return;
|
|
10544
|
+
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
10545
|
+
if (typeof adapter.buildSetEMode !== "function") {
|
|
10546
|
+
printOutput({
|
|
10547
|
+
error: `[${ctx.protocol.name}] adapter does not implement buildSetEMode (Aave V3 only)`
|
|
10548
|
+
}, getOpts());
|
|
10549
|
+
return;
|
|
10550
|
+
}
|
|
10551
|
+
const id = parseInt(opts.categoryId, 10);
|
|
10552
|
+
if (!Number.isInteger(id) || id < 0 || id > 255) {
|
|
10553
|
+
printOutput({ error: `--category-id must be an integer in [0, 255], got '${opts.categoryId}'` }, getOpts());
|
|
10554
|
+
return;
|
|
10555
|
+
}
|
|
10556
|
+
const tx = await adapter.buildSetEMode(id);
|
|
10557
|
+
const result = await executor.execute(tx);
|
|
10558
|
+
printOutput(result, getOpts());
|
|
10559
|
+
});
|
|
10560
|
+
lending.command("enter-markets").description("Compound V2 (Venus): enter supplied assets as collateral via Comptroller.enterMarkets").requiredOption("--protocol <protocol>", "Protocol slug (must be a Compound V2 fork)").requiredOption("--asset <token>", "Underlying asset symbol or address (resolved to its cToken)").action(async (opts) => {
|
|
10561
|
+
const executor = makeExecutor2();
|
|
10562
|
+
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
10563
|
+
if (!ctx) return;
|
|
10564
|
+
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
10565
|
+
if (typeof adapter.buildEnterMarkets !== "function") {
|
|
10566
|
+
printOutput({
|
|
10567
|
+
error: `[${ctx.protocol.name}] adapter does not implement buildEnterMarkets. This is a Compound V2 family operation; Aave V3 uses toggle-collateral instead.`
|
|
10568
|
+
}, getOpts());
|
|
10569
|
+
return;
|
|
10570
|
+
}
|
|
10571
|
+
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
10572
|
+
const contracts = ctx.protocol.contracts ?? {};
|
|
10573
|
+
const vTokenEntries = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k));
|
|
10574
|
+
if (vTokenEntries.length === 0) {
|
|
10575
|
+
printOutput({ error: `[${ctx.protocol.name}] no vTokens registered in TOML` }, getOpts());
|
|
10576
|
+
return;
|
|
10577
|
+
}
|
|
10578
|
+
const symbol = opts.asset.toLowerCase();
|
|
10579
|
+
const matchedKey = vTokenEntries.find(([k]) => k.toLowerCase() === `v${symbol}`);
|
|
10580
|
+
const vToken = matchedKey ? matchedKey[1] : void 0;
|
|
10581
|
+
if (!vToken) {
|
|
10582
|
+
printOutput({
|
|
10583
|
+
error: `[${ctx.protocol.name}] could not resolve a vToken for '${opts.asset}'. Registered vTokens: ${vTokenEntries.map(([k]) => k).join(", ")}. Pass --asset matching the symbol after the 'v' prefix (e.g. USDT for vusdt).`
|
|
10584
|
+
}, getOpts());
|
|
10585
|
+
return;
|
|
10586
|
+
}
|
|
10587
|
+
void asset;
|
|
10588
|
+
const tx = await adapter.buildEnterMarkets([vToken]);
|
|
10589
|
+
const result = await executor.execute(tx);
|
|
10590
|
+
printOutput(result, getOpts());
|
|
10591
|
+
});
|
|
10592
|
+
lending.command("supply-collateral").description("Supply the collateral side of a Morpho Blue market (different selector from supply)").requiredOption("--protocol <protocol>", "Protocol slug (must be a Morpho Blue adapter)").requiredOption("--asset <token>", "Collateral token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").requiredOption("--market <marketId>", "32-byte Morpho marketId (find via Morpho API)").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
|
|
10593
|
+
const executor = makeExecutor2();
|
|
10594
|
+
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
10595
|
+
if (!ctx) return;
|
|
10596
|
+
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
10597
|
+
if (typeof adapter.buildSupplyCollateral !== "function") {
|
|
10598
|
+
printOutput({
|
|
10599
|
+
error: `[${ctx.protocol.name}] adapter does not implement buildSupplyCollateral. Only Morpho Blue forks expose this; Aave V3 / Compound use plain supply.`
|
|
10600
|
+
}, getOpts());
|
|
10601
|
+
return;
|
|
10602
|
+
}
|
|
10603
|
+
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
10604
|
+
const onBehalfOf = resolveWallet(opts.onBehalfOf);
|
|
10605
|
+
const tx = await adapter.buildSupplyCollateral({
|
|
10606
|
+
protocol: ctx.protocol.name,
|
|
10607
|
+
asset,
|
|
10608
|
+
amount: parseAmount(opts.amount),
|
|
10609
|
+
on_behalf_of: onBehalfOf,
|
|
10610
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
10611
|
+
});
|
|
10612
|
+
const result = await executor.execute(tx);
|
|
10613
|
+
printOutput(result, getOpts());
|
|
10614
|
+
});
|
|
10615
|
+
lending.command("withdraw-collateral").description("Withdraw the collateral side of a Morpho Blue market").requiredOption("--protocol <protocol>", "Protocol slug (must be a Morpho Blue adapter)").requiredOption("--asset <token>", "Collateral token symbol or address").requiredOption("--amount <amount>", "Amount in wei (or 'max')").requiredOption("--market <marketId>", "32-byte Morpho marketId").option("--to <address>", "Recipient address").action(async (opts) => {
|
|
10616
|
+
const executor = makeExecutor2();
|
|
10617
|
+
const ctx = resolveContext(parent, getOpts, opts.protocol);
|
|
10618
|
+
if (!ctx) return;
|
|
10619
|
+
const adapter = createLending(ctx.protocol, ctx.rpcUrl);
|
|
10620
|
+
if (typeof adapter.buildWithdrawCollateral !== "function") {
|
|
10621
|
+
printOutput({
|
|
10622
|
+
error: `[${ctx.protocol.name}] adapter does not implement buildWithdrawCollateral.`
|
|
10623
|
+
}, getOpts());
|
|
10624
|
+
return;
|
|
10625
|
+
}
|
|
10626
|
+
const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
|
|
10627
|
+
const to = resolveWallet(opts.to);
|
|
10628
|
+
const tx = await adapter.buildWithdrawCollateral({
|
|
10629
|
+
protocol: ctx.protocol.name,
|
|
10630
|
+
asset,
|
|
10631
|
+
amount: parseAmount(opts.amount),
|
|
10632
|
+
to,
|
|
10633
|
+
market_id: resolveMarketInput(adapter, opts.market)
|
|
10634
|
+
});
|
|
9812
10635
|
const result = await executor.execute(tx);
|
|
9813
10636
|
printOutput(result, getOpts());
|
|
9814
10637
|
});
|
|
@@ -9825,7 +10648,8 @@ function resolveAsset(registry, chain, asset) {
|
|
|
9825
10648
|
}
|
|
9826
10649
|
async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
9827
10650
|
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
|
|
9828
|
-
const
|
|
10651
|
+
const rates = [];
|
|
10652
|
+
const errors = [];
|
|
9829
10653
|
let first = true;
|
|
9830
10654
|
for (const proto of protos) {
|
|
9831
10655
|
if (!first) {
|
|
@@ -9834,19 +10658,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
|
|
|
9834
10658
|
first = false;
|
|
9835
10659
|
try {
|
|
9836
10660
|
const lending = createLending(proto, rpc);
|
|
9837
|
-
const
|
|
9838
|
-
|
|
10661
|
+
const r = await lending.getRates(assetAddr);
|
|
10662
|
+
rates.push(r);
|
|
9839
10663
|
} catch (err) {
|
|
9840
10664
|
process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
|
|
9841
10665
|
`);
|
|
10666
|
+
errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
|
|
9842
10667
|
}
|
|
9843
10668
|
}
|
|
9844
|
-
return
|
|
10669
|
+
return { rates, errors };
|
|
9845
10670
|
}
|
|
9846
10671
|
async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
9847
10672
|
const opportunities = [];
|
|
9848
|
-
const
|
|
9849
|
-
|
|
10673
|
+
const errors = [];
|
|
10674
|
+
const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10675
|
+
errors.push(...lendingResult.errors);
|
|
10676
|
+
for (const r of lendingResult.rates) {
|
|
9850
10677
|
if (r.supply_apy > 0) {
|
|
9851
10678
|
opportunities.push({
|
|
9852
10679
|
protocol: r.protocol,
|
|
@@ -9872,7 +10699,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9872
10699
|
utilization: rates.utilization
|
|
9873
10700
|
});
|
|
9874
10701
|
}
|
|
9875
|
-
} catch {
|
|
10702
|
+
} catch (e) {
|
|
10703
|
+
errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
|
|
9876
10704
|
}
|
|
9877
10705
|
}
|
|
9878
10706
|
}
|
|
@@ -9888,7 +10716,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9888
10716
|
apy: info.apy ?? 0,
|
|
9889
10717
|
total_assets: info.total_assets.toString()
|
|
9890
10718
|
});
|
|
9891
|
-
} catch {
|
|
10719
|
+
} catch (e) {
|
|
10720
|
+
errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
|
|
9892
10721
|
}
|
|
9893
10722
|
}
|
|
9894
10723
|
}
|
|
@@ -9897,7 +10726,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
|
|
|
9897
10726
|
const ba = b["apy"] ?? 0;
|
|
9898
10727
|
return ba - aa;
|
|
9899
10728
|
});
|
|
9900
|
-
return opportunities;
|
|
10729
|
+
return { opportunities, errors };
|
|
9901
10730
|
}
|
|
9902
10731
|
async function runYieldScan(registry, asset, output) {
|
|
9903
10732
|
const t0 = Date.now();
|
|
@@ -10039,10 +10868,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10039
10868
|
const chain = registry.getChain(chainName);
|
|
10040
10869
|
const rpc = chain.effectiveRpcUrl();
|
|
10041
10870
|
const assetAddr = resolveAsset(registry, chainName, opts.asset);
|
|
10042
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10871
|
+
const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10043
10872
|
if (results.length === 0) {
|
|
10044
10873
|
printOutput(
|
|
10045
|
-
|
|
10874
|
+
ratesErrors.length > 0 ? {
|
|
10875
|
+
error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
|
|
10876
|
+
failed_probes: ratesErrors
|
|
10877
|
+
} : { error: `No lending rate data available for asset '${opts.asset}'` },
|
|
10046
10878
|
getOpts()
|
|
10047
10879
|
);
|
|
10048
10880
|
process.exit(1);
|
|
@@ -10284,9 +11116,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10284
11116
|
const assetAddr = resolveAsset(registry, chainName, asset);
|
|
10285
11117
|
const strategy = opts.strategy ?? "auto";
|
|
10286
11118
|
if (strategy === "auto") {
|
|
10287
|
-
const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
11119
|
+
const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
|
|
10288
11120
|
if (opportunities.length === 0) {
|
|
10289
|
-
|
|
11121
|
+
if (errors.length > 0) {
|
|
11122
|
+
printOutput({
|
|
11123
|
+
error: `Could not collect yield data for '${asset}': ${errors.length} probe(s) failed (likely RPC throttling or transport error). Retry, or set a private RPC URL via ${chainName.toUpperCase()}_RPC_URL.`,
|
|
11124
|
+
failed_probes: errors
|
|
11125
|
+
}, getOpts());
|
|
11126
|
+
} else {
|
|
11127
|
+
printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
|
|
11128
|
+
}
|
|
10290
11129
|
process.exit(1);
|
|
10291
11130
|
return;
|
|
10292
11131
|
}
|
|
@@ -10316,9 +11155,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10316
11155
|
getOpts()
|
|
10317
11156
|
);
|
|
10318
11157
|
} else if (strategy === "best-supply") {
|
|
10319
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
11158
|
+
const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10320
11159
|
if (results.length === 0) {
|
|
10321
|
-
printOutput(
|
|
11160
|
+
printOutput(
|
|
11161
|
+
rErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${rErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: rErr } : { error: `No lending rate data available for asset '${asset}'` },
|
|
11162
|
+
getOpts()
|
|
11163
|
+
);
|
|
10322
11164
|
process.exit(1);
|
|
10323
11165
|
return;
|
|
10324
11166
|
}
|
|
@@ -10341,9 +11183,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10341
11183
|
getOpts()
|
|
10342
11184
|
);
|
|
10343
11185
|
} else if (strategy === "leverage-loop") {
|
|
10344
|
-
const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
11186
|
+
const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
|
|
10345
11187
|
if (results.length === 0) {
|
|
10346
|
-
printOutput(
|
|
11188
|
+
printOutput(
|
|
11189
|
+
lErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${lErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: lErr } : { error: `No lending rate data available for asset '${asset}'` },
|
|
11190
|
+
getOpts()
|
|
11191
|
+
);
|
|
10347
11192
|
process.exit(1);
|
|
10348
11193
|
return;
|
|
10349
11194
|
}
|
|
@@ -10407,14 +11252,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
10407
11252
|
|
|
10408
11253
|
// src/commands/portfolio.ts
|
|
10409
11254
|
init_dist();
|
|
10410
|
-
import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
|
|
11255
|
+
import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
|
|
10411
11256
|
|
|
10412
11257
|
// src/portfolio-tracker.ts
|
|
10413
11258
|
init_dist();
|
|
10414
11259
|
import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
10415
11260
|
import { homedir } from "os";
|
|
10416
11261
|
import { resolve as resolve3 } from "path";
|
|
10417
|
-
import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
|
|
11262
|
+
import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
|
|
10418
11263
|
var ERC20_ABI4 = parseAbi31([
|
|
10419
11264
|
"function balanceOf(address owner) external view returns (uint256)"
|
|
10420
11265
|
]);
|
|
@@ -10465,7 +11310,16 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10465
11310
|
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
10466
11311
|
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10467
11312
|
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
11313
|
+
const priceTokens = [];
|
|
10468
11314
|
if (oracleAddr) {
|
|
11315
|
+
for (const t of tokenEntries) {
|
|
11316
|
+
calls.push([
|
|
11317
|
+
oracleAddr,
|
|
11318
|
+
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
|
|
11319
|
+
]);
|
|
11320
|
+
callLabels.push(`price:${t.address}`);
|
|
11321
|
+
priceTokens.push(t.address);
|
|
11322
|
+
}
|
|
10469
11323
|
calls.push([
|
|
10470
11324
|
oracleAddr,
|
|
10471
11325
|
encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10476,10 +11330,19 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10476
11330
|
if (calls.length > 0) {
|
|
10477
11331
|
results = await multicallRead(rpc, calls);
|
|
10478
11332
|
}
|
|
11333
|
+
const balanceCount = tokenEntries.length;
|
|
11334
|
+
const lendingCount = lendingProtocols.length;
|
|
11335
|
+
const priceStartIdx = balanceCount + lendingCount;
|
|
10479
11336
|
let nativePriceUsd = 0;
|
|
11337
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10480
11338
|
if (oracleAddr) {
|
|
10481
|
-
|
|
10482
|
-
|
|
11339
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
11340
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
11341
|
+
const px = Number(decodeU256Word(priceData)) / 1e8;
|
|
11342
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
11343
|
+
}
|
|
11344
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
11345
|
+
nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
|
|
10483
11346
|
}
|
|
10484
11347
|
let idx = 0;
|
|
10485
11348
|
const tokens = [];
|
|
@@ -10489,8 +11352,20 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10489
11352
|
const balance = decodeU256Word(results[idx] ?? null);
|
|
10490
11353
|
const balF64 = Number(balance) / 10 ** entry.decimals;
|
|
10491
11354
|
const symbolUpper = entry.symbol.toUpperCase();
|
|
10492
|
-
const
|
|
10493
|
-
|
|
11355
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
11356
|
+
let priceUsd;
|
|
11357
|
+
let valueUsd;
|
|
11358
|
+
if (symbolUpper.includes("USD")) {
|
|
11359
|
+
priceUsd = 1;
|
|
11360
|
+
valueUsd = balF64;
|
|
11361
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
11362
|
+
priceUsd = nativePriceUsd;
|
|
11363
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
11364
|
+
} else {
|
|
11365
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
11366
|
+
priceUsd = px ?? 0;
|
|
11367
|
+
valueUsd = px && px > 0 ? balF64 * px : 0;
|
|
11368
|
+
}
|
|
10494
11369
|
totalValueUsd += valueUsd;
|
|
10495
11370
|
tokens.push({
|
|
10496
11371
|
token: entry.address,
|
|
@@ -10531,6 +11406,23 @@ async function takeSnapshot(chainName, wallet, registry) {
|
|
|
10531
11406
|
}
|
|
10532
11407
|
idx++;
|
|
10533
11408
|
}
|
|
11409
|
+
try {
|
|
11410
|
+
const client = createPublicClient24({ transport: http24(rpc) });
|
|
11411
|
+
const nativeBalance = await client.getBalance({ address: user });
|
|
11412
|
+
if (nativeBalance > 0n) {
|
|
11413
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
11414
|
+
const nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
11415
|
+
totalValueUsd += nativeValueUsd;
|
|
11416
|
+
tokens.push({
|
|
11417
|
+
token: wrappedNative,
|
|
11418
|
+
symbol: chain.native_token ?? "NATIVE",
|
|
11419
|
+
balance: nativeBalance,
|
|
11420
|
+
value_usd: nativeValueUsd,
|
|
11421
|
+
price_usd: nativePriceUsd
|
|
11422
|
+
});
|
|
11423
|
+
}
|
|
11424
|
+
} catch {
|
|
11425
|
+
}
|
|
10534
11426
|
return {
|
|
10535
11427
|
timestamp: Date.now(),
|
|
10536
11428
|
chain: chainName,
|
|
@@ -10648,6 +11540,10 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10648
11540
|
const calls = [];
|
|
10649
11541
|
const callLabels = [];
|
|
10650
11542
|
const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
|
|
11543
|
+
const tokenAddrsByCallIdx = [];
|
|
11544
|
+
const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
|
|
11545
|
+
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
11546
|
+
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
10651
11547
|
for (const symbol of tokenSymbols) {
|
|
10652
11548
|
let entry;
|
|
10653
11549
|
try {
|
|
@@ -10661,6 +11557,7 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10661
11557
|
encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
|
|
10662
11558
|
]);
|
|
10663
11559
|
callLabels.push(`balance:${symbol}`);
|
|
11560
|
+
tokenAddrsByCallIdx.push(entry.address);
|
|
10664
11561
|
}
|
|
10665
11562
|
const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
|
|
10666
11563
|
for (const p of lendingProtocols) {
|
|
@@ -10670,10 +11567,16 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10670
11567
|
]);
|
|
10671
11568
|
callLabels.push(`lending:${p.name}`);
|
|
10672
11569
|
}
|
|
10673
|
-
const
|
|
10674
|
-
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
10675
|
-
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
11570
|
+
const priceTokens = [];
|
|
10676
11571
|
if (oracleAddr) {
|
|
11572
|
+
for (const tokenAddr of tokenAddrsByCallIdx) {
|
|
11573
|
+
calls.push([
|
|
11574
|
+
oracleAddr,
|
|
11575
|
+
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
|
|
11576
|
+
]);
|
|
11577
|
+
callLabels.push(`price:${tokenAddr}`);
|
|
11578
|
+
priceTokens.push(tokenAddr);
|
|
11579
|
+
}
|
|
10677
11580
|
calls.push([
|
|
10678
11581
|
oracleAddr,
|
|
10679
11582
|
encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
@@ -10698,10 +11601,19 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10698
11601
|
printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
|
|
10699
11602
|
return;
|
|
10700
11603
|
}
|
|
11604
|
+
const balanceCallCount = tokenAddrsByCallIdx.length;
|
|
11605
|
+
const lendingCallCount = lendingProtocols.length;
|
|
11606
|
+
const priceStartIdx = balanceCallCount + lendingCallCount;
|
|
10701
11607
|
let nativePriceUsd = 0;
|
|
11608
|
+
const priceByToken = /* @__PURE__ */ new Map();
|
|
10702
11609
|
if (oracleAddr) {
|
|
10703
|
-
|
|
10704
|
-
|
|
11610
|
+
for (let i = 0; i < priceTokens.length; i++) {
|
|
11611
|
+
const priceData = results[priceStartIdx + i] ?? null;
|
|
11612
|
+
const px = Number(decodeU2562(priceData)) / 1e8;
|
|
11613
|
+
if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
|
|
11614
|
+
}
|
|
11615
|
+
const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
|
|
11616
|
+
nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
|
|
10705
11617
|
}
|
|
10706
11618
|
let totalValueUsd = 0;
|
|
10707
11619
|
let idx = 0;
|
|
@@ -10720,12 +11632,21 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10720
11632
|
const decimals = entry.decimals;
|
|
10721
11633
|
const balF64 = Number(balance) / 10 ** decimals;
|
|
10722
11634
|
const symbolUpper = symbol.toUpperCase();
|
|
10723
|
-
const
|
|
10724
|
-
|
|
11635
|
+
const tokenAddrLower = entry.address.toLowerCase();
|
|
11636
|
+
let valueUsd;
|
|
11637
|
+
if (symbolUpper.includes("USD")) {
|
|
11638
|
+
valueUsd = balF64;
|
|
11639
|
+
} else if (tokenAddrLower === wrappedNative.toLowerCase()) {
|
|
11640
|
+
valueUsd = balF64 * nativePriceUsd;
|
|
11641
|
+
} else {
|
|
11642
|
+
const px = priceByToken.get(tokenAddrLower);
|
|
11643
|
+
valueUsd = px && px > 0 ? balF64 * px : null;
|
|
11644
|
+
}
|
|
11645
|
+
if (valueUsd !== null) totalValueUsd += valueUsd;
|
|
10725
11646
|
tokenBalances.push({
|
|
10726
11647
|
symbol,
|
|
10727
11648
|
balance: balF64.toFixed(4),
|
|
10728
|
-
value_usd: valueUsd.toFixed(2)
|
|
11649
|
+
value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
|
|
10729
11650
|
});
|
|
10730
11651
|
}
|
|
10731
11652
|
idx++;
|
|
@@ -10755,11 +11676,23 @@ function registerPortfolio(parent, getOpts) {
|
|
|
10755
11676
|
}
|
|
10756
11677
|
idx++;
|
|
10757
11678
|
}
|
|
11679
|
+
let nativeBalance = 0n;
|
|
11680
|
+
let nativeValueUsd = 0;
|
|
11681
|
+
try {
|
|
11682
|
+
const client = createPublicClient25({ transport: http25(rpc) });
|
|
11683
|
+
nativeBalance = await client.getBalance({ address: user });
|
|
11684
|
+
const nativeF64 = Number(nativeBalance) / 1e18;
|
|
11685
|
+
nativeValueUsd = nativeF64 * nativePriceUsd;
|
|
11686
|
+
if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
|
|
11687
|
+
} catch {
|
|
11688
|
+
}
|
|
10758
11689
|
printOutput(
|
|
10759
11690
|
{
|
|
10760
11691
|
address: user,
|
|
10761
11692
|
chain: chain.name,
|
|
10762
11693
|
native_price_usd: nativePriceUsd.toFixed(2),
|
|
11694
|
+
native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
|
|
11695
|
+
native_value_usd: nativeValueUsd.toFixed(2),
|
|
10763
11696
|
total_value_usd: totalValueUsd.toFixed(2),
|
|
10764
11697
|
token_balances: tokenBalances,
|
|
10765
11698
|
lending_positions: lendingPositions
|
|
@@ -11025,38 +11958,50 @@ function registerPrice(parent, getOpts) {
|
|
|
11025
11958
|
|
|
11026
11959
|
// src/commands/wallet.ts
|
|
11027
11960
|
init_dist();
|
|
11028
|
-
import { createPublicClient as
|
|
11961
|
+
import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
|
|
11962
|
+
function resolveCurrentAddress(override) {
|
|
11963
|
+
if (override) return { address: override, source: "flag" };
|
|
11964
|
+
try {
|
|
11965
|
+
const { address, signer } = resolveWalletWithSigner();
|
|
11966
|
+
return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
|
|
11967
|
+
} catch {
|
|
11968
|
+
return { address: null, source: "none" };
|
|
11969
|
+
}
|
|
11970
|
+
}
|
|
11029
11971
|
function registerWallet(parent, getOpts) {
|
|
11030
11972
|
const wallet = parent.command("wallet").description("Wallet management");
|
|
11031
|
-
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
11973
|
+
wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to OWS vault, DEFI_PRIVATE_KEY, or DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
11032
11974
|
const chainName = requireChain(parent, getOpts);
|
|
11033
11975
|
if (!chainName) return;
|
|
11034
|
-
const addr = opts.address
|
|
11976
|
+
const { address: addr, source } = resolveCurrentAddress(opts.address);
|
|
11035
11977
|
if (!addr) {
|
|
11036
|
-
printOutput({
|
|
11978
|
+
printOutput({
|
|
11979
|
+
error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
|
|
11980
|
+
}, getOpts());
|
|
11037
11981
|
return;
|
|
11038
11982
|
}
|
|
11039
11983
|
const registry = Registry.loadEmbedded();
|
|
11040
11984
|
const chain = registry.getChain(chainName);
|
|
11041
|
-
const client =
|
|
11985
|
+
const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
|
|
11042
11986
|
const balance = await client.getBalance({ address: addr });
|
|
11043
11987
|
printOutput({
|
|
11044
11988
|
chain: chain.name,
|
|
11045
11989
|
address: addr,
|
|
11990
|
+
wallet_source: source,
|
|
11046
11991
|
native_token: chain.native_token,
|
|
11047
11992
|
balance_wei: balance,
|
|
11048
11993
|
balance_formatted: formatEther(balance)
|
|
11049
11994
|
}, getOpts());
|
|
11050
11995
|
});
|
|
11051
11996
|
wallet.command("address").description("Show configured wallet address").action(async () => {
|
|
11052
|
-
const
|
|
11053
|
-
printOutput({ address
|
|
11997
|
+
const { address, source } = resolveCurrentAddress();
|
|
11998
|
+
printOutput({ address, source }, getOpts());
|
|
11054
11999
|
});
|
|
11055
12000
|
}
|
|
11056
12001
|
|
|
11057
12002
|
// src/commands/token.ts
|
|
11058
12003
|
init_dist();
|
|
11059
|
-
import { createPublicClient as
|
|
12004
|
+
import { createPublicClient as createPublicClient27, encodeFunctionData as encodeFunctionData30, http as http27, maxUint256 as maxUint2562, parseAbi as parseAbi34 } from "viem";
|
|
11060
12005
|
function registerToken(parent, getOpts, makeExecutor2) {
|
|
11061
12006
|
const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
|
|
11062
12007
|
token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").option("--owner <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
|
|
@@ -11069,7 +12014,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11069
12014
|
}
|
|
11070
12015
|
const registry = Registry.loadEmbedded();
|
|
11071
12016
|
const chain = registry.getChain(chainName);
|
|
11072
|
-
const client =
|
|
12017
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11073
12018
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11074
12019
|
const [balance, symbol, decimals] = await Promise.all([
|
|
11075
12020
|
client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
|
|
@@ -11090,7 +12035,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11090
12035
|
if (!chainName) return;
|
|
11091
12036
|
const registry = Registry.loadEmbedded();
|
|
11092
12037
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11093
|
-
const amount = opts.amount === "max" ?
|
|
12038
|
+
const amount = opts.amount === "max" ? maxUint2562 : BigInt(opts.amount);
|
|
11094
12039
|
const tx = buildApprove(tokenAddr, opts.spender, amount);
|
|
11095
12040
|
const result = await executor.execute(tx);
|
|
11096
12041
|
printOutput(result, getOpts());
|
|
@@ -11105,7 +12050,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11105
12050
|
}
|
|
11106
12051
|
const registry = Registry.loadEmbedded();
|
|
11107
12052
|
const chain = registry.getChain(chainName);
|
|
11108
|
-
const client =
|
|
12053
|
+
const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
|
|
11109
12054
|
const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
|
|
11110
12055
|
const allowance = await client.readContract({
|
|
11111
12056
|
address: tokenAddr,
|
|
@@ -11115,6 +12060,52 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11115
12060
|
});
|
|
11116
12061
|
printOutput({ token: tokenAddr, owner, spender: opts.spender, allowance }, getOpts());
|
|
11117
12062
|
});
|
|
12063
|
+
const WETH9_ABI = parseAbi34([
|
|
12064
|
+
"function deposit() payable",
|
|
12065
|
+
"function withdraw(uint256 amount)"
|
|
12066
|
+
]);
|
|
12067
|
+
token.command("wrap").description("Wrap native gas token into its ERC-20 wrapped form (WrappedNative.deposit())").requiredOption("--amount <amount>", "Amount of native token to wrap (in wei)").action(async (opts) => {
|
|
12068
|
+
const executor = makeExecutor2();
|
|
12069
|
+
const chainName = requireChain(parent, getOpts);
|
|
12070
|
+
if (!chainName) return;
|
|
12071
|
+
const registry = Registry.loadEmbedded();
|
|
12072
|
+
const chain = registry.getChain(chainName);
|
|
12073
|
+
if (!chain.wrapped_native) {
|
|
12074
|
+
printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
|
|
12075
|
+
return;
|
|
12076
|
+
}
|
|
12077
|
+
const amount = BigInt(opts.amount);
|
|
12078
|
+
const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "deposit" });
|
|
12079
|
+
const result = await executor.execute({
|
|
12080
|
+
description: `[${chainName}] Wrap ${opts.amount} ${chain.native_token} \u2192 W${chain.native_token}`,
|
|
12081
|
+
to: chain.wrapped_native,
|
|
12082
|
+
data,
|
|
12083
|
+
value: amount,
|
|
12084
|
+
gas_estimate: 8e4
|
|
12085
|
+
});
|
|
12086
|
+
printOutput(result, getOpts());
|
|
12087
|
+
});
|
|
12088
|
+
token.command("unwrap").description("Unwrap wrapped-native ERC-20 back into native gas token (WrappedNative.withdraw(amount))").requiredOption("--amount <amount>", "Amount of wrapped token to unwrap (in wei)").action(async (opts) => {
|
|
12089
|
+
const executor = makeExecutor2();
|
|
12090
|
+
const chainName = requireChain(parent, getOpts);
|
|
12091
|
+
if (!chainName) return;
|
|
12092
|
+
const registry = Registry.loadEmbedded();
|
|
12093
|
+
const chain = registry.getChain(chainName);
|
|
12094
|
+
if (!chain.wrapped_native) {
|
|
12095
|
+
printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
|
|
12096
|
+
return;
|
|
12097
|
+
}
|
|
12098
|
+
const amount = BigInt(opts.amount);
|
|
12099
|
+
const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "withdraw", args: [amount] });
|
|
12100
|
+
const result = await executor.execute({
|
|
12101
|
+
description: `[${chainName}] Unwrap ${opts.amount} W${chain.native_token} \u2192 ${chain.native_token}`,
|
|
12102
|
+
to: chain.wrapped_native,
|
|
12103
|
+
data,
|
|
12104
|
+
value: 0n,
|
|
12105
|
+
gas_estimate: 8e4
|
|
12106
|
+
});
|
|
12107
|
+
printOutput(result, getOpts());
|
|
12108
|
+
});
|
|
11118
12109
|
token.command("transfer").description("Transfer tokens to an address").requiredOption("--token <token>", "Token symbol or address").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Amount to transfer (in wei)").action(async (opts) => {
|
|
11119
12110
|
const executor = makeExecutor2();
|
|
11120
12111
|
const chainName = requireChain(parent, getOpts);
|
|
@@ -11129,9 +12120,50 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
11129
12120
|
|
|
11130
12121
|
// src/commands/bridge.ts
|
|
11131
12122
|
init_dist();
|
|
12123
|
+
function resolveDestExecutor(slug, broadcast) {
|
|
12124
|
+
const registry = Registry.loadEmbedded();
|
|
12125
|
+
try {
|
|
12126
|
+
const c = registry.getChain(slug);
|
|
12127
|
+
return new Executor(broadcast, c.effectiveRpcUrl(), c.explorer_url, c.viemChain());
|
|
12128
|
+
} catch {
|
|
12129
|
+
const envKey = `${slug.toUpperCase()}_RPC_URL`;
|
|
12130
|
+
const envVal = process.env[envKey];
|
|
12131
|
+
const meta = DEST_CHAIN_META[slug];
|
|
12132
|
+
if (!meta) throw new Error(`Cannot resolve destination chain: ${slug}`);
|
|
12133
|
+
const rpc = envVal ?? DEST_RPC_FALLBACKS[slug];
|
|
12134
|
+
if (!rpc) throw new Error(`No RPC URL for ${slug}. Set ${envKey} or use a registered chain.`);
|
|
12135
|
+
const minimal = {
|
|
12136
|
+
id: meta.chain_id,
|
|
12137
|
+
name: meta.name,
|
|
12138
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
12139
|
+
rpcUrls: { default: { http: [rpc] } }
|
|
12140
|
+
};
|
|
12141
|
+
return new Executor(broadcast, rpc, void 0, minimal);
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
async function pollCctpAttestation(srcDomain, burnTxHash, maxSeconds) {
|
|
12145
|
+
const intervalMs = 5e3;
|
|
12146
|
+
const deadline = Date.now() + maxSeconds * 1e3;
|
|
12147
|
+
while (Date.now() < deadline) {
|
|
12148
|
+
try {
|
|
12149
|
+
const res = await fetch(`https://iris-api.circle.com/v2/messages/${srcDomain}?transactionHash=${burnTxHash}`);
|
|
12150
|
+
if (res.ok) {
|
|
12151
|
+
const data = await res.json();
|
|
12152
|
+
const m = data.messages?.[0];
|
|
12153
|
+
if (m && m.status === "complete" && m.attestation && m.attestation !== "PENDING") {
|
|
12154
|
+
return { message: m.message, attestation: m.attestation };
|
|
12155
|
+
}
|
|
12156
|
+
}
|
|
12157
|
+
} catch {
|
|
12158
|
+
}
|
|
12159
|
+
await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
|
|
12160
|
+
}
|
|
12161
|
+
throw new Error(`CCTP attestation timeout after ${maxSeconds}s for tx ${burnTxHash}`);
|
|
12162
|
+
}
|
|
11132
12163
|
var LIFI_API = "https://li.quest/v1";
|
|
11133
12164
|
var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
|
|
11134
12165
|
var CCTP_FEE_API = "https://iris-api.circle.com/v2/burn/USDC/fees";
|
|
12166
|
+
var RELAY_API = "https://api.relay.link";
|
|
11135
12167
|
var DEST_CHAIN_META = {
|
|
11136
12168
|
ethereum: { chain_id: 1, name: "Ethereum" },
|
|
11137
12169
|
optimism: { chain_id: 10, name: "Optimism" },
|
|
@@ -11203,6 +12235,63 @@ async function getDebridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amou
|
|
|
11203
12235
|
raw: createData
|
|
11204
12236
|
};
|
|
11205
12237
|
}
|
|
12238
|
+
async function getRelayBridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amountRaw, user, recipient) {
|
|
12239
|
+
const body = {
|
|
12240
|
+
user,
|
|
12241
|
+
recipient,
|
|
12242
|
+
originChainId: srcChainId,
|
|
12243
|
+
destinationChainId: dstChainId,
|
|
12244
|
+
originCurrency: srcToken,
|
|
12245
|
+
destinationCurrency: dstToken,
|
|
12246
|
+
tradeType: "EXACT_INPUT",
|
|
12247
|
+
amount: amountRaw
|
|
12248
|
+
};
|
|
12249
|
+
const res = await fetch(`${RELAY_API}/quote`, {
|
|
12250
|
+
method: "POST",
|
|
12251
|
+
headers: { "content-type": "application/json" },
|
|
12252
|
+
body: JSON.stringify(body)
|
|
12253
|
+
});
|
|
12254
|
+
if (!res.ok) {
|
|
12255
|
+
const text = await res.text();
|
|
12256
|
+
let code = "";
|
|
12257
|
+
let msg = text;
|
|
12258
|
+
try {
|
|
12259
|
+
const j = JSON.parse(text);
|
|
12260
|
+
code = String(j.errorCode ?? j.code ?? "");
|
|
12261
|
+
msg = String(j.message ?? text);
|
|
12262
|
+
} catch {
|
|
12263
|
+
}
|
|
12264
|
+
const hints = {
|
|
12265
|
+
AMOUNT_TOO_LOW: "Try a larger amount or use --provider lifi (Relay enforces a per-route fee floor)",
|
|
12266
|
+
INVALID_INPUT_CURRENCY: "Source token unsupported by Relay on this chain \u2014 try --provider lifi",
|
|
12267
|
+
INVALID_OUTPUT_CURRENCY: "Destination token unsupported by Relay on this chain \u2014 try --provider lifi or change --token",
|
|
12268
|
+
NO_QUOTES: "No Relay liquidity for this route right now \u2014 try --provider lifi or retry later",
|
|
12269
|
+
AMOUNT_TOO_HIGH: "Amount exceeds Relay per-tx cap \u2014 split into smaller amounts",
|
|
12270
|
+
UNSUPPORTED_CHAIN: "Relay does not support this origin/destination chain \u2014 use --provider lifi"
|
|
12271
|
+
};
|
|
12272
|
+
const hint = hints[code];
|
|
12273
|
+
const detail = code ? `${code}: ${msg}` : msg;
|
|
12274
|
+
throw new Error(`Relay quote failed (${res.status}): ${detail}${hint ? ` \u2014 ${hint}` : ""}`);
|
|
12275
|
+
}
|
|
12276
|
+
const json = await res.json();
|
|
12277
|
+
const steps = json.steps;
|
|
12278
|
+
const swapStep = steps?.find((s) => s.id !== "approve") ?? steps?.[steps?.length ?? 1 - 1];
|
|
12279
|
+
const items = swapStep?.items;
|
|
12280
|
+
const txData = items?.[0]?.data;
|
|
12281
|
+
if (!txData) throw new Error("Relay: no executable step in cross-chain quote");
|
|
12282
|
+
const details = json.details;
|
|
12283
|
+
const currencyOut = details?.currencyOut;
|
|
12284
|
+
const timeEst = details?.timeEstimate ?? 30;
|
|
12285
|
+
return {
|
|
12286
|
+
to: String(txData.to),
|
|
12287
|
+
data: String(txData.data),
|
|
12288
|
+
value: String(txData.value ?? "0x0"),
|
|
12289
|
+
amountOut: String(currencyOut?.amount ?? "0"),
|
|
12290
|
+
estimatedTime: timeEst,
|
|
12291
|
+
tool: String(swapStep?.id ?? "relay"),
|
|
12292
|
+
raw: json
|
|
12293
|
+
};
|
|
12294
|
+
}
|
|
11206
12295
|
var CCTP_DOMAINS = {
|
|
11207
12296
|
ethereum: 0,
|
|
11208
12297
|
avalanche: 1,
|
|
@@ -11215,6 +12304,16 @@ var CCTP_DOMAINS = {
|
|
|
11215
12304
|
aptos: 9
|
|
11216
12305
|
};
|
|
11217
12306
|
var CCTP_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
|
|
12307
|
+
var CCTP_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
|
|
12308
|
+
var DEST_RPC_FALLBACKS = {
|
|
12309
|
+
ethereum: "https://eth.merkle.io",
|
|
12310
|
+
arbitrum: "https://arbitrum.drpc.org",
|
|
12311
|
+
optimism: "https://optimism.drpc.org",
|
|
12312
|
+
polygon: "https://polygon.drpc.org",
|
|
12313
|
+
avalanche: "https://avalanche.drpc.org",
|
|
12314
|
+
linea: "https://linea.drpc.org",
|
|
12315
|
+
zksync: "https://zksync.drpc.org"
|
|
12316
|
+
};
|
|
11218
12317
|
var CCTP_USDC_ADDRESSES = {
|
|
11219
12318
|
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
11220
12319
|
avalanche: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
@@ -11223,6 +12322,17 @@ var CCTP_USDC_ADDRESSES = {
|
|
|
11223
12322
|
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
11224
12323
|
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
11225
12324
|
};
|
|
12325
|
+
function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
|
|
12326
|
+
if (amountWei <= maxFeeSubunits) {
|
|
12327
|
+
const amountUsdc = Number(amountWei) / 1e6;
|
|
12328
|
+
return {
|
|
12329
|
+
error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
|
|
12330
|
+
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
12331
|
+
minimum_amount_usdc: feeUsdc
|
|
12332
|
+
};
|
|
12333
|
+
}
|
|
12334
|
+
return null;
|
|
12335
|
+
}
|
|
11226
12336
|
async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
|
|
11227
12337
|
try {
|
|
11228
12338
|
const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
|
|
@@ -11247,8 +12357,8 @@ async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
|
|
|
11247
12357
|
}
|
|
11248
12358
|
return { fee: 0.25, maxFeeSubunits: 250000n };
|
|
11249
12359
|
}
|
|
11250
|
-
function registerBridge(parent, getOpts) {
|
|
11251
|
-
parent.command("bridge").description("Cross-chain bridge: move assets between chains").requiredOption("--token <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").requiredOption("--to-chain <chain>", "Destination chain name").option("--recipient <address>", "Recipient address on destination chain").option("--slippage <bps>", "Slippage in bps (LI.FI only)", "50").option("--provider <name>", "Bridge provider: lifi, debridge, cctp", "lifi").action(async (opts) => {
|
|
12360
|
+
function registerBridge(parent, getOpts, makeExecutor2) {
|
|
12361
|
+
parent.command("bridge").description("Cross-chain bridge: move assets between chains").requiredOption("--token <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").requiredOption("--to-chain <chain>", "Destination chain name").option("--recipient <address>", "Recipient address on destination chain").option("--slippage <bps>", "Slippage in bps (LI.FI only)", "50").option("--provider <name>", "Bridge provider: lifi, relay, debridge, cctp", "lifi").option("--auto-receive", "(CCTP only) After burn, poll Circle attestation and auto-call MessageTransmitter.receiveMessage on destination", false).option("--receive-timeout <seconds>", "(CCTP --auto-receive) max seconds to wait for attestation", "1200").action(async (opts) => {
|
|
11252
12362
|
const chainName = requireChain(parent, getOpts);
|
|
11253
12363
|
if (!chainName) return;
|
|
11254
12364
|
const registry = Registry.loadEmbedded();
|
|
@@ -11261,8 +12371,55 @@ function registerBridge(parent, getOpts) {
|
|
|
11261
12371
|
return;
|
|
11262
12372
|
}
|
|
11263
12373
|
const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
|
|
12374
|
+
let dstTokenAddr = tokenAddr;
|
|
12375
|
+
if (!opts.token.startsWith("0x")) {
|
|
12376
|
+
try {
|
|
12377
|
+
dstTokenAddr = registry.resolveToken(opts.toChain, opts.token).address;
|
|
12378
|
+
} catch {
|
|
12379
|
+
}
|
|
12380
|
+
}
|
|
11264
12381
|
const recipient = resolveWallet(opts.recipient);
|
|
11265
12382
|
const provider = opts.provider.toLowerCase();
|
|
12383
|
+
if (provider === "relay") {
|
|
12384
|
+
try {
|
|
12385
|
+
const result = await getRelayBridgeQuote(
|
|
12386
|
+
fromChain.chain_id,
|
|
12387
|
+
toChain.chain_id,
|
|
12388
|
+
tokenAddr,
|
|
12389
|
+
dstTokenAddr,
|
|
12390
|
+
opts.amount,
|
|
12391
|
+
recipient,
|
|
12392
|
+
recipient
|
|
12393
|
+
);
|
|
12394
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
|
|
12395
|
+
const executor = makeExecutor2();
|
|
12396
|
+
const approvals = isNative ? [] : [{
|
|
12397
|
+
token: tokenAddr,
|
|
12398
|
+
spender: result.to,
|
|
12399
|
+
amount: BigInt(opts.amount)
|
|
12400
|
+
}];
|
|
12401
|
+
const action = await executor.execute({
|
|
12402
|
+
to: result.to,
|
|
12403
|
+
data: result.data,
|
|
12404
|
+
value: BigInt(result.value || "0"),
|
|
12405
|
+
description: `Relay bridge ${fromChain.name} \u2192 ${toChain.name}`,
|
|
12406
|
+
approvals
|
|
12407
|
+
});
|
|
12408
|
+
printOutput({
|
|
12409
|
+
from_chain: fromChain.name,
|
|
12410
|
+
to_chain: toChain.name,
|
|
12411
|
+
token: tokenAddr,
|
|
12412
|
+
amount: opts.amount,
|
|
12413
|
+
bridge: `Relay (${result.tool})`,
|
|
12414
|
+
estimated_output: result.amountOut,
|
|
12415
|
+
estimated_time_seconds: result.estimatedTime,
|
|
12416
|
+
action
|
|
12417
|
+
}, getOpts());
|
|
12418
|
+
} catch (e) {
|
|
12419
|
+
printOutput({ error: `Relay API error: ${errMsg(e)}` }, getOpts());
|
|
12420
|
+
}
|
|
12421
|
+
return;
|
|
12422
|
+
}
|
|
11266
12423
|
if (provider === "debridge") {
|
|
11267
12424
|
try {
|
|
11268
12425
|
const srcId = DLN_CHAIN_IDS[chainName] ?? fromChain.chain_id;
|
|
@@ -11271,11 +12428,29 @@ function registerBridge(parent, getOpts) {
|
|
|
11271
12428
|
srcId,
|
|
11272
12429
|
dstId,
|
|
11273
12430
|
tokenAddr,
|
|
11274
|
-
|
|
12431
|
+
dstTokenAddr,
|
|
11275
12432
|
opts.amount,
|
|
11276
12433
|
recipient
|
|
11277
12434
|
);
|
|
11278
12435
|
const tx = result.raw.tx;
|
|
12436
|
+
if (!tx?.to || !tx?.data) {
|
|
12437
|
+
printOutput({ error: "deBridge: API did not return a tx envelope" }, getOpts());
|
|
12438
|
+
return;
|
|
12439
|
+
}
|
|
12440
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
|
|
12441
|
+
const executor = makeExecutor2();
|
|
12442
|
+
const approvals = isNative ? [] : [{
|
|
12443
|
+
token: tokenAddr,
|
|
12444
|
+
spender: tx.to,
|
|
12445
|
+
amount: BigInt(opts.amount)
|
|
12446
|
+
}];
|
|
12447
|
+
const action = await executor.execute({
|
|
12448
|
+
to: tx.to,
|
|
12449
|
+
data: tx.data,
|
|
12450
|
+
value: BigInt(tx.value ?? "0"),
|
|
12451
|
+
description: `deBridge DLN ${fromChain.name} \u2192 ${toChain.name}`,
|
|
12452
|
+
approvals
|
|
12453
|
+
});
|
|
11279
12454
|
printOutput({
|
|
11280
12455
|
from_chain: fromChain.name,
|
|
11281
12456
|
to_chain: toChain.name,
|
|
@@ -11284,7 +12459,7 @@ function registerBridge(parent, getOpts) {
|
|
|
11284
12459
|
bridge: "deBridge DLN",
|
|
11285
12460
|
estimated_output: result.amountOut,
|
|
11286
12461
|
estimated_time_seconds: result.estimatedTime,
|
|
11287
|
-
|
|
12462
|
+
action
|
|
11288
12463
|
}, getOpts());
|
|
11289
12464
|
} catch (e) {
|
|
11290
12465
|
printOutput({ error: `deBridge API error: ${errMsg(e)}` }, getOpts());
|
|
@@ -11311,20 +12486,17 @@ function registerBridge(parent, getOpts) {
|
|
|
11311
12486
|
}
|
|
11312
12487
|
const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
|
|
11313
12488
|
const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
minimum_amount_wei: maxFeeSubunits.toString(),
|
|
11318
|
-
minimum_amount_usdc: fee
|
|
11319
|
-
}, getOpts());
|
|
12489
|
+
const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
|
|
12490
|
+
if (guardErr) {
|
|
12491
|
+
printOutput(guardErr, getOpts());
|
|
11320
12492
|
return;
|
|
11321
12493
|
}
|
|
11322
12494
|
const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
|
|
11323
|
-
const { encodeFunctionData:
|
|
11324
|
-
const tokenMessengerAbi =
|
|
12495
|
+
const { encodeFunctionData: encodeFunctionData33, parseAbi: parseAbi36 } = await import("viem");
|
|
12496
|
+
const tokenMessengerAbi = parseAbi36([
|
|
11325
12497
|
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) external returns (uint64 nonce)"
|
|
11326
12498
|
]);
|
|
11327
|
-
const data =
|
|
12499
|
+
const data = encodeFunctionData33({
|
|
11328
12500
|
abi: tokenMessengerAbi,
|
|
11329
12501
|
functionName: "depositForBurn",
|
|
11330
12502
|
args: [
|
|
@@ -11339,6 +12511,48 @@ function registerBridge(parent, getOpts) {
|
|
|
11339
12511
|
// standard finality
|
|
11340
12512
|
]
|
|
11341
12513
|
});
|
|
12514
|
+
const executor = makeExecutor2();
|
|
12515
|
+
const burnAction = await executor.execute({
|
|
12516
|
+
to: CCTP_TOKEN_MESSENGER_V2,
|
|
12517
|
+
data,
|
|
12518
|
+
value: 0n,
|
|
12519
|
+
description: `CCTP burn ${fromChain.name} \u2192 ${toChain.name}`,
|
|
12520
|
+
approvals: [{
|
|
12521
|
+
token: usdcSrc,
|
|
12522
|
+
spender: CCTP_TOKEN_MESSENGER_V2,
|
|
12523
|
+
amount: BigInt(opts.amount)
|
|
12524
|
+
}]
|
|
12525
|
+
});
|
|
12526
|
+
let receiveAction = void 0;
|
|
12527
|
+
if (opts.autoReceive && burnAction.tx_hash && burnAction.status === "confirmed") {
|
|
12528
|
+
try {
|
|
12529
|
+
const timeoutSec = parseInt(opts.receiveTimeout) || 1200;
|
|
12530
|
+
process.stderr.write(`Polling Circle attestation for ${burnAction.tx_hash} (max ${timeoutSec}s)...
|
|
12531
|
+
`);
|
|
12532
|
+
const { message, attestation } = await pollCctpAttestation(srcDomain, burnAction.tx_hash, timeoutSec);
|
|
12533
|
+
process.stderr.write(`Attestation ready. Calling receiveMessage on ${opts.toChain}...
|
|
12534
|
+
`);
|
|
12535
|
+
const { encodeFunctionData: encReceive, parseAbi: parseAbiReceive } = await import("viem");
|
|
12536
|
+
const receiveAbi = parseAbiReceive([
|
|
12537
|
+
"function receiveMessage(bytes message, bytes attestation) external"
|
|
12538
|
+
]);
|
|
12539
|
+
const receiveData = encReceive({
|
|
12540
|
+
abi: receiveAbi,
|
|
12541
|
+
functionName: "receiveMessage",
|
|
12542
|
+
args: [message, attestation]
|
|
12543
|
+
});
|
|
12544
|
+
const destExecutor = resolveDestExecutor(opts.toChain, !!parent.opts().broadcast);
|
|
12545
|
+
receiveAction = await destExecutor.execute({
|
|
12546
|
+
to: CCTP_MESSAGE_TRANSMITTER_V2,
|
|
12547
|
+
data: receiveData,
|
|
12548
|
+
value: 0n,
|
|
12549
|
+
description: `CCTP receive on ${toChain.name}`,
|
|
12550
|
+
approvals: []
|
|
12551
|
+
});
|
|
12552
|
+
} catch (e) {
|
|
12553
|
+
receiveAction = { error: `auto-receive failed: ${errMsg(e)}` };
|
|
12554
|
+
}
|
|
12555
|
+
}
|
|
11342
12556
|
printOutput({
|
|
11343
12557
|
from_chain: fromChain.name,
|
|
11344
12558
|
to_chain: toChain.name,
|
|
@@ -11348,12 +12562,9 @@ function registerBridge(parent, getOpts) {
|
|
|
11348
12562
|
bridge: "Circle CCTP V2",
|
|
11349
12563
|
estimated_fee_usdc: fee,
|
|
11350
12564
|
estimated_output: String(BigInt(opts.amount) - maxFeeSubunits),
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
data,
|
|
11355
|
-
value: "0x0"
|
|
11356
|
-
}
|
|
12565
|
+
burn: burnAction,
|
|
12566
|
+
receive: receiveAction,
|
|
12567
|
+
note: opts.autoReceive ? void 0 : "Pass --auto-receive to poll Circle attestation and auto-call MessageTransmitter.receiveMessage on the destination chain."
|
|
11357
12568
|
}, getOpts());
|
|
11358
12569
|
} catch (e) {
|
|
11359
12570
|
printOutput({ error: `CCTP error: ${errMsg(e)}` }, getOpts());
|
|
@@ -11365,26 +12576,41 @@ function registerBridge(parent, getOpts) {
|
|
|
11365
12576
|
fromChain: String(fromChain.chain_id),
|
|
11366
12577
|
toChain: String(toChain.chain_id),
|
|
11367
12578
|
fromToken: tokenAddr,
|
|
11368
|
-
toToken:
|
|
12579
|
+
toToken: dstTokenAddr,
|
|
11369
12580
|
fromAmount: opts.amount,
|
|
11370
12581
|
fromAddress: recipient,
|
|
11371
12582
|
slippage: String(parseInt(opts.slippage) / 1e4)
|
|
11372
12583
|
});
|
|
11373
12584
|
const res = await fetch(`${LIFI_API}/quote?${params}`);
|
|
11374
12585
|
const quote = await res.json();
|
|
11375
|
-
if (quote.transactionRequest) {
|
|
11376
|
-
printOutput({
|
|
11377
|
-
from_chain: fromChain.name,
|
|
11378
|
-
to_chain: toChain.name,
|
|
11379
|
-
token: tokenAddr,
|
|
11380
|
-
amount: opts.amount,
|
|
11381
|
-
bridge: quote.toolDetails?.name ?? "LI.FI",
|
|
11382
|
-
estimated_output: quote.estimate?.toAmount,
|
|
11383
|
-
tx: { to: quote.transactionRequest.to, data: quote.transactionRequest.data, value: quote.transactionRequest.value }
|
|
11384
|
-
}, getOpts());
|
|
11385
|
-
} else {
|
|
12586
|
+
if (!quote.transactionRequest) {
|
|
11386
12587
|
printOutput({ error: "No LI.FI route found", details: quote }, getOpts());
|
|
12588
|
+
return;
|
|
11387
12589
|
}
|
|
12590
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
|
|
12591
|
+
const spender = quote.estimate?.approvalAddress ?? quote.transactionRequest.to;
|
|
12592
|
+
const executor = makeExecutor2();
|
|
12593
|
+
const approvals = isNative ? [] : [{
|
|
12594
|
+
token: tokenAddr,
|
|
12595
|
+
spender,
|
|
12596
|
+
amount: BigInt(opts.amount)
|
|
12597
|
+
}];
|
|
12598
|
+
const action = await executor.execute({
|
|
12599
|
+
to: quote.transactionRequest.to,
|
|
12600
|
+
data: quote.transactionRequest.data,
|
|
12601
|
+
value: BigInt(quote.transactionRequest.value ?? "0"),
|
|
12602
|
+
description: `LI.FI ${fromChain.name} \u2192 ${toChain.name}`,
|
|
12603
|
+
approvals
|
|
12604
|
+
});
|
|
12605
|
+
printOutput({
|
|
12606
|
+
from_chain: fromChain.name,
|
|
12607
|
+
to_chain: toChain.name,
|
|
12608
|
+
token: tokenAddr,
|
|
12609
|
+
amount: opts.amount,
|
|
12610
|
+
bridge: quote.toolDetails?.name ?? "LI.FI",
|
|
12611
|
+
estimated_output: quote.estimate?.toAmount,
|
|
12612
|
+
action
|
|
12613
|
+
}, getOpts());
|
|
11388
12614
|
} catch (e) {
|
|
11389
12615
|
printOutput({ error: `LI.FI API error: ${errMsg(e)}` }, getOpts());
|
|
11390
12616
|
}
|
|
@@ -11472,7 +12698,7 @@ async function lifiQuote(chainId, fromToken, toToken, fromAmount, fromAddress, s
|
|
|
11472
12698
|
outAmount: String(estimate?.toAmount ?? "0")
|
|
11473
12699
|
};
|
|
11474
12700
|
}
|
|
11475
|
-
var
|
|
12701
|
+
var RELAY_API2 = "https://api.relay.link";
|
|
11476
12702
|
async function relayQuote(chainId, fromToken, toToken, amount, user) {
|
|
11477
12703
|
const body = {
|
|
11478
12704
|
user,
|
|
@@ -11484,7 +12710,7 @@ async function relayQuote(chainId, fromToken, toToken, amount, user) {
|
|
|
11484
12710
|
tradeType: "EXACT_INPUT",
|
|
11485
12711
|
amount
|
|
11486
12712
|
};
|
|
11487
|
-
const res = await fetch(`${
|
|
12713
|
+
const res = await fetch(`${RELAY_API2}/quote`, {
|
|
11488
12714
|
method: "POST",
|
|
11489
12715
|
headers: { "content-type": "application/json" },
|
|
11490
12716
|
body: JSON.stringify(body)
|
|
@@ -11524,7 +12750,7 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
|
|
|
11524
12750
|
};
|
|
11525
12751
|
}
|
|
11526
12752
|
function registerSwap(parent, getOpts, makeExecutor2) {
|
|
11527
|
-
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", "
|
|
12753
|
+
parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap, LI.FI, Relay)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid, lifi, relay", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "100").action(async (opts) => {
|
|
11528
12754
|
const executor = makeExecutor2();
|
|
11529
12755
|
const chainName = requireChain(parent, getOpts);
|
|
11530
12756
|
if (!chainName) return;
|
|
@@ -11553,12 +12779,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
11553
12779
|
wallet,
|
|
11554
12780
|
slippageBps
|
|
11555
12781
|
);
|
|
12782
|
+
const fromLowerKyber = fromAddr.toLowerCase();
|
|
12783
|
+
const isNativeInputKyber = fromLowerKyber === "0x0000000000000000000000000000000000000000" || fromLowerKyber === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
11556
12784
|
const tx = {
|
|
11557
12785
|
description: `KyberSwap: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11558
12786
|
to: txData.to,
|
|
11559
12787
|
data: txData.data,
|
|
11560
|
-
value: parseBigIntValue(txData.value),
|
|
11561
|
-
approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }]
|
|
12788
|
+
value: isNativeInputKyber ? BigInt(opts.amount) : parseBigIntValue(txData.value),
|
|
12789
|
+
...isNativeInputKyber ? {} : { approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }] }
|
|
11562
12790
|
};
|
|
11563
12791
|
const result = await executor.execute(tx);
|
|
11564
12792
|
printOutput({
|
|
@@ -11597,12 +12825,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
11597
12825
|
slippagePct,
|
|
11598
12826
|
wallet
|
|
11599
12827
|
);
|
|
12828
|
+
const fromLower = fromAddr.toLowerCase();
|
|
12829
|
+
const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
11600
12830
|
const tx = {
|
|
11601
12831
|
description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
|
|
11602
12832
|
to: swap.to,
|
|
11603
12833
|
data: swap.data,
|
|
11604
12834
|
value: parseBigIntValue(swap.value),
|
|
11605
|
-
approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
|
|
12835
|
+
...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
|
|
11606
12836
|
};
|
|
11607
12837
|
const result = await executor.execute(tx);
|
|
11608
12838
|
printOutput({
|
|
@@ -11694,7 +12924,8 @@ import pc2 from "picocolors";
|
|
|
11694
12924
|
import { createInterface } from "readline";
|
|
11695
12925
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
11696
12926
|
import { resolve as resolve4 } from "path";
|
|
11697
|
-
|
|
12927
|
+
import { homedir as homedir2 } from "os";
|
|
12928
|
+
var DEFI_DIR = resolve4(homedir2(), ".defi");
|
|
11698
12929
|
var ENV_FILE = resolve4(DEFI_DIR, ".env");
|
|
11699
12930
|
function ensureDefiDir() {
|
|
11700
12931
|
if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
|
|
@@ -11729,6 +12960,44 @@ function writeEnvFile(env) {
|
|
|
11729
12960
|
function ask(rl, question) {
|
|
11730
12961
|
return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
|
|
11731
12962
|
}
|
|
12963
|
+
function askSecret(rl, question) {
|
|
12964
|
+
const stdin = process.stdin;
|
|
12965
|
+
if (!stdin.isTTY) return ask(rl, question);
|
|
12966
|
+
process.stdout.write(question);
|
|
12967
|
+
const rlAny = rl;
|
|
12968
|
+
const original = rlAny._writeToOutput;
|
|
12969
|
+
rlAny._writeToOutput = (s) => {
|
|
12970
|
+
if (s.includes("\n") || s.includes("\r")) {
|
|
12971
|
+
original?.call(rl, s);
|
|
12972
|
+
}
|
|
12973
|
+
};
|
|
12974
|
+
return new Promise((res) => {
|
|
12975
|
+
rl.question("", (answer) => {
|
|
12976
|
+
rlAny._writeToOutput = original;
|
|
12977
|
+
process.stdout.write("\n");
|
|
12978
|
+
res(answer.trim());
|
|
12979
|
+
});
|
|
12980
|
+
});
|
|
12981
|
+
}
|
|
12982
|
+
function maskRpcUrl(s) {
|
|
12983
|
+
try {
|
|
12984
|
+
const u = new URL(s);
|
|
12985
|
+
if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
|
|
12986
|
+
return `${u.protocol}//${u.host}/***`;
|
|
12987
|
+
}
|
|
12988
|
+
return `${u.protocol}//${u.host}`;
|
|
12989
|
+
} catch {
|
|
12990
|
+
return "***";
|
|
12991
|
+
}
|
|
12992
|
+
}
|
|
12993
|
+
function isValidRpcUrl(s) {
|
|
12994
|
+
try {
|
|
12995
|
+
const u = new URL(s);
|
|
12996
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
12997
|
+
} catch {
|
|
12998
|
+
return false;
|
|
12999
|
+
}
|
|
13000
|
+
}
|
|
11732
13001
|
function isValidAddress(s) {
|
|
11733
13002
|
return /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
11734
13003
|
}
|
|
@@ -11753,7 +13022,14 @@ function registerSetup(program2) {
|
|
|
11753
13022
|
if (Object.keys(existing).length > 0) {
|
|
11754
13023
|
console.log(pc2.white(" Current configuration:"));
|
|
11755
13024
|
for (const [key, value] of Object.entries(existing)) {
|
|
11756
|
-
|
|
13025
|
+
let masked;
|
|
13026
|
+
if (key.toLowerCase().includes("key")) {
|
|
13027
|
+
masked = value.slice(0, 6) + "..." + value.slice(-4);
|
|
13028
|
+
} else if (key.endsWith("RPC_URL")) {
|
|
13029
|
+
masked = maskRpcUrl(value);
|
|
13030
|
+
} else {
|
|
13031
|
+
masked = value;
|
|
13032
|
+
}
|
|
11757
13033
|
console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
|
|
11758
13034
|
}
|
|
11759
13035
|
console.log();
|
|
@@ -11767,7 +13043,7 @@ function registerSetup(program2) {
|
|
|
11767
13043
|
}
|
|
11768
13044
|
const newEnv = {};
|
|
11769
13045
|
console.log(pc2.cyan(pc2.bold(" Wallet")));
|
|
11770
|
-
const privateKey = await
|
|
13046
|
+
const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
|
|
11771
13047
|
if (privateKey) {
|
|
11772
13048
|
const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
11773
13049
|
if (!isValidPrivateKey(normalized)) {
|
|
@@ -11803,6 +13079,10 @@ function registerSetup(program2) {
|
|
|
11803
13079
|
for (const { env, label } of rpcPrompts) {
|
|
11804
13080
|
const value = await ask(rl, ` ${label} RPC URL: `);
|
|
11805
13081
|
if (value) {
|
|
13082
|
+
if (!isValidRpcUrl(value)) {
|
|
13083
|
+
console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
|
|
13084
|
+
continue;
|
|
13085
|
+
}
|
|
11806
13086
|
newEnv[env] = value;
|
|
11807
13087
|
console.log(` ${pc2.green("OK")} ${label} RPC set`);
|
|
11808
13088
|
}
|
|
@@ -11826,7 +13106,7 @@ function registerSetup(program2) {
|
|
|
11826
13106
|
];
|
|
11827
13107
|
for (const [k, label] of rpcSummary) {
|
|
11828
13108
|
const v = finalEnv[k];
|
|
11829
|
-
if (v) console.log(` ${label}: ${pc2.gray(v)}`);
|
|
13109
|
+
if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
|
|
11830
13110
|
}
|
|
11831
13111
|
console.log(pc2.bold(pc2.white("\n Next steps:")));
|
|
11832
13112
|
console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
|
|
@@ -11845,11 +13125,11 @@ function registerSetup(program2) {
|
|
|
11845
13125
|
// src/commands/ows.ts
|
|
11846
13126
|
import pc3 from "picocolors";
|
|
11847
13127
|
init_dist();
|
|
11848
|
-
import { createPublicClient as
|
|
13128
|
+
import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
|
|
11849
13129
|
async function getEvmBalance(address, chainName) {
|
|
11850
13130
|
const registry = Registry.loadEmbedded();
|
|
11851
13131
|
const chain = registry.getChain(chainName);
|
|
11852
|
-
const client =
|
|
13132
|
+
const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
|
|
11853
13133
|
const balance = await client.getBalance({ address });
|
|
11854
13134
|
return {
|
|
11855
13135
|
native_token: chain.native_token,
|
|
@@ -12073,7 +13353,8 @@ function makeExecutor() {
|
|
|
12073
13353
|
process.exit(1);
|
|
12074
13354
|
}
|
|
12075
13355
|
const chain = registry.getChain(opts.chain);
|
|
12076
|
-
|
|
13356
|
+
const viemChain = chain.viemChain();
|
|
13357
|
+
return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url, viemChain);
|
|
12077
13358
|
}
|
|
12078
13359
|
registerStatus(program, getOutputMode);
|
|
12079
13360
|
registerSchema(program, getOutputMode);
|
|
@@ -12084,7 +13365,7 @@ registerPortfolio(program, getOutputMode);
|
|
|
12084
13365
|
registerPrice(program, getOutputMode);
|
|
12085
13366
|
registerWallet(program, getOutputMode);
|
|
12086
13367
|
registerToken(program, getOutputMode, makeExecutor);
|
|
12087
|
-
registerBridge(program, getOutputMode);
|
|
13368
|
+
registerBridge(program, getOutputMode, makeExecutor);
|
|
12088
13369
|
registerSwap(program, getOutputMode, makeExecutor);
|
|
12089
13370
|
registerSetup(program);
|
|
12090
13371
|
registerOws(program, getOutputMode);
|
|
@@ -12092,7 +13373,7 @@ registerOws(program, getOutputMode);
|
|
|
12092
13373
|
// src/landing.ts
|
|
12093
13374
|
init_dist();
|
|
12094
13375
|
import pc4 from "picocolors";
|
|
12095
|
-
import { encodeFunctionData as
|
|
13376
|
+
import { encodeFunctionData as encodeFunctionData31, parseAbi as parseAbi35, formatUnits } from "viem";
|
|
12096
13377
|
var DASHBOARD_CHAINS = [
|
|
12097
13378
|
{ slug: "hyperevm", tokens: ["HYPE", "WHYPE", "USDC", "USDT0", "USDe", "kHYPE", "wstHYPE"] },
|
|
12098
13379
|
{ slug: "mantle", tokens: ["MNT", "WMNT", "USDC", "USDT", "WETH", "mETH"] },
|
|
@@ -12100,10 +13381,10 @@ var DASHBOARD_CHAINS = [
|
|
|
12100
13381
|
{ slug: "bnb", tokens: ["BNB", "WBNB", "USDT", "USDC", "BUSD", "CAKE"] },
|
|
12101
13382
|
{ slug: "monad", tokens: ["MON", "WMON", "USDC", "USDT0", "WETH", "WBTC"] }
|
|
12102
13383
|
];
|
|
12103
|
-
var balanceOfAbi =
|
|
13384
|
+
var balanceOfAbi = parseAbi35([
|
|
12104
13385
|
"function balanceOf(address account) view returns (uint256)"
|
|
12105
13386
|
]);
|
|
12106
|
-
var getEthBalanceAbi =
|
|
13387
|
+
var getEthBalanceAbi = parseAbi35([
|
|
12107
13388
|
"function getEthBalance(address addr) view returns (uint256)"
|
|
12108
13389
|
]);
|
|
12109
13390
|
async function fetchBalances(rpcUrl, wallet, tokens) {
|
|
@@ -12112,12 +13393,12 @@ async function fetchBalances(rpcUrl, wallet, tokens) {
|
|
|
12112
13393
|
if (isNative) {
|
|
12113
13394
|
return [
|
|
12114
13395
|
MULTICALL3_ADDRESS,
|
|
12115
|
-
|
|
13396
|
+
encodeFunctionData31({ abi: getEthBalanceAbi, functionName: "getEthBalance", args: [wallet] })
|
|
12116
13397
|
];
|
|
12117
13398
|
}
|
|
12118
13399
|
return [
|
|
12119
13400
|
t.address,
|
|
12120
|
-
|
|
13401
|
+
encodeFunctionData31({ abi: balanceOfAbi, functionName: "balanceOf", args: [wallet] })
|
|
12121
13402
|
];
|
|
12122
13403
|
});
|
|
12123
13404
|
let results;
|