Skip to content

Commit 8307630

Browse files
committed
feat: add getPool and useGetPool
1 parent be3a6b7 commit 8307630

File tree

6 files changed

+564
-0
lines changed

6 files changed

+564
-0
lines changed

src/hooks/useGetPool.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { UseGetPoolOptions } from "@/types/hooks/useGetPool";
2+
import type { PoolParams, PoolResponse } from "@/types/utils/getPool";
3+
import { getPool } from "@/utils/getPool";
4+
import { useQuery } from "@tanstack/react-query";
5+
6+
/**
7+
* React hook for fetching Uniswap V4 pool data using React Query.
8+
* Handles caching, loading states, and error handling automatically.
9+
*
10+
* @param options - Configuration options for the hook
11+
* @returns Query result containing pool data, loading state, error state, and refetch function
12+
*
13+
* @example
14+
* ```tsx
15+
* const { data, isLoading, error, refetch } = useGetPool({
16+
* params: {
17+
* tokens: [token0, token1],
18+
* chainId: 1,
19+
* fee: FeeTier.MEDIUM,
20+
* hooks: "0x0000000000000000000000000000000000000000"
21+
* },
22+
* queryOptions: {
23+
* enabled: true,
24+
* staleTime: 30000,
25+
* gcTime: 300000,
26+
* retry: 3,
27+
* onSuccess: (data) => console.log('Pool data received:', data)
28+
* }
29+
* });
30+
* ```
31+
*/
32+
function serializeParams(params?: PoolParams) {
33+
if (!params) return undefined;
34+
return {
35+
...params,
36+
tokens: params.tokens.map((t) => t.toLowerCase()),
37+
};
38+
}
39+
40+
export function useGetPool({
41+
params,
42+
queryOptions = {},
43+
}: UseGetPoolOptions = {}) {
44+
if (!params) throw new Error("No params provided");
45+
return useQuery<PoolResponse, Error, PoolResponse, unknown[]>({
46+
queryKey: ["pool", serializeParams(params)],
47+
queryFn: () => getPool(params),
48+
...queryOptions,
49+
});
50+
}

