@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.
Files changed (54) hide show
  1. package/README.md +9 -2
  2. package/config/chains.toml +6 -1
  3. package/config/protocols/dex/aerodrome_base.toml +2 -1
  4. package/config/protocols/dex/aerodrome_cl.toml +2 -1
  5. package/config/protocols/dex/apeswap_bnb.toml +1 -1
  6. package/config/protocols/dex/babydogeswap_bnb.toml +1 -1
  7. package/config/protocols/dex/bakeryswap_bnb.toml +2 -2
  8. package/config/protocols/dex/biswap_bnb.toml +1 -1
  9. package/config/protocols/dex/bscswap_bnb.toml +1 -1
  10. package/config/protocols/dex/curve_hyperevm.toml +1 -1
  11. package/config/protocols/dex/fstswap_bnb.toml +1 -1
  12. package/config/protocols/dex/hybra.toml +4 -2
  13. package/config/protocols/dex/hyperswap.toml +1 -1
  14. package/config/protocols/dex/kittenswap.toml +1 -1
  15. package/config/protocols/dex/merchantmoe_mantle.toml +2 -1
  16. package/config/protocols/dex/nest.toml +6 -5
  17. package/config/protocols/dex/pancakeswap_v2_bnb.toml +1 -1
  18. package/config/protocols/dex/pancakeswap_v3_bnb.toml +2 -1
  19. package/config/protocols/dex/project_x.toml +1 -1
  20. package/config/protocols/dex/ramses_cl.toml +1 -1
  21. package/config/protocols/dex/ramses_hl.toml +1 -1
  22. package/config/protocols/dex/thena_v1_bnb.toml +2 -1
  23. package/config/protocols/dex/traderjoe_monad.toml +2 -1
  24. package/config/protocols/dex/uniswap_v2_monad.toml +2 -1
  25. package/config/protocols/dex/uniswap_v3_base.toml +2 -1
  26. package/config/protocols/dex/uniswap_v3_bnb.toml +3 -1
  27. package/config/protocols/dex/uniswap_v3_mantle.toml +2 -1
  28. package/config/protocols/dex/uniswap_v3_monad.toml +2 -1
  29. package/config/protocols/lending/felix_morpho.toml +16 -0
  30. package/config/protocols/lending/hyperlend.toml +1 -1
  31. package/config/protocols/lending/hypurrfi.toml +1 -1
  32. package/config/protocols/lending/kinza_bnb.toml +1 -1
  33. package/config/protocols/lending/morpho_blue_monad.toml +17 -0
  34. package/config/protocols/lending/venus_flux_bnb.toml +5 -1
  35. package/dist/index.js +1590 -309
  36. package/dist/index.js.map +1 -1
  37. package/dist/main.js +1595 -314
  38. package/dist/main.js.map +1 -1
  39. package/dist/mcp-server.js +810 -196
  40. package/dist/mcp-server.js.map +1 -1
  41. package/package.json +1 -1
  42. package/skills/defi-cli/SKILL.md +54 -5
  43. package/skills/defi-cli/references/commands.md +41 -8
  44. package/skills/defi-cli/references/protocols.md +2 -2
  45. package/skills/defi-cli/scripts/bridge-quote.sh +34 -0
  46. package/skills/defi-cli/scripts/lending-supply-flow.sh +28 -0
  47. package/skills/defi-cli/scripts/lp-claim-all.sh +25 -0
  48. package/skills/defi-cli/scripts/lp-emission-discover.sh +20 -0
  49. package/skills/defi-cli/scripts/portfolio-snapshot.sh +0 -0
  50. package/skills/defi-cli/scripts/preflight.sh +0 -0
  51. package/skills/defi-cli/scripts/swap-quote.sh +37 -0
  52. package/skills/defi-cli/scripts/wallet-status.sh +30 -0
  53. package/skills/defi-cli/scripts/yield-scan.sh +0 -0
  54. package/skills/defi-cli/scripts/exploit-scan.sh +0 -10
