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..fe7199e 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. @@ -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,21 +455,23 @@ const result = await sdk.bidInAuction({ --- -### MEV Protection +### MEV Descending Fees -#### `sdk.getMevBlockDelay()` — Get configured block delay +#### `sdk.getMevDescendingFeesBlockDelay()` — Configured block delay ```typescript -const delay = await sdk.getMevBlockDelay(); -// delay: bigint — number of blocks +const delay = await sdk.getMevDescendingFeesBlockDelay(); +// delay: bigint — number of blocks the MEV module fee window covers ``` -#### `sdk.getPoolUnlockTime(poolId)` — When does MEV lock expire +`sdk.getMevBlockDelay()` is a deprecated alias for the same call. + +#### `sdk.getPoolUnlockTime(poolId)` — When MEV lock expires ```typescript const unlockTime = await sdk.getPoolUnlockTime(poolId); -// unlockTime: bigint — unix timestamp -// If Date.now()/1000 < unlockTime, pool is still locked +// 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. ``` --- @@ -516,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 | @@ -587,7 +589,8 @@ import { LiquidSniperUtilV2Abi, LiquidAirdropV2Abi, LiquidPoolExtensionAllowlistAbi, - LiquidMevBlockDelayAbi, + LiquidMevDescendingFeesAbi, + LiquidMevBlockDelayAbi, // deprecated alias — prefer LiquidMevDescendingFeesAbi LiquidLpLockerAbi, ERC20Abi, } from "liquid-sdk"; 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..e287f9e 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. --- @@ -209,10 +209,11 @@ await sdk.getAuctionGasPriceForBid(gasPeg, amount) // → bigint await sdk.bidInAuction(params, maxFeePerGas) // → { txHash } (write) ``` -### MEV Protection +### MEV Descending Fees ```typescript -await sdk.getMevBlockDelay() // → bigint +await sdk.getMevDescendingFeesBlockDelay() // → bigint (block window) +await sdk.getMevBlockDelay() // → bigint (deprecated alias) await sdk.getPoolUnlockTime(poolId) // → bigint (unix timestamp) ``` @@ -366,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 new file mode 100644 index 0000000..99f43dc --- /dev/null +++ b/LP-SIMULATOR-PLAN.md @@ -0,0 +1,286 @@ +# 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/ +│ │ ├── 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/ +│ └── 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: number[]; // per position; number is enough for projection precision + currentPositionIndex: number; // resume point — never revisit exhausted positions + cumulativeFees: number; +} + +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 (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 = 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`) + +- **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: -230400, // from simulator (SDK default starting tick) + tickSpacing: 200, + tickLower: [-230400, -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: + +Only inputs are real state — `simState` and chart series are derived via `useMemo` so they cannot drift from inputs. + +```typescript +interface SimulatorInputs { + startingMcapUSD: number; + ethPriceUSD: number; + feeRate: number; // 0.01, 0.02, or 0.03 + tickSpacing: number; + airdropPct: number; + vaultPct: number; + presalePct: number; + positions: { tickLower: number; tickUpper: number; positionBps: number }[]; + 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 + +``` +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) + +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 +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) +- 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..722cb78 100644 --- a/examples/08-read-only-queries.ts +++ b/examples/08-read-only-queries.ts @@ -35,9 +35,9 @@ 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"); + // 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 413abe7..a26c6cd 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,8 +86,8 @@ 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 +### MEV Descending Fees +- `sdk.getMevDescendingFeesBlockDelay()` — Configured block delay (also: `getMevBlockDelay()` deprecated alias) - `sdk.getPoolUnlockTime(poolId)` — When MEV lock expires (unix timestamp) ### Factory & Allowlist 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