@hypurrquant/defi-cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dw/@hypurrquant/defi-cli.svg)](https://www.npmjs.com/package/@hypurrquant/defi-cli)
5
5
  [![license](https://img.shields.io/npm/l/@hypurrquant/defi-cli.svg)](https://github.com/hypurrquant/defi-cli/blob/main/LICENSE)
6
6
 
7
- Multi-chain DeFi CLI — **7 chains · 48 protocols · 5 aggregators**. Lending, LP farming with emission claim, DEX swap via aggregator, cross-chain bridge.
7
+ Multi-chain DeFi CLI — **5 chains · 39 protocols · 5 aggregators**. Lending, LP farming with emission claim, DEX swap via aggregator, cross-chain bridge.
8
8
 
9
9
  ```bash
10
10
  npm install -g @hypurrquant/defi-cli
@@ -23,8 +23,6 @@ npx -y @hypurrquant/defi-cli --json status
23
23
  | Base | 8453 | 🟢 production | 5 |
24
24
  | BNB | 56 | 🟡 staged | 16 |
25
25
  | Monad | 143 | 🟡 staged | 4 |
26
- | Arbitrum | 42161 | 🟡 staged | 3 |
27
- | Ethereum | 1 | 🟡 staged | 6 |
28
26
 
29
27
  🟢 = full lifecycle broadcast verified (mint/supply → claim → withdraw)
30
28
  🟡 = configs + read-only paths verified, awaiting funded broadcast
@@ -68,21 +66,15 @@ npx -y @hypurrquant/defi-cli --json status
68
66
  ### Monad (4)
69
67
  `uniswap-v2-monad`, `uniswap-v3-monad`, `traderjoe-monad` (LB), `morpho-blue-monad`
70
68
 
71
- ### Ethereum (6)
72
- `aave-v2-ethereum`, `aave-v3-ethereum`, `compound-v3-ethereum`, `morpho-blue-ethereum`, `uniswap-v2-ethereum`, `uniswap-v3-ethereum`
73
-
74
- ### Arbitrum (3)
75
- `aave-v3-arbitrum`, `compound-v3-arbitrum`, `uniswap-v3-arbitrum`
76
-
77
69
  ## DEX Aggregators (Live-verified)
78
70
 
79
- | Aggregator | HyperEVM | Mantle | Base | BNB | Ethereum | Arbitrum |
80
- |---|---|---|---|---|---|---|
81
- | KyberSwap | ✅ | ❌ | ✅ | ✅ | | ✅ |
82
- | OpenOcean | ✅ | ✅ | ✅ | ✅ | | ✅ |
83
- | LiquidSwap | ✅ | — | — | — | — | — |
84
- | LI.FI | ✅ | ✅ | ✅ | ✅ | | ✅ |
85
- | Relay | ✅ | ✅ | ✅ | ✅ | | ✅ |
71
+ | Aggregator | HyperEVM | Mantle | Base | BNB | Monad |
72
+ |---|---|---|---|---|---|
73
+ | KyberSwap | ✅ | ❌ | ✅ | ✅ | |
74
+ | OpenOcean | ✅ | ✅ | ✅ | ✅ | |
75
+ | LiquidSwap | ✅ | — | — | — | — |
76
+ | LI.FI | ✅ | ✅ | ✅ | ✅ | |
77
+ | Relay | ✅ | ✅ | ✅ | ✅ | |
86
78
 
87
79
  ## Setup
88
80
 
@@ -169,7 +161,7 @@ defi --chain base lp claim --protocol aerodrome-cl \
169
161
  # Pick the cheapest provider per chain
170
162
  defi --chain mantle swap --provider lifi --from MOE --to WMNT --amount <wei> --broadcast
171
163
  defi --chain base swap --provider kyber --from WETH --to USDC --amount <wei> --broadcast
172
- defi --chain ethereum swap --provider relay --from WETH --to USDC --amount <wei> --broadcast
164
+ defi --chain bnb swap --provider relay --from WBNB --to USDT --amount <wei> --broadcast
173
165
  ```
174
166
 
175
167
  ## Agent-First Design
@@ -229,7 +221,7 @@ Or copy `skills/defi-cli/` into your Claude Code skills directory.
229
221
  ## Global Flags
230
222
 
231
223
  ```bash
232
- --chain <name> # hyperevm | mantle | base | bnb | monad | arbitrum | ethereum
224
+ --chain <name> # hyperevm | mantle | base | bnb | monad
233
225
  --json # JSON output
234
226
  --ndjson # NDJSON streaming
235
227
  --fields <a,b> # Output field filter
@@ -7543,7 +7543,7 @@ server.tool(
7543
7543
  "defi_status",
7544
7544
  "Show chain and protocol status: lists all protocols deployed on a chain with contract addresses and categories",
7545
7545
  {
7546
- chain: z.string().optional().describe("Chain name (default: hyperevm). E.g. hyperevm, ethereum, arbitrum, base")
7546
+ chain: z.string().optional().describe("Chain name (default: hyperevm). One of: hyperevm, mantle, base, bnb, monad")
7547
7547
  },
7548
7548
  async ({ chain }) => {
7549
7549
  try {
@@ -7800,8 +7800,8 @@ server.tool(
7800
7800
  "defi_bridge",
7801
7801
  "Get a cross-chain bridge quote via LI.FI, deBridge DLN, or Circle CCTP. Returns estimated output amount and fees",
7802
7802
  {
7803
- from_chain: z.string().describe("Source chain name, e.g. ethereum, arbitrum, base"),
7804
- to_chain: z.string().describe("Destination chain name, e.g. hyperevm, arbitrum"),
7803
+ from_chain: z.string().describe("Source chain name (supported source chains: hyperevm, mantle, base, bnb, monad)"),
7804
+ to_chain: z.string().describe("Destination chain name. CCTP V2 destinations include ethereum, arbitrum, optimism, polygon, avalanche; LI.FI/deBridge accept any chain"),
7805
7805
  token: z.string().optional().describe("Token symbol to bridge (default: USDC). Use native for native token"),
7806
7806
  amount: z.string().describe("Amount in human-readable units, e.g. '100' for 100 USDC"),
7807
7807
  recipient: z.string().optional().describe("Recipient address on destination chain")
@@ -8465,6 +8465,352 @@ server.tool(
8465
8465
  };
8466
8466
  }
8467
8467
  );
8468
+ server.tool(
8469
+ "defi_lp_compound",
8470
+ "V3 fee auto-compound: collect accrued LP fees and re-add them as liquidity in one tx. Requires NFT tokenId. Returns simulated/confirmed tx with collected fee amounts.",
8471
+ {
8472
+ chain: z.string().describe("Chain name (e.g. base, hyperevm, mantle)"),
8473
+ protocol: z.string().describe("Protocol slug (e.g. uniswap-v3-base, hyperswap, project-x)"),
8474
+ token_id: z.string().describe("NFT tokenId of the LP position to compound"),
8475
+ slippage_bps: z.number().optional().describe("Slippage tolerance in bps (default: 50 = 0.5%)"),
8476
+ address: z.string().optional().describe("Recipient address (defaults to DEFI_WALLET_ADDRESS)"),
8477
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8478
+ },
8479
+ async ({ chain, protocol, token_id, slippage_bps, address, broadcast }) => {
8480
+ try {
8481
+ const chainName = chain.toLowerCase();
8482
+ const registry = getRegistry();
8483
+ const chainConfig = registry.getChain(chainName);
8484
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8485
+ const proto = registry.getProtocol(protocol);
8486
+ const recipient = address ?? process.env["DEFI_WALLET_ADDRESS"];
8487
+ if (!recipient) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8488
+ const adapter = createDex(proto, rpcUrl);
8489
+ if (!("buildCompound" in adapter) || typeof adapter.buildCompound !== "function") {
8490
+ throw new Error(`[${proto.name}] adapter does not support compound (V3 fee-only protocols only)`);
8491
+ }
8492
+ const compoundOpts = slippage_bps !== void 0 ? { slippageBps: slippage_bps } : void 0;
8493
+ const tx = await adapter.buildCompound(BigInt(token_id), recipient, compoundOpts);
8494
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8495
+ const result = await executor.execute(tx);
8496
+ return {
8497
+ content: [{ type: "text", text: ok(result, { chain: chainName, protocol, token_id, broadcast: broadcast ?? false }) }]
8498
+ };
8499
+ } catch (e) {
8500
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol, token_id }) }], isError: true };
8501
+ }
8502
+ }
8503
+ );
8504
+ server.tool(
8505
+ "defi_lp_claim",
8506
+ "Claim LP rewards from a pool. Auto-dispatches by protocol interface: V3 fees (NPM.collect), gauge emission (Solidly/Hybra/Ramses CL), Merchant Moe LB hooks, KittenSwap eternal farming, off-chain Nest tickets. Requires either token-id (CL/NFT positions) or gauge (V2-style staking) plus pool for LB.",
8507
+ {
8508
+ chain: z.string().describe("Chain name"),
8509
+ protocol: z.string().describe("Protocol slug"),
8510
+ pool: z.string().optional().describe("Pool address (required for LB / KittenSwap farming)"),
8511
+ gauge: z.string().optional().describe("Gauge address (required for solidly/hybra)"),
8512
+ token_id: z.string().optional().describe("NFT tokenId (CL gauge or farming positions)"),
8513
+ bins: z.string().optional().describe("Comma-separated bin IDs (Merchant Moe LB; auto-detected if omitted)"),
8514
+ address: z.string().optional().describe("Wallet address (defaults to DEFI_WALLET_ADDRESS)"),
8515
+ redeem_type: z.number().optional().describe("Hybra: 0=instant exit (penalty), 1=2yr veHYBR lock (default)"),
8516
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8517
+ },
8518
+ async ({ chain, protocol, pool, gauge, token_id, bins, address, redeem_type, broadcast }) => {
8519
+ try {
8520
+ const chainName = chain.toLowerCase();
8521
+ const registry = getRegistry();
8522
+ const chainConfig = registry.getChain(chainName);
8523
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8524
+ const proto = registry.getProtocol(protocol);
8525
+ const account = address ?? process.env["DEFI_WALLET_ADDRESS"];
8526
+ if (!account) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8527
+ const iface = proto.interface;
8528
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8529
+ const { createKittenSwapFarming: createKittenSwapFarming2, createMerchantMoeLB: createMerchantMoeLB2, createGauge: createGauge2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8530
+ const isV3Fee = proto.reward_strategy === "lp_fee_only" || iface === "uniswap_v3" && token_id && !gauge;
8531
+ if (isV3Fee) {
8532
+ if (!token_id) throw new Error("token_id required for V3 fee collection");
8533
+ const adapter = createDex(proto, rpcUrl);
8534
+ if (!("buildCollectFees" in adapter) || typeof adapter.buildCollectFees !== "function") {
8535
+ throw new Error(`[${proto.name}] adapter does not support buildCollectFees`);
8536
+ }
8537
+ const tx = await adapter.buildCollectFees(BigInt(token_id), account);
8538
+ const result = await executor.execute(tx);
8539
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "v3_fee", broadcast: broadcast ?? false }) }] };
8540
+ }
8541
+ if (iface === "algebra_v3" && proto.contracts?.["farming_center"]) {
8542
+ if (!pool) throw new Error("pool required for KittenSwap farming claim");
8543
+ if (!token_id) throw new Error("token_id required for KittenSwap farming claim");
8544
+ const adapter = createKittenSwapFarming2(proto, rpcUrl);
8545
+ const tx = await adapter.buildCollectRewards(BigInt(token_id), pool, account);
8546
+ const result = await executor.execute(tx);
8547
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "kittenswap_farming", broadcast: broadcast ?? false }) }] };
8548
+ }
8549
+ if (iface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8550
+ if (!pool) throw new Error("pool required for Merchant Moe LB claim");
8551
+ const adapter = createMerchantMoeLB2(proto, rpcUrl);
8552
+ const binIds = bins ? bins.split(",").map((s) => parseInt(s.trim(), 10)) : void 0;
8553
+ const tx = await adapter.buildClaimRewards(account, pool, binIds);
8554
+ const result = await executor.execute(tx);
8555
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "lb", broadcast: broadcast ?? false }) }] };
8556
+ }
8557
+ if (["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(iface) || iface === "uniswap_v3" && proto.contracts?.["voter"]) {
8558
+ if (!gauge) throw new Error("gauge required for gauge-based claim");
8559
+ const adapter = createGauge2(proto, rpcUrl);
8560
+ let tx;
8561
+ if (token_id && iface === "uniswap_v3" && proto.reward_strategy === "auto_stake" && "buildClaimRewardsViaNPMPeriodReward" in adapter && typeof adapter.buildClaimRewardsViaNPMPeriodReward === "function") {
8562
+ const npm = proto.contracts?.["position_manager"];
8563
+ if (!npm) throw new Error(`${proto.name} requires contracts.position_manager for NPM-based claim`);
8564
+ tx = await adapter.buildClaimRewardsViaNPMPeriodReward(npm, BigInt(token_id), account, { gauge });
8565
+ } else if (token_id) {
8566
+ if (!adapter.buildClaimRewardsByTokenId) throw new Error(`${proto.name} does not support NFT-based claim`);
8567
+ const claimOpts = redeem_type !== void 0 ? { redeemType: redeem_type } : void 0;
8568
+ tx = await adapter.buildClaimRewardsByTokenId(gauge, BigInt(token_id), claimOpts);
8569
+ } else {
8570
+ tx = await adapter.buildClaimRewards(gauge, account);
8571
+ }
8572
+ const result = await executor.execute(tx);
8573
+ return { content: [{ type: "text", text: ok(result, { chain: chainName, protocol, kind: "gauge", broadcast: broadcast ?? false }) }] };
8574
+ }
8575
+ throw new Error(`No claim method found for protocol interface '${iface}'`);
8576
+ } catch (e) {
8577
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol }) }], isError: true };
8578
+ }
8579
+ }
8580
+ );
8581
+ server.tool(
8582
+ "defi_lp_farm",
8583
+ "Add liquidity and auto-stake into gauge/farming for emissions. Two-step flow: mint LP NFT, then deposit into gauge (Solidly/Hybra) or enterFarming (KittenSwap eternal). Merchant Moe LB hooks need no staking.",
8584
+ {
8585
+ chain: z.string().describe("Chain name"),
8586
+ protocol: z.string().describe("Protocol slug"),
8587
+ token_a: z.string().describe("First token symbol or address"),
8588
+ token_b: z.string().describe("Second token symbol or address"),
8589
+ amount_a: z.string().describe("Amount of token A in wei"),
8590
+ amount_b: z.string().describe("Amount of token B in wei"),
8591
+ pool: z.string().optional().describe("Pool name (e.g. WHYPE/USDC) or address"),
8592
+ gauge: z.string().optional().describe("Gauge address (auto-resolved if omitted)"),
8593
+ recipient: z.string().optional().describe("Recipient/owner (defaults to DEFI_WALLET_ADDRESS)"),
8594
+ tick_lower: z.number().optional().describe("Lower tick (CL)"),
8595
+ tick_upper: z.number().optional().describe("Upper tick (CL)"),
8596
+ range_pct: z.number().optional().describe("\xB1N% concentrated range around current price"),
8597
+ broadcast: z.boolean().optional().describe("Broadcast tx (default: false = dry-run)")
8598
+ },
8599
+ async ({ chain, protocol, token_a, token_b, amount_a, amount_b, pool, gauge, recipient, tick_lower, tick_upper, range_pct, broadcast }) => {
8600
+ try {
8601
+ const chainName = chain.toLowerCase();
8602
+ const registry = getRegistry();
8603
+ const chainConfig = registry.getChain(chainName);
8604
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8605
+ const proto = registry.getProtocol(protocol);
8606
+ const owner = recipient ?? process.env["DEFI_WALLET_ADDRESS"] ?? "0x0000000000000000000000000000000000000001";
8607
+ const tokenA = resolveToken(registry, chainName, token_a);
8608
+ const tokenB = resolveToken(registry, chainName, token_b);
8609
+ let poolAddr;
8610
+ if (pool) {
8611
+ poolAddr = pool.startsWith("0x") ? pool : registry.resolvePool(protocol, pool).address;
8612
+ }
8613
+ const dexAdapter = createDex(proto, rpcUrl);
8614
+ const addTx = await dexAdapter.buildAddLiquidity({
8615
+ protocol: proto.name,
8616
+ token_a: tokenA,
8617
+ token_b: tokenB,
8618
+ amount_a: BigInt(amount_a),
8619
+ amount_b: BigInt(amount_b),
8620
+ recipient: owner,
8621
+ tick_lower,
8622
+ tick_upper,
8623
+ range_pct,
8624
+ pool: poolAddr
8625
+ });
8626
+ const executor = makeExecutor(broadcast ?? false, rpcUrl, chainConfig.explorer_url);
8627
+ const addResult = await executor.execute(addTx);
8628
+ const steps = [{ step: "lp_add", ...addResult }];
8629
+ if (addResult.status !== "confirmed" && addResult.status !== "simulated") {
8630
+ return { content: [{ type: "text", text: ok({ steps, note: "LP add did not succeed; staking skipped." }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8631
+ }
8632
+ const mintedTokenId = addResult.details?.minted_token_id ? BigInt(addResult.details.minted_token_id) : void 0;
8633
+ const iface = proto.interface;
8634
+ const { createKittenSwapFarming: createKittenSwapFarming2, createGauge: createGauge2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8635
+ if (iface === "algebra_v3" && proto.contracts?.["farming_center"]) {
8636
+ if (!mintedTokenId) {
8637
+ steps.push({ step: "stake_farming", skipped: "no tokenId in dry-run mode" });
8638
+ } else if (!poolAddr) {
8639
+ throw new Error("pool required for KittenSwap farming");
8640
+ } else {
8641
+ const farmAdapter = createKittenSwapFarming2(proto, rpcUrl);
8642
+ const stakeTx = await farmAdapter.buildEnterFarming(mintedTokenId, poolAddr, owner);
8643
+ const stakeResult = await executor.execute(stakeTx);
8644
+ steps.push({ step: "stake_farming", ...stakeResult });
8645
+ }
8646
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8647
+ }
8648
+ const isGaugeStakeable = ["solidly_v2", "solidly_cl", "hybra"].includes(iface) || iface === "uniswap_v3" && proto.contracts?.["voter"];
8649
+ if (isGaugeStakeable) {
8650
+ if (!mintedTokenId && iface !== "solidly_v2") {
8651
+ steps.push({ step: "stake_gauge", skipped: "no tokenId in dry-run mode" });
8652
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8653
+ }
8654
+ let gaugeAddr = gauge;
8655
+ const gaugeAdapter = createGauge2(proto, rpcUrl);
8656
+ if (!gaugeAddr && poolAddr && gaugeAdapter.resolveGauge) {
8657
+ try {
8658
+ gaugeAddr = await gaugeAdapter.resolveGauge(poolAddr);
8659
+ } catch {
8660
+ }
8661
+ }
8662
+ if (!gaugeAddr) throw new Error("gauge required (could not auto-resolve)");
8663
+ let amountArg = 0n;
8664
+ if (iface === "solidly_v2" && poolAddr) {
8665
+ const { createPublicClient: createPublicClient24, http: httpTransport, parseAbi: parseAbi31 } = await import("viem");
8666
+ const erc20Abi3 = parseAbi31(["function balanceOf(address) view returns (uint256)"]);
8667
+ const lpClient = createPublicClient24({ transport: httpTransport(rpcUrl) });
8668
+ amountArg = await lpClient.readContract({
8669
+ address: poolAddr,
8670
+ abi: erc20Abi3,
8671
+ functionName: "balanceOf",
8672
+ args: [owner]
8673
+ });
8674
+ if (amountArg === 0n) {
8675
+ steps.push({ step: "stake_gauge", skipped: "LP balance 0 after mint" });
8676
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8677
+ }
8678
+ }
8679
+ const lpTokenArg = iface === "solidly_v2" ? poolAddr : void 0;
8680
+ const stakeTx = await gaugeAdapter.buildDeposit(gaugeAddr, amountArg, mintedTokenId, lpTokenArg);
8681
+ const stakeResult = await executor.execute(stakeTx);
8682
+ steps.push({ step: "stake_gauge", ...stakeResult });
8683
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8684
+ }
8685
+ if (iface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8686
+ steps.push({ step: "stake_gauge", skipped: "Merchant Moe LB hooks auto-handle rewards" });
8687
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8688
+ }
8689
+ steps.push({ step: "stake_gauge", skipped: "No staking adapter for this interface" });
8690
+ return { content: [{ type: "text", text: ok({ steps }, { chain: chainName, protocol, broadcast: broadcast ?? false }) }] };
8691
+ } catch (e) {
8692
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain, protocol }) }], isError: true };
8693
+ }
8694
+ }
8695
+ );
8696
+ server.tool(
8697
+ "defi_lp_positions",
8698
+ "Show all LP positions for a wallet across protocols on a chain. Includes Merchant Moe LB bins (with pending MOE), V3/Algebra/Hybra NFT positions. Auto-detects user's actual bin IDs for LB pools.",
8699
+ {
8700
+ chain: z.string().describe("Chain name"),
8701
+ protocol: z.string().optional().describe("Filter to a single protocol slug"),
8702
+ pool: z.string().optional().describe("Filter to a specific pool address"),
8703
+ bins: z.string().optional().describe("Comma-separated bin IDs (LB; auto-detected if omitted)"),
8704
+ address: z.string().optional().describe("Wallet address (defaults to DEFI_WALLET_ADDRESS)")
8705
+ },
8706
+ async ({ chain, protocol, pool, bins, address }) => {
8707
+ try {
8708
+ const chainName = chain.toLowerCase();
8709
+ const registry = getRegistry();
8710
+ const chainConfig = registry.getChain(chainName);
8711
+ const rpcUrl = chainConfig.effectiveRpcUrl();
8712
+ const user = address ?? process.env["DEFI_WALLET_ADDRESS"];
8713
+ if (!user) throw new Error("address required (or set DEFI_WALLET_ADDRESS)");
8714
+ const { createMerchantMoeLB: createMerchantMoeLB2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8715
+ const { createPublicClient: createPublicClient24, http: httpTransport, parseAbi: parseAbi31 } = await import("viem");
8716
+ const allProtocols = registry.getProtocolsForChain(chainName);
8717
+ const protocols = protocol ? [registry.getProtocol(protocol)] : allProtocols;
8718
+ const results = [];
8719
+ await Promise.allSettled(
8720
+ protocols.map(async (proto) => {
8721
+ try {
8722
+ if (proto.interface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
8723
+ const adapter = createMerchantMoeLB2(proto, rpcUrl);
8724
+ const binIds = bins ? bins.split(",").map((s) => parseInt(s.trim(), 10)) : void 0;
8725
+ const poolsToScan = pool ? [pool] : (await adapter.discoverRewardedPools()).map((p) => p.pool);
8726
+ for (const poolAddr of poolsToScan) {
8727
+ try {
8728
+ const userBins = binIds ?? await adapter.findUserBinsWithBalance(poolAddr, user);
8729
+ if (userBins.length === 0) continue;
8730
+ const positions = await adapter.getUserPositions(user, poolAddr, userBins);
8731
+ if (positions.length === 0) continue;
8732
+ const pending = await adapter.getPendingRewards(user, poolAddr, userBins).catch(() => []);
8733
+ const totalPending = pending.reduce((s, r) => s + (r.amount ?? 0n), 0n);
8734
+ for (const pos of positions) {
8735
+ results.push({
8736
+ protocol: proto.slug,
8737
+ type: "lb",
8738
+ pool: poolAddr,
8739
+ ...pos,
8740
+ pending_reward: totalPending.toString(),
8741
+ pending_reward_token: pending[0]?.token
8742
+ });
8743
+ }
8744
+ } catch {
8745
+ }
8746
+ }
8747
+ }
8748
+ const npm = proto.contracts?.["position_manager"];
8749
+ if (npm && ["uniswap_v3", "algebra_v3", "hybra"].includes(proto.interface)) {
8750
+ const npmAbi = parseAbi31([
8751
+ "function balanceOf(address) view returns (uint256)",
8752
+ "function tokenOfOwnerByIndex(address, uint256) view returns (uint256)",
8753
+ "function positions(uint256) view returns (uint96 nonce, address op, address t0, address t1, uint24 fee, int24 tl, int24 tu, uint128 liq, uint256 a, uint256 b, uint128 o0, uint128 o1)"
8754
+ ]);
8755
+ const ramsesAbi = parseAbi31([
8756
+ "function positions(uint256) view returns (address t0, address t1, int24 ts, int24 tl, int24 tu, uint128 liq, uint256 a, uint256 b, uint128 o0, uint128 o1)"
8757
+ ]);
8758
+ const client = createPublicClient24({ transport: httpTransport(rpcUrl) });
8759
+ let count;
8760
+ try {
8761
+ count = await client.readContract({ address: npm, abi: npmAbi, functionName: "balanceOf", args: [user] });
8762
+ } catch {
8763
+ return;
8764
+ }
8765
+ const max = Math.min(Number(count), 50);
8766
+ for (let i = 0; i < max; i++) {
8767
+ try {
8768
+ const tokenId = await client.readContract({
8769
+ address: npm,
8770
+ abi: npmAbi,
8771
+ functionName: "tokenOfOwnerByIndex",
8772
+ args: [user, BigInt(i)]
8773
+ });
8774
+ let liq = 0n;
8775
+ let token0;
8776
+ let token1;
8777
+ let tickLower;
8778
+ let tickUpper;
8779
+ try {
8780
+ const r = await client.readContract({ address: npm, abi: ramsesAbi, functionName: "positions", args: [tokenId] });
8781
+ [token0, token1, , tickLower, tickUpper, liq] = r;
8782
+ } catch {
8783
+ const r = await client.readContract({ address: npm, abi: npmAbi, functionName: "positions", args: [tokenId] });
8784
+ [, , token0, token1, , tickLower, tickUpper, liq] = r;
8785
+ }
8786
+ if (liq > 0n) {
8787
+ results.push({
8788
+ protocol: proto.slug,
8789
+ type: "v3_nft",
8790
+ token_id: tokenId.toString(),
8791
+ token0,
8792
+ token1,
8793
+ liquidity: liq.toString(),
8794
+ tick_lower: tickLower,
8795
+ tick_upper: tickUpper
8796
+ });
8797
+ }
8798
+ } catch {
8799
+ }
8800
+ }
8801
+ }
8802
+ } catch {
8803
+ }
8804
+ })
8805
+ );
8806
+ return {
8807
+ content: [{ type: "text", text: ok({ chain: chainName, positions: results, total: results.length }, { wallet: user, scanned_protocols: protocols.length }) }]
8808
+ };
8809
+ } catch (e) {
8810
+ return { content: [{ type: "text", text: err(e instanceof Error ? e.message : String(e), { chain }) }], isError: true };
8811
+ }
8812
+ }
8813
+ );
8468
8814
  var transport = new StdioServerTransport();
8469
8815
  await server.connect(transport);
8470
8816
  //# sourceMappingURL=mcp-server.js.map