Skip to content

Commit 4fd8fd3

Browse files
authored
misc changes (#21)
* getTickInfo * rename & UT fixes * getPosition UT refactor * getPositionInfo UT * update package.json version * copilot bot feedback
1 parent 7aa0651 commit 4fd8fd3

23 files changed

+907
-260
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,26 @@ const quote = await uniDevKit.getQuote({
9595
// Returns { amountOut, estimatedGasUsed, timestamp }
9696
```
9797

98-
#### `getPositionDetails`
99-
Fetches position state from the PositionManager and decodes the tick range, liquidity, and pool key. Uses multicall to batch `V4PositionManager.getPoolAndPositionInfo()` and `V4PositionManager.getPositionLiquidity()` calls, and handles data decoding.
98+
#### `getPositionInfo`
99+
Fetches basic position information without creating SDK instances. Returns raw position data from the blockchain including tick range, liquidity, pool key, and current pool state. Uses multicall to efficiently batch contract calls and decodes packed position data.
100100

101-
**Without this SDK:** Call getPoolAndPositionInfo() and getPositionLiquidity() separately, decode packed position data, extract tick bounds and pool key manually.
101+
Use this when you only need position metadata without SDK operations. For SDK instances (Position, Pool objects), use `getPosition()` instead.
102+
103+
**Without this SDK:** Call getPoolAndPositionInfo() and getPositionLiquidity() separately, decode packed position data, extract tick bounds and pool key manually, fetch slot0 and pool liquidity separately.
104+
105+
```ts
106+
const positionInfo = await uniDevKit.getPositionInfo("123");
107+
// Returns { tokenId, tickLower, tickUpper, liquidity, poolKey, currentTick, slot0, poolLiquidity }
108+
```
109+
110+
#### `getPosition`
111+
Fetches complete position data with initialized SDK instances. Returns fully usable Position and Pool objects from the Uniswap V4 SDK, ready for swaps, calculations, and other operations. Validates that the position has liquidity.
112+
113+
**Without this SDK:** Do everything from `getPositionInfo()` plus create Position and Pool instances manually using the SDK constructors.
102114

103115
```ts
104-
const position = await uniDevKit.getPositionDetails("123");
105-
// Returns { tokenId, tickLower, tickUpper, liquidity, poolKey }
116+
const position = await uniDevKit.getPosition("123");
117+
// Returns { position: Position, pool: Pool, currency0, currency1, poolId, tokenId, currentTick }
106118
```
107119

108120
### Swap Operations

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "uniswap-dev-kit",
3-
"version": "1.1.1",
3+
"version": "2.1.1",
44
"description": "A modern TypeScript library for integrating Uniswap into your dapp.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/core/uniDevKitV4.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { buildCollectFeesCallData } from '@/utils/buildCollectFeesCallData'
55
import { buildRemoveLiquidityCallData } from '@/utils/buildRemoveLiquidityCallData'
66
import { buildSwapCallData } from '@/utils/buildSwapCallData'
77
import { getPool } from '@/utils/getPool'
8-
import { getPositionDetails } from '@/utils/getPosition'
8+
import { getPosition } from '@/utils/getPosition'
9+
import { getPositionInfo } from '@/utils/getPositionInfo'
910
import { getQuote } from '@/utils/getQuote'
11+
import { getTickInfo } from '@/utils/getTickInfo'
1012
import { getTokens } from '@/utils/getTokens'
1113
import { preparePermit2BatchData } from '@/utils/preparePermit2BatchData'
1214
import { preparePermit2Data } from '@/utils/preparePermit2Data'
@@ -16,7 +18,7 @@ import type {
1618
BuildAddLiquidityArgs,
1719
BuildAddLiquidityCallDataResult,
1820
} from '@/types/utils/buildAddLiquidityCallData'
19-
import type { GetPositionDetailsResponse } from '@/types/utils/getPosition'
21+
import type { GetPositionInfoResponse, GetPositionResponse } from '@/types/utils/getPosition'
2022
import type { QuoteResponse, SwapExactInSingle } from '@/types/utils/getQuote'
2123
import type { GetTokensArgs } from '@/types/utils/getTokens'
2224
import type {
@@ -28,6 +30,7 @@ import type {
2830
import type { BuildCollectFeesCallDataArgs } from '@/types/utils/buildCollectFeesCallData'
2931
import type { BuildRemoveLiquidityCallDataArgs } from '@/types/utils/buildRemoveLiquidityCallData'
3032
import type { PoolArgs } from '@/types/utils/getPool'
33+
import type { GetTickInfoArgs, TickInfoResponse } from '@/types/utils/getTickInfo'
3134
import type { Currency } from '@uniswap/sdk-core'
3235
import type { Pool } from '@uniswap/v4-sdk'
3336
import { type Address, createPublicClient, http, type PublicClient } from 'viem'
@@ -127,18 +130,57 @@ export class UniDevKitV4 {
127130
}
128131

129132
/**
130-
* Fetches detailed position information from the V4 PositionManager contract.
133+
* Fetches tick information for a given pool key and tick from V4 StateView.
131134
*
132-
* This method uses multicall to efficiently call V4PositionManager.getPoolAndPositionInfo() and
133-
* getPositionLiquidity() in a single transaction. It retrieves the position's tick range, liquidity,
134-
* and associated pool key, then decodes the raw position data to provide structured information.
135+
* This method uses client.readContract() to call V4StateView.getTickInfo() and retrieve
136+
* tick data including liquidity and fee growth information. It first creates Token instances
137+
* from the pool key currencies, computes the PoolId, and then reads the tick info from the
138+
* blockchain.
139+
*
140+
* @param args - Tick query parameters including pool key and tick index
141+
* @returns Promise<TickInfoResponse> - Tick information including liquidity and fee growth data
142+
* @throws Error if tick data cannot be fetched or contract call reverts
143+
*/
144+
public async getTickInfo(args: GetTickInfoArgs): Promise<TickInfoResponse> {
145+
return getTickInfo(args, this.instance)
146+
}
147+
148+
/**
149+
* Retrieves a complete Uniswap V4 position instance with pool and token information.
150+
*
151+
* This method fetches position details and builds a fully initialized Position instance
152+
* using the Uniswap V4 SDK. It includes the pool state, token metadata, position
153+
* liquidity data, and current pool tick, providing a comprehensive view of the position.
154+
*
155+
* @param tokenId - The NFT token ID of the position
156+
* @returns Promise<GetPositionResponse> - Complete position data including position instance, pool, tokens, pool ID, and current tick
157+
* @throws Error if position data cannot be fetched, position doesn't exist, or liquidity is 0
158+
*/
159+
public async getPosition(tokenId: string): Promise<GetPositionResponse> {
160+
return getPosition(tokenId, this.instance)
161+
}
162+
163+
/**
164+
* Retrieves basic position information without SDK instances.
165+
*
166+
* This method fetches raw position data from the blockchain and returns it without creating
167+
* SDK instances. It's more efficient when you only need position metadata (tick range, liquidity,
168+
* pool key) without requiring Position or Pool objects. Also fetches pool state (slot0 and
169+
* liquidity) to avoid redundant calls when building full position instances.
170+
*
171+
* Use this method when:
172+
* - Displaying position information in a UI
173+
* - Checking if a position exists
174+
* - Getting position metadata without SDK operations
175+
*
176+
* Use `getPosition()` instead when you need SDK instances for swaps, calculations, or other operations.
135177
*
136178
* @param tokenId - The NFT token ID of the position
137-
* @returns Promise<GetPositionDetailsResponse> - Position details including tick range, liquidity, and pool key
179+
* @returns Promise<GetPositionInfoResponse> - Basic position information with pool state
138180
* @throws Error if position data cannot be fetched or position doesn't exist
139181
*/
140-
public async getPositionDetails(tokenId: string): Promise<GetPositionDetailsResponse> {
141-
return getPositionDetails(tokenId, this.instance)
182+
public async getPositionInfo(tokenId: string): Promise<GetPositionInfoResponse> {
183+
return getPositionInfo(tokenId, this.instance)
142184
}
143185

144186
/**
@@ -195,7 +237,7 @@ export class UniDevKitV4 {
195237
* Generates V4PositionManager calldata for collecting accumulated fees from positions.
196238
*
197239
* This method uses V4PositionManager.collectCallParameters to create calldata for
198-
* collecting fees earned by a liquidity position. It handles both token0 and token1
240+
* collecting fees earned by a liquidity position. It handles both currency0 and currency1
199241
* fee collection with proper recipient addressing. No blockchain calls are made -
200242
* this is purely a calldata generation method.
201243
*

src/helpers/tokens.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/**
22
* Sorts two tokens in a consistent order (lexicographically by address)
3-
* @param token0 First token address
4-
* @param token1 Second token address
5-
* @returns Tuple of [token0, token1] in sorted order
3+
* @param currency0 First currency/token address
4+
* @param currency1 Second currency/token address
5+
* @returns Tuple of [currency0, currency1] in sorted order
66
*/
77
export function sortTokens(
8-
token0: `0x${string}`,
9-
token1: `0x${string}`,
8+
currency0: `0x${string}`,
9+
currency1: `0x${string}`,
1010
): [`0x${string}`, `0x${string}`] {
11-
return token0.toLowerCase() < token1.toLowerCase() ? [token0, token1] : [token1, token0]
11+
return currency0.toLowerCase() < currency1.toLowerCase() ? [currency0, currency1] : [currency1, currency0]
1212
}

src/test/helpers/testFactories.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export const TEST_ADDRESSES = {
2626
} as const
2727

2828
// Factory functions
29-
export const createTestPool = (token0 = USDC, token1 = WETH) =>
29+
export const createTestPool = (currency0 = USDC, currency1 = WETH) =>
3030
new Pool(
31-
token0,
32-
token1,
31+
currency0,
32+
currency1,
3333
3000, // fee
3434
60, // tickSpacing
3535
TEST_ADDRESSES.hooks,
@@ -49,11 +49,18 @@ export const createTestPosition = (pool = createTestPool()) =>
4949
export const createMockPositionData = (
5050
pool = createTestPool(),
5151
position = createTestPosition(pool),
52-
) => ({
53-
position,
54-
pool,
55-
token0: pool.token0,
56-
token1: pool.token1,
57-
poolId: TEST_ADDRESSES.hooks,
58-
tokenId: '1',
59-
})
52+
) => {
53+
// Extract currencies from pool
54+
const currency0 = pool.currency0
55+
const currency1 = pool.currency1
56+
57+
return {
58+
position,
59+
pool,
60+
currency0,
61+
currency1,
62+
poolId: TEST_ADDRESSES.hooks,
63+
tokenId: '1',
64+
currentTick: 0, // Mock current tick (matching the test pool's tick parameter)
65+
}
66+
}

src/test/utils/buildAddLiquidityCallData.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,10 @@ describe('buildAddLiquidityCallData', () => {
369369
})
370370

371371
it('should call V4PositionManager.addCallParameters with native currency when pool has native token', async () => {
372-
// Create a pool with native token (WETH as native)
372+
// Create a pool with native token
373373
const nativePool = createTestPool()
374-
Object.defineProperty(nativePool.token0, 'isNative', {
374+
const currency0 = nativePool.currency0
375+
Object.defineProperty(currency0, 'isNative', {
375376
value: true,
376377
writable: true,
377378
})
@@ -388,7 +389,7 @@ describe('buildAddLiquidityCallData', () => {
388389
expect(mockAddCallParameters).toHaveBeenCalledTimes(1)
389390
const [, options] = mockAddCallParameters.mock.calls[0]
390391

391-
expect(options.useNative).toBe(nativePool.token0)
392+
expect(options.useNative).toBe(currency0)
392393
})
393394

394395
it('should throw error when neither amount0 nor amount1 is provided', async () => {

src/test/utils/buildRemoveLiquidityCallData.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('buildRemoveLiquidityCallData', () => {
9292
const result = await buildRemoveLiquidityCallData(params, instance)
9393

9494
// Verify getPosition was called with correct tokenId
95-
expect(mockGetPosition).toHaveBeenCalledWith({ tokenId: MOCK_TOKEN_ID }, instance)
95+
expect(mockGetPosition).toHaveBeenCalledWith(MOCK_TOKEN_ID, instance)
9696

9797
// Verify getDefaultDeadline was NOT called since custom deadline was provided
9898
expect(mockGetDefaultDeadline).not.toHaveBeenCalled()

src/test/utils/getPool.test.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ vi.mock('wagmi', () => ({
2424

2525
describe('getPool', () => {
2626
// USDC and WETH on Mainnet
27-
const mockTokens: [Address, Address] = [
27+
const mockCurrencies: [Address, Address] = [
2828
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
2929
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
3030
]
@@ -36,25 +36,25 @@ describe('getPool', () => {
3636
})
3737

3838
it('should throw error if pool does not exist', async () => {
39-
const mockTokenInstances = [
40-
new Token(1, mockTokens[0], 18, 'TOKEN0', 'Token 0'),
41-
new Token(1, mockTokens[1], 18, 'TOKEN1', 'Token 1'),
39+
const mockCurrencyInstances = [
40+
new Token(1, mockCurrencies[0], 18, 'CURRENCY0', 'Currency 0'),
41+
new Token(1, mockCurrencies[1], 18, 'CURRENCY1', 'Currency 1'),
4242
]
4343

4444
const mockPoolData = [
45-
[mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 0, zeroAddress], // poolKeys with 0 tickSpacing
45+
[mockCurrencies[0], mockCurrencies[1], FeeTier.MEDIUM, 0, zeroAddress], // poolKeys with 0 tickSpacing
4646
null, // slot0
4747
null, // liquidity
4848
]
4949

50-
mockGetTokens.mockResolvedValueOnce(mockTokenInstances)
50+
mockGetTokens.mockResolvedValueOnce(mockCurrencyInstances)
5151
vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData)
5252

5353
await expect(
5454
getPool(
5555
{
56-
currencyA: mockTokens[0],
57-
currencyB: mockTokens[1],
56+
currencyA: mockCurrencies[0],
57+
currencyB: mockCurrencies[1],
5858
fee: FeeTier.MEDIUM,
5959
},
6060
mockDeps,
@@ -63,9 +63,9 @@ describe('getPool', () => {
6363
})
6464

6565
it('should return pool when it exists', async () => {
66-
const mockTokenInstances = [
67-
new Token(1, mockTokens[0], 6, 'USDC', 'USD Coin'),
68-
new Token(1, mockTokens[1], 18, 'WETH', 'Wrapped Ether'),
66+
const mockCurrencyInstances = [
67+
new Token(1, mockCurrencies[0], 6, 'USDC', 'USD Coin'),
68+
new Token(1, mockCurrencies[1], 18, 'WETH', 'Wrapped Ether'),
6969
]
7070

7171
// Mock the multicall response with the correct structure
@@ -74,13 +74,13 @@ describe('getPool', () => {
7474

7575
const mockPoolData = [mockSlot0Data, mockLiquidityData]
7676

77-
mockGetTokens.mockResolvedValueOnce(mockTokenInstances)
77+
mockGetTokens.mockResolvedValueOnce(mockCurrencyInstances)
7878
vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData)
7979

8080
const result = await getPool(
8181
{
82-
currencyA: mockTokens[0],
83-
currencyB: mockTokens[1],
82+
currencyA: mockCurrencies[0],
83+
currencyB: mockCurrencies[1],
8484
fee: FeeTier.MEDIUM,
8585
},
8686
mockDeps,
@@ -91,25 +91,25 @@ describe('getPool', () => {
9191
})
9292

9393
it('should throw error if pool creation fails', async () => {
94-
const mockTokenInstances = [
95-
new Token(1, mockTokens[0], 18, 'TOKEN0', 'Token 0'),
96-
new Token(1, mockTokens[1], 18, 'TOKEN1', 'Token 1'),
94+
const mockCurrencyInstances = [
95+
new Token(1, mockCurrencies[0], 18, 'CURRENCY0', 'Currency 0'),
96+
new Token(1, mockCurrencies[1], 18, 'CURRENCY1', 'Currency 1'),
9797
]
9898

9999
const mockPoolData = [
100-
[mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 60, zeroAddress],
100+
[mockCurrencies[0], mockCurrencies[1], FeeTier.MEDIUM, 60, zeroAddress],
101101
['invalid', 0, 0, 0, 0, 0], // invalid sqrtPriceX96
102102
'1000000000000000000',
103103
]
104104

105-
mockGetTokens.mockResolvedValueOnce(mockTokenInstances)
105+
mockGetTokens.mockResolvedValueOnce(mockCurrencyInstances)
106106
vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData)
107107

108108
await expect(
109109
getPool(
110110
{
111-
currencyA: mockTokens[0],
112-
currencyB: mockTokens[1],
111+
currencyA: mockCurrencies[0],
112+
currencyB: mockCurrencies[1],
113113
fee: FeeTier.MEDIUM,
114114
},
115115
mockDeps,

0 commit comments

Comments
 (0)