Comparison
Comparison primitives answer “which is better for my position?” across choices that are otherwise apples-to-apples-ish.
Two primitives:
CompareFeeTiers— V3-only: compare candidate fee tiers for the same token pairCompareProtocols— cross-protocol: IL and slippage for the same trade across two AMM types
All primitives in the Agentic Primitives section follow the same contract: stateless construction, computation at .apply(), typed dataclass return.
from defipy.twin import MockProvider, StateTwinBuilderfrom defipy.utils.data import FeeTierCandidate
provider = MockProvider()builder = StateTwinBuilder()lp_v2 = builder.build(provider.snapshot("eth_dai_v2"))lp_v3 = builder.build(provider.snapshot("eth_dai_v3"))lp_bal = builder.build(provider.snapshot("eth_dai_balancer_50_50"))v2_tokens = lp_v2.factory.token_from_exchange[lp_v2.name]CompareFeeTiers
Section titled “CompareFeeTiers”Purpose. Compare V3 fee tiers for the same token pair. Mismatched token pairs raise ValueError; non-V3 pools raise ValueError.
Signature.
CompareFeeTiers().apply(candidates) -> FeeTierComparisoncandidates is a list of FeeTierCandidate(lp, position_size_lp, lwr_tick, upr_tick, name=None). observed_fee_yield is None when the pool has no fees recorded / zero spot / zero TVL — None-yield candidates sort last in ranking_by_observed_fee_yield. position_size_lp is currently an echo (not used in v1 ranking).
Note: the canonical
MockProviderexposes a single V3 ETH/DAI recipe (3000 bps fee), so the example below shows the API shape with one candidate. In practice you’d pass 2-3 candidates from different fee tiers (500 / 3000 / 10000) of the same pair to drive the ranking.
from defipy import CompareFeeTiers
candidates = [ FeeTierCandidate( lp = lp_v3, position_size_lp = 10000.0, lwr_tick = 45000, upr_tick = 47000, name = "3000bps_narrow", ),]
result = CompareFeeTiers().apply(candidates)print(f"numeraire: {result.numeraire}")print(f"pair: {result.pair}")print(f"ranking_by_observed_fee_yield: {result.ranking_by_observed_fee_yield}")print(f"ranking_by_tvl: {result.ranking_by_tvl}")print()for t in result.tiers: print(f" {t.name}: fee_tier_bps={t.fee_tier_bps}, tvl={t.pool_tvl_in_token0:.4f}, " f"yield={t.observed_fee_yield}, in_range={t.in_range}, width={t.range_width_pct:.4f}")CompareProtocols
Section titled “CompareProtocols”Purpose. Compare IL at a price shock + slippage at a trade size between two pools. Pools may be different protocols.
Signature.
CompareProtocols(price_shock=0.10, v3_range_pct=0.10).apply( lp_a, lp_b, amount, token_in=None,) -> ProtocolComparisontoken_in defaults to lp_a’s token0 — both pools must contain it. V3 il_at_shock is None when price_shock > v3_range_pct (out-of-range regime). Stableswap il_at_shock is None on DepegUnreachableError. Balancer/Stableswap slippage_at_amount is None (CalculateSlippage is Uniswap-only). Advantage labels: "pool_a", "pool_b", "tied", or None (when either side is None).
from defipy import CompareProtocols
# Compare V2 against Balancer on the same ETH/DAI pair, 10 ETH trade.result = CompareProtocols().apply( lp_v2, lp_bal, amount = 10.0, token_in = v2_tokens["ETH"],)print(f"price_shock used: {result.price_shock}")print(f"amount / token_in: {result.amount} {result.token_in_name}")print(f"il_advantage: {result.il_advantage}")print(f"slippage_advantage: {result.slippage_advantage}")print()print(f" pool_a ({result.pool_a.protocol}): il={result.pool_a.il_at_shock}, " f"slippage={result.pool_a.slippage_at_amount}, tvl={result.pool_a.tvl_in_token_in}")print(f" pool_b ({result.pool_b.protocol}): il={result.pool_b.il_at_shock}, " f"slippage={result.pool_b.slippage_at_amount}, tvl={result.pool_b.tvl_in_token_in}")print()print(f"notes: {result.notes}")Protocol coverage
Section titled “Protocol coverage”| Protocol | Supported | Notes |
|---|---|---|
| Uniswap V2 | ✅ | CompareProtocols (full) |
| Uniswap V3 | ✅ | CompareFeeTiers (single-protocol); CompareProtocols (full, with v3_range_pct constraint) |
| Balancer | ⚠️ | CompareProtocols (IL only — slippage is None) |
| Stableswap | ⚠️ | CompareProtocols (IL only; None on unreachable depeg) |
MCP tool exposure
Section titled “MCP tool exposure”Neither primitive is in the curated 10. Comparison answers (“which fee tier?”, “which protocol?”) are second-order decisions an agent makes after a position-level analysis — they’re better composed LLM-side over the leaf primitives. The category exists for when an agent wants the structured comparison instead of building it from scratch.