-
Notifications
You must be signed in to change notification settings - Fork 562
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
StateView & Flash Accounting guides added (#848)
Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> Co-authored-by: ant <0xdevant@gmail.com>
- Loading branch information
1 parent
d5ce3fa
commit 70c7e8a
Showing
2 changed files
with
544 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
--- | ||
title: StateView | ||
--- | ||
|
||
# Introduction | ||
|
||
When building on **Uniswap v4**, you will often need to read pool state for both onchain and offchain use cases. Onchain contracts can directly invoke the [**StateLibrary**](https://github.com/Uniswap/v4-core/blob/main/src/libraries/StateLibrary.sol) to execute these reads during transactions, but offchain systems—such as frontends or analytics services—require a deployed contract with view functions. This is where [**StateView**](https://github.com/Uniswap/v4-periphery/blob/main/src/lens/StateView.sol) comes in. | ||
|
||
> *In short: Use StateLibrary within onchain contracts and use StateView with an RPC for frontends and analytics.* | ||
> | ||
By providing a dedicated interface for offchain reads, **StateView** helps: | ||
|
||
- Retrieve pool state without paying gas | ||
- Simplify integration for frontends, dashboards, and analytics | ||
- Ensure a clean separation between onchain logic and offchain queries | ||
|
||
## Comparing onchain and offhain Access | ||
|
||
If you’re familiar with [Reading Pool State](/contracts/v4/guides/read-pool-state), you already know that Uniswap v4 uses **extsload** for efficient data access. For onchain usage, we rely on **StateLibrary** within contracts. However, offchain clients cannot rely on an onchain library for state reads. | ||
|
||
Instead, **StateView** provides these same calls in a single contract designed explicitly for offchain consumption. | ||
|
||
> *Because StateLibrary operates via onchain function calls, it’s not directly accessible to offchain clients. Hence, StateView provides a simple, gas-free interface designed for frontends and analytics.* | ||
> | ||
For instance, an onchain contract might use the `StateLibrary` as follows: | ||
|
||
```solidity | ||
// Onchain contract using StateLibrary | ||
contract MyProtocol { | ||
using StateLibrary for IPoolManager; | ||
function checkPoolPrice(PoolId poolId) external returns (uint160) { | ||
(uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolId); | ||
// ... use the price in contract logic ... | ||
return sqrtPriceX96; | ||
} | ||
} | ||
``` | ||
|
||
By contrast, an offchain frontend or analytics service should interact with `StateView`: | ||
|
||
```tsx | ||
// Frontend or analytics client using StateView | ||
const stateView = getContract({ | ||
address: STATE_VIEW_ADDRESS, | ||
abi: stateViewABI | ||
}); | ||
|
||
const { sqrtPriceX96 } = await stateView.read.getSlot0([poolId]); | ||
// ... use the price in your application ... | ||
``` | ||
|
||
This separation ensures that each context (onchain vs. offchain) uses the most efficient data reading pattern. | ||
|
||
# Usage With Frontend Clients | ||
|
||
Frontend applications frequently display real-time information about pools, positions, and other market data—without incurring transaction costs. **StateView** addresses these requirements by exposing read-only functions tailored for offchain integrations. | ||
|
||
## Setting Up With Viem | ||
|
||
We’ll use [**viem**](https://viem.sh/), a TypeScript library for Ethereum, to demonstrate how to connect to **StateView**. | ||
|
||
```tsx | ||
import { createPublicClient, http } from 'viem'; | ||
import { mainnet } from 'viem/chains'; | ||
|
||
// Initialize the client | ||
const client = createPublicClient({ | ||
chain: mainnet, | ||
transport: http() | ||
}); | ||
|
||
// Set up StateView contract instance | ||
const stateView = getContract({ | ||
address: STATE_VIEW_ADDRESS, | ||
abi: stateViewABI, | ||
client | ||
}) | ||
``` | ||
|
||
> **Note:** _The stateView object comes from our getContract call above. Make sure you’ve imported stateViewABI correctly before attempting to read from the contract._ | ||
> | ||
With this setup, you can now: | ||
|
||
- **Connect to an Ethereum network** | ||
- **Call StateView’s read functions** | ||
- **Retrieve pool information offchain at no gas cost** | ||
|
||
### Handling Errors and Invalid Pool IDs | ||
|
||
When calling `stateView.read.<function>([poolId])`, be mindful that: | ||
|
||
- If you pass an invalid `poolId` (typically a [`bytes32`](https://github.com/Uniswap/v4-core/blob/main/src/types/PoolId.sol#L6) in Uniswap v4), the call may revert or return unexpected data. | ||
- Consider adding try-catch (or equivalent error handling in your framework) to gracefully handle failures if the pool does not exist or if the call fails onchain. | ||
|
||
# Reading Pool Data | ||
|
||
Here are common examples of how to retrieve pool data using **StateView**. | ||
|
||
## Getting Pool State | ||
|
||
A pool’s core state, such as its current price or fees, is often necessary for frontends. Use `getSlot0`: | ||
|
||
```tsx | ||
// Example: Reading pool price and fees | ||
const getPoolState = async (poolId: string) => { | ||
// getSlot0 returns: | ||
// - Current price (sqrtPriceX96) in Q64.96 fixed-point format | ||
// - Active tick | ||
// - Protocol and LP fee settings | ||
const { | ||
sqrtPriceX96, | ||
tick, | ||
protocolFee, | ||
lpFee | ||
} = await stateView.read.getSlot0([poolId]); | ||
|
||
return { | ||
price: calculatePrice(sqrtPriceX96), // implement your math logic for Q64.96 | ||
tick, | ||
protocolFee, | ||
lpFee | ||
}; | ||
}; | ||
``` | ||
|
||
**What it Returns:** | ||
|
||
- **`sqrtPriceX96`**: The current pool price in Q64.96 fixed-point format. | ||
- **`tick`**: The current tick in which the pool is operating. | ||
- **`protocolFee`** and **`lpFee`**: Fee parameters for protocol and LP fee tiers. | ||
|
||
## Getting Pool Liquidity | ||
|
||
To understand how much liquidity a pool holds: | ||
|
||
```tsx | ||
// Example: Reading the total active liquidity of a pool | ||
const getPoolLiquidity = async (poolId: string) => { | ||
// getLiquidity returns the total liquidity currently active in the pool | ||
const liquidity = await stateView.read.getLiquidity([poolId]); | ||
return liquidity; | ||
}; | ||
``` | ||
|
||
**Why It Matters:** | ||
|
||
- Helps gauge the depth of the pool | ||
- Influences price impact calculations in trading | ||
- Provides context for the pool’s capacity to absorb trades | ||
|
||
# Core Functions and Return Types | ||
|
||
While **StateView** exposes many functions, here are several essential calls for most offchain applications. Each function typically takes a `poolId` (of type `bytes32`) as the key input, identifying which pool to query. | ||
|
||
1. **[`getSlot0(poolId)`](/contracts/v4/reference/periphery/lens/StateView#getslot0)** | ||
- Returns `(uint160 sqrtPriceX96, int24 tick, uint8 protocolFee, uint8 lpFee)`. | ||
- Essential for displaying real-time price data and fees. | ||
2. **[`getLiquidity(poolId)`](/contracts/v4/reference/periphery/lens/StateView#getliquidity)** | ||
- Returns `uint128 liquidity` (the total active pool liquidity). | ||
- Used to assess trading depth and volatility. | ||
3. **[`getPositionInfo(positionId)`](/contracts/v4/reference/periphery/lens/StateView#getpositioninfo)** | ||
- Returns `(uint128 liquidity, uint256 feeGrowthInside0Last, uint256 feeGrowthInside1Last)`. | ||
- Critical for tracking user positions, especially to calculate earned fees over time. | ||
4. **[`getFeeGrowthGlobals(poolId)`](/contracts/v4/reference/periphery/lens/StateView#getfeegrowthglobals)** | ||
- Returns `(uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1)`. | ||
- Useful for analytics around total fee accumulation in the pool. | ||
|
||
### Note on `poolId` and `positionId` | ||
|
||
- In **Uniswap v4**, a `poolId` is typically a `bytes32` that is derived by calling | ||
`keccak256(abi.encode(poolKey))` where poolKey contains: | ||
- currency0: The lower currency address of the pool | ||
- currency1: The higher currency address of the pool | ||
- fee: The pool LP fee (uint24) | ||
- tickSpacing: The tick spacing value (int24) | ||
- hooks: The hooks contract address | ||
- A `positionId` may also be a `bytes32` or other unique identifier that references a specific position. | ||
|
||
# Security and Gas Considerations | ||
|
||
- **Offchain Reads**: Calls to `StateView` are purely read-only, so they cost no gas. This makes them ideal for frequently refreshing UI/analytics data. | ||
- **Onchain vs. Offchain**: Remember that if you need to integrate pool data into a live transaction, you must use `StateLibrary` within your smart contract. | ||
- **Edge Cases**: Always verify the returned data before using it in your application. Network or contract errors could lead to unexpected values. | ||
|
||
# Conclusion | ||
|
||
**StateView** is a powerful and efficient way to read Uniswap v4 pool data offchain. By separating onchain logic (using `StateLibrary`) and offchain reads (using `StateView`), Uniswap ensures the best developer experience for both contexts. | ||
|
||
To recap: | ||
|
||
1. **Setup**: Use libraries like `viem` to connect to the Ethereum network. | ||
2. **Read**: Call `getSlot0`, `getLiquidity`, `getPositionInfo`, and other methods for crucial state data. | ||
3. **Handle Errors**: Implement basic checks for invalid `poolId` or connection failures. |
Oops, something went wrong.