package/dist/index.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 cached = providerCache.get(rpcUrl);
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({ transport: http(rpcUrl) });
62
- providerCache.set(rpcUrl, client);
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, zeroAddress as zeroAddress2 } from "viem";
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 zeroAddress3 } from "viem";
427
- import { createPublicClient as createPublicClient5, decodeFunctionResult as decodeFunctionResult22, encodeFunctionData as encodeFunctionData8, http as http5, parseAbi as parseAbi8, zeroAddress as zeroAddress4 } from "viem";
428
- import { encodeFunctionData as encodeFunctionData9, parseAbi as parseAbi9, zeroAddress as zeroAddress5 } from "viem";
429
- import { createPublicClient as createPublicClient6, decodeFunctionResult as decodeFunctionResult3, encodeFunctionData as encodeFunctionData10, http as http6, parseAbi as parseAbi10, zeroAddress as zeroAddress6 } from "viem";
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 zeroAddress7
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 zeroAddress8 } from "viem";
450
- import { createPublicClient as createPublicClient11, http as http11, parseAbi as parseAbi15, encodeFunctionData as encodeFunctionData15, zeroAddress as zeroAddress9 } from "viem";
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 zeroAddress10 } from "viem";
456
- import { createPublicClient as createPublicClient16, http as http16, parseAbi as parseAbi21, encodeFunctionData as encodeFunctionData20, zeroAddress as zeroAddress11 } from "viem";
457
- import { createPublicClient as createPublicClient17, http as http17, parseAbi as parseAbi222 } from "viem";
458
- import { createPublicClient as createPublicClient18, http as http18, parseAbi as parseAbi23, encodeFunctionData as encodeFunctionData21 } from "viem";
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 createPublicClient19, http as http19, parseAbi as parseAbi25, encodeFunctionData as encodeFunctionData23, zeroAddress as zeroAddress12 } from "viem";
461
- import { createPublicClient as createPublicClient20, http as http20, parseAbi as parseAbi26, encodeFunctionData as encodeFunctionData24, zeroAddress as zeroAddress13 } from "viem";
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 createPublicClient21, http as http21, parseAbi as parseAbi29 } from "viem";
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 = 0n;
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 [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
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, slot0] = await Promise.all([
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.readContract({ address: params.pool, abi: poolAbi2, functionName: "slot0" }).catch(() => null)
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 && slot0 && poolTs !== null) {
1245
- const currentTick = slot0[1];
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.useTickSpacingQuoter ? encodeFunctionData4({
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: 0n,
1266
- amount1Min: 0n,
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: 0n,
1285
- amount1Min: 0n,
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: 0n, amount1Min: 0n, deadline }]
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 = 0n;
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 [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
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: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
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: 0n, amount1Min: 0n, recipient: params.recipient, deadline: BigInt("18446744073709551615") }]
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: 0n, amount1Min: 0n, deadline }]
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
- const minAmountOut = 0n;
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
- zeroAddress2,
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[2] amounts, uint256 min_mint_amount) external returns (uint256)",
1931
- "function remove_liquidity(uint256 amount, uint256[2] min_amounts) external returns (uint256[2])"
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: this.router,
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: this.router,
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: 0n,
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 [token0, token1, rawAmount0, rawAmount1] = params.token_a.toLowerCase() < params.token_b.toLowerCase() ? [params.token_a, params.token_b, params.amount_a, params.amount_b] : [params.token_b, params.token_a, params.amount_b, params.amount_a];
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 === zeroAddress3) throw new DefiError("CONTRACT_ERROR", "Pool not found");
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: 0n,
2289
- amount1Min: 0n,
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: 0n, amount1Min: 0n, deadline }]
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"] ?? zeroAddress4;
2384
- this.positionManager = entry.contracts?.["position_manager"] ?? zeroAddress4;
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 !== zeroAddress4);
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 !== zeroAddress4) {
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 !== zeroAddress4) tokenAddrs.add(t0);
2452
- if (t1 && t1 !== zeroAddress4) tokenAddrs.add(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 === zeroAddress4) throw new DefiError("CONTRACT_ERROR", `No gauge for pool ${pool}`);
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
- zeroAddress5
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 !== zeroAddress6);
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 !== zeroAddress6);
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) === zeroAddress6 || decodeAddress2(r) === null);
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) === zeroAddress6 || decodeAddress2(r) === null);
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 !== zeroAddress6) {
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 !== zeroAddress6) tokenAddrs.add(t0);
2938
- if (t1 && t1 !== zeroAddress6) tokenAddrs.add(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 !== zeroAddress6) {
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) === zeroAddress6 || decodeAddress2(r) === null);
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 !== zeroAddress6) {
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 !== zeroAddress6 ? rawT0 : tokenA;
3082
- const t1 = rawT1 && rawT1 !== zeroAddress6 ? rawT1 : tokenB;
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 !== zeroAddress6) return 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 !== zeroAddress6) return { tokens: [rt], multiToken: false };
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 ?? zeroAddress6]
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: zeroAddress6, symbol: "unknown", amount: earned });
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
- let activeIdDesired = params.activeIdDesired;
3839
- if (activeIdDesired === void 0) {
3840
- const rpcUrl = this.requireRpc();
3841
- const client = createPublicClient8({ transport: http8(rpcUrl) });
3842
- const activeId = await client.readContract({
3843
- address: params.pool,
3844
- abi: lbPairAbi,
3845
- functionName: "getActiveId"
3846
- });
3847
- activeIdDesired = activeId;
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: params.tokenX,
3860
- tokenY: params.tokenY,
4027
+ tokenX,
4028
+ tokenY,
3861
4029
  binStep: BigInt(params.binStep),
3862
- amountX: params.amountX,
3863
- amountY: params.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 ${params.amountX} tokenX + ${params.amountY} tokenY across ${deltaIds.length} bins`,
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: params.tokenX, spender: this.lbRouter, amount: params.amountX },
3885
- { token: params.tokenY, spender: this.lbRouter, amount: params.amountY }
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
- params.tokenX,
3899
- params.tokenY,
4098
+ tokenX,
4099
+ tokenY,
3900
4100
  params.binStep,
3901
- params.amountXMin ?? 0n,
3902
- params.amountYMin ?? 0n,
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 !== zeroAddress7) {
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 !== zeroAddress8) {
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: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
5498
- if (debtUsd > 0) borrows.push({ asset: zeroAddress8, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
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: zeroAddress9, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5666
- const borrows = debtUsd > 0 ? [{ asset: zeroAddress9, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
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
- const u = await client.readContract({ address: v, abi: CTOKEN_ABI, functionName: "underlying" });
5795
- return [u.toLowerCase(), v];
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 === "fulfilled") map.set(r.value[0], r.value[1]);
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 data2 = encodeFunctionData19({
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: data2,
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
- const market = defaultMarketParams(params.asset);
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: "supply",
6848
+ functionName: "repay",
6323
6849
  args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6324
6850
  });
6325
6851
  return {
6326
- description: `[${this.protocolName}] Supply ${params.amount} to Morpho market`,
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: 3e5
6856
+ gas_estimate: 35e4,
6857
+ approvals: [{ token: params.asset, spender: this.morpho, amount: params.amount }]
6331
6858
  };
6332
6859
  }
6333
- async buildBorrow(params) {
6334
- const market = defaultMarketParams(params.asset);
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: "borrow",
6338
- args: [market, params.amount, 0n, params.on_behalf_of, params.on_behalf_of]
6864
+ functionName: "supplyCollateral",
6865
+ args: [market, params.amount, params.on_behalf_of, "0x"]
6339
6866
  });
6340
6867
  return {
6341
- description: `[${this.protocolName}] Borrow ${params.amount} from Morpho market`,
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 buildRepay(params) {
6349
- const market = defaultMarketParams(params.asset);
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: "repay",
6353
- args: [market, params.amount, 0n, params.on_behalf_of, "0x"]
6880
+ functionName: "withdrawCollateral",
6881
+ args: [market, params.amount, params.to, params.to]
6354
6882
  });
6355
6883
  return {
6356
- description: `[${this.protocolName}] Repay ${params.amount} to Morpho market`,
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 data3 = encodeFunctionData19({
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: data3,
6923
+ data: data2,
6381
6924
  value: 0n,
6382
6925
  gas_estimate: 4e5
6383
6926
  };
6384
6927
  }
6385
- const data2 = encodeFunctionData19({
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: data2,
6936
+ data,
6394
6937
  value: 0n,
6395
6938
  gas_estimate: 4e5
6396
6939
  };
6397
6940
  }
6398
- const market = defaultMarketParams(params.asset);
6399
- const data = encodeFunctionData19({
6400
- abi: MORPHO_ABI,
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 = createPublicClient16({ transport: http16(this.rpcUrl) });
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 = createPublicClient16({ transport: http16(this.rpcUrl) });
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: zeroAddress11,
7169
+ token: zeroAddress10,
6637
7170
  symbol: "WHYPE",
6638
7171
  amount: entireColl,
6639
7172
  decimals: 18
6640
7173
  },
6641
7174
  debt: {
6642
- token: zeroAddress11,
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 = createPublicClient17({ transport: http17(this.rpcUrl) });
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 = createPublicClient18({ transport: http18(this.rpcUrl) });
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 = createPublicClient18({ transport: http18(this.rpcUrl) });
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 = createPublicClient18({ transport: http18(this.rpcUrl) });
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 = createPublicClient18({ transport: http18(this.rpcUrl) });
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: [zeroAddress12]
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 = createPublicClient19({ transport: http19(this.rpcUrl) });
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: zeroAddress12,
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 = createPublicClient20({ transport: http20(this.rpcUrl) });
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: zeroAddress13,
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 = createPublicClient21({ transport: http21(this.rpcUrl) });
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 = createPublicClient21({ transport: http21(this.rpcUrl) });
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 = createPublicClient21({ transport: http21(this.rpcUrl) });
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
  });
@@ -7324,15 +7857,34 @@ var Executor = class _Executor {
7324
7857
  dryRun;
7325
7858
  rpcUrl;
7326
7859
  explorerUrl;
7327
- constructor(broadcast, rpcUrl, explorerUrl) {
7860
+ /**
7861
+ * Optional viem Chain object. When set, all wallet/public clients built
7862
+ * inside this executor anchor to the explicit chainId at construction
7863
+ * time, defending against MITM RPCs that lie about eth_chainId and
7864
+ * keeping offline-signing safe under RPC drift (SSOT 7.4).
7865
+ */
7866
+ chain;
7867
+ constructor(broadcast, rpcUrl, explorerUrl, chain) {
7328
7868
  this.dryRun = !broadcast;
7329
7869
  this.rpcUrl = rpcUrl;
7330
7870
  this.explorerUrl = explorerUrl;
7871
+ this.chain = chain;
7872
+ }
7873
+ /** Returns the optional `{ chain }` spread for viem client constructors. */
7874
+ chainOpt() {
7875
+ return this.chain ? { chain: this.chain } : {};
7331
7876
  }
7332
7877
  /** Apply 20% buffer to a gas estimate */
7333
7878
  static applyGasBuffer(gas) {
7334
7879
  return gas * GAS_BUFFER_BPS / 10000n;
7335
7880
  }
7881
+ /**
7882
+ * EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
7883
+ * Extracted as a static so the math is unit-testable without mocking viem.
7884
+ */
7885
+ static computeMaxFee(baseFeePerGas, priorityFee) {
7886
+ return baseFeePerGas * 125n / 100n + priorityFee;
7887
+ }
7336
7888
  /**
7337
7889
  * Check allowance for a single token/spender pair and send an approve tx if needed.
7338
7890
  * Only called in broadcast mode (not dry-run).
@@ -7444,7 +7996,7 @@ var Executor = class _Executor {
7444
7996
  */
7445
7997
  async fetchEip1559Fees(rpcUrl) {
7446
7998
  try {
7447
- const client = createPublicClient2({ transport: http2(rpcUrl) });
7999
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7448
8000
  let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
7449
8001
  try {
7450
8002
  priorityFee = await client.estimateMaxPriorityFeePerGas();
@@ -7453,8 +8005,7 @@ var Executor = class _Executor {
7453
8005
  try {
7454
8006
  const block = await client.getBlock({ blockTag: "latest" });
7455
8007
  if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
7456
- const maxFee = block.baseFeePerGas * 125n / 100n + priorityFee;
7457
- return [maxFee, priorityFee];
8008
+ return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
7458
8009
  }
7459
8010
  } catch {
7460
8011
  }
@@ -7467,7 +8018,7 @@ var Executor = class _Executor {
7467
8018
  /** Estimate gas dynamically with buffer, falling back to a hardcoded estimate */
7468
8019
  async estimateGasWithBuffer(rpcUrl, tx, from) {
7469
8020
  try {
7470
- const client = createPublicClient2({ transport: http2(rpcUrl) });
8021
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7471
8022
  const estimated = await client.estimateGas({
7472
8023
  to: tx.to,
7473
8024
  data: tx.data,
@@ -7491,7 +8042,7 @@ var Executor = class _Executor {
7491
8042
  if (!rpcUrl) {
7492
8043
  throw DefiError.rpcError("No RPC URL \u2014 cannot simulate. Set HYPEREVM_RPC_URL.");
7493
8044
  }
7494
- const client = createPublicClient2({ transport: http2(rpcUrl) });
8045
+ const client = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
7495
8046
  const privateKey = process.env["DEFI_PRIVATE_KEY"];
7496
8047
  const from = privateKey ? privateKeyToAccount(privateKey).address : "0x0000000000000000000000000000000000000001";
7497
8048
  if (tx.approvals && tx.approvals.length > 0) {
@@ -7605,8 +8156,8 @@ var Executor = class _Executor {
7605
8156
  if (!rpcUrl) {
7606
8157
  throw DefiError.rpcError("No RPC URL configured for broadcasting");
7607
8158
  }
7608
- const publicClient = createPublicClient2({ transport: http2(rpcUrl) });
7609
- const walletClient = createWalletClient({ account, transport: http2(rpcUrl) });
8159
+ const publicClient = createPublicClient2({ transport: http2(rpcUrl), ...this.chainOpt() });
8160
+ const walletClient = createWalletClient({ account, transport: http2(rpcUrl), ...this.chainOpt() });
7610
8161
  if (tx.pre_txs && tx.pre_txs.length > 0) {
7611
8162
  for (const preTx of tx.pre_txs) {
7612
8163
  process.stderr.write(` Pre-tx: ${preTx.description}...
@@ -8276,7 +8827,8 @@ function handleSchema(params) {
8276
8827
  function registerSchema(parent, getOpts) {
8277
8828
  parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
8278
8829
  const mode = getOpts();
8279
- const action = opts.all ? "all" : command ?? "all";
8830
+ const raw = opts.all ? "all" : command ?? "all";
8831
+ const action = raw.replace(/-/g, ".");
8280
8832
  const params = { action };
8281
8833
  const schema = handleSchema(params);
8282
8834
  printOutput(schema, mode);
@@ -8287,7 +8839,7 @@ function registerSchema(parent, getOpts) {
8287
8839
  init_dist();
8288
8840
  init_dist();
8289
8841
  init_dist2();
8290
- import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as zeroAddress14 } from "viem";
8842
+ import { parseAbi as parseAbi30, encodeFunctionData as encodeFunctionData27, decodeFunctionResult as decodeFunctionResult8, createPublicClient as createPublicClient23, http as http23, zeroAddress as zeroAddress13 } from "viem";
8291
8843
 
8292
8844
  // src/whitelist.ts
8293
8845
  import { readFileSync as readFileSync2 } from "fs";
@@ -8467,6 +9019,11 @@ function buildPipelineSteps(p, input = {}) {
8467
9019
  ["--protocol", slug, "slug"],
8468
9020
  ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
8469
9021
  ]);
9022
+ const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
9023
+ ["--protocol", slug, "slug"],
9024
+ ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
9025
+ ["--token-id", input.tokenId, "token-id-from-mint-result"]
9026
+ ]);
8470
9027
  switch (p.reward_strategy) {
8471
9028
  case "lp_fee_only":
8472
9029
  return [
@@ -8491,11 +9048,18 @@ function buildPipelineSteps(p, input = {}) {
8491
9048
  { step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
8492
9049
  { step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
8493
9050
  ];
8494
- case "auto_stake":
9051
+ case "auto_stake": {
9052
+ const isNftAutoStake = p.interface === "uniswap_v3";
8495
9053
  return [
8496
9054
  { step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
8497
- { step: "claim", function: "gauge.getReward(account, tokens[])", note: "Multi-token reward (xRAM + WHYPE on Ramses HL)", cli_command: claimWithGauge() }
9055
+ {
9056
+ step: "claim",
9057
+ function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
9058
+ 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)",
9059
+ cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
9060
+ }
8498
9061
  ];
9062
+ }
8499
9063
  case "on_chain_masterchef":
8500
9064
  return [
8501
9065
  { step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
@@ -8696,6 +9260,24 @@ function registerLP(parent, getOpts, makeExecutor2) {
8696
9260
  await Promise.allSettled(
8697
9261
  protocols.map(async (protocol) => {
8698
9262
  try {
9263
+ if (protocol.reward_strategy === "off_chain_api") {
9264
+ const adapter = createNestOffChain(protocol);
9265
+ const pools = await adapter.getLiquidityPools();
9266
+ for (const p of pools) {
9267
+ if (p.aprPercent === 0 && opts.emissionOnly) continue;
9268
+ results.push({
9269
+ protocol: protocol.slug,
9270
+ pool: p.pool,
9271
+ pair: `${p.token0.symbol}/${p.token1.symbol}`,
9272
+ type: p.aprPercent > 0 ? "EMISSION" : "FEE",
9273
+ source: "off_chain_api",
9274
+ aprPercent: p.aprPercent,
9275
+ poolTvlUsd: p.tvlUSD,
9276
+ emissionUsd: p.curEpochEmissionRewardsUSD
9277
+ });
9278
+ }
9279
+ return;
9280
+ }
8699
9281
  const isGaugeProtocol = ["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(protocol.interface) || protocol.interface === "uniswap_v3" && protocol.contracts?.["voter"];
8700
9282
  if (isGaugeProtocol) {
8701
9283
  const chainTokens = registry.tokens.get(chainName)?.map((t) => t.address);
@@ -8811,7 +9393,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8811
9393
  try {
8812
9394
  const decoded = decodeFunctionResult8({ abi: mcAbi, functionName: "poolInfo", data: raw });
8813
9395
  const [allocPoint, v3Pool, t0, t1, , totalLiq] = decoded;
8814
- if (allocPoint === 0n || v3Pool === zeroAddress14) continue;
9396
+ if (allocPoint === 0n || v3Pool === zeroAddress13) continue;
8815
9397
  const tokens = registry.tokens.get(chainName);
8816
9398
  const sym0 = tokens?.find((t) => t.address.toLowerCase() === t0?.toLowerCase())?.symbol ?? t0?.slice(0, 8) ?? "?";
8817
9399
  const sym1 = tokens?.find((t) => t.address.toLowerCase() === t1?.toLowerCase())?.symbol ?? t1?.slice(0, 8) ?? "?";
@@ -8878,7 +9460,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8878
9460
  if (opts.emissionOnly) {
8879
9461
  printOutput(
8880
9462
  results.filter(
8881
- (r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n)
9463
+ (r) => r.type === "EMISSION" && ((r.moePerDay ?? 0) > 0 || r.rewardRate && BigInt(r.rewardRate) > 0n || (r.aprPercent ?? 0) > 0)
8882
9464
  ),
8883
9465
  getOpts()
8884
9466
  );
@@ -8886,7 +9468,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
8886
9468
  printOutput(results, getOpts());
8887
9469
  }
8888
9470
  });
8889
- 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) => {
9471
+ 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) => {
8890
9472
  const executor = makeExecutor2();
8891
9473
  const chainName = parent.opts().chain;
8892
9474
  if (!chainName) {
@@ -8903,13 +9485,27 @@ function registerLP(parent, getOpts, makeExecutor2) {
8903
9485
  if (protocol.interface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
8904
9486
  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)`);
8905
9487
  const lbAdapter = createMerchantMoeLB(protocol, chain.effectiveRpcUrl());
8906
- 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)];
8907
9488
  const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
8908
- const binStep = await client.readContract({
8909
- address: poolAddr,
8910
- abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
8911
- functionName: "getBinStep"
8912
- });
9489
+ const lbPairOrderAbi = parseAbi30([
9490
+ "function getTokenX() view returns (address)",
9491
+ "function getTokenY() view returns (address)",
9492
+ "function getBinStep() view returns (uint16)"
9493
+ ]);
9494
+ const [poolTokenX, poolTokenY, binStep] = await Promise.all([
9495
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenX" }),
9496
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getTokenY" }),
9497
+ client.readContract({ address: poolAddr, abi: lbPairOrderAbi, functionName: "getBinStep" })
9498
+ ]);
9499
+ const aIsX = tokenA.toLowerCase() === poolTokenX.toLowerCase();
9500
+ const bIsX = tokenB.toLowerCase() === poolTokenX.toLowerCase();
9501
+ const aIsY = tokenA.toLowerCase() === poolTokenY.toLowerCase();
9502
+ const bIsY = tokenB.toLowerCase() === poolTokenY.toLowerCase();
9503
+ if (!(aIsX && bIsY || bIsX && aIsY)) {
9504
+ throw new Error(
9505
+ `[${protocol.name}] --token-a/--token-b (${tokenA}/${tokenB}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
9506
+ );
9507
+ }
9508
+ const [tokenX, tokenY, amountX, amountY] = aIsX ? [tokenA, tokenB, BigInt(opts.amountA), BigInt(opts.amountB)] : [tokenB, tokenA, BigInt(opts.amountB), BigInt(opts.amountA)];
8913
9509
  const tx2 = await lbAdapter.buildAddLiquidity({
8914
9510
  pool: poolAddr,
8915
9511
  tokenX,
@@ -8935,12 +9531,15 @@ function registerLP(parent, getOpts, makeExecutor2) {
8935
9531
  tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
8936
9532
  tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
8937
9533
  range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
8938
- pool: poolAddr
9534
+ pool: poolAddr,
9535
+ slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
9536
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
9537
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
8939
9538
  });
8940
9539
  const result = await executor.execute(tx);
8941
9540
  printOutput(result, getOpts());
8942
9541
  });
8943
- 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) => {
9542
+ 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) => {
8944
9543
  const executor = makeExecutor2();
8945
9544
  const chainName = parent.opts().chain;
8946
9545
  if (!chainName) {
@@ -8966,7 +9565,10 @@ function registerLP(parent, getOpts, makeExecutor2) {
8966
9565
  tick_lower: opts.tickLower !== void 0 ? parseInt(opts.tickLower) : void 0,
8967
9566
  tick_upper: opts.tickUpper !== void 0 ? parseInt(opts.tickUpper) : void 0,
8968
9567
  range_pct: opts.range !== void 0 ? parseFloat(opts.range) : void 0,
8969
- pool: poolAddr
9568
+ pool: poolAddr,
9569
+ slippage: opts.slippage !== void 0 ? { bps: parseInt(opts.slippage, 10) } : void 0,
9570
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
9571
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0
8970
9572
  });
8971
9573
  process.stderr.write("Step 1/2: Adding liquidity...\n");
8972
9574
  const addResult = await executor.execute(addTx);
@@ -9206,7 +9808,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9206
9808
  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."
9207
9809
  }, getOpts());
9208
9810
  });
9209
- lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--liquidity <amount>", "Liquidity amount to remove in wei").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
9811
+ 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) => {
9210
9812
  const executor = makeExecutor2();
9211
9813
  const chainName = parent.opts().chain;
9212
9814
  if (!chainName) {
@@ -9222,17 +9824,34 @@ function registerLP(parent, getOpts, makeExecutor2) {
9222
9824
  if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
9223
9825
  if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
9224
9826
  if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
9827
+ if (!opts.tokenA || !opts.tokenB) {
9828
+ throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
9829
+ }
9225
9830
  const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
9226
9831
  const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9227
9832
  const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
9228
- const [tokenX, tokenY] = tokenA2.toLowerCase() < tokenB2.toLowerCase() ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
9229
9833
  const binIds = opts.bins.split(",").map((s) => parseInt(s.trim()));
9230
9834
  const client = createPublicClient23({ transport: http23(rpcUrl) });
9231
- const binStep = await client.readContract({
9232
- address: opts.pool,
9233
- abi: parseAbi30(["function getBinStep() view returns (uint16)"]),
9234
- functionName: "getBinStep"
9235
- });
9835
+ const lbPairOrderAbi = parseAbi30([
9836
+ "function getTokenX() view returns (address)",
9837
+ "function getTokenY() view returns (address)",
9838
+ "function getBinStep() view returns (uint16)"
9839
+ ]);
9840
+ const [poolTokenX, poolTokenY, binStep] = await Promise.all([
9841
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenX" }),
9842
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getTokenY" }),
9843
+ client.readContract({ address: opts.pool, abi: lbPairOrderAbi, functionName: "getBinStep" })
9844
+ ]);
9845
+ const aIsX = tokenA2.toLowerCase() === poolTokenX.toLowerCase();
9846
+ const bIsX = tokenB2.toLowerCase() === poolTokenX.toLowerCase();
9847
+ const aIsY = tokenA2.toLowerCase() === poolTokenY.toLowerCase();
9848
+ const bIsY = tokenB2.toLowerCase() === poolTokenY.toLowerCase();
9849
+ if (!(aIsX && bIsY || bIsX && aIsY)) {
9850
+ throw new Error(
9851
+ `[${protocol.name}] --token-a/--token-b (${tokenA2}/${tokenB2}) do not match pool tokens (X=${poolTokenX}, Y=${poolTokenY})`
9852
+ );
9853
+ }
9854
+ const [tokenX, tokenY] = aIsX ? [tokenA2, tokenB2] : [tokenB2, tokenA2];
9236
9855
  let amounts;
9237
9856
  if (opts.amounts) {
9238
9857
  amounts = opts.amounts.split(",").map((s) => BigInt(s.trim()));
@@ -9252,6 +9871,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9252
9871
  gas_estimate: 8e4
9253
9872
  };
9254
9873
  const tx = await lbAdapter.buildRemoveLiquidity({
9874
+ pool: opts.pool,
9255
9875
  tokenX,
9256
9876
  tokenY,
9257
9877
  binStep,
@@ -9264,8 +9884,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
9264
9884
  printOutput({ step: "lb_remove", ...result }, getOpts());
9265
9885
  return;
9266
9886
  }
9267
- const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9268
- const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
9887
+ const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
9888
+ const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
9889
+ if (!isNftRemove) {
9890
+ const missing = [];
9891
+ if (!opts.tokenA) missing.push("--token-a");
9892
+ if (!opts.tokenB) missing.push("--token-b");
9893
+ if (!opts.liquidity) missing.push("--liquidity");
9894
+ if (missing.length > 0) {
9895
+ printOutput({
9896
+ error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
9897
+ }, getOpts());
9898
+ return;
9899
+ }
9900
+ }
9901
+ const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
9902
+ const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
9269
9903
  const poolAddr = opts.pool ? opts.pool : void 0;
9270
9904
  let didUnstake = false;
9271
9905
  if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
@@ -9315,7 +9949,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9315
9949
  if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
9316
9950
  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");
9317
9951
  }
9318
- const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
9952
+ const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
9319
9953
  const withdrawResult = await executor.execute(withdrawTx);
9320
9954
  printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
9321
9955
  if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
@@ -9330,13 +9964,37 @@ function registerLP(parent, getOpts, makeExecutor2) {
9330
9964
  }
9331
9965
  process.stderr.write("Step 2/2: Removing liquidity...\n");
9332
9966
  const dexAdapter = createDex(protocol, rpcUrl);
9967
+ let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
9968
+ if (isNftRemove && removeLiquidity === 0n) {
9969
+ const npm = protocol.contracts?.["position_manager"];
9970
+ if (npm) {
9971
+ const c = createPublicClient23({ transport: http23(rpcUrl) });
9972
+ const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
9973
+ if (pos) {
9974
+ removeLiquidity = pos.liquidity;
9975
+ process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
9976
+ `);
9977
+ }
9978
+ }
9979
+ }
9980
+ if (isNftRemove && removeLiquidity === 0n) {
9981
+ printOutput({
9982
+ error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
9983
+ }, getOpts());
9984
+ return;
9985
+ }
9986
+ const ZERO = "0x0000000000000000000000000000000000000000";
9987
+ const removePoolAddr = opts.pool ? resolvePoolAddress(registry, opts.protocol, opts.pool) : void 0;
9333
9988
  const removeTx = await dexAdapter.buildRemoveLiquidity({
9334
9989
  protocol: protocol.name,
9335
- token_a: tokenA,
9336
- token_b: tokenB,
9337
- liquidity: BigInt(opts.liquidity),
9990
+ token_a: tokenA ?? ZERO,
9991
+ token_b: tokenB ?? ZERO,
9992
+ liquidity: removeLiquidity,
9338
9993
  recipient,
9339
- token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
9994
+ token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0,
9995
+ amount_a_min: opts.amountAMin !== void 0 ? BigInt(opts.amountAMin) : void 0,
9996
+ amount_b_min: opts.amountBMin !== void 0 ? BigInt(opts.amountBMin) : void 0,
9997
+ pool: removePoolAddr
9340
9998
  });
9341
9999
  const removeResult = await executor.execute(removeTx);
9342
10000
  printOutput({ step: "lp_remove", ...removeResult }, getOpts());
@@ -9689,6 +10347,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9689
10347
  // src/commands/lending.ts
9690
10348
  init_dist();
9691
10349
  init_dist2();
10350
+ import { maxUint256 } from "viem";
9692
10351
 
9693
10352
  // src/utils.ts
9694
10353
  init_dist();
@@ -9713,12 +10372,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
9713
10372
  return registry.resolveToken(chainName, tokenOrAddress).address;
9714
10373
  }
9715
10374
  var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
10375
+ var warnedFallback = false;
9716
10376
  function resolveWallet(override) {
9717
10377
  if (override) return override;
9718
10378
  try {
9719
10379
  const { address } = resolveWalletWithSigner();
9720
10380
  return address;
9721
10381
  } catch {
10382
+ if (!warnedFallback) {
10383
+ process.stderr.write(
10384
+ `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.
10385
+ `
10386
+ );
10387
+ warnedFallback = true;
10388
+ }
9722
10389
  return FALLBACK_ADDRESS;
9723
10390
  }
9724
10391
  }
@@ -9730,6 +10397,26 @@ function parseBigIntValue(v) {
9730
10397
  }
9731
10398
 
9732
10399
  // src/commands/lending.ts
10400
+ function parseAmount(s) {
10401
+ const lower = s.toLowerCase();
10402
+ if (lower === "max" || lower === "all") return maxUint256;
10403
+ return BigInt(s);
10404
+ }
10405
+ function resolveMarketInput(adapter, raw) {
10406
+ if (!raw) return void 0;
10407
+ if (/^0x[0-9a-fA-F]{64}$/.test(raw)) return raw;
10408
+ if (typeof adapter.resolveMarketIdByName !== "function") {
10409
+ throw new Error(
10410
+ `--market must be a 32-byte hex value; got '${raw}'. This adapter does not support named-market lookup.`
10411
+ );
10412
+ }
10413
+ const id = adapter.resolveMarketIdByName(raw);
10414
+ if (id) return id;
10415
+ const known = adapter.listNamedMarkets?.()?.map((m) => m.name).join(", ") ?? "(none)";
10416
+ throw new Error(
10417
+ `--market '${raw}' is not a 32-byte hex and is not registered on this protocol. Known markets: ${known}.`
10418
+ );
10419
+ }
9733
10420
  function registerLending(parent, getOpts, makeExecutor2) {
9734
10421
  const lending = parent.command("lending").description("Lending operations: supply, borrow, repay, withdraw, rates, position");
9735
10422
  lending.command("rates").description("Show current lending rates").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").action(async (opts) => {
@@ -9752,18 +10439,24 @@ function registerLending(parent, getOpts, makeExecutor2) {
9752
10439
  const position = await adapter.getUserPosition(address);
9753
10440
  printOutput(position, getOpts());
9754
10441
  });
9755
- 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) => {
10442
+ 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) => {
9756
10443
  const executor = makeExecutor2();
9757
10444
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9758
10445
  if (!ctx) return;
9759
10446
  const adapter = createLending(ctx.protocol, ctx.rpcUrl);
9760
10447
  const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
9761
10448
  const onBehalfOf = resolveWallet(opts.onBehalfOf);
9762
- const tx = await adapter.buildSupply({ protocol: ctx.protocol.name, asset, amount: BigInt(opts.amount), on_behalf_of: onBehalfOf });
10449
+ const tx = await adapter.buildSupply({
10450
+ protocol: ctx.protocol.name,
10451
+ asset,
10452
+ amount: parseAmount(opts.amount),
10453
+ on_behalf_of: onBehalfOf,
10454
+ market_id: resolveMarketInput(adapter, opts.market)
10455
+ });
9763
10456
  const result = await executor.execute(tx);
9764
10457
  printOutput(result, getOpts());
9765
10458
  });
9766
- 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) => {
10459
+ 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) => {
9767
10460
  const executor = makeExecutor2();
9768
10461
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9769
10462
  if (!ctx) return;
@@ -9773,14 +10466,15 @@ function registerLending(parent, getOpts, makeExecutor2) {
9773
10466
  const tx = await adapter.buildBorrow({
9774
10467
  protocol: ctx.protocol.name,
9775
10468
  asset,
9776
- amount: BigInt(opts.amount),
10469
+ amount: parseAmount(opts.amount),
9777
10470
  interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
9778
- on_behalf_of: onBehalfOf
10471
+ on_behalf_of: onBehalfOf,
10472
+ market_id: resolveMarketInput(adapter, opts.market)
9779
10473
  });
9780
10474
  const result = await executor.execute(tx);
9781
10475
  printOutput(result, getOpts());
9782
10476
  });
9783
- 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) => {
10477
+ 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) => {
9784
10478
  const executor = makeExecutor2();
9785
10479
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9786
10480
  if (!ctx) return;
@@ -9790,21 +10484,150 @@ function registerLending(parent, getOpts, makeExecutor2) {
9790
10484
  const tx = await adapter.buildRepay({
9791
10485
  protocol: ctx.protocol.name,
9792
10486
  asset,
9793
- amount: BigInt(opts.amount),
10487
+ amount: parseAmount(opts.amount),
9794
10488
  interest_rate_mode: opts.rateMode === "stable" ? InterestRateMode.Stable : InterestRateMode.Variable,
9795
- on_behalf_of: onBehalfOf
10489
+ on_behalf_of: onBehalfOf,
10490
+ market_id: resolveMarketInput(adapter, opts.market)
9796
10491
  });
9797
10492
  const result = await executor.execute(tx);
9798
10493
  printOutput(result, getOpts());
9799
10494
  });
9800
- 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) => {
10495
+ 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) => {
9801
10496
  const executor = makeExecutor2();
9802
10497
  const ctx = resolveContext(parent, getOpts, opts.protocol);
9803
10498
  if (!ctx) return;
9804
10499
  const adapter = createLending(ctx.protocol, ctx.rpcUrl);
9805
10500
  const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
9806
10501
  const to = resolveWallet(opts.to);
9807
- const tx = await adapter.buildWithdraw({ protocol: ctx.protocol.name, asset, amount: BigInt(opts.amount), to });
10502
+ const tx = await adapter.buildWithdraw({
10503
+ protocol: ctx.protocol.name,
10504
+ asset,
10505
+ amount: parseAmount(opts.amount),
10506
+ to,
10507
+ market_id: resolveMarketInput(adapter, opts.market)
10508
+ });
10509
+ const result = await executor.execute(tx);
10510
+ printOutput(result, getOpts());
10511
+ });
10512
+ 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) => {
10513
+ const executor = makeExecutor2();
10514
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10515
+ if (!ctx) return;
10516
+ if (!opts.enable && !opts.disable) {
10517
+ printOutput({ error: "must pass either --enable or --disable" }, getOpts());
10518
+ return;
10519
+ }
10520
+ if (opts.enable && opts.disable) {
10521
+ printOutput({ error: "--enable and --disable are mutually exclusive" }, getOpts());
10522
+ return;
10523
+ }
10524
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10525
+ if (typeof adapter.buildSetUseReserveAsCollateral !== "function") {
10526
+ printOutput({
10527
+ error: `[${ctx.protocol.name}] adapter does not implement buildSetUseReserveAsCollateral. Aave V3 forks support this; Compound V2/Morpho Blue use different flows.`
10528
+ }, getOpts());
10529
+ return;
10530
+ }
10531
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10532
+ const tx = await adapter.buildSetUseReserveAsCollateral(asset, !!opts.enable);
10533
+ const result = await executor.execute(tx);
10534
+ printOutput(result, getOpts());
10535
+ });
10536
+ 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) => {
10537
+ const executor = makeExecutor2();
10538
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10539
+ if (!ctx) return;
10540
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10541
+ if (typeof adapter.buildSetEMode !== "function") {
10542
+ printOutput({
10543
+ error: `[${ctx.protocol.name}] adapter does not implement buildSetEMode (Aave V3 only)`
10544
+ }, getOpts());
10545
+ return;
10546
+ }
10547
+ const id = parseInt(opts.categoryId, 10);
10548
+ if (!Number.isInteger(id) || id < 0 || id > 255) {
10549
+ printOutput({ error: `--category-id must be an integer in [0, 255], got '${opts.categoryId}'` }, getOpts());
10550
+ return;
10551
+ }
10552
+ const tx = await adapter.buildSetEMode(id);
10553
+ const result = await executor.execute(tx);
10554
+ printOutput(result, getOpts());
10555
+ });
10556
+ 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) => {
10557
+ const executor = makeExecutor2();
10558
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10559
+ if (!ctx) return;
10560
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10561
+ if (typeof adapter.buildEnterMarkets !== "function") {
10562
+ printOutput({
10563
+ error: `[${ctx.protocol.name}] adapter does not implement buildEnterMarkets. This is a Compound V2 family operation; Aave V3 uses toggle-collateral instead.`
10564
+ }, getOpts());
10565
+ return;
10566
+ }
10567
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10568
+ const contracts = ctx.protocol.contracts ?? {};
10569
+ const vTokenEntries = Object.entries(contracts).filter(([k]) => /^v[a-z][a-z0-9]*$/i.test(k));
10570
+ if (vTokenEntries.length === 0) {
10571
+ printOutput({ error: `[${ctx.protocol.name}] no vTokens registered in TOML` }, getOpts());
10572
+ return;
10573
+ }
10574
+ const symbol = opts.asset.toLowerCase();
10575
+ const matchedKey = vTokenEntries.find(([k]) => k.toLowerCase() === `v${symbol}`);
10576
+ const vToken = matchedKey ? matchedKey[1] : void 0;
10577
+ if (!vToken) {
10578
+ printOutput({
10579
+ 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).`
10580
+ }, getOpts());
10581
+ return;
10582
+ }
10583
+ void asset;
10584
+ const tx = await adapter.buildEnterMarkets([vToken]);
10585
+ const result = await executor.execute(tx);
10586
+ printOutput(result, getOpts());
10587
+ });
10588
+ 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) => {
10589
+ const executor = makeExecutor2();
10590
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10591
+ if (!ctx) return;
10592
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10593
+ if (typeof adapter.buildSupplyCollateral !== "function") {
10594
+ printOutput({
10595
+ error: `[${ctx.protocol.name}] adapter does not implement buildSupplyCollateral. Only Morpho Blue forks expose this; Aave V3 / Compound use plain supply.`
10596
+ }, getOpts());
10597
+ return;
10598
+ }
10599
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10600
+ const onBehalfOf = resolveWallet(opts.onBehalfOf);
10601
+ const tx = await adapter.buildSupplyCollateral({
10602
+ protocol: ctx.protocol.name,
10603
+ asset,
10604
+ amount: parseAmount(opts.amount),
10605
+ on_behalf_of: onBehalfOf,
10606
+ market_id: resolveMarketInput(adapter, opts.market)
10607
+ });
10608
+ const result = await executor.execute(tx);
10609
+ printOutput(result, getOpts());
10610
+ });
10611
+ 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) => {
10612
+ const executor = makeExecutor2();
10613
+ const ctx = resolveContext(parent, getOpts, opts.protocol);
10614
+ if (!ctx) return;
10615
+ const adapter = createLending(ctx.protocol, ctx.rpcUrl);
10616
+ if (typeof adapter.buildWithdrawCollateral !== "function") {
10617
+ printOutput({
10618
+ error: `[${ctx.protocol.name}] adapter does not implement buildWithdrawCollateral.`
10619
+ }, getOpts());
10620
+ return;
10621
+ }
10622
+ const asset = resolveTokenAddress(ctx.registry, ctx.chainName, opts.asset);
10623
+ const to = resolveWallet(opts.to);
10624
+ const tx = await adapter.buildWithdrawCollateral({
10625
+ protocol: ctx.protocol.name,
10626
+ asset,
10627
+ amount: parseAmount(opts.amount),
10628
+ to,
10629
+ market_id: resolveMarketInput(adapter, opts.market)
10630
+ });
9808
10631
  const result = await executor.execute(tx);
9809
10632
  printOutput(result, getOpts());
9810
10633
  });
@@ -9821,7 +10644,8 @@ function resolveAsset(registry, chain, asset) {
9821
10644
  }
9822
10645
  async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9823
10646
  const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
9824
- const results = [];
10647
+ const rates = [];
10648
+ const errors = [];
9825
10649
  let first = true;
9826
10650
  for (const proto of protos) {
9827
10651
  if (!first) {
@@ -9830,19 +10654,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9830
10654
  first = false;
9831
10655
  try {
9832
10656
  const lending = createLending(proto, rpc);
9833
- const rates = await lending.getRates(assetAddr);
9834
- results.push(rates);
10657
+ const r = await lending.getRates(assetAddr);
10658
+ rates.push(r);
9835
10659
  } catch (err) {
9836
10660
  process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
9837
10661
  `);
10662
+ errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
9838
10663
  }
9839
10664
  }
9840
- return results;
10665
+ return { rates, errors };
9841
10666
  }
9842
10667
  async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9843
10668
  const opportunities = [];
9844
- const lendingRates = await collectLendingRates(registry, chainName, rpc, assetAddr);
9845
- for (const r of lendingRates) {
10669
+ const errors = [];
10670
+ const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
10671
+ errors.push(...lendingResult.errors);
10672
+ for (const r of lendingResult.rates) {
9846
10673
  if (r.supply_apy > 0) {
9847
10674
  opportunities.push({
9848
10675
  protocol: r.protocol,
@@ -9868,7 +10695,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9868
10695
  utilization: rates.utilization
9869
10696
  });
9870
10697
  }
9871
- } catch {
10698
+ } catch (e) {
10699
+ errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
9872
10700
  }
9873
10701
  }
9874
10702
  }
@@ -9884,7 +10712,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9884
10712
  apy: info.apy ?? 0,
9885
10713
  total_assets: info.total_assets.toString()
9886
10714
  });
9887
- } catch {
10715
+ } catch (e) {
10716
+ errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
9888
10717
  }
9889
10718
  }
9890
10719
  }
@@ -9893,7 +10722,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9893
10722
  const ba = b["apy"] ?? 0;
9894
10723
  return ba - aa;
9895
10724
  });
9896
- return opportunities;
10725
+ return { opportunities, errors };
9897
10726
  }
9898
10727
  async function runYieldScan(registry, asset, output) {
9899
10728
  const t0 = Date.now();
@@ -10035,10 +10864,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
10035
10864
  const chain = registry.getChain(chainName);
10036
10865
  const rpc = chain.effectiveRpcUrl();
10037
10866
  const assetAddr = resolveAsset(registry, chainName, opts.asset);
10038
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10867
+ const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10039
10868
  if (results.length === 0) {
10040
10869
  printOutput(
10041
- { error: `No lending rate data available for asset '${opts.asset}'` },
10870
+ ratesErrors.length > 0 ? {
10871
+ error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
10872
+ failed_probes: ratesErrors
10873
+ } : { error: `No lending rate data available for asset '${opts.asset}'` },
10042
10874
  getOpts()
10043
10875
  );
10044
10876
  process.exit(1);
@@ -10280,9 +11112,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
10280
11112
  const assetAddr = resolveAsset(registry, chainName, asset);
10281
11113
  const strategy = opts.strategy ?? "auto";
10282
11114
  if (strategy === "auto") {
10283
- const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
11115
+ const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
10284
11116
  if (opportunities.length === 0) {
10285
- printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
11117
+ if (errors.length > 0) {
11118
+ printOutput({
11119
+ 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.`,
11120
+ failed_probes: errors
11121
+ }, getOpts());
11122
+ } else {
11123
+ printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
11124
+ }
10286
11125
  process.exit(1);
10287
11126
  return;
10288
11127
  }
@@ -10312,9 +11151,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10312
11151
  getOpts()
10313
11152
  );
10314
11153
  } else if (strategy === "best-supply") {
10315
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
11154
+ const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10316
11155
  if (results.length === 0) {
10317
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
11156
+ printOutput(
11157
+ 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}'` },
11158
+ getOpts()
11159
+ );
10318
11160
  process.exit(1);
10319
11161
  return;
10320
11162
  }
@@ -10337,9 +11179,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10337
11179
  getOpts()
10338
11180
  );