src/test/hooks/useGetPool.test.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { useGetPool } from "@/hooks/useGetPool";
2+
import type { UseGetPoolOptions } from "@/types/hooks/useGetPool";
3+
import { getPool } from "@/utils/getPool";
4+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
import { renderHook, waitFor } from "@testing-library/react";
6+
import { jsx as _jsx } from "react/jsx-runtime";
7+
import type { Mock } from "vitest";
8+
import { beforeEach, describe, expect, it, vi } from "vitest";
9+
10+
// Mock the getPool function
11+
vi.mock("@/utils/getPool", () => ({
12+
getPool: vi.fn(),
13+
}));
14+
15+
// Mock tokens
16+
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as const;
17+
const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" as const;
18+
19+
describe("useGetPool", () => {
20+
let queryClient: QueryClient;
21+
22+
beforeEach(() => {
23+
queryClient = new QueryClient({
24+
defaultOptions: {
25+
queries: {
26+
retry: false,
27+
},
28+
},
29+
});
30+
vi.clearAllMocks();
31+
});
32+
33+
const wrapper = ({ children }: { children: React.ReactNode }) =>
34+
_jsx(QueryClientProvider, { client: queryClient, children });
35+
36+
it("should fetch pool data successfully", async () => {
37+
const mockPool = {
38+
token0: {
39+
address: USDC,
40+
decimals: 6,
41+
name: "USD Coin",
42+
symbol: "USDC",
43+
},
44+
token1: {
45+
address: WETH,
46+
decimals: 18,
47+
name: "Wrapped Ether",
48+
symbol: "WETH",
49+
},
50+
fee: 3000,
51+
tickSpacing: 60,
52+
};
53+
54+
(getPool as Mock).mockReturnValue(mockPool);
55+
56+
const { result } = renderHook(
57+
() =>
58+
useGetPool({
59+
params: {
60+
tokens: [USDC, WETH],
61+
fee: 3000,
62+
chainId: 1,
63+
},
64+
}),
65+
{ wrapper },
66+
);
67+
68+
await waitFor(() => {
69+
expect(result.current.isLoading).toBe(false);
70+
});
71+
72+
expect(result.current.data).toEqual(mockPool);
73+
expect(result.current.error).toBeNull();
74+
expect(result.current.isLoading).toBe(false);
75+
expect(result.current.status).toBe("success");
76+
expect(getPool).toHaveBeenCalledWith({
77+
tokens: [USDC, WETH],
78+
fee: 3000,
79+
chainId: 1,
80+
});
81+
});
82+
83+
it("should handle errors", async () => {
84+
const error = new Error("Failed to fetch pool");
85+
(getPool as Mock).mockImplementation(() => {
86+
throw error;
87+
});
88+
89+
const { result } = renderHook(
90+
() =>
91+
useGetPool({
92+
params: {
93+
tokens: [USDC, WETH],
94+
fee: 3000,
95+
chainId: 1,
96+
},
97+
}),
98+
{ wrapper },
99+
);
100+
101+
await waitFor(() => {
102+
expect(result.current.isLoading).toBe(false);
103+
});
104+
105+
expect(result.current.data).toBeUndefined();
106+
expect(result.current.error).toBe(error);
107+
expect(result.current.isLoading).toBe(false);
108+
expect(result.current.status).toBe("error");
109+
});
110+
111+
it("should throw error if no params provided", () => {
112+
expect(() => {
113+
renderHook(() => useGetPool(undefined as unknown as UseGetPoolOptions), {
114+
wrapper,
115+
});
116+
}).toThrow("No params provided");
117+
});
118+
119+
it("should handle custom query options", async () => {
120+
const mockPool = {
121+
token0: {
122+
address: USDC,
123+
decimals: 6,
124+
name: "USD Coin",
125+
symbol: "USDC",
126+
},
127+
token1: {
128+
address: WETH,
129+
decimals: 18,
130+
name: "Wrapped Ether",
131+
symbol: "WETH",
132+
},
133+
fee: 3000,
134+
tickSpacing: 60,
135+
};
136+
137+
(getPool as Mock).mockReturnValue(mockPool);
138+
139+
const { result } = renderHook(
140+
() =>
141+
useGetPool({
142+
params: {
143+
tokens: [USDC, WETH],
144+
fee: 3000,
145+
chainId: 1,
146+
},
147+
queryOptions: {
148+
enabled: true,
149+
staleTime: 5000,
150+
},
151+
}),
152+
{ wrapper },
153+
);
154+
155+
await waitFor(() => {
156+
expect(result.current.isLoading).toBe(false);
157+
});
158+
159+
expect(result.current.data).toEqual(mockPool);
160+
expect(result.current.error).toBeNull();
161+
expect(result.current.isLoading).toBe(false);
162+
expect(result.current.status).toBe("success");
163+
});
164+
});

src/test/utils/getPool.test.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { FeeTier } from "@/types/utils/getPool";
2+
import { getPool } from "@/utils/getPool";
3+
import { Token } from "@uniswap/sdk-core";
4+
import { type Address, zeroAddress } from "viem";
5+
import { beforeEach, describe, expect, it, vi } from "vitest";
6+
7+
const mockGetInstance = vi.fn();
8+
const mockGetTokenInstances = vi.fn();
9+
const mockUseReadContracts = vi.fn();
10+
11+
vi.mock("@/core/uniDevKitV4Factory", () => ({
12+
getInstance: () => mockGetInstance(),
13+
}));
14+
15+
vi.mock("@/utils/getTokenInstance", () => ({
16+
getTokenInstances: () => mockGetTokenInstances(),
17+
}));
18+
19+
vi.mock("wagmi", () => ({
20+
useReadContracts: () => mockUseReadContracts(),
21+
}));
22+
23+
describe("useV4Pool", () => {
24+
// USDC and WETH on Mainnet
25+
const mockTokens: [Address, Address] = [
26+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
27+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
28+
];
29+
const mockChainId = 1;
30+
31+
beforeEach(() => {
32+
vi.resetAllMocks();
33+
});
34+
35+
it("should throw error if SDK instance not found", async () => {
36+
mockGetInstance.mockReturnValueOnce(undefined);
37+
await expect(
38+
getPool({
39+
tokens: mockTokens,
40+
chainId: mockChainId,
41+
fee: FeeTier.MEDIUM,
42+
}),
43+
).rejects.toThrow("SDK not found");
44+
});
45+
46+
it("should throw error if token instances not found", async () => {
47+
mockGetInstance.mockReturnValueOnce({
48+
getClient: vi.fn(),
49+
getContractAddress: vi.fn(),
50+
});
51+
mockGetTokenInstances.mockResolvedValueOnce(null);
52+
53+
await expect(
54+
getPool({
55+
tokens: mockTokens,
56+
chainId: mockChainId,
57+
fee: FeeTier.MEDIUM,
58+
}),
59+
).rejects.toThrow("Failed to fetch token instances");
60+
});
61+
62+
it("should return pool data when pool exists", async () => {
63+
const mockTokenInstances = [
64+
new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
65+
new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
66+
];
67+
68+
const mockPoolData = [
69+
[mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 60, zeroAddress],
70+
// slot0: [sqrtPriceX96, tick, observationIndex, observationCardinality, observationCardinalityNext, feeProtocol]
71+
["79228162514264337593543950336", 0, 0, 0, 0, 0],
72+
// liquidity
73+
"1000000000000000000",
74+
];
75+
76+
mockGetInstance.mockReturnValueOnce({
77+
getClient: vi.fn(),
78+
getContractAddress: vi.fn(() => "0xMockAddress"),
79+
});
80+
81+
mockGetTokenInstances.mockResolvedValueOnce(mockTokenInstances);
82+
mockUseReadContracts.mockReturnValueOnce({
83+
data: mockPoolData,
84+
isLoading: false,
85+
});
86+
87+
const result = await getPool({
88+
tokens: mockTokens,
89+
chainId: mockChainId,
90+
fee: FeeTier.MEDIUM,
91+
});
92+
93+
expect(result.data).toBeDefined();
94+
expect(result.isLoading).toBe(false);
95+
expect(result.error).toBeNull();
96+
});
97+
98+
it("should return undefined data when pool does not exist", async () => {
99+
const mockTokenInstances = [
100+
new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
101+
new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
102+
];
103+
104+
mockGetInstance.mockReturnValueOnce({
105+
getClient: vi.fn(),
106+
getContractAddress: vi.fn(() => "0xMockAddress"),
107+
});
108+
109+
mockGetTokenInstances.mockResolvedValueOnce(mockTokenInstances);
110+
mockUseReadContracts.mockReturnValueOnce({
111+
data: null,
112+
isLoading: false,
113+
});
114+
115+
const result = await getPool({
116+
tokens: mockTokens,
117+
chainId: mockChainId,
118+
fee: FeeTier.MEDIUM,
119+
});
120+
121+
expect(result.data).toBeUndefined();
122+
expect(result.isLoading).toBe(false);
123+
expect(result.error).toBeDefined();
124+
});
125+
126+
it("should handle pool creation error", async () => {
127+
const mockTokenInstances = [
128+
new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
129+
new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
130+
];
131+
132+
const mockPoolData = [
133+
[mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 60, zeroAddress],
134+
["invalid", 0, 0, 0, 0, 0],
135+
"1000000000000000000",
136+
];
137+
138+
mockGetInstance.mockReturnValueOnce({
139+
getClient: vi.fn(),
140+
getContractAddress: vi.fn(() => "0xMockAddress"),
141+
});
142+
143+
mockGetTokenInstances.mockResolvedValueOnce(mockTokenInstances);
144+
mockUseReadContracts.mockReturnValueOnce({
145+
data: mockPoolData,
146+
isLoading: false,
147+
});
148+
149+
const result = await getPool({
150+
tokens: mockTokens,
151+
chainId: mockChainId,
152+
fee: FeeTier.MEDIUM,
153+
});
154+
155+
expect(result.data).toBeUndefined();
156+
expect(result.isLoading).toBe(false);
157+
expect(result.error).toBeDefined();
158+
});
159+
});

src/types/hooks/useGetPool.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { PoolParams, PoolResponse } from "@/types/utils/getPool";
2+
import type { UseQueryOptions } from "@tanstack/react-query";
3+
4+
/**
5+
* Configuration options for the useGetPool hook.
6+
*/
7+
export type UseGetPoolOptions = {
8+
/** Initial pool parameters */
9+
params?: PoolParams;
10+
/** React Query options */
11+
queryOptions?: Omit<
12+
UseQueryOptions<PoolResponse, Error, PoolResponse, unknown[]>,
13+
"queryKey"
14+
>;
15+
};

0 commit comments

Comments
 (0)