Created
May 26, 2026 22:30
-
-
Save 0xdeployer/a1202aedd8374a539a831d91516a7d0e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: uniswap-v4-single-sided-lp | |
| description: Mint, unwind, or reposition a single-sided Uniswap v4 LP position (one currency only) using raw PositionManager.modifyLiquidities calldata. Covers tick math for token0/token1 ordering, dynamic-fee Doppler pools, Permit2 approval chain, MINT_POSITION + SETTLE_PAIR (mint) and BURN_POSITION + TAKE_PAIR (unwind) action sequences on Base. | |
| visibility: private | |
| --- | |
| # Uniswap v4 single-sided LP (raw calldata) | |
| How to mint, unwind, or reposition a single-sided concentrated liquidity position on a Uniswap v4 pool when no dedicated tool exists. Battle-tested on Base against Doppler-hooked WETH/ERC20 pools. | |
| ## Key contracts (Base) | |
| - **PositionManager (v4)**: `0x7C5f5A4bBd8fD63184577525326123B519429bDc` | |
| - **PoolManager (v4)**: `0x498581fF718922c3f8e6A244956aF099B2652b2b` | |
| - **Permit2**: `0x000000000022D473030F116dDEE9F6B43aC78BA3` | |
| - **StateView (read pool state)**: `0xA3c0c9b65baD0b08107Aa264b0f3dB444b867A71` | |
| - **WETH (currency0 when paired with ERC20)**: `0x4200000000000000000000000000000000000006` | |
| For other chains, look up the v4 deployment addresses — the calldata shape below is identical. | |
| ## Action codes (from periphery `Actions.sol`) | |
| Liquidity actions (PositionManager): | |
| - `0x00` INCREASE_LIQUIDITY | |
| - `0x01` DECREASE_LIQUIDITY | |
| - `0x02` MINT_POSITION | |
| - `0x03` BURN_POSITION | |
| - `0x04` INCREASE_LIQUIDITY_FROM_DELTAS | |
| - `0x05` MINT_POSITION_FROM_DELTAS | |
| Settle/Take actions: | |
| - `0x0b` SETTLE | |
| - `0x0c` SETTLE_ALL | |
| - `0x0d` SETTLE_PAIR | |
| - `0x0e` TAKE | |
| - `0x0f` TAKE_ALL | |
| - `0x10` TAKE_PORTION | |
| - `0x11` TAKE_PAIR | |
| - `0x12` CLOSE_CURRENCY | |
| - `0x13` CLEAR_OR_TAKE | |
| - `0x14` SWEEP | |
| Common sequences: | |
| - **Mint single-sided**: `0x020d` (MINT_POSITION + SETTLE_PAIR) | |
| - **Unwind in one tx**: `0x0311` (BURN_POSITION + TAKE_PAIR) | |
| - **Decrease only (keep NFT)**: `0x0111` (DECREASE_LIQUIDITY + TAKE_PAIR) | |
| ## Concepts you MUST get right | |
| ### 1. Currency ordering | |
| v4 pools sort `currency0 < currency1` by address. WETH on Base (`0x4200…0006`) is almost always `currency0` when paired with a higher-address ERC20. | |
| The pool's **tick** represents `price = currency1 / currency0`, i.e. how much currency1 you get per 1 currency0. | |
| ### 2. Single-sided side selection | |
| - Position holds **only currency0** when `currentTick < tickLower` (entire range above current). | |
| - Position holds **only currency1** when `currentTick > tickUpper` (entire range below current). | |
| - Mixed when in range. | |
| ### 3. Tick math | |
| ``` | |
| priceRatio = 1.0001 ^ tick // currency1 per currency0 (raw) | |
| ``` | |
| For human pricing of currency1 in USD: | |
| ``` | |
| usdPrice(c1) = usdPrice(c0) / priceRatio | |
| ``` | |
| So **higher tick → cheaper currency1** (more currency1 per WETH). | |
| To move currency1's USD price UP by x%, tick must go DOWN by `ln(1+x)/ln(1.0001) ≈ x*10000` ticks (for small x). "1% above current price" → `tickUpper ≈ currentTick - 100`. | |
| ### 4. tickSpacing rounding | |
| Both `tickLower` and `tickUpper` must be multiples of the pool's `tickSpacing`. Round toward the side that keeps the position correctly single-sided (i.e. keep tickUpper strictly below currentTick for single-sided currency1). | |
| ### 5. Liquidity from a target amount | |
| For a position holding only currency1 (below current): | |
| ``` | |
| L = amount1 * 2^96 / (sqrtP(tickUpper) - sqrtP(tickLower)) | |
| ``` | |
| For only currency0 (above current): | |
| ``` | |
| L = amount0 * sqrtP(tickLower) * sqrtP(tickUpper) / (2^96 * (sqrtP(tickUpper) - sqrtP(tickLower))) | |
| ``` | |
| where `sqrtP(t) = sqrt(1.0001^t) * 2^96` (Q64.96). Use the full v3 TickMath.getSqrtRatioAtTick BigInt implementation — the polynomial constants matter at large |tick|. The inline `compute-liquidity.js` script below includes a copy-paste implementation. | |
| ## Step-by-step workflow (MINT) | |
| ### Step 0 — gather pool data | |
| Read pool state from StateView using the poolId (a `bytes32` hash of the PoolKey): | |
| ``` | |
| StateView.getSlot0(poolId) → (sqrtPriceX96, tick, protocolFee, lpFee) | |
| ``` | |
| You also need the PoolKey fields (`currency0`, `currency1`, `fee`, `tickSpacing`, `hooks`). For pools you didn't create, decode the PoolKey from the pool's deployment tx, a subgraph, or — if you already have a position there — `PositionManager.getPoolAndPositionInfo(tokenId)` returns the full PoolKey tuple. For Doppler pools, `fee = 0x800000 = 8388608` (dynamic-fee sentinel — hook controls the actual fee). | |
| ### Step 1 — plan ticks and L | |
| 1. Compute current human price and current tick. | |
| 2. Translate user's price/mcap targets into target ticks. | |
| 3. Round to `tickSpacing`, picking the rounding direction that keeps the position single-sided (`tickUpper < currentTick` for currency1-only, `tickLower > currentTick` for currency0-only). | |
| 4. Compute L from the target amount using the formula above (or run `compute-liquidity.js`). | |
| 5. **Sanity-check**: re-derive amount needed from L and confirm it matches the target within rounding. | |
| ### Step 2 — approval chain (TWO approvals required) | |
| v4 uses Permit2 indirection. For each ERC20 you're depositing: | |
| **Approval A — ERC20 → Permit2** (one-time per token): | |
| ``` | |
| ERC20.approve(Permit2, max uint256) | |
| ``` | |
| **Approval B — Permit2 → PositionManager** (one-time per token+spender): | |
| ``` | |
| Permit2.approve(token, PositionManager, max uint160, max uint48) | |
| ``` | |
| Use `max uint160 = 2^160 - 1` and `max uint48 = 2^48 - 1` for "infinite". | |
| **Check before approving**: | |
| - `ERC20.allowance(owner, Permit2)` — skip if already max. | |
| - `Permit2.allowance(owner, token, PositionManager)` returns `(uint160 amount, uint48 expiration, uint48 nonce)` — skip if amount is high and not expired. | |
| **CRITICAL — both sides matter even for single-sided positions.** If your range is right next to the current tick, the v4 math may compute a 1-wei delta on the "empty" currency. Two options: | |
| - (a) Approve a small amount of the other currency too (safer for tight ranges). | |
| - (b) Set `amount0Max = 0` (or `amount1Max = 0`), accepting the tx will revert if any dust is owed. Safe with a healthy tick gap from current. | |
| A mint will revert with `TRANSFER_FROM_FAILED` if `amount0Max = 1` triggers a 1-wei pull on the "empty" currency with no approval in place. Setting that side's `amountMax = 0` avoids the dust pull entirely. | |
| ### Step 3 — encode and submit modifyLiquidities | |
| `PositionManager.modifyLiquidities(bytes unlockData, uint256 deadline)` (selector `0xdd46508f`) | |
| Where `unlockData = abi.encode(bytes actions, bytes[] params)`. | |
| **actions** = `0x020d` (MINT_POSITION + SETTLE_PAIR) | |
| **params[0] — MINT_POSITION** (8 fields, abi-encoded): | |
| 1. `PoolKey` tuple: `(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks)` | |
| 2. `int24 tickLower` | |
| 3. `int24 tickUpper` | |
| 4. `uint256 liquidity` — the L you computed | |
| 5. `uint128 amount0Max` — max currency0 you'll spend (use 0 for single-sided c1 with healthy tick gap) | |
| 6. `uint128 amount1Max` — max currency1 you'll spend (target + ~1% buffer) | |
| 7. `address recipient` — usually `msg.sender` | |
| 8. `bytes hookData` — `0x` for most pools (Doppler ignores it) | |
| **params[1] — SETTLE_PAIR** (2 fields): `address currency0`, `address currency1`. Pulls owed currencies from `msg.sender` via Permit2. | |
| Use the inline `build-mint-calldata.js` script below to encode all of this with viem. | |
| ### Step 4 — verify and save | |
| After tx success, PositionManager mints an LP NFT to the recipient. The new tokenId is in the ERC721 `Transfer` log from `address(0)` (topic1) to the recipient — read the receipt with `getTransactionReceipt` and filter logs by PositionManager address and `Transfer` topic `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`. | |
| Save position details (tokenId, poolId, ticks, L, hook, recipient, tx hash) to `/.memory/project_<protocol>_<pool>.md`. | |
| ## Repositioning workflow | |
| To shift an existing position to a new range, do it in TWO transactions: unwind, then re-mint. (A multicall combining both in one tx is possible but trickier; for reliability prefer two txs.) | |
| ### Step R0 — read current position | |
| ``` | |
| PositionManager.getPoolAndPositionInfo(tokenId) → (PoolKey poolKey, uint256 info) | |
| PositionManager.getPositionLiquidity(tokenId) → uint128 L | |
| StateView.getSlot0(poolId) → (sqrtPriceX96, tick, ...) | |
| ``` | |
| Confirm the position is still where you think it is and that the user owns the NFT (`ownerOf(tokenId)`). | |
| ### Step R1 — unwind: BURN_POSITION + TAKE_PAIR (one tx) | |
| `BURN_POSITION` decreases ALL remaining liquidity AND burns the NFT in one action — no separate decrease step required when fully exiting. `TAKE_PAIR` then sweeps both currency credits to the recipient. | |
| **actions** = `0x0311` | |
| **params[0] — BURN_POSITION** (4 fields): | |
| 1. `uint256 tokenId` | |
| 2. `uint128 amount0Min` (slippage floor for currency0 returned; `0` if you're OK with anything) | |
| 3. `uint128 amount1Min` (same for currency1) | |
| 4. `bytes hookData` — `0x` | |
| **params[1] — TAKE_PAIR** (3 fields): | |
| 1. `address currency0` | |
| 2. `address currency1` | |
| 3. `address recipient` | |
| After this tx, both currencies land in the recipient's wallet (no Permit2 needed for receiving) and `getPositionLiquidity(tokenId)` reverts/returns 0 since the NFT is burned. | |
| Use the inline `build-unwind-calldata.js` script below to encode the unwind. | |
| ### Step R2 — re-read pool state, compute new ticks | |
| The tick may have moved during/after the unwind tx. Re-call `StateView.getSlot0(poolId)` to get the freshest `currentTick`, then plan the new `tickLower`/`tickUpper` per Step 1 above. | |
| If the position was in-range when unwound, the wallet now holds BOTH currencies. To re-mint single-sided in the same currency, you may need to swap the other side first OR choose a new range that's still single-sided on the side you have more of. | |
| ### Step R3 — re-mint per Steps 2–4 above | |
| Approvals from the original mint are still live (Permit2 approvals don't expire for ~80 years), so this is just `modifyLiquidities(0x020d, …)` again with the new ticks and updated L. The new tokenId comes from the receipt's `Transfer` log. | |
| **Repositioning gotcha**: if the price moved while you were in-range, the unwind returns BOTH currencies. Example: a single-sided currency1 position with `tickUpper` below the price at mint can end up straddling `tickUpper` if the price drops into the range — when you unwind you'll receive a mix of currency0 + currency1, not just currency1. Account for this before re-minting single-sided (swap the unwanted side, or pick a new range that's single-sided on whichever side you have more of). | |
| ## Common failure modes | |
| - **`TRANSFER_FROM_FAILED`** — Permit2 approval missing on a currency the position needs (often the "empty" side due to dust). Approve a small buffer of the other side OR set its `amountMax = 0`. | |
| - **`InvalidTickSpacing` / `TickMisaligned`** — `tickLower` or `tickUpper` not a multiple of `tickSpacing`. | |
| - **Position has tokens on the wrong side** — you crossed the current tick. For single-sided c1, ensure `tickUpper < currentTick`. For single-sided c0, ensure `tickLower > currentTick`. | |
| - **Position got both currencies returned on unwind even though you minted single-sided** — current tick moved into your range while you held it. Expected; account for the extra currency before re-minting. | |
| - **Hook reverts** — Doppler hooks have schedules and may reject liquidity mints outside certain windows. Check the hook's `beforeAddLiquidity` requirements. | |
| ## Inline scripts | |
| All three scripts use viem (`bun add viem` or `npm i viem`). Run with `node <file>.js`. Edit the `CONFIG` block at the top of each before running. | |
| ### compute-liquidity.js | |
| Given target amount, ticks, and current tick, output L and the back-derived amount. Self-contained BigInt port of Uniswap v3 TickMath.getSqrtRatioAtTick — no dependencies. | |
| ```js | |
| // Compute Uniswap v4 liquidity L from a target amount and tick range. | |
| // Usage: node compute-liquidity.js | |
| // Edit the CONFIG section below. | |
| const CONFIG = { | |
| tickLower: 0, // multiple of pool tickSpacing | |
| tickUpper: 0, // multiple of pool tickSpacing | |
| currentTick: 0, // from StateView.getSlot0 | |
| side: 'currency1', // 'currency0' or 'currency1' | |
| amount: 0n, // target amount in atomic units (e.g. 1B * 1e18 = 10n ** 9n * 10n ** 18n) | |
| }; | |
| // --- TickMath: sqrtPriceX96 = sqrt(1.0001^tick) * 2^96 --- | |
| // Port of Uniswap v3/v4 TickMath.sol getSqrtRatioAtTick. | |
| function getSqrtRatioAtTick(tick) { | |
| const absTick = BigInt(tick < 0 ? -tick : tick); | |
| let ratio = (absTick & 0x1n) !== 0n | |
| ? 0xfffcb933bd6fad37aa2d162d1a594001n | |
| : 0x100000000000000000000000000000000n; | |
| const m = (x) => { ratio = (ratio * x) >> 128n; }; | |
| if ((absTick & 0x2n) !== 0n) m(0xfff97272373d413259a46990580e213an); | |
| if ((absTick & 0x4n) !== 0n) m(0xfff2e50f5f656932ef12357cf3c7fdccn); | |
| if ((absTick & 0x8n) !== 0n) m(0xffe5caca7e10e4e61c3624eaa0941cd0n); | |
| if ((absTick & 0x10n) !== 0n) m(0xffcb9843d60f6159c9db58835c926644n); | |
| if ((absTick & 0x20n) !== 0n) m(0xff973b41fa98c081472e6896dfb254c0n); | |
| if ((absTick & 0x40n) !== 0n) m(0xff2ea16466c96a3843ec78b326b52861n); | |
| if ((absTick & 0x80n) !== 0n) m(0xfe5dee046a99a2a811c461f1969c3053n); | |
| if ((absTick & 0x100n) !== 0n) m(0xfcbe86c7900a88aedcffc83b479aa3a4n); | |
| if ((absTick & 0x200n) !== 0n) m(0xf987a7253ac413176f2b074cf7815e54n); | |
| if ((absTick & 0x400n) !== 0n) m(0xf3392b0822b70005940c7a398e4b70f3n); | |
| if ((absTick & 0x800n) !== 0n) m(0xe7159475a2c29b7443b29c7fa6e889d9n); | |
| if ((absTick & 0x1000n) !== 0n) m(0xd097f3bdfd2022b8845ad8f792aa5825n); | |
| if ((absTick & 0x2000n) !== 0n) m(0xa9f746462d870fdf8a65dc1f90e061e5n); | |
| if ((absTick & 0x4000n) !== 0n) m(0x70d869a156d2a1b890bb3df62baf32f7n); | |
| if ((absTick & 0x8000n) !== 0n) m(0x31be135f97d08fd981231505542fcfa6n); | |
| if ((absTick & 0x10000n) !== 0n) m(0x9aa508b5b7a84e1c677de54f3e99bc9n); | |
| if ((absTick & 0x20000n) !== 0n) m(0x5d6af8dedb81196699c329225ee604n); | |
| if ((absTick & 0x40000n) !== 0n) m(0x2216e584f5fa1ea926041bedfe98n); | |
| if ((absTick & 0x80000n) !== 0n) m(0x48a170391f7dc42444e8fa2n); | |
| if (tick > 0) ratio = (1n << 256n) / ratio; | |
| // sqrtPriceX96 = ratio >> 32 | |
| return ratio >> 32n; | |
| } | |
| const Q96 = 1n << 96n; | |
| const liquidityForCurrency1 = (amount1, sqrtPL, sqrtPU) => | |
| (amount1 * Q96) / (sqrtPU - sqrtPL); | |
| const liquidityForCurrency0 = (amount0, sqrtPL, sqrtPU) => | |
| (amount0 * sqrtPL * sqrtPU) / Q96 / (sqrtPU - sqrtPL); | |
| const amount1FromL = (L, sqrtPL, sqrtPU) => (L * (sqrtPU - sqrtPL)) / Q96; | |
| const amount0FromL = (L, sqrtPL, sqrtPU) => (L * Q96 * (sqrtPU - sqrtPL)) / (sqrtPU * sqrtPL); | |
| const { tickLower, tickUpper, currentTick, side, amount } = CONFIG; | |
| if (tickLower >= tickUpper) throw new Error('tickLower must be < tickUpper'); | |
| if (side === 'currency1' && tickUpper >= currentTick) { | |
| console.warn('WARNING: tickUpper >= currentTick — position will hold currency0 too'); | |
| } | |
| if (side === 'currency0' && tickLower <= currentTick) { | |
| console.warn('WARNING: tickLower <= currentTick — position will hold currency1 too'); | |
| } | |
| const sqrtPL = getSqrtRatioAtTick(tickLower); | |
| const sqrtPU = getSqrtRatioAtTick(tickUpper); | |
| let L, recheck; | |
| if (side === 'currency1') { | |
| L = liquidityForCurrency1(amount, sqrtPL, sqrtPU); | |
| recheck = amount1FromL(L, sqrtPL, sqrtPU); | |
| } else { | |
| L = liquidityForCurrency0(amount, sqrtPL, sqrtPU); | |
| recheck = amount0FromL(L, sqrtPL, sqrtPU); | |
| } | |
| console.log('sqrtPL :', sqrtPL.toString()); | |
| console.log('sqrtPU :', sqrtPU.toString()); | |
| console.log('L :', L.toString()); | |
| console.log('L hex :', '0x' + L.toString(16)); | |
| console.log('target :', amount.toString()); | |
| console.log('rederive:', recheck.toString()); | |
| console.log('delta :', (amount - recheck).toString(), '(should be small/non-negative)'); | |
| ``` | |
| ### build-mint-calldata.js | |
| Given PoolKey, ticks, L, max amounts, recipient → outputs ready-to-submit `modifyLiquidities` calldata for the `0x020d` (MINT_POSITION + SETTLE_PAIR) sequence. | |
| ```js | |
| // Build Uniswap v4 PositionManager.modifyLiquidities calldata for a single-sided MINT_POSITION. | |
| // Requires viem. bun add viem (or npm i viem) | |
| // node build-mint-calldata.js | |
| import { encodeAbiParameters, encodeFunctionData } from 'viem'; | |
| const CONFIG = { | |
| poolKey: { | |
| currency0: '0x4200000000000000000000000000000000000006', // WETH on Base (replace for other chains/pairs) | |
| currency1: '0x0000000000000000000000000000000000000000', // the ERC20 you're LPing | |
| fee: 8388608, // 0x800000 = dynamic-fee sentinel (Doppler). For static-fee pools use e.g. 3000 = 0.30%. | |
| tickSpacing: 200, | |
| hooks: '0x0000000000000000000000000000000000000000', // hook address, or 0x0000...0000 for hookless pool | |
| }, | |
| tickLower: 0, | |
| tickUpper: 0, | |
| liquidity: 0n, // from compute-liquidity.js | |
| amount0Max: 0n, // 0 for single-sided currency1 (healthy tick gap) | |
| amount1Max: 0n, // target + ~1% buffer for single-sided currency1 | |
| recipient: '0x0000000000000000000000000000000000000000', // usually msg.sender | |
| hookData: '0x', | |
| deadlineSecondsFromNow: 3600, | |
| }; | |
| const POSITION_MANAGER = '0x7C5f5A4bBd8fD63184577525326123B519429bDc'; // Base | |
| const ACTION_MINT_POSITION = 0x02; | |
| const ACTION_SETTLE_PAIR = 0x0d; | |
| const { poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData } = CONFIG; | |
| const mintParams = encodeAbiParameters( | |
| [ | |
| { type: 'tuple', components: [ | |
| { type: 'address' }, { type: 'address' }, | |
| { type: 'uint24' }, { type: 'int24' }, { type: 'address' }, | |
| ]}, | |
| { type: 'int24' }, { type: 'int24' }, | |
| { type: 'uint256' }, | |
| { type: 'uint128' }, { type: 'uint128' }, | |
| { type: 'address' }, | |
| { type: 'bytes' }, | |
| ], | |
| [ | |
| [poolKey.currency0, poolKey.currency1, poolKey.fee, poolKey.tickSpacing, poolKey.hooks], | |
| tickLower, tickUpper, | |
| liquidity, | |
| amount0Max, amount1Max, | |
| recipient, | |
| hookData, | |
| ] | |
| ); | |
| const settleParams = encodeAbiParameters( | |
| [{ type: 'address' }, { type: 'address' }], | |
| [poolKey.currency0, poolKey.currency1] | |
| ); | |
| const actions = '0x' | |
| + ACTION_MINT_POSITION.toString(16).padStart(2,'0') | |
| + ACTION_SETTLE_PAIR.toString(16).padStart(2,'0'); | |
| const unlockData = encodeAbiParameters( | |
| [{ type: 'bytes' }, { type: 'bytes[]' }], | |
| [actions, [mintParams, settleParams]] | |
| ); | |
| const deadline = BigInt(Math.floor(Date.now()/1000) + CONFIG.deadlineSecondsFromNow); | |
| const data = encodeFunctionData({ | |
| abi: [{ name:'modifyLiquidities', type:'function', inputs:[ | |
| { name:'unlockData', type:'bytes' }, | |
| { name:'deadline', type:'uint256' }, | |
| ]}], | |
| functionName: 'modifyLiquidities', | |
| args: [unlockData, deadline], | |
| }); | |
| console.log('to :', POSITION_MANAGER); | |
| console.log('value : 0'); | |
| console.log('deadline:', deadline.toString()); | |
| console.log('data :', data); | |
| ``` | |
| ### build-unwind-calldata.js | |
| Given tokenId, the two pool currencies, and a recipient → outputs `modifyLiquidities` calldata for the `0x0311` (BURN_POSITION + TAKE_PAIR) sequence. Fully exits the position and burns the LP NFT in one tx. | |
| ```js | |
| // Build Uniswap v4 PositionManager.modifyLiquidities calldata for a full-exit UNWIND. | |
| // BURN_POSITION decreases ALL remaining liquidity AND burns the NFT — one tx, full exit. | |
| // Requires viem. bun add viem (or npm i viem) | |
| // node build-unwind-calldata.js | |
| import { encodeAbiParameters, encodeFunctionData } from 'viem'; | |
| const CONFIG = { | |
| tokenId: 0n, // LP NFT tokenId to unwind (PositionManager ERC721) | |
| currency0: '0x4200000000000000000000000000000000000006', // pool's currency0 (lower address) | |
| currency1: '0x0000000000000000000000000000000000000000', // pool's currency1 (higher address) | |
| amount0Min: 0n, // slippage floor for currency0 returned (0 = accept anything) | |
| amount1Min: 0n, // slippage floor for currency1 returned | |
| recipient: '0x0000000000000000000000000000000000000000', // where the freed currencies land | |
| hookData: '0x', | |
| deadlineSecondsFromNow: 3600, | |
| }; | |
| const POSITION_MANAGER = '0x7C5f5A4bBd8fD63184577525326123B519429bDc'; // Base | |
| const ACTION_BURN_POSITION = 0x03; | |
| const ACTION_TAKE_PAIR = 0x11; | |
| const { tokenId, currency0, currency1, amount0Min, amount1Min, recipient, hookData } = CONFIG; | |
| const burnParams = encodeAbiParameters( | |
| [ | |
| { type: 'uint256' }, // tokenId | |
| { type: 'uint128' }, // amount0Min | |
| { type: 'uint128' }, // amount1Min | |
| { type: 'bytes' }, // hookData | |
| ], | |
| [tokenId, amount0Min, amount1Min, hookData] | |
| ); | |
| const takeParams = encodeAbiParameters( | |
| [ | |
| { type: 'address' }, // currency0 | |
| { type: 'address' }, // currency1 | |
| { type: 'address' }, // recipient | |
| ], | |
| [currency0, currency1, recipient] | |
| ); | |
| const actions = '0x' | |
| + ACTION_BURN_POSITION.toString(16).padStart(2,'0') | |
| + ACTION_TAKE_PAIR.toString(16).padStart(2,'0'); | |
| const unlockData = encodeAbiParameters( | |
| [{ type: 'bytes' }, { type: 'bytes[]' }], | |
| [actions, [burnParams, takeParams]] | |
| ); | |
| const deadline = BigInt(Math.floor(Date.now()/1000) + CONFIG.deadlineSecondsFromNow); | |
| const data = encodeFunctionData({ | |
| abi: [{ name:'modifyLiquidities', type:'function', inputs:[ | |
| { name:'unlockData', type:'bytes' }, | |
| { name:'deadline', type:'uint256' }, | |
| ]}], | |
| functionName: 'modifyLiquidities', | |
| args: [unlockData, deadline], | |
| }); | |
| console.log('to :', POSITION_MANAGER); | |
| console.log('value : 0'); | |
| console.log('deadline:', deadline.toString()); | |
| console.log('data :', data); | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment