From d537f9a467351f53b0233b847f5c1fe2604e2f5a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 22:54:39 +0000 Subject: [PATCH 1/5] chore: fix stale docs, remove dead MevBlockDelay refs, reorganize scripts https://claude.ai/code/session_01LYo8yCXSHF8vm1KR7nt6b4 --- .gitignore | 1 + AGENT_README.md | 2 +- ARCHITECTURE.md | 14 +++++++------- CLAUDE.md | 2 +- .../claim-protocol-fees.ts | 0 collect-rewards.ts => scripts/collect-rewards.ts | 0 test-deploy.ts => scripts/test-deploy.ts | 0 test-swap-out.ts => scripts/test-swap-out.ts | 0 test-swap.ts => scripts/test-swap.ts | 0 .../update-reward-admin.ts | 0 .../update-reward-recipient.ts | 0 view-rewards.ts => scripts/view-rewards.ts | 0 12 files changed, 10 insertions(+), 9 deletions(-) rename claim-protocol-fees.ts => scripts/claim-protocol-fees.ts (100%) rename collect-rewards.ts => scripts/collect-rewards.ts (100%) rename test-deploy.ts => scripts/test-deploy.ts (100%) rename test-swap-out.ts => scripts/test-swap-out.ts (100%) rename test-swap.ts => scripts/test-swap.ts (100%) rename update-reward-admin.ts => scripts/update-reward-admin.ts (100%) rename update-reward-recipient.ts => scripts/update-reward-recipient.ts (100%) rename view-rewards.ts => scripts/view-rewards.ts (100%) diff --git a/.gitignore b/.gitignore index 8f581d8..f4bd7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist/ !.env.example .DS_Store coverage/ +.cursor/ diff --git a/AGENT_README.md b/AGENT_README.md index 18d2bbe..62aeb5e 100644 --- a/AGENT_README.md +++ b/AGENT_README.md @@ -35,7 +35,7 @@ No API keys, no backend, no database. The SDK talks directly to Base mainnet con Liquid Protocol deploys ERC-20 tokens on Base with: - Uniswap V4 liquidity pools (automatic) - LP fee collection and reward distribution -- MEV protection via block delay +- MEV protection via sniper auction - Optional extensions: dev buy, vault lockup/vesting, airdrops Every token gets 100 billion supply (18 decimals), a Uniswap V4 pool, and locked liquidity with configurable reward splits. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ffb5a51..aa6cf3d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -87,13 +87,13 @@ When `deployToken()` is called with minimal params, the SDK fills these defaults | Parameter | Default | Notes | |-----------|---------|-------| -| `hook` | HookDynamicFeeV2 | Tick-based dynamic fees | +| `hook` | HookStaticFeeV2 | 1% buy + 1% sell static fees | | `pairedToken` | WETH | `0x4200...0006` on Base | -| `tickIfToken0IsLiquid` | -198720 | ~$0.001 WETH/token initial price | -| `tickSpacing` | 60 | Standard Uniswap v4 spacing | -| `locker` | LP_LOCKER | Standard LP locker | -| `mevModule` | MEV_BLOCK_DELAY | Block-based MEV protection | -| `tickLower/tickUpper` | [-887220, 887220] | Full-range position | -| `positionBps` | [10000] | Single position, 100% | +| `tickIfToken0IsLiquid` | -230400 | ~10 ETH / ~$20K market cap | +| `tickSpacing` | 200 | Uniswap v4 tick spacing | +| `locker` | LP_LOCKER_FEE_CONVERSION | LP locker with fee conversion to ETH | +| `mevModule` | SNIPER_AUCTION_V2 | 80%→40% over 20s sniper auction | +| `tickLower/tickUpper` | 5-position Liquid layout | See POOL_POSITIONS.Liquid | +| `positionBps` | [1000, 5000, 1500, 2000, 500] | 5-position Liquid layout | | `rewardBps` | [10000] | All rewards to caller | | `salt` | keccak256(name + symbol + timestamp) | Random per deployment | diff --git a/CLAUDE.md b/CLAUDE.md index 3105364..aaa135d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ ## What Is This Project? -**liquid-sdk** (`v1.5.1`) is a TypeScript SDK for the Liquid Protocol token launcher on Base (chain ID 8453). It deploys ERC-20 tokens with permanent, locked Uniswap V4 liquidity pools and provides read/write helpers for pool state, fee claiming, vaults, airdrops, auctions, and MEV bidding. +**liquid-sdk** (`v1.6.3`) is a TypeScript SDK for the Liquid Protocol token launcher on Base (chain ID 8453). It deploys ERC-20 tokens with permanent, locked Uniswap V4 liquidity pools and provides read/write helpers for pool state, fee claiming, vaults, airdrops, auctions, and MEV bidding. --- diff --git a/claim-protocol-fees.ts b/scripts/claim-protocol-fees.ts similarity index 100% rename from claim-protocol-fees.ts rename to scripts/claim-protocol-fees.ts diff --git a/collect-rewards.ts b/scripts/collect-rewards.ts similarity index 100% rename from collect-rewards.ts rename to scripts/collect-rewards.ts diff --git a/test-deploy.ts b/scripts/test-deploy.ts similarity index 100% rename from test-deploy.ts rename to scripts/test-deploy.ts diff --git a/test-swap-out.ts b/scripts/test-swap-out.ts similarity index 100% rename from test-swap-out.ts rename to scripts/test-swap-out.ts diff --git a/test-swap.ts b/scripts/test-swap.ts similarity index 100% rename from test-swap.ts rename to scripts/test-swap.ts diff --git a/update-reward-admin.ts b/scripts/update-reward-admin.ts similarity index 100% rename from update-reward-admin.ts rename to scripts/update-reward-admin.ts diff --git a/update-reward-recipient.ts b/scripts/update-reward-recipient.ts similarity index 100% rename from update-reward-recipient.ts rename to scripts/update-reward-recipient.ts diff --git a/view-rewards.ts b/scripts/view-rewards.ts similarity index 100% rename from view-rewards.ts rename to scripts/view-rewards.ts From 2ea08394af8e13de66139528d9422754e0eeb757 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 22:59:45 +0000 Subject: [PATCH 2/5] chore: remove remaining MevBlockDelay doc refs, add LP Simulator plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove getMevBlockDelay/getPoolUnlockTime from llms.txt, AGENT_README, CLAUDE.md - Fix example 08 to not call removed method - Update "block delay" → "sniper auction" in llms.txt - Add LP-SIMULATOR-PLAN.md with full technical spec https://claude.ai/code/session_01LYo8yCXSHF8vm1KR7nt6b4 --- AGENT_README.md | 21 +-- CLAUDE.md | 7 - LP-SIMULATOR-PLAN.md | 283 +++++++++++++++++++++++++++++++ examples/08-read-only-queries.ts | 4 - llms.txt | 6 +- 5 files changed, 285 insertions(+), 36 deletions(-) create mode 100644 LP-SIMULATOR-PLAN.md diff --git a/AGENT_README.md b/AGENT_README.md index 62aeb5e..3bb209c 100644 --- a/AGENT_README.md +++ b/AGENT_README.md @@ -161,7 +161,7 @@ const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); await publicClient.waitForTransactionReceipt({ hash: txHash }); ``` -**Important:** After deployment, the pool is locked for a MEV block delay. `collectRewardsWithoutUnlock` may revert with `ManagerLocked` if called too soon. Use `getPoolUnlockTime` to check. +**Important:** After deployment, the sniper auction MEV module may block early reward collection. If `collectRewardsWithoutUnlock` reverts with `ManagerLocked`, wait for the auction period to end. #### `sdk.updateRewardRecipient(tokenAddress, rewardIndex, newRecipient)` — Change reward recipient @@ -455,25 +455,6 @@ const result = await sdk.bidInAuction({ --- -### MEV Protection - -#### `sdk.getMevBlockDelay()` — Get configured block delay - -```typescript -const delay = await sdk.getMevBlockDelay(); -// delay: bigint — number of blocks -``` - -#### `sdk.getPoolUnlockTime(poolId)` — When does MEV lock expire - -```typescript -const unlockTime = await sdk.getPoolUnlockTime(poolId); -// unlockTime: bigint — unix timestamp -// If Date.now()/1000 < unlockTime, pool is still locked -``` - ---- - ### Factory & Allowlist Checks #### `sdk.isFactoryDeprecated()` — Is the factory still active diff --git a/CLAUDE.md b/CLAUDE.md index aaa135d..bd0c460 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -209,13 +209,6 @@ await sdk.getAuctionGasPriceForBid(gasPeg, amount) // → bigint await sdk.bidInAuction(params, maxFeePerGas) // → { txHash } (write) ``` -### MEV Protection - -```typescript -await sdk.getMevBlockDelay() // → bigint -await sdk.getPoolUnlockTime(poolId) // → bigint (unix timestamp) -``` - ### Factory Status ```typescript diff --git a/LP-SIMULATOR-PLAN.md b/LP-SIMULATOR-PLAN.md new file mode 100644 index 0000000..8f383c4 --- /dev/null +++ b/LP-SIMULATOR-PLAN.md @@ -0,0 +1,283 @@ +# LP Position Simulator — Technical Plan + +> Build an interactive LP position simulator for Liquid Protocol, equivalent to the Clanker LP Simulator. + +## Overview + +The LP Simulator lets creators model token launch configurations before deploying. It visualizes how liquidity is distributed across price ranges, simulates how ETH buys move through positions, and projects creator fee revenue. + +## Architecture + +``` +lp-simulator/ +├── package.json +├── tsconfig.json +├── next.config.ts +├── tailwind.config.ts +├── src/ +│ ├── app/ +│ │ ├── layout.tsx # Dark theme shell, metadata +│ │ └── page.tsx # Main simulator page (single-page app) +│ ├── components/ +│ │ ├── QuickPresets.tsx # Preset selector (Legacy / Liquid 5-pos / Custom) +│ │ ├── MarketCapInput.tsx # Starting market cap (USD + ETH) input +│ │ ├── SupplyAllocations.tsx # Airdrop %, Vault %, Presale %, Pool % +│ │ ├── PositionEditor.tsx # Add/remove/edit LP positions with tick ranges +│ │ ├── LiquidityChart.tsx # Area chart: supply % vs market cap +│ │ ├── SimulateBuys.tsx # Quick buy buttons (+0.1, +0.5, +1, +5, +10, +50 ETH) +│ │ ├── SimulationResults.tsx # Total ETH, USD value, starting/current mcap +│ │ ├── FeeProjection.tsx # Static fee rate toggle (1%/2%/3%) + projected fees +│ │ └── ExportConfig.tsx # Show/copy SDK DeployTokenParams config +│ ├── lib/ +│ │ ├── tick-math.ts # Import from liquid-sdk or port +│ │ ├── positions.ts # Import from liquid-sdk or port +│ │ ├── simulation.ts # Buy simulation engine +│ │ ├── fee-calculator.ts # Fee projection math +│ │ └── constants.ts # Liquid Protocol defaults +│ └── hooks/ +│ └── useSimulator.ts # Main state management hook +├── public/ +│ └── og-image.png +└── README.md +``` + +## Tech Stack + +- **Next.js 14+** (App Router, React Server Components where appropriate) +- **Tailwind CSS** (dark theme matching Liquid Protocol branding) +- **Recharts** or **lightweight-charts** for the liquidity distribution visualization +- **liquid-sdk** utilities (tick-math, positions, encoding — can be imported directly) +- **Deployed on Vercel** + +## Core Components + +### 1. Quick Presets (`QuickPresets.tsx`) + +Three presets matching Liquid Protocol's standard configurations: + +| Preset | Description | Positions | +|--------|-------------|-----------| +| Legacy (Single Position) | Single wide range | 1 position, 100% | +| Liquid 5-Position (Default) | Optimized for price discovery | 5 positions: 10%/50%/15%/20%/5% | +| Custom | User-defined | Editable | + +### 2. Market Cap & Price Input (`MarketCapInput.tsx`) + +- **Starting Market Cap**: USD input, auto-converts to ETH using live or manual ETH price +- **ETH Price**: Fetched from CoinGecko/DeFi Llama API, or manually overridable +- Output: starting tick via `getTickFromMarketCapUSD()` + +### 3. Supply Allocations (`SupplyAllocations.tsx`) + +| Field | Default | Constraint | +|-------|---------|-----------| +| Airdrop % | 0 | 0-90% | +| Vault % | 0 | 0-90% | +| Presale % | 0 | 0-90% | +| Pool % | 100 | = 100 - airdrop - vault - presale | + +Total supply: 100B tokens. Pool supply is what goes into LP positions. + +### 4. Position Editor (`PositionEditor.tsx`) + +Each position has: +- **Lower bound**: Market cap (auto-converts to tick) +- **Upper bound**: Market cap (auto-converts to tick) +- **Supply %**: Percentage of pool supply allocated (slider) +- **Delete button** +- **Add position button** (max 7 positions per SDK constraint) + +Validation: positionBps must sum to 10,000. + +### 5. Liquidity Distribution Chart (`LiquidityChart.tsx`) + +Multi-series area chart: +- **X-axis**: Market cap (log scale, from starting mcap to $1B+) +- **Left Y-axis**: Supply in pool (% of total) +- **Right Y-axis**: Cumulative % sold +- **Series**: One colored area per position + "Supply Sold" line +- **Annotations**: Position boundaries marked + +### 6. Buy Simulation Engine (`simulation.ts`) + +Core algorithm: +```typescript +interface SimState { + currentTick: number; + currentMcapETH: number; + totalETHBought: number; + tokensRemaining: bigint[]; // per position + cumulativeFees: number; +} + +function simulateBuy(state: SimState, ethAmount: number, feeRate: number): SimState { + let remainingETH = ethAmount; + const fee = remainingETH * feeRate; + remainingETH -= fee; + + // Walk through positions from current tick upward + // Each position has a token reserve that gets consumed + // Price increases as tokens are bought within each range + // When a position is exhausted, move to the next one + + // Uses concentrated liquidity math: + // For each position: L = supply / (sqrt(price_upper) - sqrt(price_lower)) + // Tokens out = L * (sqrt(price_current) - sqrt(price_lower)) + // ETH in = L * (1/sqrt(price_lower) - 1/sqrt(price_current)) + + return newState; +} +``` + +Key math from Uniswap V4 concentrated liquidity: +- `price = 1.0001^tick` +- `L (liquidity) = tokenReserve / (sqrt(priceUpper) - sqrt(priceLower))` +- `ETH needed = L * (sqrt(priceAfter) - sqrt(priceBefore))` (simplified for token0/token1 ordering) + +### 7. Fee Projection (`FeeProjection.tsx`) + +- **Static Fee Rate**: Toggle between 1%, 2%, 3% (matching SDK's `encodeStaticFeePoolData`) +- **Swap Volume**: Input or derived from simulated buys +- **Fee Rate**: Selected rate +- **Your Fees**: `swapVolume * feeRate * (1 - protocolFee)` + - Protocol fee = 20% (PROTOCOL_FEE_NUMERATOR = 200,000 / 1,000,000) + - Creator keeps 80% of LP fees +- **In ETH**: Convert using ETH price + +### 8. Export Config (`ExportConfig.tsx`) + +Generate a ready-to-use SDK config: +```typescript +import { LiquidSDK } from "liquid-sdk"; + +const config = { + name: "MyToken", + symbol: "MTK", + tickIfToken0IsLiquid: -226600, // from simulator + tickSpacing: 200, + tickLower: [-226600, -216000, -202000, -155000, -141000], + tickUpper: [-216000, -155000, -155000, -120000, -120000], + positionBps: [1000, 5000, 1500, 2000, 500], + // ... extensions if configured +}; + +const result = await sdk.deployToken(config); +``` + +## State Management + +Single `useSimulator` hook manages all state: + +```typescript +interface SimulatorState { + // Config + startingMcapUSD: number; + ethPriceUSD: number; + feeRate: number; // 0.01, 0.02, or 0.03 + tickSpacing: number; + + // Supply + airdropPct: number; + vaultPct: number; + presalePct: number; + + // Positions + positions: { tickLower: number; tickUpper: number; positionBps: number }[]; + + // Simulation + buys: number[]; // ETH amounts + simState: SimState; +} +``` + +## Data Flow + +``` +User Input (mcap, positions, allocations) + → tick-math conversion + → position arrays + → liquidity distribution calculation + → chart data + +User clicks "Simulate Buy" + → simulation engine processes buy + → updates current tick / mcap + → updates chart (supply sold line) + → updates fee projection + +User clicks "Export" + → generates DeployTokenParams + → displays as code block + → copy to clipboard +``` + +## SDK Integration Points + +| Simulator Feature | SDK Function | File | +|-------------------|-------------|------| +| Market cap → tick | `getTickFromMarketCapUSD()` | `src/utils/tick-math.ts` | +| Tick → market cap | `marketCapFromTickUSD()` | `src/utils/tick-math.ts` | +| Position building | `createPositionsUSD()` | `src/utils/positions.ts` | +| Default positions | `createDefaultPositions()` | `src/utils/positions.ts` | +| Position descriptions | `describePositions()` | `src/utils/positions.ts` | +| Fee encoding | `encodeStaticFeePoolData()` | `src/utils/encoding.ts` | +| MEV encoding | `encodeSniperAuctionData()` | `src/utils/encoding.ts` | +| Constants | `ADDRESSES`, `DEFAULTS`, `FEE`, `TOKEN` | `src/constants.ts` | + +## Implementation Phases + +### Phase 1: Core simulator (MVP) +- Position editor with presets +- Liquidity distribution chart +- Starting market cap input +- Export SDK config + +### Phase 2: Buy simulation +- Simulate ETH buys +- Track price movement through positions +- Cumulative sold line on chart + +### Phase 3: Fee projection +- Static fee rate selector +- Fee calculation with protocol fee deduction +- Projected revenue at various volumes + +### Phase 4: Polish +- Supply allocations (airdrop/vault/presale) +- Custom buy amounts +- Live ETH price fetch +- Mobile responsive +- Deploy to Vercel + +## Key Constants (from SDK) + +```typescript +// Token +const TOTAL_SUPPLY = 100_000_000_000n * 10n ** 18n; // 100B tokens + +// Fees +const FEE_DENOMINATOR = 1_000_000; // 100% +const PROTOCOL_FEE = 200_000; // 20% of LP fees +const MAX_LP_FEE = 100_000; // 10% +const MAX_MEV_FEE = 800_000; // 80% + +// Positions +const MAX_POSITIONS = 7; +const BPS_DENOMINATOR = 10_000; +const DEFAULT_TICK_SPACING = 200; + +// Default: 5-position Liquid layout +const DEFAULT_POSITIONS = [ + { tickLower: -230400, tickUpper: -216000, positionBps: 1000 }, // 10% + { tickLower: -216000, tickUpper: -155000, positionBps: 5000 }, // 50% + { tickLower: -202000, tickUpper: -155000, positionBps: 1500 }, // 15% + { tickLower: -155000, tickUpper: -120000, positionBps: 2000 }, // 20% + { tickLower: -141000, tickUpper: -120000, positionBps: 500 }, // 5% +]; +``` + +## Deployment + +- Host on Vercel (already connected via MCP) +- Domain: `simulator.liquidprotocol.xyz` or similar +- Add to SDK README and llms.txt as a tool reference diff --git a/examples/08-read-only-queries.ts b/examples/08-read-only-queries.ts index 8fe9ffb..d14b778 100644 --- a/examples/08-read-only-queries.ts +++ b/examples/08-read-only-queries.ts @@ -35,10 +35,6 @@ async function main() { const deprecated = await sdk.isFactoryDeprecated(); console.log("\nFactory deprecated:", deprecated); - // MEV block delay - const blockDelay = await sdk.getMevBlockDelay(); - console.log("MEV block delay:", blockDelay.toString(), "blocks"); - console.log("\nDone — all queries completed without a wallet!"); } diff --git a/llms.txt b/llms.txt index 413abe7..46599f9 100644 --- a/llms.txt +++ b/llms.txt @@ -30,7 +30,7 @@ console.log(result.tokenAddress); Liquid Protocol deploys ERC-20 tokens on Base (chain 8453) with: - Uniswap V4 liquidity pools (created automatically) - Locked LP with configurable reward splits -- MEV protection (sniper auction or block delay) +- MEV protection (sniper auction with descending fees) - Optional extensions: dev buy, vault lockup/vesting, merkle airdrops Every token gets 100 billion supply (18 decimals), a Uniswap V4 pool, and locked liquidity. @@ -86,10 +86,6 @@ Every token gets 100 billion supply (18 decimals), a Uniswap V4 pool, and locked - `sdk.getAuctionMaxRounds()` — Max auction rounds - `sdk.getAuctionGasPriceForBid(gasPeg, bidAmount)` — Calculate gas price for bid -### MEV Protection -- `sdk.getMevBlockDelay()` — Configured block delay -- `sdk.getPoolUnlockTime(poolId)` — When MEV lock expires (unix timestamp) - ### Factory & Allowlist - `sdk.isFactoryDeprecated()` — Is factory still active - `sdk.isLockerEnabled(locker, hook)` — Is locker approved for hook From c8b6008d358f11750f7148da03683eee85b3503e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 23:11:23 +0000 Subject: [PATCH 3/5] feat: add RAG knowledge base for Docs Agent and LP Simulator 27 structured documents across 5 categories: - rag/addresses.json: all deployed contract addresses with descriptions - rag/schemas/: JSON Schemas for DeployTokenParams, PoolConfig, etc. - rag/contracts/: 9 smart contract docs (factory, token, hooks, fee locker, LP locker, vault, airdrop, MEV protection, extensions) - rag/sdk/: 8 SDK method guides (deploy, fees, rewards, vault, airdrop, sniper auction, pool reads, position builder) - rag/concepts/: 6 protocol concept guides (token lifecycle, fee system, MEV protection, LP positions, extension system, tick math) 4,167 lines of structured documentation for zero-memory-issue agent consumption. https://claude.ai/code/session_01LYo8yCXSHF8vm1KR7nt6b4 --- rag/README.md | 35 +++ rag/addresses.json | 111 ++++++++++ rag/concepts/extension-system.md | 166 ++++++++++++++ rag/concepts/fee-system.md | 197 +++++++++++++++++ rag/concepts/lp-positions.md | 190 +++++++++++++++++ rag/concepts/mev-protection.md | 157 ++++++++++++++ rag/concepts/tick-math.md | 230 ++++++++++++++++++++ rag/concepts/token-lifecycle.md | 200 +++++++++++++++++ rag/contracts/liquid-airdrop.md | 123 +++++++++++ rag/contracts/liquid-extensions.md | 152 +++++++++++++ rag/contracts/liquid-factory.md | 140 ++++++++++++ rag/contracts/liquid-fee-locker.md | 102 +++++++++ rag/contracts/liquid-hooks.md | 169 +++++++++++++++ rag/contracts/liquid-lp-locker.md | 136 ++++++++++++ rag/contracts/liquid-mev-protection.md | 173 +++++++++++++++ rag/contracts/liquid-token.md | 104 +++++++++ rag/contracts/liquid-vault.md | 134 ++++++++++++ rag/schemas/deploy-params.json | 200 +++++++++++++++++ rag/schemas/pool-config.json | 171 +++++++++++++++ rag/sdk/airdrop-system.md | 130 +++++++++++ rag/sdk/deploy-token.md | 285 +++++++++++++++++++++++++ rag/sdk/fee-management.md | 116 ++++++++++ rag/sdk/pool-reads.md | 124 +++++++++++ rag/sdk/position-builder.md | 172 +++++++++++++++ rag/sdk/reward-management.md | 129 +++++++++++ rag/sdk/sniper-auction.md | 183 ++++++++++++++++ rag/sdk/vault-lifecycle.md | 138 ++++++++++++ 27 files changed, 4167 insertions(+) create mode 100644 rag/README.md create mode 100644 rag/addresses.json create mode 100644 rag/concepts/extension-system.md create mode 100644 rag/concepts/fee-system.md create mode 100644 rag/concepts/lp-positions.md create mode 100644 rag/concepts/mev-protection.md create mode 100644 rag/concepts/tick-math.md create mode 100644 rag/concepts/token-lifecycle.md create mode 100644 rag/contracts/liquid-airdrop.md create mode 100644 rag/contracts/liquid-extensions.md create mode 100644 rag/contracts/liquid-factory.md create mode 100644 rag/contracts/liquid-fee-locker.md create mode 100644 rag/contracts/liquid-hooks.md create mode 100644 rag/contracts/liquid-lp-locker.md create mode 100644 rag/contracts/liquid-mev-protection.md create mode 100644 rag/contracts/liquid-token.md create mode 100644 rag/contracts/liquid-vault.md create mode 100644 rag/schemas/deploy-params.json create mode 100644 rag/schemas/pool-config.json create mode 100644 rag/sdk/airdrop-system.md create mode 100644 rag/sdk/deploy-token.md create mode 100644 rag/sdk/fee-management.md create mode 100644 rag/sdk/pool-reads.md create mode 100644 rag/sdk/position-builder.md create mode 100644 rag/sdk/reward-management.md create mode 100644 rag/sdk/sniper-auction.md create mode 100644 rag/sdk/vault-lifecycle.md diff --git a/rag/README.md b/rag/README.md new file mode 100644 index 0000000..af9e7b0 --- /dev/null +++ b/rag/README.md @@ -0,0 +1,35 @@ +# Liquid Protocol RAG Knowledge Base + +Structured reference documents for the Liquid Protocol SDK on Base (chain ID 8453). Designed for consumption by AI agents (Docs Agent, LP Simulator) and human developers. + +## Structure + +| Directory | Contents | +|-----------|----------| +| `addresses.json` | All deployed contract addresses with descriptions | +| `schemas/` | JSON Schemas for deployment parameters and config types | +| `contracts/` | Smart contract documentation -- what each contract does, key functions, events | +| `sdk/` | SDK method guides -- how to call each SDK function with examples | +| `concepts/` | Protocol concepts -- end-to-end explanations of how systems work | + +## How to Use + +**AI Agents:** Load individual files into your context window as needed. Each file is self-contained. Start with `concepts/token-lifecycle.md` for a full overview, then drill into specific topics. + +**Docs Agent:** Index all `.md` files for semantic search. Use `addresses.json` and `schemas/*.json` for structured lookups. + +**LP Simulator:** Load `concepts/tick-math.md`, `concepts/lp-positions.md`, and `concepts/fee-system.md` for the math behind liquidity positions and fee calculations. + +## Key Facts + +- **Chain:** Base mainnet (chain ID 8453) +- **Token supply:** Always 100 billion (100,000,000,000) with 18 decimals +- **Paired asset:** WETH (`0x4200000000000000000000000000000000000006`) +- **Default fee:** 1% static on both buys and sells +- **Default MEV:** Sniper Auction V2 (80% to 40% decay over 20 seconds) +- **LP:** Permanently locked -- cannot be withdrawn +- **SDK:** `npm install liquid-sdk viem` + +## Cross-References + +Documents reference each other using relative paths like `../concepts/tick-math.md`. Follow these links for deeper context on any topic. diff --git a/rag/addresses.json b/rag/addresses.json new file mode 100644 index 0000000..5fb75a3 --- /dev/null +++ b/rag/addresses.json @@ -0,0 +1,111 @@ +{ + "chain": { + "name": "Base", + "chainId": 8453, + "rpc": "https://mainnet.base.org", + "explorer": "https://basescan.org" + }, + "liquidProtocol": { + "FACTORY": { + "address": "0x04F1a284168743759BE6554f607a10CEBdB77760", + "description": "Liquid.sol -- Token factory that orchestrates deployment of ERC-20 tokens, Uniswap V4 pool initialization, LP locking, MEV protection setup, and extension execution.", + "sdkConstant": "ADDRESSES.FACTORY" + }, + "FEE_LOCKER": { + "address": "0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF", + "description": "LiquidFeeLocker -- Escrow contract for LP fees. Stores per-owner, per-token fee balances. Only allowlisted depositors (LP Locker, Sniper Auction) can deposit. Anyone can trigger claim() for a fee owner.", + "sdkConstant": "ADDRESSES.FEE_LOCKER" + }, + "LP_LOCKER_FEE_CONVERSION": { + "address": "0x77247fCD1d5e34A3703AcA898A591Dc7422435f3", + "description": "LiquidLpLockerFeeConversion -- Permanently locks Uniswap V4 LP positions. Collects fees from positions and converts them to a preferred token (ETH by default) before routing to the Fee Locker. Manages reward recipient addresses and BPS splits.", + "sdkConstant": "ADDRESSES.LP_LOCKER_FEE_CONVERSION" + }, + "POOL_EXTENSION_ALLOWLIST": { + "address": "0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa", + "description": "LiquidPoolExtensionAllowlist -- Manages per-pool extension permissions. Owned by the admin Gnosis Safe. Controls which extensions can interact with which pools.", + "sdkConstant": "ADDRESSES.POOL_EXTENSION_ALLOWLIST" + }, + "HOOK_DYNAMIC_FEE_V2": { + "address": "0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC", + "description": "LiquidHookDynamicFeeV2 -- Uniswap V4 hook implementing tick-based dynamic LP fee curves. Fee adjusts based on price volatility with configurable base/max fees, decay periods, and reset logic.", + "sdkConstant": "ADDRESSES.HOOK_DYNAMIC_FEE_V2" + }, + "HOOK_STATIC_FEE_V2": { + "address": "0x9811f10Cd549c754Fa9E5785989c422A762c28cc", + "description": "LiquidHookStaticFeeV2 -- Uniswap V4 hook implementing fixed LP fees. Default is 1% on both buys and sells (100 BPS each direction). This is the default hook used by the SDK.", + "sdkConstant": "ADDRESSES.HOOK_STATIC_FEE_V2" + }, + "VAULT": { + "address": "0xdFCCC93257c20519A9005A2281CFBdF84836d50E", + "description": "LiquidVault -- Extension for token lockup with linear vesting. Tokens are locked for a configurable lockup period, then vest linearly until vestingEndTime. Admin can claim vested tokens.", + "sdkConstant": "ADDRESSES.VAULT" + }, + "SNIPER_AUCTION_V2": { + "address": "0x187e8627c02c58F31831953C1268e157d3BfCefd", + "description": "LiquidSniperAuctionV2 -- MEV protection module. Runs a gas-price auction for 5 rounds (every 2 blocks) starting 2 blocks after deployment. Fee decays from 80% to 40% over 20 seconds. Highest gas-price bidder wins each round.", + "sdkConstant": "ADDRESSES.SNIPER_AUCTION_V2" + }, + "SNIPER_UTIL_V2": { + "address": "0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f", + "description": "LiquidSniperUtilV2 -- Utility contract for executing sniper auction bids. Handles WETH transfer, swap execution, and gas price encoding for bid amount calculation.", + "sdkConstant": "ADDRESSES.SNIPER_UTIL_V2" + }, + "MEV_DESCENDING_FEES": { + "address": "0x8D6B080e48756A99F3893491D556B5d6907b6910", + "description": "LiquidMevDescendingFees -- Alternative MEV protection module. Applies a parabolic fee decay starting at up to 80% (800,000 uniBps) that decays over a maximum of 2 minutes. No auction mechanics -- purely time-based fee decay.", + "sdkConstant": "ADDRESSES.MEV_DESCENDING_FEES" + }, + "AIRDROP_V2": { + "address": "0x1423974d48f525462f1c087cBFdCC20BDBc33CdD", + "description": "LiquidAirdropV2 -- Merkle-based token airdrop extension. Supports mutable merkle root (admin can update), lockup period before claims open, linear vesting after lockup, and admin reclaim of unclaimed tokens.", + "sdkConstant": "ADDRESSES.AIRDROP_V2" + }, + "UNIV4_ETH_DEV_BUY": { + "address": "0x5934097864dC487D21A7B4e4EEe201A39ceF728D", + "description": "LiquidUniv4EthDevBuy -- Extension that executes a dev buy (ETH-to-token swap) through the Uniswap V4 pool in the same transaction as token deployment. Used when devBuy is specified in deployToken().", + "sdkConstant": "ADDRESSES.UNIV4_ETH_DEV_BUY" + }, + "UNIV3_ETH_DEV_BUY": { + "address": "0x376028cfb6b9A120E24Aa14c3FAc4205179c0025", + "description": "LiquidUniv3EthDevBuy -- Extension for dev buy through Uniswap V3 pools (legacy). Swaps ETH for tokens at deployment time via V3 routing.", + "sdkConstant": "ADDRESSES.UNIV3_ETH_DEV_BUY" + }, + "PRESALE_ETH_TO_CREATOR": { + "address": "0x3bca63EcB49d5f917092d10fA879Fdb422740163", + "description": "LiquidPresaleEthToCreator -- Presale extension where participants send ETH and receive tokens. The ETH is forwarded directly to the token creator.", + "sdkConstant": "ADDRESSES.PRESALE_ETH_TO_CREATOR" + }, + "PRESALE_ALLOWLIST": { + "address": "0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B", + "description": "LiquidPresaleAllowlist -- Allowlist-gated presale extension. Only addresses on the allowlist can participate in the presale.", + "sdkConstant": "ADDRESSES.PRESALE_ALLOWLIST" + } + }, + "external": { + "POOL_MANAGER": { + "address": "0x498581fF718922c3f8e6A244956aF099B2652b2b", + "description": "Uniswap V4 PoolManager -- Core contract that manages all V4 pools. All swaps and liquidity operations go through this singleton.", + "sdkConstant": "EXTERNAL.POOL_MANAGER" + }, + "WETH": { + "address": "0x4200000000000000000000000000000000000006", + "description": "Wrapped ETH on Base -- The paired token for all Liquid Protocol pools. Always currency0 in pool keys (numerically lower than token addresses).", + "sdkConstant": "EXTERNAL.WETH" + }, + "UNIVERSAL_ROUTER": { + "address": "0x6fF5693b99212Da76ad316178A184AB56D299b43", + "description": "Uniswap Universal Router -- Entry point for swaps via Uniswap V4. Handles WRAP_ETH, V4_SWAP, and UNWRAP_WETH commands.", + "sdkConstant": "EXTERNAL.UNIVERSAL_ROUTER" + }, + "PERMIT2": { + "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "description": "Uniswap Permit2 -- Token approval management used for sell-side swaps (token-to-ETH). Requires token.approve(PERMIT2) + Permit2.approve(token, UNIVERSAL_ROUTER).", + "sdkConstant": "EXTERNAL.PERMIT2" + } + }, + "adminSafe": { + "address": "0x872c561f699B42977c093F0eD8b4C9a431280c6c", + "description": "Gnosis Safe multisig that owns the Factory and Pool Extension Allowlist. Required for enabling new hooks, lockers, extensions, and MEV modules." + } +} diff --git a/rag/concepts/extension-system.md b/rag/concepts/extension-system.md new file mode 100644 index 0000000..4db3365 --- /dev/null +++ b/rag/concepts/extension-system.md @@ -0,0 +1,166 @@ +# Concept: Extension System + +How extensions work in Liquid Protocol: the allowlist process, available extensions, and how they integrate with token deployment. + +## What Are Extensions? + +Extensions are modular smart contracts that receive a portion of the token supply at deployment time. They enable pre-launch token distribution without modifying the core factory logic. + +## How Extensions Work + +### At Deployment + +``` +deployToken() called with extensions[] array + | + v +Factory validates each extension: + 1. Is extension on the allowlist? (isExtensionEnabled) + 2. Is extensionBps within limits? + 3. Total extensionBps <= 9000 (90%)? + | + v +For each extension in order: + 1. Calculate allocation: TOKEN_SUPPLY * extensionBps / 10000 + 2. Factory approves extension to spend tokens + 3. Call extension.receiveTokens(token, amount, extensionData) + | + v +Remaining supply (10000 - sum(extensionBps)) -> LP positions +``` + +### Constraints + +| Constraint | Value | SDK Constant | +|------------|-------|-------------| +| Max extensions per token | 10 | `TOKEN.MAX_EXTENSIONS` | +| Max total supply to extensions | 90% (9000 BPS) | `TOKEN.MAX_EXTENSION_BPS` | +| Remaining supply | Goes to LP | Locked permanently | + +## Extension Allowlist + +Extensions must be approved by the Liquid Protocol admin before they can be used. + +### Current Allowlisted Extensions + +| Extension | Address | Type | +|-----------|---------|------| +| LiquidVault | `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` | Token lockup + vesting | +| LiquidAirdropV2 | `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` | Merkle distribution | +| LiquidUniv4EthDevBuy | `0x5934097864dC487D21A7B4e4EEe201A39ceF728D` | Buy tokens via V4 at launch | +| LiquidUniv3EthDevBuy | `0x376028cfb6b9A120E24Aa14c3FAc4205179c0025` | Buy tokens via V3 at launch | +| LiquidPresaleEthToCreator | `0x3bca63EcB49d5f917092d10fA879Fdb422740163` | Presale with ETH to creator | +| LiquidPresaleAllowlist | `0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B` | Allowlist-gated presale | + +All extensions are covered by 0xMacro (A-3) and Cantina audits of the Clanker v4 codebase. + +### Approval Process for New Extensions + +New extensions require ALL of: + +1. **Full third-party audit** by a recognized firm +2. **Uniswap alignment** -- approval from a Uniswap V4 core contributor +3. **Internal review** by Liquid Protocol engineering lead +4. **Admin Safe approval** -- multisig transaction through Gnosis Safe (`0x872c561f699B42977c093F0eD8b4C9a431280c6c`) + +**Current status:** No plans to approve additional extensions. Contact `slaterg@mog.capital` to apply. + +### On-Chain Mechanism + +```typescript +// Check if extension is enabled +const isEnabled = await sdk.isExtensionEnabled(extensionAddress); +``` + +The allowlist is managed by `LiquidPoolExtensionAllowlist` (`0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa`). + +## Available Extensions (Detail) + +### Dev Buy (Recommended for Deployers) + +The best way to acquire tokens at launch. Swaps ETH for tokens through the Uniswap V4 pool in the same deployment transaction. + +**Key advantage:** Uses normal 1% LP fee, not the 80% MEV auction fee. + +```typescript +// Simplest way -- SDK builds the extension automatically +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", + devBuy: { + ethAmount: parseEther("0.01"), + recipient: account.address, + }, +}); +``` + +Or build manually: + +```typescript +const ext = sdk.buildDevBuyExtension({ + ethAmount: parseEther("0.01"), + recipient: account.address, +}); + +// ext: ExtensionConfig ready to include in extensions[] +``` + +### Vault + +Locks tokens with lockup + linear vesting. See [../contracts/liquid-vault.md](../contracts/liquid-vault.md). + +**Use cases:** Team lockups, investor vesting, treasury management. + +### Airdrop V2 + +Merkle-based token distribution with mutable root, lockup, vesting, and admin reclaim. See [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md). + +**Use cases:** Community airdrops, retroactive rewards, ecosystem grants. + +### Presale (ETH to Creator) + +Participants send ETH and receive tokens. ETH goes directly to the token creator. + +**Use cases:** Pre-launch fundraising, community pre-sales. + +### Presale (Allowlist) + +Allowlist-gated presale. Only approved addresses can participate. + +**Use cases:** VIP pre-sales, KYC-gated sales, partner allocations. + +## Extension Config Structure + +```typescript +interface ExtensionConfig { + extension: Address; // Must be on the allowlist + msgValue: bigint; // ETH to send (0n except for dev buy / presale) + extensionBps: number; // Supply allocation (0-9000 BPS) + extensionData: Hex; // ABI-encoded init data (varies by extension) +} +``` + +## Supply Distribution Example + +``` +Token: 100,000,000,000 (100B) total supply + +Extensions: + Vault: 2000 BPS = 20% = 20,000,000,000 tokens (locked + vesting) + Airdrop: 1000 BPS = 10% = 10,000,000,000 tokens (merkle claims) + Dev Buy: 0 BPS = 0% = buys from pool, not from supply + +LP Positions: 7000 BPS = 70% = 70,000,000,000 tokens + Position 1: 40% of 70B = 28,000,000,000 tokens + Position 2: 50% of 70B = 35,000,000,000 tokens + Position 3: 10% of 70B = 7,000,000,000 tokens +``` + +Note: Dev buy uses 0 BPS because it buys from the pool (post-initialization), not from the supply allocation. + +## See Also + +- [../contracts/liquid-extensions.md](../contracts/liquid-extensions.md) -- Extension contracts +- [../contracts/liquid-vault.md](../contracts/liquid-vault.md) -- Vault details +- [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md) -- Airdrop details +- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with extensions diff --git a/rag/concepts/fee-system.md b/rag/concepts/fee-system.md new file mode 100644 index 0000000..100dd3d --- /dev/null +++ b/rag/concepts/fee-system.md @@ -0,0 +1,197 @@ +# Concept: Fee System + +Complete explanation of how fees work in Liquid Protocol: LP fees (static/dynamic), protocol fees, fee conversion, and reward distribution. + +## Fee Layers + +Every swap through a Liquid Protocol pool passes through multiple fee layers: + +| Layer | Rate | When Active | Goes To | +|-------|------|-------------|---------| +| MEV Fee | 80% to 40% | First ~20s after launch | Protocol + LP | +| LP Fee | 1% (default) | Always | LP position (80%) + Protocol (20%) | +| Protocol Fee | 20% of LP fee | Always | Team fee recipient | + +## Fee Constants + +```typescript +import { FEE } from "liquid-sdk"; + +FEE.DENOMINATOR // 1,000,000 -- Uniswap V4 fee unit (100%) +FEE.PROTOCOL_FEE_NUMERATOR // 200,000 -- 20% of LP fees to protocol +FEE.MAX_LP_FEE // 100,000 -- 10% max LP fee +FEE.MAX_MEV_FEE // 800,000 -- 80% max MEV fee +FEE.BPS // 10,000 -- basis points denominator +``` + +### Fee Unit Conversions + +| BPS | UniBps | Percentage | +|-----|--------|------------| +| 1 | 100 | 0.01% | +| 100 | 10,000 | 1% | +| 500 | 50,000 | 5% | +| 1000 | 100,000 | 10% | +| 8000 | 800,000 | 80% | + +The SDK uses BPS for input (e.g., `100` = 1%) and converts to uniBps internally by multiplying by 100. + +## Static Fees (Default) + +The default hook (`HOOK_STATIC_FEE_V2`) charges a fixed fee on every swap: + +- **Liquid fee (sell):** Fee when swapping token to ETH. Default: 100 BPS (1%) +- **Paired fee (buy):** Fee when swapping ETH to token. Default: 100 BPS (1%) + +```typescript +import { encodeStaticFeePoolData } from "liquid-sdk"; + +// Default: 1% both directions +const poolData = encodeStaticFeePoolData(100, 100); + +// Custom: 0% sell, 2% buy +const poolData = encodeStaticFeePoolData(0, 200); + +// Custom: 0.5% sell, 3% buy +const poolData = encodeStaticFeePoolData(50, 300); +``` + +## Dynamic Fees + +The dynamic fee hook (`HOOK_DYNAMIC_FEE_V2`) adjusts the fee based on price volatility: + +- **Base fee:** Minimum fee during calm markets +- **Max fee:** Maximum fee during high volatility +- **Volatility tracking:** Uses tick movement to detect volatility +- **Decay:** Fee decays back toward base when volatility subsides + +```typescript +import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; + +const poolData = encodeDynamicFeePoolData({ + baseFeeBps: 100, // 1% base + maxFeeBps: 500, // 5% max + referenceTickFilterPeriod: 30, // 30s smoothing + resetPeriod: 120, // 2 min reset + resetTickFilter: 200, // 200 tick threshold + feeControlNumerator: 500000000n, // scaling + decayFilterBps: 7500, // 75% decay +}); +``` + +## Protocol Fee + +A fixed 20% of all LP fees goes to the Liquid Protocol team: + +``` +LP Fee = 1% of swap +Protocol share = 1% * 20% = 0.2% of swap +LP share = 1% * 80% = 0.8% of swap +``` + +The protocol fee is collected by the hook's `afterSwap` callback and routed to the factory's `teamFeeRecipient`. + +## Fee Conversion + +The LP Locker Fee Conversion contract can convert collected fees to a preferred token before distributing: + +| FeePreference | Value | Behavior | +|---------------|-------|----------| +| `Both` | 0 | No conversion -- receive fees in both WETH and token | +| `Paired` | 1 | Convert all fees to WETH/ETH. **Default.** | +| `Liquid` | 2 | Convert all fees to the liquid token | + +```typescript +import { encodeFeeConversionLockerData, FeePreference } from "liquid-sdk"; + +// Default: all fees as ETH +const lockerData = encodeFeeConversionLockerData([FeePreference.Paired]); + +// Two recipients: one gets ETH, one gets token +const lockerData = encodeFeeConversionLockerData([ + FeePreference.Paired, + FeePreference.Liquid, +]); +``` + +## Reward Distribution + +Fees are split among reward recipients according to immutable BPS allocations: + +```typescript +// Set at deployment +const result = await sdk.deployToken({ + rewardAdmins: [walletA, walletB, treasury], + rewardRecipients: [walletA, walletB, treasury], + rewardBps: [5000, 3000, 2000], // 50% / 30% / 20% +}); +``` + +**Rules:** +- BPS splits are IMMUTABLE after deployment +- Recipient addresses can be updated by their admin +- Admin addresses are IMMUTABLE +- BPS must sum to exactly 10000 + +## Complete Fee Flow + +``` +User swaps ETH -> Token (buy) via Universal Router + | + v +Hook.beforeSwap(): + |-- If MEV active: apply 80-40% MEV fee + |-- Calculate LP fee: 1% (10,000 uniBps) + |-- Calculate protocol fee: 1% * 20% = 0.2% + | + v +Uniswap V4 executes swap, deducting fees + | + v +Hook.afterSwap(): + |-- Protocol fee (0.2%) -> Factory team fee recipient + |-- LP fee (0.8%) -> Accrues in LP position + | + v +collectRewards(tokenAddress) called (by anyone) + | + v +LP Locker: + 1. Reads LP positions from PoolManager + 2. Collects accrued fees (WETH + token) + 3. Converts to ETH (FeePreference.Paired) + 4. For each recipient: + fee = totalFees * recipientBps / 10000 + FeeLocker.storeFees(recipient, WETH, fee) + | + v +claimFees(recipient, WETH) called + | + v +FeeLocker transfers WETH to recipient +``` + +## Fee Example (Numbers) + +For a $1000 swap at default settings: + +| Component | Amount | +|-----------|--------| +| Swap amount | $1000 | +| LP fee (1%) | $10 | +| Protocol fee (20% of LP) | $2 | +| Net to LP position | $8 | +| MEV fee (if active, 80%) | $800 additional | + +After collection with single recipient: +- Recipient gets $8 in WETH (per swap) +- Accumulates over many swaps before collection + +## See Also + +- [mev-protection.md](mev-protection.md) -- MEV fee details +- [../contracts/liquid-hooks.md](../contracts/liquid-hooks.md) -- Hook contracts +- [../contracts/liquid-fee-locker.md](../contracts/liquid-fee-locker.md) -- Fee Locker +- [../contracts/liquid-lp-locker.md](../contracts/liquid-lp-locker.md) -- LP Locker +- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming +- [../sdk/reward-management.md](../sdk/reward-management.md) -- SDK reward management diff --git a/rag/concepts/lp-positions.md b/rag/concepts/lp-positions.md new file mode 100644 index 0000000..82358e8 --- /dev/null +++ b/rag/concepts/lp-positions.md @@ -0,0 +1,190 @@ +# Concept: LP Positions + +How concentrated liquidity positions work in Liquid Protocol: multi-tranche layouts, tick math, position BPS, and market cap ranges. + +## What Are LP Positions? + +Liquid Protocol uses Uniswap V4 concentrated liquidity. Instead of spreading liquidity across all prices (like V2), liquidity is concentrated into specific price ranges called "positions" or "tranches." + +Each position is defined by: +- **tickLower:** The lower bound (lower market cap) +- **tickUpper:** The upper bound (higher market cap) +- **positionBps:** The percentage of pool supply allocated (in basis points, sum = 10000) + +## Why Multiple Positions? + +Different market cap ranges need different liquidity depths: + +- **Low cap ($20K-$500K):** Tokens spend most of their life here. Needs deep liquidity for smooth trading. +- **Mid cap ($500K-$10M):** Growth phase. Still needs significant liquidity. +- **High cap ($10M-$1B):** Aspirational range. Less liquidity needed since few tokens reach here. + +By allocating more supply to lower ranges, the protocol ensures: +- Better price execution for everyday trading +- Less slippage at common market caps +- Some liquidity at high caps for when tokens "moon" + +## Default Position Layouts + +### 5-Position "Liquid" Layout (SDK Default) + +This is the hardcoded default when no positions are specified: + +| # | Supply | Tick Range | MC Range (~$2000/ETH) | Notes | +|---|--------|-----------|----------------------|-------| +| 1 | 10% | -230,400 to -216,000 | $20K to $83K | Launch range | +| 2 | 50% | -216,000 to -155,000 | $83K to $37M | Core range (50%!) | +| 3 | 15% | -202,000 to -155,000 | $338K to $37M | Overlaps with P2 | +| 4 | 20% | -155,000 to -120,000 | $37M to $1.2B | High cap | +| 5 | 5% | -141,000 to -120,000 | $151M to $1.2B | Overlaps with P4 | + +Positions 2-3 and 4-5 overlap, creating deeper liquidity in their intersection zones. + +```typescript +import { POOL_POSITIONS } from "liquid-sdk"; +const layout = POOL_POSITIONS.Liquid; +``` + +### 3-Tranche Default (via createDefaultPositions) + +Generated dynamically based on current ETH price: + +| # | Supply | USD Range | +|---|--------|-----------| +| 1 | 40% | Starting to $500K | +| 2 | 50% | $500K to $10M | +| 3 | 10% | $10M to $1B | + +```typescript +import { createDefaultPositions } from "liquid-sdk"; +const positions = createDefaultPositions(20_000, 2070); // $20K start, $2070/ETH +``` + +### Single Position (Standard) + +100% of supply in one wide range: + +```typescript +import { POOL_POSITIONS } from "liquid-sdk"; +const standard = POOL_POSITIONS.Standard; +// { tickLower: -230400, tickUpper: -120000, positionBps: 10000 } +``` + +## Position BPS + +BPS (basis points) determine how the pool supply is split across positions: + +``` +Total pool supply = TOKEN_SUPPLY - extensionAllocations +Pool supply distributed per positionBps: + Position 1: poolSupply * positionBps[0] / 10000 + Position 2: poolSupply * positionBps[1] / 10000 + ... +``` + +**Rules:** +- BPS must sum to exactly 10000 (100%) +- Minimum 1 position, maximum 7 +- Each position creates a Uniswap V4 LP NFT held by the LP Locker + +## Market Cap to Tick Conversion + +The relationship between market cap and Uniswap V4 ticks: + +``` +Total supply = 100,000,000,000 (100 billion) +Price per token = marketCapETH / totalSupply +Tick = floor(log(price) / log(1.0001) / tickSpacing) * tickSpacing +``` + +See [tick-math.md](tick-math.md) for detailed formulas and worked examples. + +### Quick Reference Table + +| Market Cap (ETH) | Market Cap (~$2000/ETH) | Tick (spacing=200) | +|------------------|------------------------|---------------------| +| 10 | $20,000 | -230,400 | +| 100 | $200,000 | -207,200 | +| 250 | $500,000 | -198,600 | +| 1,000 | $2,000,000 | -184,400 | +| 5,000 | $10,000,000 | -168,600 | +| 50,000 | $100,000,000 | -145,400 | +| 250,000 | $500,000,000 | -129,000 | +| 500,000 | $1,000,000,000 | -122,200 | + +## Building Custom Positions + +### From USD Market Caps + +```typescript +import { createPositionsUSD } from "liquid-sdk"; + +const positions = createPositionsUSD(20_000, 2070, [ + { upperMarketCapUSD: 100_000, supplyPct: 30 }, + { upperMarketCapUSD: 1_000_000, supplyPct: 40 }, + { upperMarketCapUSD: 100_000_000, supplyPct: 30 }, +]); + +await sdk.deployToken({ + ...positions, + tickIfToken0IsLiquid: positions.tickLower[0], +}); +``` + +### From ETH Market Caps + +```typescript +import { createPositions } from "liquid-sdk"; + +const positions = createPositions(10, [ + { upperMarketCapETH: 100, supplyPct: 40 }, + { upperMarketCapETH: 5000, supplyPct: 50 }, + { upperMarketCapETH: 500000, supplyPct: 10 }, +]); +``` + +### Describing Positions + +```typescript +import { describePositions } from "liquid-sdk"; + +const desc = describePositions(positions, 2070); +for (const p of desc) { + console.log(`P${p.index + 1}: ${p.supplyPct}% | ` + + `$${p.marketCapLowerUSD?.toFixed(0)} - $${p.marketCapUpperUSD?.toFixed(0)}`); +} +``` + +## How Concentrated Liquidity Works + +In a concentrated liquidity position: +- Liquidity is only active when the current price is within the tick range +- Tokens earn fees only while active +- As price moves through the range, the position converts from one token to the other + +For a Liquid token (token/WETH pair): +- At launch (low tick): position is mostly token +- As price rises (tick increases): token converts to WETH through trades +- At upper tick: position is fully WETH (all token sold) + +This means: +- Lower positions "sell" token supply as the price rises +- Higher positions provide liquidity for larger swaps at higher prices +- Overlapping positions create extra depth in their intersection + +## Validation Rules + +When deploying with custom positions: + +1. 1-7 positions allowed +2. `positionBps` must sum to 10000 +3. All ticks divisible by `tickSpacing` (default 200) +4. All `tickLower` values >= `tickIfToken0IsLiquid` +5. At least one position must have `tickLower == tickIfToken0IsLiquid` +6. Tranches must be ordered by ascending market cap (for builder functions) + +## See Also + +- [tick-math.md](tick-math.md) -- Detailed tick math formulas +- [fee-system.md](fee-system.md) -- How positions earn fees +- [../sdk/position-builder.md](../sdk/position-builder.md) -- SDK position builder guide diff --git a/rag/concepts/mev-protection.md b/rag/concepts/mev-protection.md new file mode 100644 index 0000000..4829653 --- /dev/null +++ b/rag/concepts/mev-protection.md @@ -0,0 +1,157 @@ +# Concept: MEV Protection + +Why MEV protection matters for token launches and how Liquid Protocol implements it. + +## The Problem + +When a new token is deployed with a Uniswap V4 pool: + +1. The deployment transaction appears in the mempool +2. Sniper bots detect it and prepare buy transactions +3. They buy large amounts at the lowest price in the next block +4. Retail traders arriving seconds later face inflated prices +5. Snipers dump on retail for profit + +This extracts value from both the deployer and the community. Without protection, the first seconds of a token's life are a race won by bots with the fastest infrastructure. + +## Solution: Taxing Early Trading + +Liquid Protocol makes sniping unprofitable by applying extremely high fees during the first seconds after launch. Two modules are available: + +### Module 1: Sniper Auction V2 (Default) + +An auction-based system combining high fees with gas-price bidding. + +**Address:** `0x187e8627c02c58F31831953C1268e157d3BfCefd` + +**Mechanism:** +- Fee starts at **80%** and decays linearly to **40%** over 20 seconds +- Runs in **5 rounds**, every 2 blocks (~4 seconds per round) +- Bidders compete via gas price -- highest gas price wins each round +- Bid amount is encoded as: `(txGasPrice - gasPeg) * paymentPerGasUnit` +- Revenue from bids goes to protocol and LP holders + +**Why it works:** +- At 80% fee, a sniper needs a **5x price increase** just to break even +- The gas price auction ensures only one buyer per round (no sandwich attacks) +- Bid revenue compensates the protocol and LP holders for early trading risk + +**Timeline:** +``` +Deploy (block N) + | + 2 blocks + | +Round 1 (block N+2) -- Fee: ~80% + | + 2 blocks + | +Round 2 (block N+4) -- Fee: ~70% + | + 2 blocks + | +Round 3 (block N+6) -- Fee: ~60% + | + 2 blocks + | +Round 4 (block N+8) -- Fee: ~50% + | + 2 blocks + | +Round 5 (block N+10) -- Fee: ~40% + | +Normal trading at 1% LP fee +``` + +### Module 2: MevDescendingFees + +A simpler time-based approach without auction mechanics. + +**Address:** `0x8D6B080e48756A99F3893491D556B5d6907b6910` + +**Mechanism:** +- Fee starts at up to **80%** and decays **parabolically** (quadratic curve) +- Maximum decay duration: **2 minutes** +- No auction rounds or gas price bidding +- First-come, first-served -- anyone can swap, but at the high fee + +**Why it works:** +- The parabolic curve means fees drop fast at first, then slow down +- This gives a natural price discovery window +- Simpler to understand and configure than the auction + +**Comparison:** + +| Aspect | Sniper Auction | Descending Fees | +|--------|---------------|-----------------| +| Complexity | High | Low | +| Competition | Gas price bidding | First-come | +| Extra revenue | Yes (bid ETH) | No | +| Duration | ~20s (5 rounds) | Up to 2 min | +| Decay curve | Linear | Parabolic | +| Default | Yes | No | + +## Dev Buy: The Best Alternative + +If you are the token deployer and want to acquire tokens early, use the `devBuy` parameter: + +```typescript +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", + devBuy: { + ethAmount: parseEther("0.01"), + recipient: account.address, + }, +}); +``` + +The dev buy executes in the **same transaction** as deployment: +- Uses normal 1% LP fee (NOT auction fees) +- Atomic -- no front-running risk +- Cheapest way to get tokens at launch + +## MEV Block Delay + +Both modules interact with the MEV block delay system. During the delay: +- `collectRewards()` may revert with `ManagerLocked` +- Use `collectRewardsWithoutUnlock()` as an alternative + +```typescript +const delay = await sdk.getMevBlockDelay(); +const unlockTime = await sdk.getPoolUnlockTime(poolId); + +const now = BigInt(Math.floor(Date.now() / 1000)); +if (now < unlockTime) { + console.log("Pool locked, use collectRewardsWithoutUnlock"); +} +``` + +## Custom MEV Configuration + +```typescript +import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; + +// Custom sniper auction: 60% to 20% over 30s +const result = await sdk.deployToken({ + mevModule: ADDRESSES.SNIPER_AUCTION_V2, + mevModuleData: encodeSniperAuctionData({ + startingFee: 600_000, // 60% + endingFee: 200_000, // 20% + secondsToDecay: 30, // 30 seconds + }), + // ...other params +}); + +// Or use descending fees instead +const result2 = await sdk.deployToken({ + mevModule: ADDRESSES.MEV_DESCENDING_FEES, + // ...other params +}); +``` + +## See Also + +- [../contracts/liquid-mev-protection.md](../contracts/liquid-mev-protection.md) -- Contract details +- [../sdk/sniper-auction.md](../sdk/sniper-auction.md) -- SDK auction guide +- [token-lifecycle.md](token-lifecycle.md) -- Full lifecycle including MEV phase diff --git a/rag/concepts/tick-math.md b/rag/concepts/tick-math.md new file mode 100644 index 0000000..c32840c --- /dev/null +++ b/rag/concepts/tick-math.md @@ -0,0 +1,230 @@ +# Concept: Tick Math + +Detailed explanation of how Uniswap V4 ticks relate to token prices and market caps in Liquid Protocol. Includes formulas, constants, and worked examples. + +## Core Formulas + +### Constants + +``` +Total supply = 100,000,000,000 (100 billion, 1e11) +Decimals = 18 +Tick base = 1.0001 +Tick spacing = 200 (default) +LOG_BASE = ln(1.0001) = 0.000099995... +``` + +### Market Cap to Price + +``` +price = marketCapETH / totalSupply +price = marketCapETH / 1e11 +``` + +Price is in WETH per token (how much WETH one token costs). + +### Price to Tick + +``` +rawTick = ln(price) / ln(1.0001) +tick = floor(rawTick / tickSpacing) * tickSpacing +``` + +The floor + alignment ensures ticks are always multiples of `tickSpacing`. + +### Tick to Price + +``` +price = 1.0001^tick +``` + +### Tick to Market Cap + +``` +marketCapETH = 1.0001^tick * totalSupply +marketCapETH = 1.0001^tick * 1e11 +``` + +### USD Conversion + +``` +marketCapUSD = marketCapETH * ethPriceUSD +tickFromUSD = tickFromETH(marketCapUSD / ethPriceUSD) +``` + +## SDK Helper Functions + +```typescript +import { + getTickFromMarketCapETH, + getTickFromMarketCapUSD, + marketCapFromTickETH, + marketCapFromTickUSD, +} from "liquid-sdk"; + +// Market cap -> Tick +getTickFromMarketCapETH(10) // -230400 +getTickFromMarketCapETH(200) // -200200 +getTickFromMarketCapUSD(500_000, 2070) // -198600 (approx) +getTickFromMarketCapUSD(10_000_000, 2070) // -168600 (approx) + +// Tick -> Market cap +marketCapFromTickETH(-230400) // ~9.99 ETH +marketCapFromTickUSD(-230400, 2070) // ~$20,680 +marketCapFromTickETH(-120000) // ~603,000 ETH +marketCapFromTickUSD(-120000, 2070) // ~$1.25B +``` + +## Worked Examples + +### Example 1: Default Starting Tick + +**Given:** Starting market cap = 10 ETH, tick spacing = 200 + +``` +price = 10 / 1e11 = 1e-10 +rawTick = ln(1e-10) / ln(1.0001) + = -23.0259 / 0.000099995 + = -230,268.5 +tick = floor(-230268.5 / 200) * 200 + = floor(-1151.34) * 200 + = -1152 * 200 + = -230,400 +``` + +**Result:** tick = -230,400 (the SDK default `DEFAULTS.TICK_IF_TOKEN0_IS_LIQUID`) + +### Example 2: $500K Market Cap at $2070/ETH + +``` +marketCapETH = 500,000 / 2,070 = 241.55 ETH +price = 241.55 / 1e11 = 2.4155e-9 +rawTick = ln(2.4155e-9) / ln(1.0001) = -198,549 +tick = floor(-198549 / 200) * 200 = -198,600 +``` + +**Result:** tick = -198,600 + +### Example 3: $10M Market Cap at $2070/ETH + +``` +marketCapETH = 10,000,000 / 2,070 = 4,830.9 ETH +price = 4830.9 / 1e11 = 4.831e-8 +rawTick = ln(4.831e-8) / ln(1.0001) = -168,537 +tick = floor(-168537 / 200) * 200 = -168,600 +``` + +**Result:** tick = -168,600 + +### Example 4: $1B Market Cap at $2070/ETH + +``` +marketCapETH = 1,000,000,000 / 2,070 = 483,092 ETH +price = 483092 / 1e11 = 4.831e-6 +rawTick = ln(4.831e-6) / ln(1.0001) = -122,337 +tick = floor(-122337 / 200) * 200 = -122,400 +``` + +**Result:** tick = -122,400 + +## Reference Table + +| Market Cap (ETH) | Market Cap (~$2000/ETH) | Tick | Price (WETH/token) | +|------------------|------------------------|------|-------------------| +| 1 | $2,000 | -253,400 | 1e-11 | +| 10 | $20,000 | -230,400 | 1e-10 | +| 100 | $200,000 | -207,200 | 1e-9 | +| 250 | $500,000 | -198,000 | 2.5e-9 | +| 1,000 | $2,000,000 | -184,400 | 1e-8 | +| 5,000 | $10,000,000 | -168,200 | 5e-8 | +| 10,000 | $20,000,000 | -161,200 | 1e-7 | +| 50,000 | $100,000,000 | -145,000 | 5e-7 | +| 100,000 | $200,000,000 | -138,000 | 1e-6 | +| 250,000 | $500,000,000 | -128,800 | 2.5e-6 | +| 500,000 | $1,000,000,000 | -122,000 | 5e-6 | +| 1,000,000 | $2,000,000,000 | -115,000 | 1e-5 | + +*Note: Exact tick values depend on tickSpacing alignment.* + +## Tick Spacing + +Ticks must be multiples of `tickSpacing` (default: 200). This means: + +- Prices can only exist at discrete points: 1.0001^0, 1.0001^200, 1.0001^400, ... +- Each "step" represents a ~2% price change (1.0001^200 = 1.0202) +- Finer tick spacing (e.g., 60) allows tighter ranges but costs more gas +- Coarser tick spacing (e.g., 200) is more gas-efficient but less precise + +## Why Ticks Are Negative + +In Uniswap V4, prices are expressed as `currency0 / currency1`. Since: +- currency0 = WETH (numerically lower address) +- currency1 = liquid token (numerically higher address) + +The price represents "WETH per token." Since tokens have 100B supply, the price per token is extremely small (e.g., 1e-10), resulting in a very negative tick. + +As the token's market cap increases: +- Price per token increases +- Tick moves toward zero (becomes less negative) + +## Position Building with Tick Math + +```typescript +import { createPositionsUSD, describePositions } from "liquid-sdk"; + +// Define positions using USD market caps +const positions = createPositionsUSD(20_000, 2070, [ + { upperMarketCapUSD: 500_000, supplyPct: 40 }, + { upperMarketCapUSD: 10_000_000, supplyPct: 50 }, + { upperMarketCapUSD: 1_000_000_000, supplyPct: 10 }, +]); + +// See what was generated +const desc = describePositions(positions, 2070); +desc.forEach(p => { + console.log(`Position ${p.index}: ${p.supplyPct}%`); + console.log(` Ticks: ${p.tickLower} to ${p.tickUpper}`); + console.log(` ETH: ${p.marketCapLowerETH.toFixed(2)} to ${p.marketCapUpperETH.toFixed(2)}`); + console.log(` USD: $${p.marketCapLowerUSD?.toFixed(0)} to $${p.marketCapUpperUSD?.toFixed(0)}`); +}); +``` + +## Stablecoin Pairing + +For tokens paired with stablecoins instead of WETH: + +```typescript +import { getTickFromMarketCapStable } from "liquid-sdk"; + +// USDC (6 decimals) +const tick = getTickFromMarketCapStable(500_000, 6); + +// DAI (18 decimals) +const tick = getTickFromMarketCapStable(500_000, 18); +``` + +## Implementation + +The tick math utilities are in `src/utils/tick-math.ts`: + +```typescript +const LOG_BASE = Math.log(1.0001); +const TOTAL_SUPPLY = 1e11; + +function getTickFromMarketCapETH(marketCapETH, tickSpacing = 200) { + const price = marketCapETH / TOTAL_SUPPLY; + const rawTick = Math.log(price) / LOG_BASE; + return Math.floor(rawTick / tickSpacing) * tickSpacing; +} + +function marketCapFromTickETH(tick) { + const price = Math.pow(1.0001, tick); + return price * TOTAL_SUPPLY; +} +``` + +## See Also + +- [lp-positions.md](lp-positions.md) -- LP position concepts +- [../sdk/position-builder.md](../sdk/position-builder.md) -- SDK position builder +- [fee-system.md](fee-system.md) -- How positions earn fees diff --git a/rag/concepts/token-lifecycle.md b/rag/concepts/token-lifecycle.md new file mode 100644 index 0000000..704e665 --- /dev/null +++ b/rag/concepts/token-lifecycle.md @@ -0,0 +1,200 @@ +# Concept: Token Lifecycle + +End-to-end lifecycle of a Liquid Protocol token, from deployment through trading, fee accrual, and reward distribution. + +## Phase 1: Deployment + +A single `sdk.deployToken()` call triggers an atomic on-chain transaction: + +``` +1. Factory deploys LiquidToken (ERC-20) + - 100 billion supply, 18 decimals + - ERC20 + Permit + Votes + Burnable + IERC7802 + - CREATE2 deterministic address from salt + +2. Factory initializes Uniswap V4 pool + - Paired with WETH + - Starting tick determines initial market cap + - Fee hook (static or dynamic) installed + - Pool key: { WETH, token, 0x800000, 200, hook } + +3. Factory locks LP permanently + - Token supply (minus extensions) split into positions + - Positions created in LP Locker + - Reward recipients and BPS splits configured + - Fee conversion preference set (default: all to ETH) + +4. Factory activates MEV protection + - Sniper Auction V2: 80% to 40% fee decay over 20s + - Or MevDescendingFees: parabolic decay up to 2 min + +5. Factory executes extensions (if any) + - Vault: locks tokens with lockup + vesting + - Airdrop: allocates tokens for merkle claims + - Dev Buy: swaps ETH for tokens at launch + - Presale: allocates tokens for pre-sale + +6. Factory emits TokenCreated event + - Contains all deployment metadata + - Indexed by token address for fast lookup +``` + +**Result:** Token is live with a Uniswap V4 pool, locked LP, and MEV protection active. + +## Phase 2: MEV Protection Window (~20 seconds) + +Immediately after deployment, the MEV protection module is active: + +``` +Block N: Token deployed +Block N+2: First auction round (Sniper Auction) +Block N+4: Second auction round +Block N+6: Third auction round +Block N+8: Fourth auction round +Block N+10: Fifth and final auction round + +Fee decay: 80% at t=0 -> 40% at t=20s (linear) +``` + +During this window: +- Swaps are taxed at 80-40% MEV fee ON TOP of the regular LP fee +- Only auction winners can swap (for Sniper Auction module) +- `collectRewards()` may revert with `ManagerLocked` +- Dev buy (if configured) executes at normal 1% fee, not auction fee + +## Phase 3: Normal Trading + +After the MEV protection window ends: +- Standard LP fees apply (default: 1% buy + 1% sell) +- Anyone can trade via Uniswap V4 Universal Router +- No auction mechanics -- first-come, first-served +- Pool operates as a standard Uniswap V4 concentrated liquidity pool + +### Trade Flow + +``` +User submits swap via Universal Router + | + v +Uniswap V4 PoolManager routes to hook + | + v +Hook.beforeSwap(): + - Calculates LP fee (1% or dynamic) + - Applies protocol fee (20% of LP fee) + | + v +PoolManager executes swap + | + v +Hook.afterSwap(): + - Collects protocol fee portion +``` + +## Phase 4: Fee Accrual + +Fees accumulate in the LP positions held by the LP Locker: + +``` +Trading generates LP fees + |-- 20% of LP fee -> Protocol (team fee recipient) + |-- 80% of LP fee -> LP position (accrues in pool) + | + v +Fees sit in LP positions until collected +``` + +## Phase 5: Fee Collection and Distribution + +Anyone can trigger fee collection: + +``` +sdk.collectRewards(tokenAddress) + | + v +LP Locker collects fees from all positions + | + v +Converts fees to preferred token (ETH by default) + | + v +Splits by reward BPS: + - Recipient A: 70% -> FeeLocker.storeFees(A, WETH, amount) + - Recipient B: 30% -> FeeLocker.storeFees(B, WETH, amount) +``` + +## Phase 6: Fee Claiming + +Recipients withdraw their accumulated fees: + +``` +sdk.claimFees(ownerAddress, tokenAddress) + | + v +FeeLocker transfers WETH to ownerAddress +``` + +## Phase 7: Extension Lifecycle + +### Vault Vesting + +``` +Deploy ------> Lockup ends ------> Vesting ends + | | + |-- Linear vesting ---| + | claim() available | +``` + +### Airdrop Claims + +``` +Deploy --> Merkle root set --> Lockup ends --> Vesting ends + | | + |-- Claims open --| + | | + Admin can reclaim unclaimed after adminClaimTime +``` + +## Phase 8: Ongoing Operations + +After initial setup, these operations continue indefinitely: + +| Operation | Who | Frequency | +|-----------|-----|-----------| +| Trading | Anyone | Continuous | +| Fee collection | Anyone | Periodic (as needed) | +| Fee claiming | Reward recipients | When fees accumulate | +| Vault claiming | Vault admin | As tokens vest | +| Airdrop claiming | Recipients | After lockup ends | +| Metadata updates | Token admin | As needed | +| Recipient updates | Reward admins | As needed | + +## Key Invariants + +1. **LP is permanent** -- Locked liquidity can never be withdrawn +2. **Supply is fixed** -- 100B tokens, no minting after deploy +3. **BPS splits are immutable** -- Reward percentages cannot change +4. **Pool is standard Uniswap V4** -- Fully composable with V4 ecosystem +5. **Fees convert to ETH** -- Default behavior, configurable per recipient + +## Contract Flow Diagram + +``` + Liquid.sol (Factory) + | + +------------+-------------+ + | | | + LiquidToken Hook (V2) LP Locker + (ERC-20) | | | + | | +-- FeeLocker + | | | + MevModule Pool claimFees() + (Auction) Extension +``` + +## See Also + +- [fee-system.md](fee-system.md) -- Detailed fee system +- [mev-protection.md](mev-protection.md) -- MEV protection details +- [lp-positions.md](lp-positions.md) -- LP position concepts +- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- SDK deployment guide diff --git a/rag/contracts/liquid-airdrop.md b/rag/contracts/liquid-airdrop.md new file mode 100644 index 0000000..52adc1d --- /dev/null +++ b/rag/contracts/liquid-airdrop.md @@ -0,0 +1,123 @@ +# LiquidAirdropV2 + +Merkle-based token airdrop extension with mutable root, admin controls, lockup/vesting, and admin reclaim. + +## Contract Details + +- **Address:** `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` +- **SDK Constant:** `ADDRESSES.AIRDROP_V2` +- **Type:** Extension (receives tokens via `receiveTokens`) + +## How It Works + +1. **At deployment:** Factory transfers tokens (per `extensionBps`) to the Airdrop contract +2. **Merkle root set:** Admin sets the merkle root defining who can claim and how much +3. **Lockup period:** Claims are blocked until `lockupEndTime` +4. **Vesting period:** After lockup, tokens vest linearly until `vestingEndTime` +5. **Claiming:** Recipients submit merkle proofs to claim their vested allocation +6. **Admin reclaim:** After `adminClaimTime`, admin can reclaim unclaimed tokens + +## Airdrop Info Structure + +```typescript +interface AirdropInfo { + admin: Address; // Airdrop administrator + merkleRoot: Hex; // Merkle root for claim verification + totalSupply: bigint; // Total tokens allocated to airdrop + totalClaimed: bigint; // Tokens claimed so far + lockupEndTime: bigint; // When claims can begin + vestingEndTime: bigint; // When vesting fully completes + adminClaimTime: bigint; // When admin can reclaim unclaimed + adminClaimed: boolean; // Whether admin has reclaimed +} +``` + +## Key Features + +### Mutable Merkle Root + +The admin can update the merkle root after deployment. This allows: +- Correcting errors in the original distribution +- Adding new recipients +- Adjusting allocations before claims begin + +### Lockup + Vesting + +Same model as the Vault: +- **Lockup:** No claims until `lockupEndTime` +- **Vesting:** Linear vesting from `lockupEndTime` to `vestingEndTime` +- Each recipient's allocation vests independently based on their total amount + +### Admin Reclaim + +After `adminClaimTime`, the admin can reclaim any unclaimed tokens. This prevents tokens from being permanently locked if recipients never claim. + +## SDK Methods + +### Check airdrop state + +```typescript +const info = await sdk.getAirdropInfo(tokenAddress); +console.log("Merkle root:", info.merkleRoot); +console.log("Total supply:", info.totalSupply); +console.log("Total claimed:", info.totalClaimed); +console.log("Lockup ends:", new Date(Number(info.lockupEndTime) * 1000)); +console.log("Vesting ends:", new Date(Number(info.vestingEndTime) * 1000)); +console.log("Admin:", info.admin); +console.log("Admin claimed:", info.adminClaimed); +``` + +### Check claimable for a recipient + +```typescript +const claimable = await sdk.getAirdropClaimable( + tokenAddress, + recipientAddress, + allocatedAmount, // bigint -- total allocation for this recipient (18 decimals) +); +``` + +### Claim airdrop + +```typescript +const txHash = await sdk.claimAirdrop( + tokenAddress, + recipientAddress, + allocatedAmount, // bigint -- must match the merkle leaf + merkleProof, // Hex[] -- generated off-chain from the merkle tree +); +``` + +## Merkle Proof Generation + +Merkle proofs must be generated off-chain from the original airdrop tree. The SDK does not include merkle tree generation -- use a library like `merkletreejs` or `@openzeppelin/merkle-tree`: + +```typescript +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; + +// Build the tree +const values = [ + ["0xRecipient1", "1000000000000000000000"], // 1000 tokens + ["0xRecipient2", "500000000000000000000"], // 500 tokens +]; + +const tree = StandardMerkleTree.of(values, ["address", "uint256"]); +const root = tree.root; // Set this as merkleRoot + +// Get proof for a specific recipient +const proof = tree.getProof(["0xRecipient1", "1000000000000000000000"]); +``` + +## Common Errors + +| Error | Cause | Resolution | +|-------|-------|------------| +| `AlreadyClaimed` | Recipient already claimed their allocation | Check `getAirdropClaimable()` first | +| `LockupNotEnded` | Claims not yet open | Wait for `lockupEndTime` | +| Invalid proof | Proof doesn't match the merkle root | Regenerate proof from the tree | +| `Unauthorized` | Only admin can update root or reclaim | Use admin wallet | + +## See Also + +- [../sdk/airdrop-system.md](../sdk/airdrop-system.md) -- SDK airdrop guide +- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview diff --git a/rag/contracts/liquid-extensions.md b/rag/contracts/liquid-extensions.md new file mode 100644 index 0000000..5c36a60 --- /dev/null +++ b/rag/contracts/liquid-extensions.md @@ -0,0 +1,152 @@ +# Extension System + +Extensions are modular contracts that receive a portion of the token supply at deployment. They enable pre-launch token distribution (vesting, airdrops, dev buys, presales) without modifying the core factory logic. + +## How Extensions Work + +1. The deployer includes `ExtensionConfig[]` in the deployment parameters +2. During `deployToken()`, the factory allocates `extensionBps / 10000 * TOKEN_SUPPLY` to each extension +3. The factory calls `extension.receiveTokens(token, amount, extensionData)` for each +4. Extensions hold and distribute tokens according to their logic + +### Constraints + +| Constraint | Value | Description | +|------------|-------|-------------| +| Max extensions | 10 | Per token deployment | +| Max total BPS | 9000 | 90% of total supply across all extensions | +| Remaining supply | Goes to LP | Locked in positions via LP Locker | + +## Extension Allowlist + +Extensions must be explicitly approved by the Liquid Protocol admin. The allowlist is managed by `LiquidPoolExtensionAllowlist` (`0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa`), owned by the Gnosis Safe multisig. + +### Approval Process + +New extensions require ALL of the following: + +1. **Full third-party audit** -- By a recognized firm +2. **Uniswap alignment** -- Approval from a Uniswap V4 core contributor +3. **Internal review** -- Liquid Protocol engineering lead must approve +4. **Admin Safe approval** -- Multisig transaction through the Gnosis Safe + +**Current status:** No plans to add new extensions. Contact `slaterg@mog.capital` and `admin@mog.capital` to apply. + +### Checking Allowlist Status + +```typescript +const isEnabled = await sdk.isExtensionEnabled(extensionAddress); +``` + +## Available Extensions + +### 1. Dev Buy (V4) -- LiquidUniv4EthDevBuy + +**Address:** `0x5934097864dC487D21A7B4e4EEe201A39ceF728D` + +Buys tokens with ETH through the Uniswap V4 pool in the same transaction as deployment. This is the recommended way to acquire tokens at launch because: + +- Uses normal 1% LP fees (NOT auction fees) +- Atomic -- no front-running risk +- Tokens delivered to specified recipient immediately + +```typescript +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", + devBuy: { + ethAmount: parseEther("0.01"), // ETH to spend + recipient: account.address, // Who gets tokens + }, +}); +``` + +The SDK automatically builds the extension config and appends it. The `ethAmount` is sent as `msg.value`. + +### 2. Dev Buy (V3) -- LiquidUniv3EthDevBuy + +**Address:** `0x376028cfb6b9A120E24Aa14c3FAc4205179c0025` + +Legacy extension for buying through Uniswap V3 pools. Same concept as V4 dev buy but routes through V3. Not commonly used for new deployments. + +### 3. Vault -- LiquidVault + +**Address:** `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` + +Locks tokens with a lockup period followed by linear vesting. See [liquid-vault.md](liquid-vault.md) for full details. + +**Use cases:** +- Team token lockup +- Investor vesting schedules +- Treasury management + +### 4. Airdrop V2 -- LiquidAirdropV2 + +**Address:** `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` + +Merkle-based token distribution with lockup/vesting and admin controls. See [liquid-airdrop.md](liquid-airdrop.md) for full details. + +**Use cases:** +- Community airdrops +- Retroactive rewards +- Ecosystem grants + +### 5. Presale (ETH to Creator) -- LiquidPresaleEthToCreator + +**Address:** `0x3bca63EcB49d5f917092d10fA879Fdb422740163` + +Presale where participants send ETH and receive tokens. The ETH is forwarded directly to the token creator (deployer). + +**Use cases:** +- Pre-launch fundraising +- Community pre-sales + +### 6. Presale (Allowlist) -- LiquidPresaleAllowlist + +**Address:** `0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B` + +Allowlist-gated presale. Only addresses on the allowlist can participate. Provides a controlled pre-sale mechanism. + +**Use cases:** +- VIP/early supporter pre-sales +- KYC-gated sales +- Partner allocations + +## Extension Config Structure + +```typescript +interface ExtensionConfig { + extension: Address; // Contract address (must be allowlisted) + msgValue: bigint; // ETH to send (usually 0n, non-zero for dev buy) + extensionBps: number; // Supply allocation (0-9000 BPS) + extensionData: Hex; // ABI-encoded init data (extension-specific) +} +``` + +## Building Extension Configs Manually + +```typescript +// Dev buy (the SDK does this automatically when devBuy is passed) +const devBuyExt: ExtensionConfig = { + extension: ADDRESSES.UNIV4_ETH_DEV_BUY, + msgValue: parseEther("0.01"), // ETH for the swap + extensionBps: 0, // Dev buy uses 0 BPS (buys from pool) + extensionData: encodeAbiParameters( + [{ type: "address" }], + [recipientAddress], + ), +}; + +// Or use the SDK helper +const devBuyExt = sdk.buildDevBuyExtension({ + ethAmount: parseEther("0.01"), + recipient: recipientAddress, +}); +``` + +## See Also + +- [liquid-vault.md](liquid-vault.md) -- Vault extension details +- [liquid-airdrop.md](liquid-airdrop.md) -- Airdrop extension details +- [../concepts/extension-system.md](../concepts/extension-system.md) -- Conceptual overview +- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with extensions diff --git a/rag/contracts/liquid-factory.md b/rag/contracts/liquid-factory.md new file mode 100644 index 0000000..e30c57a --- /dev/null +++ b/rag/contracts/liquid-factory.md @@ -0,0 +1,140 @@ +# Liquid Factory (Liquid.sol) + +The Liquid factory is the core orchestrator of the Liquid Protocol. It deploys ERC-20 tokens with Uniswap V4 liquidity pools in a single atomic transaction. + +## Contract Details + +- **Address:** `0x04F1a284168743759BE6554f607a10CEBdB77760` +- **Chain:** Base (8453) +- **SDK Constant:** `ADDRESSES.FACTORY` +- **Solidity:** `^0.8.28`, optimizer 20,000 runs, EVM target Cancun +- **Inherits:** `OwnerAdmins`, `ReentrancyGuard`, `ILiquid` +- **Owner:** Gnosis Safe `0x872c561f699B42977c093F0eD8b4C9a431280c6c` + +## Protocol Constants + +| Constant | Value | Description | +|----------|-------|-------------| +| `TOKEN_SUPPLY` | `100_000_000_000e18` | 100 billion tokens, 18 decimals | +| `BPS` | `10_000` | Basis points denominator | +| `MAX_EXTENSIONS` | `10` | Maximum extensions per token | +| `MAX_EXTENSION_BPS` | `9000` | Max 90% of supply to extensions | + +## Key Functions + +### `deployToken(DeploymentConfig config)` + +The primary entry point. Orchestrates the full deployment flow: + +1. **Deploy token** -- Uses `LiquidDeployer` library with CREATE2 for deterministic addresses +2. **Initialize Uniswap V4 pool** -- Calls the hook to set up the pool with the provided fee configuration +3. **Lock LP** -- Transfers all liquidity to the LP Locker contract (permanent, non-reversible) +4. **Set up MEV protection** -- Registers the MEV module (sniper auction or descending fees) with the hook +5. **Execute extensions** -- Calls each extension (vault, airdrop, dev buy) in sequence, allocating token supply per `extensionBps` +6. **Emit `TokenCreated` event** -- Contains all deployment data for indexing + +**Input struct (DeploymentConfig):** +``` +DeploymentConfig { + tokenConfig: TokenConfig // name, symbol, image, metadata, context, salt, admin + poolConfig: PoolConfig // hook, pairedToken, tick, tickSpacing, poolData + lockerConfig: LockerConfig // locker, rewards, positions + mevModuleConfig: MevModuleConfig // MEV module + data + extensionConfigs: ExtensionConfig[] // vault, airdrop, dev buy, etc. +} +``` + +### `tokenDeploymentInfo(address token) -> DeploymentInfo` + +Returns the stored deployment information for any token deployed by this factory. + +```typescript +// SDK equivalent +const info = await sdk.getDeploymentInfo(tokenAddress); +// info.token, info.hook, info.locker, info.extensions +``` + +### Module Management (Owner/Admin only) + +| Function | Description | +|----------|-------------| +| `setHook(address, bool)` | Enable/disable a hook contract | +| `setLocker(address, address, bool)` | Enable/disable a locker for a specific hook | +| `setExtension(address, bool)` | Enable/disable an extension | +| `setMevModule(address, bool)` | Enable/disable an MEV module | +| `setDeprecated(bool)` | Pause/unpause the factory | +| `setTeamFeeRecipient(address)` | Set protocol fee recipient | +| `claimTeamFees(address token)` | Claim accumulated protocol fees | + +### Factory Status Checks (SDK) + +```typescript +await sdk.isFactoryDeprecated(); // Is factory still active? +await sdk.isLockerEnabled(locker, hook); // Is locker approved for hook? +await sdk.isExtensionEnabled(extension); // Is extension on allowlist? +``` + +## Events + +### `TokenCreated` + +Emitted on every successful token deployment. Contains all information needed to index and interact with the token. + +| Field | Type | Description | +|-------|------|-------------| +| `msgSender` | `address` | Deployer address | +| `tokenAddress` | `address` | Deployed ERC-20 contract | +| `tokenAdmin` | `address` | Admin who can update metadata | +| `tokenImage` | `string` | Image URL | +| `tokenName` | `string` | Token name | +| `tokenSymbol` | `string` | Token symbol | +| `tokenMetadata` | `string` | JSON metadata | +| `tokenContext` | `string` | JSON deployment context | +| `startingTick` | `int24` | Initial pool tick | +| `poolHook` | `address` | Hook contract used | +| `poolId` | `bytes32` | Uniswap V4 pool ID | +| `pairedToken` | `address` | Quote token (WETH) | +| `locker` | `address` | LP locker contract | +| `mevModule` | `address` | MEV protection module | +| `extensionsSupply` | `uint256` | Total supply allocated to extensions | +| `extensions` | `address[]` | Extension contracts used | + +## Deployment Flow Diagram + +``` +sdk.deployToken(params) + | + v +SDK builds DeploymentConfig with defaults + | + v +Factory.deployToken(config) + |-- 1. LiquidDeployer.deploy(salt) --> new LiquidToken (100B supply) + |-- 2. Hook.initializePool(poolKey, startingTick, poolData) + |-- 3. token.approve(locker, remaining supply) + |-- 4. locker.lockLiquidity(positions, rewards) + |-- 5. For each extension: + | |-- token.approve(extension, extensionBps * supply / BPS) + | |-- extension.receiveTokens(token, amount, data) + |-- 6. mevModule.register(poolId, mevData) + |-- 7. emit TokenCreated(...) + | + v +SDK parses TokenCreated event from receipt + | + v +Returns { tokenAddress, txHash, event } +``` + +## Security + +- Factory is protected by `ReentrancyGuard` on `deployToken` +- Only enabled hooks/lockers/extensions/MEV modules can be used +- Forked from Clanker v4, audited by 0xMacro (A-3) and Cantina +- Owner is a Gnosis Safe multisig -- no single party can modify factory configuration + +## See Also + +- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- SDK deployment guide +- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full lifecycle overview +- [liquid-token.md](liquid-token.md) -- The ERC-20 token contract diff --git a/rag/contracts/liquid-fee-locker.md b/rag/contracts/liquid-fee-locker.md new file mode 100644 index 0000000..25badbd --- /dev/null +++ b/rag/contracts/liquid-fee-locker.md @@ -0,0 +1,102 @@ +# LiquidFeeLocker + +The Fee Locker is an escrow contract that stores accumulated LP fees and allows fee owners to claim them. It acts as the central fee distribution point in the Liquid Protocol. + +## Contract Details + +- **Address:** `0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF` +- **SDK Constant:** `ADDRESSES.FEE_LOCKER` +- **Inherits:** `ILiquidFeeLocker`, `ReentrancyGuard`, `Ownable` +- **Owner:** Gnosis Safe + +## How It Works + +1. **Allowlisted depositors** (LP Locker, Sniper Auction) call `storeFees()` to deposit tokens for a specific fee owner +2. **Fees accumulate** in a `mapping(feeOwner => mapping(token => balance))` ledger +3. **Anyone can call `claim()`** on behalf of a fee owner to transfer their accumulated balance + +The Fee Locker uses balance deltas (checking `balanceOf` before and after transfer) to support fee-on-transfer tokens. + +## Key Functions + +### `storeFees(address feeOwner, address token, uint256 amount)` + +Deposits fees for a specific owner. Only callable by allowlisted depositors. + +- Transfers `amount` of `token` from `msg.sender` to the Fee Locker +- Uses `SafeERC20.safeTransferFrom` for safe transfer +- Records the actual received amount (supports fee-on-transfer tokens) +- Emits `StoreTokens(depositor, feeOwner, token, newBalance, amount)` + +### `claim(address feeOwner, address token)` + +Claims all accumulated fees for a fee owner. Callable by anyone (permissionless). + +- Reads the full balance for `(feeOwner, token)` +- Sets the balance to 0 +- Transfers the full amount to `feeOwner` +- Reverts with `NoFeesToClaim` if balance is 0 +- Emits `ClaimTokens(feeOwner, token, amount)` + +### `availableFees(address feeOwner, address token) -> uint256` + +Read-only. Returns the current claimable balance for a fee owner and token. + +### `addDepositor(address depositor)` + +Owner-only. Adds an address to the allowlist of approved depositors. + +## SDK Methods + +```typescript +// Check total unlocked fees +const available = await sdk.getAvailableFees(ownerAddress, tokenAddress); + +// Check claimable fees (same as available for Fee Locker) +const claimable = await sdk.getFeesToClaim(ownerAddress, tokenAddress); + +// Claim all fees +if (claimable > 0n) { + const txHash = await sdk.claimFees(ownerAddress, tokenAddress); +} +``` + +## Fee Flow + +``` +Trading Activity (Uniswap V4 pool) + | + v +Hook calculates LP fee (e.g., 1%) + |-- 20% -> Protocol (factory team fee) + |-- 80% -> LP position + | + v +LP Locker collects fees from positions + | + v +LP Locker Fee Conversion converts to preferred token (ETH) + | + v +LP Locker calls FeeLocker.storeFees(feeOwner, WETH, amount) + |-- Split by reward BPS: e.g., 70% to recipient A, 30% to recipient B + | + v +Fee recipients (or anyone on their behalf) call FeeLocker.claim() + | + v +WETH transferred to fee owner's wallet +``` + +## Security + +- Protected by `ReentrancyGuard` on both `storeFees` and `claim` +- Only allowlisted depositors can store fees (prevents unauthorized balance inflation) +- Uses `SafeERC20` for all transfers +- Balance delta tracking prevents fee-on-transfer token accounting issues + +## See Also + +- [liquid-lp-locker.md](liquid-lp-locker.md) -- LP Locker that deposits into Fee Locker +- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming guide +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system overview diff --git a/rag/contracts/liquid-hooks.md b/rag/contracts/liquid-hooks.md new file mode 100644 index 0000000..6e0106e --- /dev/null +++ b/rag/contracts/liquid-hooks.md @@ -0,0 +1,169 @@ +# Liquid Hook System + +The hook contracts are Uniswap V4 BaseHook implementations that control fee logic, MEV module integration, and pool lifecycle for Liquid Protocol pools. + +## Architecture + +``` +LiquidHookV2 (abstract base) + |-- LiquidHookStaticFeeV2 (fixed fees, default) + |-- LiquidHookDynamicFeeV2 (volatility-responsive fees) +``` + +## LiquidHookV2 (Base Contract) + +Abstract contract that all Liquid hooks inherit from. + +### Constants + +| Constant | Value | Description | +|----------|-------|-------------| +| `MAX_LP_FEE` | `100_000` | Max LP fee: 10% (100,000 / 1,000,000) | +| `MAX_MEV_LP_FEE` | `800_000` | Max MEV fee: 80% | +| `PROTOCOL_FEE_NUMERATOR` | `200_000` | 20% of LP fees go to protocol | +| `FEE_DENOMINATOR` | `1_000_000` | 100% in Uniswap V4 fee units | +| `MAX_MEV_MODULE_DELAY` | `2 minutes` | Max duration for MEV module effects | + +### Per-Pool State + +| Mapping | Type | Description | +|---------|------|-------------| +| `liquidIsToken0` | `PoolId => bool` | Whether the liquid token is token0 in the pair | +| `locker` | `PoolId => address` | LP locker for this pool | +| `mevModule` | `PoolId => address` | MEV protection module | +| `mevModuleEnabled` | `PoolId => bool` | Whether MEV module is active | +| `poolCreationTimestamp` | `PoolId => uint256` | Block timestamp when pool was created | +| `poolExtension` | `PoolId => address` | Optional pool extension | + +### Hook Callbacks + +The hook implements Uniswap V4 lifecycle callbacks: + +1. **`afterInitialize`** -- Called when pool is created. Records pool metadata, sets up MEV module +2. **`beforeSwap`** -- Called before every swap. Applies MEV fee if active, calculates LP fee +3. **`afterSwap`** -- Called after every swap. Collects protocol fee portion + +### Fee Calculation Flow (per swap) + +``` +Swap initiated + | + v +beforeSwap(): + 1. Check if MEV module is active + 2. If yes: apply MEV fee (up to 80%) + 3. Calculate LP fee (static or dynamic) + 4. Apply protocol fee = LP fee * 200,000 / 1,000,000 (20%) + | + v +Uniswap V4 executes swap with calculated fee + | + v +afterSwap(): + 1. Collect protocol fee portion + 2. Route to factory for team fee recipient +``` + +## LiquidHookStaticFeeV2 (Default) + +Fixed fee hook. The fee is set at pool initialization and never changes. + +- **Address:** `0x9811f10Cd549c754Fa9E5785989c422A762c28cc` +- **SDK Constant:** `ADDRESSES.HOOK_STATIC_FEE_V2` +- **Default fees:** 1% on buys (paired fee), 1% on sells (liquid fee) + +### Pool Data Encoding + +```typescript +import { encodeStaticFeePoolData } from "liquid-sdk"; + +// 1% both directions (default) +const poolData = encodeStaticFeePoolData(100, 100); +// Args: (liquidFeeBps, pairedFeeBps) +// liquidFeeBps: fee when selling token (token -> ETH) +// pairedFeeBps: fee when buying token (ETH -> token) + +// 0% sell, 2% buy +const customData = encodeStaticFeePoolData(0, 200); +``` + +The encoding uses two layers: +1. **Inner:** `abi.encode(uint24 liquidFee, uint24 pairedFee)` in uniBps (BPS * 100) +2. **Outer:** `PoolInitializationData { extension, extensionData, feeData }` + +## LiquidHookDynamicFeeV2 + +Volatility-responsive fee hook. Fee adjusts based on tick movement (price volatility). + +- **Address:** `0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC` +- **SDK Constant:** `ADDRESSES.HOOK_DYNAMIC_FEE_V2` + +### Configuration Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `baseFeeBps` | `number` | Minimum fee in BPS (e.g., 100 = 1%) | +| `maxFeeBps` | `number` | Maximum fee in BPS (e.g., 500 = 5%) | +| `referenceTickFilterPeriod` | `number` | Seconds for reference tick filtering | +| `resetPeriod` | `number` | Seconds before fee state resets | +| `resetTickFilter` | `number` | Tick movement threshold for reset | +| `feeControlNumerator` | `bigint` | Scaling constant for fee curve | +| `decayFilterBps` | `number` | Decay filter in BPS | + +### Pool Data Encoding + +```typescript +import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; + +const poolData = encodeDynamicFeePoolData({ + baseFeeBps: 100, // 1% base fee + maxFeeBps: 500, // 5% max fee + referenceTickFilterPeriod: 30, // 30s filter period + resetPeriod: 120, // 2 min reset + resetTickFilter: 200, // 200 tick threshold + feeControlNumerator: 500000000n, // scaling constant + decayFilterBps: 7500, // 75% decay filter +}); + +const result = await sdk.deployToken({ + hook: ADDRESSES.HOOK_DYNAMIC_FEE_V2, + poolData, + // ...other params +}); +``` + +### Reading Dynamic Fee State + +```typescript +// Get pool configuration (immutable after deploy) +const config = await sdk.getPoolConfig(poolId); +// config.baseFee, config.maxLpFee, config.referenceTickFilterPeriod, etc. + +// Get current fee state (changes with each swap) +const state = await sdk.getPoolFeeState(poolId); +// state.referenceTick, state.resetTick, state.appliedVR, state.prevVA +``` + +## Pool Key Structure + +Every Liquid pool has a standard pool key: + +```typescript +const poolKey = { + currency0: EXTERNAL.WETH, // 0x4200...0006 (always lower) + currency1: tokenAddress, // deployed token (always higher) + fee: 8388608, // 0x800000 = dynamic fee flag + tickSpacing: 200, // Liquid default + hooks: hookAddress, // which hook contract +}; +``` + +The `fee: 0x800000` is NOT an actual fee value -- it signals to Uniswap V4 that the hook controls the fee dynamically via `beforeSwap`. + +Pool ID is: `keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks))` + +## See Also + +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system explanation +- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- MEV module details +- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with custom hooks diff --git a/rag/contracts/liquid-lp-locker.md b/rag/contracts/liquid-lp-locker.md new file mode 100644 index 0000000..864f9a1 --- /dev/null +++ b/rag/contracts/liquid-lp-locker.md @@ -0,0 +1,136 @@ +# LiquidLpLockerFeeConversion + +The LP Locker permanently locks Uniswap V4 LP positions and manages fee collection, conversion, and distribution to reward recipients. + +## Contract Details + +- **Address:** `0x77247fCD1d5e34A3703AcA898A591Dc7422435f3` +- **SDK Constant:** `ADDRESSES.LP_LOCKER_FEE_CONVERSION` +- **Role:** Default LP locker for all Liquid Protocol tokens + +## What It Does + +1. **Locks LP permanently** -- Liquidity positions are transferred to this contract and can never be withdrawn. This is a core anti-rug guarantee. +2. **Collects fees** -- Periodically collects accrued LP fees from Uniswap V4 positions +3. **Converts fees** -- Converts collected fees to a preferred token per recipient (ETH by default) +4. **Distributes fees** -- Routes converted fees to the Fee Locker, split by reward BPS + +## Fee Conversion (FeePreference) + +Each reward recipient can specify how they want their fees: + +| Value | Enum | Behavior | +|-------|------|----------| +| 0 | `FeePreference.Both` | No conversion -- receive fees in whichever token accrues | +| 1 | `FeePreference.Paired` | Convert all fees to paired token (WETH/ETH). **Default.** | +| 2 | `FeePreference.Liquid` | Convert all fees to the liquid token | + +```typescript +import { encodeFeeConversionLockerData, FeePreference } from "liquid-sdk"; + +// Single recipient, all fees as ETH (default) +const lockerData = encodeFeeConversionLockerData([FeePreference.Paired]); + +// Two recipients: first gets ETH, second gets the token +const lockerData = encodeFeeConversionLockerData([ + FeePreference.Paired, + FeePreference.Liquid, +]); +``` + +## Reward Configuration + +Set at deployment time. The BPS splits are immutable, but recipient addresses can be updated by their admins. + +| Field | Description | Mutable? | +|-------|-------------|----------| +| `rewardRecipients` | Who receives fees | Yes (by admin) | +| `rewardAdmins` | Who can update each recipient | No | +| `rewardBps` | Split percentages (sum = 10000) | No | + +### Reading Reward Config + +```typescript +const rewards = await sdk.getTokenRewards(tokenAddress); +// rewards.rewardRecipients: Address[] +// rewards.rewardBps: number[] -- e.g., [7000, 3000] = 70%/30% +// rewards.rewardAdmins: Address[] +// rewards.poolKey: PoolKey +// rewards.positionId: bigint +// rewards.numPositions: bigint +``` + +### Updating Recipients + +```typescript +// Only the admin at index N can update recipient N +const txHash = await sdk.updateRewardRecipient( + tokenAddress, + 0n, // reward index (bigint) + newRecipientAddress, +); +``` + +## Collecting Rewards + +Two methods for collecting fees from LP positions: + +### `collectRewards(tokenAddress)` -- Full collect + unlock + +Collects all accrued LP fees, converts them per fee preferences, and distributes to the Fee Locker. Also unlocks the LP position (related to MEV block delay). + +```typescript +const txHash = await sdk.collectRewards(tokenAddress); +``` + +**Note:** Will revert with `ManagerLocked` during the MEV block delay period. Check `getPoolUnlockTime()` first. + +### `collectRewardsWithoutUnlock(tokenAddress)` -- Collect only + +Same as above but skips the unlock step. Useful during the MEV protection window or to avoid MEV during collection. + +```typescript +const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); +``` + +## Fee Distribution Flow + +``` +collectRewards(tokenAddress) called + | + v +LP Locker reads positions from Uniswap V4 PoolManager + | + v +Collects accrued fees (token0 + token1) + | + v +For each reward recipient: + |-- Check FeePreference + |-- If Paired: swap token fees -> WETH via pool + |-- If Liquid: swap WETH fees -> token via pool + |-- If Both: no conversion + | + v +Calculate recipient share: totalFees * recipientBps / 10000 + | + v +Call FeeLocker.storeFees(recipient, token, amount) + | + v +Recipient can call sdk.claimFees() to withdraw +``` + +## Key Invariants + +- LP is **permanently locked** -- there is no withdraw or unlock path for the liquidity itself +- Reward BPS splits are **immutable** -- set at deployment, cannot be changed +- Only reward admins can update recipient addresses +- Fee conversion uses the same Uniswap V4 pool for swaps + +## See Also + +- [liquid-fee-locker.md](liquid-fee-locker.md) -- Where converted fees are stored +- [../sdk/reward-management.md](../sdk/reward-management.md) -- SDK reward guide +- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming guide +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Full fee system overview diff --git a/rag/contracts/liquid-mev-protection.md b/rag/contracts/liquid-mev-protection.md new file mode 100644 index 0000000..79683a5 --- /dev/null +++ b/rag/contracts/liquid-mev-protection.md @@ -0,0 +1,173 @@ +# MEV Protection Modules + +Liquid Protocol provides two MEV protection modules that activate at token launch to prevent sniper bots from extracting value in the first seconds of trading. + +## Why MEV Protection Matters + +When a new token is deployed, its pool starts at a low market cap. Without protection: +- Sniper bots detect the deployment transaction in the mempool +- They immediately buy large amounts at the lowest price +- They dump on retail traders who arrive seconds later +- The deployer and community get worse prices + +MEV protection taxes early trading activity, making sniping unprofitable. + +## Module 1: SniperAuctionV2 (Default) + +An auction-based system where early traders compete via gas price bidding. + +- **Address:** `0x187e8627c02c58F31831953C1268e157d3BfCefd` +- **SDK Constant:** `ADDRESSES.SNIPER_AUCTION_V2` +- **Utility contract:** `0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f` (`SNIPER_UTIL_V2`) + +### Default Configuration + +| Parameter | Value | Description | +|-----------|-------|-------------| +| Starting fee | 800,000 (80%) | Fee at auction start | +| Ending fee | 400,000 (40%) | Fee floor after decay | +| Decay period | 20 seconds | Linear decay from start to end | +| Max rounds | 5 | Total auction rounds | +| Blocks between rounds | 2 | One round every 2 blocks | +| First auction block | Deploy block + 2 | Auction starts 2 blocks after deployment | +| Payment per gas unit | 0.0001 ETH (1e14 wei) | Converts gas price delta to bid amount | + +### How It Works + +1. **Token deploys** -- Pool is created, sniper auction activates +2. **Fee decay begins** -- MEV fee starts at 80% and decays linearly to 40% over 20 seconds +3. **Rounds** -- Every 2 blocks, an auction round opens for exactly 1 block +4. **Gas price bidding** -- Bidders set their gas price above the `gasPeg` (base fee at deployment). The difference encodes their bid: `bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit` +5. **Winner** -- Highest gas price transaction in each auction block wins the swap +6. **Revenue** -- Bid amounts (ETH) flow to protocol and LP holders via the Fee Locker +7. **Auction ends** -- After 5 rounds (~10 blocks, ~20 seconds), normal trading resumes at standard LP fees + +### Bid Encoding Formula + +``` +bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit + +Where: + txGasPrice = both maxFeePerGas AND maxPriorityFeePerGas (must be equal) + gasPeg = base fee recorded at pool creation (~6.3M wei on Base) + paymentPerGasUnit = 0.0001 ETH (1e14 wei) +``` + +### Fee Impact + +| Time | Fee | Tokens Received | Breakeven Multiple | +|------|-----|-----------------|-------------------| +| 0s (start) | 80% | 20% of fair value | 5x | +| 5s | 70% | 30% of fair value | 3.3x | +| 10s | 60% | 40% of fair value | 2.5x | +| 15s | 50% | 50% of fair value | 2x | +| 20s (end) | 40% | 60% of fair value | 1.7x | +| After auction | 1% (LP fee) | 99% of fair value | 1.01x | + +### SDK Methods + +```typescript +// Read auction state +const auction = await sdk.getAuctionState(poolId); +// auction.nextAuctionBlock, auction.round, auction.gasPeg, auction.currentFee + +// Read fee config +const feeConfig = await sdk.getAuctionFeeConfig(poolId); +// feeConfig.startingFee, feeConfig.endingFee, feeConfig.secondsToDecay + +// Get decay start time +const startTime = await sdk.getAuctionDecayStartTime(poolId); + +// Get max rounds +const maxRounds = await sdk.getAuctionMaxRounds(); + +// Calculate gas price for desired bid +const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, parseEther("0.001")); + +// Execute bid (auto-wraps WETH, approves SniperUtil, sets gas) +const result = await sdk.bidInAuction({ + poolKey: rewards.poolKey, + zeroForOne, + amountIn: parseEther("0.001"), + amountOutMinimum: 0n, + round: auction.round, + bidAmount: parseEther("0.0005"), +}, gasPrice); +``` + +### Custom Sniper Auction Config + +```typescript +import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; + +const mevModuleData = encodeSniperAuctionData({ + startingFee: 800_000, // 80% starting fee + endingFee: 400_000, // 40% ending fee + secondsToDecay: 20, // 20 seconds decay +}); + +const result = await sdk.deployToken({ + name: "Custom MEV Token", + symbol: "CMEV", + mevModule: ADDRESSES.SNIPER_AUCTION_V2, + mevModuleData, +}); +``` + +## Module 2: MevDescendingFees + +A simpler time-based MEV protection without auction mechanics. Fee decays parabolically from a high starting point. + +- **Address:** `0x8D6B080e48756A99F3893491D556B5d6907b6910` +- **SDK Constant:** `ADDRESSES.MEV_DESCENDING_FEES` + +### Configuration + +| Parameter | Constraint | +|-----------|------------| +| Max initial fee | 800,000 (80%) | +| Max decay duration | 2 minutes (120 seconds) | +| Decay curve | Parabolic (quadratic) | +| Auction mechanics | None -- purely time-based | + +### How It Works + +1. **Token deploys** -- Descending fee module activates +2. **High initial fee** -- Swaps are taxed at up to 80% +3. **Parabolic decay** -- Fee decreases over time following a quadratic curve (faster at start, slower at end) +4. **Normal trading** -- After the decay period (max 2 min), standard LP fees apply + +### Key Differences from Sniper Auction + +| Aspect | Sniper Auction | Descending Fees | +|--------|---------------|-----------------| +| Mechanism | Gas price bidding | Time-based decay | +| Competition | Highest gas wins | First-come, first-served | +| Revenue | Bid ETH to protocol/LP | Fees from swaps only | +| Complexity | Complex (rounds, gas encoding) | Simple (just time) | +| Duration | ~20s (5 rounds) | Up to 2 minutes | +| Decay curve | Linear | Parabolic | + +## MEV Block Delay + +Both modules interact with the MEV block delay system: + +```typescript +// Check block delay +const delay = await sdk.getMevBlockDelay(); + +// Check when pool unlocks +const unlockTime = await sdk.getPoolUnlockTime(poolId); +const now = BigInt(Math.floor(Date.now() / 1000)); + +if (now < unlockTime) { + console.log("Pool still locked -- collectRewards will revert"); + // Use collectRewardsWithoutUnlock instead +} +``` + +## See Also + +- [../sdk/sniper-auction.md](../sdk/sniper-auction.md) -- SDK auction guide +- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- Conceptual overview +- [liquid-hooks.md](liquid-hooks.md) -- Hook integration with MEV modules diff --git a/rag/contracts/liquid-token.md b/rag/contracts/liquid-token.md new file mode 100644 index 0000000..d878e7f --- /dev/null +++ b/rag/contracts/liquid-token.md @@ -0,0 +1,104 @@ +# LiquidToken (ERC-20) + +The token contract deployed by the Liquid factory for every token launch. A feature-rich ERC-20 with governance, burning, and cross-chain support. + +## Contract Details + +- **Deployed per token** -- Each `deployToken()` call creates a new LiquidToken instance via CREATE2 +- **Solidity:** `^0.8.28` +- **License:** MIT + +## Token Properties + +| Property | Value | +|----------|-------| +| **Total Supply** | 100,000,000,000 (100 billion) | +| **Decimals** | 18 | +| **Supply (raw)** | `100_000_000_000 * 10^18` = `100_000_000_000_000_000_000_000_000_000` | +| **SDK Constant** | `TOKEN.SUPPLY` = `100_000_000_000n * 10n ** 18n` | +| **Minting** | Fixed supply, no additional minting possible | + +## Inherited Interfaces + +LiquidToken inherits from multiple OpenZeppelin and OP Stack contracts: + +| Interface | Source | What It Provides | +|-----------|--------|-----------------| +| **ERC20** | OpenZeppelin | Standard token: `transfer`, `approve`, `balanceOf`, `allowance` | +| **ERC20Permit** | OpenZeppelin | Gasless approvals via EIP-2612 signed messages | +| **ERC20Votes** | OpenZeppelin | On-chain governance: `delegate`, `getVotes`, `getPastVotes` | +| **ERC20Burnable** | OpenZeppelin | Token burning: `burn`, `burnFrom` | +| **IERC7802** | OP Stack | Cross-chain token standard for Optimism Superchain bridges | +| **IERC165** | OpenZeppelin | Interface detection: `supportsInterface` | + +## Key Features + +### Permit (EIP-2612) + +Allows gasless token approvals. Users sign a permit off-chain, and anyone can submit the signature to grant approval: + +```typescript +// No on-chain approve() transaction needed +// The permit signature can be submitted by anyone +``` + +### Votes (ERC-5805) + +Full on-chain governance support. Token holders can delegate their voting power: + +```typescript +// Delegate voting power +await tokenContract.write.delegate([delegateAddress]); + +// Check voting power +const votes = await tokenContract.read.getVotes([address]); +``` + +### Burnable + +Token holders can burn their own tokens, permanently reducing the circulating supply: + +```typescript +// Burn own tokens +await tokenContract.write.burn([amount]); + +// Burn from approved address +await tokenContract.write.burnFrom([owner, amount]); +``` + +### Cross-Chain (IERC7802) + +Supports the Optimism Superchain cross-chain token standard. Enables native bridging across OP Stack chains without wrapped token contracts. Only the `SuperchainTokenBridge` predeploy can call `crosschainMint` and `crosschainBurn`. + +## Admin Functions + +The token has an admin (set at deployment, defaults to deployer) who can: + +| Function | Description | SDK Method | +|----------|-------------|------------| +| `updateImage(string)` | Change the token image URL | `sdk.updateImage(token, url)` | +| `updateMetadata(string)` | Change the token metadata JSON | `sdk.updateMetadata(token, json)` | +| `updateAdmin(address)` | Transfer admin role | Direct contract call | + +## Supply Distribution + +At deployment, the 100B supply is distributed as: + +``` +100B Total Supply + |-- Extensions allocation (0-90%, per extensionBps) + | |-- Vault (lockup + vesting) + | |-- Airdrop (merkle distribution) + | |-- Dev Buy (swap through pool) + | |-- Presale + | + |-- Remaining → LP positions (locked permanently) + |-- Position 1: X% of remaining + |-- Position 2: Y% of remaining + |-- ... (up to 7 positions) +``` + +## See Also + +- [liquid-factory.md](liquid-factory.md) -- Factory that deploys tokens +- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full lifecycle diff --git a/rag/contracts/liquid-vault.md b/rag/contracts/liquid-vault.md new file mode 100644 index 0000000..59cb59a --- /dev/null +++ b/rag/contracts/liquid-vault.md @@ -0,0 +1,134 @@ +# LiquidVault + +The Vault extension enables token lockup with linear vesting. Tokens are locked for a configurable lockup period, then vest linearly over a vesting period. + +## Contract Details + +- **Address:** `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` +- **SDK Constant:** `ADDRESSES.VAULT` +- **Type:** Extension (receives tokens via `receiveTokens`) + +## How It Works + +1. **At deployment:** The factory transfers a portion of token supply (per `extensionBps`) to the Vault +2. **Lockup period:** Tokens are fully locked until `lockupEndTime`. No claims possible. +3. **Vesting period:** After lockup ends, tokens vest linearly from `lockupEndTime` to `vestingEndTime` +4. **Claiming:** The vault admin can claim vested tokens at any time after lockup ends + +### Vesting Formula + +``` +if (now < lockupEndTime): + claimable = 0 + +if (now >= vestingEndTime): + claimable = amountTotal - amountClaimed + +if (lockupEndTime <= now < vestingEndTime): + elapsed = now - lockupEndTime + vestingDuration = vestingEndTime - lockupEndTime + totalVested = amountTotal * elapsed / vestingDuration + claimable = totalVested - amountClaimed +``` + +## Vault Allocation Structure + +```typescript +interface VaultAllocation { + token: Address; // The token being vested + amountTotal: bigint; // Total tokens locked in vault + amountClaimed: bigint; // Tokens already claimed + lockupEndTime: bigint; // Unix timestamp: lockup ends + vestingEndTime: bigint; // Unix timestamp: vesting completes + admin: Address; // Who can claim +} +``` + +## SDK Methods + +### Check vault state + +```typescript +const vault = await sdk.getVaultAllocation(tokenAddress); +console.log("Total locked:", vault.amountTotal); +console.log("Already claimed:", vault.amountClaimed); +console.log("Lockup ends:", new Date(Number(vault.lockupEndTime) * 1000)); +console.log("Vesting ends:", new Date(Number(vault.vestingEndTime) * 1000)); +console.log("Admin:", vault.admin); +``` + +### Check claimable amount + +```typescript +const claimable = await sdk.getVaultClaimable(tokenAddress); +// claimable: bigint -- tokens available to claim right now +``` + +### Claim vested tokens + +```typescript +if (claimable > 0n) { + const txHash = await sdk.claimVault(tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); +} +``` + +## Configuration at Deployment + +To include a vault in your token deployment, add it as an extension: + +```typescript +// The vault extension data must encode: +// - lockupDuration (uint256): seconds of lockup after deployment +// - vestingDuration (uint256): seconds of linear vesting after lockup +// - admin (address): who can claim +``` + +The vault receives `extensionBps / 10000 * TOKEN_SUPPLY` tokens at deployment. + +## Common Errors + +| Error | Cause | Resolution | +|-------|-------|------------| +| `LockupNotEnded` | Attempting to claim before `lockupEndTime` | Wait for lockup to end | +| `Unauthorized` | Caller is not the vault admin | Use the admin wallet | +| Zero claimable | All vested tokens already claimed | Wait for more to vest | + +## Example: Full Vault Lifecycle + +```typescript +import { LiquidSDK } from "liquid-sdk"; +import { formatUnits } from "viem"; + +const sdk = new LiquidSDK({ publicClient, walletClient }); + +// 1. Check vault state +const vault = await sdk.getVaultAllocation(tokenAddress); +const now = BigInt(Math.floor(Date.now() / 1000)); + +console.log("Total:", formatUnits(vault.amountTotal, 18), "tokens"); +console.log("Claimed:", formatUnits(vault.amountClaimed, 18), "tokens"); + +// 2. Check if lockup has ended +if (now < vault.lockupEndTime) { + const remaining = Number(vault.lockupEndTime - now); + console.log(`Locked for ${remaining} more seconds`); + return; +} + +// 3. Check claimable +const claimable = await sdk.getVaultClaimable(tokenAddress); +console.log("Claimable:", formatUnits(claimable, 18), "tokens"); + +// 4. Claim +if (claimable > 0n) { + const txHash = await sdk.claimVault(tokenAddress); + console.log("Claimed in tx:", txHash); +} +``` + +## See Also + +- [../sdk/vault-lifecycle.md](../sdk/vault-lifecycle.md) -- SDK vault guide +- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension system overview +- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full token lifecycle diff --git a/rag/schemas/deploy-params.json b/rag/schemas/deploy-params.json new file mode 100644 index 0000000..8c3c11f --- /dev/null +++ b/rag/schemas/deploy-params.json @@ -0,0 +1,200 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "DeployTokenParams", + "description": "Parameters for sdk.deployToken(). Only 'name' and 'symbol' are required -- all other fields have sensible defaults.", + "type": "object", + "required": ["name", "symbol"], + "properties": { + "name": { + "type": "string", + "description": "Token name (e.g., 'My Token'). Stored on-chain in the ERC-20 contract and emitted in the TokenCreated event." + }, + "symbol": { + "type": "string", + "description": "Token symbol (e.g., 'MTK'). Stored on-chain in the ERC-20 contract." + }, + "image": { + "type": "string", + "default": "", + "description": "Token image URL. IPFS URIs recommended (e.g., 'ipfs://QmHash'). Stored on-chain in the TokenCreated event. Recommended: 256x256 or 512x512 PNG." + }, + "metadata": { + "type": "string", + "default": "", + "description": "JSON string with token metadata. Schema: { description?: string, socialMediaUrls?: { platform: string, url: string }[], auditUrls?: string[] }. Use buildMetadata() helper to construct." + }, + "context": { + "type": "string", + "default": "{\"interface\":\"SDK\"}", + "description": "JSON string with deployment provenance. Schema: { interface: string, platform?: string, messageId?: string, id?: string }. Auto-set to {\"interface\":\"SDK\"} if omitted. Use buildContext() helper." + }, + "tokenAdmin": { + "type": "string", + "format": "address", + "default": "", + "description": "Address that can update token image and metadata after deployment. Defaults to the deployer wallet address." + }, + "salt": { + "type": "string", + "format": "hex", + "default": "keccak256(name + symbol + timestamp)", + "description": "CREATE2 salt for deterministic token address. Auto-generated from name + symbol + current timestamp if omitted." + }, + "hook": { + "type": "string", + "format": "address", + "default": "0x9811f10Cd549c754Fa9E5785989c422A762c28cc", + "description": "Fee hook contract address. Default: HOOK_STATIC_FEE_V2 (1% buy + 1% sell static fees). Alternative: HOOK_DYNAMIC_FEE_V2 (0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC) for volatility-responsive fees." + }, + "pairedToken": { + "type": "string", + "format": "address", + "default": "0x4200000000000000000000000000000000000006", + "description": "Quote token for the Uniswap V4 pool. Default: WETH on Base. This is always currency0 in the pool key." + }, + "tickIfToken0IsLiquid": { + "type": "integer", + "default": -230400, + "description": "Starting tick for the pool, which determines the initial market cap. Default: -230400 (approximately 10 ETH / ~$20K market cap). Formula: price = 1.0001^tick, marketCap = price * 100B supply." + }, + "tickSpacing": { + "type": "integer", + "default": 200, + "description": "Uniswap V4 tick spacing. All tick values must be divisible by this number. Default: 200." + }, + "poolData": { + "type": "string", + "format": "hex", + "default": "encodeStaticFeePoolData(100, 100)", + "description": "ABI-encoded pool initialization data for the hook. Default encodes 1% sell fee (100 BPS) + 1% buy fee (100 BPS) for the static fee hook. Use encodeStaticFeePoolData() or encodeDynamicFeePoolData() helpers." + }, + "locker": { + "type": "string", + "format": "address", + "default": "0x77247fCD1d5e34A3703AcA898A591Dc7422435f3", + "description": "LP locker contract. Default: LP_LOCKER_FEE_CONVERSION which converts all fees to a preferred token (ETH by default) before distributing to reward recipients." + }, + "rewardAdmins": { + "type": "array", + "items": { "type": "string", "format": "address" }, + "default": "[walletAddress]", + "description": "Admin addresses for each reward recipient slot. Each admin can update the recipient at their index. Must be same length as rewardRecipients." + }, + "rewardRecipients": { + "type": "array", + "items": { "type": "string", "format": "address" }, + "default": "[walletAddress]", + "description": "Addresses that receive LP fee rewards. Must be same length as rewardAdmins and rewardBps." + }, + "rewardBps": { + "type": "array", + "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "default": [10000], + "description": "Basis points for each reward recipient. Must sum to exactly 10000 (100%). Example: [7000, 3000] = 70%/30% split. IMMUTABLE after deployment." + }, + "tickLower": { + "type": "array", + "items": { "type": "integer" }, + "default": "[-230400, -216000, -202000, -155000, -141000]", + "description": "Lower tick bounds for each LP position. Must be divisible by tickSpacing. At least one must equal tickIfToken0IsLiquid. Default: 5-position Liquid layout." + }, + "tickUpper": { + "type": "array", + "items": { "type": "integer" }, + "default": "[-216000, -155000, -155000, -120000, -120000]", + "description": "Upper tick bounds for each LP position. Must be divisible by tickSpacing. Must be same length as tickLower." + }, + "positionBps": { + "type": "array", + "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "default": [1000, 5000, 1500, 2000, 500], + "description": "Supply allocation per position in basis points. Must sum to 10000. Default 5-position Liquid layout: 10%/50%/15%/20%/5%." + }, + "lockerData": { + "type": "string", + "format": "hex", + "default": "encodeFeeConversionLockerData([FeePreference.Paired])", + "description": "ABI-encoded locker initialization data. Default sets FeePreference.Paired (convert all fees to ETH) for each reward recipient." + }, + "mevModule": { + "type": "string", + "format": "address", + "default": "0x187e8627c02c58F31831953C1268e157d3BfCefd", + "description": "MEV protection module. Default: SNIPER_AUCTION_V2. Alternative: MEV_DESCENDING_FEES (0x8D6B080e48756A99F3893491D556B5d6907b6910) for time-based parabolic fee decay." + }, + "mevModuleData": { + "type": "string", + "format": "hex", + "default": "encodeSniperAuctionData({ startingFee: 800000, endingFee: 400000, secondsToDecay: 20 })", + "description": "ABI-encoded MEV module config. Default: 80% starting fee decaying to 40% over 20 seconds. Use encodeSniperAuctionData() helper." + }, + "extensions": { + "type": "array", + "items": { + "$ref": "#/$defs/ExtensionConfig" + }, + "default": [], + "description": "Additional extensions (vault, airdrop, presale). Max 10 extensions, max 90% (9000 BPS) of supply allocated to extensions total.", + "maxItems": 10 + }, + "devBuy": { + "$ref": "#/$defs/DevBuyParams", + "description": "Optional dev buy -- buy tokens with ETH in the same transaction as deployment. The SDK automatically appends a UNIV4_ETH_DEV_BUY extension. The ETH is swapped through the pool at normal 1% LP fees (not auction fees)." + } + }, + "$defs": { + "ExtensionConfig": { + "type": "object", + "required": ["extension", "msgValue", "extensionBps", "extensionData"], + "properties": { + "extension": { + "type": "string", + "format": "address", + "description": "Extension contract address. Must be on the allowlist." + }, + "msgValue": { + "type": "string", + "description": "ETH to send with the extension call (as bigint string). Usually 0n except for dev buy." + }, + "extensionBps": { + "type": "integer", + "minimum": 0, + "maximum": 9000, + "description": "Basis points of total token supply allocated to this extension." + }, + "extensionData": { + "type": "string", + "format": "hex", + "description": "ABI-encoded initialization data specific to the extension." + } + } + }, + "DevBuyParams": { + "type": "object", + "required": ["ethAmount", "recipient"], + "properties": { + "ethAmount": { + "type": "string", + "description": "Amount of ETH to spend buying tokens (as bigint, in wei). Sent as msg.value." + }, + "recipient": { + "type": "string", + "format": "address", + "description": "Address that receives the purchased tokens." + } + } + } + }, + "validationRules": [ + "1-7 positions allowed", + "positionBps must sum to exactly 10000", + "All ticks must be divisible by tickSpacing", + "All tickLower[i] must be >= tickIfToken0IsLiquid", + "At least one position must have tickLower == tickIfToken0IsLiquid", + "tickLower, tickUpper, and positionBps arrays must be the same length", + "rewardAdmins, rewardRecipients, and rewardBps arrays must be the same length", + "rewardBps must sum to exactly 10000", + "Max 10 extensions total", + "Total extensionBps across all extensions must not exceed 9000 (90% of supply)" + ] +} diff --git a/rag/schemas/pool-config.json b/rag/schemas/pool-config.json new file mode 100644 index 0000000..8f008f4 --- /dev/null +++ b/rag/schemas/pool-config.json @@ -0,0 +1,171 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Liquid Protocol Configuration Schemas", + "description": "JSON Schemas for PoolConfig, LockerConfig, MevModuleConfig, and ExtensionConfig -- the on-chain struct types used in the DeploymentConfig.", + "definitions": { + "PoolConfig": { + "title": "PoolConfig", + "description": "Uniswap V4 pool configuration. Mirrors the on-chain PoolConfig struct in Liquid.sol.", + "type": "object", + "required": ["hook", "pairedToken", "tickIfToken0IsLiquid", "tickSpacing", "poolData"], + "properties": { + "hook": { + "type": "string", + "format": "address", + "description": "Fee hook contract address. Must be an enabled hook in the factory. Options: HOOK_STATIC_FEE_V2 (0x9811f10Cd549c754Fa9E5785989c422A762c28cc) for fixed fees, or HOOK_DYNAMIC_FEE_V2 (0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC) for volatility-responsive fees." + }, + "pairedToken": { + "type": "string", + "format": "address", + "description": "Quote token for the pool. Always WETH (0x4200000000000000000000000000000000000006) on Base. This becomes currency0 in the Uniswap V4 pool key because WETH is numerically lower than deployed token addresses." + }, + "tickIfToken0IsLiquid": { + "type": "integer", + "description": "Starting tick that determines the initial token price and market cap. Since WETH is always token0, this tick represents the price of WETH in terms of the liquid token. Typical values: -230400 (~10 ETH / ~$20K market cap), -200200 (~200 ETH)." + }, + "tickSpacing": { + "type": "integer", + "default": 200, + "description": "Uniswap V4 tick spacing. All position ticks must be divisible by this value. Default: 200." + }, + "poolData": { + "type": "string", + "format": "hex", + "description": "ABI-encoded hook initialization data. Uses two-layer encoding: inner (fee params) wrapped in outer (PoolInitializationData with optional pool extension). For static fees: encodeStaticFeePoolData(liquidFeeBps, pairedFeeBps). For dynamic fees: encodeDynamicFeePoolData(config)." + } + } + }, + "LockerConfig": { + "title": "LockerConfig", + "description": "LP locker configuration. Controls how liquidity is locked and how fees are distributed.", + "type": "object", + "required": ["locker", "rewardAdmins", "rewardRecipients", "rewardBps", "tickLower", "tickUpper", "positionBps", "lockerData"], + "properties": { + "locker": { + "type": "string", + "format": "address", + "description": "LP locker contract address. Default: LP_LOCKER_FEE_CONVERSION (0x77247fCD1d5e34A3703AcA898A591Dc7422435f3). Locks LP permanently and converts fees to preferred token before distributing." + }, + "rewardAdmins": { + "type": "array", + "items": { "type": "string", "format": "address" }, + "description": "Admin address for each reward recipient slot. Admin at index N can update the recipient at index N. Cannot be changed after deployment." + }, + "rewardRecipients": { + "type": "array", + "items": { "type": "string", "format": "address" }, + "description": "Fee reward recipient addresses. Can be updated by the corresponding admin. Receive LP fees according to rewardBps split." + }, + "rewardBps": { + "type": "array", + "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "description": "Basis points (1/10000) for each reward recipient. Must sum to exactly 10000. IMMUTABLE after deployment -- only recipient addresses can change, not splits." + }, + "tickLower": { + "type": "array", + "items": { "type": "integer" }, + "description": "Lower tick bounds for LP positions. Each defines where a liquidity tranche starts. Must be divisible by tickSpacing." + }, + "tickUpper": { + "type": "array", + "items": { "type": "integer" }, + "description": "Upper tick bounds for LP positions. Each defines where a liquidity tranche ends. Must be divisible by tickSpacing." + }, + "positionBps": { + "type": "array", + "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, + "description": "Supply allocation per position in basis points. Must sum to 10000 (100% of pool supply, which is total supply minus extension allocations). Max 7 positions." + }, + "lockerData": { + "type": "string", + "format": "hex", + "description": "ABI-encoded locker init data. For LP_LOCKER_FEE_CONVERSION: encode a FeeIn[] array with one entry per reward recipient. Values: 0 = Both (no conversion), 1 = Paired (convert to ETH), 2 = Liquid (convert to token). Use encodeFeeConversionLockerData()." + } + } + }, + "MevModuleConfig": { + "title": "MevModuleConfig", + "description": "MEV protection module configuration.", + "type": "object", + "required": ["mevModule", "mevModuleData"], + "properties": { + "mevModule": { + "type": "string", + "format": "address", + "description": "MEV module contract address. Options: SNIPER_AUCTION_V2 (0x187e8627c02c58F31831953C1268e157d3BfCefd) for auction-based protection, or MEV_DESCENDING_FEES (0x8D6B080e48756A99F3893491D556B5d6907b6910) for time-based fee decay." + }, + "mevModuleData": { + "type": "string", + "format": "hex", + "description": "ABI-encoded MEV module init data. For Sniper Auction: encode { startingFee: uint24, endingFee: uint24, secondsToDecay: uint256 } where fees are in uniBps (800000 = 80%). Use encodeSniperAuctionData()." + } + } + }, + "ExtensionConfig": { + "title": "ExtensionConfig", + "description": "Configuration for a token extension (vault, airdrop, dev buy, presale).", + "type": "object", + "required": ["extension", "msgValue", "extensionBps", "extensionData"], + "properties": { + "extension": { + "type": "string", + "format": "address", + "description": "Extension contract address. Must be enabled in the Pool Extension Allowlist." + }, + "msgValue": { + "type": "string", + "description": "ETH (in wei) to send as msg.value when calling the extension. Only non-zero for dev buy (ethAmount) and presale extensions." + }, + "extensionBps": { + "type": "integer", + "minimum": 0, + "maximum": 9000, + "description": "Basis points of total token supply (100B) to allocate to this extension. Total across all extensions must not exceed 9000 (90%)." + }, + "extensionData": { + "type": "string", + "format": "hex", + "description": "ABI-encoded extension-specific init data. Content varies by extension type." + } + } + }, + "FeePreference": { + "title": "FeePreference", + "description": "Fee conversion preference for LP_LOCKER_FEE_CONVERSION. One value per reward recipient.", + "type": "integer", + "enum": [0, 1, 2], + "enumDescriptions": { + "0": "Both -- No conversion, fees paid in whichever token accrues from LP", + "1": "Paired -- Convert all fees to paired token (WETH/ETH). This is the default.", + "2": "Liquid -- Convert all fees to the liquid token" + } + }, + "PoolDynamicConfigVars": { + "title": "PoolDynamicConfigVars", + "description": "Dynamic fee pool configuration. Returned by sdk.getPoolConfig(poolId). Only meaningful for pools using HOOK_DYNAMIC_FEE_V2.", + "type": "object", + "properties": { + "baseFee": { "type": "integer", "description": "Minimum fee in BPS (e.g., 100 = 1%)" }, + "maxLpFee": { "type": "integer", "description": "Maximum LP fee in BPS" }, + "referenceTickFilterPeriod": { "type": "string", "description": "Filter period in seconds (bigint)" }, + "resetPeriod": { "type": "string", "description": "Reset period in seconds (bigint)" }, + "resetTickFilter": { "type": "integer", "description": "Reset tick filter threshold (tick units)" }, + "feeControlNumerator": { "type": "string", "description": "Scaling constant for fee calculation (bigint)" }, + "decayFilterBps": { "type": "integer", "description": "Decay filter in BPS" } + } + }, + "PoolDynamicFeeVars": { + "title": "PoolDynamicFeeVars", + "description": "Current dynamic fee state. Returned by sdk.getPoolFeeState(poolId).", + "type": "object", + "properties": { + "referenceTick": { "type": "integer", "description": "Current reference tick for fee calculation" }, + "resetTick": { "type": "integer", "description": "Tick at last reset" }, + "resetTickTimestamp": { "type": "string", "description": "Unix timestamp of last reset (bigint)" }, + "lastSwapTimestamp": { "type": "string", "description": "Unix timestamp of last swap (bigint)" }, + "appliedVR": { "type": "integer", "description": "Applied volatility ratio" }, + "prevVA": { "type": "integer", "description": "Previous volatility accumulator" } + } + } + } +} diff --git a/rag/sdk/airdrop-system.md b/rag/sdk/airdrop-system.md new file mode 100644 index 0000000..62fc69a --- /dev/null +++ b/rag/sdk/airdrop-system.md @@ -0,0 +1,130 @@ +# SDK Guide: Airdrop System + +How to interact with merkle-based airdrops: check state, verify claimable amounts, and claim tokens. + +## Overview + +The Airdrop V2 extension distributes tokens via merkle proof claims. It supports mutable merkle roots, lockup/vesting, and admin reclaim of unclaimed tokens. + +## SDK Methods + +### `getAirdropInfo(tokenAddress)` + +Returns the airdrop state for a token. + +```typescript +const info = await sdk.getAirdropInfo(tokenAddress); + +info.admin // Address -- airdrop admin +info.merkleRoot // Hex -- merkle root for verification +info.totalSupply // bigint -- total airdrop allocation +info.totalClaimed // bigint -- tokens claimed so far +info.lockupEndTime // bigint -- when claims open (unix timestamp) +info.vestingEndTime // bigint -- when vesting completes (unix timestamp) +info.adminClaimTime // bigint -- when admin can reclaim unclaimed +info.adminClaimed // boolean -- whether admin has reclaimed +``` + +### `getAirdropClaimable(tokenAddress, recipient, allocatedAmount)` + +Returns the amount a specific recipient can claim right now. + +```typescript +const claimable = await sdk.getAirdropClaimable( + tokenAddress, + recipientAddress, + allocatedAmount, // bigint -- total allocation for this recipient (18 decimals) +); +``` + +### `claimAirdrop(tokenAddress, recipient, allocatedAmount, proof)` + +Claims the airdrop for a recipient. Requires wallet. + +```typescript +const txHash = await sdk.claimAirdrop( + tokenAddress, + recipientAddress, + allocatedAmount, // bigint -- must match the merkle leaf exactly + merkleProof, // Hex[] -- generated off-chain from the merkle tree +); +await publicClient.waitForTransactionReceipt({ hash: txHash }); +``` + +## Complete Example + +```typescript +import { LiquidSDK } from "liquid-sdk"; +import { formatUnits, parseUnits } from "viem"; + +const sdk = new LiquidSDK({ publicClient, walletClient }); + +// 1. Check airdrop state +const info = await sdk.getAirdropInfo(tokenAddress); +const now = BigInt(Math.floor(Date.now() / 1000)); + +console.log("Merkle root:", info.merkleRoot); +console.log("Total supply:", formatUnits(info.totalSupply, 18)); +console.log("Claimed so far:", formatUnits(info.totalClaimed, 18)); + +// 2. Check if claims are open +if (now < info.lockupEndTime) { + console.log("Claims not yet open. Opens:", new Date(Number(info.lockupEndTime) * 1000)); + return; +} + +// 3. Check claimable for a recipient +const myAllocation = parseUnits("1000", 18); // 1000 tokens allocated +const claimable = await sdk.getAirdropClaimable( + tokenAddress, + recipientAddress, + myAllocation, +); +console.log("Claimable:", formatUnits(claimable, 18), "tokens"); + +// 4. Claim (need merkle proof from off-chain tree) +if (claimable > 0n) { + const txHash = await sdk.claimAirdrop( + tokenAddress, + recipientAddress, + myAllocation, + merkleProof, // Hex[] from the merkle tree + ); + console.log("Claimed in tx:", txHash); +} +``` + +## Merkle Proof Generation + +The SDK does not generate merkle proofs. Use a library like `@openzeppelin/merkle-tree`: + +```typescript +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; + +// Build tree from recipient list +const values = [ + ["0xRecipient1", "1000000000000000000000"], // 1000 tokens (18 decimals) + ["0xRecipient2", "500000000000000000000"], // 500 tokens + // ... more recipients +]; + +const tree = StandardMerkleTree.of(values, ["address", "uint256"]); +console.log("Root:", tree.root); // Set this as the merkle root + +// Get proof for a recipient +const proof = tree.getProof(["0xRecipient1", "1000000000000000000000"]); +``` + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `AlreadyClaimed` | Recipient already claimed | Check `getAirdropClaimable()` first | +| `LockupNotEnded` | Claims haven't opened yet | Wait for `lockupEndTime` | +| Invalid proof | Proof doesn't match root | Regenerate from the tree | +| `Unauthorized` | Admin-only function called by non-admin | Use admin wallet | + +## See Also + +- [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md) -- Airdrop contract details +- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview diff --git a/rag/sdk/deploy-token.md b/rag/sdk/deploy-token.md new file mode 100644 index 0000000..83c7673 --- /dev/null +++ b/rag/sdk/deploy-token.md @@ -0,0 +1,285 @@ +# SDK Guide: Deploy Token + +Complete guide to `sdk.deployToken()` -- the primary entry point for launching tokens on Liquid Protocol. + +## Prerequisites + +```bash +npm install liquid-sdk viem +``` + +- A wallet with ETH on Base (for gas + optional dev buy) +- An RPC endpoint for Base mainnet (chain ID 8453) + +## Setup + +```typescript +import { createPublicClient, createWalletClient, http, parseEther } from "viem"; +import { base } from "viem/chains"; +import { privateKeyToAccount } from "viem/accounts"; +import { LiquidSDK } from "liquid-sdk"; + +const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY"); +const publicClient = createPublicClient({ chain: base, transport: http() }); +const walletClient = createWalletClient({ account, chain: base, transport: http() }); +const sdk = new LiquidSDK({ publicClient, walletClient }); +``` + +## Minimal Deploy + +Only `name` and `symbol` are required. Everything else gets sensible defaults: + +```typescript +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", +}); + +console.log("Token:", result.tokenAddress); +console.log("Pool ID:", result.event.poolId); +console.log("Tx:", result.txHash); +``` + +### What the Defaults Provide + +| Setting | Default Value | +|---------|--------------| +| Fee hook | Static 1% buy + 1% sell | +| Starting market cap | ~10 ETH (~$20K) | +| Positions | 5-position Liquid layout | +| MEV protection | Sniper Auction (80% to 40% over 20s) | +| Reward split | 100% to deployer | +| Fee conversion | All fees to ETH | +| LP | Permanently locked | + +## Deploy with Image + +```typescript +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", + image: "ipfs://QmYourImageCID", +}); +``` + +Recommended: 256x256 or 512x512 PNG, pinned to IPFS. + +## Deploy with Dev Buy + +Buy tokens with ETH in the same transaction. Uses normal 1% LP fees (not auction fees). + +```typescript +const result = await sdk.deployToken({ + name: "My Token", + symbol: "MTK", + devBuy: { + ethAmount: parseEther("0.01"), + recipient: account.address, + }, +}); +``` + +## Deploy with Custom Fees + +### Static Fees + +```typescript +import { encodeStaticFeePoolData } from "liquid-sdk"; + +// 2% on both directions +const result = await sdk.deployToken({ + name: "High Fee Token", + symbol: "HFT", + poolData: encodeStaticFeePoolData(200, 200), + // Args: (liquidFeeBps, pairedFeeBps) + // liquidFeeBps = sell fee (token -> ETH) + // pairedFeeBps = buy fee (ETH -> token) +}); + +// 0% sell, 3% buy +const result2 = await sdk.deployToken({ + name: "Buy Fee Only", + symbol: "BFO", + poolData: encodeStaticFeePoolData(0, 300), +}); +``` + +### Dynamic Fees + +```typescript +import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; + +const result = await sdk.deployToken({ + name: "Dynamic Token", + symbol: "DYN", + hook: ADDRESSES.HOOK_DYNAMIC_FEE_V2, + poolData: encodeDynamicFeePoolData({ + baseFeeBps: 100, + maxFeeBps: 500, + referenceTickFilterPeriod: 30, + resetPeriod: 120, + resetTickFilter: 200, + feeControlNumerator: 500000000n, + decayFilterBps: 7500, + }), +}); +``` + +## Deploy with Custom Positions + +### Using Default 3-Tranche Split + +```typescript +import { createDefaultPositions } from "liquid-sdk"; + +const positions = createDefaultPositions(20_000, 2070); +// Creates 3 tranches: 40% to $500K, 50% to $10M, 10% to $1B + +const result = await sdk.deployToken({ + name: "Positioned Token", + symbol: "POS", + ...positions, // tickLower, tickUpper, positionBps, tickIfToken0IsLiquid +}); +``` + +### Using Custom USD Tranches + +```typescript +import { createPositionsUSD } from "liquid-sdk"; + +const positions = createPositionsUSD(50_000, 2500, [ + { upperMarketCapUSD: 1_000_000, supplyPct: 30 }, + { upperMarketCapUSD: 50_000_000, supplyPct: 50 }, + { upperMarketCapUSD: 500_000_000, supplyPct: 20 }, +]); + +const result = await sdk.deployToken({ + name: "Custom Positions", + symbol: "CPS", + ...positions, + tickIfToken0IsLiquid: positions.tickLower[0], +}); +``` + +## Deploy with Custom Reward Splits + +```typescript +const result = await sdk.deployToken({ + name: "Split Token", + symbol: "SPLIT", + rewardAdmins: [walletA, walletB], + rewardRecipients: [walletA, walletB], + rewardBps: [7000, 3000], // 70% / 30% +}); +``` + +**Rules:** +- Arrays must be same length +- rewardBps must sum to exactly 10000 +- BPS splits are IMMUTABLE after deployment +- Admin at index N can update recipient at index N + +## Deploy with Metadata and Context + +```typescript +import { buildContext, buildMetadata } from "liquid-sdk"; + +const result = await sdk.deployToken({ + name: "Social Token", + symbol: "SOC", + image: "ipfs://QmImageHash", + metadata: buildMetadata({ + description: "A community token for builders", + socialMediaUrls: [ + { platform: "Twitter", url: "https://x.com/myproject" }, + { platform: "Website", url: "https://myproject.xyz" }, + ], + }), + context: buildContext({ + interface: "My Agent", + platform: "Farcaster", + messageId: "0x123abc", + }), +}); +``` + +## Deploy with Custom MEV Protection + +```typescript +import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; + +// Custom sniper auction: 60% to 30% over 30s +const result = await sdk.deployToken({ + name: "Custom MEV Token", + symbol: "CMEV", + mevModule: ADDRESSES.SNIPER_AUCTION_V2, + mevModuleData: encodeSniperAuctionData({ + startingFee: 600_000, + endingFee: 300_000, + secondsToDecay: 30, + }), +}); + +// Use descending fees instead +const result2 = await sdk.deployToken({ + name: "Descending Fee Token", + symbol: "DFT", + mevModule: ADDRESSES.MEV_DESCENDING_FEES, + // mevModuleData encoding depends on the module +}); +``` + +## Return Value + +```typescript +interface DeployTokenResult { + tokenAddress: Address; // Deployed ERC-20 contract + txHash: Hash; // Transaction hash + event: TokenCreatedEvent; // Full on-chain event with poolId, hook, etc. +} +``` + +The `event` contains: `poolId`, `poolHook`, `locker`, `mevModule`, `extensions`, `tokenAdmin`, `startingTick`, `pairedToken`, and more. + +## Validation Rules + +The SDK validates before sending the transaction: + +- 1-7 positions allowed +- `positionBps` must sum to 10000 +- All ticks divisible by `tickSpacing` +- All `tickLower` values >= `tickIfToken0IsLiquid` +- At least one position must start at `tickIfToken0IsLiquid` +- 1+ reward recipients, `rewardBps` sum to 10000 +- Max 10 extensions, total extensionBps <= 9000 + +## Post-Deploy + +```typescript +// Check deployment +const info = await sdk.getDeploymentInfo(result.tokenAddress); +const tokenInfo = await sdk.getTokenInfo(result.tokenAddress); + +// Update metadata (admin only) +await sdk.updateImage(result.tokenAddress, "https://new-image.png"); +await sdk.updateMetadata(result.tokenAddress, '{"description":"Updated"}'); + +// Check MEV status +const unlockTime = await sdk.getPoolUnlockTime(result.event.poolId); +``` + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `TickRangeLowerThanStartingTick` | tickLower < tickIfToken0IsLiquid | Adjust position ticks | +| Insufficient funds | Not enough ETH for gas + devBuy | Fund wallet | +| `rewardBps must sum to 10000` | BPS array wrong | Fix values | +| `Deprecated()` | Factory is paused | Contact team | + +## See Also + +- [../schemas/deploy-params.json](../schemas/deploy-params.json) -- Full parameter schema +- [../contracts/liquid-factory.md](../contracts/liquid-factory.md) -- Factory contract details +- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- What happens on-chain +- [../concepts/lp-positions.md](../concepts/lp-positions.md) -- Position math diff --git a/rag/sdk/fee-management.md b/rag/sdk/fee-management.md new file mode 100644 index 0000000..1365e05 --- /dev/null +++ b/rag/sdk/fee-management.md @@ -0,0 +1,116 @@ +# SDK Guide: Fee Management + +How to check and claim LP fees using the Liquid SDK. + +## Overview + +When users trade tokens on Uniswap V4, LP fees accrue in the pool. The LP Locker collects these fees, converts them to ETH (by default), and deposits them into the Fee Locker for each reward recipient. Recipients then call `claimFees()` to withdraw. + +## Fee Flow + +``` +Trading activity on Uniswap V4 + |-- Hook applies LP fee (e.g., 1%) + |-- 20% of LP fee -> Protocol (team) + |-- 80% of LP fee -> LP position + | +collectRewards(tokenAddress) -- anyone can call + |-- Collects fees from LP positions + |-- Converts to ETH (FeePreference.Paired) + |-- Splits by reward BPS + |-- Deposits into Fee Locker + | +claimFees(owner, token) -- anyone can call + |-- Transfers accumulated WETH to fee owner +``` + +## SDK Methods + +### `getAvailableFees(feeOwner, tokenAddress)` + +Returns total unlocked fees for a fee owner and token pair. + +```typescript +const available = await sdk.getAvailableFees(ownerAddress, tokenAddress); +// available: bigint -- fee balance in the Fee Locker (usually WETH) +``` + +### `getFeesToClaim(feeOwner, tokenAddress)` + +Returns the currently claimable fee balance. In the current implementation, this is the same as `getAvailableFees`. + +```typescript +const claimable = await sdk.getFeesToClaim(ownerAddress, tokenAddress); +// claimable: bigint +``` + +### `claimFees(feeOwner, tokenAddress)` + +Claims all accumulated fees for a fee owner. Requires wallet. The fees are transferred to the `feeOwner` address. + +```typescript +if (claimable > 0n) { + const txHash = await sdk.claimFees(ownerAddress, tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); +} +``` + +**Note:** `claimFees` is permissionless -- anyone can trigger a claim on behalf of any fee owner. The funds always go to the `feeOwner` address. + +## Complete Example + +```typescript +import { createPublicClient, createWalletClient, http, formatEther } from "viem"; +import { base } from "viem/chains"; +import { privateKeyToAccount } from "viem/accounts"; +import { LiquidSDK } from "liquid-sdk"; + +const account = privateKeyToAccount("0x..."); +const publicClient = createPublicClient({ chain: base, transport: http() }); +const walletClient = createWalletClient({ account, chain: base, transport: http() }); +const sdk = new LiquidSDK({ publicClient, walletClient }); + +// 1. Check available fees +const available = await sdk.getAvailableFees(account.address, tokenAddress); +console.log("Available fees:", formatEther(available), "ETH"); + +// 2. Check claimable +const claimable = await sdk.getFeesToClaim(account.address, tokenAddress); +console.log("Claimable fees:", formatEther(claimable), "ETH"); + +// 3. Claim +if (claimable > 0n) { + console.log("Claiming fees..."); + const txHash = await sdk.claimFees(account.address, tokenAddress); + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + console.log("Claimed in tx:", receipt.transactionHash); +} else { + console.log("No fees to claim yet."); +} +``` + +## Important Notes + +- Fees only accrue from trading activity. No trading = no fees. +- Fees are denominated in the paired asset (WETH) when using the default `FeePreference.Paired`. +- The `collectRewards()` step must happen before fees appear in the Fee Locker. See [reward-management.md](reward-management.md). +- `claimFees` reverts with `NoFeesToClaim` if balance is zero. + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `NoFeesToClaim` | No fees accrued | Wait for trading, or call `collectRewards()` first | + +## Contract Addresses + +| Contract | Address | +|----------|---------| +| Fee Locker | `0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF` | +| LP Locker Fee Conversion | `0x77247fCD1d5e34A3703AcA898A591Dc7422435f3` | + +## See Also + +- [reward-management.md](reward-management.md) -- Collecting LP rewards (prerequisite for fee claiming) +- [../contracts/liquid-fee-locker.md](../contracts/liquid-fee-locker.md) -- Fee Locker contract +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system overview diff --git a/rag/sdk/pool-reads.md b/rag/sdk/pool-reads.md new file mode 100644 index 0000000..22e664f --- /dev/null +++ b/rag/sdk/pool-reads.md @@ -0,0 +1,124 @@ +# SDK Guide: Pool Reads + +How to query pool configuration, fee state, creation time, and token sort order. + +## Overview + +These read-only methods query on-chain pool state from the Uniswap V4 hook contracts. No wallet required. + +```typescript +const sdk = new LiquidSDK({ publicClient }); // read-only +``` + +## SDK Methods + +### `getPoolConfig(poolId)` + +Returns the dynamic fee configuration for a pool. Most useful for pools using `HOOK_DYNAMIC_FEE_V2`. + +```typescript +const config = await sdk.getPoolConfig(poolId); + +config.baseFee // number -- minimum fee (BPS) +config.maxLpFee // number -- maximum LP fee +config.referenceTickFilterPeriod // bigint -- seconds +config.resetPeriod // bigint -- seconds +config.resetTickFilter // number -- tick units +config.feeControlNumerator // bigint -- scaling constant +config.decayFilterBps // number -- decay filter in BPS +``` + +### `getPoolFeeState(poolId)` + +Returns the current fee state variables that change with each swap. + +```typescript +const state = await sdk.getPoolFeeState(poolId); + +state.referenceTick // number -- current reference tick +state.resetTick // number -- tick at last reset +state.resetTickTimestamp // bigint -- unix timestamp of last reset +state.lastSwapTimestamp // bigint -- unix timestamp of last swap +state.appliedVR // number -- applied volatility ratio +state.prevVA // number -- previous volatility accumulator +``` + +### `getPoolCreationTimestamp(poolId)` + +Returns the unix timestamp when the pool was created. + +```typescript +const timestamp = await sdk.getPoolCreationTimestamp(poolId); +const date = new Date(Number(timestamp) * 1000); +console.log("Created:", date.toISOString()); +``` + +### `isLiquidToken0(poolId)` + +Returns whether the liquid token is token0 or token1 in the Uniswap V4 pool. + +```typescript +const isToken0 = await sdk.isLiquidToken0(poolId); +// In practice, WETH (0x4200...) is almost always token0 (lower address) +// So this typically returns false (the liquid token is token1) +``` + +This is important for determining swap direction: +- If liquid token is token0: `zeroForOne = true` to sell, `false` to buy +- If liquid token is token1 (typical): `zeroForOne = true` to buy, `false` to sell + +## Pool Key Structure + +Every Liquid pool has a standard key: + +```typescript +const poolKey = { + currency0: EXTERNAL.WETH, // 0x4200...0006 + currency1: tokenAddress, // deployed token + fee: 8388608, // 0x800000 = dynamic fee flag + tickSpacing: 200, // Liquid default + hooks: hookAddress, // which hook contract +}; + +// Pool ID = keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks)) +``` + +You can retrieve the pool key from token rewards: + +```typescript +const rewards = await sdk.getTokenRewards(tokenAddress); +const poolKey = rewards.poolKey; +const poolId = tokenEvent.poolId; // from getTokenEvent() +``` + +## Complete Example + +```typescript +import { LiquidSDK } from "liquid-sdk"; + +const sdk = new LiquidSDK({ publicClient }); // read-only + +// Get pool ID from token +const tokenEvent = await sdk.getTokenEvent(tokenAddress); +const poolId = tokenEvent.poolId; + +// Read pool state +const config = await sdk.getPoolConfig(poolId); +console.log("Base fee:", config.baseFee, "BPS"); +console.log("Max LP fee:", config.maxLpFee, "BPS"); + +const state = await sdk.getPoolFeeState(poolId); +console.log("Reference tick:", state.referenceTick); +console.log("Last swap:", new Date(Number(state.lastSwapTimestamp) * 1000)); + +const created = await sdk.getPoolCreationTimestamp(poolId); +console.log("Pool created:", new Date(Number(created) * 1000)); + +const isToken0 = await sdk.isLiquidToken0(poolId); +console.log("Liquid is token0:", isToken0); +``` + +## See Also + +- [../contracts/liquid-hooks.md](../contracts/liquid-hooks.md) -- Hook contract details +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Fee system overview diff --git a/rag/sdk/position-builder.md b/rag/sdk/position-builder.md new file mode 100644 index 0000000..c39cb55 --- /dev/null +++ b/rag/sdk/position-builder.md @@ -0,0 +1,172 @@ +# SDK Guide: Position Builder + +How to create custom liquidity position layouts using market cap targets. + +## Overview + +Liquid Protocol deploys tokens with concentrated liquidity across multiple tick ranges ("positions" or "tranches"). Each position covers a market cap range and holds a percentage of the pool supply. + +The SDK provides helpers to convert market cap targets (USD or ETH) into the tick arrays needed by `deployToken()`. + +## Key Functions + +### `createPositions(startingCapETH, tranches, tickSpacing?)` + +Builds position arrays from ETH-denominated market cap tranches. + +```typescript +import { createPositions } from "liquid-sdk"; + +const positions = createPositions(10, [ + { upperMarketCapETH: 241.5, supplyPct: 40 }, // ~$500K @ $2070/ETH + { upperMarketCapETH: 4830, supplyPct: 50 }, // ~$10M + { upperMarketCapETH: 483050, supplyPct: 10 }, // ~$1B +]); + +// Returns: +// { +// tickLower: [-230400, -198600, -168600], +// tickUpper: [-198600, -168600, -122400], +// positionBps: [4000, 5000, 1000] +// } +``` + +### `createPositionsUSD(startingCapUSD, ethPriceUSD, tranches, tickSpacing?)` + +Builds positions from USD-denominated market caps. Convenience wrapper that converts USD to ETH. + +```typescript +import { createPositionsUSD } from "liquid-sdk"; + +const positions = createPositionsUSD(20_000, 2070, [ + { upperMarketCapUSD: 500_000, supplyPct: 40 }, + { upperMarketCapUSD: 10_000_000, supplyPct: 50 }, + { upperMarketCapUSD: 1_000_000_000, supplyPct: 10 }, +]); + +const result = await sdk.deployToken({ + name: "Custom Positions", + symbol: "CPS", + ...positions, + tickIfToken0IsLiquid: positions.tickLower[0], +}); +``` + +### `createDefaultPositions(startingCapUSD, ethPriceUSD, tickSpacing?)` + +Builds the standard 3-tranche Liquid layout: +- 40% of pool supply: Starting to $500K market cap +- 50% of pool supply: $500K to $10M market cap +- 10% of pool supply: $10M to $1B market cap + +```typescript +import { createDefaultPositions } from "liquid-sdk"; + +const positions = createDefaultPositions(20_000, 2070); +// Returns: { tickLower, tickUpper, positionBps, tickIfToken0IsLiquid } + +const result = await sdk.deployToken({ + name: "Default Layout", + symbol: "DEF", + ...positions, +}); +``` + +### `describePositions(positions, ethPriceUSD?)` + +Returns human-readable descriptions of position ranges. + +```typescript +import { describePositions } from "liquid-sdk"; + +const desc = describePositions(positions, 2070); +for (const p of desc) { + console.log(`Position ${p.index}: ${p.supplyPct}%`); + console.log(` Ticks: ${p.tickLower} -> ${p.tickUpper}`); + console.log(` Market cap: ${p.marketCapLowerETH.toFixed(1)} ETH -> ${p.marketCapUpperETH.toFixed(1)} ETH`); + if (p.marketCapLowerUSD) { + console.log(` USD: $${p.marketCapLowerUSD.toFixed(0)} -> $${p.marketCapUpperUSD.toFixed(0)}`); + } +} +``` + +## Default Position Layouts + +### 5-Position "Liquid" Layout (SDK Default) + +This is the hardcoded default used when no positions are specified: + +| # | Supply | Tick Range | Market Cap Range (~$2000/ETH) | +|---|--------|-----------|------------------------------| +| 1 | 10% | -230,400 to -216,000 | ~$20K to ~$83K | +| 2 | 50% | -216,000 to -155,000 | ~$83K to ~$37M | +| 3 | 15% | -202,000 to -155,000 | ~$338K to ~$37M | +| 4 | 20% | -155,000 to -120,000 | ~$37M to ~$1.2B | +| 5 | 5% | -141,000 to -120,000 | ~$151M to ~$1.2B | + +Note: Positions 2-3 and 4-5 overlap, concentrating more liquidity in the mid-range. + +### Standard Single Position + +```typescript +import { POOL_POSITIONS } from "liquid-sdk"; + +const standard = POOL_POSITIONS.Standard; +// Single position: -230400 to -120000, 100% of supply +``` + +### Default 3-Tranche (via createDefaultPositions) + +| # | Supply | USD Range | +|---|--------|-----------| +| 1 | 40% | Starting to $500K | +| 2 | 50% | $500K to $10M | +| 3 | 10% | $10M to $1B | + +## Tick Math Primer + +See [../concepts/tick-math.md](../concepts/tick-math.md) for full details. + +**Quick formulas:** + +``` +Total supply = 100,000,000,000 (100B) +Price per token = marketCapETH / totalSupply +Tick = floor(log(price) / log(1.0001) / tickSpacing) * tickSpacing + +Reverse: +Price = 1.0001^tick +MarketCapETH = price * totalSupply +MarketCapUSD = marketCapETH * ethPriceUSD +``` + +**Helper functions:** + +```typescript +import { + getTickFromMarketCapETH, + getTickFromMarketCapUSD, + marketCapFromTickETH, + marketCapFromTickUSD, +} from "liquid-sdk"; + +getTickFromMarketCapETH(10) // -230400 (~10 ETH) +getTickFromMarketCapUSD(500_000, 2070) // tick for $500K +marketCapFromTickETH(-230400) // ~10 ETH +marketCapFromTickUSD(-230400, 2070) // ~$20,700 +``` + +## Validation Rules + +- 1-7 positions allowed (max 7) +- `positionBps` must sum to exactly 10000 (100%) +- `supplyPct` in tranches must sum to 100 +- All ticks must be divisible by `tickSpacing` (default 200) +- Tranches must be ordered by ascending market cap +- Each tranche's upper tick must be above the previous + +## See Also + +- [../concepts/tick-math.md](../concepts/tick-math.md) -- Detailed tick math formulas +- [../concepts/lp-positions.md](../concepts/lp-positions.md) -- Position concepts +- [deploy-token.md](deploy-token.md) -- Deploying with custom positions diff --git a/rag/sdk/reward-management.md b/rag/sdk/reward-management.md new file mode 100644 index 0000000..7c985e0 --- /dev/null +++ b/rag/sdk/reward-management.md @@ -0,0 +1,129 @@ +# SDK Guide: Reward Management + +How to manage LP rewards: check reward configuration, collect fees from LP positions, and update reward recipients. + +## Overview + +LP rewards are distributed to reward recipients configured at deployment. The LP Locker holds the Uniswap V4 LP positions permanently and distributes collected fees according to BPS splits. + +## Reward Flow + +``` +LP positions accrue fees from trading + | + v +collectRewards(tokenAddress) called + |-- Collects fees from all positions + |-- Converts to preferred token (ETH by default) + |-- Splits by reward BPS + |-- Deposits into Fee Locker + | + v +Fee recipients call claimFees() to withdraw +``` + +## SDK Methods + +### `getTokenRewards(tokenAddress)` + +Returns the complete reward configuration for a token. + +```typescript +const rewards = await sdk.getTokenRewards(tokenAddress); + +console.log("Recipients:", rewards.rewardRecipients); // Address[] +console.log("BPS:", rewards.rewardBps); // number[] (sum = 10000) +console.log("Admins:", rewards.rewardAdmins); // Address[] +console.log("Pool key:", rewards.poolKey); // PoolKey +console.log("Position ID:", rewards.positionId); // bigint +console.log("Num positions:", rewards.numPositions); // bigint +``` + +### `collectRewards(tokenAddress)` + +Collects all accrued LP fees from Uniswap V4 positions and distributes to reward recipients via the Fee Locker. Also unlocks the LP position. + +```typescript +const txHash = await sdk.collectRewards(tokenAddress); +await publicClient.waitForTransactionReceipt({ hash: txHash }); +``` + +**Important:** Reverts with `ManagerLocked` during the MEV block delay period. Check `getPoolUnlockTime()` first. + +### `collectRewardsWithoutUnlock(tokenAddress)` + +Same as `collectRewards` but skips the unlock step. Use this to avoid MEV during fee collection or when the pool is still in the MEV protection window. + +```typescript +const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); +await publicClient.waitForTransactionReceipt({ hash: txHash }); +``` + +### `updateRewardRecipient(tokenAddress, rewardIndex, newRecipient)` + +Changes the reward recipient at a specific index. Only callable by the reward admin at that index. + +```typescript +const txHash = await sdk.updateRewardRecipient( + tokenAddress, + 0n, // reward index (bigint) + newRecipientAddress, +); +await publicClient.waitForTransactionReceipt({ hash: txHash }); +``` + +## Complete Example + +```typescript +import { LiquidSDK } from "liquid-sdk"; + +const sdk = new LiquidSDK({ publicClient, walletClient }); + +// 1. Check reward config +const rewards = await sdk.getTokenRewards(tokenAddress); +console.log("Recipients:", rewards.rewardRecipients); +console.log("Splits:", rewards.rewardBps.map(b => `${b/100}%`)); + +// 2. Check if pool is unlocked +const unlockTime = await sdk.getPoolUnlockTime(poolId); +const now = BigInt(Math.floor(Date.now() / 1000)); + +if (now < unlockTime) { + console.log("Pool locked until:", new Date(Number(unlockTime) * 1000)); + // Use without unlock + const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); +} else { + // Full collect + unlock + const txHash = await sdk.collectRewards(tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); +} + +// 3. Now claim fees (see fee-management.md) +const claimable = await sdk.getFeesToClaim(account.address, tokenAddress); +if (claimable > 0n) { + await sdk.claimFees(account.address, tokenAddress); +} +``` + +## Key Rules + +- **BPS splits are immutable** -- Set at deployment, cannot be changed +- **Only recipient addresses can change** -- Via `updateRewardRecipient` +- **Only the admin at index N can update recipient N** -- Each admin controls only their index +- **Admin addresses are immutable** -- Set at deployment, cannot be changed +- **`collectRewards` is permissionless** -- Anyone can trigger fee collection +- **Fees are converted to ETH by default** -- Using `FeePreference.Paired` + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `ManagerLocked` | Pool in MEV lock period | Use `collectRewardsWithoutUnlock` or wait | +| `Unauthorized` | Not the admin for that index | Use correct wallet | + +## See Also + +- [fee-management.md](fee-management.md) -- Claiming fees after collection +- [../contracts/liquid-lp-locker.md](../contracts/liquid-lp-locker.md) -- LP Locker contract +- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system diff --git a/rag/sdk/sniper-auction.md b/rag/sdk/sniper-auction.md new file mode 100644 index 0000000..b73e630 --- /dev/null +++ b/rag/sdk/sniper-auction.md @@ -0,0 +1,183 @@ +# SDK Guide: Sniper Auction + +How to read auction state, calculate bids, and participate in MEV sniper auctions. + +## Overview + +The Sniper Auction V2 is an MEV protection mechanism that runs for the first ~20 seconds after token deployment. Early traders compete via gas price bidding, with fees starting at 80% and decaying to 40%. + +**Important:** In most cases, waiting for the auction to end and trading at normal 1% fees is the better strategy. The auction is designed to extract value from snipers, not help them. + +## SDK Methods + +### `getAuctionState(poolId)` + +Returns the current auction state. + +```typescript +const auction = await sdk.getAuctionState(poolId); + +auction.nextAuctionBlock // bigint -- the ONE block where bids are valid +auction.round // bigint -- current round number +auction.gasPeg // bigint -- base gas price reference +auction.currentFee // number -- current fee in uniBps (800000 = 80%) +``` + +### `getAuctionFeeConfig(poolId)` + +Returns the fee decay configuration. + +```typescript +const feeConfig = await sdk.getAuctionFeeConfig(poolId); + +feeConfig.startingFee // number -- e.g., 800000 (80%) +feeConfig.endingFee // number -- e.g., 400000 (40%) +feeConfig.secondsToDecay // bigint -- e.g., 20n +``` + +### `getAuctionDecayStartTime(poolId)` + +Returns the unix timestamp when fee decay started. + +```typescript +const startTime = await sdk.getAuctionDecayStartTime(poolId); +``` + +### `getAuctionMaxRounds()` + +Returns the maximum number of auction rounds. + +```typescript +const maxRounds = await sdk.getAuctionMaxRounds(); +// maxRounds: bigint -- typically 5n +``` + +### `getAuctionGasPriceForBid(gasPeg, bidAmount)` + +Calculates the exact gas price needed for a desired bid amount. + +```typescript +const gasPrice = await sdk.getAuctionGasPriceForBid( + auction.gasPeg, + parseEther("0.001"), // desired bid in ETH +); +``` + +**Formula:** `bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit` where `paymentPerGasUnit = 0.0001 ETH (1e14 wei)`. + +### `bidInAuction(params, gasPrice)` + +Executes an auction bid + swap. The SDK handles: +- Auto-wrapping ETH to WETH for `amountIn` +- Auto-approving SniperUtilV2 for WETH spending +- Setting `gas: 800_000n` (skips estimation) +- Setting both `maxFeePerGas` and `maxPriorityFeePerGas` to `gasPrice` + +```typescript +const result = await sdk.bidInAuction({ + poolKey: rewards.poolKey, + zeroForOne, // true if WETH is currency0 + amountIn: parseEther("0.001"), // WETH to swap + amountOutMinimum: 0n, // set slippage in production! + round: auction.round, // must match current on-chain round + bidAmount: parseEther("0.0005"), // ETH bid (sent as msg.value) +}, gasPrice); + +console.log("Tx:", result.txHash); +``` + +## Complete Bidding Flow + +```typescript +import { LiquidSDK, EXTERNAL } from "liquid-sdk"; +import { parseEther, formatEther } from "viem"; + +const sdk = new LiquidSDK({ publicClient, walletClient }); + +// 1. Get token event for pool ID +const tokenEvent = await sdk.getTokenEvent(tokenAddress); +const poolId = tokenEvent.poolId; + +// 2. Get auction state +const auction = await sdk.getAuctionState(poolId); +const maxRounds = await sdk.getAuctionMaxRounds(); + +if (auction.round >= maxRounds) { + console.log("Auction ended -- trade at normal fees"); + return; +} + +console.log("Fee:", Number(auction.currentFee) / 10000, "%"); +console.log("Round:", auction.round.toString()); + +// 3. Get pool key and swap direction +const rewards = await sdk.getTokenRewards(tokenAddress); +const zeroForOne = rewards.poolKey.currency0.toLowerCase() === EXTERNAL.WETH.toLowerCase(); + +// 4. Calculate gas price +const bidAmount = parseEther("0.001"); +const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, bidAmount); + +// 5. Wait for auction block +while (true) { + const currentBlock = await publicClient.getBlockNumber(); + const gap = Number(auction.nextAuctionBlock - currentBlock); + if (gap <= 0) { console.log("Missed this round"); return; } + if (gap === 1) break; + await new Promise(r => setTimeout(r, gap > 2 ? 500 : 200)); +} + +// 6. Fire bid +const result = await sdk.bidInAuction({ + poolKey: rewards.poolKey, + zeroForOne, + amountIn: parseEther("0.001"), + amountOutMinimum: 0n, + round: auction.round, + bidAmount, +}, gasPrice); + +const receipt = await publicClient.waitForTransactionReceipt({ hash: result.txHash }); +console.log(receipt.status === "success" ? "WON" : "FAILED"); +``` + +## Two Separate ETH Costs + +- **`bidAmount` (msg.value):** ETH sent to the auction as your bid. Goes to protocol/LP. +- **`amountIn` (WETH transfer):** The actual swap input. Pulled from your WETH balance via `transferFrom`. Separate from the bid. + +Total ETH needed: `bidAmount + amountIn + gas fees` + +## Auction Constants + +| Parameter | Value | +|-----------|-------| +| Max rounds | 5 | +| Blocks between rounds | 2 | +| First auction block | Deploy block + 2 | +| Payment per gas unit | 0.0001 ETH (1e14 wei) | +| Default starting fee | 800,000 (80%) | +| Default ending fee | 400,000 (40%) | +| Default decay period | 20 seconds | +| Gas peg | ~6.3M wei (set at pool creation) | + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `GasPriceTooLow()` | txGasPrice <= gasPeg | Use `getAuctionGasPriceForBid()` | +| `NotAuctionBlock()` | Wrong block | Submit 1 block before `nextAuctionBlock` | +| `Unauthorized()` | Fee Locker not configured | Protocol admin issue | +| WETH transferFrom revert | Insufficient WETH | SDK handles auto-wrap | + +## Contract Addresses + +| Contract | Address | +|----------|---------| +| Sniper Auction V2 | `0x187e8627c02c58F31831953C1268e157d3BfCefd` | +| Sniper Util V2 | `0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f` | + +## See Also + +- [../contracts/liquid-mev-protection.md](../contracts/liquid-mev-protection.md) -- MEV contract details +- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- Conceptual overview diff --git a/rag/sdk/vault-lifecycle.md b/rag/sdk/vault-lifecycle.md new file mode 100644 index 0000000..4775ab5 --- /dev/null +++ b/rag/sdk/vault-lifecycle.md @@ -0,0 +1,138 @@ +# SDK Guide: Vault Lifecycle + +How to check vault lockup/vesting status and claim vested tokens. + +## Overview + +The Vault extension locks tokens with a lockup period followed by linear vesting. After the lockup period ends, tokens vest linearly until the vesting end time. + +## Timeline + +``` +Deployment lockupEndTime vestingEndTime + | | | + |--- Lockup ---------|--- Linear Vesting ---| + | (no claims) | (claim vested amt) | (claim all remaining) +``` + +## SDK Methods + +### `getVaultAllocation(tokenAddress)` + +Returns the vault state for a token. + +```typescript +const vault = await sdk.getVaultAllocation(tokenAddress); + +vault.token // Address -- the token +vault.amountTotal // bigint -- total tokens locked +vault.amountClaimed // bigint -- already claimed +vault.lockupEndTime // bigint -- unix timestamp when lockup ends +vault.vestingEndTime // bigint -- unix timestamp when fully vested +vault.admin // Address -- who can claim +``` + +### `getVaultClaimable(tokenAddress)` + +Returns the number of tokens available to claim right now. + +```typescript +const claimable = await sdk.getVaultClaimable(tokenAddress); +// claimable: bigint -- tokens available now (18 decimals) +``` + +### `claimVault(tokenAddress)` + +Claims all currently vested tokens. Requires wallet. Only callable by the vault admin. + +```typescript +if (claimable > 0n) { + const txHash = await sdk.claimVault(tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); +} +``` + +## Complete Example + +```typescript +import { createPublicClient, createWalletClient, http, formatUnits } from "viem"; +import { base } from "viem/chains"; +import { privateKeyToAccount } from "viem/accounts"; +import { LiquidSDK } from "liquid-sdk"; + +const account = privateKeyToAccount("0x..."); +const publicClient = createPublicClient({ chain: base, transport: http() }); +const walletClient = createWalletClient({ account, chain: base, transport: http() }); +const sdk = new LiquidSDK({ publicClient, walletClient }); + +async function checkAndClaimVault(tokenAddress: `0x${string}`) { + // 1. Get vault state + const vault = await sdk.getVaultAllocation(tokenAddress); + const now = BigInt(Math.floor(Date.now() / 1000)); + + console.log("Total locked:", formatUnits(vault.amountTotal, 18), "tokens"); + console.log("Already claimed:", formatUnits(vault.amountClaimed, 18), "tokens"); + console.log("Admin:", vault.admin); + console.log("Lockup ends:", new Date(Number(vault.lockupEndTime) * 1000).toISOString()); + console.log("Vesting ends:", new Date(Number(vault.vestingEndTime) * 1000).toISOString()); + + // 2. Check lockup + if (now < vault.lockupEndTime) { + const remaining = Number(vault.lockupEndTime - now); + console.log(`Still locked for ${remaining} seconds`); + return; + } + + // 3. Check vesting progress + if (now >= vault.vestingEndTime) { + console.log("Fully vested!"); + } else { + const elapsed = Number(now - vault.lockupEndTime); + const total = Number(vault.vestingEndTime - vault.lockupEndTime); + const pct = (elapsed / total * 100).toFixed(1); + console.log(`Vesting: ${pct}% complete`); + } + + // 4. Check claimable + const claimable = await sdk.getVaultClaimable(tokenAddress); + console.log("Claimable now:", formatUnits(claimable, 18), "tokens"); + + // 5. Claim + if (claimable > 0n) { + console.log("Claiming..."); + const txHash = await sdk.claimVault(tokenAddress); + await publicClient.waitForTransactionReceipt({ hash: txHash }); + console.log("Claimed in tx:", txHash); + } +} +``` + +## Vesting Math + +``` +if now < lockupEndTime: + claimable = 0 + +if now >= vestingEndTime: + claimable = amountTotal - amountClaimed + +if lockupEndTime <= now < vestingEndTime: + elapsed = now - lockupEndTime + vestingDuration = vestingEndTime - lockupEndTime + totalVested = amountTotal * elapsed / vestingDuration + claimable = totalVested - amountClaimed +``` + +## Common Errors + +| Error | Cause | Fix | +|-------|-------|-----| +| `LockupNotEnded` | Lockup period hasn't passed | Wait for `lockupEndTime` | +| `Unauthorized` | Caller is not the vault admin | Use admin wallet | +| Zero claimable | All vested tokens already claimed | Wait for more to vest | + +## See Also + +- [../contracts/liquid-vault.md](../contracts/liquid-vault.md) -- Vault contract details +- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview +- [deploy-token.md](deploy-token.md) -- Deploying with vault extension From de21b2272c39fc654ad851710ddb050ccb66b634 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 00:06:32 +0000 Subject: [PATCH 4/5] chore: remove RAG knowledge base from SDK (migrating to ops repo) The RAG knowledge base (27 files, 4,167 lines) is being migrated to Liquid-Protocol-Ops/ops where it belongs alongside operational tooling. The full RAG content is preserved in commit 8ff183d and can be cherry-picked into the ops repo: git cherry-pick 8ff183d https://claude.ai/code/session_01LYo8yCXSHF8vm1KR7nt6b4 --- rag/README.md | 35 --- rag/addresses.json | 111 ---------- rag/concepts/extension-system.md | 166 -------------- rag/concepts/fee-system.md | 197 ----------------- rag/concepts/lp-positions.md | 190 ----------------- rag/concepts/mev-protection.md | 157 -------------- rag/concepts/tick-math.md | 230 -------------------- rag/concepts/token-lifecycle.md | 200 ----------------- rag/contracts/liquid-airdrop.md | 123 ----------- rag/contracts/liquid-extensions.md | 152 ------------- rag/contracts/liquid-factory.md | 140 ------------ rag/contracts/liquid-fee-locker.md | 102 --------- rag/contracts/liquid-hooks.md | 169 --------------- rag/contracts/liquid-lp-locker.md | 136 ------------ rag/contracts/liquid-mev-protection.md | 173 --------------- rag/contracts/liquid-token.md | 104 --------- rag/contracts/liquid-vault.md | 134 ------------ rag/schemas/deploy-params.json | 200 ----------------- rag/schemas/pool-config.json | 171 --------------- rag/sdk/airdrop-system.md | 130 ----------- rag/sdk/deploy-token.md | 285 ------------------------- rag/sdk/fee-management.md | 116 ---------- rag/sdk/pool-reads.md | 124 ----------- rag/sdk/position-builder.md | 172 --------------- rag/sdk/reward-management.md | 129 ----------- rag/sdk/sniper-auction.md | 183 ---------------- rag/sdk/vault-lifecycle.md | 138 ------------ 27 files changed, 4167 deletions(-) delete mode 100644 rag/README.md delete mode 100644 rag/addresses.json delete mode 100644 rag/concepts/extension-system.md delete mode 100644 rag/concepts/fee-system.md delete mode 100644 rag/concepts/lp-positions.md delete mode 100644 rag/concepts/mev-protection.md delete mode 100644 rag/concepts/tick-math.md delete mode 100644 rag/concepts/token-lifecycle.md delete mode 100644 rag/contracts/liquid-airdrop.md delete mode 100644 rag/contracts/liquid-extensions.md delete mode 100644 rag/contracts/liquid-factory.md delete mode 100644 rag/contracts/liquid-fee-locker.md delete mode 100644 rag/contracts/liquid-hooks.md delete mode 100644 rag/contracts/liquid-lp-locker.md delete mode 100644 rag/contracts/liquid-mev-protection.md delete mode 100644 rag/contracts/liquid-token.md delete mode 100644 rag/contracts/liquid-vault.md delete mode 100644 rag/schemas/deploy-params.json delete mode 100644 rag/schemas/pool-config.json delete mode 100644 rag/sdk/airdrop-system.md delete mode 100644 rag/sdk/deploy-token.md delete mode 100644 rag/sdk/fee-management.md delete mode 100644 rag/sdk/pool-reads.md delete mode 100644 rag/sdk/position-builder.md delete mode 100644 rag/sdk/reward-management.md delete mode 100644 rag/sdk/sniper-auction.md delete mode 100644 rag/sdk/vault-lifecycle.md diff --git a/rag/README.md b/rag/README.md deleted file mode 100644 index af9e7b0..0000000 --- a/rag/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Liquid Protocol RAG Knowledge Base - -Structured reference documents for the Liquid Protocol SDK on Base (chain ID 8453). Designed for consumption by AI agents (Docs Agent, LP Simulator) and human developers. - -## Structure - -| Directory | Contents | -|-----------|----------| -| `addresses.json` | All deployed contract addresses with descriptions | -| `schemas/` | JSON Schemas for deployment parameters and config types | -| `contracts/` | Smart contract documentation -- what each contract does, key functions, events | -| `sdk/` | SDK method guides -- how to call each SDK function with examples | -| `concepts/` | Protocol concepts -- end-to-end explanations of how systems work | - -## How to Use - -**AI Agents:** Load individual files into your context window as needed. Each file is self-contained. Start with `concepts/token-lifecycle.md` for a full overview, then drill into specific topics. - -**Docs Agent:** Index all `.md` files for semantic search. Use `addresses.json` and `schemas/*.json` for structured lookups. - -**LP Simulator:** Load `concepts/tick-math.md`, `concepts/lp-positions.md`, and `concepts/fee-system.md` for the math behind liquidity positions and fee calculations. - -## Key Facts - -- **Chain:** Base mainnet (chain ID 8453) -- **Token supply:** Always 100 billion (100,000,000,000) with 18 decimals -- **Paired asset:** WETH (`0x4200000000000000000000000000000000000006`) -- **Default fee:** 1% static on both buys and sells -- **Default MEV:** Sniper Auction V2 (80% to 40% decay over 20 seconds) -- **LP:** Permanently locked -- cannot be withdrawn -- **SDK:** `npm install liquid-sdk viem` - -## Cross-References - -Documents reference each other using relative paths like `../concepts/tick-math.md`. Follow these links for deeper context on any topic. diff --git a/rag/addresses.json b/rag/addresses.json deleted file mode 100644 index 5fb75a3..0000000 --- a/rag/addresses.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "chain": { - "name": "Base", - "chainId": 8453, - "rpc": "https://mainnet.base.org", - "explorer": "https://basescan.org" - }, - "liquidProtocol": { - "FACTORY": { - "address": "0x04F1a284168743759BE6554f607a10CEBdB77760", - "description": "Liquid.sol -- Token factory that orchestrates deployment of ERC-20 tokens, Uniswap V4 pool initialization, LP locking, MEV protection setup, and extension execution.", - "sdkConstant": "ADDRESSES.FACTORY" - }, - "FEE_LOCKER": { - "address": "0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF", - "description": "LiquidFeeLocker -- Escrow contract for LP fees. Stores per-owner, per-token fee balances. Only allowlisted depositors (LP Locker, Sniper Auction) can deposit. Anyone can trigger claim() for a fee owner.", - "sdkConstant": "ADDRESSES.FEE_LOCKER" - }, - "LP_LOCKER_FEE_CONVERSION": { - "address": "0x77247fCD1d5e34A3703AcA898A591Dc7422435f3", - "description": "LiquidLpLockerFeeConversion -- Permanently locks Uniswap V4 LP positions. Collects fees from positions and converts them to a preferred token (ETH by default) before routing to the Fee Locker. Manages reward recipient addresses and BPS splits.", - "sdkConstant": "ADDRESSES.LP_LOCKER_FEE_CONVERSION" - }, - "POOL_EXTENSION_ALLOWLIST": { - "address": "0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa", - "description": "LiquidPoolExtensionAllowlist -- Manages per-pool extension permissions. Owned by the admin Gnosis Safe. Controls which extensions can interact with which pools.", - "sdkConstant": "ADDRESSES.POOL_EXTENSION_ALLOWLIST" - }, - "HOOK_DYNAMIC_FEE_V2": { - "address": "0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC", - "description": "LiquidHookDynamicFeeV2 -- Uniswap V4 hook implementing tick-based dynamic LP fee curves. Fee adjusts based on price volatility with configurable base/max fees, decay periods, and reset logic.", - "sdkConstant": "ADDRESSES.HOOK_DYNAMIC_FEE_V2" - }, - "HOOK_STATIC_FEE_V2": { - "address": "0x9811f10Cd549c754Fa9E5785989c422A762c28cc", - "description": "LiquidHookStaticFeeV2 -- Uniswap V4 hook implementing fixed LP fees. Default is 1% on both buys and sells (100 BPS each direction). This is the default hook used by the SDK.", - "sdkConstant": "ADDRESSES.HOOK_STATIC_FEE_V2" - }, - "VAULT": { - "address": "0xdFCCC93257c20519A9005A2281CFBdF84836d50E", - "description": "LiquidVault -- Extension for token lockup with linear vesting. Tokens are locked for a configurable lockup period, then vest linearly until vestingEndTime. Admin can claim vested tokens.", - "sdkConstant": "ADDRESSES.VAULT" - }, - "SNIPER_AUCTION_V2": { - "address": "0x187e8627c02c58F31831953C1268e157d3BfCefd", - "description": "LiquidSniperAuctionV2 -- MEV protection module. Runs a gas-price auction for 5 rounds (every 2 blocks) starting 2 blocks after deployment. Fee decays from 80% to 40% over 20 seconds. Highest gas-price bidder wins each round.", - "sdkConstant": "ADDRESSES.SNIPER_AUCTION_V2" - }, - "SNIPER_UTIL_V2": { - "address": "0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f", - "description": "LiquidSniperUtilV2 -- Utility contract for executing sniper auction bids. Handles WETH transfer, swap execution, and gas price encoding for bid amount calculation.", - "sdkConstant": "ADDRESSES.SNIPER_UTIL_V2" - }, - "MEV_DESCENDING_FEES": { - "address": "0x8D6B080e48756A99F3893491D556B5d6907b6910", - "description": "LiquidMevDescendingFees -- Alternative MEV protection module. Applies a parabolic fee decay starting at up to 80% (800,000 uniBps) that decays over a maximum of 2 minutes. No auction mechanics -- purely time-based fee decay.", - "sdkConstant": "ADDRESSES.MEV_DESCENDING_FEES" - }, - "AIRDROP_V2": { - "address": "0x1423974d48f525462f1c087cBFdCC20BDBc33CdD", - "description": "LiquidAirdropV2 -- Merkle-based token airdrop extension. Supports mutable merkle root (admin can update), lockup period before claims open, linear vesting after lockup, and admin reclaim of unclaimed tokens.", - "sdkConstant": "ADDRESSES.AIRDROP_V2" - }, - "UNIV4_ETH_DEV_BUY": { - "address": "0x5934097864dC487D21A7B4e4EEe201A39ceF728D", - "description": "LiquidUniv4EthDevBuy -- Extension that executes a dev buy (ETH-to-token swap) through the Uniswap V4 pool in the same transaction as token deployment. Used when devBuy is specified in deployToken().", - "sdkConstant": "ADDRESSES.UNIV4_ETH_DEV_BUY" - }, - "UNIV3_ETH_DEV_BUY": { - "address": "0x376028cfb6b9A120E24Aa14c3FAc4205179c0025", - "description": "LiquidUniv3EthDevBuy -- Extension for dev buy through Uniswap V3 pools (legacy). Swaps ETH for tokens at deployment time via V3 routing.", - "sdkConstant": "ADDRESSES.UNIV3_ETH_DEV_BUY" - }, - "PRESALE_ETH_TO_CREATOR": { - "address": "0x3bca63EcB49d5f917092d10fA879Fdb422740163", - "description": "LiquidPresaleEthToCreator -- Presale extension where participants send ETH and receive tokens. The ETH is forwarded directly to the token creator.", - "sdkConstant": "ADDRESSES.PRESALE_ETH_TO_CREATOR" - }, - "PRESALE_ALLOWLIST": { - "address": "0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B", - "description": "LiquidPresaleAllowlist -- Allowlist-gated presale extension. Only addresses on the allowlist can participate in the presale.", - "sdkConstant": "ADDRESSES.PRESALE_ALLOWLIST" - } - }, - "external": { - "POOL_MANAGER": { - "address": "0x498581fF718922c3f8e6A244956aF099B2652b2b", - "description": "Uniswap V4 PoolManager -- Core contract that manages all V4 pools. All swaps and liquidity operations go through this singleton.", - "sdkConstant": "EXTERNAL.POOL_MANAGER" - }, - "WETH": { - "address": "0x4200000000000000000000000000000000000006", - "description": "Wrapped ETH on Base -- The paired token for all Liquid Protocol pools. Always currency0 in pool keys (numerically lower than token addresses).", - "sdkConstant": "EXTERNAL.WETH" - }, - "UNIVERSAL_ROUTER": { - "address": "0x6fF5693b99212Da76ad316178A184AB56D299b43", - "description": "Uniswap Universal Router -- Entry point for swaps via Uniswap V4. Handles WRAP_ETH, V4_SWAP, and UNWRAP_WETH commands.", - "sdkConstant": "EXTERNAL.UNIVERSAL_ROUTER" - }, - "PERMIT2": { - "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "description": "Uniswap Permit2 -- Token approval management used for sell-side swaps (token-to-ETH). Requires token.approve(PERMIT2) + Permit2.approve(token, UNIVERSAL_ROUTER).", - "sdkConstant": "EXTERNAL.PERMIT2" - } - }, - "adminSafe": { - "address": "0x872c561f699B42977c093F0eD8b4C9a431280c6c", - "description": "Gnosis Safe multisig that owns the Factory and Pool Extension Allowlist. Required for enabling new hooks, lockers, extensions, and MEV modules." - } -} diff --git a/rag/concepts/extension-system.md b/rag/concepts/extension-system.md deleted file mode 100644 index 4db3365..0000000 --- a/rag/concepts/extension-system.md +++ /dev/null @@ -1,166 +0,0 @@ -# Concept: Extension System - -How extensions work in Liquid Protocol: the allowlist process, available extensions, and how they integrate with token deployment. - -## What Are Extensions? - -Extensions are modular smart contracts that receive a portion of the token supply at deployment time. They enable pre-launch token distribution without modifying the core factory logic. - -## How Extensions Work - -### At Deployment - -``` -deployToken() called with extensions[] array - | - v -Factory validates each extension: - 1. Is extension on the allowlist? (isExtensionEnabled) - 2. Is extensionBps within limits? - 3. Total extensionBps <= 9000 (90%)? - | - v -For each extension in order: - 1. Calculate allocation: TOKEN_SUPPLY * extensionBps / 10000 - 2. Factory approves extension to spend tokens - 3. Call extension.receiveTokens(token, amount, extensionData) - | - v -Remaining supply (10000 - sum(extensionBps)) -> LP positions -``` - -### Constraints - -| Constraint | Value | SDK Constant | -|------------|-------|-------------| -| Max extensions per token | 10 | `TOKEN.MAX_EXTENSIONS` | -| Max total supply to extensions | 90% (9000 BPS) | `TOKEN.MAX_EXTENSION_BPS` | -| Remaining supply | Goes to LP | Locked permanently | - -## Extension Allowlist - -Extensions must be approved by the Liquid Protocol admin before they can be used. - -### Current Allowlisted Extensions - -| Extension | Address | Type | -|-----------|---------|------| -| LiquidVault | `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` | Token lockup + vesting | -| LiquidAirdropV2 | `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` | Merkle distribution | -| LiquidUniv4EthDevBuy | `0x5934097864dC487D21A7B4e4EEe201A39ceF728D` | Buy tokens via V4 at launch | -| LiquidUniv3EthDevBuy | `0x376028cfb6b9A120E24Aa14c3FAc4205179c0025` | Buy tokens via V3 at launch | -| LiquidPresaleEthToCreator | `0x3bca63EcB49d5f917092d10fA879Fdb422740163` | Presale with ETH to creator | -| LiquidPresaleAllowlist | `0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B` | Allowlist-gated presale | - -All extensions are covered by 0xMacro (A-3) and Cantina audits of the Clanker v4 codebase. - -### Approval Process for New Extensions - -New extensions require ALL of: - -1. **Full third-party audit** by a recognized firm -2. **Uniswap alignment** -- approval from a Uniswap V4 core contributor -3. **Internal review** by Liquid Protocol engineering lead -4. **Admin Safe approval** -- multisig transaction through Gnosis Safe (`0x872c561f699B42977c093F0eD8b4C9a431280c6c`) - -**Current status:** No plans to approve additional extensions. Contact `slaterg@mog.capital` to apply. - -### On-Chain Mechanism - -```typescript -// Check if extension is enabled -const isEnabled = await sdk.isExtensionEnabled(extensionAddress); -``` - -The allowlist is managed by `LiquidPoolExtensionAllowlist` (`0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa`). - -## Available Extensions (Detail) - -### Dev Buy (Recommended for Deployers) - -The best way to acquire tokens at launch. Swaps ETH for tokens through the Uniswap V4 pool in the same deployment transaction. - -**Key advantage:** Uses normal 1% LP fee, not the 80% MEV auction fee. - -```typescript -// Simplest way -- SDK builds the extension automatically -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", - devBuy: { - ethAmount: parseEther("0.01"), - recipient: account.address, - }, -}); -``` - -Or build manually: - -```typescript -const ext = sdk.buildDevBuyExtension({ - ethAmount: parseEther("0.01"), - recipient: account.address, -}); - -// ext: ExtensionConfig ready to include in extensions[] -``` - -### Vault - -Locks tokens with lockup + linear vesting. See [../contracts/liquid-vault.md](../contracts/liquid-vault.md). - -**Use cases:** Team lockups, investor vesting, treasury management. - -### Airdrop V2 - -Merkle-based token distribution with mutable root, lockup, vesting, and admin reclaim. See [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md). - -**Use cases:** Community airdrops, retroactive rewards, ecosystem grants. - -### Presale (ETH to Creator) - -Participants send ETH and receive tokens. ETH goes directly to the token creator. - -**Use cases:** Pre-launch fundraising, community pre-sales. - -### Presale (Allowlist) - -Allowlist-gated presale. Only approved addresses can participate. - -**Use cases:** VIP pre-sales, KYC-gated sales, partner allocations. - -## Extension Config Structure - -```typescript -interface ExtensionConfig { - extension: Address; // Must be on the allowlist - msgValue: bigint; // ETH to send (0n except for dev buy / presale) - extensionBps: number; // Supply allocation (0-9000 BPS) - extensionData: Hex; // ABI-encoded init data (varies by extension) -} -``` - -## Supply Distribution Example - -``` -Token: 100,000,000,000 (100B) total supply - -Extensions: - Vault: 2000 BPS = 20% = 20,000,000,000 tokens (locked + vesting) - Airdrop: 1000 BPS = 10% = 10,000,000,000 tokens (merkle claims) - Dev Buy: 0 BPS = 0% = buys from pool, not from supply - -LP Positions: 7000 BPS = 70% = 70,000,000,000 tokens - Position 1: 40% of 70B = 28,000,000,000 tokens - Position 2: 50% of 70B = 35,000,000,000 tokens - Position 3: 10% of 70B = 7,000,000,000 tokens -``` - -Note: Dev buy uses 0 BPS because it buys from the pool (post-initialization), not from the supply allocation. - -## See Also - -- [../contracts/liquid-extensions.md](../contracts/liquid-extensions.md) -- Extension contracts -- [../contracts/liquid-vault.md](../contracts/liquid-vault.md) -- Vault details -- [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md) -- Airdrop details -- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with extensions diff --git a/rag/concepts/fee-system.md b/rag/concepts/fee-system.md deleted file mode 100644 index 100dd3d..0000000 --- a/rag/concepts/fee-system.md +++ /dev/null @@ -1,197 +0,0 @@ -# Concept: Fee System - -Complete explanation of how fees work in Liquid Protocol: LP fees (static/dynamic), protocol fees, fee conversion, and reward distribution. - -## Fee Layers - -Every swap through a Liquid Protocol pool passes through multiple fee layers: - -| Layer | Rate | When Active | Goes To | -|-------|------|-------------|---------| -| MEV Fee | 80% to 40% | First ~20s after launch | Protocol + LP | -| LP Fee | 1% (default) | Always | LP position (80%) + Protocol (20%) | -| Protocol Fee | 20% of LP fee | Always | Team fee recipient | - -## Fee Constants - -```typescript -import { FEE } from "liquid-sdk"; - -FEE.DENOMINATOR // 1,000,000 -- Uniswap V4 fee unit (100%) -FEE.PROTOCOL_FEE_NUMERATOR // 200,000 -- 20% of LP fees to protocol -FEE.MAX_LP_FEE // 100,000 -- 10% max LP fee -FEE.MAX_MEV_FEE // 800,000 -- 80% max MEV fee -FEE.BPS // 10,000 -- basis points denominator -``` - -### Fee Unit Conversions - -| BPS | UniBps | Percentage | -|-----|--------|------------| -| 1 | 100 | 0.01% | -| 100 | 10,000 | 1% | -| 500 | 50,000 | 5% | -| 1000 | 100,000 | 10% | -| 8000 | 800,000 | 80% | - -The SDK uses BPS for input (e.g., `100` = 1%) and converts to uniBps internally by multiplying by 100. - -## Static Fees (Default) - -The default hook (`HOOK_STATIC_FEE_V2`) charges a fixed fee on every swap: - -- **Liquid fee (sell):** Fee when swapping token to ETH. Default: 100 BPS (1%) -- **Paired fee (buy):** Fee when swapping ETH to token. Default: 100 BPS (1%) - -```typescript -import { encodeStaticFeePoolData } from "liquid-sdk"; - -// Default: 1% both directions -const poolData = encodeStaticFeePoolData(100, 100); - -// Custom: 0% sell, 2% buy -const poolData = encodeStaticFeePoolData(0, 200); - -// Custom: 0.5% sell, 3% buy -const poolData = encodeStaticFeePoolData(50, 300); -``` - -## Dynamic Fees - -The dynamic fee hook (`HOOK_DYNAMIC_FEE_V2`) adjusts the fee based on price volatility: - -- **Base fee:** Minimum fee during calm markets -- **Max fee:** Maximum fee during high volatility -- **Volatility tracking:** Uses tick movement to detect volatility -- **Decay:** Fee decays back toward base when volatility subsides - -```typescript -import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; - -const poolData = encodeDynamicFeePoolData({ - baseFeeBps: 100, // 1% base - maxFeeBps: 500, // 5% max - referenceTickFilterPeriod: 30, // 30s smoothing - resetPeriod: 120, // 2 min reset - resetTickFilter: 200, // 200 tick threshold - feeControlNumerator: 500000000n, // scaling - decayFilterBps: 7500, // 75% decay -}); -``` - -## Protocol Fee - -A fixed 20% of all LP fees goes to the Liquid Protocol team: - -``` -LP Fee = 1% of swap -Protocol share = 1% * 20% = 0.2% of swap -LP share = 1% * 80% = 0.8% of swap -``` - -The protocol fee is collected by the hook's `afterSwap` callback and routed to the factory's `teamFeeRecipient`. - -## Fee Conversion - -The LP Locker Fee Conversion contract can convert collected fees to a preferred token before distributing: - -| FeePreference | Value | Behavior | -|---------------|-------|----------| -| `Both` | 0 | No conversion -- receive fees in both WETH and token | -| `Paired` | 1 | Convert all fees to WETH/ETH. **Default.** | -| `Liquid` | 2 | Convert all fees to the liquid token | - -```typescript -import { encodeFeeConversionLockerData, FeePreference } from "liquid-sdk"; - -// Default: all fees as ETH -const lockerData = encodeFeeConversionLockerData([FeePreference.Paired]); - -// Two recipients: one gets ETH, one gets token -const lockerData = encodeFeeConversionLockerData([ - FeePreference.Paired, - FeePreference.Liquid, -]); -``` - -## Reward Distribution - -Fees are split among reward recipients according to immutable BPS allocations: - -```typescript -// Set at deployment -const result = await sdk.deployToken({ - rewardAdmins: [walletA, walletB, treasury], - rewardRecipients: [walletA, walletB, treasury], - rewardBps: [5000, 3000, 2000], // 50% / 30% / 20% -}); -``` - -**Rules:** -- BPS splits are IMMUTABLE after deployment -- Recipient addresses can be updated by their admin -- Admin addresses are IMMUTABLE -- BPS must sum to exactly 10000 - -## Complete Fee Flow - -``` -User swaps ETH -> Token (buy) via Universal Router - | - v -Hook.beforeSwap(): - |-- If MEV active: apply 80-40% MEV fee - |-- Calculate LP fee: 1% (10,000 uniBps) - |-- Calculate protocol fee: 1% * 20% = 0.2% - | - v -Uniswap V4 executes swap, deducting fees - | - v -Hook.afterSwap(): - |-- Protocol fee (0.2%) -> Factory team fee recipient - |-- LP fee (0.8%) -> Accrues in LP position - | - v -collectRewards(tokenAddress) called (by anyone) - | - v -LP Locker: - 1. Reads LP positions from PoolManager - 2. Collects accrued fees (WETH + token) - 3. Converts to ETH (FeePreference.Paired) - 4. For each recipient: - fee = totalFees * recipientBps / 10000 - FeeLocker.storeFees(recipient, WETH, fee) - | - v -claimFees(recipient, WETH) called - | - v -FeeLocker transfers WETH to recipient -``` - -## Fee Example (Numbers) - -For a $1000 swap at default settings: - -| Component | Amount | -|-----------|--------| -| Swap amount | $1000 | -| LP fee (1%) | $10 | -| Protocol fee (20% of LP) | $2 | -| Net to LP position | $8 | -| MEV fee (if active, 80%) | $800 additional | - -After collection with single recipient: -- Recipient gets $8 in WETH (per swap) -- Accumulates over many swaps before collection - -## See Also - -- [mev-protection.md](mev-protection.md) -- MEV fee details -- [../contracts/liquid-hooks.md](../contracts/liquid-hooks.md) -- Hook contracts -- [../contracts/liquid-fee-locker.md](../contracts/liquid-fee-locker.md) -- Fee Locker -- [../contracts/liquid-lp-locker.md](../contracts/liquid-lp-locker.md) -- LP Locker -- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming -- [../sdk/reward-management.md](../sdk/reward-management.md) -- SDK reward management diff --git a/rag/concepts/lp-positions.md b/rag/concepts/lp-positions.md deleted file mode 100644 index 82358e8..0000000 --- a/rag/concepts/lp-positions.md +++ /dev/null @@ -1,190 +0,0 @@ -# Concept: LP Positions - -How concentrated liquidity positions work in Liquid Protocol: multi-tranche layouts, tick math, position BPS, and market cap ranges. - -## What Are LP Positions? - -Liquid Protocol uses Uniswap V4 concentrated liquidity. Instead of spreading liquidity across all prices (like V2), liquidity is concentrated into specific price ranges called "positions" or "tranches." - -Each position is defined by: -- **tickLower:** The lower bound (lower market cap) -- **tickUpper:** The upper bound (higher market cap) -- **positionBps:** The percentage of pool supply allocated (in basis points, sum = 10000) - -## Why Multiple Positions? - -Different market cap ranges need different liquidity depths: - -- **Low cap ($20K-$500K):** Tokens spend most of their life here. Needs deep liquidity for smooth trading. -- **Mid cap ($500K-$10M):** Growth phase. Still needs significant liquidity. -- **High cap ($10M-$1B):** Aspirational range. Less liquidity needed since few tokens reach here. - -By allocating more supply to lower ranges, the protocol ensures: -- Better price execution for everyday trading -- Less slippage at common market caps -- Some liquidity at high caps for when tokens "moon" - -## Default Position Layouts - -### 5-Position "Liquid" Layout (SDK Default) - -This is the hardcoded default when no positions are specified: - -| # | Supply | Tick Range | MC Range (~$2000/ETH) | Notes | -|---|--------|-----------|----------------------|-------| -| 1 | 10% | -230,400 to -216,000 | $20K to $83K | Launch range | -| 2 | 50% | -216,000 to -155,000 | $83K to $37M | Core range (50%!) | -| 3 | 15% | -202,000 to -155,000 | $338K to $37M | Overlaps with P2 | -| 4 | 20% | -155,000 to -120,000 | $37M to $1.2B | High cap | -| 5 | 5% | -141,000 to -120,000 | $151M to $1.2B | Overlaps with P4 | - -Positions 2-3 and 4-5 overlap, creating deeper liquidity in their intersection zones. - -```typescript -import { POOL_POSITIONS } from "liquid-sdk"; -const layout = POOL_POSITIONS.Liquid; -``` - -### 3-Tranche Default (via createDefaultPositions) - -Generated dynamically based on current ETH price: - -| # | Supply | USD Range | -|---|--------|-----------| -| 1 | 40% | Starting to $500K | -| 2 | 50% | $500K to $10M | -| 3 | 10% | $10M to $1B | - -```typescript -import { createDefaultPositions } from "liquid-sdk"; -const positions = createDefaultPositions(20_000, 2070); // $20K start, $2070/ETH -``` - -### Single Position (Standard) - -100% of supply in one wide range: - -```typescript -import { POOL_POSITIONS } from "liquid-sdk"; -const standard = POOL_POSITIONS.Standard; -// { tickLower: -230400, tickUpper: -120000, positionBps: 10000 } -``` - -## Position BPS - -BPS (basis points) determine how the pool supply is split across positions: - -``` -Total pool supply = TOKEN_SUPPLY - extensionAllocations -Pool supply distributed per positionBps: - Position 1: poolSupply * positionBps[0] / 10000 - Position 2: poolSupply * positionBps[1] / 10000 - ... -``` - -**Rules:** -- BPS must sum to exactly 10000 (100%) -- Minimum 1 position, maximum 7 -- Each position creates a Uniswap V4 LP NFT held by the LP Locker - -## Market Cap to Tick Conversion - -The relationship between market cap and Uniswap V4 ticks: - -``` -Total supply = 100,000,000,000 (100 billion) -Price per token = marketCapETH / totalSupply -Tick = floor(log(price) / log(1.0001) / tickSpacing) * tickSpacing -``` - -See [tick-math.md](tick-math.md) for detailed formulas and worked examples. - -### Quick Reference Table - -| Market Cap (ETH) | Market Cap (~$2000/ETH) | Tick (spacing=200) | -|------------------|------------------------|---------------------| -| 10 | $20,000 | -230,400 | -| 100 | $200,000 | -207,200 | -| 250 | $500,000 | -198,600 | -| 1,000 | $2,000,000 | -184,400 | -| 5,000 | $10,000,000 | -168,600 | -| 50,000 | $100,000,000 | -145,400 | -| 250,000 | $500,000,000 | -129,000 | -| 500,000 | $1,000,000,000 | -122,200 | - -## Building Custom Positions - -### From USD Market Caps - -```typescript -import { createPositionsUSD } from "liquid-sdk"; - -const positions = createPositionsUSD(20_000, 2070, [ - { upperMarketCapUSD: 100_000, supplyPct: 30 }, - { upperMarketCapUSD: 1_000_000, supplyPct: 40 }, - { upperMarketCapUSD: 100_000_000, supplyPct: 30 }, -]); - -await sdk.deployToken({ - ...positions, - tickIfToken0IsLiquid: positions.tickLower[0], -}); -``` - -### From ETH Market Caps - -```typescript -import { createPositions } from "liquid-sdk"; - -const positions = createPositions(10, [ - { upperMarketCapETH: 100, supplyPct: 40 }, - { upperMarketCapETH: 5000, supplyPct: 50 }, - { upperMarketCapETH: 500000, supplyPct: 10 }, -]); -``` - -### Describing Positions - -```typescript -import { describePositions } from "liquid-sdk"; - -const desc = describePositions(positions, 2070); -for (const p of desc) { - console.log(`P${p.index + 1}: ${p.supplyPct}% | ` + - `$${p.marketCapLowerUSD?.toFixed(0)} - $${p.marketCapUpperUSD?.toFixed(0)}`); -} -``` - -## How Concentrated Liquidity Works - -In a concentrated liquidity position: -- Liquidity is only active when the current price is within the tick range -- Tokens earn fees only while active -- As price moves through the range, the position converts from one token to the other - -For a Liquid token (token/WETH pair): -- At launch (low tick): position is mostly token -- As price rises (tick increases): token converts to WETH through trades -- At upper tick: position is fully WETH (all token sold) - -This means: -- Lower positions "sell" token supply as the price rises -- Higher positions provide liquidity for larger swaps at higher prices -- Overlapping positions create extra depth in their intersection - -## Validation Rules - -When deploying with custom positions: - -1. 1-7 positions allowed -2. `positionBps` must sum to 10000 -3. All ticks divisible by `tickSpacing` (default 200) -4. All `tickLower` values >= `tickIfToken0IsLiquid` -5. At least one position must have `tickLower == tickIfToken0IsLiquid` -6. Tranches must be ordered by ascending market cap (for builder functions) - -## See Also - -- [tick-math.md](tick-math.md) -- Detailed tick math formulas -- [fee-system.md](fee-system.md) -- How positions earn fees -- [../sdk/position-builder.md](../sdk/position-builder.md) -- SDK position builder guide diff --git a/rag/concepts/mev-protection.md b/rag/concepts/mev-protection.md deleted file mode 100644 index 4829653..0000000 --- a/rag/concepts/mev-protection.md +++ /dev/null @@ -1,157 +0,0 @@ -# Concept: MEV Protection - -Why MEV protection matters for token launches and how Liquid Protocol implements it. - -## The Problem - -When a new token is deployed with a Uniswap V4 pool: - -1. The deployment transaction appears in the mempool -2. Sniper bots detect it and prepare buy transactions -3. They buy large amounts at the lowest price in the next block -4. Retail traders arriving seconds later face inflated prices -5. Snipers dump on retail for profit - -This extracts value from both the deployer and the community. Without protection, the first seconds of a token's life are a race won by bots with the fastest infrastructure. - -## Solution: Taxing Early Trading - -Liquid Protocol makes sniping unprofitable by applying extremely high fees during the first seconds after launch. Two modules are available: - -### Module 1: Sniper Auction V2 (Default) - -An auction-based system combining high fees with gas-price bidding. - -**Address:** `0x187e8627c02c58F31831953C1268e157d3BfCefd` - -**Mechanism:** -- Fee starts at **80%** and decays linearly to **40%** over 20 seconds -- Runs in **5 rounds**, every 2 blocks (~4 seconds per round) -- Bidders compete via gas price -- highest gas price wins each round -- Bid amount is encoded as: `(txGasPrice - gasPeg) * paymentPerGasUnit` -- Revenue from bids goes to protocol and LP holders - -**Why it works:** -- At 80% fee, a sniper needs a **5x price increase** just to break even -- The gas price auction ensures only one buyer per round (no sandwich attacks) -- Bid revenue compensates the protocol and LP holders for early trading risk - -**Timeline:** -``` -Deploy (block N) - | - 2 blocks - | -Round 1 (block N+2) -- Fee: ~80% - | - 2 blocks - | -Round 2 (block N+4) -- Fee: ~70% - | - 2 blocks - | -Round 3 (block N+6) -- Fee: ~60% - | - 2 blocks - | -Round 4 (block N+8) -- Fee: ~50% - | - 2 blocks - | -Round 5 (block N+10) -- Fee: ~40% - | -Normal trading at 1% LP fee -``` - -### Module 2: MevDescendingFees - -A simpler time-based approach without auction mechanics. - -**Address:** `0x8D6B080e48756A99F3893491D556B5d6907b6910` - -**Mechanism:** -- Fee starts at up to **80%** and decays **parabolically** (quadratic curve) -- Maximum decay duration: **2 minutes** -- No auction rounds or gas price bidding -- First-come, first-served -- anyone can swap, but at the high fee - -**Why it works:** -- The parabolic curve means fees drop fast at first, then slow down -- This gives a natural price discovery window -- Simpler to understand and configure than the auction - -**Comparison:** - -| Aspect | Sniper Auction | Descending Fees | -|--------|---------------|-----------------| -| Complexity | High | Low | -| Competition | Gas price bidding | First-come | -| Extra revenue | Yes (bid ETH) | No | -| Duration | ~20s (5 rounds) | Up to 2 min | -| Decay curve | Linear | Parabolic | -| Default | Yes | No | - -## Dev Buy: The Best Alternative - -If you are the token deployer and want to acquire tokens early, use the `devBuy` parameter: - -```typescript -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", - devBuy: { - ethAmount: parseEther("0.01"), - recipient: account.address, - }, -}); -``` - -The dev buy executes in the **same transaction** as deployment: -- Uses normal 1% LP fee (NOT auction fees) -- Atomic -- no front-running risk -- Cheapest way to get tokens at launch - -## MEV Block Delay - -Both modules interact with the MEV block delay system. During the delay: -- `collectRewards()` may revert with `ManagerLocked` -- Use `collectRewardsWithoutUnlock()` as an alternative - -```typescript -const delay = await sdk.getMevBlockDelay(); -const unlockTime = await sdk.getPoolUnlockTime(poolId); - -const now = BigInt(Math.floor(Date.now() / 1000)); -if (now < unlockTime) { - console.log("Pool locked, use collectRewardsWithoutUnlock"); -} -``` - -## Custom MEV Configuration - -```typescript -import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; - -// Custom sniper auction: 60% to 20% over 30s -const result = await sdk.deployToken({ - mevModule: ADDRESSES.SNIPER_AUCTION_V2, - mevModuleData: encodeSniperAuctionData({ - startingFee: 600_000, // 60% - endingFee: 200_000, // 20% - secondsToDecay: 30, // 30 seconds - }), - // ...other params -}); - -// Or use descending fees instead -const result2 = await sdk.deployToken({ - mevModule: ADDRESSES.MEV_DESCENDING_FEES, - // ...other params -}); -``` - -## See Also - -- [../contracts/liquid-mev-protection.md](../contracts/liquid-mev-protection.md) -- Contract details -- [../sdk/sniper-auction.md](../sdk/sniper-auction.md) -- SDK auction guide -- [token-lifecycle.md](token-lifecycle.md) -- Full lifecycle including MEV phase diff --git a/rag/concepts/tick-math.md b/rag/concepts/tick-math.md deleted file mode 100644 index c32840c..0000000 --- a/rag/concepts/tick-math.md +++ /dev/null @@ -1,230 +0,0 @@ -# Concept: Tick Math - -Detailed explanation of how Uniswap V4 ticks relate to token prices and market caps in Liquid Protocol. Includes formulas, constants, and worked examples. - -## Core Formulas - -### Constants - -``` -Total supply = 100,000,000,000 (100 billion, 1e11) -Decimals = 18 -Tick base = 1.0001 -Tick spacing = 200 (default) -LOG_BASE = ln(1.0001) = 0.000099995... -``` - -### Market Cap to Price - -``` -price = marketCapETH / totalSupply -price = marketCapETH / 1e11 -``` - -Price is in WETH per token (how much WETH one token costs). - -### Price to Tick - -``` -rawTick = ln(price) / ln(1.0001) -tick = floor(rawTick / tickSpacing) * tickSpacing -``` - -The floor + alignment ensures ticks are always multiples of `tickSpacing`. - -### Tick to Price - -``` -price = 1.0001^tick -``` - -### Tick to Market Cap - -``` -marketCapETH = 1.0001^tick * totalSupply -marketCapETH = 1.0001^tick * 1e11 -``` - -### USD Conversion - -``` -marketCapUSD = marketCapETH * ethPriceUSD -tickFromUSD = tickFromETH(marketCapUSD / ethPriceUSD) -``` - -## SDK Helper Functions - -```typescript -import { - getTickFromMarketCapETH, - getTickFromMarketCapUSD, - marketCapFromTickETH, - marketCapFromTickUSD, -} from "liquid-sdk"; - -// Market cap -> Tick -getTickFromMarketCapETH(10) // -230400 -getTickFromMarketCapETH(200) // -200200 -getTickFromMarketCapUSD(500_000, 2070) // -198600 (approx) -getTickFromMarketCapUSD(10_000_000, 2070) // -168600 (approx) - -// Tick -> Market cap -marketCapFromTickETH(-230400) // ~9.99 ETH -marketCapFromTickUSD(-230400, 2070) // ~$20,680 -marketCapFromTickETH(-120000) // ~603,000 ETH -marketCapFromTickUSD(-120000, 2070) // ~$1.25B -``` - -## Worked Examples - -### Example 1: Default Starting Tick - -**Given:** Starting market cap = 10 ETH, tick spacing = 200 - -``` -price = 10 / 1e11 = 1e-10 -rawTick = ln(1e-10) / ln(1.0001) - = -23.0259 / 0.000099995 - = -230,268.5 -tick = floor(-230268.5 / 200) * 200 - = floor(-1151.34) * 200 - = -1152 * 200 - = -230,400 -``` - -**Result:** tick = -230,400 (the SDK default `DEFAULTS.TICK_IF_TOKEN0_IS_LIQUID`) - -### Example 2: $500K Market Cap at $2070/ETH - -``` -marketCapETH = 500,000 / 2,070 = 241.55 ETH -price = 241.55 / 1e11 = 2.4155e-9 -rawTick = ln(2.4155e-9) / ln(1.0001) = -198,549 -tick = floor(-198549 / 200) * 200 = -198,600 -``` - -**Result:** tick = -198,600 - -### Example 3: $10M Market Cap at $2070/ETH - -``` -marketCapETH = 10,000,000 / 2,070 = 4,830.9 ETH -price = 4830.9 / 1e11 = 4.831e-8 -rawTick = ln(4.831e-8) / ln(1.0001) = -168,537 -tick = floor(-168537 / 200) * 200 = -168,600 -``` - -**Result:** tick = -168,600 - -### Example 4: $1B Market Cap at $2070/ETH - -``` -marketCapETH = 1,000,000,000 / 2,070 = 483,092 ETH -price = 483092 / 1e11 = 4.831e-6 -rawTick = ln(4.831e-6) / ln(1.0001) = -122,337 -tick = floor(-122337 / 200) * 200 = -122,400 -``` - -**Result:** tick = -122,400 - -## Reference Table - -| Market Cap (ETH) | Market Cap (~$2000/ETH) | Tick | Price (WETH/token) | -|------------------|------------------------|------|-------------------| -| 1 | $2,000 | -253,400 | 1e-11 | -| 10 | $20,000 | -230,400 | 1e-10 | -| 100 | $200,000 | -207,200 | 1e-9 | -| 250 | $500,000 | -198,000 | 2.5e-9 | -| 1,000 | $2,000,000 | -184,400 | 1e-8 | -| 5,000 | $10,000,000 | -168,200 | 5e-8 | -| 10,000 | $20,000,000 | -161,200 | 1e-7 | -| 50,000 | $100,000,000 | -145,000 | 5e-7 | -| 100,000 | $200,000,000 | -138,000 | 1e-6 | -| 250,000 | $500,000,000 | -128,800 | 2.5e-6 | -| 500,000 | $1,000,000,000 | -122,000 | 5e-6 | -| 1,000,000 | $2,000,000,000 | -115,000 | 1e-5 | - -*Note: Exact tick values depend on tickSpacing alignment.* - -## Tick Spacing - -Ticks must be multiples of `tickSpacing` (default: 200). This means: - -- Prices can only exist at discrete points: 1.0001^0, 1.0001^200, 1.0001^400, ... -- Each "step" represents a ~2% price change (1.0001^200 = 1.0202) -- Finer tick spacing (e.g., 60) allows tighter ranges but costs more gas -- Coarser tick spacing (e.g., 200) is more gas-efficient but less precise - -## Why Ticks Are Negative - -In Uniswap V4, prices are expressed as `currency0 / currency1`. Since: -- currency0 = WETH (numerically lower address) -- currency1 = liquid token (numerically higher address) - -The price represents "WETH per token." Since tokens have 100B supply, the price per token is extremely small (e.g., 1e-10), resulting in a very negative tick. - -As the token's market cap increases: -- Price per token increases -- Tick moves toward zero (becomes less negative) - -## Position Building with Tick Math - -```typescript -import { createPositionsUSD, describePositions } from "liquid-sdk"; - -// Define positions using USD market caps -const positions = createPositionsUSD(20_000, 2070, [ - { upperMarketCapUSD: 500_000, supplyPct: 40 }, - { upperMarketCapUSD: 10_000_000, supplyPct: 50 }, - { upperMarketCapUSD: 1_000_000_000, supplyPct: 10 }, -]); - -// See what was generated -const desc = describePositions(positions, 2070); -desc.forEach(p => { - console.log(`Position ${p.index}: ${p.supplyPct}%`); - console.log(` Ticks: ${p.tickLower} to ${p.tickUpper}`); - console.log(` ETH: ${p.marketCapLowerETH.toFixed(2)} to ${p.marketCapUpperETH.toFixed(2)}`); - console.log(` USD: $${p.marketCapLowerUSD?.toFixed(0)} to $${p.marketCapUpperUSD?.toFixed(0)}`); -}); -``` - -## Stablecoin Pairing - -For tokens paired with stablecoins instead of WETH: - -```typescript -import { getTickFromMarketCapStable } from "liquid-sdk"; - -// USDC (6 decimals) -const tick = getTickFromMarketCapStable(500_000, 6); - -// DAI (18 decimals) -const tick = getTickFromMarketCapStable(500_000, 18); -``` - -## Implementation - -The tick math utilities are in `src/utils/tick-math.ts`: - -```typescript -const LOG_BASE = Math.log(1.0001); -const TOTAL_SUPPLY = 1e11; - -function getTickFromMarketCapETH(marketCapETH, tickSpacing = 200) { - const price = marketCapETH / TOTAL_SUPPLY; - const rawTick = Math.log(price) / LOG_BASE; - return Math.floor(rawTick / tickSpacing) * tickSpacing; -} - -function marketCapFromTickETH(tick) { - const price = Math.pow(1.0001, tick); - return price * TOTAL_SUPPLY; -} -``` - -## See Also - -- [lp-positions.md](lp-positions.md) -- LP position concepts -- [../sdk/position-builder.md](../sdk/position-builder.md) -- SDK position builder -- [fee-system.md](fee-system.md) -- How positions earn fees diff --git a/rag/concepts/token-lifecycle.md b/rag/concepts/token-lifecycle.md deleted file mode 100644 index 704e665..0000000 --- a/rag/concepts/token-lifecycle.md +++ /dev/null @@ -1,200 +0,0 @@ -# Concept: Token Lifecycle - -End-to-end lifecycle of a Liquid Protocol token, from deployment through trading, fee accrual, and reward distribution. - -## Phase 1: Deployment - -A single `sdk.deployToken()` call triggers an atomic on-chain transaction: - -``` -1. Factory deploys LiquidToken (ERC-20) - - 100 billion supply, 18 decimals - - ERC20 + Permit + Votes + Burnable + IERC7802 - - CREATE2 deterministic address from salt - -2. Factory initializes Uniswap V4 pool - - Paired with WETH - - Starting tick determines initial market cap - - Fee hook (static or dynamic) installed - - Pool key: { WETH, token, 0x800000, 200, hook } - -3. Factory locks LP permanently - - Token supply (minus extensions) split into positions - - Positions created in LP Locker - - Reward recipients and BPS splits configured - - Fee conversion preference set (default: all to ETH) - -4. Factory activates MEV protection - - Sniper Auction V2: 80% to 40% fee decay over 20s - - Or MevDescendingFees: parabolic decay up to 2 min - -5. Factory executes extensions (if any) - - Vault: locks tokens with lockup + vesting - - Airdrop: allocates tokens for merkle claims - - Dev Buy: swaps ETH for tokens at launch - - Presale: allocates tokens for pre-sale - -6. Factory emits TokenCreated event - - Contains all deployment metadata - - Indexed by token address for fast lookup -``` - -**Result:** Token is live with a Uniswap V4 pool, locked LP, and MEV protection active. - -## Phase 2: MEV Protection Window (~20 seconds) - -Immediately after deployment, the MEV protection module is active: - -``` -Block N: Token deployed -Block N+2: First auction round (Sniper Auction) -Block N+4: Second auction round -Block N+6: Third auction round -Block N+8: Fourth auction round -Block N+10: Fifth and final auction round - -Fee decay: 80% at t=0 -> 40% at t=20s (linear) -``` - -During this window: -- Swaps are taxed at 80-40% MEV fee ON TOP of the regular LP fee -- Only auction winners can swap (for Sniper Auction module) -- `collectRewards()` may revert with `ManagerLocked` -- Dev buy (if configured) executes at normal 1% fee, not auction fee - -## Phase 3: Normal Trading - -After the MEV protection window ends: -- Standard LP fees apply (default: 1% buy + 1% sell) -- Anyone can trade via Uniswap V4 Universal Router -- No auction mechanics -- first-come, first-served -- Pool operates as a standard Uniswap V4 concentrated liquidity pool - -### Trade Flow - -``` -User submits swap via Universal Router - | - v -Uniswap V4 PoolManager routes to hook - | - v -Hook.beforeSwap(): - - Calculates LP fee (1% or dynamic) - - Applies protocol fee (20% of LP fee) - | - v -PoolManager executes swap - | - v -Hook.afterSwap(): - - Collects protocol fee portion -``` - -## Phase 4: Fee Accrual - -Fees accumulate in the LP positions held by the LP Locker: - -``` -Trading generates LP fees - |-- 20% of LP fee -> Protocol (team fee recipient) - |-- 80% of LP fee -> LP position (accrues in pool) - | - v -Fees sit in LP positions until collected -``` - -## Phase 5: Fee Collection and Distribution - -Anyone can trigger fee collection: - -``` -sdk.collectRewards(tokenAddress) - | - v -LP Locker collects fees from all positions - | - v -Converts fees to preferred token (ETH by default) - | - v -Splits by reward BPS: - - Recipient A: 70% -> FeeLocker.storeFees(A, WETH, amount) - - Recipient B: 30% -> FeeLocker.storeFees(B, WETH, amount) -``` - -## Phase 6: Fee Claiming - -Recipients withdraw their accumulated fees: - -``` -sdk.claimFees(ownerAddress, tokenAddress) - | - v -FeeLocker transfers WETH to ownerAddress -``` - -## Phase 7: Extension Lifecycle - -### Vault Vesting - -``` -Deploy ------> Lockup ends ------> Vesting ends - | | - |-- Linear vesting ---| - | claim() available | -``` - -### Airdrop Claims - -``` -Deploy --> Merkle root set --> Lockup ends --> Vesting ends - | | - |-- Claims open --| - | | - Admin can reclaim unclaimed after adminClaimTime -``` - -## Phase 8: Ongoing Operations - -After initial setup, these operations continue indefinitely: - -| Operation | Who | Frequency | -|-----------|-----|-----------| -| Trading | Anyone | Continuous | -| Fee collection | Anyone | Periodic (as needed) | -| Fee claiming | Reward recipients | When fees accumulate | -| Vault claiming | Vault admin | As tokens vest | -| Airdrop claiming | Recipients | After lockup ends | -| Metadata updates | Token admin | As needed | -| Recipient updates | Reward admins | As needed | - -## Key Invariants - -1. **LP is permanent** -- Locked liquidity can never be withdrawn -2. **Supply is fixed** -- 100B tokens, no minting after deploy -3. **BPS splits are immutable** -- Reward percentages cannot change -4. **Pool is standard Uniswap V4** -- Fully composable with V4 ecosystem -5. **Fees convert to ETH** -- Default behavior, configurable per recipient - -## Contract Flow Diagram - -``` - Liquid.sol (Factory) - | - +------------+-------------+ - | | | - LiquidToken Hook (V2) LP Locker - (ERC-20) | | | - | | +-- FeeLocker - | | | - MevModule Pool claimFees() - (Auction) Extension -``` - -## See Also - -- [fee-system.md](fee-system.md) -- Detailed fee system -- [mev-protection.md](mev-protection.md) -- MEV protection details -- [lp-positions.md](lp-positions.md) -- LP position concepts -- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- SDK deployment guide diff --git a/rag/contracts/liquid-airdrop.md b/rag/contracts/liquid-airdrop.md deleted file mode 100644 index 52adc1d..0000000 --- a/rag/contracts/liquid-airdrop.md +++ /dev/null @@ -1,123 +0,0 @@ -# LiquidAirdropV2 - -Merkle-based token airdrop extension with mutable root, admin controls, lockup/vesting, and admin reclaim. - -## Contract Details - -- **Address:** `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` -- **SDK Constant:** `ADDRESSES.AIRDROP_V2` -- **Type:** Extension (receives tokens via `receiveTokens`) - -## How It Works - -1. **At deployment:** Factory transfers tokens (per `extensionBps`) to the Airdrop contract -2. **Merkle root set:** Admin sets the merkle root defining who can claim and how much -3. **Lockup period:** Claims are blocked until `lockupEndTime` -4. **Vesting period:** After lockup, tokens vest linearly until `vestingEndTime` -5. **Claiming:** Recipients submit merkle proofs to claim their vested allocation -6. **Admin reclaim:** After `adminClaimTime`, admin can reclaim unclaimed tokens - -## Airdrop Info Structure - -```typescript -interface AirdropInfo { - admin: Address; // Airdrop administrator - merkleRoot: Hex; // Merkle root for claim verification - totalSupply: bigint; // Total tokens allocated to airdrop - totalClaimed: bigint; // Tokens claimed so far - lockupEndTime: bigint; // When claims can begin - vestingEndTime: bigint; // When vesting fully completes - adminClaimTime: bigint; // When admin can reclaim unclaimed - adminClaimed: boolean; // Whether admin has reclaimed -} -``` - -## Key Features - -### Mutable Merkle Root - -The admin can update the merkle root after deployment. This allows: -- Correcting errors in the original distribution -- Adding new recipients -- Adjusting allocations before claims begin - -### Lockup + Vesting - -Same model as the Vault: -- **Lockup:** No claims until `lockupEndTime` -- **Vesting:** Linear vesting from `lockupEndTime` to `vestingEndTime` -- Each recipient's allocation vests independently based on their total amount - -### Admin Reclaim - -After `adminClaimTime`, the admin can reclaim any unclaimed tokens. This prevents tokens from being permanently locked if recipients never claim. - -## SDK Methods - -### Check airdrop state - -```typescript -const info = await sdk.getAirdropInfo(tokenAddress); -console.log("Merkle root:", info.merkleRoot); -console.log("Total supply:", info.totalSupply); -console.log("Total claimed:", info.totalClaimed); -console.log("Lockup ends:", new Date(Number(info.lockupEndTime) * 1000)); -console.log("Vesting ends:", new Date(Number(info.vestingEndTime) * 1000)); -console.log("Admin:", info.admin); -console.log("Admin claimed:", info.adminClaimed); -``` - -### Check claimable for a recipient - -```typescript -const claimable = await sdk.getAirdropClaimable( - tokenAddress, - recipientAddress, - allocatedAmount, // bigint -- total allocation for this recipient (18 decimals) -); -``` - -### Claim airdrop - -```typescript -const txHash = await sdk.claimAirdrop( - tokenAddress, - recipientAddress, - allocatedAmount, // bigint -- must match the merkle leaf - merkleProof, // Hex[] -- generated off-chain from the merkle tree -); -``` - -## Merkle Proof Generation - -Merkle proofs must be generated off-chain from the original airdrop tree. The SDK does not include merkle tree generation -- use a library like `merkletreejs` or `@openzeppelin/merkle-tree`: - -```typescript -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; - -// Build the tree -const values = [ - ["0xRecipient1", "1000000000000000000000"], // 1000 tokens - ["0xRecipient2", "500000000000000000000"], // 500 tokens -]; - -const tree = StandardMerkleTree.of(values, ["address", "uint256"]); -const root = tree.root; // Set this as merkleRoot - -// Get proof for a specific recipient -const proof = tree.getProof(["0xRecipient1", "1000000000000000000000"]); -``` - -## Common Errors - -| Error | Cause | Resolution | -|-------|-------|------------| -| `AlreadyClaimed` | Recipient already claimed their allocation | Check `getAirdropClaimable()` first | -| `LockupNotEnded` | Claims not yet open | Wait for `lockupEndTime` | -| Invalid proof | Proof doesn't match the merkle root | Regenerate proof from the tree | -| `Unauthorized` | Only admin can update root or reclaim | Use admin wallet | - -## See Also - -- [../sdk/airdrop-system.md](../sdk/airdrop-system.md) -- SDK airdrop guide -- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview diff --git a/rag/contracts/liquid-extensions.md b/rag/contracts/liquid-extensions.md deleted file mode 100644 index 5c36a60..0000000 --- a/rag/contracts/liquid-extensions.md +++ /dev/null @@ -1,152 +0,0 @@ -# Extension System - -Extensions are modular contracts that receive a portion of the token supply at deployment. They enable pre-launch token distribution (vesting, airdrops, dev buys, presales) without modifying the core factory logic. - -## How Extensions Work - -1. The deployer includes `ExtensionConfig[]` in the deployment parameters -2. During `deployToken()`, the factory allocates `extensionBps / 10000 * TOKEN_SUPPLY` to each extension -3. The factory calls `extension.receiveTokens(token, amount, extensionData)` for each -4. Extensions hold and distribute tokens according to their logic - -### Constraints - -| Constraint | Value | Description | -|------------|-------|-------------| -| Max extensions | 10 | Per token deployment | -| Max total BPS | 9000 | 90% of total supply across all extensions | -| Remaining supply | Goes to LP | Locked in positions via LP Locker | - -## Extension Allowlist - -Extensions must be explicitly approved by the Liquid Protocol admin. The allowlist is managed by `LiquidPoolExtensionAllowlist` (`0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa`), owned by the Gnosis Safe multisig. - -### Approval Process - -New extensions require ALL of the following: - -1. **Full third-party audit** -- By a recognized firm -2. **Uniswap alignment** -- Approval from a Uniswap V4 core contributor -3. **Internal review** -- Liquid Protocol engineering lead must approve -4. **Admin Safe approval** -- Multisig transaction through the Gnosis Safe - -**Current status:** No plans to add new extensions. Contact `slaterg@mog.capital` and `admin@mog.capital` to apply. - -### Checking Allowlist Status - -```typescript -const isEnabled = await sdk.isExtensionEnabled(extensionAddress); -``` - -## Available Extensions - -### 1. Dev Buy (V4) -- LiquidUniv4EthDevBuy - -**Address:** `0x5934097864dC487D21A7B4e4EEe201A39ceF728D` - -Buys tokens with ETH through the Uniswap V4 pool in the same transaction as deployment. This is the recommended way to acquire tokens at launch because: - -- Uses normal 1% LP fees (NOT auction fees) -- Atomic -- no front-running risk -- Tokens delivered to specified recipient immediately - -```typescript -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", - devBuy: { - ethAmount: parseEther("0.01"), // ETH to spend - recipient: account.address, // Who gets tokens - }, -}); -``` - -The SDK automatically builds the extension config and appends it. The `ethAmount` is sent as `msg.value`. - -### 2. Dev Buy (V3) -- LiquidUniv3EthDevBuy - -**Address:** `0x376028cfb6b9A120E24Aa14c3FAc4205179c0025` - -Legacy extension for buying through Uniswap V3 pools. Same concept as V4 dev buy but routes through V3. Not commonly used for new deployments. - -### 3. Vault -- LiquidVault - -**Address:** `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` - -Locks tokens with a lockup period followed by linear vesting. See [liquid-vault.md](liquid-vault.md) for full details. - -**Use cases:** -- Team token lockup -- Investor vesting schedules -- Treasury management - -### 4. Airdrop V2 -- LiquidAirdropV2 - -**Address:** `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` - -Merkle-based token distribution with lockup/vesting and admin controls. See [liquid-airdrop.md](liquid-airdrop.md) for full details. - -**Use cases:** -- Community airdrops -- Retroactive rewards -- Ecosystem grants - -### 5. Presale (ETH to Creator) -- LiquidPresaleEthToCreator - -**Address:** `0x3bca63EcB49d5f917092d10fA879Fdb422740163` - -Presale where participants send ETH and receive tokens. The ETH is forwarded directly to the token creator (deployer). - -**Use cases:** -- Pre-launch fundraising -- Community pre-sales - -### 6. Presale (Allowlist) -- LiquidPresaleAllowlist - -**Address:** `0xCBb4ccC4B94E23233c14759f4F9629F7dD01f10B` - -Allowlist-gated presale. Only addresses on the allowlist can participate. Provides a controlled pre-sale mechanism. - -**Use cases:** -- VIP/early supporter pre-sales -- KYC-gated sales -- Partner allocations - -## Extension Config Structure - -```typescript -interface ExtensionConfig { - extension: Address; // Contract address (must be allowlisted) - msgValue: bigint; // ETH to send (usually 0n, non-zero for dev buy) - extensionBps: number; // Supply allocation (0-9000 BPS) - extensionData: Hex; // ABI-encoded init data (extension-specific) -} -``` - -## Building Extension Configs Manually - -```typescript -// Dev buy (the SDK does this automatically when devBuy is passed) -const devBuyExt: ExtensionConfig = { - extension: ADDRESSES.UNIV4_ETH_DEV_BUY, - msgValue: parseEther("0.01"), // ETH for the swap - extensionBps: 0, // Dev buy uses 0 BPS (buys from pool) - extensionData: encodeAbiParameters( - [{ type: "address" }], - [recipientAddress], - ), -}; - -// Or use the SDK helper -const devBuyExt = sdk.buildDevBuyExtension({ - ethAmount: parseEther("0.01"), - recipient: recipientAddress, -}); -``` - -## See Also - -- [liquid-vault.md](liquid-vault.md) -- Vault extension details -- [liquid-airdrop.md](liquid-airdrop.md) -- Airdrop extension details -- [../concepts/extension-system.md](../concepts/extension-system.md) -- Conceptual overview -- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with extensions diff --git a/rag/contracts/liquid-factory.md b/rag/contracts/liquid-factory.md deleted file mode 100644 index e30c57a..0000000 --- a/rag/contracts/liquid-factory.md +++ /dev/null @@ -1,140 +0,0 @@ -# Liquid Factory (Liquid.sol) - -The Liquid factory is the core orchestrator of the Liquid Protocol. It deploys ERC-20 tokens with Uniswap V4 liquidity pools in a single atomic transaction. - -## Contract Details - -- **Address:** `0x04F1a284168743759BE6554f607a10CEBdB77760` -- **Chain:** Base (8453) -- **SDK Constant:** `ADDRESSES.FACTORY` -- **Solidity:** `^0.8.28`, optimizer 20,000 runs, EVM target Cancun -- **Inherits:** `OwnerAdmins`, `ReentrancyGuard`, `ILiquid` -- **Owner:** Gnosis Safe `0x872c561f699B42977c093F0eD8b4C9a431280c6c` - -## Protocol Constants - -| Constant | Value | Description | -|----------|-------|-------------| -| `TOKEN_SUPPLY` | `100_000_000_000e18` | 100 billion tokens, 18 decimals | -| `BPS` | `10_000` | Basis points denominator | -| `MAX_EXTENSIONS` | `10` | Maximum extensions per token | -| `MAX_EXTENSION_BPS` | `9000` | Max 90% of supply to extensions | - -## Key Functions - -### `deployToken(DeploymentConfig config)` - -The primary entry point. Orchestrates the full deployment flow: - -1. **Deploy token** -- Uses `LiquidDeployer` library with CREATE2 for deterministic addresses -2. **Initialize Uniswap V4 pool** -- Calls the hook to set up the pool with the provided fee configuration -3. **Lock LP** -- Transfers all liquidity to the LP Locker contract (permanent, non-reversible) -4. **Set up MEV protection** -- Registers the MEV module (sniper auction or descending fees) with the hook -5. **Execute extensions** -- Calls each extension (vault, airdrop, dev buy) in sequence, allocating token supply per `extensionBps` -6. **Emit `TokenCreated` event** -- Contains all deployment data for indexing - -**Input struct (DeploymentConfig):** -``` -DeploymentConfig { - tokenConfig: TokenConfig // name, symbol, image, metadata, context, salt, admin - poolConfig: PoolConfig // hook, pairedToken, tick, tickSpacing, poolData - lockerConfig: LockerConfig // locker, rewards, positions - mevModuleConfig: MevModuleConfig // MEV module + data - extensionConfigs: ExtensionConfig[] // vault, airdrop, dev buy, etc. -} -``` - -### `tokenDeploymentInfo(address token) -> DeploymentInfo` - -Returns the stored deployment information for any token deployed by this factory. - -```typescript -// SDK equivalent -const info = await sdk.getDeploymentInfo(tokenAddress); -// info.token, info.hook, info.locker, info.extensions -``` - -### Module Management (Owner/Admin only) - -| Function | Description | -|----------|-------------| -| `setHook(address, bool)` | Enable/disable a hook contract | -| `setLocker(address, address, bool)` | Enable/disable a locker for a specific hook | -| `setExtension(address, bool)` | Enable/disable an extension | -| `setMevModule(address, bool)` | Enable/disable an MEV module | -| `setDeprecated(bool)` | Pause/unpause the factory | -| `setTeamFeeRecipient(address)` | Set protocol fee recipient | -| `claimTeamFees(address token)` | Claim accumulated protocol fees | - -### Factory Status Checks (SDK) - -```typescript -await sdk.isFactoryDeprecated(); // Is factory still active? -await sdk.isLockerEnabled(locker, hook); // Is locker approved for hook? -await sdk.isExtensionEnabled(extension); // Is extension on allowlist? -``` - -## Events - -### `TokenCreated` - -Emitted on every successful token deployment. Contains all information needed to index and interact with the token. - -| Field | Type | Description | -|-------|------|-------------| -| `msgSender` | `address` | Deployer address | -| `tokenAddress` | `address` | Deployed ERC-20 contract | -| `tokenAdmin` | `address` | Admin who can update metadata | -| `tokenImage` | `string` | Image URL | -| `tokenName` | `string` | Token name | -| `tokenSymbol` | `string` | Token symbol | -| `tokenMetadata` | `string` | JSON metadata | -| `tokenContext` | `string` | JSON deployment context | -| `startingTick` | `int24` | Initial pool tick | -| `poolHook` | `address` | Hook contract used | -| `poolId` | `bytes32` | Uniswap V4 pool ID | -| `pairedToken` | `address` | Quote token (WETH) | -| `locker` | `address` | LP locker contract | -| `mevModule` | `address` | MEV protection module | -| `extensionsSupply` | `uint256` | Total supply allocated to extensions | -| `extensions` | `address[]` | Extension contracts used | - -## Deployment Flow Diagram - -``` -sdk.deployToken(params) - | - v -SDK builds DeploymentConfig with defaults - | - v -Factory.deployToken(config) - |-- 1. LiquidDeployer.deploy(salt) --> new LiquidToken (100B supply) - |-- 2. Hook.initializePool(poolKey, startingTick, poolData) - |-- 3. token.approve(locker, remaining supply) - |-- 4. locker.lockLiquidity(positions, rewards) - |-- 5. For each extension: - | |-- token.approve(extension, extensionBps * supply / BPS) - | |-- extension.receiveTokens(token, amount, data) - |-- 6. mevModule.register(poolId, mevData) - |-- 7. emit TokenCreated(...) - | - v -SDK parses TokenCreated event from receipt - | - v -Returns { tokenAddress, txHash, event } -``` - -## Security - -- Factory is protected by `ReentrancyGuard` on `deployToken` -- Only enabled hooks/lockers/extensions/MEV modules can be used -- Forked from Clanker v4, audited by 0xMacro (A-3) and Cantina -- Owner is a Gnosis Safe multisig -- no single party can modify factory configuration - -## See Also - -- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- SDK deployment guide -- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full lifecycle overview -- [liquid-token.md](liquid-token.md) -- The ERC-20 token contract diff --git a/rag/contracts/liquid-fee-locker.md b/rag/contracts/liquid-fee-locker.md deleted file mode 100644 index 25badbd..0000000 --- a/rag/contracts/liquid-fee-locker.md +++ /dev/null @@ -1,102 +0,0 @@ -# LiquidFeeLocker - -The Fee Locker is an escrow contract that stores accumulated LP fees and allows fee owners to claim them. It acts as the central fee distribution point in the Liquid Protocol. - -## Contract Details - -- **Address:** `0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF` -- **SDK Constant:** `ADDRESSES.FEE_LOCKER` -- **Inherits:** `ILiquidFeeLocker`, `ReentrancyGuard`, `Ownable` -- **Owner:** Gnosis Safe - -## How It Works - -1. **Allowlisted depositors** (LP Locker, Sniper Auction) call `storeFees()` to deposit tokens for a specific fee owner -2. **Fees accumulate** in a `mapping(feeOwner => mapping(token => balance))` ledger -3. **Anyone can call `claim()`** on behalf of a fee owner to transfer their accumulated balance - -The Fee Locker uses balance deltas (checking `balanceOf` before and after transfer) to support fee-on-transfer tokens. - -## Key Functions - -### `storeFees(address feeOwner, address token, uint256 amount)` - -Deposits fees for a specific owner. Only callable by allowlisted depositors. - -- Transfers `amount` of `token` from `msg.sender` to the Fee Locker -- Uses `SafeERC20.safeTransferFrom` for safe transfer -- Records the actual received amount (supports fee-on-transfer tokens) -- Emits `StoreTokens(depositor, feeOwner, token, newBalance, amount)` - -### `claim(address feeOwner, address token)` - -Claims all accumulated fees for a fee owner. Callable by anyone (permissionless). - -- Reads the full balance for `(feeOwner, token)` -- Sets the balance to 0 -- Transfers the full amount to `feeOwner` -- Reverts with `NoFeesToClaim` if balance is 0 -- Emits `ClaimTokens(feeOwner, token, amount)` - -### `availableFees(address feeOwner, address token) -> uint256` - -Read-only. Returns the current claimable balance for a fee owner and token. - -### `addDepositor(address depositor)` - -Owner-only. Adds an address to the allowlist of approved depositors. - -## SDK Methods - -```typescript -// Check total unlocked fees -const available = await sdk.getAvailableFees(ownerAddress, tokenAddress); - -// Check claimable fees (same as available for Fee Locker) -const claimable = await sdk.getFeesToClaim(ownerAddress, tokenAddress); - -// Claim all fees -if (claimable > 0n) { - const txHash = await sdk.claimFees(ownerAddress, tokenAddress); -} -``` - -## Fee Flow - -``` -Trading Activity (Uniswap V4 pool) - | - v -Hook calculates LP fee (e.g., 1%) - |-- 20% -> Protocol (factory team fee) - |-- 80% -> LP position - | - v -LP Locker collects fees from positions - | - v -LP Locker Fee Conversion converts to preferred token (ETH) - | - v -LP Locker calls FeeLocker.storeFees(feeOwner, WETH, amount) - |-- Split by reward BPS: e.g., 70% to recipient A, 30% to recipient B - | - v -Fee recipients (or anyone on their behalf) call FeeLocker.claim() - | - v -WETH transferred to fee owner's wallet -``` - -## Security - -- Protected by `ReentrancyGuard` on both `storeFees` and `claim` -- Only allowlisted depositors can store fees (prevents unauthorized balance inflation) -- Uses `SafeERC20` for all transfers -- Balance delta tracking prevents fee-on-transfer token accounting issues - -## See Also - -- [liquid-lp-locker.md](liquid-lp-locker.md) -- LP Locker that deposits into Fee Locker -- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming guide -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system overview diff --git a/rag/contracts/liquid-hooks.md b/rag/contracts/liquid-hooks.md deleted file mode 100644 index 6e0106e..0000000 --- a/rag/contracts/liquid-hooks.md +++ /dev/null @@ -1,169 +0,0 @@ -# Liquid Hook System - -The hook contracts are Uniswap V4 BaseHook implementations that control fee logic, MEV module integration, and pool lifecycle for Liquid Protocol pools. - -## Architecture - -``` -LiquidHookV2 (abstract base) - |-- LiquidHookStaticFeeV2 (fixed fees, default) - |-- LiquidHookDynamicFeeV2 (volatility-responsive fees) -``` - -## LiquidHookV2 (Base Contract) - -Abstract contract that all Liquid hooks inherit from. - -### Constants - -| Constant | Value | Description | -|----------|-------|-------------| -| `MAX_LP_FEE` | `100_000` | Max LP fee: 10% (100,000 / 1,000,000) | -| `MAX_MEV_LP_FEE` | `800_000` | Max MEV fee: 80% | -| `PROTOCOL_FEE_NUMERATOR` | `200_000` | 20% of LP fees go to protocol | -| `FEE_DENOMINATOR` | `1_000_000` | 100% in Uniswap V4 fee units | -| `MAX_MEV_MODULE_DELAY` | `2 minutes` | Max duration for MEV module effects | - -### Per-Pool State - -| Mapping | Type | Description | -|---------|------|-------------| -| `liquidIsToken0` | `PoolId => bool` | Whether the liquid token is token0 in the pair | -| `locker` | `PoolId => address` | LP locker for this pool | -| `mevModule` | `PoolId => address` | MEV protection module | -| `mevModuleEnabled` | `PoolId => bool` | Whether MEV module is active | -| `poolCreationTimestamp` | `PoolId => uint256` | Block timestamp when pool was created | -| `poolExtension` | `PoolId => address` | Optional pool extension | - -### Hook Callbacks - -The hook implements Uniswap V4 lifecycle callbacks: - -1. **`afterInitialize`** -- Called when pool is created. Records pool metadata, sets up MEV module -2. **`beforeSwap`** -- Called before every swap. Applies MEV fee if active, calculates LP fee -3. **`afterSwap`** -- Called after every swap. Collects protocol fee portion - -### Fee Calculation Flow (per swap) - -``` -Swap initiated - | - v -beforeSwap(): - 1. Check if MEV module is active - 2. If yes: apply MEV fee (up to 80%) - 3. Calculate LP fee (static or dynamic) - 4. Apply protocol fee = LP fee * 200,000 / 1,000,000 (20%) - | - v -Uniswap V4 executes swap with calculated fee - | - v -afterSwap(): - 1. Collect protocol fee portion - 2. Route to factory for team fee recipient -``` - -## LiquidHookStaticFeeV2 (Default) - -Fixed fee hook. The fee is set at pool initialization and never changes. - -- **Address:** `0x9811f10Cd549c754Fa9E5785989c422A762c28cc` -- **SDK Constant:** `ADDRESSES.HOOK_STATIC_FEE_V2` -- **Default fees:** 1% on buys (paired fee), 1% on sells (liquid fee) - -### Pool Data Encoding - -```typescript -import { encodeStaticFeePoolData } from "liquid-sdk"; - -// 1% both directions (default) -const poolData = encodeStaticFeePoolData(100, 100); -// Args: (liquidFeeBps, pairedFeeBps) -// liquidFeeBps: fee when selling token (token -> ETH) -// pairedFeeBps: fee when buying token (ETH -> token) - -// 0% sell, 2% buy -const customData = encodeStaticFeePoolData(0, 200); -``` - -The encoding uses two layers: -1. **Inner:** `abi.encode(uint24 liquidFee, uint24 pairedFee)` in uniBps (BPS * 100) -2. **Outer:** `PoolInitializationData { extension, extensionData, feeData }` - -## LiquidHookDynamicFeeV2 - -Volatility-responsive fee hook. Fee adjusts based on tick movement (price volatility). - -- **Address:** `0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC` -- **SDK Constant:** `ADDRESSES.HOOK_DYNAMIC_FEE_V2` - -### Configuration Parameters - -| Parameter | Type | Description | -|-----------|------|-------------| -| `baseFeeBps` | `number` | Minimum fee in BPS (e.g., 100 = 1%) | -| `maxFeeBps` | `number` | Maximum fee in BPS (e.g., 500 = 5%) | -| `referenceTickFilterPeriod` | `number` | Seconds for reference tick filtering | -| `resetPeriod` | `number` | Seconds before fee state resets | -| `resetTickFilter` | `number` | Tick movement threshold for reset | -| `feeControlNumerator` | `bigint` | Scaling constant for fee curve | -| `decayFilterBps` | `number` | Decay filter in BPS | - -### Pool Data Encoding - -```typescript -import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; - -const poolData = encodeDynamicFeePoolData({ - baseFeeBps: 100, // 1% base fee - maxFeeBps: 500, // 5% max fee - referenceTickFilterPeriod: 30, // 30s filter period - resetPeriod: 120, // 2 min reset - resetTickFilter: 200, // 200 tick threshold - feeControlNumerator: 500000000n, // scaling constant - decayFilterBps: 7500, // 75% decay filter -}); - -const result = await sdk.deployToken({ - hook: ADDRESSES.HOOK_DYNAMIC_FEE_V2, - poolData, - // ...other params -}); -``` - -### Reading Dynamic Fee State - -```typescript -// Get pool configuration (immutable after deploy) -const config = await sdk.getPoolConfig(poolId); -// config.baseFee, config.maxLpFee, config.referenceTickFilterPeriod, etc. - -// Get current fee state (changes with each swap) -const state = await sdk.getPoolFeeState(poolId); -// state.referenceTick, state.resetTick, state.appliedVR, state.prevVA -``` - -## Pool Key Structure - -Every Liquid pool has a standard pool key: - -```typescript -const poolKey = { - currency0: EXTERNAL.WETH, // 0x4200...0006 (always lower) - currency1: tokenAddress, // deployed token (always higher) - fee: 8388608, // 0x800000 = dynamic fee flag - tickSpacing: 200, // Liquid default - hooks: hookAddress, // which hook contract -}; -``` - -The `fee: 0x800000` is NOT an actual fee value -- it signals to Uniswap V4 that the hook controls the fee dynamically via `beforeSwap`. - -Pool ID is: `keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks))` - -## See Also - -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system explanation -- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- MEV module details -- [../sdk/deploy-token.md](../sdk/deploy-token.md) -- Deploying with custom hooks diff --git a/rag/contracts/liquid-lp-locker.md b/rag/contracts/liquid-lp-locker.md deleted file mode 100644 index 864f9a1..0000000 --- a/rag/contracts/liquid-lp-locker.md +++ /dev/null @@ -1,136 +0,0 @@ -# LiquidLpLockerFeeConversion - -The LP Locker permanently locks Uniswap V4 LP positions and manages fee collection, conversion, and distribution to reward recipients. - -## Contract Details - -- **Address:** `0x77247fCD1d5e34A3703AcA898A591Dc7422435f3` -- **SDK Constant:** `ADDRESSES.LP_LOCKER_FEE_CONVERSION` -- **Role:** Default LP locker for all Liquid Protocol tokens - -## What It Does - -1. **Locks LP permanently** -- Liquidity positions are transferred to this contract and can never be withdrawn. This is a core anti-rug guarantee. -2. **Collects fees** -- Periodically collects accrued LP fees from Uniswap V4 positions -3. **Converts fees** -- Converts collected fees to a preferred token per recipient (ETH by default) -4. **Distributes fees** -- Routes converted fees to the Fee Locker, split by reward BPS - -## Fee Conversion (FeePreference) - -Each reward recipient can specify how they want their fees: - -| Value | Enum | Behavior | -|-------|------|----------| -| 0 | `FeePreference.Both` | No conversion -- receive fees in whichever token accrues | -| 1 | `FeePreference.Paired` | Convert all fees to paired token (WETH/ETH). **Default.** | -| 2 | `FeePreference.Liquid` | Convert all fees to the liquid token | - -```typescript -import { encodeFeeConversionLockerData, FeePreference } from "liquid-sdk"; - -// Single recipient, all fees as ETH (default) -const lockerData = encodeFeeConversionLockerData([FeePreference.Paired]); - -// Two recipients: first gets ETH, second gets the token -const lockerData = encodeFeeConversionLockerData([ - FeePreference.Paired, - FeePreference.Liquid, -]); -``` - -## Reward Configuration - -Set at deployment time. The BPS splits are immutable, but recipient addresses can be updated by their admins. - -| Field | Description | Mutable? | -|-------|-------------|----------| -| `rewardRecipients` | Who receives fees | Yes (by admin) | -| `rewardAdmins` | Who can update each recipient | No | -| `rewardBps` | Split percentages (sum = 10000) | No | - -### Reading Reward Config - -```typescript -const rewards = await sdk.getTokenRewards(tokenAddress); -// rewards.rewardRecipients: Address[] -// rewards.rewardBps: number[] -- e.g., [7000, 3000] = 70%/30% -// rewards.rewardAdmins: Address[] -// rewards.poolKey: PoolKey -// rewards.positionId: bigint -// rewards.numPositions: bigint -``` - -### Updating Recipients - -```typescript -// Only the admin at index N can update recipient N -const txHash = await sdk.updateRewardRecipient( - tokenAddress, - 0n, // reward index (bigint) - newRecipientAddress, -); -``` - -## Collecting Rewards - -Two methods for collecting fees from LP positions: - -### `collectRewards(tokenAddress)` -- Full collect + unlock - -Collects all accrued LP fees, converts them per fee preferences, and distributes to the Fee Locker. Also unlocks the LP position (related to MEV block delay). - -```typescript -const txHash = await sdk.collectRewards(tokenAddress); -``` - -**Note:** Will revert with `ManagerLocked` during the MEV block delay period. Check `getPoolUnlockTime()` first. - -### `collectRewardsWithoutUnlock(tokenAddress)` -- Collect only - -Same as above but skips the unlock step. Useful during the MEV protection window or to avoid MEV during collection. - -```typescript -const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); -``` - -## Fee Distribution Flow - -``` -collectRewards(tokenAddress) called - | - v -LP Locker reads positions from Uniswap V4 PoolManager - | - v -Collects accrued fees (token0 + token1) - | - v -For each reward recipient: - |-- Check FeePreference - |-- If Paired: swap token fees -> WETH via pool - |-- If Liquid: swap WETH fees -> token via pool - |-- If Both: no conversion - | - v -Calculate recipient share: totalFees * recipientBps / 10000 - | - v -Call FeeLocker.storeFees(recipient, token, amount) - | - v -Recipient can call sdk.claimFees() to withdraw -``` - -## Key Invariants - -- LP is **permanently locked** -- there is no withdraw or unlock path for the liquidity itself -- Reward BPS splits are **immutable** -- set at deployment, cannot be changed -- Only reward admins can update recipient addresses -- Fee conversion uses the same Uniswap V4 pool for swaps - -## See Also - -- [liquid-fee-locker.md](liquid-fee-locker.md) -- Where converted fees are stored -- [../sdk/reward-management.md](../sdk/reward-management.md) -- SDK reward guide -- [../sdk/fee-management.md](../sdk/fee-management.md) -- SDK fee claiming guide -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Full fee system overview diff --git a/rag/contracts/liquid-mev-protection.md b/rag/contracts/liquid-mev-protection.md deleted file mode 100644 index 79683a5..0000000 --- a/rag/contracts/liquid-mev-protection.md +++ /dev/null @@ -1,173 +0,0 @@ -# MEV Protection Modules - -Liquid Protocol provides two MEV protection modules that activate at token launch to prevent sniper bots from extracting value in the first seconds of trading. - -## Why MEV Protection Matters - -When a new token is deployed, its pool starts at a low market cap. Without protection: -- Sniper bots detect the deployment transaction in the mempool -- They immediately buy large amounts at the lowest price -- They dump on retail traders who arrive seconds later -- The deployer and community get worse prices - -MEV protection taxes early trading activity, making sniping unprofitable. - -## Module 1: SniperAuctionV2 (Default) - -An auction-based system where early traders compete via gas price bidding. - -- **Address:** `0x187e8627c02c58F31831953C1268e157d3BfCefd` -- **SDK Constant:** `ADDRESSES.SNIPER_AUCTION_V2` -- **Utility contract:** `0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f` (`SNIPER_UTIL_V2`) - -### Default Configuration - -| Parameter | Value | Description | -|-----------|-------|-------------| -| Starting fee | 800,000 (80%) | Fee at auction start | -| Ending fee | 400,000 (40%) | Fee floor after decay | -| Decay period | 20 seconds | Linear decay from start to end | -| Max rounds | 5 | Total auction rounds | -| Blocks between rounds | 2 | One round every 2 blocks | -| First auction block | Deploy block + 2 | Auction starts 2 blocks after deployment | -| Payment per gas unit | 0.0001 ETH (1e14 wei) | Converts gas price delta to bid amount | - -### How It Works - -1. **Token deploys** -- Pool is created, sniper auction activates -2. **Fee decay begins** -- MEV fee starts at 80% and decays linearly to 40% over 20 seconds -3. **Rounds** -- Every 2 blocks, an auction round opens for exactly 1 block -4. **Gas price bidding** -- Bidders set their gas price above the `gasPeg` (base fee at deployment). The difference encodes their bid: `bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit` -5. **Winner** -- Highest gas price transaction in each auction block wins the swap -6. **Revenue** -- Bid amounts (ETH) flow to protocol and LP holders via the Fee Locker -7. **Auction ends** -- After 5 rounds (~10 blocks, ~20 seconds), normal trading resumes at standard LP fees - -### Bid Encoding Formula - -``` -bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit - -Where: - txGasPrice = both maxFeePerGas AND maxPriorityFeePerGas (must be equal) - gasPeg = base fee recorded at pool creation (~6.3M wei on Base) - paymentPerGasUnit = 0.0001 ETH (1e14 wei) -``` - -### Fee Impact - -| Time | Fee | Tokens Received | Breakeven Multiple | -|------|-----|-----------------|-------------------| -| 0s (start) | 80% | 20% of fair value | 5x | -| 5s | 70% | 30% of fair value | 3.3x | -| 10s | 60% | 40% of fair value | 2.5x | -| 15s | 50% | 50% of fair value | 2x | -| 20s (end) | 40% | 60% of fair value | 1.7x | -| After auction | 1% (LP fee) | 99% of fair value | 1.01x | - -### SDK Methods - -```typescript -// Read auction state -const auction = await sdk.getAuctionState(poolId); -// auction.nextAuctionBlock, auction.round, auction.gasPeg, auction.currentFee - -// Read fee config -const feeConfig = await sdk.getAuctionFeeConfig(poolId); -// feeConfig.startingFee, feeConfig.endingFee, feeConfig.secondsToDecay - -// Get decay start time -const startTime = await sdk.getAuctionDecayStartTime(poolId); - -// Get max rounds -const maxRounds = await sdk.getAuctionMaxRounds(); - -// Calculate gas price for desired bid -const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, parseEther("0.001")); - -// Execute bid (auto-wraps WETH, approves SniperUtil, sets gas) -const result = await sdk.bidInAuction({ - poolKey: rewards.poolKey, - zeroForOne, - amountIn: parseEther("0.001"), - amountOutMinimum: 0n, - round: auction.round, - bidAmount: parseEther("0.0005"), -}, gasPrice); -``` - -### Custom Sniper Auction Config - -```typescript -import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; - -const mevModuleData = encodeSniperAuctionData({ - startingFee: 800_000, // 80% starting fee - endingFee: 400_000, // 40% ending fee - secondsToDecay: 20, // 20 seconds decay -}); - -const result = await sdk.deployToken({ - name: "Custom MEV Token", - symbol: "CMEV", - mevModule: ADDRESSES.SNIPER_AUCTION_V2, - mevModuleData, -}); -``` - -## Module 2: MevDescendingFees - -A simpler time-based MEV protection without auction mechanics. Fee decays parabolically from a high starting point. - -- **Address:** `0x8D6B080e48756A99F3893491D556B5d6907b6910` -- **SDK Constant:** `ADDRESSES.MEV_DESCENDING_FEES` - -### Configuration - -| Parameter | Constraint | -|-----------|------------| -| Max initial fee | 800,000 (80%) | -| Max decay duration | 2 minutes (120 seconds) | -| Decay curve | Parabolic (quadratic) | -| Auction mechanics | None -- purely time-based | - -### How It Works - -1. **Token deploys** -- Descending fee module activates -2. **High initial fee** -- Swaps are taxed at up to 80% -3. **Parabolic decay** -- Fee decreases over time following a quadratic curve (faster at start, slower at end) -4. **Normal trading** -- After the decay period (max 2 min), standard LP fees apply - -### Key Differences from Sniper Auction - -| Aspect | Sniper Auction | Descending Fees | -|--------|---------------|-----------------| -| Mechanism | Gas price bidding | Time-based decay | -| Competition | Highest gas wins | First-come, first-served | -| Revenue | Bid ETH to protocol/LP | Fees from swaps only | -| Complexity | Complex (rounds, gas encoding) | Simple (just time) | -| Duration | ~20s (5 rounds) | Up to 2 minutes | -| Decay curve | Linear | Parabolic | - -## MEV Block Delay - -Both modules interact with the MEV block delay system: - -```typescript -// Check block delay -const delay = await sdk.getMevBlockDelay(); - -// Check when pool unlocks -const unlockTime = await sdk.getPoolUnlockTime(poolId); -const now = BigInt(Math.floor(Date.now() / 1000)); - -if (now < unlockTime) { - console.log("Pool still locked -- collectRewards will revert"); - // Use collectRewardsWithoutUnlock instead -} -``` - -## See Also - -- [../sdk/sniper-auction.md](../sdk/sniper-auction.md) -- SDK auction guide -- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- Conceptual overview -- [liquid-hooks.md](liquid-hooks.md) -- Hook integration with MEV modules diff --git a/rag/contracts/liquid-token.md b/rag/contracts/liquid-token.md deleted file mode 100644 index d878e7f..0000000 --- a/rag/contracts/liquid-token.md +++ /dev/null @@ -1,104 +0,0 @@ -# LiquidToken (ERC-20) - -The token contract deployed by the Liquid factory for every token launch. A feature-rich ERC-20 with governance, burning, and cross-chain support. - -## Contract Details - -- **Deployed per token** -- Each `deployToken()` call creates a new LiquidToken instance via CREATE2 -- **Solidity:** `^0.8.28` -- **License:** MIT - -## Token Properties - -| Property | Value | -|----------|-------| -| **Total Supply** | 100,000,000,000 (100 billion) | -| **Decimals** | 18 | -| **Supply (raw)** | `100_000_000_000 * 10^18` = `100_000_000_000_000_000_000_000_000_000` | -| **SDK Constant** | `TOKEN.SUPPLY` = `100_000_000_000n * 10n ** 18n` | -| **Minting** | Fixed supply, no additional minting possible | - -## Inherited Interfaces - -LiquidToken inherits from multiple OpenZeppelin and OP Stack contracts: - -| Interface | Source | What It Provides | -|-----------|--------|-----------------| -| **ERC20** | OpenZeppelin | Standard token: `transfer`, `approve`, `balanceOf`, `allowance` | -| **ERC20Permit** | OpenZeppelin | Gasless approvals via EIP-2612 signed messages | -| **ERC20Votes** | OpenZeppelin | On-chain governance: `delegate`, `getVotes`, `getPastVotes` | -| **ERC20Burnable** | OpenZeppelin | Token burning: `burn`, `burnFrom` | -| **IERC7802** | OP Stack | Cross-chain token standard for Optimism Superchain bridges | -| **IERC165** | OpenZeppelin | Interface detection: `supportsInterface` | - -## Key Features - -### Permit (EIP-2612) - -Allows gasless token approvals. Users sign a permit off-chain, and anyone can submit the signature to grant approval: - -```typescript -// No on-chain approve() transaction needed -// The permit signature can be submitted by anyone -``` - -### Votes (ERC-5805) - -Full on-chain governance support. Token holders can delegate their voting power: - -```typescript -// Delegate voting power -await tokenContract.write.delegate([delegateAddress]); - -// Check voting power -const votes = await tokenContract.read.getVotes([address]); -``` - -### Burnable - -Token holders can burn their own tokens, permanently reducing the circulating supply: - -```typescript -// Burn own tokens -await tokenContract.write.burn([amount]); - -// Burn from approved address -await tokenContract.write.burnFrom([owner, amount]); -``` - -### Cross-Chain (IERC7802) - -Supports the Optimism Superchain cross-chain token standard. Enables native bridging across OP Stack chains without wrapped token contracts. Only the `SuperchainTokenBridge` predeploy can call `crosschainMint` and `crosschainBurn`. - -## Admin Functions - -The token has an admin (set at deployment, defaults to deployer) who can: - -| Function | Description | SDK Method | -|----------|-------------|------------| -| `updateImage(string)` | Change the token image URL | `sdk.updateImage(token, url)` | -| `updateMetadata(string)` | Change the token metadata JSON | `sdk.updateMetadata(token, json)` | -| `updateAdmin(address)` | Transfer admin role | Direct contract call | - -## Supply Distribution - -At deployment, the 100B supply is distributed as: - -``` -100B Total Supply - |-- Extensions allocation (0-90%, per extensionBps) - | |-- Vault (lockup + vesting) - | |-- Airdrop (merkle distribution) - | |-- Dev Buy (swap through pool) - | |-- Presale - | - |-- Remaining → LP positions (locked permanently) - |-- Position 1: X% of remaining - |-- Position 2: Y% of remaining - |-- ... (up to 7 positions) -``` - -## See Also - -- [liquid-factory.md](liquid-factory.md) -- Factory that deploys tokens -- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full lifecycle diff --git a/rag/contracts/liquid-vault.md b/rag/contracts/liquid-vault.md deleted file mode 100644 index 59cb59a..0000000 --- a/rag/contracts/liquid-vault.md +++ /dev/null @@ -1,134 +0,0 @@ -# LiquidVault - -The Vault extension enables token lockup with linear vesting. Tokens are locked for a configurable lockup period, then vest linearly over a vesting period. - -## Contract Details - -- **Address:** `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` -- **SDK Constant:** `ADDRESSES.VAULT` -- **Type:** Extension (receives tokens via `receiveTokens`) - -## How It Works - -1. **At deployment:** The factory transfers a portion of token supply (per `extensionBps`) to the Vault -2. **Lockup period:** Tokens are fully locked until `lockupEndTime`. No claims possible. -3. **Vesting period:** After lockup ends, tokens vest linearly from `lockupEndTime` to `vestingEndTime` -4. **Claiming:** The vault admin can claim vested tokens at any time after lockup ends - -### Vesting Formula - -``` -if (now < lockupEndTime): - claimable = 0 - -if (now >= vestingEndTime): - claimable = amountTotal - amountClaimed - -if (lockupEndTime <= now < vestingEndTime): - elapsed = now - lockupEndTime - vestingDuration = vestingEndTime - lockupEndTime - totalVested = amountTotal * elapsed / vestingDuration - claimable = totalVested - amountClaimed -``` - -## Vault Allocation Structure - -```typescript -interface VaultAllocation { - token: Address; // The token being vested - amountTotal: bigint; // Total tokens locked in vault - amountClaimed: bigint; // Tokens already claimed - lockupEndTime: bigint; // Unix timestamp: lockup ends - vestingEndTime: bigint; // Unix timestamp: vesting completes - admin: Address; // Who can claim -} -``` - -## SDK Methods - -### Check vault state - -```typescript -const vault = await sdk.getVaultAllocation(tokenAddress); -console.log("Total locked:", vault.amountTotal); -console.log("Already claimed:", vault.amountClaimed); -console.log("Lockup ends:", new Date(Number(vault.lockupEndTime) * 1000)); -console.log("Vesting ends:", new Date(Number(vault.vestingEndTime) * 1000)); -console.log("Admin:", vault.admin); -``` - -### Check claimable amount - -```typescript -const claimable = await sdk.getVaultClaimable(tokenAddress); -// claimable: bigint -- tokens available to claim right now -``` - -### Claim vested tokens - -```typescript -if (claimable > 0n) { - const txHash = await sdk.claimVault(tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); -} -``` - -## Configuration at Deployment - -To include a vault in your token deployment, add it as an extension: - -```typescript -// The vault extension data must encode: -// - lockupDuration (uint256): seconds of lockup after deployment -// - vestingDuration (uint256): seconds of linear vesting after lockup -// - admin (address): who can claim -``` - -The vault receives `extensionBps / 10000 * TOKEN_SUPPLY` tokens at deployment. - -## Common Errors - -| Error | Cause | Resolution | -|-------|-------|------------| -| `LockupNotEnded` | Attempting to claim before `lockupEndTime` | Wait for lockup to end | -| `Unauthorized` | Caller is not the vault admin | Use the admin wallet | -| Zero claimable | All vested tokens already claimed | Wait for more to vest | - -## Example: Full Vault Lifecycle - -```typescript -import { LiquidSDK } from "liquid-sdk"; -import { formatUnits } from "viem"; - -const sdk = new LiquidSDK({ publicClient, walletClient }); - -// 1. Check vault state -const vault = await sdk.getVaultAllocation(tokenAddress); -const now = BigInt(Math.floor(Date.now() / 1000)); - -console.log("Total:", formatUnits(vault.amountTotal, 18), "tokens"); -console.log("Claimed:", formatUnits(vault.amountClaimed, 18), "tokens"); - -// 2. Check if lockup has ended -if (now < vault.lockupEndTime) { - const remaining = Number(vault.lockupEndTime - now); - console.log(`Locked for ${remaining} more seconds`); - return; -} - -// 3. Check claimable -const claimable = await sdk.getVaultClaimable(tokenAddress); -console.log("Claimable:", formatUnits(claimable, 18), "tokens"); - -// 4. Claim -if (claimable > 0n) { - const txHash = await sdk.claimVault(tokenAddress); - console.log("Claimed in tx:", txHash); -} -``` - -## See Also - -- [../sdk/vault-lifecycle.md](../sdk/vault-lifecycle.md) -- SDK vault guide -- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension system overview -- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- Full token lifecycle diff --git a/rag/schemas/deploy-params.json b/rag/schemas/deploy-params.json deleted file mode 100644 index 8c3c11f..0000000 --- a/rag/schemas/deploy-params.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "DeployTokenParams", - "description": "Parameters for sdk.deployToken(). Only 'name' and 'symbol' are required -- all other fields have sensible defaults.", - "type": "object", - "required": ["name", "symbol"], - "properties": { - "name": { - "type": "string", - "description": "Token name (e.g., 'My Token'). Stored on-chain in the ERC-20 contract and emitted in the TokenCreated event." - }, - "symbol": { - "type": "string", - "description": "Token symbol (e.g., 'MTK'). Stored on-chain in the ERC-20 contract." - }, - "image": { - "type": "string", - "default": "", - "description": "Token image URL. IPFS URIs recommended (e.g., 'ipfs://QmHash'). Stored on-chain in the TokenCreated event. Recommended: 256x256 or 512x512 PNG." - }, - "metadata": { - "type": "string", - "default": "", - "description": "JSON string with token metadata. Schema: { description?: string, socialMediaUrls?: { platform: string, url: string }[], auditUrls?: string[] }. Use buildMetadata() helper to construct." - }, - "context": { - "type": "string", - "default": "{\"interface\":\"SDK\"}", - "description": "JSON string with deployment provenance. Schema: { interface: string, platform?: string, messageId?: string, id?: string }. Auto-set to {\"interface\":\"SDK\"} if omitted. Use buildContext() helper." - }, - "tokenAdmin": { - "type": "string", - "format": "address", - "default": "", - "description": "Address that can update token image and metadata after deployment. Defaults to the deployer wallet address." - }, - "salt": { - "type": "string", - "format": "hex", - "default": "keccak256(name + symbol + timestamp)", - "description": "CREATE2 salt for deterministic token address. Auto-generated from name + symbol + current timestamp if omitted." - }, - "hook": { - "type": "string", - "format": "address", - "default": "0x9811f10Cd549c754Fa9E5785989c422A762c28cc", - "description": "Fee hook contract address. Default: HOOK_STATIC_FEE_V2 (1% buy + 1% sell static fees). Alternative: HOOK_DYNAMIC_FEE_V2 (0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC) for volatility-responsive fees." - }, - "pairedToken": { - "type": "string", - "format": "address", - "default": "0x4200000000000000000000000000000000000006", - "description": "Quote token for the Uniswap V4 pool. Default: WETH on Base. This is always currency0 in the pool key." - }, - "tickIfToken0IsLiquid": { - "type": "integer", - "default": -230400, - "description": "Starting tick for the pool, which determines the initial market cap. Default: -230400 (approximately 10 ETH / ~$20K market cap). Formula: price = 1.0001^tick, marketCap = price * 100B supply." - }, - "tickSpacing": { - "type": "integer", - "default": 200, - "description": "Uniswap V4 tick spacing. All tick values must be divisible by this number. Default: 200." - }, - "poolData": { - "type": "string", - "format": "hex", - "default": "encodeStaticFeePoolData(100, 100)", - "description": "ABI-encoded pool initialization data for the hook. Default encodes 1% sell fee (100 BPS) + 1% buy fee (100 BPS) for the static fee hook. Use encodeStaticFeePoolData() or encodeDynamicFeePoolData() helpers." - }, - "locker": { - "type": "string", - "format": "address", - "default": "0x77247fCD1d5e34A3703AcA898A591Dc7422435f3", - "description": "LP locker contract. Default: LP_LOCKER_FEE_CONVERSION which converts all fees to a preferred token (ETH by default) before distributing to reward recipients." - }, - "rewardAdmins": { - "type": "array", - "items": { "type": "string", "format": "address" }, - "default": "[walletAddress]", - "description": "Admin addresses for each reward recipient slot. Each admin can update the recipient at their index. Must be same length as rewardRecipients." - }, - "rewardRecipients": { - "type": "array", - "items": { "type": "string", "format": "address" }, - "default": "[walletAddress]", - "description": "Addresses that receive LP fee rewards. Must be same length as rewardAdmins and rewardBps." - }, - "rewardBps": { - "type": "array", - "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, - "default": [10000], - "description": "Basis points for each reward recipient. Must sum to exactly 10000 (100%). Example: [7000, 3000] = 70%/30% split. IMMUTABLE after deployment." - }, - "tickLower": { - "type": "array", - "items": { "type": "integer" }, - "default": "[-230400, -216000, -202000, -155000, -141000]", - "description": "Lower tick bounds for each LP position. Must be divisible by tickSpacing. At least one must equal tickIfToken0IsLiquid. Default: 5-position Liquid layout." - }, - "tickUpper": { - "type": "array", - "items": { "type": "integer" }, - "default": "[-216000, -155000, -155000, -120000, -120000]", - "description": "Upper tick bounds for each LP position. Must be divisible by tickSpacing. Must be same length as tickLower." - }, - "positionBps": { - "type": "array", - "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, - "default": [1000, 5000, 1500, 2000, 500], - "description": "Supply allocation per position in basis points. Must sum to 10000. Default 5-position Liquid layout: 10%/50%/15%/20%/5%." - }, - "lockerData": { - "type": "string", - "format": "hex", - "default": "encodeFeeConversionLockerData([FeePreference.Paired])", - "description": "ABI-encoded locker initialization data. Default sets FeePreference.Paired (convert all fees to ETH) for each reward recipient." - }, - "mevModule": { - "type": "string", - "format": "address", - "default": "0x187e8627c02c58F31831953C1268e157d3BfCefd", - "description": "MEV protection module. Default: SNIPER_AUCTION_V2. Alternative: MEV_DESCENDING_FEES (0x8D6B080e48756A99F3893491D556B5d6907b6910) for time-based parabolic fee decay." - }, - "mevModuleData": { - "type": "string", - "format": "hex", - "default": "encodeSniperAuctionData({ startingFee: 800000, endingFee: 400000, secondsToDecay: 20 })", - "description": "ABI-encoded MEV module config. Default: 80% starting fee decaying to 40% over 20 seconds. Use encodeSniperAuctionData() helper." - }, - "extensions": { - "type": "array", - "items": { - "$ref": "#/$defs/ExtensionConfig" - }, - "default": [], - "description": "Additional extensions (vault, airdrop, presale). Max 10 extensions, max 90% (9000 BPS) of supply allocated to extensions total.", - "maxItems": 10 - }, - "devBuy": { - "$ref": "#/$defs/DevBuyParams", - "description": "Optional dev buy -- buy tokens with ETH in the same transaction as deployment. The SDK automatically appends a UNIV4_ETH_DEV_BUY extension. The ETH is swapped through the pool at normal 1% LP fees (not auction fees)." - } - }, - "$defs": { - "ExtensionConfig": { - "type": "object", - "required": ["extension", "msgValue", "extensionBps", "extensionData"], - "properties": { - "extension": { - "type": "string", - "format": "address", - "description": "Extension contract address. Must be on the allowlist." - }, - "msgValue": { - "type": "string", - "description": "ETH to send with the extension call (as bigint string). Usually 0n except for dev buy." - }, - "extensionBps": { - "type": "integer", - "minimum": 0, - "maximum": 9000, - "description": "Basis points of total token supply allocated to this extension." - }, - "extensionData": { - "type": "string", - "format": "hex", - "description": "ABI-encoded initialization data specific to the extension." - } - } - }, - "DevBuyParams": { - "type": "object", - "required": ["ethAmount", "recipient"], - "properties": { - "ethAmount": { - "type": "string", - "description": "Amount of ETH to spend buying tokens (as bigint, in wei). Sent as msg.value." - }, - "recipient": { - "type": "string", - "format": "address", - "description": "Address that receives the purchased tokens." - } - } - } - }, - "validationRules": [ - "1-7 positions allowed", - "positionBps must sum to exactly 10000", - "All ticks must be divisible by tickSpacing", - "All tickLower[i] must be >= tickIfToken0IsLiquid", - "At least one position must have tickLower == tickIfToken0IsLiquid", - "tickLower, tickUpper, and positionBps arrays must be the same length", - "rewardAdmins, rewardRecipients, and rewardBps arrays must be the same length", - "rewardBps must sum to exactly 10000", - "Max 10 extensions total", - "Total extensionBps across all extensions must not exceed 9000 (90% of supply)" - ] -} diff --git a/rag/schemas/pool-config.json b/rag/schemas/pool-config.json deleted file mode 100644 index 8f008f4..0000000 --- a/rag/schemas/pool-config.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Liquid Protocol Configuration Schemas", - "description": "JSON Schemas for PoolConfig, LockerConfig, MevModuleConfig, and ExtensionConfig -- the on-chain struct types used in the DeploymentConfig.", - "definitions": { - "PoolConfig": { - "title": "PoolConfig", - "description": "Uniswap V4 pool configuration. Mirrors the on-chain PoolConfig struct in Liquid.sol.", - "type": "object", - "required": ["hook", "pairedToken", "tickIfToken0IsLiquid", "tickSpacing", "poolData"], - "properties": { - "hook": { - "type": "string", - "format": "address", - "description": "Fee hook contract address. Must be an enabled hook in the factory. Options: HOOK_STATIC_FEE_V2 (0x9811f10Cd549c754Fa9E5785989c422A762c28cc) for fixed fees, or HOOK_DYNAMIC_FEE_V2 (0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC) for volatility-responsive fees." - }, - "pairedToken": { - "type": "string", - "format": "address", - "description": "Quote token for the pool. Always WETH (0x4200000000000000000000000000000000000006) on Base. This becomes currency0 in the Uniswap V4 pool key because WETH is numerically lower than deployed token addresses." - }, - "tickIfToken0IsLiquid": { - "type": "integer", - "description": "Starting tick that determines the initial token price and market cap. Since WETH is always token0, this tick represents the price of WETH in terms of the liquid token. Typical values: -230400 (~10 ETH / ~$20K market cap), -200200 (~200 ETH)." - }, - "tickSpacing": { - "type": "integer", - "default": 200, - "description": "Uniswap V4 tick spacing. All position ticks must be divisible by this value. Default: 200." - }, - "poolData": { - "type": "string", - "format": "hex", - "description": "ABI-encoded hook initialization data. Uses two-layer encoding: inner (fee params) wrapped in outer (PoolInitializationData with optional pool extension). For static fees: encodeStaticFeePoolData(liquidFeeBps, pairedFeeBps). For dynamic fees: encodeDynamicFeePoolData(config)." - } - } - }, - "LockerConfig": { - "title": "LockerConfig", - "description": "LP locker configuration. Controls how liquidity is locked and how fees are distributed.", - "type": "object", - "required": ["locker", "rewardAdmins", "rewardRecipients", "rewardBps", "tickLower", "tickUpper", "positionBps", "lockerData"], - "properties": { - "locker": { - "type": "string", - "format": "address", - "description": "LP locker contract address. Default: LP_LOCKER_FEE_CONVERSION (0x77247fCD1d5e34A3703AcA898A591Dc7422435f3). Locks LP permanently and converts fees to preferred token before distributing." - }, - "rewardAdmins": { - "type": "array", - "items": { "type": "string", "format": "address" }, - "description": "Admin address for each reward recipient slot. Admin at index N can update the recipient at index N. Cannot be changed after deployment." - }, - "rewardRecipients": { - "type": "array", - "items": { "type": "string", "format": "address" }, - "description": "Fee reward recipient addresses. Can be updated by the corresponding admin. Receive LP fees according to rewardBps split." - }, - "rewardBps": { - "type": "array", - "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, - "description": "Basis points (1/10000) for each reward recipient. Must sum to exactly 10000. IMMUTABLE after deployment -- only recipient addresses can change, not splits." - }, - "tickLower": { - "type": "array", - "items": { "type": "integer" }, - "description": "Lower tick bounds for LP positions. Each defines where a liquidity tranche starts. Must be divisible by tickSpacing." - }, - "tickUpper": { - "type": "array", - "items": { "type": "integer" }, - "description": "Upper tick bounds for LP positions. Each defines where a liquidity tranche ends. Must be divisible by tickSpacing." - }, - "positionBps": { - "type": "array", - "items": { "type": "integer", "minimum": 0, "maximum": 10000 }, - "description": "Supply allocation per position in basis points. Must sum to 10000 (100% of pool supply, which is total supply minus extension allocations). Max 7 positions." - }, - "lockerData": { - "type": "string", - "format": "hex", - "description": "ABI-encoded locker init data. For LP_LOCKER_FEE_CONVERSION: encode a FeeIn[] array with one entry per reward recipient. Values: 0 = Both (no conversion), 1 = Paired (convert to ETH), 2 = Liquid (convert to token). Use encodeFeeConversionLockerData()." - } - } - }, - "MevModuleConfig": { - "title": "MevModuleConfig", - "description": "MEV protection module configuration.", - "type": "object", - "required": ["mevModule", "mevModuleData"], - "properties": { - "mevModule": { - "type": "string", - "format": "address", - "description": "MEV module contract address. Options: SNIPER_AUCTION_V2 (0x187e8627c02c58F31831953C1268e157d3BfCefd) for auction-based protection, or MEV_DESCENDING_FEES (0x8D6B080e48756A99F3893491D556B5d6907b6910) for time-based fee decay." - }, - "mevModuleData": { - "type": "string", - "format": "hex", - "description": "ABI-encoded MEV module init data. For Sniper Auction: encode { startingFee: uint24, endingFee: uint24, secondsToDecay: uint256 } where fees are in uniBps (800000 = 80%). Use encodeSniperAuctionData()." - } - } - }, - "ExtensionConfig": { - "title": "ExtensionConfig", - "description": "Configuration for a token extension (vault, airdrop, dev buy, presale).", - "type": "object", - "required": ["extension", "msgValue", "extensionBps", "extensionData"], - "properties": { - "extension": { - "type": "string", - "format": "address", - "description": "Extension contract address. Must be enabled in the Pool Extension Allowlist." - }, - "msgValue": { - "type": "string", - "description": "ETH (in wei) to send as msg.value when calling the extension. Only non-zero for dev buy (ethAmount) and presale extensions." - }, - "extensionBps": { - "type": "integer", - "minimum": 0, - "maximum": 9000, - "description": "Basis points of total token supply (100B) to allocate to this extension. Total across all extensions must not exceed 9000 (90%)." - }, - "extensionData": { - "type": "string", - "format": "hex", - "description": "ABI-encoded extension-specific init data. Content varies by extension type." - } - } - }, - "FeePreference": { - "title": "FeePreference", - "description": "Fee conversion preference for LP_LOCKER_FEE_CONVERSION. One value per reward recipient.", - "type": "integer", - "enum": [0, 1, 2], - "enumDescriptions": { - "0": "Both -- No conversion, fees paid in whichever token accrues from LP", - "1": "Paired -- Convert all fees to paired token (WETH/ETH). This is the default.", - "2": "Liquid -- Convert all fees to the liquid token" - } - }, - "PoolDynamicConfigVars": { - "title": "PoolDynamicConfigVars", - "description": "Dynamic fee pool configuration. Returned by sdk.getPoolConfig(poolId). Only meaningful for pools using HOOK_DYNAMIC_FEE_V2.", - "type": "object", - "properties": { - "baseFee": { "type": "integer", "description": "Minimum fee in BPS (e.g., 100 = 1%)" }, - "maxLpFee": { "type": "integer", "description": "Maximum LP fee in BPS" }, - "referenceTickFilterPeriod": { "type": "string", "description": "Filter period in seconds (bigint)" }, - "resetPeriod": { "type": "string", "description": "Reset period in seconds (bigint)" }, - "resetTickFilter": { "type": "integer", "description": "Reset tick filter threshold (tick units)" }, - "feeControlNumerator": { "type": "string", "description": "Scaling constant for fee calculation (bigint)" }, - "decayFilterBps": { "type": "integer", "description": "Decay filter in BPS" } - } - }, - "PoolDynamicFeeVars": { - "title": "PoolDynamicFeeVars", - "description": "Current dynamic fee state. Returned by sdk.getPoolFeeState(poolId).", - "type": "object", - "properties": { - "referenceTick": { "type": "integer", "description": "Current reference tick for fee calculation" }, - "resetTick": { "type": "integer", "description": "Tick at last reset" }, - "resetTickTimestamp": { "type": "string", "description": "Unix timestamp of last reset (bigint)" }, - "lastSwapTimestamp": { "type": "string", "description": "Unix timestamp of last swap (bigint)" }, - "appliedVR": { "type": "integer", "description": "Applied volatility ratio" }, - "prevVA": { "type": "integer", "description": "Previous volatility accumulator" } - } - } - } -} diff --git a/rag/sdk/airdrop-system.md b/rag/sdk/airdrop-system.md deleted file mode 100644 index 62fc69a..0000000 --- a/rag/sdk/airdrop-system.md +++ /dev/null @@ -1,130 +0,0 @@ -# SDK Guide: Airdrop System - -How to interact with merkle-based airdrops: check state, verify claimable amounts, and claim tokens. - -## Overview - -The Airdrop V2 extension distributes tokens via merkle proof claims. It supports mutable merkle roots, lockup/vesting, and admin reclaim of unclaimed tokens. - -## SDK Methods - -### `getAirdropInfo(tokenAddress)` - -Returns the airdrop state for a token. - -```typescript -const info = await sdk.getAirdropInfo(tokenAddress); - -info.admin // Address -- airdrop admin -info.merkleRoot // Hex -- merkle root for verification -info.totalSupply // bigint -- total airdrop allocation -info.totalClaimed // bigint -- tokens claimed so far -info.lockupEndTime // bigint -- when claims open (unix timestamp) -info.vestingEndTime // bigint -- when vesting completes (unix timestamp) -info.adminClaimTime // bigint -- when admin can reclaim unclaimed -info.adminClaimed // boolean -- whether admin has reclaimed -``` - -### `getAirdropClaimable(tokenAddress, recipient, allocatedAmount)` - -Returns the amount a specific recipient can claim right now. - -```typescript -const claimable = await sdk.getAirdropClaimable( - tokenAddress, - recipientAddress, - allocatedAmount, // bigint -- total allocation for this recipient (18 decimals) -); -``` - -### `claimAirdrop(tokenAddress, recipient, allocatedAmount, proof)` - -Claims the airdrop for a recipient. Requires wallet. - -```typescript -const txHash = await sdk.claimAirdrop( - tokenAddress, - recipientAddress, - allocatedAmount, // bigint -- must match the merkle leaf exactly - merkleProof, // Hex[] -- generated off-chain from the merkle tree -); -await publicClient.waitForTransactionReceipt({ hash: txHash }); -``` - -## Complete Example - -```typescript -import { LiquidSDK } from "liquid-sdk"; -import { formatUnits, parseUnits } from "viem"; - -const sdk = new LiquidSDK({ publicClient, walletClient }); - -// 1. Check airdrop state -const info = await sdk.getAirdropInfo(tokenAddress); -const now = BigInt(Math.floor(Date.now() / 1000)); - -console.log("Merkle root:", info.merkleRoot); -console.log("Total supply:", formatUnits(info.totalSupply, 18)); -console.log("Claimed so far:", formatUnits(info.totalClaimed, 18)); - -// 2. Check if claims are open -if (now < info.lockupEndTime) { - console.log("Claims not yet open. Opens:", new Date(Number(info.lockupEndTime) * 1000)); - return; -} - -// 3. Check claimable for a recipient -const myAllocation = parseUnits("1000", 18); // 1000 tokens allocated -const claimable = await sdk.getAirdropClaimable( - tokenAddress, - recipientAddress, - myAllocation, -); -console.log("Claimable:", formatUnits(claimable, 18), "tokens"); - -// 4. Claim (need merkle proof from off-chain tree) -if (claimable > 0n) { - const txHash = await sdk.claimAirdrop( - tokenAddress, - recipientAddress, - myAllocation, - merkleProof, // Hex[] from the merkle tree - ); - console.log("Claimed in tx:", txHash); -} -``` - -## Merkle Proof Generation - -The SDK does not generate merkle proofs. Use a library like `@openzeppelin/merkle-tree`: - -```typescript -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; - -// Build tree from recipient list -const values = [ - ["0xRecipient1", "1000000000000000000000"], // 1000 tokens (18 decimals) - ["0xRecipient2", "500000000000000000000"], // 500 tokens - // ... more recipients -]; - -const tree = StandardMerkleTree.of(values, ["address", "uint256"]); -console.log("Root:", tree.root); // Set this as the merkle root - -// Get proof for a recipient -const proof = tree.getProof(["0xRecipient1", "1000000000000000000000"]); -``` - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `AlreadyClaimed` | Recipient already claimed | Check `getAirdropClaimable()` first | -| `LockupNotEnded` | Claims haven't opened yet | Wait for `lockupEndTime` | -| Invalid proof | Proof doesn't match root | Regenerate from the tree | -| `Unauthorized` | Admin-only function called by non-admin | Use admin wallet | - -## See Also - -- [../contracts/liquid-airdrop.md](../contracts/liquid-airdrop.md) -- Airdrop contract details -- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview diff --git a/rag/sdk/deploy-token.md b/rag/sdk/deploy-token.md deleted file mode 100644 index 83c7673..0000000 --- a/rag/sdk/deploy-token.md +++ /dev/null @@ -1,285 +0,0 @@ -# SDK Guide: Deploy Token - -Complete guide to `sdk.deployToken()` -- the primary entry point for launching tokens on Liquid Protocol. - -## Prerequisites - -```bash -npm install liquid-sdk viem -``` - -- A wallet with ETH on Base (for gas + optional dev buy) -- An RPC endpoint for Base mainnet (chain ID 8453) - -## Setup - -```typescript -import { createPublicClient, createWalletClient, http, parseEther } from "viem"; -import { base } from "viem/chains"; -import { privateKeyToAccount } from "viem/accounts"; -import { LiquidSDK } from "liquid-sdk"; - -const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY"); -const publicClient = createPublicClient({ chain: base, transport: http() }); -const walletClient = createWalletClient({ account, chain: base, transport: http() }); -const sdk = new LiquidSDK({ publicClient, walletClient }); -``` - -## Minimal Deploy - -Only `name` and `symbol` are required. Everything else gets sensible defaults: - -```typescript -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", -}); - -console.log("Token:", result.tokenAddress); -console.log("Pool ID:", result.event.poolId); -console.log("Tx:", result.txHash); -``` - -### What the Defaults Provide - -| Setting | Default Value | -|---------|--------------| -| Fee hook | Static 1% buy + 1% sell | -| Starting market cap | ~10 ETH (~$20K) | -| Positions | 5-position Liquid layout | -| MEV protection | Sniper Auction (80% to 40% over 20s) | -| Reward split | 100% to deployer | -| Fee conversion | All fees to ETH | -| LP | Permanently locked | - -## Deploy with Image - -```typescript -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", - image: "ipfs://QmYourImageCID", -}); -``` - -Recommended: 256x256 or 512x512 PNG, pinned to IPFS. - -## Deploy with Dev Buy - -Buy tokens with ETH in the same transaction. Uses normal 1% LP fees (not auction fees). - -```typescript -const result = await sdk.deployToken({ - name: "My Token", - symbol: "MTK", - devBuy: { - ethAmount: parseEther("0.01"), - recipient: account.address, - }, -}); -``` - -## Deploy with Custom Fees - -### Static Fees - -```typescript -import { encodeStaticFeePoolData } from "liquid-sdk"; - -// 2% on both directions -const result = await sdk.deployToken({ - name: "High Fee Token", - symbol: "HFT", - poolData: encodeStaticFeePoolData(200, 200), - // Args: (liquidFeeBps, pairedFeeBps) - // liquidFeeBps = sell fee (token -> ETH) - // pairedFeeBps = buy fee (ETH -> token) -}); - -// 0% sell, 3% buy -const result2 = await sdk.deployToken({ - name: "Buy Fee Only", - symbol: "BFO", - poolData: encodeStaticFeePoolData(0, 300), -}); -``` - -### Dynamic Fees - -```typescript -import { encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk"; - -const result = await sdk.deployToken({ - name: "Dynamic Token", - symbol: "DYN", - hook: ADDRESSES.HOOK_DYNAMIC_FEE_V2, - poolData: encodeDynamicFeePoolData({ - baseFeeBps: 100, - maxFeeBps: 500, - referenceTickFilterPeriod: 30, - resetPeriod: 120, - resetTickFilter: 200, - feeControlNumerator: 500000000n, - decayFilterBps: 7500, - }), -}); -``` - -## Deploy with Custom Positions - -### Using Default 3-Tranche Split - -```typescript -import { createDefaultPositions } from "liquid-sdk"; - -const positions = createDefaultPositions(20_000, 2070); -// Creates 3 tranches: 40% to $500K, 50% to $10M, 10% to $1B - -const result = await sdk.deployToken({ - name: "Positioned Token", - symbol: "POS", - ...positions, // tickLower, tickUpper, positionBps, tickIfToken0IsLiquid -}); -``` - -### Using Custom USD Tranches - -```typescript -import { createPositionsUSD } from "liquid-sdk"; - -const positions = createPositionsUSD(50_000, 2500, [ - { upperMarketCapUSD: 1_000_000, supplyPct: 30 }, - { upperMarketCapUSD: 50_000_000, supplyPct: 50 }, - { upperMarketCapUSD: 500_000_000, supplyPct: 20 }, -]); - -const result = await sdk.deployToken({ - name: "Custom Positions", - symbol: "CPS", - ...positions, - tickIfToken0IsLiquid: positions.tickLower[0], -}); -``` - -## Deploy with Custom Reward Splits - -```typescript -const result = await sdk.deployToken({ - name: "Split Token", - symbol: "SPLIT", - rewardAdmins: [walletA, walletB], - rewardRecipients: [walletA, walletB], - rewardBps: [7000, 3000], // 70% / 30% -}); -``` - -**Rules:** -- Arrays must be same length -- rewardBps must sum to exactly 10000 -- BPS splits are IMMUTABLE after deployment -- Admin at index N can update recipient at index N - -## Deploy with Metadata and Context - -```typescript -import { buildContext, buildMetadata } from "liquid-sdk"; - -const result = await sdk.deployToken({ - name: "Social Token", - symbol: "SOC", - image: "ipfs://QmImageHash", - metadata: buildMetadata({ - description: "A community token for builders", - socialMediaUrls: [ - { platform: "Twitter", url: "https://x.com/myproject" }, - { platform: "Website", url: "https://myproject.xyz" }, - ], - }), - context: buildContext({ - interface: "My Agent", - platform: "Farcaster", - messageId: "0x123abc", - }), -}); -``` - -## Deploy with Custom MEV Protection - -```typescript -import { encodeSniperAuctionData, ADDRESSES } from "liquid-sdk"; - -// Custom sniper auction: 60% to 30% over 30s -const result = await sdk.deployToken({ - name: "Custom MEV Token", - symbol: "CMEV", - mevModule: ADDRESSES.SNIPER_AUCTION_V2, - mevModuleData: encodeSniperAuctionData({ - startingFee: 600_000, - endingFee: 300_000, - secondsToDecay: 30, - }), -}); - -// Use descending fees instead -const result2 = await sdk.deployToken({ - name: "Descending Fee Token", - symbol: "DFT", - mevModule: ADDRESSES.MEV_DESCENDING_FEES, - // mevModuleData encoding depends on the module -}); -``` - -## Return Value - -```typescript -interface DeployTokenResult { - tokenAddress: Address; // Deployed ERC-20 contract - txHash: Hash; // Transaction hash - event: TokenCreatedEvent; // Full on-chain event with poolId, hook, etc. -} -``` - -The `event` contains: `poolId`, `poolHook`, `locker`, `mevModule`, `extensions`, `tokenAdmin`, `startingTick`, `pairedToken`, and more. - -## Validation Rules - -The SDK validates before sending the transaction: - -- 1-7 positions allowed -- `positionBps` must sum to 10000 -- All ticks divisible by `tickSpacing` -- All `tickLower` values >= `tickIfToken0IsLiquid` -- At least one position must start at `tickIfToken0IsLiquid` -- 1+ reward recipients, `rewardBps` sum to 10000 -- Max 10 extensions, total extensionBps <= 9000 - -## Post-Deploy - -```typescript -// Check deployment -const info = await sdk.getDeploymentInfo(result.tokenAddress); -const tokenInfo = await sdk.getTokenInfo(result.tokenAddress); - -// Update metadata (admin only) -await sdk.updateImage(result.tokenAddress, "https://new-image.png"); -await sdk.updateMetadata(result.tokenAddress, '{"description":"Updated"}'); - -// Check MEV status -const unlockTime = await sdk.getPoolUnlockTime(result.event.poolId); -``` - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `TickRangeLowerThanStartingTick` | tickLower < tickIfToken0IsLiquid | Adjust position ticks | -| Insufficient funds | Not enough ETH for gas + devBuy | Fund wallet | -| `rewardBps must sum to 10000` | BPS array wrong | Fix values | -| `Deprecated()` | Factory is paused | Contact team | - -## See Also - -- [../schemas/deploy-params.json](../schemas/deploy-params.json) -- Full parameter schema -- [../contracts/liquid-factory.md](../contracts/liquid-factory.md) -- Factory contract details -- [../concepts/token-lifecycle.md](../concepts/token-lifecycle.md) -- What happens on-chain -- [../concepts/lp-positions.md](../concepts/lp-positions.md) -- Position math diff --git a/rag/sdk/fee-management.md b/rag/sdk/fee-management.md deleted file mode 100644 index 1365e05..0000000 --- a/rag/sdk/fee-management.md +++ /dev/null @@ -1,116 +0,0 @@ -# SDK Guide: Fee Management - -How to check and claim LP fees using the Liquid SDK. - -## Overview - -When users trade tokens on Uniswap V4, LP fees accrue in the pool. The LP Locker collects these fees, converts them to ETH (by default), and deposits them into the Fee Locker for each reward recipient. Recipients then call `claimFees()` to withdraw. - -## Fee Flow - -``` -Trading activity on Uniswap V4 - |-- Hook applies LP fee (e.g., 1%) - |-- 20% of LP fee -> Protocol (team) - |-- 80% of LP fee -> LP position - | -collectRewards(tokenAddress) -- anyone can call - |-- Collects fees from LP positions - |-- Converts to ETH (FeePreference.Paired) - |-- Splits by reward BPS - |-- Deposits into Fee Locker - | -claimFees(owner, token) -- anyone can call - |-- Transfers accumulated WETH to fee owner -``` - -## SDK Methods - -### `getAvailableFees(feeOwner, tokenAddress)` - -Returns total unlocked fees for a fee owner and token pair. - -```typescript -const available = await sdk.getAvailableFees(ownerAddress, tokenAddress); -// available: bigint -- fee balance in the Fee Locker (usually WETH) -``` - -### `getFeesToClaim(feeOwner, tokenAddress)` - -Returns the currently claimable fee balance. In the current implementation, this is the same as `getAvailableFees`. - -```typescript -const claimable = await sdk.getFeesToClaim(ownerAddress, tokenAddress); -// claimable: bigint -``` - -### `claimFees(feeOwner, tokenAddress)` - -Claims all accumulated fees for a fee owner. Requires wallet. The fees are transferred to the `feeOwner` address. - -```typescript -if (claimable > 0n) { - const txHash = await sdk.claimFees(ownerAddress, tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); -} -``` - -**Note:** `claimFees` is permissionless -- anyone can trigger a claim on behalf of any fee owner. The funds always go to the `feeOwner` address. - -## Complete Example - -```typescript -import { createPublicClient, createWalletClient, http, formatEther } from "viem"; -import { base } from "viem/chains"; -import { privateKeyToAccount } from "viem/accounts"; -import { LiquidSDK } from "liquid-sdk"; - -const account = privateKeyToAccount("0x..."); -const publicClient = createPublicClient({ chain: base, transport: http() }); -const walletClient = createWalletClient({ account, chain: base, transport: http() }); -const sdk = new LiquidSDK({ publicClient, walletClient }); - -// 1. Check available fees -const available = await sdk.getAvailableFees(account.address, tokenAddress); -console.log("Available fees:", formatEther(available), "ETH"); - -// 2. Check claimable -const claimable = await sdk.getFeesToClaim(account.address, tokenAddress); -console.log("Claimable fees:", formatEther(claimable), "ETH"); - -// 3. Claim -if (claimable > 0n) { - console.log("Claiming fees..."); - const txHash = await sdk.claimFees(account.address, tokenAddress); - const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - console.log("Claimed in tx:", receipt.transactionHash); -} else { - console.log("No fees to claim yet."); -} -``` - -## Important Notes - -- Fees only accrue from trading activity. No trading = no fees. -- Fees are denominated in the paired asset (WETH) when using the default `FeePreference.Paired`. -- The `collectRewards()` step must happen before fees appear in the Fee Locker. See [reward-management.md](reward-management.md). -- `claimFees` reverts with `NoFeesToClaim` if balance is zero. - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `NoFeesToClaim` | No fees accrued | Wait for trading, or call `collectRewards()` first | - -## Contract Addresses - -| Contract | Address | -|----------|---------| -| Fee Locker | `0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF` | -| LP Locker Fee Conversion | `0x77247fCD1d5e34A3703AcA898A591Dc7422435f3` | - -## See Also - -- [reward-management.md](reward-management.md) -- Collecting LP rewards (prerequisite for fee claiming) -- [../contracts/liquid-fee-locker.md](../contracts/liquid-fee-locker.md) -- Fee Locker contract -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system overview diff --git a/rag/sdk/pool-reads.md b/rag/sdk/pool-reads.md deleted file mode 100644 index 22e664f..0000000 --- a/rag/sdk/pool-reads.md +++ /dev/null @@ -1,124 +0,0 @@ -# SDK Guide: Pool Reads - -How to query pool configuration, fee state, creation time, and token sort order. - -## Overview - -These read-only methods query on-chain pool state from the Uniswap V4 hook contracts. No wallet required. - -```typescript -const sdk = new LiquidSDK({ publicClient }); // read-only -``` - -## SDK Methods - -### `getPoolConfig(poolId)` - -Returns the dynamic fee configuration for a pool. Most useful for pools using `HOOK_DYNAMIC_FEE_V2`. - -```typescript -const config = await sdk.getPoolConfig(poolId); - -config.baseFee // number -- minimum fee (BPS) -config.maxLpFee // number -- maximum LP fee -config.referenceTickFilterPeriod // bigint -- seconds -config.resetPeriod // bigint -- seconds -config.resetTickFilter // number -- tick units -config.feeControlNumerator // bigint -- scaling constant -config.decayFilterBps // number -- decay filter in BPS -``` - -### `getPoolFeeState(poolId)` - -Returns the current fee state variables that change with each swap. - -```typescript -const state = await sdk.getPoolFeeState(poolId); - -state.referenceTick // number -- current reference tick -state.resetTick // number -- tick at last reset -state.resetTickTimestamp // bigint -- unix timestamp of last reset -state.lastSwapTimestamp // bigint -- unix timestamp of last swap -state.appliedVR // number -- applied volatility ratio -state.prevVA // number -- previous volatility accumulator -``` - -### `getPoolCreationTimestamp(poolId)` - -Returns the unix timestamp when the pool was created. - -```typescript -const timestamp = await sdk.getPoolCreationTimestamp(poolId); -const date = new Date(Number(timestamp) * 1000); -console.log("Created:", date.toISOString()); -``` - -### `isLiquidToken0(poolId)` - -Returns whether the liquid token is token0 or token1 in the Uniswap V4 pool. - -```typescript -const isToken0 = await sdk.isLiquidToken0(poolId); -// In practice, WETH (0x4200...) is almost always token0 (lower address) -// So this typically returns false (the liquid token is token1) -``` - -This is important for determining swap direction: -- If liquid token is token0: `zeroForOne = true` to sell, `false` to buy -- If liquid token is token1 (typical): `zeroForOne = true` to buy, `false` to sell - -## Pool Key Structure - -Every Liquid pool has a standard key: - -```typescript -const poolKey = { - currency0: EXTERNAL.WETH, // 0x4200...0006 - currency1: tokenAddress, // deployed token - fee: 8388608, // 0x800000 = dynamic fee flag - tickSpacing: 200, // Liquid default - hooks: hookAddress, // which hook contract -}; - -// Pool ID = keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks)) -``` - -You can retrieve the pool key from token rewards: - -```typescript -const rewards = await sdk.getTokenRewards(tokenAddress); -const poolKey = rewards.poolKey; -const poolId = tokenEvent.poolId; // from getTokenEvent() -``` - -## Complete Example - -```typescript -import { LiquidSDK } from "liquid-sdk"; - -const sdk = new LiquidSDK({ publicClient }); // read-only - -// Get pool ID from token -const tokenEvent = await sdk.getTokenEvent(tokenAddress); -const poolId = tokenEvent.poolId; - -// Read pool state -const config = await sdk.getPoolConfig(poolId); -console.log("Base fee:", config.baseFee, "BPS"); -console.log("Max LP fee:", config.maxLpFee, "BPS"); - -const state = await sdk.getPoolFeeState(poolId); -console.log("Reference tick:", state.referenceTick); -console.log("Last swap:", new Date(Number(state.lastSwapTimestamp) * 1000)); - -const created = await sdk.getPoolCreationTimestamp(poolId); -console.log("Pool created:", new Date(Number(created) * 1000)); - -const isToken0 = await sdk.isLiquidToken0(poolId); -console.log("Liquid is token0:", isToken0); -``` - -## See Also - -- [../contracts/liquid-hooks.md](../contracts/liquid-hooks.md) -- Hook contract details -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Fee system overview diff --git a/rag/sdk/position-builder.md b/rag/sdk/position-builder.md deleted file mode 100644 index c39cb55..0000000 --- a/rag/sdk/position-builder.md +++ /dev/null @@ -1,172 +0,0 @@ -# SDK Guide: Position Builder - -How to create custom liquidity position layouts using market cap targets. - -## Overview - -Liquid Protocol deploys tokens with concentrated liquidity across multiple tick ranges ("positions" or "tranches"). Each position covers a market cap range and holds a percentage of the pool supply. - -The SDK provides helpers to convert market cap targets (USD or ETH) into the tick arrays needed by `deployToken()`. - -## Key Functions - -### `createPositions(startingCapETH, tranches, tickSpacing?)` - -Builds position arrays from ETH-denominated market cap tranches. - -```typescript -import { createPositions } from "liquid-sdk"; - -const positions = createPositions(10, [ - { upperMarketCapETH: 241.5, supplyPct: 40 }, // ~$500K @ $2070/ETH - { upperMarketCapETH: 4830, supplyPct: 50 }, // ~$10M - { upperMarketCapETH: 483050, supplyPct: 10 }, // ~$1B -]); - -// Returns: -// { -// tickLower: [-230400, -198600, -168600], -// tickUpper: [-198600, -168600, -122400], -// positionBps: [4000, 5000, 1000] -// } -``` - -### `createPositionsUSD(startingCapUSD, ethPriceUSD, tranches, tickSpacing?)` - -Builds positions from USD-denominated market caps. Convenience wrapper that converts USD to ETH. - -```typescript -import { createPositionsUSD } from "liquid-sdk"; - -const positions = createPositionsUSD(20_000, 2070, [ - { upperMarketCapUSD: 500_000, supplyPct: 40 }, - { upperMarketCapUSD: 10_000_000, supplyPct: 50 }, - { upperMarketCapUSD: 1_000_000_000, supplyPct: 10 }, -]); - -const result = await sdk.deployToken({ - name: "Custom Positions", - symbol: "CPS", - ...positions, - tickIfToken0IsLiquid: positions.tickLower[0], -}); -``` - -### `createDefaultPositions(startingCapUSD, ethPriceUSD, tickSpacing?)` - -Builds the standard 3-tranche Liquid layout: -- 40% of pool supply: Starting to $500K market cap -- 50% of pool supply: $500K to $10M market cap -- 10% of pool supply: $10M to $1B market cap - -```typescript -import { createDefaultPositions } from "liquid-sdk"; - -const positions = createDefaultPositions(20_000, 2070); -// Returns: { tickLower, tickUpper, positionBps, tickIfToken0IsLiquid } - -const result = await sdk.deployToken({ - name: "Default Layout", - symbol: "DEF", - ...positions, -}); -``` - -### `describePositions(positions, ethPriceUSD?)` - -Returns human-readable descriptions of position ranges. - -```typescript -import { describePositions } from "liquid-sdk"; - -const desc = describePositions(positions, 2070); -for (const p of desc) { - console.log(`Position ${p.index}: ${p.supplyPct}%`); - console.log(` Ticks: ${p.tickLower} -> ${p.tickUpper}`); - console.log(` Market cap: ${p.marketCapLowerETH.toFixed(1)} ETH -> ${p.marketCapUpperETH.toFixed(1)} ETH`); - if (p.marketCapLowerUSD) { - console.log(` USD: $${p.marketCapLowerUSD.toFixed(0)} -> $${p.marketCapUpperUSD.toFixed(0)}`); - } -} -``` - -## Default Position Layouts - -### 5-Position "Liquid" Layout (SDK Default) - -This is the hardcoded default used when no positions are specified: - -| # | Supply | Tick Range | Market Cap Range (~$2000/ETH) | -|---|--------|-----------|------------------------------| -| 1 | 10% | -230,400 to -216,000 | ~$20K to ~$83K | -| 2 | 50% | -216,000 to -155,000 | ~$83K to ~$37M | -| 3 | 15% | -202,000 to -155,000 | ~$338K to ~$37M | -| 4 | 20% | -155,000 to -120,000 | ~$37M to ~$1.2B | -| 5 | 5% | -141,000 to -120,000 | ~$151M to ~$1.2B | - -Note: Positions 2-3 and 4-5 overlap, concentrating more liquidity in the mid-range. - -### Standard Single Position - -```typescript -import { POOL_POSITIONS } from "liquid-sdk"; - -const standard = POOL_POSITIONS.Standard; -// Single position: -230400 to -120000, 100% of supply -``` - -### Default 3-Tranche (via createDefaultPositions) - -| # | Supply | USD Range | -|---|--------|-----------| -| 1 | 40% | Starting to $500K | -| 2 | 50% | $500K to $10M | -| 3 | 10% | $10M to $1B | - -## Tick Math Primer - -See [../concepts/tick-math.md](../concepts/tick-math.md) for full details. - -**Quick formulas:** - -``` -Total supply = 100,000,000,000 (100B) -Price per token = marketCapETH / totalSupply -Tick = floor(log(price) / log(1.0001) / tickSpacing) * tickSpacing - -Reverse: -Price = 1.0001^tick -MarketCapETH = price * totalSupply -MarketCapUSD = marketCapETH * ethPriceUSD -``` - -**Helper functions:** - -```typescript -import { - getTickFromMarketCapETH, - getTickFromMarketCapUSD, - marketCapFromTickETH, - marketCapFromTickUSD, -} from "liquid-sdk"; - -getTickFromMarketCapETH(10) // -230400 (~10 ETH) -getTickFromMarketCapUSD(500_000, 2070) // tick for $500K -marketCapFromTickETH(-230400) // ~10 ETH -marketCapFromTickUSD(-230400, 2070) // ~$20,700 -``` - -## Validation Rules - -- 1-7 positions allowed (max 7) -- `positionBps` must sum to exactly 10000 (100%) -- `supplyPct` in tranches must sum to 100 -- All ticks must be divisible by `tickSpacing` (default 200) -- Tranches must be ordered by ascending market cap -- Each tranche's upper tick must be above the previous - -## See Also - -- [../concepts/tick-math.md](../concepts/tick-math.md) -- Detailed tick math formulas -- [../concepts/lp-positions.md](../concepts/lp-positions.md) -- Position concepts -- [deploy-token.md](deploy-token.md) -- Deploying with custom positions diff --git a/rag/sdk/reward-management.md b/rag/sdk/reward-management.md deleted file mode 100644 index 7c985e0..0000000 --- a/rag/sdk/reward-management.md +++ /dev/null @@ -1,129 +0,0 @@ -# SDK Guide: Reward Management - -How to manage LP rewards: check reward configuration, collect fees from LP positions, and update reward recipients. - -## Overview - -LP rewards are distributed to reward recipients configured at deployment. The LP Locker holds the Uniswap V4 LP positions permanently and distributes collected fees according to BPS splits. - -## Reward Flow - -``` -LP positions accrue fees from trading - | - v -collectRewards(tokenAddress) called - |-- Collects fees from all positions - |-- Converts to preferred token (ETH by default) - |-- Splits by reward BPS - |-- Deposits into Fee Locker - | - v -Fee recipients call claimFees() to withdraw -``` - -## SDK Methods - -### `getTokenRewards(tokenAddress)` - -Returns the complete reward configuration for a token. - -```typescript -const rewards = await sdk.getTokenRewards(tokenAddress); - -console.log("Recipients:", rewards.rewardRecipients); // Address[] -console.log("BPS:", rewards.rewardBps); // number[] (sum = 10000) -console.log("Admins:", rewards.rewardAdmins); // Address[] -console.log("Pool key:", rewards.poolKey); // PoolKey -console.log("Position ID:", rewards.positionId); // bigint -console.log("Num positions:", rewards.numPositions); // bigint -``` - -### `collectRewards(tokenAddress)` - -Collects all accrued LP fees from Uniswap V4 positions and distributes to reward recipients via the Fee Locker. Also unlocks the LP position. - -```typescript -const txHash = await sdk.collectRewards(tokenAddress); -await publicClient.waitForTransactionReceipt({ hash: txHash }); -``` - -**Important:** Reverts with `ManagerLocked` during the MEV block delay period. Check `getPoolUnlockTime()` first. - -### `collectRewardsWithoutUnlock(tokenAddress)` - -Same as `collectRewards` but skips the unlock step. Use this to avoid MEV during fee collection or when the pool is still in the MEV protection window. - -```typescript -const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); -await publicClient.waitForTransactionReceipt({ hash: txHash }); -``` - -### `updateRewardRecipient(tokenAddress, rewardIndex, newRecipient)` - -Changes the reward recipient at a specific index. Only callable by the reward admin at that index. - -```typescript -const txHash = await sdk.updateRewardRecipient( - tokenAddress, - 0n, // reward index (bigint) - newRecipientAddress, -); -await publicClient.waitForTransactionReceipt({ hash: txHash }); -``` - -## Complete Example - -```typescript -import { LiquidSDK } from "liquid-sdk"; - -const sdk = new LiquidSDK({ publicClient, walletClient }); - -// 1. Check reward config -const rewards = await sdk.getTokenRewards(tokenAddress); -console.log("Recipients:", rewards.rewardRecipients); -console.log("Splits:", rewards.rewardBps.map(b => `${b/100}%`)); - -// 2. Check if pool is unlocked -const unlockTime = await sdk.getPoolUnlockTime(poolId); -const now = BigInt(Math.floor(Date.now() / 1000)); - -if (now < unlockTime) { - console.log("Pool locked until:", new Date(Number(unlockTime) * 1000)); - // Use without unlock - const txHash = await sdk.collectRewardsWithoutUnlock(tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); -} else { - // Full collect + unlock - const txHash = await sdk.collectRewards(tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); -} - -// 3. Now claim fees (see fee-management.md) -const claimable = await sdk.getFeesToClaim(account.address, tokenAddress); -if (claimable > 0n) { - await sdk.claimFees(account.address, tokenAddress); -} -``` - -## Key Rules - -- **BPS splits are immutable** -- Set at deployment, cannot be changed -- **Only recipient addresses can change** -- Via `updateRewardRecipient` -- **Only the admin at index N can update recipient N** -- Each admin controls only their index -- **Admin addresses are immutable** -- Set at deployment, cannot be changed -- **`collectRewards` is permissionless** -- Anyone can trigger fee collection -- **Fees are converted to ETH by default** -- Using `FeePreference.Paired` - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `ManagerLocked` | Pool in MEV lock period | Use `collectRewardsWithoutUnlock` or wait | -| `Unauthorized` | Not the admin for that index | Use correct wallet | - -## See Also - -- [fee-management.md](fee-management.md) -- Claiming fees after collection -- [../contracts/liquid-lp-locker.md](../contracts/liquid-lp-locker.md) -- LP Locker contract -- [../concepts/fee-system.md](../concepts/fee-system.md) -- Complete fee system diff --git a/rag/sdk/sniper-auction.md b/rag/sdk/sniper-auction.md deleted file mode 100644 index b73e630..0000000 --- a/rag/sdk/sniper-auction.md +++ /dev/null @@ -1,183 +0,0 @@ -# SDK Guide: Sniper Auction - -How to read auction state, calculate bids, and participate in MEV sniper auctions. - -## Overview - -The Sniper Auction V2 is an MEV protection mechanism that runs for the first ~20 seconds after token deployment. Early traders compete via gas price bidding, with fees starting at 80% and decaying to 40%. - -**Important:** In most cases, waiting for the auction to end and trading at normal 1% fees is the better strategy. The auction is designed to extract value from snipers, not help them. - -## SDK Methods - -### `getAuctionState(poolId)` - -Returns the current auction state. - -```typescript -const auction = await sdk.getAuctionState(poolId); - -auction.nextAuctionBlock // bigint -- the ONE block where bids are valid -auction.round // bigint -- current round number -auction.gasPeg // bigint -- base gas price reference -auction.currentFee // number -- current fee in uniBps (800000 = 80%) -``` - -### `getAuctionFeeConfig(poolId)` - -Returns the fee decay configuration. - -```typescript -const feeConfig = await sdk.getAuctionFeeConfig(poolId); - -feeConfig.startingFee // number -- e.g., 800000 (80%) -feeConfig.endingFee // number -- e.g., 400000 (40%) -feeConfig.secondsToDecay // bigint -- e.g., 20n -``` - -### `getAuctionDecayStartTime(poolId)` - -Returns the unix timestamp when fee decay started. - -```typescript -const startTime = await sdk.getAuctionDecayStartTime(poolId); -``` - -### `getAuctionMaxRounds()` - -Returns the maximum number of auction rounds. - -```typescript -const maxRounds = await sdk.getAuctionMaxRounds(); -// maxRounds: bigint -- typically 5n -``` - -### `getAuctionGasPriceForBid(gasPeg, bidAmount)` - -Calculates the exact gas price needed for a desired bid amount. - -```typescript -const gasPrice = await sdk.getAuctionGasPriceForBid( - auction.gasPeg, - parseEther("0.001"), // desired bid in ETH -); -``` - -**Formula:** `bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit` where `paymentPerGasUnit = 0.0001 ETH (1e14 wei)`. - -### `bidInAuction(params, gasPrice)` - -Executes an auction bid + swap. The SDK handles: -- Auto-wrapping ETH to WETH for `amountIn` -- Auto-approving SniperUtilV2 for WETH spending -- Setting `gas: 800_000n` (skips estimation) -- Setting both `maxFeePerGas` and `maxPriorityFeePerGas` to `gasPrice` - -```typescript -const result = await sdk.bidInAuction({ - poolKey: rewards.poolKey, - zeroForOne, // true if WETH is currency0 - amountIn: parseEther("0.001"), // WETH to swap - amountOutMinimum: 0n, // set slippage in production! - round: auction.round, // must match current on-chain round - bidAmount: parseEther("0.0005"), // ETH bid (sent as msg.value) -}, gasPrice); - -console.log("Tx:", result.txHash); -``` - -## Complete Bidding Flow - -```typescript -import { LiquidSDK, EXTERNAL } from "liquid-sdk"; -import { parseEther, formatEther } from "viem"; - -const sdk = new LiquidSDK({ publicClient, walletClient }); - -// 1. Get token event for pool ID -const tokenEvent = await sdk.getTokenEvent(tokenAddress); -const poolId = tokenEvent.poolId; - -// 2. Get auction state -const auction = await sdk.getAuctionState(poolId); -const maxRounds = await sdk.getAuctionMaxRounds(); - -if (auction.round >= maxRounds) { - console.log("Auction ended -- trade at normal fees"); - return; -} - -console.log("Fee:", Number(auction.currentFee) / 10000, "%"); -console.log("Round:", auction.round.toString()); - -// 3. Get pool key and swap direction -const rewards = await sdk.getTokenRewards(tokenAddress); -const zeroForOne = rewards.poolKey.currency0.toLowerCase() === EXTERNAL.WETH.toLowerCase(); - -// 4. Calculate gas price -const bidAmount = parseEther("0.001"); -const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, bidAmount); - -// 5. Wait for auction block -while (true) { - const currentBlock = await publicClient.getBlockNumber(); - const gap = Number(auction.nextAuctionBlock - currentBlock); - if (gap <= 0) { console.log("Missed this round"); return; } - if (gap === 1) break; - await new Promise(r => setTimeout(r, gap > 2 ? 500 : 200)); -} - -// 6. Fire bid -const result = await sdk.bidInAuction({ - poolKey: rewards.poolKey, - zeroForOne, - amountIn: parseEther("0.001"), - amountOutMinimum: 0n, - round: auction.round, - bidAmount, -}, gasPrice); - -const receipt = await publicClient.waitForTransactionReceipt({ hash: result.txHash }); -console.log(receipt.status === "success" ? "WON" : "FAILED"); -``` - -## Two Separate ETH Costs - -- **`bidAmount` (msg.value):** ETH sent to the auction as your bid. Goes to protocol/LP. -- **`amountIn` (WETH transfer):** The actual swap input. Pulled from your WETH balance via `transferFrom`. Separate from the bid. - -Total ETH needed: `bidAmount + amountIn + gas fees` - -## Auction Constants - -| Parameter | Value | -|-----------|-------| -| Max rounds | 5 | -| Blocks between rounds | 2 | -| First auction block | Deploy block + 2 | -| Payment per gas unit | 0.0001 ETH (1e14 wei) | -| Default starting fee | 800,000 (80%) | -| Default ending fee | 400,000 (40%) | -| Default decay period | 20 seconds | -| Gas peg | ~6.3M wei (set at pool creation) | - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `GasPriceTooLow()` | txGasPrice <= gasPeg | Use `getAuctionGasPriceForBid()` | -| `NotAuctionBlock()` | Wrong block | Submit 1 block before `nextAuctionBlock` | -| `Unauthorized()` | Fee Locker not configured | Protocol admin issue | -| WETH transferFrom revert | Insufficient WETH | SDK handles auto-wrap | - -## Contract Addresses - -| Contract | Address | -|----------|---------| -| Sniper Auction V2 | `0x187e8627c02c58F31831953C1268e157d3BfCefd` | -| Sniper Util V2 | `0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f` | - -## See Also - -- [../contracts/liquid-mev-protection.md](../contracts/liquid-mev-protection.md) -- MEV contract details -- [../concepts/mev-protection.md](../concepts/mev-protection.md) -- Conceptual overview diff --git a/rag/sdk/vault-lifecycle.md b/rag/sdk/vault-lifecycle.md deleted file mode 100644 index 4775ab5..0000000 --- a/rag/sdk/vault-lifecycle.md +++ /dev/null @@ -1,138 +0,0 @@ -# SDK Guide: Vault Lifecycle - -How to check vault lockup/vesting status and claim vested tokens. - -## Overview - -The Vault extension locks tokens with a lockup period followed by linear vesting. After the lockup period ends, tokens vest linearly until the vesting end time. - -## Timeline - -``` -Deployment lockupEndTime vestingEndTime - | | | - |--- Lockup ---------|--- Linear Vesting ---| - | (no claims) | (claim vested amt) | (claim all remaining) -``` - -## SDK Methods - -### `getVaultAllocation(tokenAddress)` - -Returns the vault state for a token. - -```typescript -const vault = await sdk.getVaultAllocation(tokenAddress); - -vault.token // Address -- the token -vault.amountTotal // bigint -- total tokens locked -vault.amountClaimed // bigint -- already claimed -vault.lockupEndTime // bigint -- unix timestamp when lockup ends -vault.vestingEndTime // bigint -- unix timestamp when fully vested -vault.admin // Address -- who can claim -``` - -### `getVaultClaimable(tokenAddress)` - -Returns the number of tokens available to claim right now. - -```typescript -const claimable = await sdk.getVaultClaimable(tokenAddress); -// claimable: bigint -- tokens available now (18 decimals) -``` - -### `claimVault(tokenAddress)` - -Claims all currently vested tokens. Requires wallet. Only callable by the vault admin. - -```typescript -if (claimable > 0n) { - const txHash = await sdk.claimVault(tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); -} -``` - -## Complete Example - -```typescript -import { createPublicClient, createWalletClient, http, formatUnits } from "viem"; -import { base } from "viem/chains"; -import { privateKeyToAccount } from "viem/accounts"; -import { LiquidSDK } from "liquid-sdk"; - -const account = privateKeyToAccount("0x..."); -const publicClient = createPublicClient({ chain: base, transport: http() }); -const walletClient = createWalletClient({ account, chain: base, transport: http() }); -const sdk = new LiquidSDK({ publicClient, walletClient }); - -async function checkAndClaimVault(tokenAddress: `0x${string}`) { - // 1. Get vault state - const vault = await sdk.getVaultAllocation(tokenAddress); - const now = BigInt(Math.floor(Date.now() / 1000)); - - console.log("Total locked:", formatUnits(vault.amountTotal, 18), "tokens"); - console.log("Already claimed:", formatUnits(vault.amountClaimed, 18), "tokens"); - console.log("Admin:", vault.admin); - console.log("Lockup ends:", new Date(Number(vault.lockupEndTime) * 1000).toISOString()); - console.log("Vesting ends:", new Date(Number(vault.vestingEndTime) * 1000).toISOString()); - - // 2. Check lockup - if (now < vault.lockupEndTime) { - const remaining = Number(vault.lockupEndTime - now); - console.log(`Still locked for ${remaining} seconds`); - return; - } - - // 3. Check vesting progress - if (now >= vault.vestingEndTime) { - console.log("Fully vested!"); - } else { - const elapsed = Number(now - vault.lockupEndTime); - const total = Number(vault.vestingEndTime - vault.lockupEndTime); - const pct = (elapsed / total * 100).toFixed(1); - console.log(`Vesting: ${pct}% complete`); - } - - // 4. Check claimable - const claimable = await sdk.getVaultClaimable(tokenAddress); - console.log("Claimable now:", formatUnits(claimable, 18), "tokens"); - - // 5. Claim - if (claimable > 0n) { - console.log("Claiming..."); - const txHash = await sdk.claimVault(tokenAddress); - await publicClient.waitForTransactionReceipt({ hash: txHash }); - console.log("Claimed in tx:", txHash); - } -} -``` - -## Vesting Math - -``` -if now < lockupEndTime: - claimable = 0 - -if now >= vestingEndTime: - claimable = amountTotal - amountClaimed - -if lockupEndTime <= now < vestingEndTime: - elapsed = now - lockupEndTime - vestingDuration = vestingEndTime - lockupEndTime - totalVested = amountTotal * elapsed / vestingDuration - claimable = totalVested - amountClaimed -``` - -## Common Errors - -| Error | Cause | Fix | -|-------|-------|-----| -| `LockupNotEnded` | Lockup period hasn't passed | Wait for `lockupEndTime` | -| `Unauthorized` | Caller is not the vault admin | Use admin wallet | -| Zero claimable | All vested tokens already claimed | Wait for more to vest | - -## See Also - -- [../contracts/liquid-vault.md](../contracts/liquid-vault.md) -- Vault contract details -- [../concepts/extension-system.md](../concepts/extension-system.md) -- Extension overview -- [deploy-token.md](deploy-token.md) -- Deploying with vault extension From ff4fc33d3804ae59f2e1fd50d824e404b809a82a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Apr 2026 18:42:42 +0000 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20simplify=20review=20fixes=20?= =?UTF-8?q?=E2=80=94=20restore=20MEV=20docs,=20fix=20LP=20simulator=20drif?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After rebase onto main, the SDK still ships getMevDescendingFeesBlockDelay() + deprecated getMevBlockDelay() shim + getPoolUnlockTime(). Doc edits had removed all mention of these, leaving the public API undocumented. Restored: - AGENT_README.md: MEV Descending Fees section, fixed defaults table to 5-position layout, added LiquidMevDescendingFeesAbi to import block - CLAUDE.md: MEV Descending Fees section, fixed ABI table - llms.txt: MEV Descending Fees section - examples/08: re-added a getMevDescendingFeesBlockDelay() call LP-SIMULATOR-PLAN.md fixes: - Wrong starting tick in export example (-226600 → -230400) - Replaced hardcoded constants block with imports from liquid-sdk (TOKEN, FEE, DEFAULTS, POOL_POSITIONS) so the plan stays in sync with SDK bumps - Removed redundant comments in simulation engine - Added isLiquidToken0 note to math (asymmetric formula) - Made SimState derived (useMemo) instead of stored — buys + config are the only real state, simState/chartSeries are computed - Added precomputePositions for sqrt(price)/L caching - Added currentPositionIndex to avoid N+1 walk on every buy - Switched tokensRemaining bigint[] → number[] for projection precision - Capped buys log at last 50 to bound replay cost - Pinned ethPrice to one fetch on mount (SWR 5-min cache) https://claude.ai/code/session_01LYo8yCXSHF8vm1KR7nt6b4 --- AGENT_README.md | 30 +++++++- CLAUDE.md | 11 ++- LP-SIMULATOR-PLAN.md | 121 ++++++++++++++++--------------- examples/08-read-only-queries.ts | 4 + llms.txt | 4 + 5 files changed, 106 insertions(+), 64 deletions(-) diff --git a/AGENT_README.md b/AGENT_README.md index 3bb209c..fe7199e 100644 --- a/AGENT_README.md +++ b/AGENT_README.md @@ -455,6 +455,27 @@ const result = await sdk.bidInAuction({ --- +### MEV Descending Fees + +#### `sdk.getMevDescendingFeesBlockDelay()` — Configured block delay + +```typescript +const delay = await sdk.getMevDescendingFeesBlockDelay(); +// delay: bigint — number of blocks the MEV module fee window covers +``` + +`sdk.getMevBlockDelay()` is a deprecated alias for the same call. + +#### `sdk.getPoolUnlockTime(poolId)` — When MEV lock expires + +```typescript +const unlockTime = await sdk.getPoolUnlockTime(poolId); +// If Date.now()/1000 < unlockTime, the pool is still inside the MEV window +// and writes that go through the MevDescendingFees hook may revert with ManagerLocked. +``` + +--- + ### Factory & Allowlist Checks #### `sdk.isFactoryDeprecated()` — Is the factory still active @@ -497,9 +518,9 @@ When calling `deployToken`, all fields except `name` and `symbol` are optional. | `rewardAdmins` | `[walletAddress]` | Deployer is admin | | `rewardRecipients` | `[walletAddress]` | Deployer gets rewards | | `rewardBps` | `[10000]` | 100% to deployer | -| `tickLower` | `[-230400, -198600, -168600]` | 3-tranche Liquid default | -| `tickUpper` | `[-198600, -168600, -122600]` | 3-tranche Liquid default | -| `positionBps` | `[4000, 5000, 1000]` | 40% / 50% / 10% | +| `tickLower` | `[-230400, -216000, -202000, -155000, -141000]` | 5-position Liquid layout | +| `tickUpper` | `[-216000, -155000, -155000, -120000, -120000]` | 5-position Liquid layout | +| `positionBps` | `[1000, 5000, 1500, 2000, 500]` | 10% / 50% / 15% / 20% / 5% | | `mevModule` | `ADDRESSES.SNIPER_AUCTION_V2` | 80%→40% over 20s | | `extensions` | `[]` | No extensions | @@ -568,7 +589,8 @@ import { LiquidSniperUtilV2Abi, LiquidAirdropV2Abi, LiquidPoolExtensionAllowlistAbi, - LiquidMevBlockDelayAbi, + LiquidMevDescendingFeesAbi, + LiquidMevBlockDelayAbi, // deprecated alias — prefer LiquidMevDescendingFeesAbi LiquidLpLockerAbi, ERC20Abi, } from "liquid-sdk"; diff --git a/CLAUDE.md b/CLAUDE.md index bd0c460..e287f9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -209,6 +209,14 @@ await sdk.getAuctionGasPriceForBid(gasPeg, amount) // → bigint await sdk.bidInAuction(params, maxFeePerGas) // → { txHash } (write) ``` +### MEV Descending Fees + +```typescript +await sdk.getMevDescendingFeesBlockDelay() // → bigint (block window) +await sdk.getMevBlockDelay() // → bigint (deprecated alias) +await sdk.getPoolUnlockTime(poolId) // → bigint (unix timestamp) +``` + ### Factory Status ```typescript @@ -359,7 +367,8 @@ The SDK exports 13 ABIs: | `LiquidSniperUtilV2Abi` | Sniper Util (bidInAuction, getTxGasPriceForBidAmount) | | `LiquidAirdropV2Abi` | Airdrop (airdrops, claim, amountAvailableToClaim) | | `LiquidPoolExtensionAllowlistAbi` | Extension allowlist | -| `LiquidMevBlockDelayAbi` | MEV Block Delay (blockDelay, poolUnlockTime) | +| `LiquidMevDescendingFeesAbi` | MEV Descending Fees (blockDelay, poolUnlockTime) | +| `LiquidMevBlockDelayAbi` | Deprecated alias — use `LiquidMevDescendingFeesAbi` | | `LiquidLpLockerAbi` | LP Locker (tokenRewards, collectRewards, updateRewardRecipient, updateRewardAdmin) | | `LiquidTokenAbi` | Token (updateImage, updateMetadata, updateAdmin) | | `LiquidUniv4EthDevBuyAbi` | Dev Buy Extension (receiveTokens) | diff --git a/LP-SIMULATOR-PLAN.md b/LP-SIMULATOR-PLAN.md index 8f383c4..99f43dc 100644 --- a/LP-SIMULATOR-PLAN.md +++ b/LP-SIMULATOR-PLAN.md @@ -29,11 +29,8 @@ lp-simulator/ │ │ ├── FeeProjection.tsx # Static fee rate toggle (1%/2%/3%) + projected fees │ │ └── ExportConfig.tsx # Show/copy SDK DeployTokenParams config │ ├── lib/ -│ │ ├── tick-math.ts # Import from liquid-sdk or port -│ │ ├── positions.ts # Import from liquid-sdk or port -│ │ ├── simulation.ts # Buy simulation engine -│ │ ├── fee-calculator.ts # Fee projection math -│ │ └── constants.ts # Liquid Protocol defaults +│ │ ├── simulation.ts # Buy simulation engine (uses liquid-sdk tick math) +│ │ └── fee-calculator.ts # Fee projection (uses liquid-sdk FEE constants) │ └── hooks/ │ └── useSimulator.ts # Main state management hook ├── public/ @@ -106,33 +103,36 @@ interface SimState { currentTick: number; currentMcapETH: number; totalETHBought: number; - tokensRemaining: bigint[]; // per position + tokensRemaining: number[]; // per position; number is enough for projection precision + currentPositionIndex: number; // resume point — never revisit exhausted positions cumulativeFees: number; } -function simulateBuy(state: SimState, ethAmount: number, feeRate: number): SimState { - let remainingETH = ethAmount; - const fee = remainingETH * feeRate; - remainingETH -= fee; - - // Walk through positions from current tick upward - // Each position has a token reserve that gets consumed - // Price increases as tokens are bought within each range - // When a position is exhausted, move to the next one - - // Uses concentrated liquidity math: - // For each position: L = supply / (sqrt(price_upper) - sqrt(price_lower)) - // Tokens out = L * (sqrt(price_current) - sqrt(price_lower)) - // ETH in = L * (1/sqrt(price_lower) - 1/sqrt(price_current)) - - return newState; +interface PositionPrecomputed { + tickLower: number; + tickUpper: number; + sqrtPriceLower: number; + sqrtPriceUpper: number; + liquidityL: number; +} + +// Precompute sqrt(price) and L per position when positions change. +// Memoize keyed on the positions array so per-buy work stays O(1) for the active range. +function precomputePositions(positions: Position[]): PositionPrecomputed[] { /* ... */ } + +function simulateBuy(state: SimState, ethAmount: number, feeRate: number, pre: PositionPrecomputed[]): SimState { + const fee = ethAmount * feeRate; + let remainingETH = ethAmount - fee; + // Resume from the active position — exhausted ranges are never re-scanned. + // ...consume ETH against pre[currentPositionIndex], advance index when reserve hits zero. } ``` -Key math from Uniswap V4 concentrated liquidity: +Key math from Uniswap V4 concentrated liquidity (note: WETH/token sort order is set by `isLiquidToken0` at deploy time — the simulator must mirror that to pick the right side of the formula): - `price = 1.0001^tick` -- `L (liquidity) = tokenReserve / (sqrt(priceUpper) - sqrt(priceLower))` -- `ETH needed = L * (sqrt(priceAfter) - sqrt(priceBefore))` (simplified for token0/token1 ordering) +- `L = tokenReserve / (sqrt(priceUpper) - sqrt(priceLower))` +- `ETH needed = L * (sqrt(priceAfter) - sqrt(priceBefore))` when WETH is token1 +- `ETH needed = L * (1/sqrt(priceBefore) - 1/sqrt(priceAfter))` when WETH is token0 ### 7. Fee Projection (`FeeProjection.tsx`) @@ -153,9 +153,9 @@ import { LiquidSDK } from "liquid-sdk"; const config = { name: "MyToken", symbol: "MTK", - tickIfToken0IsLiquid: -226600, // from simulator + tickIfToken0IsLiquid: -230400, // from simulator (SDK default starting tick) tickSpacing: 200, - tickLower: [-226600, -216000, -202000, -155000, -141000], + tickLower: [-230400, -216000, -202000, -155000, -141000], tickUpper: [-216000, -155000, -155000, -120000, -120000], positionBps: [1000, 5000, 1500, 2000, 500], // ... extensions if configured @@ -168,28 +168,29 @@ const result = await sdk.deployToken(config); Single `useSimulator` hook manages all state: +Only inputs are real state — `simState` and chart series are derived via `useMemo` so they cannot drift from inputs. + ```typescript -interface SimulatorState { - // Config +interface SimulatorInputs { startingMcapUSD: number; ethPriceUSD: number; feeRate: number; // 0.01, 0.02, or 0.03 tickSpacing: number; - - // Supply airdropPct: number; vaultPct: number; presalePct: number; - - // Positions positions: { tickLower: number; tickUpper: number; positionBps: number }[]; - - // Simulation - buys: number[]; // ETH amounts - simState: SimState; + buys: number[]; // ETH amounts in append order; cap to last 50 for replay cost } + +// Derived (never stored): +// precomputed = useMemo(() => precomputePositions(positions), [positions]) +// simState = useMemo(() => buys.reduce((s, eth) => simulateBuy(s, eth, feeRate, precomputed), initial), [buys, feeRate, precomputed]) +// chartSeries = useMemo(() => buildSeries(precomputed, simState.currentTick, ethPriceUSD), [precomputed, simState.currentTick, ethPriceUSD]) ``` +ETH price is fetched once on mount via SWR (5-min cache) with manual override; do not refetch on input keystrokes. + ## Data Flow ``` @@ -251,31 +252,33 @@ User clicks "Export" ## Key Constants (from SDK) +Import directly from `liquid-sdk` — do not redefine in the simulator. The SDK is the single source of truth and bumps with protocol changes. + ```typescript -// Token -const TOTAL_SUPPLY = 100_000_000_000n * 10n ** 18n; // 100B tokens - -// Fees -const FEE_DENOMINATOR = 1_000_000; // 100% -const PROTOCOL_FEE = 200_000; // 20% of LP fees -const MAX_LP_FEE = 100_000; // 10% -const MAX_MEV_FEE = 800_000; // 80% - -// Positions -const MAX_POSITIONS = 7; -const BPS_DENOMINATOR = 10_000; -const DEFAULT_TICK_SPACING = 200; - -// Default: 5-position Liquid layout -const DEFAULT_POSITIONS = [ - { tickLower: -230400, tickUpper: -216000, positionBps: 1000 }, // 10% - { tickLower: -216000, tickUpper: -155000, positionBps: 5000 }, // 50% - { tickLower: -202000, tickUpper: -155000, positionBps: 1500 }, // 15% - { tickLower: -155000, tickUpper: -120000, positionBps: 2000 }, // 20% - { tickLower: -141000, tickUpper: -120000, positionBps: 500 }, // 5% -]; +import { TOKEN, FEE, DEFAULTS, POOL_POSITIONS } from "liquid-sdk"; + +// TOKEN.SUPPLY // 100B * 10^18 +// TOKEN.DECIMALS // 18 +// TOKEN.MAX_EXTENSIONS // 10 +// TOKEN.MAX_EXTENSION_BPS // 9000 + +// FEE.DENOMINATOR // 1_000_000 (100%) +// FEE.PROTOCOL_FEE_NUMERATOR // 200_000 (20% of LP fees) +// FEE.MAX_LP_FEE // 100_000 (10%) +// FEE.MAX_MEV_FEE // 800_000 (80%) +// FEE.BPS // 10_000 + +// DEFAULTS.TICK_SPACING // 200 +// DEFAULTS.TICK_IF_TOKEN0_IS_LIQUID // -230400 +// DEFAULTS.PAIRED_FEE_BPS // 100 (1%) +// DEFAULTS.LIQUID_FEE_BPS // 100 (1%) + +// POOL_POSITIONS.Liquid // 5-position layout used in Export examples +// POOL_POSITIONS.Standard // single full-range position ``` +`MAX_POSITIONS = 7` is enforced by `createPositions()` in `liquid-sdk/src/utils/positions.ts`; reuse that helper for validation rather than duplicating the constant. + ## Deployment - Host on Vercel (already connected via MCP) diff --git a/examples/08-read-only-queries.ts b/examples/08-read-only-queries.ts index d14b778..722cb78 100644 --- a/examples/08-read-only-queries.ts +++ b/examples/08-read-only-queries.ts @@ -35,6 +35,10 @@ async function main() { const deprecated = await sdk.isFactoryDeprecated(); console.log("\nFactory deprecated:", deprecated); + // MEV descending fees window + const blockDelay = await sdk.getMevDescendingFeesBlockDelay(); + console.log("MEV descending fees block delay:", blockDelay.toString(), "blocks"); + console.log("\nDone — all queries completed without a wallet!"); } diff --git a/llms.txt b/llms.txt index 46599f9..a26c6cd 100644 --- a/llms.txt +++ b/llms.txt @@ -86,6 +86,10 @@ Every token gets 100 billion supply (18 decimals), a Uniswap V4 pool, and locked - `sdk.getAuctionMaxRounds()` — Max auction rounds - `sdk.getAuctionGasPriceForBid(gasPeg, bidAmount)` — Calculate gas price for bid +### MEV Descending Fees +- `sdk.getMevDescendingFeesBlockDelay()` — Configured block delay (also: `getMevBlockDelay()` deprecated alias) +- `sdk.getPoolUnlockTime(poolId)` — When MEV lock expires (unix timestamp) + ### Factory & Allowlist - `sdk.isFactoryDeprecated()` — Is factory still active - `sdk.isLockerEnabled(locker, hook)` — Is locker approved for hook