@hypurrquant/defi-cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -39
- package/dist/index.js +7588 -5875
- package/dist/index.js.map +1 -1
- package/dist/main.js +7631 -5916
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +310 -63
- package/dist/mcp-server.js.map +1 -1
- package/package.json +4 -3
- package/skills/defi-cli/SKILL.md +181 -0
- package/skills/defi-cli/package.json +38 -0
- package/skills/defi-cli/references/commands.md +162 -0
- package/skills/defi-cli/references/protocols.md +115 -0
- package/skills/defi-cli/scripts/exploit-scan.sh +10 -0
- package/skills/defi-cli/scripts/portfolio-snapshot.sh +19 -0
- package/skills/defi-cli/scripts/preflight.sh +14 -0
- package/skills/defi-cli/scripts/yield-scan.sh +10 -0
package/dist/mcp-server.js
CHANGED
|
@@ -436,9 +436,9 @@ var init_dist = __esm({
|
|
|
436
436
|
getProtocolsByCategory(category) {
|
|
437
437
|
return this.protocols.filter((p) => p.category === category);
|
|
438
438
|
}
|
|
439
|
-
getProtocolsForChain(chain) {
|
|
439
|
+
getProtocolsForChain(chain, includeUnverified = false) {
|
|
440
440
|
return this.protocols.filter(
|
|
441
|
-
(p) => p.chain.toLowerCase() === chain.toLowerCase()
|
|
441
|
+
(p) => p.chain.toLowerCase() === chain.toLowerCase() && (includeUnverified || p.verified !== false)
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
444
|
resolveToken(chain, symbol) {
|
|
@@ -932,7 +932,7 @@ function createLiquidStaking(entry, rpcUrl) {
|
|
|
932
932
|
return new GenericLstAdapter(entry, rpcUrl);
|
|
933
933
|
}
|
|
934
934
|
}
|
|
935
|
-
function createGauge(entry, rpcUrl) {
|
|
935
|
+
function createGauge(entry, rpcUrl, tokens) {
|
|
936
936
|
if (entry.interface === "hybra" || entry.contracts?.["gauge_manager"]) {
|
|
937
937
|
return new HybraGaugeAdapter(entry, rpcUrl);
|
|
938
938
|
}
|
|
@@ -940,7 +940,11 @@ function createGauge(entry, rpcUrl) {
|
|
|
940
940
|
case "solidly_v2":
|
|
941
941
|
case "solidly_cl":
|
|
942
942
|
case "algebra_v3":
|
|
943
|
-
return new SolidlyGaugeAdapter(entry, rpcUrl);
|
|
943
|
+
return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
|
|
944
|
+
// uniswap_v3 with voter = ve(3,3) CL (e.g., Aerodrome Slipstream, Ramses CL)
|
|
945
|
+
case "uniswap_v3":
|
|
946
|
+
if (entry.contracts?.["voter"]) return new SolidlyGaugeAdapter(entry, rpcUrl, tokens);
|
|
947
|
+
throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported (no voter contract)`);
|
|
944
948
|
default:
|
|
945
949
|
throw DefiError.unsupported(`Gauge interface '${entry.interface}' not supported`);
|
|
946
950
|
}
|
|
@@ -2295,6 +2299,8 @@ var init_dist2 = __esm({
|
|
|
2295
2299
|
gauge,
|
|
2296
2300
|
token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
|
|
2297
2301
|
token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
|
|
2302
|
+
token0Addr: t0 ?? void 0,
|
|
2303
|
+
token1Addr: t1 ?? void 0,
|
|
2298
2304
|
type: "CL"
|
|
2299
2305
|
});
|
|
2300
2306
|
}
|
|
@@ -2500,6 +2506,7 @@ var init_dist2 = __esm({
|
|
|
2500
2506
|
"function getReward(address account, address[] tokens) external",
|
|
2501
2507
|
"function getReward(uint256 tokenId) external",
|
|
2502
2508
|
"function earned(address account) external view returns (uint256)",
|
|
2509
|
+
"function earned(address account, uint256 tokenId) external view returns (uint256)",
|
|
2503
2510
|
"function earned(address token, address account) external view returns (uint256)",
|
|
2504
2511
|
"function earned(uint256 tokenId) external view returns (uint256)",
|
|
2505
2512
|
"function rewardRate() external view returns (uint256)",
|
|
@@ -2550,7 +2557,8 @@ var init_dist2 = __esm({
|
|
|
2550
2557
|
rpcUrl;
|
|
2551
2558
|
clFactory;
|
|
2552
2559
|
v2Factory;
|
|
2553
|
-
|
|
2560
|
+
tokens;
|
|
2561
|
+
constructor(entry, rpcUrl, tokens) {
|
|
2554
2562
|
this.protocolName = entry.name;
|
|
2555
2563
|
const voter = entry.contracts?.["voter"];
|
|
2556
2564
|
if (!voter) {
|
|
@@ -2563,6 +2571,7 @@ var init_dist2 = __esm({
|
|
|
2563
2571
|
this.voter = voter;
|
|
2564
2572
|
this.veToken = veToken;
|
|
2565
2573
|
this.rpcUrl = rpcUrl;
|
|
2574
|
+
this.tokens = tokens;
|
|
2566
2575
|
this.clFactory = entry.contracts?.["cl_factory"] ?? entry.contracts?.["factory"];
|
|
2567
2576
|
this.v2Factory = entry.contracts?.["pair_factory"] ?? entry.contracts?.["factory"];
|
|
2568
2577
|
}
|
|
@@ -2577,57 +2586,148 @@ var init_dist2 = __esm({
|
|
|
2577
2586
|
this._discoverV2GaugedPools(results),
|
|
2578
2587
|
this._discoverCLGaugedPools(results)
|
|
2579
2588
|
]);
|
|
2589
|
+
await this._enrichGaugeMetrics(results);
|
|
2580
2590
|
return results;
|
|
2581
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Batch query rewardRate, totalSupply, rewardToken for all discovered gauges.
|
|
2594
|
+
* Handles both single-token (rewardRate) and multi-token (rewardData) gauges.
|
|
2595
|
+
*/
|
|
2596
|
+
async _enrichGaugeMetrics(pools) {
|
|
2597
|
+
if (!this.rpcUrl || pools.length === 0) return;
|
|
2598
|
+
const _u256Abi = parseAbi10(["function f() view returns (uint256)"]);
|
|
2599
|
+
const calls = [];
|
|
2600
|
+
for (const p of pools) {
|
|
2601
|
+
calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardRate" })]);
|
|
2602
|
+
calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "totalSupply" })]);
|
|
2603
|
+
calls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardToken" })]);
|
|
2604
|
+
}
|
|
2605
|
+
const results = await multicallRead(this.rpcUrl, calls).catch(() => []);
|
|
2606
|
+
for (let i = 0; i < pools.length; i++) {
|
|
2607
|
+
const base = i * 3;
|
|
2608
|
+
try {
|
|
2609
|
+
pools[i].rewardRate = results[base] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base] }) : 0n;
|
|
2610
|
+
} catch {
|
|
2611
|
+
pools[i].rewardRate = 0n;
|
|
2612
|
+
}
|
|
2613
|
+
try {
|
|
2614
|
+
pools[i].totalStaked = results[base + 1] ? decodeFunctionResult3({ abi: _u256Abi, functionName: "f", data: results[base + 1] }) : 0n;
|
|
2615
|
+
} catch {
|
|
2616
|
+
pools[i].totalStaked = 0n;
|
|
2617
|
+
}
|
|
2618
|
+
try {
|
|
2619
|
+
pools[i].rewardToken = results[base + 2] ? decodeAddress2(results[base + 2]) ?? void 0 : void 0;
|
|
2620
|
+
} catch {
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
const KNOWN_REWARD_TOKENS = [
|
|
2624
|
+
"0x555570a286F15EbDFE42B66eDE2f724Aa1AB5555",
|
|
2625
|
+
// xRAM
|
|
2626
|
+
"0x5555555555555555555555555555555555555555",
|
|
2627
|
+
// WHYPE
|
|
2628
|
+
"0x067b0C72aa4C6Bd3BFEFfF443c536DCd6a25a9C8",
|
|
2629
|
+
// HYBR
|
|
2630
|
+
"0x07c57E32a3C29D5659bda1d3EFC2E7BF004E3035"
|
|
2631
|
+
// NEST
|
|
2632
|
+
];
|
|
2633
|
+
const needsFallback = pools.filter((p) => (p.rewardRate ?? 0n) === 0n && (p.totalStaked ?? 0n) > 0n);
|
|
2634
|
+
if (needsFallback.length === 0) return;
|
|
2635
|
+
const rdCalls = [];
|
|
2636
|
+
const rdMeta = [];
|
|
2637
|
+
for (const p of needsFallback) {
|
|
2638
|
+
const poolIdx = pools.indexOf(p);
|
|
2639
|
+
for (const token of KNOWN_REWARD_TOKENS) {
|
|
2640
|
+
rdCalls.push([p.gauge, encodeFunctionData10({ abi: gaugeAbi, functionName: "rewardData", args: [token] })]);
|
|
2641
|
+
rdMeta.push({ poolIdx, token });
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
const rdResults = await multicallRead(this.rpcUrl, rdCalls).catch(() => []);
|
|
2645
|
+
const _rdAbi = parseAbi10(["function f() view returns (uint256, uint256, uint256, uint256)"]);
|
|
2646
|
+
for (let i = 0; i < rdMeta.length; i++) {
|
|
2647
|
+
const { poolIdx, token } = rdMeta[i];
|
|
2648
|
+
const pool = pools[poolIdx];
|
|
2649
|
+
if ((pool.rewardRate ?? 0n) > 0n) continue;
|
|
2650
|
+
try {
|
|
2651
|
+
if (!rdResults[i]) continue;
|
|
2652
|
+
const decoded = decodeFunctionResult3({ abi: _rdAbi, functionName: "f", data: rdResults[i] });
|
|
2653
|
+
const [periodFinish, rewardRate] = decoded;
|
|
2654
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
2655
|
+
if (rewardRate > 0n && periodFinish > now) {
|
|
2656
|
+
pool.rewardRate = rewardRate;
|
|
2657
|
+
pool.rewardToken = token;
|
|
2658
|
+
}
|
|
2659
|
+
} catch {
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2582
2663
|
async _discoverV2GaugedPools(out) {
|
|
2583
2664
|
if (!this.rpcUrl || !this.v2Factory) return;
|
|
2584
|
-
const v2FactoryAbi = parseAbi10([
|
|
2585
|
-
"function allPairsLength() external view returns (uint256)",
|
|
2586
|
-
"function allPairs(uint256) external view returns (address)"
|
|
2587
|
-
]);
|
|
2588
2665
|
const pairAbi = parseAbi10([
|
|
2589
2666
|
"function token0() external view returns (address)",
|
|
2590
2667
|
"function token1() external view returns (address)",
|
|
2591
2668
|
"function stable() external view returns (bool)"
|
|
2592
2669
|
]);
|
|
2593
2670
|
const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
|
|
2671
|
+
const voterLengthAbi = parseAbi10(["function length() external view returns (uint256)"]);
|
|
2672
|
+
const voterPoolsAbi = parseAbi10(["function pools(uint256) external view returns (address)"]);
|
|
2673
|
+
const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
|
|
2674
|
+
const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
|
|
2675
|
+
const gaugesAbi = parseAbi10(["function gauges(address) external view returns (address)"]);
|
|
2594
2676
|
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
2595
|
-
let
|
|
2677
|
+
let pairs = [];
|
|
2678
|
+
let voterPoolCount = 0;
|
|
2596
2679
|
try {
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
abi: v2FactoryAbi,
|
|
2600
|
-
functionName: "allPairsLength"
|
|
2601
|
-
});
|
|
2680
|
+
const len = await client.readContract({ address: this.voter, abi: voterLengthAbi, functionName: "length" });
|
|
2681
|
+
voterPoolCount = Number(len);
|
|
2602
2682
|
} catch {
|
|
2603
|
-
return;
|
|
2604
2683
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
this.
|
|
2611
|
-
|
|
2612
|
-
|
|
2684
|
+
if (voterPoolCount > 0) {
|
|
2685
|
+
const MAX_SCAN = 500;
|
|
2686
|
+
const startIdx = Math.max(0, voterPoolCount - MAX_SCAN);
|
|
2687
|
+
const poolCalls = [];
|
|
2688
|
+
for (let i = startIdx; i < voterPoolCount; i++) {
|
|
2689
|
+
poolCalls.push([this.voter, encodeFunctionData10({ abi: voterPoolsAbi, functionName: "pools", args: [BigInt(i)] })]);
|
|
2690
|
+
}
|
|
2691
|
+
const poolResults = await multicallRead(this.rpcUrl, poolCalls);
|
|
2692
|
+
pairs = poolResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
|
|
2693
|
+
} else {
|
|
2694
|
+
const v2FactoryAbi = parseAbi10(["function allPairsLength() view returns (uint256)", "function allPairs(uint256) view returns (address)"]);
|
|
2695
|
+
const solidlyFactoryAbi = parseAbi10(["function allPoolsLength() view returns (uint256)", "function allPools(uint256) view returns (address)"]);
|
|
2696
|
+
let pairCount = 0n;
|
|
2697
|
+
let useSolidly = false;
|
|
2698
|
+
try {
|
|
2699
|
+
pairCount = await client.readContract({ address: this.v2Factory, abi: v2FactoryAbi, functionName: "allPairsLength" });
|
|
2700
|
+
} catch {
|
|
2701
|
+
try {
|
|
2702
|
+
pairCount = await client.readContract({ address: this.v2Factory, abi: solidlyFactoryAbi, functionName: "allPoolsLength" });
|
|
2703
|
+
useSolidly = true;
|
|
2704
|
+
} catch {
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const count = Number(pairCount);
|
|
2709
|
+
if (count === 0) return;
|
|
2710
|
+
const MAX_SCAN = 500;
|
|
2711
|
+
const startIdx = Math.max(0, count - MAX_SCAN);
|
|
2712
|
+
const fetchAbi = useSolidly ? solidlyFactoryAbi : v2FactoryAbi;
|
|
2713
|
+
const fetchFn = useSolidly ? "allPools" : "allPairs";
|
|
2714
|
+
const pairCalls = [];
|
|
2715
|
+
for (let i = startIdx; i < count; i++) pairCalls.push([this.v2Factory, encodeFunctionData10({ abi: fetchAbi, functionName: fetchFn, args: [BigInt(i)] })]);
|
|
2716
|
+
const pairResults = await multicallRead(this.rpcUrl, pairCalls);
|
|
2717
|
+
pairs = pairResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
|
|
2613
2718
|
}
|
|
2614
|
-
const pairAddressResults = await multicallRead(this.rpcUrl, pairAddressCalls);
|
|
2615
|
-
const pairs = pairAddressResults.map((r) => decodeAddress2(r)).filter((a) => a !== null && a !== zeroAddress6);
|
|
2616
2719
|
if (pairs.length === 0) return;
|
|
2617
|
-
const
|
|
2618
|
-
const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
|
|
2619
|
-
const gaugeCalls = pairs.map((pair) => [
|
|
2620
|
-
this.voter,
|
|
2621
|
-
encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [pair] })
|
|
2622
|
-
]);
|
|
2720
|
+
const gaugeCalls = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugeForPoolAbi, functionName: "gaugeForPool", args: [p] })]);
|
|
2623
2721
|
let gaugeResults = await multicallRead(this.rpcUrl, gaugeCalls);
|
|
2624
|
-
const
|
|
2625
|
-
if (
|
|
2626
|
-
const
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2722
|
+
const allNull = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
|
|
2723
|
+
if (allNull) {
|
|
2724
|
+
const fb1 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: poolToGaugeAbi, functionName: "poolToGauge", args: [p] })]);
|
|
2725
|
+
gaugeResults = await multicallRead(this.rpcUrl, fb1);
|
|
2726
|
+
}
|
|
2727
|
+
const allNull2 = gaugeResults.every((r) => !r || decodeAddress2(r) === zeroAddress6 || decodeAddress2(r) === null);
|
|
2728
|
+
if (allNull2) {
|
|
2729
|
+
const fb2 = pairs.map((p) => [this.voter, encodeFunctionData10({ abi: gaugesAbi, functionName: "gauges", args: [p] })]);
|
|
2730
|
+
gaugeResults = await multicallRead(this.rpcUrl, fb2);
|
|
2631
2731
|
}
|
|
2632
2732
|
const gaugedPairs = [];
|
|
2633
2733
|
for (let i = 0; i < pairs.length; i++) {
|
|
@@ -2672,6 +2772,8 @@ var init_dist2 = __esm({
|
|
|
2672
2772
|
gauge,
|
|
2673
2773
|
token0: t0 ? symbolMap.get(t0) ?? t0.slice(0, 10) : "?",
|
|
2674
2774
|
token1: t1 ? symbolMap.get(t1) ?? t1.slice(0, 10) : "?",
|
|
2775
|
+
token0Addr: t0 ?? void 0,
|
|
2776
|
+
token1Addr: t1 ?? void 0,
|
|
2675
2777
|
type: "V2",
|
|
2676
2778
|
stable
|
|
2677
2779
|
});
|
|
@@ -2692,8 +2794,7 @@ var init_dist2 = __esm({
|
|
|
2692
2794
|
const erc20SymbolAbi = parseAbi10(["function symbol() external view returns (string)"]);
|
|
2693
2795
|
const gaugeForPoolAbi = parseAbi10(["function gaugeForPool(address) external view returns (address)"]);
|
|
2694
2796
|
const poolToGaugeAbi = parseAbi10(["function poolToGauge(address) external view returns (address)"]);
|
|
2695
|
-
const
|
|
2696
|
-
const tokenAddresses = tokenEntries.map(([, addr]) => addr);
|
|
2797
|
+
const tokenAddresses = this.tokens ?? Object.values(HYPEREVM_TOKENS);
|
|
2697
2798
|
const pairs = [];
|
|
2698
2799
|
for (let i = 0; i < tokenAddresses.length; i++) {
|
|
2699
2800
|
for (let j = i + 1; j < tokenAddresses.length; j++) {
|
|
@@ -2798,6 +2899,8 @@ var init_dist2 = __esm({
|
|
|
2798
2899
|
gauge,
|
|
2799
2900
|
token0: symbolMap.get(t0) ?? t0.slice(0, 10),
|
|
2800
2901
|
token1: symbolMap.get(t1) ?? t1.slice(0, 10),
|
|
2902
|
+
token0Addr: t0,
|
|
2903
|
+
token1Addr: t1,
|
|
2801
2904
|
type: "CL",
|
|
2802
2905
|
tickSpacing
|
|
2803
2906
|
});
|
|
@@ -3032,6 +3135,21 @@ var init_dist2 = __esm({
|
|
|
3032
3135
|
args: [tokenId]
|
|
3033
3136
|
});
|
|
3034
3137
|
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Get pending rewards for an Aerodrome Slipstream CL gauge NFT position.
|
|
3140
|
+
* Uses the earned(address account, uint256 tokenId) overload, which is required
|
|
3141
|
+
* for CL gauges — the single-param earned(address) reverts on these contracts.
|
|
3142
|
+
*/
|
|
3143
|
+
async getPendingRewardsByCLTokenId(gauge, user, tokenId) {
|
|
3144
|
+
if (!this.rpcUrl) throw DefiError.rpcError("RPC URL required");
|
|
3145
|
+
const client = createPublicClient6({ transport: http6(this.rpcUrl) });
|
|
3146
|
+
return await client.readContract({
|
|
3147
|
+
address: gauge,
|
|
3148
|
+
abi: gaugeAbi,
|
|
3149
|
+
functionName: "earned",
|
|
3150
|
+
args: [user, tokenId]
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3035
3153
|
// IVoteEscrow
|
|
3036
3154
|
async buildCreateLock(amount, lockDuration) {
|
|
3037
3155
|
const data = encodeFunctionData10({
|
|
@@ -3312,7 +3430,8 @@ var init_dist2 = __esm({
|
|
|
3312
3430
|
"function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) external view returns ((address[] route, address[] pairs, uint256[] binSteps, uint256[] versions, uint128[] amounts, uint128[] virtualAmountsWithoutSlippage, uint128[] fees))"
|
|
3313
3431
|
]);
|
|
3314
3432
|
erc20Abi2 = parseAbi12([
|
|
3315
|
-
"function symbol() external view returns (string)"
|
|
3433
|
+
"function symbol() external view returns (string)",
|
|
3434
|
+
"function balanceOf(address account) external view returns (uint256)"
|
|
3316
3435
|
]);
|
|
3317
3436
|
_addressAbi = parseAbi12(["function f() external view returns (address)"]);
|
|
3318
3437
|
_uint256Abi = parseAbi12(["function f() external view returns (uint256)"]);
|
|
@@ -3729,6 +3848,74 @@ var init_dist2 = __esm({
|
|
|
3729
3848
|
} catch {
|
|
3730
3849
|
}
|
|
3731
3850
|
}
|
|
3851
|
+
const stableSymbols = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "MUSD", "AUSD", "USDY", "FDUSD", "USDe", "sUSDe"]);
|
|
3852
|
+
const mntSymbols = /* @__PURE__ */ new Set(["WMNT", "MNT"]);
|
|
3853
|
+
const moeSymbols = /* @__PURE__ */ new Set(["MOE"]);
|
|
3854
|
+
const sixDecimalStables = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "FDUSD"]);
|
|
3855
|
+
const tokenPriceMap = /* @__PURE__ */ new Map();
|
|
3856
|
+
const tokenDecimalsMap = /* @__PURE__ */ new Map();
|
|
3857
|
+
for (const [addr, sym] of symbolMap) {
|
|
3858
|
+
const key = addr.toLowerCase();
|
|
3859
|
+
if (stableSymbols.has(sym)) {
|
|
3860
|
+
tokenPriceMap.set(key, 1);
|
|
3861
|
+
tokenDecimalsMap.set(key, sixDecimalStables.has(sym) ? 6 : 18);
|
|
3862
|
+
} else if (mntSymbols.has(sym)) {
|
|
3863
|
+
tokenPriceMap.set(key, wmntPriceUsd);
|
|
3864
|
+
tokenDecimalsMap.set(key, 18);
|
|
3865
|
+
} else if (moeSymbols.has(sym)) {
|
|
3866
|
+
tokenPriceMap.set(key, moePriceUsd);
|
|
3867
|
+
tokenDecimalsMap.set(key, 18);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
const unknownTokenAddrs = [];
|
|
3871
|
+
for (let i = 0; i < rewardedPairs.length; i++) {
|
|
3872
|
+
for (const addr of [tokenXAddresses[i], tokenYAddresses[i]]) {
|
|
3873
|
+
if (addr && !tokenPriceMap.has(addr.toLowerCase())) {
|
|
3874
|
+
if (!unknownTokenAddrs.some((a) => a.toLowerCase() === addr.toLowerCase())) {
|
|
3875
|
+
unknownTokenAddrs.push(addr);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (unknownTokenAddrs.length > 0 && this.lbQuoter && this.wmnt && wmntPriceUsd > 0) {
|
|
3881
|
+
const erc20DecimalsAbi = parseAbi12(["function decimals() external view returns (uint8)"]);
|
|
3882
|
+
const decCalls = unknownTokenAddrs.map((addr) => [
|
|
3883
|
+
addr,
|
|
3884
|
+
encodeFunctionData12({ abi: erc20DecimalsAbi, functionName: "decimals" })
|
|
3885
|
+
]);
|
|
3886
|
+
const decResults = await multicallRead(rpcUrl, decCalls).catch(() => []);
|
|
3887
|
+
for (let i = 0; i < unknownTokenAddrs.length; i++) {
|
|
3888
|
+
const dec = decResults[i] ? Number(decodeUint256Result(decResults[i]) ?? 18n) : 18;
|
|
3889
|
+
tokenDecimalsMap.set(unknownTokenAddrs[i].toLowerCase(), dec);
|
|
3890
|
+
}
|
|
3891
|
+
const quotePromises = unknownTokenAddrs.map(async (tokenAddr) => {
|
|
3892
|
+
try {
|
|
3893
|
+
const dec = tokenDecimalsMap.get(tokenAddr.toLowerCase()) ?? 18;
|
|
3894
|
+
const quoteUnit = 10n ** BigInt(Math.max(dec - 2, 0));
|
|
3895
|
+
const quote = await client.readContract({
|
|
3896
|
+
address: this.lbQuoter,
|
|
3897
|
+
abi: lbQuoterAbi2,
|
|
3898
|
+
functionName: "findBestPathFromAmountIn",
|
|
3899
|
+
args: [[tokenAddr, this.wmnt], quoteUnit]
|
|
3900
|
+
});
|
|
3901
|
+
const amountOut = quote.amounts?.at(-1) ?? 0n;
|
|
3902
|
+
const priceInWmnt = Number(amountOut) / 1e18 * (10 ** dec / Number(quoteUnit));
|
|
3903
|
+
return { addr: tokenAddr, price: priceInWmnt * wmntPriceUsd };
|
|
3904
|
+
} catch {
|
|
3905
|
+
return { addr: tokenAddr, price: 0 };
|
|
3906
|
+
}
|
|
3907
|
+
});
|
|
3908
|
+
const priceResults = await Promise.all(quotePromises);
|
|
3909
|
+
for (const { addr, price } of priceResults) {
|
|
3910
|
+
if (price > 0) tokenPriceMap.set(addr.toLowerCase(), price);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
const getTokenPriceUsd = (_sym, addr) => {
|
|
3914
|
+
return tokenPriceMap.get(addr.toLowerCase()) ?? 0;
|
|
3915
|
+
};
|
|
3916
|
+
const getTokenDecimals = (_sym, addr) => {
|
|
3917
|
+
return tokenDecimalsMap.get(addr.toLowerCase()) ?? 18;
|
|
3918
|
+
};
|
|
3732
3919
|
const binRequests = [];
|
|
3733
3920
|
for (let i = 0; i < rewardedPairs.length; i++) {
|
|
3734
3921
|
const range = poolData[i].range;
|
|
@@ -3759,19 +3946,23 @@ var init_dist2 = __esm({
|
|
|
3759
3946
|
binReservesY.get(poolIdx).set(binId, decoded[1]);
|
|
3760
3947
|
}
|
|
3761
3948
|
}
|
|
3762
|
-
const
|
|
3763
|
-
const
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3949
|
+
const poolBalanceX = /* @__PURE__ */ new Map();
|
|
3950
|
+
const poolBalanceY = /* @__PURE__ */ new Map();
|
|
3951
|
+
{
|
|
3952
|
+
const balCalls = [];
|
|
3953
|
+
for (let i = 0; i < rewardedPairs.length; i++) {
|
|
3954
|
+
const tx = tokenXAddresses[i];
|
|
3955
|
+
const ty = tokenYAddresses[i];
|
|
3956
|
+
const pool = rewardedPairs[i].pool;
|
|
3957
|
+
balCalls.push([tx ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
|
|
3958
|
+
balCalls.push([ty ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
|
|
3959
|
+
}
|
|
3960
|
+
const balResults = await multicallRead(rpcUrl, balCalls).catch(() => []);
|
|
3961
|
+
for (let i = 0; i < rewardedPairs.length; i++) {
|
|
3962
|
+
poolBalanceX.set(i, decodeUint256Result(balResults[i * 2] ?? null) ?? 0n);
|
|
3963
|
+
poolBalanceY.set(i, decodeUint256Result(balResults[i * 2 + 1] ?? null) ?? 0n);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3775
3966
|
const results = [];
|
|
3776
3967
|
for (let i = 0; i < rewardedPairs.length; i++) {
|
|
3777
3968
|
const { pool, rewarder } = rewardedPairs[i];
|
|
@@ -3796,18 +3987,25 @@ var init_dist2 = __esm({
|
|
|
3796
3987
|
const maxBin = Number(range[1]);
|
|
3797
3988
|
rewardedBins = maxBin - minBin + 1;
|
|
3798
3989
|
if (rxMap && ryMap) {
|
|
3799
|
-
const
|
|
3800
|
-
const
|
|
3801
|
-
const
|
|
3802
|
-
const
|
|
3990
|
+
const priceX2 = getTokenPriceUsd(symX, tokenX);
|
|
3991
|
+
const priceY2 = getTokenPriceUsd(symY, tokenY);
|
|
3992
|
+
const decX2 = getTokenDecimals(symX, tokenX);
|
|
3993
|
+
const decY2 = getTokenDecimals(symY, tokenY);
|
|
3803
3994
|
for (let b = minBin; b <= maxBin; b++) {
|
|
3804
3995
|
const rx = rxMap.get(b) ?? 0n;
|
|
3805
3996
|
const ry = ryMap.get(b) ?? 0n;
|
|
3806
|
-
rangeTvlUsd += Number(rx) / 10 **
|
|
3807
|
-
rangeTvlUsd += Number(ry) / 10 **
|
|
3997
|
+
rangeTvlUsd += Number(rx) / 10 ** decX2 * priceX2;
|
|
3998
|
+
rangeTvlUsd += Number(ry) / 10 ** decY2 * priceY2;
|
|
3808
3999
|
}
|
|
3809
4000
|
}
|
|
3810
4001
|
}
|
|
4002
|
+
const priceX = getTokenPriceUsd(symX, tokenX);
|
|
4003
|
+
const priceY = getTokenPriceUsd(symY, tokenY);
|
|
4004
|
+
const decX = getTokenDecimals(symX, tokenX);
|
|
4005
|
+
const decY = getTokenDecimals(symY, tokenY);
|
|
4006
|
+
const fullBalX = poolBalanceX.get(i) ?? 0n;
|
|
4007
|
+
const fullBalY = poolBalanceY.get(i) ?? 0n;
|
|
4008
|
+
const poolTvlUsd = Number(fullBalX) / 10 ** decX * priceX + Number(fullBalY) / 10 ** decY * priceY;
|
|
3811
4009
|
const aprPercent = rangeTvlUsd > 0 && moePriceUsd > 0 ? poolMoePerDay * moePriceUsd * 365 / rangeTvlUsd * 100 : 0;
|
|
3812
4010
|
results.push({
|
|
3813
4011
|
pool,
|
|
@@ -3824,8 +4022,11 @@ var init_dist2 = __esm({
|
|
|
3824
4022
|
isTopPool,
|
|
3825
4023
|
moePerDay: poolMoePerDay,
|
|
3826
4024
|
rangeTvlUsd,
|
|
4025
|
+
poolTvlUsd,
|
|
3827
4026
|
aprPercent,
|
|
3828
|
-
rewardedBins
|
|
4027
|
+
rewardedBins,
|
|
4028
|
+
totalMoePerDay: moePerDay,
|
|
4029
|
+
moePriceUsd
|
|
3829
4030
|
});
|
|
3830
4031
|
}
|
|
3831
4032
|
return results;
|
|
@@ -5241,7 +5442,7 @@ var init_dist2 = __esm({
|
|
|
5241
5442
|
async getRates(asset) {
|
|
5242
5443
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5243
5444
|
if (!this.defaultVault) {
|
|
5244
|
-
|
|
5445
|
+
return { protocol: this.protocolName, asset, supply_apy: 0, borrow_variable_apy: 0, borrow_stable_apy: 0, utilization: 0, total_supply: 0n, total_borrow: 0n };
|
|
5245
5446
|
}
|
|
5246
5447
|
const [queueLenRaw] = await multicallRead(this.rpcUrl, [
|
|
5247
5448
|
[this.defaultVault, encodeFunctionData19({ abi: META_MORPHO_ABI, functionName: "supplyQueueLength" })]
|
|
@@ -7386,6 +7587,52 @@ server.tool(
|
|
|
7386
7587
|
}
|
|
7387
7588
|
}
|
|
7388
7589
|
);
|
|
7590
|
+
server.tool(
|
|
7591
|
+
"defi_yield_scan",
|
|
7592
|
+
"Scan all configured chains for the best lending yield opportunities for a given asset. Compares supply APY across Aave V3, Compound, Venus, Morpho, etc. Returns ranked results with chain, protocol, supply_apy, borrow_apy, utilization.",
|
|
7593
|
+
{
|
|
7594
|
+
asset: z.string().optional().describe("Token symbol to scan (default: USDC)")
|
|
7595
|
+
},
|
|
7596
|
+
async ({ asset }) => {
|
|
7597
|
+
const registry = Registry.loadEmbedded();
|
|
7598
|
+
const symbol = asset ?? "USDC";
|
|
7599
|
+
const results = [];
|
|
7600
|
+
const chains = Array.from(registry.chains.keys());
|
|
7601
|
+
await Promise.allSettled(
|
|
7602
|
+
chains.map(async (chainName) => {
|
|
7603
|
+
const chain = registry.getChain(chainName);
|
|
7604
|
+
const rpcUrl = chain.effectiveRpcUrl();
|
|
7605
|
+
const lendingProtos = registry.getProtocolsForChain(chainName).filter((p) => p.category === "lending");
|
|
7606
|
+
for (const proto of lendingProtos) {
|
|
7607
|
+
try {
|
|
7608
|
+
const adapter = createLending(proto, rpcUrl);
|
|
7609
|
+
const tokens = registry.tokens.get(chainName);
|
|
7610
|
+
const token = tokens?.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());
|
|
7611
|
+
if (!token) continue;
|
|
7612
|
+
const rates = await adapter.getRates(token.address);
|
|
7613
|
+
results.push({
|
|
7614
|
+
chain: chainName,
|
|
7615
|
+
protocol: proto.name,
|
|
7616
|
+
slug: proto.slug,
|
|
7617
|
+
asset: token.symbol,
|
|
7618
|
+
asset_address: token.address,
|
|
7619
|
+
supply_apy: rates.supply_apy,
|
|
7620
|
+
borrow_variable_apy: rates.borrow_variable_apy,
|
|
7621
|
+
utilization: rates.utilization,
|
|
7622
|
+
total_supply: rates.total_supply?.toString(),
|
|
7623
|
+
total_borrow: rates.total_borrow?.toString()
|
|
7624
|
+
});
|
|
7625
|
+
} catch {
|
|
7626
|
+
}
|
|
7627
|
+
}
|
|
7628
|
+
})
|
|
7629
|
+
);
|
|
7630
|
+
results.sort((a, b) => b.supply_apy - a.supply_apy);
|
|
7631
|
+
return {
|
|
7632
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7635
|
+
);
|
|
7389
7636
|
var transport = new StdioServerTransport();
|
|
7390
7637
|
await server.connect(transport);
|
|
7391
7638
|
//# sourceMappingURL=mcp-server.js.map
|