Skip to content

Add Positions tracking based on mint, burn and collect events #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 1 addition & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
# Uniswap V3 Subgraph

### Subgraph Endpoint

Synced at: https://thegraph.com/hosted-service/subgraph/ianlapham/uniswap-v3-subgraph?selected=playground

Pending Changes at same URL

### Running Unit Tests

1. Install [Docker](https://docs.docker.com/get-docker/) if you don't have it already
2. Install postgres: `brew install postgresql`
3. `yarn run build:docker`
4. `yarn run test`

### Adding New Chains

1. Create a new subgraph config in `src/utils/chains.ts`. This will require adding a new `<NETWORK_NAME>_NETWORK_NAME` const for the corresponding network.
2. Add a new entry in `networks.json` for the new chain. The network name should be derived from the CLI Name in The Graph's [supported networks documenation](https://thegraph.com/docs/en/developing/supported-networks/). The factory address can be derived from Uniswap's [deployments documentation](https://docs.uniswap.org/contracts/v3/reference/deployments/ethereum-deployments).
3. To deploy to Alchemy, run the following command:

```
yarn run deploy:alchemy --
<SUBGRAPH_NAME>
--version-label <VERSION_LABEL>
--deploy-key <DEPLOYMENT_KEY>
--network <NETWORK_NAME>
```
# Uniswap V3 Subgraph with Positions indexing
78 changes: 78 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,82 @@ type Token @entity {
tokenDayData: [TokenDayData!]! @derivedFrom(field: "token")
}

# New Position entity to track liquidity positions
type Position @entity {
# Format: <owner address>-<pool address>-<lower tick>-<upper tick>
id: ID!
# Owner of the position
owner: Bytes!
# Pool the position is in
pool: Pool!
# Token0 of the pool
token0: Token!
# Token1 of the pool
token1: Token!
# Lower tick of the position
tickLower: BigInt!
# Upper tick of the position
tickUpper: BigInt!
# Current liquidity of the position
liquidity: BigInt!
# Deposited amount of token0
depositedToken0: BigDecimal!
# Deposited amount of token1
depositedToken1: BigDecimal!
# Withdrawn amount of token0
withdrawnToken0: BigDecimal!
# Withdrawn amount of token1
withdrawnToken1: BigDecimal!
# Collected fees of token0
collectedFeesToken0: BigDecimal!
# Collected fees of token1
collectedFeesToken1: BigDecimal!
# Transaction in which the position was created
transaction: Transaction!
# Timestamp when the position was created
createdAtTimestamp: BigInt!
# Block when the position was created
createdAtBlockNumber: BigInt!
# Timestamp when the position was last updated
updatedAtTimestamp: BigInt!
# Block when the position was last updated
updatedAtBlockNumber: BigInt!
# Closed position
closed: Boolean!
}

# Position snapshot for historical data
type PositionSnapshot @entity {
# Format: <position id>-<block number>
id: ID!
# Owner of the position
owner: Bytes!
# Pool the position is in
pool: Pool!
# Position this is a snapshot of
position: Position!
# Block number of the snapshot
blockNumber: BigInt!
# Timestamp of the snapshot
timestamp: BigInt!
# Current liquidity of the position
liquidity: BigInt!
# Deposited amount of token0
depositedToken0: BigDecimal!
# Deposited amount of token1
depositedToken1: BigDecimal!
# Withdrawn amount of token0
withdrawnToken0: BigDecimal!
# Withdrawn amount of token1
withdrawnToken1: BigDecimal!
# Collected fees of token0
collectedFeesToken0: BigDecimal!
# Collected fees of token1
collectedFeesToken1: BigDecimal!
# Transaction hash of the snapshot
transaction: Transaction!
}

type Pool @entity {
# pool address
id: ID!
Expand Down Expand Up @@ -139,6 +215,8 @@ type Pool @entity {
swaps: [Swap!]! @derivedFrom(field: "pool")
collects: [Collect!]! @derivedFrom(field: "pool")
ticks: [Tick!]! @derivedFrom(field: "pool")
# positions in this pool
positions: [Position!]! @derivedFrom(field: "pool")
}

type Tick @entity {
Expand Down
16 changes: 16 additions & 0 deletions src/mappings/pool/burn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
updateTokenHourData,
updateUniswapDayData,
} from '../../utils/intervalUpdates'
import { getOrCreatePosition, updatePositionWithBurn } from '../../utils/position'
import { createPositionSnapshot } from '../../utils/positionSnapshot'

export function handleBurn(event: BurnEvent): void {
handleBurnHelper(event)
Expand Down Expand Up @@ -79,6 +81,20 @@ export function handleBurnHelper(event: BurnEvent, subgraphConfig: SubgraphConfi
burn.tickUpper = BigInt.fromI32(event.params.tickUpper)
burn.logIndex = event.logIndex

// Update position
const position = getOrCreatePosition(
event.transaction.from,
pool,
BigInt.fromI32(event.params.tickLower),
BigInt.fromI32(event.params.tickUpper),
event,
)
updatePositionWithBurn(position, event.params.amount, amount0, amount1)
position.save()

// Create position snapshot
createPositionSnapshot(position, event)

// tick entities
const lowerTickId = poolAddress + '#' + BigInt.fromI32(event.params.tickLower).toString()
const upperTickId = poolAddress + '#' + BigInt.fromI32(event.params.tickUpper).toString()
Expand Down
30 changes: 24 additions & 6 deletions src/mappings/pool/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
updateTokenHourData,
updateUniswapDayData,
} from '../../utils/intervalUpdates'
import { getOrCreatePosition, updatePositionWithCollect } from '../../utils/position'
import { createPositionSnapshot } from '../../utils/positionSnapshot'
import { getTrackedAmountUSD } from '../../utils/pricing'

export function handleCollect(event: CollectEvent): void {
Expand All @@ -23,11 +25,8 @@ export function handleCollectHelper(event: CollectEvent, subgraphConfig: Subgrap
const whitelistTokens = subgraphConfig.whitelistTokens

const bundle = Bundle.load('1')!
const pool = Pool.load(event.address.toHexString())
if (pool == null) {
return
}
const transaction = loadTransaction(event)
const poolAddress = event.address.toHexString()
const pool = Pool.load(poolAddress)!
const factory = Factory.load(factoryAddress)!

const token0 = Token.load(pool.token0)
Expand All @@ -36,6 +35,8 @@ export function handleCollectHelper(event: CollectEvent, subgraphConfig: Subgrap
return
}

const transaction = loadTransaction(event)

// Get formatted amounts collected.
const collectedAmountToken0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
const collectedAmountToken1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
Expand Down Expand Up @@ -80,9 +81,26 @@ export function handleCollectHelper(event: CollectEvent, subgraphConfig: Subgrap
factory.totalValueLockedETH = factory.totalValueLockedETH.plus(pool.totalValueLockedETH)
factory.totalValueLockedUSD = factory.totalValueLockedETH.times(bundle.ethPriceUSD)

// Update position
if (event.params.owner) {
const position = getOrCreatePosition(
event.params.owner,
pool,
BigInt.fromI32(event.params.tickLower),
BigInt.fromI32(event.params.tickUpper),
event,
)
updatePositionWithCollect(position, collectedAmountToken0, collectedAmountToken1)
position.save()

// Create position snapshot
createPositionSnapshot(position, event)
}

// collect entity
const collect = new Collect(transaction.id + '-' + event.logIndex.toString())
collect.transaction = transaction.id
collect.timestamp = event.block.timestamp
collect.timestamp = transaction.timestamp
collect.pool = pool.id
collect.owner = event.params.owner
collect.amount0 = collectedAmountToken0
Expand Down
20 changes: 18 additions & 2 deletions src/mappings/pool/mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
updateTokenHourData,
updateUniswapDayData,
} from '../../utils/intervalUpdates'
import { getOrCreatePosition, updatePositionWithMint } from '../../utils/position'
import { createPositionSnapshot } from '../../utils/positionSnapshot'
import { createTick } from '../../utils/tick'

export function handleMint(event: MintEvent): void {
Expand Down Expand Up @@ -99,8 +101,8 @@ export function handleMintHelper(event: MintEvent, subgraphConfig: SubgraphConfi
const lowerTickIdx = event.params.tickLower
const upperTickIdx = event.params.tickUpper

const lowerTickId = poolAddress + '#' + BigInt.fromI32(event.params.tickLower).toString()
const upperTickId = poolAddress + '#' + BigInt.fromI32(event.params.tickUpper).toString()
const lowerTickId = poolAddress + '#' + event.params.tickLower.toString()
const upperTickId = poolAddress + '#' + event.params.tickUpper.toString()

let lowerTick = Tick.load(lowerTickId)
let upperTick = Tick.load(upperTickId)
Expand All @@ -119,6 +121,20 @@ export function handleMintHelper(event: MintEvent, subgraphConfig: SubgraphConfi
upperTick.liquidityGross = upperTick.liquidityGross.plus(amount)
upperTick.liquidityNet = upperTick.liquidityNet.minus(amount)

// Update position
const position = getOrCreatePosition(
event.transaction.from,
pool,
BigInt.fromI32(event.params.tickLower),
BigInt.fromI32(event.params.tickUpper),
event,
)
updatePositionWithMint(position, event.params.amount, amount0, amount1)
position.save()

// Create position snapshot
createPositionSnapshot(position, event)

lowerTick.save()
upperTick.save()

Expand Down
4 changes: 2 additions & 2 deletions src/mappings/pool/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function handleSwapHelper(event: SwapEvent, subgraphConfig: SubgraphConfi

// Update the pool with the new active liquidity, price, and tick.
pool.liquidity = event.params.liquidity
pool.tick = BigInt.fromI32(event.params.tick as i32)
pool.tick = BigInt.fromI32(event.params.tick)
pool.sqrtPrice = event.params.sqrtPriceX96
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.plus(amount0)
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.plus(amount1)
Expand Down Expand Up @@ -171,7 +171,7 @@ export function handleSwapHelper(event: SwapEvent, subgraphConfig: SubgraphConfi
swap.amount0 = amount0
swap.amount1 = amount1
swap.amountUSD = amountTotalUSDTracked
swap.tick = BigInt.fromI32(event.params.tick as i32)
swap.tick = BigInt.fromI32(event.params.tick)
swap.sqrtPriceX96 = event.params.sqrtPriceX96
swap.logIndex = event.logIndex

Expand Down
Loading