Addresses
austintgriffith/ethskills
Verified contract addresses for major Ethereum protocols across mainnet and L2s.
A skill your agent uses when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity…
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install ccashwell/evm-cortex lp-analyst --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/lp-analyst .claude/skills/lp-analyst && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .claude/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analystType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install ccashwell/evm-cortex lp-analyst --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/lp-analyst .agents/skills/lp-analyst && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .agents/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install ccashwell/evm-cortex lp-analyst --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/lp-analyst .cursor/skills/lp-analyst && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .cursor/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/ccashwell/evm-cortex.git --path skills/lp-analyst--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install ccashwell/evm-cortex lp-analyst --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/lp-analyst .gemini/skills/lp-analyst && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .gemini/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install ccashwell/evm-cortex lp-analystInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/lp-analyst .github/skills/lp-analyst && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .github/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ccashwell/evm-cortex --skill lp-analyst -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install ccashwell/evm-cortex lp-analyst --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/lp-analyst .opencode/skills/lp-analyst && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "lp-analyst" agent skill from https://github.com/ccashwell/evm-cortex/tree/main/skills/lp-analyst into .opencode/skills/lp-analyst/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lp-analyst", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
lp-analystA skill your agent uses when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity…
Lp Analyst is an agent skill from ccashwell/evm-cortex. Use when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity management. Covers both Uniswap V3 NonfungiblePositionManager and V4 PositionManager positions.
Its SKILL.md is about 5.7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Business, Finance & HR. It works with Uniswap and Circle USDC. The repository describes itself as: Ethereum protocol engineering squad for AI coding assistants. The licence is MIT.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit f8f3301. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are solidity and graphql).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Lp Analyst loads about 5.7k tokens when it runs. Until then it costs about 69 tokens; SKILL.md has 1,456 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from ccashwell/evm-cortex at commit f8f3301, republished under its MIT licence (© ccashwell). 1,456 words, ~5,743 tokens.
.claude/skills/lp-analyst/SKILL.md (or your agent's skills folder).For a 50/50 constant-product pool where r = current_price / entry_price:
IL(r) = 2√r / (1 + r) - 1| Price Change | r | IL |
|---|---|---|
| -50% | 0.50 | -5.72% |
| -25% | 0.75 | -1.03% |
| -10% | 0.90 | -0.14% |
| 0% | 1.00 | 0.00% |
| +10% | 1.10 | -0.14% |
| +25% | 1.25 | -0.62% |
| +50% | 1.50 | -2.02% |
| +100% | 2.00 | -5.72% |
| +300% | 4.00 | -20.00% |
IL is always non-positive. The loss is symmetric on a log scale: a 2x and a 0.5x move produce the same IL.
For a position with tick range [tickLower, tickUpper] mapping to price range [Pa, Pb] where Pa < Pb, with liquidity L and current price P:
Position token amounts (the core V3 math):
If Pa ≤ P ≤ Pb (in range):
amount0 = L × (1/√P - 1/√Pb)
amount1 = L × (√P - √Pa)
If P < Pa (below range — 100% token0):
amount0 = L × (1/√Pa - 1/√Pb)
amount1 = 0
If P > Pb (above range — 100% token1):
amount0 = 0
amount1 = L × (√Pb - √Pa)Position value in token1 terms:
value(P) = amount0 × P + amount1Expanding for the in-range case:
value(P) = L × (P/√P - P/√Pb + √P - √Pa)
= L × (√P - √Pa + √P - P/√Pb)
= L × (2√P - √Pa - P/√Pb)HODL value (holding the initial token amounts without providing liquidity):
At entry price P₀ (in range), the initial amounts are:
a0 = L × (1/√P₀ - 1/√Pb)
a1 = L × (√P₀ - √Pa)HODL value at current price P:
value_HODL(P) = a0 × P + a1
= L × (P/√P₀ - P/√Pb + √P₀ - √Pa)Impermanent loss:
IL = value_LP(P) / value_HODL(P) - 1Concentrated positions amplify IL relative to V2. The amplification factor equals the capital efficiency multiplier.
Setup:
Compute initial amounts (P₀ = 3000, in range):
√3000 ≈ 54.772
√2500 = 50.000
√3500 ≈ 59.161
Liquidity L from 1 ETH + equivalent USDC:
From token0: L = amount0 / (1/√P - 1/√Pb)
From token1: L = amount1 / (√P - √Pa)
Using the ratio to find L for a balanced deposit of value V at price P₀:
V = L × (2√P₀ - √Pa - P₀/√Pb)
V = L × (2 × 54.772 - 50.000 - 3000/59.161)
V = L × (109.544 - 50.000 - 50.709)
V = L × 8.835
For V = 6000 USDC (1 ETH at $3000 + 3000 USDC):
L = 6000 / 8.835 ≈ 679.0Now ETH moves to $3500 (upper bound):
P = 3500 = Pb → position is 100% USDC (token1)
amount0 = 0
amount1 = L × (√Pb - √Pa) = 679.0 × (59.161 - 50.000) = 679.0 × 9.161 ≈ 6220
value_LP = 6220 USDCHODL value at $3500:
Initial amounts at P₀ = 3000:
a0 = 679.0 × (1/54.772 - 1/59.161) = 679.0 × (0.01826 - 0.01690) = 679.0 × 0.001359 ≈ 0.923 ETH
a1 = 679.0 × (54.772 - 50.000) = 679.0 × 4.772 ≈ 3240 USDC
value_HODL = 0.923 × 3500 + 3240 = 3230 + 3240 = 6470 USDCImpermanent loss:
IL = 6220 / 6470 - 1 ≈ -3.86%Compare with V2 full-range IL at the same price move (r = 3500/3000 ≈ 1.167):
IL_v2 = 2√1.167 / (1 + 1.167) - 1 = 2 × 1.0801 / 2.167 - 1 ≈ -0.28%The concentrated position suffers ~13-14x more IL on this move — more than the ~6.5x capital-efficiency multiplier for the 2500-3500 range. IL amplification is not a constant factor: it grows as price approaches a range edge, so the multiplier understates downside near the bounds.
fee_revenue = volume_in_range × fee_tier × (1 - protocol_share) × (position_liquidity / total_liquidity_in_range)protocol_share is no longer zero. Since the UNIfication proposal executed on 2025-12-28, Ethereum mainnet V3 pools set slot0().feeProtocol to 68 on the 0.01%/0.05% tiers (protocol takes 1/4 of LP fees) and 102 on the 0.30% tier (1/6) — decode as token0 = feeProtocol % 16, token1 = feeProtocol >> 4, share = 1/N. V4 static-fee pools have charged a protocol fee since governance activated it on 2026-07-27 (mainnet ETH/USDC 500/10: protocolFee = 512125, i.e. 125 pips per direction); it is taken from the swap input ahead of the LP fee, so for V4 protocol_share ≈ protocolFee / 1e6 per direction.
Annualized:
annual_fees = daily_fee_revenue × 365
fee_APR = annual_fees / position_valueThe net return of an LP position is:
net_return = fee_APR + ILA position is profitable when fee revenue exceeds IL.
| Tier | Fee | Tick Spacing | Typical Pairs |
|---|---|---|---|
| 0.01% | 100 | 1 | Stablecoin/stablecoin (USDC/USDT) |
| 0.05% | 500 | 10 | Correlated assets (wstETH/ETH) |
| 0.30% | 3000 | 60 | Standard pairs (ETH/USDC) |
| 1.00% | 10000 | 200 | Exotic / long-tail pairs |
Concentrated positions only earn fees while the current price is within range. The active time ratio α represents the fraction of time the position is in range:
effective_fee_APR = fee_APR × αFor a ±10% range on ETH/USDC, historical α is typically 60-80% over a month. Narrower ranges have lower α.
Uniswap V3 tracks cumulative fees per unit of liquidity using Q128.128 fixed-point accumulators:
feeGrowthGlobal0X128 — cumulative token0 fees per unit liquidity (pool-wide)
feeGrowthGlobal1X128 — cumulative token1 fees per unit liquidity (pool-wide)Per-position uncollected fees:
uncollected0 = (feeGrowthInside0CurrentX128 - feeGrowthInside0LastX128) × liquidity / 2^128
uncollected1 = (feeGrowthInside1CurrentX128 - feeGrowthInside1LastX128) × liquidity / 2^128Where feeGrowthInsideX128 is computed from the tick-level feeGrowthOutside values:
// Pseudocode for feeGrowthInside
if currentTick >= tickUpper:
feeGrowthInside = feeGrowthOutside[tickUpper] - feeGrowthOutside[tickLower]
elif currentTick < tickLower:
feeGrowthInside = feeGrowthOutside[tickLower] - feeGrowthOutside[tickUpper]
else:
feeGrowthInside = feeGrowthGlobal - feeGrowthOutside[tickLower] - feeGrowthOutside[tickUpper]For a range [Pa, Pb], capital efficiency relative to full range is:
efficiency = 1 / (1 - √(Pa / Pb))| Range | Pa/Pb | Efficiency |
|---|---|---|
| ±0.1% (stables) | 0.998 | ~1000x |
| ±1% | 0.980 | ~100x |
| ±5% | 0.905 | ~20.5x |
| ±10% | 0.818 | ~10.5x |
| ±25% | 0.600 | ~4.4x |
| ±50% | 0.333 | ~2.4x |
| Full range | 0→∞ | 1x |
Higher efficiency means more fees earned per dollar of capital, but also more IL per dollar and more frequent out-of-range events.
Stablecoin pairs (USDC/USDT):
Correlated pairs (wstETH/ETH):
Major pairs (ETH/USDC):
Volatile pairs (memecoins, new tokens):
Prices map to ticks via:
tick = floor(log(price) / log(1.0001))
price = 1.0001^tickTick spacing constrains which ticks can be used. A position's range must align to the pool's tick spacing:
tickLower = floor(desired_tick / tickSpacing) × tickSpacing
tickUpper = ceil(desired_tick / tickSpacing) × tickSpacing// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {INonfungiblePositionManager} from "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
interface IPositionReader {
function analyzePosition(uint256 tokenId) external view returns (
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity
);
}
contract V3PositionReader {
INonfungiblePositionManager public immutable NPM;
constructor(address npm_) {
NPM = INonfungiblePositionManager(npm_);
}
function getPosition(uint256 tokenId) external view returns (
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
) {
(
, // nonce
, // operator
token0,
token1,
fee,
tickLower,
tickUpper,
liquidity,
feeGrowthInside0LastX128,
feeGrowthInside1LastX128,
tokensOwed0,
tokensOwed1
) = NPM.positions(tokenId);
}
}import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PositionInfo} from "v4-periphery/src/libraries/PositionInfoLibrary.sol";
contract V4PositionReader {
IPositionManager public immutable PM;
constructor(address pm_) {
PM = IPositionManager(pm_);
}
/// @notice Reads a V4 LP position's pool key, tick range, and liquidity
function getPosition(uint256 tokenId) external view returns (
PoolKey memory poolKey,
int24 tickLower,
int24 tickUpper,
uint128 liquidity
) {
PositionInfo info;
(poolKey, info) = PM.getPoolAndPositionInfo(tokenId);
tickLower = info.tickLower();
tickUpper = info.tickUpper();
liquidity = PM.getPositionLiquidity(tokenId);
}
}import {TickMath} from "v4-core/src/libraries/TickMath.sol";
/// @notice Convert a tick to a human-readable price
/// @dev price = 1.0001^tick, adjusted for token decimals
function tickToPrice(int24 tick, uint8 decimals0, uint8 decimals1) pure returns (uint256) {
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(tick);
// price = (sqrtPriceX96 / 2^96)^2 × 10^(decimals0 - decimals1)
uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
return priceX192 * (10 ** decimals0) / (10 ** decimals1) >> 192;
}import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
/// @notice Calculate the token amounts for a position
function getAmounts(
int24 tickCurrent,
int24 tickLower,
int24 tickUpper,
uint128 liquidity
) pure returns (uint256 amount0, uint256 amount1) {
uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tickCurrent);
uint160 sqrtPriceLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtPriceUpper = TickMath.getSqrtPriceAtTick(tickUpper);
(amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity(
sqrtPriceCurrent,
sqrtPriceLower,
sqrtPriceUpper,
liquidity
);
}value_usd = amount0 × price0_usd + amount1 × price1_usdFor ETH/USDC where token0 = USDC, token1 = WETH:
value_usd = amount0 × 1.0 + amount1 × eth_price_usdAlways check token ordering — V3/V4 enforce token0 < token1 by address sort.
The NonfungiblePositionManager accumulates fees internally. To collect, first poke the position to update fee accounting, then call collect:
/// @notice Collect all accrued fees from a V3 position
/// @dev A zero-liquidity decrease pokes the position to update fee snapshots
function collectFees(
INonfungiblePositionManager npm,
uint256 tokenId
) external returns (uint256 collected0, uint256 collected1) {
npm.decreaseLiquidity(INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: 0,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
}));
(collected0, collected1) = npm.collect(INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: msg.sender,
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
}));
}V4 has no collect. Encode [Actions.DECREASE_LIQUIDITY (liquidity = 0), Actions.TAKE_PAIR] in modifyLiquidities; the zero-liquidity decrease pokes the position and its feesAccrued are taken via TAKE_PAIR (or CLOSE_CURRENCY). Fees settle through the PoolManager's transient accounting.
Rebalance at fixed intervals (e.g., every 24 hours, every 7 days). Simple to implement with Chainlink Automation or Gelato tasks.
Pros: predictable gas spend, simple logic Cons: rebalances even when unnecessary, misses urgent rebalances when price moves fast
Rebalance when price exits the current range or approaches a boundary within a configurable buffer:
trigger_lower = Pa + buffer
trigger_upper = Pb - bufferWhen P < trigger_lower or P > trigger_upper, close the position and re-open centered at the current price.
Pros: responsive to market conditions, avoids unnecessary rebalances Cons: can trigger excessively during high volatility
Monitor unrealized IL and rebalance when it exceeds a target percentage:
if |IL| > threshold:
rebalance()Typical thresholds: 1-3% for stablecoin pairs, 5-10% for major pairs.
Center the position at the geometric mean of recent prices to minimize expected IL:
P_center = exp(mean(ln(P_1), ln(P_2), ..., ln(P_n)))
Pa = P_center / k
Pb = P_center × kWhere k is the range multiplier (e.g., k = 1.1 for a ±10% range).
Set the range dynamically based on historical volatility:
μ = SMA(price, window)
σ = StdDev(price, window)
Pa = μ - k × σ
Pb = μ + k × σWith k = 2 (95% confidence), the position captures most price action. Wider k means less rebalancing but lower capital efficiency.
A rebalance is only worth executing if the expected gain exceeds costs:
expected_benefit = additional_fee_revenue + avoided_IL
cost = gas_cost + swap_slippage + swap_fees + position_entry_spread
rebalance if: expected_benefit > costOn L2s (Arbitrum, Base, Optimism) gas costs are negligible, making more frequent rebalances viable. On mainnet with gas at <1 gwei (2026), rebalancing is also cheaper than historically but still requires slippage/fee accounting.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {AutomationCompatibleInterface} from
"@chainlink/contracts/src/v0.8/automation/interfaces/AutomationCompatibleInterface.sol";
/// @title LP position rebalancer using Chainlink Automation
/// @notice Monitors a V3 position and rebalances when price exits range
abstract contract LPKeeper is AutomationCompatibleInterface {
uint256 public positionTokenId;
uint256 public bufferBps;
function checkUpkeep(bytes calldata)
external
view
override
returns (bool upkeepNeeded, bytes memory performData)
{
(int24 tickLower, int24 tickUpper, int24 tickCurrent) = _getPositionTicks();
int24 rangeTicks = tickUpper - tickLower;
int24 buffer = int24(int256(rangeTicks) * int256(uint256(bufferBps)) / 10_000);
upkeepNeeded = tickCurrent <= tickLower + buffer || tickCurrent >= tickUpper - buffer;
performData = abi.encode(tickCurrent);
}
function performUpkeep(bytes calldata performData) external override {
int24 tickCurrent = abi.decode(performData, (int24));
_rebalanceAroundTick(tickCurrent);
}
function _getPositionTicks() internal view virtual returns (int24, int24, int24);
function _rebalanceAroundTick(int24 tick) internal virtual;
}A V4 hook can auto-compound fees into the position on every swap:
// SwapParams lives in v4-core/src/types/PoolOperation.sol. BaseHook (OpenZeppelin uniswap-hooks or
// Uniswap/v4-hooks-public) keeps the external callbacks non-virtual; override the internal _afterSwap.
function _afterSwap(
address,
PoolKey calldata key,
SwapParams calldata,
BalanceDelta delta,
bytes calldata
) internal override returns (bytes4, int128) {
// Collect fees generated by the swap and re-add as liquidity
// This is possible because V4 hooks execute within the PoolManager's
// unlock context, allowing atomic fee collection + liquidity addition
return (BaseHook.afterSwap.selector, 0);
}JIT liquidity adds a large concentrated position immediately before a swap and removes it after, capturing the swap fee on a narrow range. In V4, this can be implemented as a hook:
beforeSwap: mint concentrated liquidity at the current tick ± 1 tick spacingafterSwap: burn the position, collect feesJIT liquidity is MEV-adjacent — it competes with other LPs for fee revenue without taking price risk.
Wrap Uniswap LP positions in an ERC-4626 vault for composability:
/// @title Uniswap V3 LP Vault
/// @notice ERC-4626 vault that manages a concentrated liquidity position
/// @dev Depositors receive vault shares proportional to their contribution
abstract contract LPVault {
// vault share accounting
// deposit: add liquidity to position, mint shares
// withdraw: remove liquidity proportionally, burn shares
// compound: collect fees, re-add as liquidity
// rebalance: close position, re-open at new range
}Key considerations:
{
position(id: "tokenId") {
id
owner
liquidity
depositedToken0
depositedToken1
withdrawnToken0
withdrawnToken1
collectedFeesToken0
collectedFeesToken1
pool {
token0 { symbol decimals }
token1 { symbol decimals }
feeTier
sqrtPrice
tick
}
tickLower { tickIdx }
tickUpper { tickIdx }
}
}{
poolDayDatas(
where: { pool: "poolAddress" }
orderBy: date
orderDirection: desc
first: 30
) {
date
volumeUSD
tvlUSD
feesUSD
tick
}
}daily_fees_usd = sum(feesUSD over 24h for ticks in [tickLower, tickUpper])
position_share = position_liquidity / total_liquidity_in_tick_range
my_daily_fees = daily_fees_usd × (1 - protocol_share) × position_share # subgraph feesUSD is gross of the protocol fee
fee_APR = (my_daily_fees / position_value_usd) × 365Higher V/L ratio means more fee revenue per unit of liquidity:
VL_ratio = volume_24h / TVL_in_range
expected_daily_yield = VL_ratio × fee_tier × (1 - protocol_share)Measure how IL changes per 1% price move:
IL_sensitivity = dIL/dr at r = 1
For V2: dIL/dr = (1 - √r) / (1 + r)^2
At r = 1: dIL/dr = 0 (IL is locally flat at entry)
Second derivative: d²IL/dr² = -(3 + r) / (4√r × (1 + r)^3)
At r = 1: d²IL/dr² = -1/4
IL ≈ -(Δr)² / 8 for small movesFor concentrated positions, multiply by the capital efficiency factor.
The minimum fee APR needed to offset IL over a given period:
breakeven_APR = -IL / holding_period_in_yearsFor the ETH/USDC example above (IL = -3.86% when ETH goes from $3000 to $3500):
If the move happened over 30 days:
breakeven_APR = 0.0386 / (30/365) ≈ 46.97%For a concentrated position [Pa, Pb] entered at price P₀:
If price crashes to Pa:
max_drawdown_below = value(Pa) / value(P₀) - 1
If price spikes to Pb:
max_drawdown_above = value(Pb) / value(P₀) - 1Out-of-range positions experience the worst-case: the position becomes 100% of the depreciating token (below range) or 100% of the appreciating token you no longer hold (above range).
For a token with annualized volatility σ and a holding period t (in years):
Expected IL (V2) ≈ -σ²t / 8Derivation: price follows geometric Brownian motion, ln(r) is normally distributed with variance σ²t, and the second-order Taylor expansion of IL gives the -σ²/8 coefficient.
For concentrated positions with efficiency multiplier E:
Expected IL (V3) ≈ -E × σ²t / 8Longer positions in range accumulate more fees. The crossover point where fees exceed IL depends on:
t_breakeven = |IL| / fee_rate_per_unit_timeEmpirically for ETH/USDC 0.30% tier with ±10% range:
30 days: fees typically dominate if position stays in range
token0 < token1 by address) before interpreting amountsSafeERC20 for all token transfers in LP management contractsamount0Min, amount1Min) on all liquidity operationsfeeGrowthInside0LastX128) for accurate fee accountingPoolManager.unlock() context© ccashwell, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/lp-analyst of ccashwell/evm-cortex.
Open the folder on GitHubat commit f8f3301
Lp Analyst next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Lp Analyst this skillccashwell/evm-cortex | 131 | — | ~5.7k | Automated safety check: Pass | MIT | |
| Addressesaustintgriffith/ethskills | 294 | — | ~7.1k | Automated safety check: Pass | None | |
| Aomi Transactjeremylongshore/tons-of-skills-marketplace | 2.8k | — | ~2.3k | Automated safety check: Pass | MIT | |
| Setup DcaLeoYeAI/openclaw-master-skills | 2.2k | — | ~4.3k | Automated safety check: Pass | MIT | |
| Aero Stock LpBankrBot/skills | 1.2k | — | ~5.8k | Automated safety check: Pass | None | |
| Okx Dapp Discoveryinternet-court/internet-court-skill | 6.4k | 2 repos | ~6.9k | Automated safety check: Pass | MIT |
austintgriffith/ethskills
Verified contract addresses for major Ethereum protocols across mainnet and L2s.
jeremylongshore/tons-of-skills-marketplace
Build natural-language crypto agents, web3 assistants, and trading bots that read and write EVM chain state.
LeoYeAI/openclaw-master-skills
Set up a non-custodial dollar-cost averaging strategy on Uniswap.
BankrBot/skills
LP tokenized stocks onchain — range-LP Coinbase tokenized equities (NVDA, AAPL, GOOGL, META) and AERO/USDC on Aerodrome Slipstream (Base) for trading-fee + AERO emission yield.
internet-court/internet-court-skill
Plugin router for 20 third-party DeFi protocols (Polymarket, Aave, Hyperliquid, PancakeSwap, Morpho, Raydium, Curve, Compound, Pendle, Lido, ether.fi, GMX, Kamino, Orca, Meteora, Clanker, pump.fun…
alsk1992/CloddsBot
Trade tokens on EVM chains - Uniswap V3, 1inch on Ethereum, Arbitrum, Optimism, Base, Polygon
ccashwell/evm-cortex
A skill your agent uses when preparing for a security audit, performing reconnaissance on a new codebase, or creating a protocol overview.
ccashwell/evm-cortex
A skill your agent uses when integrating with Aave V3 for lending, borrowing, flash loans, or building on top of Aave markets.
ccashwell/evm-cortex
Access control design patterns for Solidity protocols. An agent skill from ccashwell/evm-cortex.
ccashwell/evm-cortex
A skill your agent uses when running a local Ethereum node with Anvil.
ccashwell/evm-cortex
A skill your agent uses when performing systematic breadth-first review of all contracts during a security audit.
ccashwell/evm-cortex
A skill your agent uses when performing deep analysis of specific findings or high-risk areas during a security audit.
Works with
Categories
A skill your agent uses when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity…. Lp Analyst is an agent skill from ccashwell/evm-cortex. Use when analyzing LP positions, calculating impermanent loss, optimizing position ranges, estimating fee revenue, rebalancing strategies, or building automated liquidity management.
Lp Analyst fits situations like: analyzing LP positions; calculating impermanent loss; optimizing position ranges; estimating fee revenue.
Run `npx skills add ccashwell/evm-cortex --skill lp-analyst -a claude-code`. Or copy the skill folder (skills/lp-analyst in ccashwell/evm-cortex) into .claude/skills/lp-analyst in your project. Claude Code loads it when a task matches its description.
Run `npx skills add ccashwell/evm-cortex --skill lp-analyst -a codex`. Or copy the skill folder (skills/lp-analyst in ccashwell/evm-cortex) into .agents/skills/lp-analyst in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ccashwell/evm-cortex --skill lp-analyst -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/lp-analyst, .gemini/skills/lp-analyst, .github/skills/lp-analyst and .opencode/skills/lp-analyst in your project.
SKILL.md names no scripts, command-line tools or credentials: Lp Analyst is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Lp Analyst is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.7k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Lp Analyst: Addresses (austintgriffith/ethskills, 294 stars), Aomi Transact (jeremylongshore/tons-of-skills-marketplace, 2.8k stars), Setup Dca (LeoYeAI/openclaw-master-skills, 2.2k stars) and Aero Stock Lp (BankrBot/skills, 1.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
ccashwell (a GitHub user) maintains it in ccashwell/evm-cortex, which has 131 GitHub stars. The repository holds 89 skills in this directory. The repository was last updated on September 30, 2026.
Source: ccashwell/evm-cortex on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.