{"id":29125725,"url":"https://github.com/duneanalytics/sim-idx-example-univ3-lp","last_synced_at":"2026-01-20T16:36:22.049Z","repository":{"id":300741046,"uuid":"1005999241","full_name":"duneanalytics/sim-idx-example-univ3-lp","owner":"duneanalytics","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-30T12:35:37.000Z","size":148,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-30T13:24:06.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Solidity","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/duneanalytics.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-21T09:04:17.000Z","updated_at":"2025-06-30T12:05:54.000Z","dependencies_parsed_at":"2025-06-23T11:37:35.868Z","dependency_job_id":null,"html_url":"https://github.com/duneanalytics/sim-idx-example-univ3-lp","commit_stats":null,"previous_names":["duneanalytics/sim-idx-example-univ3-lp"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/duneanalytics/sim-idx-example-univ3-lp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duneanalytics%2Fsim-idx-example-univ3-lp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duneanalytics%2Fsim-idx-example-univ3-lp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duneanalytics%2Fsim-idx-example-univ3-lp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duneanalytics%2Fsim-idx-example-univ3-lp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/duneanalytics","download_url":"https://codeload.github.com/duneanalytics/sim-idx-example-univ3-lp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duneanalytics%2Fsim-idx-example-univ3-lp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265872231,"owners_count":23842151,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-29T22:02:45.373Z","updated_at":"2026-01-20T16:36:22.023Z","avatar_url":"https://github.com/duneanalytics.png","language":"Solidity","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Uniswap V3 In-Range LPs\n\nThis app will index liquidity provision events on Uniswap V3 pools and expose an endpoint which can be used in order to query the in-range positions of a specific pool at every given block.\n\nTo use this template you need to follow the steps in the Sim IDX [quickstart](https://docs.sim.dune.com/idx) but initialize with : \n`sim init --template=univ3-lp`\n\n## Exposed API\n\nWe expose the following API:\n`/lp-snapshot?pool=5777d92f208679DB4b9778590Fa3CAB3aC9e2168\u0026block_number=23753712`\n\nThis will then output a list of the in-range LPs:\n```json\n{\n  \"result\": [    \n      {\n        \"liquidity\": \"285882538055745050066296\",\n        \"token0Held\": 26845832.2898499,\n        \"token1Held\": 30324973.4259972,\n        \"tickLower\": \"-276326\",\n        \"tickUpper\": \"-276322\",\n        \"tokenId\": \"0\",\n        \"positionOwner\": \"0x50379f632ca68d36e50cfbc8f78fe16bd1499d1e\",\n        \"token0\": \"0x6b175474e89094c44da98b954eedeac495271d0f\",\n        \"token1\": \"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\"\n      },\n      {\n        \"liquidity\": \"180912980957391541890\",\n        \"token0Held\": 16988.6540775426,\n        \"token1Held\": 19190.3338247301,\n        \"tickLower\": \"-276326\",\n        \"tickUpper\": \"-276322\",\n        \"tokenId\": \"160967\",\n        \"positionOwner\": \"0x96bcc2eb087633a7d434ac332ae436335f32989e\",\n        \"token0\": \"0x6b175474e89094c44da98b954eedeac495271d0f\",\n        \"token1\": \"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\"\n      }\n    ]\n}\n```\n\nWhere `token_id = 0` indicates a position that was not created via the `NFTPositionManager` contract, but directly via interacting with the pool.\n\n## Indexing Methodology\n\nWe use a `Main.sol` file that does the following:\n- Triggers on every `Burn`, `Mint` or `Swap` event on all Uniswap V3 pools using a set of ABI Triggers.\n- Triggers on the `Transfer`, `IncreaseLiquidity` and `DecreaseLiquidity` events on the `NFTPositionManager` contract.\n- Has a block trigger.\n\nWe define the following events in our `Main.sol`:\n```solidity\n    /// Event to index the ownership changes of a positions in the NFTPositionManager.\n    event PositionOwnerChanges(\n        bytes32 txn_hash,\n        uint256 block_number,\n        uint256 block_timestamp,\n        address from_address,\n        address to_address,\n        uint256 token_id,\n        address pool\n    );\n    /// Event to index the changes in pool ticks\n    event PoolTicksPerBlock(\n        bytes32 txn_hash, \n        uint256 block_number, \n        uint256 block_timestamp, \n        address pool, \n        int64 tick,\n        uint256 sqrt_price_x96,\n        address token0,\n        address token1,\n        uint64 token0_decimals,\n        uint64 token1_decimals\n    );\n    /// Event to track changes in liquidity of positions\n    event LpEvents(\n        bytes32 txn_hash,\n        uint256 block_number,\n        uint256 block_timestamp,\n        address pool,\n        string event_type,\n        address owner,\n        uint256 amount,\n        int64 tick_lower,\n        uint256 sqrt_price_x96_lower,\n        int64 tick_upper,\n        uint256 sqrt_price_x96_upper,\n        uint256 token_id\n    );\n```\n\nOn each of the triggers we've defined for the `UniswapV3Pool` ABI, we will check if the pool is an official Uniswap V3 pool by querying its `factory()` method.\nIf so, we check if the owner of the position is the `NFTPositionManager` and if that's the case, we keep the details of the LP event in our ephemeral state in order to emit it later.\nIf the owner is not the `NFTPositionManager`, we emit the LP event message as is.\nWe use the triggers we have on `IncreaseLiquidity` and `DecreaseLiquidity` in order to augment the LP events we've saved with the `token_id` that is associated with the position we've saved in our ephemeral state. This will be later used in order to recover the owner of the position.\nWe use the `Transfer` trigger in order to keep track of the ownership of positions.\nIn addition, `Swap` events are used in order to keep track of the latest ticks for pools. We save those in the ephemeral state too as we need them to be emitted with a block-level granularity.\nFinally, inside our block trigger, we emit all saved LP events and the pool ticks we've collected.\n\nWe make use of two unique features of Sim IDX in our listener:\n - `TickMath.getSqrtPriceAtTick` - we've imported the full `TickMath.sol` library off of the Uniswap V4. This allows us to calculate the `sqrtPriceRatioX96_lower` and `sqrtPriceRatioX96_upper` that corresponds to any position's lower and upper ticks. We then augment our LP events with that information to avoid doing the calculations in SQL or TypeScript.\n - `(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(ctx.txn.call.callee).slot0();` - We use our listener's ability to access state at the time of handling triggers, this allows us to augment our `PoolTicksPerBlock` with the `sqrtPriceX96` which isn't available in the pools' `Swap` event.\n\n## Querying Methodology\n\nThe `/lp-snapshot` endpoint queries the 3 tables in our DB to determine in-range liquidity positions at a specific block number. The query executes in four main steps:\n\n### 1. Pool Tick Discovery\nFirst, we retrieve the latest tick for the specified pool at or before the target block number:\n```sql\nSELECT block_number, tick, sqrtPriceX96, token0, token1, token0Decimals, token1Decimals\nFROM pool_ticks_per_block \nWHERE pool = $pool AND block_number \u003c= $block_number \nORDER BY block_number DESC \nLIMIT 1\n```\n\n### 2. In-Range LP Events Filtering\nWe then identify all liquidity provision events (Mint/Burn) for positions that were in-range at the target block.\nWe build the different positions of the pool incrementally in the following way:\n- Filter LP events by pool and block number (≤ target block)\n- Only include positions where `tick_lower ≤ current_tick \u003c tick_upper`\n- Convert Burn events to negative liquidity amounts for proper aggregation\n\n### 3. Position Ownership Tracking\nTo determine the current owner of each position, we track ownership changes through NFT transfers:\n- Find the latest ownership change for each token_id at or before the target block\n- Exclude transfers to the zero address (burned positions)\n- This gives us the most recent owner of each position\n\n### 4. Liquidity Aggregation and Filtering\nFinally, we aggregate liquidity by position and apply filters:\n- Sum liquidity amounts per position (token_id, tick_lower, tick_upper)\n- Calculate the token0 and token1 holdings by $`x = L\\frac{\\sqrt{P}-\\sqrt{P_b}}{\\sqrt{P}*\\sqrt{P_b}}`$ and $`y = L(\\sqrt{P}-\\sqrt{P_a})`$ where $`x`$ is the `token0` holding of the position and $`y`$ is the `token1` holding of the positions (excluding any accrued fees).\n- Use the latest owner from step 3, falling back to the original LP event owner\n- Filter out positions with zero or negative liquidity\n- Exclude positions owned by the zero address\n- Order results by liquidity amount (descending)\n\n\nThis methodology ensures accurate snapshots of active liquidity provision at any point in time, accounting for the dynamic nature of Uniswap V3 positions.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduneanalytics%2Fsim-idx-example-univ3-lp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fduneanalytics%2Fsim-idx-example-univ3-lp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduneanalytics%2Fsim-idx-example-univ3-lp/lists"}