10339
11181
  } else if (strategy === "leverage-loop") {
10340
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
11182
+ const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10341
11183
  if (results.length === 0) {
10342
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
11184
+ printOutput(
11185
+ 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}'` },
11186
+ getOpts()
11187
+ );
10343
11188
  process.exit(1);
10344
11189
  return;
10345
11190
  }
@@ -10403,14 +11248,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
10403
11248
 
10404
11249
  // src/commands/portfolio.ts
10405
11250
  init_dist();
10406
- import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
11251
+ import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
10407
11252
 
10408
11253
  // src/portfolio-tracker.ts
10409
11254
  init_dist();
10410
11255
  import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
10411
11256
  import { homedir } from "os";
10412
11257
  import { resolve as resolve3 } from "path";
10413
- import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
11258
+ import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
10414
11259
  var ERC20_ABI4 = parseAbi31([
10415
11260
  "function balanceOf(address owner) external view returns (uint256)"
10416
11261
  ]);
@@ -10461,7 +11306,16 @@ async function takeSnapshot(chainName, wallet, registry) {
10461
11306
  const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10462
11307
  const oracleAddr = oracleEntry?.contracts?.["oracle"];
10463
11308
  const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
11309
+ const priceTokens = [];
10464
11310
  if (oracleAddr) {
11311
+ for (const t of tokenEntries) {
11312
+ calls.push([
11313
+ oracleAddr,
11314
+ encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
11315
+ ]);
11316
+ callLabels.push(`price:${t.address}`);
11317
+ priceTokens.push(t.address);
11318
+ }
10465
11319
  calls.push([
10466
11320
  oracleAddr,
10467
11321
  encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10472,10 +11326,19 @@ async function takeSnapshot(chainName, wallet, registry) {
10472
11326
  if (calls.length > 0) {
10473
11327
  results = await multicallRead(rpc, calls);
10474
11328
  }
11329
+ const balanceCount = tokenEntries.length;
11330
+ const lendingCount = lendingProtocols.length;
11331
+ const priceStartIdx = balanceCount + lendingCount;
10475
11332
  let nativePriceUsd = 0;
11333
+ const priceByToken = /* @__PURE__ */ new Map();
10476
11334
  if (oracleAddr) {
10477
- const priceData = results[results.length - 1] ?? null;
10478
- nativePriceUsd = Number(decodeU256Word(priceData)) / 1e8;
11335
+ for (let i = 0; i < priceTokens.length; i++) {
11336
+ const priceData = results[priceStartIdx + i] ?? null;
11337
+ const px = Number(decodeU256Word(priceData)) / 1e8;
11338
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
11339
+ }
11340
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
11341
+ nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
10479
11342
  }
10480
11343
  let idx = 0;
10481
11344
  const tokens = [];
@@ -10485,8 +11348,20 @@ async function takeSnapshot(chainName, wallet, registry) {
10485
11348
  const balance = decodeU256Word(results[idx] ?? null);
10486
11349
  const balF64 = Number(balance) / 10 ** entry.decimals;
10487
11350
  const symbolUpper = entry.symbol.toUpperCase();
10488
- const priceUsd = symbolUpper.includes("USD") ? 1 : nativePriceUsd;
10489
- const valueUsd = balF64 * priceUsd;
11351
+ const tokenAddrLower = entry.address.toLowerCase();
11352
+ let priceUsd;
11353
+ let valueUsd;
11354
+ if (symbolUpper.includes("USD")) {
11355
+ priceUsd = 1;
11356
+ valueUsd = balF64;
11357
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
11358
+ priceUsd = nativePriceUsd;
11359
+ valueUsd = balF64 * nativePriceUsd;
11360
+ } else {
11361
+ const px = priceByToken.get(tokenAddrLower);
11362
+ priceUsd = px ?? 0;
11363
+ valueUsd = px && px > 0 ? balF64 * px : 0;
11364
+ }
10490
11365
  totalValueUsd += valueUsd;
10491
11366
  tokens.push({
10492
11367
  token: entry.address,
@@ -10527,6 +11402,23 @@ async function takeSnapshot(chainName, wallet, registry) {
10527
11402
  }
10528
11403
  idx++;
10529
11404
  }
11405
+ try {
11406
+ const client = createPublicClient24({ transport: http24(rpc) });
11407
+ const nativeBalance = await client.getBalance({ address: user });
11408
+ if (nativeBalance > 0n) {
11409
+ const nativeF64 = Number(nativeBalance) / 1e18;
11410
+ const nativeValueUsd = nativeF64 * nativePriceUsd;
11411
+ totalValueUsd += nativeValueUsd;
11412
+ tokens.push({
11413
+ token: wrappedNative,
11414
+ symbol: chain.native_token ?? "NATIVE",
11415
+ balance: nativeBalance,
11416
+ value_usd: nativeValueUsd,
11417
+ price_usd: nativePriceUsd
11418
+ });
11419
+ }
11420
+ } catch {
11421
+ }
10530
11422
  return {
10531
11423
  timestamp: Date.now(),
10532
11424
  chain: chainName,
@@ -10644,6 +11536,10 @@ function registerPortfolio(parent, getOpts) {
10644
11536
  const calls = [];
10645
11537
  const callLabels = [];
10646
11538
  const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
11539
+ const tokenAddrsByCallIdx = [];
11540
+ const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
11541
+ const oracleAddr = oracleEntry?.contracts?.["oracle"];
11542
+ const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10647
11543
  for (const symbol of tokenSymbols) {
10648
11544
  let entry;
10649
11545
  try {
@@ -10657,6 +11553,7 @@ function registerPortfolio(parent, getOpts) {
10657
11553
  encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
10658
11554
  ]);
10659
11555
  callLabels.push(`balance:${symbol}`);
11556
+ tokenAddrsByCallIdx.push(entry.address);
10660
11557
  }
10661
11558
  const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
10662
11559
  for (const p of lendingProtocols) {
@@ -10666,10 +11563,16 @@ function registerPortfolio(parent, getOpts) {
10666
11563
  ]);
10667
11564
  callLabels.push(`lending:${p.name}`);
10668
11565
  }
10669
- const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10670
- const oracleAddr = oracleEntry?.contracts?.["oracle"];
10671
- const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
11566
+ const priceTokens = [];
10672
11567
  if (oracleAddr) {
11568
+ for (const tokenAddr of tokenAddrsByCallIdx) {
11569
+ calls.push([
11570
+ oracleAddr,
11571
+ encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
11572
+ ]);
11573
+ callLabels.push(`price:${tokenAddr}`);
11574
+ priceTokens.push(tokenAddr);
11575
+ }
10673
11576
  calls.push([
10674
11577
  oracleAddr,
10675
11578
  encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10694,10 +11597,19 @@ function registerPortfolio(parent, getOpts) {
10694
11597
  printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
10695
11598
  return;
10696
11599
  }
11600
+ const balanceCallCount = tokenAddrsByCallIdx.length;
11601
+ const lendingCallCount = lendingProtocols.length;
11602
+ const priceStartIdx = balanceCallCount + lendingCallCount;
10697
11603
  let nativePriceUsd = 0;
11604
+ const priceByToken = /* @__PURE__ */ new Map();
10698
11605
  if (oracleAddr) {
10699
- const priceData = results[results.length - 1] ?? null;
10700
- nativePriceUsd = Number(decodeU2562(priceData)) / 1e8;
11606
+ for (let i = 0; i < priceTokens.length; i++) {
11607
+ const priceData = results[priceStartIdx + i] ?? null;
11608
+ const px = Number(decodeU2562(priceData)) / 1e8;
11609
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
11610
+ }
11611
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
11612
+ nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
10701
11613
  }
10702
11614
  let totalValueUsd = 0;
10703
11615
  let idx = 0;
@@ -10716,12 +11628,21 @@ function registerPortfolio(parent, getOpts) {
10716
11628
  const decimals = entry.decimals;
10717
11629
  const balF64 = Number(balance) / 10 ** decimals;
10718
11630
  const symbolUpper = symbol.toUpperCase();
10719
- const valueUsd = symbolUpper.includes("USD") || symbolUpper.includes("usd") ? balF64 : balF64 * nativePriceUsd;
10720
- totalValueUsd += valueUsd;
11631
+ const tokenAddrLower = entry.address.toLowerCase();
11632
+ let valueUsd;
11633
+ if (symbolUpper.includes("USD")) {
11634
+ valueUsd = balF64;
11635
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
11636
+ valueUsd = balF64 * nativePriceUsd;
11637
+ } else {
11638
+ const px = priceByToken.get(tokenAddrLower);
11639
+ valueUsd = px && px > 0 ? balF64 * px : null;
11640
+ }
11641
+ if (valueUsd !== null) totalValueUsd += valueUsd;
10721
11642
  tokenBalances.push({
10722
11643
  symbol,
10723
11644
  balance: balF64.toFixed(4),
10724
- value_usd: valueUsd.toFixed(2)
11645
+ value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
10725
11646
  });
10726
11647
  }
10727
11648
  idx++;
@@ -10751,11 +11672,23 @@ function registerPortfolio(parent, getOpts) {
10751
11672
  }
10752
11673
  idx++;
10753
11674
  }
11675
+ let nativeBalance = 0n;
11676
+ let nativeValueUsd = 0;
11677
+ try {
11678
+ const client = createPublicClient25({ transport: http25(rpc) });
11679
+ nativeBalance = await client.getBalance({ address: user });
11680
+ const nativeF64 = Number(nativeBalance) / 1e18;
11681
+ nativeValueUsd = nativeF64 * nativePriceUsd;
11682
+ if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
11683
+ } catch {
11684
+ }
10754
11685
  printOutput(
10755
11686
  {
10756
11687
  address: user,
10757
11688
  chain: chain.name,
10758
11689
  native_price_usd: nativePriceUsd.toFixed(2),
11690
+ native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
11691
+ native_value_usd: nativeValueUsd.toFixed(2),
10759
11692
  total_value_usd: totalValueUsd.toFixed(2),
10760
11693
  token_balances: tokenBalances,
10761
11694
  lending_positions: lendingPositions
@@ -11021,38 +11954,50 @@ function registerPrice(parent, getOpts) {
11021
11954
 
11022
11955
  // src/commands/wallet.ts
11023
11956
  init_dist();
11024
- import { createPublicClient as createPublicClient24, http as http24, formatEther } from "viem";
11957
+ import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
11958
+ function resolveCurrentAddress(override) {
11959
+ if (override) return { address: override, source: "flag" };
11960
+ try {
11961
+ const { address, signer } = resolveWalletWithSigner();
11962
+ return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
11963
+ } catch {
11964
+ return { address: null, source: "none" };
11965
+ }
11966
+ }
11025
11967
  function registerWallet(parent, getOpts) {
11026
11968
  const wallet = parent.command("wallet").description("Wallet management");
11027
- wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
11969
+ 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) => {
11028
11970
  const chainName = requireChain(parent, getOpts);
11029
11971
  if (!chainName) return;
11030
- const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
11972
+ const { address: addr, source } = resolveCurrentAddress(opts.address);
11031
11973
  if (!addr) {
11032
- printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
11974
+ printOutput({
11975
+ error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
11976
+ }, getOpts());
11033
11977
  return;
11034
11978
  }
11035
11979
  const registry = Registry.loadEmbedded();
11036
11980
  const chain = registry.getChain(chainName);
11037
- const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
11981
+ const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
11038
11982
  const balance = await client.getBalance({ address: addr });
11039
11983
  printOutput({
11040
11984
  chain: chain.name,
11041
11985
  address: addr,
11986
+ wallet_source: source,
11042
11987
  native_token: chain.native_token,
11043
11988
  balance_wei: balance,
11044
11989
  balance_formatted: formatEther(balance)
11045
11990
  }, getOpts());
11046
11991
  });
11047
11992
  wallet.command("address").description("Show configured wallet address").action(async () => {
11048
- const addr = process.env.DEFI_WALLET_ADDRESS ?? "(not set)";
11049
- printOutput({ address: addr }, getOpts());
11993
+ const { address, source } = resolveCurrentAddress();
11994
+ printOutput({ address, source }, getOpts());
11050
11995
  });
11051
11996
  }
11052
11997
 
11053
11998
  // src/commands/token.ts
11054
11999
  init_dist();
11055
- import { createPublicClient as createPublicClient25, http as http25, maxUint256 } from "viem";
12000
+ import { createPublicClient as createPublicClient27, encodeFunctionData as encodeFunctionData30, http as http27, maxUint256 as maxUint2562, parseAbi as parseAbi34 } from "viem";
11056
12001
  function registerToken(parent, getOpts, makeExecutor2) {
11057
12002
  const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
11058
12003
  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) => {
@@ -11065,7 +12010,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11065
12010
  }
11066
12011
  const registry = Registry.loadEmbedded();
11067
12012
  const chain = registry.getChain(chainName);
11068
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
12013
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11069
12014
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11070
12015
  const [balance, symbol, decimals] = await Promise.all([
11071
12016
  client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
@@ -11086,7 +12031,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11086
12031
  if (!chainName) return;
11087
12032
  const registry = Registry.loadEmbedded();
11088
12033
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11089
- const amount = opts.amount === "max" ? maxUint256 : BigInt(opts.amount);
12034
+ const amount = opts.amount === "max" ? maxUint2562 : BigInt(opts.amount);
11090
12035
  const tx = buildApprove(tokenAddr, opts.spender, amount);
11091
12036
  const result = await executor.execute(tx);
11092
12037
  printOutput(result, getOpts());
@@ -11101,7 +12046,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11101
12046
  }
11102
12047
  const registry = Registry.loadEmbedded();
11103
12048
  const chain = registry.getChain(chainName);
11104
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
12049
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11105
12050
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11106
12051
  const allowance = await client.readContract({
11107
12052
  address: tokenAddr,
@@ -11111,6 +12056,52 @@ function registerToken(parent, getOpts, makeExecutor2) {
11111
12056
  });
11112
12057
  printOutput({ token: tokenAddr, owner, spender: opts.spender, allowance }, getOpts());
11113
12058
  });
12059
+ const WETH9_ABI = parseAbi34([
12060
+ "function deposit() payable",
12061
+ "function withdraw(uint256 amount)"
12062
+ ]);
12063
+ 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) => {
12064
+ const executor = makeExecutor2();
12065
+ const chainName = requireChain(parent, getOpts);
12066
+ if (!chainName) return;
12067
+ const registry = Registry.loadEmbedded();
12068
+ const chain = registry.getChain(chainName);
12069
+ if (!chain.wrapped_native) {
12070
+ printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
12071
+ return;
12072
+ }
12073
+ const amount = BigInt(opts.amount);
12074
+ const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "deposit" });
12075
+ const result = await executor.execute({
12076
+ description: `[${chainName}] Wrap ${opts.amount} ${chain.native_token} \u2192 W${chain.native_token}`,
12077
+ to: chain.wrapped_native,
12078
+ data,
12079
+ value: amount,
12080
+ gas_estimate: 8e4
12081
+ });
12082
+ printOutput(result, getOpts());
12083
+ });
12084
+ 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) => {
12085
+ const executor = makeExecutor2();
12086
+ const chainName = requireChain(parent, getOpts);
12087
+ if (!chainName) return;
12088
+ const registry = Registry.loadEmbedded();
12089
+ const chain = registry.getChain(chainName);
12090
+ if (!chain.wrapped_native) {
12091
+ printOutput({ error: `[${chainName}] no wrapped_native registered in chains.toml` }, getOpts());
12092
+ return;
12093
+ }
12094
+ const amount = BigInt(opts.amount);
12095
+ const data = encodeFunctionData30({ abi: WETH9_ABI, functionName: "withdraw", args: [amount] });
12096
+ const result = await executor.execute({
12097
+ description: `[${chainName}] Unwrap ${opts.amount} W${chain.native_token} \u2192 ${chain.native_token}`,
12098
+ to: chain.wrapped_native,
12099
+ data,
12100
+ value: 0n,
12101
+ gas_estimate: 8e4
12102
+ });
12103
+ printOutput(result, getOpts());
12104
+ });
11114
12105
  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) => {
11115
12106
  const executor = makeExecutor2();
11116
12107
  const chainName = requireChain(parent, getOpts);
@@ -11125,9 +12116,50 @@ function registerToken(parent, getOpts, makeExecutor2) {
11125
12116
 
11126
12117
  // src/commands/bridge.ts
11127
12118
  init_dist();
12119
+ function resolveDestExecutor(slug, broadcast) {
12120
+ const registry = Registry.loadEmbedded();
12121
+ try {
12122
+ const c = registry.getChain(slug);
12123
+ return new Executor(broadcast, c.effectiveRpcUrl(), c.explorer_url, c.viemChain());
12124
+ } catch {
12125
+ const envKey = `${slug.toUpperCase()}_RPC_URL`;
12126
+ const envVal = process.env[envKey];
12127
+ const meta = DEST_CHAIN_META[slug];
12128
+ if (!meta) throw new Error(`Cannot resolve destination chain: ${slug}`);
12129
+ const rpc = envVal ?? DEST_RPC_FALLBACKS[slug];
12130
+ if (!rpc) throw new Error(`No RPC URL for ${slug}. Set ${envKey} or use a registered chain.`);
12131
+ const minimal = {
12132
+ id: meta.chain_id,
12133
+ name: meta.name,
12134
+ nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
12135
+ rpcUrls: { default: { http: [rpc] } }
12136
+ };
12137
+ return new Executor(broadcast, rpc, void 0, minimal);
12138
+ }
12139
+ }
12140
+ async function pollCctpAttestation(srcDomain, burnTxHash, maxSeconds) {
12141
+ const intervalMs = 5e3;
12142
+ const deadline = Date.now() + maxSeconds * 1e3;
12143
+ while (Date.now() < deadline) {
12144
+ try {
12145
+ const res = await fetch(`https://iris-api.circle.com/v2/messages/${srcDomain}?transactionHash=${burnTxHash}`);
12146
+ if (res.ok) {
12147
+ const data = await res.json();
12148
+ const m = data.messages?.[0];
12149
+ if (m && m.status === "complete" && m.attestation && m.attestation !== "PENDING") {
12150
+ return { message: m.message, attestation: m.attestation };
12151
+ }
12152
+ }
12153
+ } catch {
12154
+ }
12155
+ await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
12156
+ }
12157
+ throw new Error(`CCTP attestation timeout after ${maxSeconds}s for tx ${burnTxHash}`);
12158
+ }
11128
12159
  var LIFI_API = "https://li.quest/v1";
11129
12160
  var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
11130
12161
  var CCTP_FEE_API = "https://iris-api.circle.com/v2/burn/USDC/fees";
12162
+ var RELAY_API = "https://api.relay.link";
11131
12163
  var DEST_CHAIN_META = {
11132
12164
  ethereum: { chain_id: 1, name: "Ethereum" },
11133
12165
  optimism: { chain_id: 10, name: "Optimism" },
@@ -11199,6 +12231,63 @@ async function getDebridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amou
11199
12231
  raw: createData
11200
12232
  };
11201
12233
  }
12234
+ async function getRelayBridgeQuote(srcChainId, dstChainId, srcToken, dstToken, amountRaw, user, recipient) {
12235
+ const body = {
12236
+ user,
12237
+ recipient,
12238
+ originChainId: srcChainId,
12239
+ destinationChainId: dstChainId,
12240
+ originCurrency: srcToken,
12241
+ destinationCurrency: dstToken,
12242
+ tradeType: "EXACT_INPUT",
12243
+ amount: amountRaw
12244
+ };
12245
+ const res = await fetch(`${RELAY_API}/quote`, {
12246
+ method: "POST",
12247
+ headers: { "content-type": "application/json" },
12248
+ body: JSON.stringify(body)
12249
+ });
12250
+ if (!res.ok) {
12251
+ const text = await res.text();
12252
+ let code = "";
12253
+ let msg = text;
12254
+ try {
12255
+ const j = JSON.parse(text);
12256
+ code = String(j.errorCode ?? j.code ?? "");
12257
+ msg = String(j.message ?? text);
12258
+ } catch {
12259
+ }
12260
+ const hints = {
12261
+ AMOUNT_TOO_LOW: "Try a larger amount or use --provider lifi (Relay enforces a per-route fee floor)",
12262
+ INVALID_INPUT_CURRENCY: "Source token unsupported by Relay on this chain \u2014 try --provider lifi",
12263
+ INVALID_OUTPUT_CURRENCY: "Destination token unsupported by Relay on this chain \u2014 try --provider lifi or change --token",
12264
+ NO_QUOTES: "No Relay liquidity for this route right now \u2014 try --provider lifi or retry later",
12265
+ AMOUNT_TOO_HIGH: "Amount exceeds Relay per-tx cap \u2014 split into smaller amounts",
12266
+ UNSUPPORTED_CHAIN: "Relay does not support this origin/destination chain \u2014 use --provider lifi"
12267
+ };
12268
+ const hint = hints[code];
12269
+ const detail = code ? `${code}: ${msg}` : msg;
12270
+ throw new Error(`Relay quote failed (${res.status}): ${detail}${hint ? ` \u2014 ${hint}` : ""}`);
12271
+ }
12272
+ const json = await res.json();
12273
+ const steps = json.steps;
12274
+ const swapStep = steps?.find((s) => s.id !== "approve") ?? steps?.[steps?.length ?? 1 - 1];
12275
+ const items = swapStep?.items;
12276
+ const txData = items?.[0]?.data;
12277
+ if (!txData) throw new Error("Relay: no executable step in cross-chain quote");
12278
+ const details = json.details;
12279
+ const currencyOut = details?.currencyOut;
12280
+ const timeEst = details?.timeEstimate ?? 30;
12281
+ return {
12282
+ to: String(txData.to),
12283
+ data: String(txData.data),
12284
+ value: String(txData.value ?? "0x0"),
12285
+ amountOut: String(currencyOut?.amount ?? "0"),
12286
+ estimatedTime: timeEst,
12287
+ tool: String(swapStep?.id ?? "relay"),
12288
+ raw: json
12289
+ };
12290
+ }
11202
12291
  var CCTP_DOMAINS = {
11203
12292
  ethereum: 0,
11204
12293
  avalanche: 1,
@@ -11211,6 +12300,16 @@ var CCTP_DOMAINS = {
11211
12300
  aptos: 9
11212
12301
  };
11213
12302
  var CCTP_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
12303
+ var CCTP_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
12304
+ var DEST_RPC_FALLBACKS = {
12305
+ ethereum: "https://eth.merkle.io",
12306
+ arbitrum: "https://arbitrum.drpc.org",
12307
+ optimism: "https://optimism.drpc.org",
12308
+ polygon: "https://polygon.drpc.org",
12309
+ avalanche: "https://avalanche.drpc.org",
12310
+ linea: "https://linea.drpc.org",
12311
+ zksync: "https://zksync.drpc.org"
12312
+ };
11214
12313
  var CCTP_USDC_ADDRESSES = {
11215
12314
  ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
11216
12315
  avalanche: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
@@ -11219,6 +12318,17 @@ var CCTP_USDC_ADDRESSES = {
11219
12318
  base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
11220
12319
  polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
11221
12320
  };
12321
+ function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
12322
+ if (amountWei <= maxFeeSubunits) {
12323
+ const amountUsdc = Number(amountWei) / 1e6;
12324
+ return {
12325
+ error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
12326
+ minimum_amount_wei: maxFeeSubunits.toString(),
12327
+ minimum_amount_usdc: feeUsdc
12328
+ };
12329
+ }
12330
+ return null;
12331
+ }
11222
12332
  async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
11223
12333
  try {
11224
12334
  const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
@@ -11243,8 +12353,8 @@ async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
11243
12353
  }
11244
12354
  return { fee: 0.25, maxFeeSubunits: 250000n };
11245
12355
  }
11246
- function registerBridge(parent, getOpts) {
11247
- 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) => {
12356
+ function registerBridge(parent, getOpts, makeExecutor2) {
12357
+ 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) => {
11248
12358
  const chainName = requireChain(parent, getOpts);
11249
12359
  if (!chainName) return;
11250
12360
  const registry = Registry.loadEmbedded();
@@ -11257,8 +12367,55 @@ function registerBridge(parent, getOpts) {
11257
12367
  return;
11258
12368
  }
11259
12369
  const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
12370
+ let dstTokenAddr = tokenAddr;
12371
+ if (!opts.token.startsWith("0x")) {
12372
+ try {
12373
+ dstTokenAddr = registry.resolveToken(opts.toChain, opts.token).address;
12374
+ } catch {
12375
+ }
12376
+ }
11260
12377
  const recipient = resolveWallet(opts.recipient);
11261
12378
  const provider = opts.provider.toLowerCase();
12379
+ if (provider === "relay") {
12380
+ try {
12381
+ const result = await getRelayBridgeQuote(
12382
+ fromChain.chain_id,
12383
+ toChain.chain_id,
12384
+ tokenAddr,
12385
+ dstTokenAddr,
12386
+ opts.amount,
12387
+ recipient,
12388
+ recipient
12389
+ );
12390
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12391
+ const executor = makeExecutor2();
12392
+ const approvals = isNative ? [] : [{
12393
+ token: tokenAddr,
12394
+ spender: result.to,
12395
+ amount: BigInt(opts.amount)
12396
+ }];
12397
+ const action = await executor.execute({
12398
+ to: result.to,
12399
+ data: result.data,
12400
+ value: BigInt(result.value || "0"),
12401
+ description: `Relay bridge ${fromChain.name} \u2192 ${toChain.name}`,
12402
+ approvals
12403
+ });
12404
+ printOutput({
12405
+ from_chain: fromChain.name,
12406
+ to_chain: toChain.name,
12407
+ token: tokenAddr,
12408
+ amount: opts.amount,
12409
+ bridge: `Relay (${result.tool})`,
12410
+ estimated_output: result.amountOut,
12411
+ estimated_time_seconds: result.estimatedTime,
12412
+ action
12413
+ }, getOpts());
12414
+ } catch (e) {
12415
+ printOutput({ error: `Relay API error: ${errMsg(e)}` }, getOpts());
12416
+ }
12417
+ return;
12418
+ }
11262
12419
  if (provider === "debridge") {
11263
12420
  try {
11264
12421
  const srcId = DLN_CHAIN_IDS[chainName] ?? fromChain.chain_id;
@@ -11267,11 +12424,29 @@ function registerBridge(parent, getOpts) {
11267
12424
  srcId,
11268
12425
  dstId,
11269
12426
  tokenAddr,
11270
- tokenAddr,
12427
+ dstTokenAddr,
11271
12428
  opts.amount,
11272
12429
  recipient
11273
12430
  );
11274
12431
  const tx = result.raw.tx;
12432
+ if (!tx?.to || !tx?.data) {
12433
+ printOutput({ error: "deBridge: API did not return a tx envelope" }, getOpts());
12434
+ return;
12435
+ }
12436
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12437
+ const executor = makeExecutor2();
12438
+ const approvals = isNative ? [] : [{
12439
+ token: tokenAddr,
12440
+ spender: tx.to,
12441
+ amount: BigInt(opts.amount)
12442
+ }];
12443
+ const action = await executor.execute({
12444
+ to: tx.to,
12445
+ data: tx.data,
12446
+ value: BigInt(tx.value ?? "0"),
12447
+ description: `deBridge DLN ${fromChain.name} \u2192 ${toChain.name}`,
12448
+ approvals
12449
+ });
11275
12450
  printOutput({
11276
12451
  from_chain: fromChain.name,
11277
12452
  to_chain: toChain.name,
@@ -11280,7 +12455,7 @@ function registerBridge(parent, getOpts) {
11280
12455
  bridge: "deBridge DLN",
11281
12456
  estimated_output: result.amountOut,
11282
12457
  estimated_time_seconds: result.estimatedTime,
11283
- tx: tx ? { to: tx.to, data: tx.data, value: tx.value } : void 0
12458
+ action
11284
12459
  }, getOpts());
11285
12460
  } catch (e) {
11286
12461
  printOutput({ error: `deBridge API error: ${errMsg(e)}` }, getOpts());
@@ -11307,20 +12482,17 @@ function registerBridge(parent, getOpts) {
11307
12482
  }
11308
12483
  const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
11309
12484
  const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
11310
- if (BigInt(opts.amount) <= maxFeeSubunits) {
11311
- printOutput({
11312
- error: `CCTP: amount ${opts.amount} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${fee} USDC). Increase --amount.`,
11313
- minimum_amount_wei: maxFeeSubunits.toString(),
11314
- minimum_amount_usdc: fee
11315
- }, getOpts());
12485
+ const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
12486
+ if (guardErr) {
12487
+ printOutput(guardErr, getOpts());
11316
12488
  return;
11317
12489
  }
11318
12490
  const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
11319
- const { encodeFunctionData: encodeFunctionData30, parseAbi: parseAbi34 } = await import("viem");
11320
- const tokenMessengerAbi = parseAbi34([
12491
+ const { encodeFunctionData: encodeFunctionData31, parseAbi: parseAbi35 } = await import("viem");
12492
+ const tokenMessengerAbi = parseAbi35([
11321
12493
  "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) external returns (uint64 nonce)"
11322
12494
  ]);
11323
- const data = encodeFunctionData30({
12495
+ const data = encodeFunctionData31({
11324
12496
  abi: tokenMessengerAbi,
11325
12497
  functionName: "depositForBurn",
11326
12498
  args: [
@@ -11335,6 +12507,48 @@ function registerBridge(parent, getOpts) {
11335
12507
  // standard finality
11336
12508
  ]
11337
12509
  });
12510
+ const executor = makeExecutor2();
12511
+ const burnAction = await executor.execute({
12512
+ to: CCTP_TOKEN_MESSENGER_V2,
12513
+ data,
12514
+ value: 0n,
12515
+ description: `CCTP burn ${fromChain.name} \u2192 ${toChain.name}`,
12516
+ approvals: [{
12517
+ token: usdcSrc,
12518
+ spender: CCTP_TOKEN_MESSENGER_V2,
12519
+ amount: BigInt(opts.amount)
12520
+ }]
12521
+ });
12522
+ let receiveAction = void 0;
12523
+ if (opts.autoReceive && burnAction.tx_hash && burnAction.status === "confirmed") {
12524
+ try {
12525
+ const timeoutSec = parseInt(opts.receiveTimeout) || 1200;
12526
+ process.stderr.write(`Polling Circle attestation for ${burnAction.tx_hash} (max ${timeoutSec}s)...
12527
+ `);
12528
+ const { message, attestation } = await pollCctpAttestation(srcDomain, burnAction.tx_hash, timeoutSec);
12529
+ process.stderr.write(`Attestation ready. Calling receiveMessage on ${opts.toChain}...
12530
+ `);
12531
+ const { encodeFunctionData: encReceive, parseAbi: parseAbiReceive } = await import("viem");
12532
+ const receiveAbi = parseAbiReceive([
12533
+ "function receiveMessage(bytes message, bytes attestation) external"
12534
+ ]);
12535
+ const receiveData = encReceive({
12536
+ abi: receiveAbi,
12537
+ functionName: "receiveMessage",
12538
+ args: [message, attestation]
12539
+ });
12540
+ const destExecutor = resolveDestExecutor(opts.toChain, !!parent.opts().broadcast);
12541
+ receiveAction = await destExecutor.execute({
12542
+ to: CCTP_MESSAGE_TRANSMITTER_V2,
12543
+ data: receiveData,
12544
+ value: 0n,
12545
+ description: `CCTP receive on ${toChain.name}`,
12546
+ approvals: []
12547
+ });
12548
+ } catch (e) {
12549
+ receiveAction = { error: `auto-receive failed: ${errMsg(e)}` };
12550
+ }
12551
+ }
11338
12552
  printOutput({
11339
12553
  from_chain: fromChain.name,
11340
12554
  to_chain: toChain.name,
@@ -11344,12 +12558,9 @@ function registerBridge(parent, getOpts) {
11344
12558
  bridge: "Circle CCTP V2",
11345
12559
  estimated_fee_usdc: fee,
11346
12560
  estimated_output: String(BigInt(opts.amount) - maxFeeSubunits),
11347
- note: "After burn, poll https://iris-api.circle.com/v2/messages/{srcDomain} for attestation, then call MessageTransmitter.receiveMessage() on destination",
11348
- tx: {
11349
- to: CCTP_TOKEN_MESSENGER_V2,
11350
- data,
11351
- value: "0x0"
11352
- }
12561
+ burn: burnAction,
12562
+ receive: receiveAction,
12563
+ note: opts.autoReceive ? void 0 : "Pass --auto-receive to poll Circle attestation and auto-call MessageTransmitter.receiveMessage on the destination chain."
11353
12564
  }, getOpts());
11354
12565
  } catch (e) {
11355
12566
  printOutput({ error: `CCTP error: ${errMsg(e)}` }, getOpts());
@@ -11361,26 +12572,41 @@ function registerBridge(parent, getOpts) {
11361
12572
  fromChain: String(fromChain.chain_id),
11362
12573
  toChain: String(toChain.chain_id),
11363
12574
  fromToken: tokenAddr,
11364
- toToken: tokenAddr,
12575
+ toToken: dstTokenAddr,
11365
12576
  fromAmount: opts.amount,
11366
12577
  fromAddress: recipient,
11367
12578
  slippage: String(parseInt(opts.slippage) / 1e4)
11368
12579
  });
11369
12580
  const res = await fetch(`${LIFI_API}/quote?${params}`);
11370
12581
  const quote = await res.json();
11371
- if (quote.transactionRequest) {
11372
- printOutput({
11373
- from_chain: fromChain.name,
11374
- to_chain: toChain.name,
11375
- token: tokenAddr,
11376
- amount: opts.amount,
11377
- bridge: quote.toolDetails?.name ?? "LI.FI",
11378
- estimated_output: quote.estimate?.toAmount,
11379
- tx: { to: quote.transactionRequest.to, data: quote.transactionRequest.data, value: quote.transactionRequest.value }
11380
- }, getOpts());
11381
- } else {
12582
+ if (!quote.transactionRequest) {
11382
12583
  printOutput({ error: "No LI.FI route found", details: quote }, getOpts());
12584
+ return;
11383
12585
  }
12586
+ const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000";
12587
+ const spender = quote.estimate?.approvalAddress ?? quote.transactionRequest.to;
12588
+ const executor = makeExecutor2();
12589
+ const approvals = isNative ? [] : [{
12590
+ token: tokenAddr,
12591
+ spender,
12592
+ amount: BigInt(opts.amount)
12593
+ }];
12594
+ const action = await executor.execute({
12595
+ to: quote.transactionRequest.to,
12596
+ data: quote.transactionRequest.data,
12597
+ value: BigInt(quote.transactionRequest.value ?? "0"),
12598
+ description: `LI.FI ${fromChain.name} \u2192 ${toChain.name}`,
12599
+ approvals
12600
+ });
12601
+ printOutput({
12602
+ from_chain: fromChain.name,
12603
+ to_chain: toChain.name,
12604
+ token: tokenAddr,
12605
+ amount: opts.amount,
12606
+ bridge: quote.toolDetails?.name ?? "LI.FI",
12607
+ estimated_output: quote.estimate?.toAmount,
12608
+ action
12609
+ }, getOpts());
11384
12610
  } catch (e) {
11385
12611
  printOutput({ error: `LI.FI API error: ${errMsg(e)}` }, getOpts());
11386
12612
  }
@@ -11468,7 +12694,7 @@ async function lifiQuote(chainId, fromToken, toToken, fromAmount, fromAddress, s
11468
12694
  outAmount: String(estimate?.toAmount ?? "0")
11469
12695
  };
11470
12696
  }
11471
- var RELAY_API = "https://api.relay.link";
12697
+ var RELAY_API2 = "https://api.relay.link";
11472
12698
  async function relayQuote(chainId, fromToken, toToken, amount, user) {
11473
12699
  const body = {
11474
12700
  user,
@@ -11480,7 +12706,7 @@ async function relayQuote(chainId, fromToken, toToken, amount, user) {
11480
12706
  tradeType: "EXACT_INPUT",
11481
12707
  amount
11482
12708
  };
11483
- const res = await fetch(`${RELAY_API}/quote`, {
12709
+ const res = await fetch(`${RELAY_API2}/quote`, {
11484
12710
  method: "POST",
11485
12711
  headers: { "content-type": "application/json" },
11486
12712
  body: JSON.stringify(body)
@@ -11520,7 +12746,7 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
11520
12746
  };
11521
12747
  }
11522
12748
  function registerSwap(parent, getOpts, makeExecutor2) {
11523
- parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap, LI.FI, Relay)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid, lifi, relay", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "50").action(async (opts) => {
12749
+ 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) => {
11524
12750
  const executor = makeExecutor2();
11525
12751
  const chainName = requireChain(parent, getOpts);
11526
12752
  if (!chainName) return;
@@ -11549,12 +12775,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
11549
12775
  wallet,
11550
12776
  slippageBps
11551
12777
  );
12778
+ const fromLowerKyber = fromAddr.toLowerCase();
12779
+ const isNativeInputKyber = fromLowerKyber === "0x0000000000000000000000000000000000000000" || fromLowerKyber === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
11552
12780
  const tx = {
11553
12781
  description: `KyberSwap: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
11554
12782
  to: txData.to,
11555
12783
  data: txData.data,
11556
- value: parseBigIntValue(txData.value),
11557
- approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }]
12784
+ value: isNativeInputKyber ? BigInt(opts.amount) : parseBigIntValue(txData.value),
12785
+ ...isNativeInputKyber ? {} : { approvals: [{ token: fromAddr, spender: txData.to, amount: BigInt(opts.amount) }] }
11558
12786
  };
11559
12787
  const result = await executor.execute(tx);
11560
12788
  printOutput({
@@ -11593,12 +12821,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
11593
12821
  slippagePct,
11594
12822
  wallet
11595
12823
  );
12824
+ const fromLower = fromAddr.toLowerCase();
12825
+ const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
11596
12826
  const tx = {
11597
12827
  description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
11598
12828
  to: swap.to,
11599
12829
  data: swap.data,
11600
12830
  value: parseBigIntValue(swap.value),
11601
- approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
12831
+ ...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
11602
12832
  };
11603
12833
  const result = await executor.execute(tx);
11604
12834
  printOutput({
@@ -11690,7 +12920,8 @@ import pc2 from "picocolors";
11690
12920
  import { createInterface } from "readline";
11691
12921
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
11692
12922
  import { resolve as resolve4 } from "path";
11693
- var DEFI_DIR = resolve4(process.env.HOME || "~", ".defi");
12923
+ import { homedir as homedir2 } from "os";
12924
+ var DEFI_DIR = resolve4(homedir2(), ".defi");
11694
12925
  var ENV_FILE = resolve4(DEFI_DIR, ".env");
11695
12926
  function ensureDefiDir() {
11696
12927
  if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
@@ -11725,6 +12956,44 @@ function writeEnvFile(env) {
11725
12956
  function ask(rl, question) {
11726
12957
  return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
11727
12958
  }
12959
+ function askSecret(rl, question) {
12960
+ const stdin = process.stdin;
12961
+ if (!stdin.isTTY) return ask(rl, question);
12962
+ process.stdout.write(question);
12963
+ const rlAny = rl;
12964
+ const original = rlAny._writeToOutput;
12965
+ rlAny._writeToOutput = (s) => {
12966
+ if (s.includes("\n") || s.includes("\r")) {
12967
+ original?.call(rl, s);
12968
+ }
12969
+ };
12970
+ return new Promise((res) => {
12971
+ rl.question("", (answer) => {
12972
+ rlAny._writeToOutput = original;
12973
+ process.stdout.write("\n");
12974
+ res(answer.trim());
12975
+ });
12976
+ });
12977
+ }
12978
+ function maskRpcUrl(s) {
12979
+ try {
12980
+ const u = new URL(s);
12981
+ if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
12982
+ return `${u.protocol}//${u.host}/***`;
12983
+ }
12984
+ return `${u.protocol}//${u.host}`;
12985
+ } catch {
12986
+ return "***";
12987
+ }
12988
+ }
12989
+ function isValidRpcUrl(s) {
12990
+ try {
12991
+ const u = new URL(s);
12992
+ return u.protocol === "http:" || u.protocol === "https:";
12993
+ } catch {
12994
+ return false;
12995
+ }
12996
+ }
11728
12997
  function isValidAddress(s) {
11729
12998
  return /^0x[0-9a-fA-F]{40}$/.test(s);
11730
12999
  }
@@ -11749,7 +13018,14 @@ function registerSetup(program2) {
11749
13018
  if (Object.keys(existing).length > 0) {
11750
13019
  console.log(pc2.white(" Current configuration:"));
11751
13020
  for (const [key, value] of Object.entries(existing)) {
11752
- const masked = key.toLowerCase().includes("key") ? value.slice(0, 6) + "..." + value.slice(-4) : value;
13021
+ let masked;
13022
+ if (key.toLowerCase().includes("key")) {
13023
+ masked = value.slice(0, 6) + "..." + value.slice(-4);
13024
+ } else if (key.endsWith("RPC_URL")) {
13025
+ masked = maskRpcUrl(value);
13026
+ } else {
13027
+ masked = value;
13028
+ }
11753
13029
  console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
11754
13030
  }
11755
13031
  console.log();
@@ -11763,7 +13039,7 @@ function registerSetup(program2) {
11763
13039
  }
11764
13040
  const newEnv = {};
11765
13041
  console.log(pc2.cyan(pc2.bold(" Wallet")));
11766
- const privateKey = await ask(rl, " Private key (optional, for --broadcast, 0x...): ");
13042
+ const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
11767
13043
  if (privateKey) {
11768
13044
  const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
11769
13045
  if (!isValidPrivateKey(normalized)) {
@@ -11799,6 +13075,10 @@ function registerSetup(program2) {
11799
13075
  for (const { env, label } of rpcPrompts) {
11800
13076
  const value = await ask(rl, ` ${label} RPC URL: `);
11801
13077
  if (value) {
13078
+ if (!isValidRpcUrl(value)) {
13079
+ console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
13080
+ continue;
13081
+ }
11802
13082
  newEnv[env] = value;
11803
13083
  console.log(` ${pc2.green("OK")} ${label} RPC set`);
11804
13084
  }
@@ -11822,7 +13102,7 @@ function registerSetup(program2) {
11822
13102
  ];
11823
13103
  for (const [k, label] of rpcSummary) {
11824
13104
  const v = finalEnv[k];
11825
- if (v) console.log(` ${label}: ${pc2.gray(v)}`);
13105
+ if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
11826
13106
  }
11827
13107
  console.log(pc2.bold(pc2.white("\n Next steps:")));
11828
13108
  console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
@@ -11841,11 +13121,11 @@ function registerSetup(program2) {
11841
13121
  // src/commands/ows.ts
11842
13122
  import pc3 from "picocolors";
11843
13123
  init_dist();
11844
- import { createPublicClient as createPublicClient26, http as http26, formatEther as formatEther2 } from "viem";
13124
+ import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
11845
13125
  async function getEvmBalance(address, chainName) {
11846
13126
  const registry = Registry.loadEmbedded();
11847
13127
  const chain = registry.getChain(chainName);
11848
- const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
13128
+ const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
11849
13129
  const balance = await client.getBalance({ address });
11850
13130
  return {
11851
13131
  native_token: chain.native_token,
@@ -12069,7 +13349,8 @@ function makeExecutor() {
12069
13349
  process.exit(1);
12070
13350
  }
12071
13351
  const chain = registry.getChain(opts.chain);
12072
- return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url);
13352
+ const viemChain = chain.viemChain();
13353
+ return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url, viemChain);
12073
13354
  }
12074
13355
  registerStatus(program, getOutputMode);
12075
13356
  registerSchema(program, getOutputMode);
@@ -12080,7 +13361,7 @@ registerPortfolio(program, getOutputMode);
12080
13361
  registerPrice(program, getOutputMode);
12081
13362
  registerWallet(program, getOutputMode);
12082
13363
  registerToken(program, getOutputMode, makeExecutor);
12083
- registerBridge(program, getOutputMode);
13364
+ registerBridge(program, getOutputMode, makeExecutor);
12084
13365
  registerSwap(program, getOutputMode, makeExecutor);
12085
13366
  registerSetup(program);
12086
13367
  registerOws(program, getOutputMode);