diff --git a/README.md b/README.md
index 3acf23c..3e7e6ef 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,14 @@
> Modern TypeScript SDK for integrating Uniswap V4 into your dapp.
> **Early version:** API may change rapidly.
----
+## Features
+
+- π Full TypeScript support
+- π Multi-chain support out of the box
+- π¦ Zero dependencies (except peer deps)
+- π Comprehensive error handling
+- π§ͺ Fully tested
+- π Well documented
## Install
@@ -22,10 +29,10 @@ npm install uniswap-dev-kit
### 1. Configure and create SDK instances
```ts
-import { createInstance } from "uniswap-dev-kit";
+import { UniDevKitV4 } from "uniswap-dev-kit";
// Create instance for Ethereum mainnet
-createInstance({
+const ethInstance = new UniDevKitV4({
chainId: 1,
rpcUrl: "https://eth.llamarpc.com",
contracts: {
@@ -40,7 +47,7 @@ createInstance({
});
// Create instance for another chain (e.g., Base)
-createInstance({
+const baseInstance = new UniDevKitV4({
chainId: 8453,
rpcUrl: "https://mainnet.base.org",
contracts: {
@@ -49,96 +56,104 @@ createInstance({
});
```
-The SDK automatically manages multiple instances based on chainId. When using hooks or utilities, just specify the chainId to use the corresponding instance:
+### 2. Get a quote
```ts
-// Will use Ethereum mainnet instance
-const ethPool = await getPool({ tokens: [...] }, 1);
-
-// Will use Base instance
-const basePool = await getPool({ tokens: [...] }, 8453);
+import { parseEther } from "viem";
-// If you only have one instance, chainId is optional
-const singleChainPool = await getPool({ tokens: [...] });
+const quote = await ethInstance.getQuote({
+ tokens: [
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
+ ],
+ feeTier: 3000,
+ amountIn: parseEther("1"),
+ zeroForOne: true
+});
+console.log(quote.amountOut);
```
-### 2. Get a quote (vanilla JS/TS)
+### 3. Get a pool
```ts
-import { getQuote } from "uniswap-dev-kit";
-import { parseEther } from "viem";
+const pool = await ethInstance.getPool({
+ tokens: [
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
+ ],
+ feeTier: 3000
+});
+console.log(pool.liquidity.toString());
+```
-const quote = await getQuote(
- {
- tokens: [
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
- ],
- feeTier: 3000,
- tickSpacing: 60,
- amountIn: parseEther("1"),
- },
- 1,
- {
- enabled: true,
- staleTime: 30000,
- gcTime: 300000,
- retry: 3,
- onSuccess: (data) => console.log("Quote received:", data),
- },
-);
-console.log(quote.amountOut);
+### 4. Get a position
+
+```ts
+const position = await ethInstance.getPosition({
+ tokenId: "123"
+});
+console.log({
+ token0: position.token0.symbol,
+ token1: position.token1.symbol,
+ liquidity: position.position.liquidity.toString()
+});
```
-### 3. Use in React (with hooks)
+## Advanced Usage
-#### Get a quote
+### Error Handling
-```tsx
-import { useGetQuote } from "uniswap-dev-kit";
-import { parseEther } from "viem";
+All SDK functions include comprehensive error handling:
-function QuoteComponent() {
- const { data, isLoading, error } = useGetQuote({
- params: {
- tokens: [
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
- ],
- feeTier: 3000,
- tickSpacing: 60,
- amountIn: parseEther("1"),
- zeroForOne: true
- },
- chainId: 1
+```ts
+try {
+ const quote = await ethInstance.getQuote({
+ tokens: [token0, token1],
+ feeTier: 3000,
+ amountIn: parseEther("1"),
+ zeroForOne: true
});
-
- if (isLoading) return Loading...;
- if (error) return Error: {error.message};
- return Quote: {data?.amountOut?.toString()};
+} catch (error) {
+ // Handle specific error types
+ if (error.message.includes("insufficient liquidity")) {
+ // Handle liquidity error
+ } else if (error.message.includes("invalid pool")) {
+ // Handle pool error
+ }
}
```
-#### Get a pool
+### Using with React
+
+You can use the SDK with React Query for data fetching:
```tsx
-import { useGetPool } from "uniswap-dev-kit";
+import { useQuery } from '@tanstack/react-query';
+import { UniDevKitV4 } from 'uniswap-dev-kit';
-function PoolComponent() {
- const { data, isLoading, error } = useGetPool({
- params: {
+// Create instance once
+const sdk = new UniDevKitV4({
+ chainId: 1,
+ rpcUrl: "https://eth.llamarpc.com",
+ contracts: {
+ // ... contract addresses
+ }
+});
+
+// Simple hook for quotes
+function useQuote() {
+ return useQuery({
+ queryKey: ['quote'],
+ queryFn: () => sdk.getQuote({
tokens: [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
],
- fee: 3000,
- },
- chainId: 1
+ feeTier: 3000,
+ amountIn: parseEther("1"),
+ zeroForOne: true
+ })
});
-
- if (isLoading) return Loading...;
- if (error) return Error: {error.message};
- return Pool: {JSON.stringify(data)};
}
```
@@ -173,10 +188,8 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
## FAQ
-- **Which React versions are supported?**
- React 18+ (see peerDependencies)
- **Does it work in Node and browser?**
- Yes, but hooks are React-only.
+ Yes, works in both environments.
- **Can I use my own ABIs?**
Yes, but Uniswap V4 ABIs are included.
diff --git a/package.json b/package.json
index b4290ec..84349e9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "uniswap-dev-kit",
- "version": "1.0.4",
+ "version": "1.0.7",
"description": "A modern TypeScript library for integrating Uniswap into your dapp.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/src/core/uniDevKitV4.ts b/src/core/uniDevKitV4.ts
index a590047..daaf8f4 100644
--- a/src/core/uniDevKitV4.ts
+++ b/src/core/uniDevKitV4.ts
@@ -1,5 +1,20 @@
import { getChainById } from "@/constants/chains";
import type { UniDevKitV4Config, UniDevKitV4Instance } from "@/types/core";
+import type { PoolParams } from "@/types/utils/getPool";
+import type { GetPoolKeyFromPoolIdParams } from "@/types/utils/getPoolKeyFromPoolId";
+import type {
+ GetPositionParams,
+ GetPositionResponse,
+} from "@/types/utils/getPosition";
+import type { QuoteParams, QuoteResponse } from "@/types/utils/getQuote";
+import type { GetTokensParams } from "@/types/utils/getTokens";
+import { getPool } from "@/utils/getPool";
+import { getPoolKeyFromPoolId } from "@/utils/getPoolKeyFromPoolId";
+import { getPosition } from "@/utils/getPosition";
+import { getQuote } from "@/utils/getQuote";
+import { getTokens } from "@/utils/getTokens";
+import type { Token } from "@uniswap/sdk-core";
+import type { Pool, PoolKey } from "@uniswap/v4-sdk";
import {
http,
type Address,
@@ -21,40 +36,19 @@ export class UniDevKitV4 {
* @throws Will throw an error if the configuration is invalid.
*/
constructor(config: UniDevKitV4Config) {
- this.instance = this.createInstance(config);
- }
+ const chain = getChainById(config.chainId);
+ const client = createPublicClient({
+ chain,
+ transport: http(config.rpcUrl || chain.rpcUrls.default.http[0]),
+ });
- /**
- * Creates a new internal instance for the SDK.
- * This method is used internally to reset the instance when the configuration changes.
- * @param config - The complete configuration for the SDK.
- * @returns A new instance of UniDevKitV4.
- */
- private createInstance(config: UniDevKitV4Config): UniDevKitV4Instance {
- const client = this.createClient(config);
- return {
+ this.instance = {
client: client as PublicClient,
- chainId: config.chainId,
+ chain,
contracts: config.contracts,
};
}
- /**
- * Creates a new PublicClient for the specified chain ID and RPC URL.
- * @param config - The complete configuration for the SDK.
- * @returns A new PublicClient instance.
- */
- private createClient(config: UniDevKitV4Config) {
- const { chainId, rpcUrl } = config;
-
- const chain = getChainById(chainId);
-
- return createPublicClient({
- chain,
- transport: http(rpcUrl || chain.rpcUrls.default.http[0]),
- });
- }
-
/**
* Returns the current PublicClient instance.
* @returns The current PublicClient.
@@ -68,7 +62,7 @@ export class UniDevKitV4 {
* @returns The chain ID currently configured.
*/
getChainId(): number {
- return this.instance.chainId;
+ return this.instance.chain.id;
}
/**
@@ -94,12 +88,48 @@ export class UniDevKitV4 {
}
/**
- * Updates the SDK configuration with a complete new set of parameters.
- * This method will reset the client and instance to reflect the new configuration.
- * @param config - The complete configuration for the SDK.
- * @throws Will throw an error if the configuration is invalid.
+ * Retrieves a Uniswap V4 pool instance for a given token pair.
+ * @param params Pool parameters including tokens, fee tier, tick spacing, and hooks configuration
+ * @returns Promise resolving to pool data
+ * @throws Error if pool data cannot be fetched
*/
- updateConfig(config: UniDevKitV4Config): void {
- this.instance = this.createInstance(config);
+ async getPool(params: PoolParams): Promise {
+ return getPool(params, this.instance);
+ }
+
+ /**
+ * Retrieves token information for a given array of token addresses.
+ * @param params Parameters including token addresses
+ * @returns Promise resolving to Token instances for each token address.
+ * @throws Error if token data cannot be fetched
+ */
+ async getTokens(params: GetTokensParams): Promise {
+ return getTokens(params, this.instance);
+ }
+
+ /**
+ * Retrieves a Uniswap V4 position information for a given token ID.
+ * @param params Position parameters including token ID
+ * @returns Promise resolving to position data including pool, token0, token1, poolId, and tokenId
+ * @throws Error if SDK instance is not found or if position data is invalid
+ */
+ async getPosition(params: GetPositionParams): Promise {
+ return getPosition(params, this.instance);
+ }
+
+ /**
+ * Retrieves a Uniswap V4 quote for a given token pair and amount in.
+ * @param params Quote parameters including token pair and amount in
+ * @returns Promise resolving to quote data including amount out, estimated gas used, and timestamp
+ * @throws Error if SDK instance is not found or if quote data is invalid
+ */
+ async getQuote(params: QuoteParams): Promise {
+ return getQuote(params, this.instance);
+ }
+
+ async getPoolKeyFromPoolId(
+ params: GetPoolKeyFromPoolIdParams,
+ ): Promise {
+ return getPoolKeyFromPoolId(params, this.instance);
}
}
diff --git a/src/core/uniDevKitV4Factory.ts b/src/core/uniDevKitV4Factory.ts
deleted file mode 100644
index 73e5330..0000000
--- a/src/core/uniDevKitV4Factory.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { UniDevKitV4 } from "@/core/uniDevKitV4";
-import type { UniDevKitV4Config } from "@/types";
-
-const instances = new Map();
-
-/**
- * Singleton factory for managing multiple UniDevKitV4 instances.
- * This approach ensures that each chain has a single instance,
- * reducing the risk of duplicate clients and configuration mismatches.
- */
-export function createInstance(config: UniDevKitV4Config): UniDevKitV4 {
- const { chainId } = config;
-
- // Return existing instance if it exists
- if (instances.has(chainId)) {
- console.warn(
- `Instance for chain ID ${chainId} already exists. Reusing existing instance.`,
- );
- const instance = instances.get(chainId);
- if (!instance)
- throw new Error("Unexpected: Instance not found after has check");
- return instance;
- }
-
- // Create a new instance if it doesn't exist
- const instance = new UniDevKitV4(config);
- instances.set(chainId, instance);
- return instance;
-}
-
-/**
- * Retrieves an existing UniDevKitV4 instance.
- * If there is only one instance, it returns it without requiring a chain ID.
- * If there are multiple instances, it requires a chain ID.
- * @param chainId - (Optional) The chain ID to retrieve.
- * @returns The existing UniDevKitV4 instance.
- * @throws Will throw an error if the instance does not exist.
- */
-export function getInstance(chainId?: number): UniDevKitV4 {
- const instanceCount = instances.size;
-
- // Return the single instance if there's only one
- if (instanceCount === 1 && !chainId) {
- return Array.from(instances.values())[0];
- }
-
- // Require chainId if there are multiple instances
- if (instanceCount > 1 && !chainId) {
- throw new Error(
- `Multiple instances found. Please specify a chain ID. Available chains: ${listChainIds().join(", ")}`,
- );
- }
-
- if (!chainId) throw new Error("Chain ID is required");
- // Return the instance for the specified chain ID
- const instance = instances.get(chainId);
- if (!instance) {
- throw new Error(
- `No instance found for chain ID ${chainId}. Make sure to call createInstance() first.`,
- );
- }
-
- return instance;
-}
-
-/**
- * Clears all registered instances.
- */
-export function resetInstances(): void {
- instances.clear();
-}
-
-/**
- * Lists all registered chain IDs.
- * @returns An array of all registered chain IDs.
- */
-export function listChainIds(): number[] {
- return Array.from(instances.keys());
-}
-
-/**
- * Removes a specific instance by chain ID.
- * @param chainId - The chain ID to remove.
- */
-export function removeInstance(chainId: number): void {
- instances.delete(chainId);
-}
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
deleted file mode 100644
index 3abc01c..0000000
--- a/src/hooks/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { useGetPool } from "@/hooks/useGetPool";
-export { useGetPoolKeyFromPoolId } from "@/hooks/useGetPoolKeyFromPoolId";
-export { useGetQuote } from "@/hooks/useGetQuote";
diff --git a/src/hooks/useGetPool.ts b/src/hooks/useGetPool.ts
deleted file mode 100644
index ee60619..0000000
--- a/src/hooks/useGetPool.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { UseGetPoolOptions } from "@/types/hooks/useGetPool";
-import { getPool } from "@/utils/getPool";
-import { useQuery } from "@tanstack/react-query";
-import type { Pool } from "@uniswap/v4-sdk";
-
-/**
- * React hook for fetching Uniswap V4 pool data using React Query.
- * Handles caching, loading states, and error handling automatically.
- *
- * @param options - Configuration options for the hook
- * @returns Query result containing pool data, loading state, error state, and refetch function
- *
- * @example
- * ```tsx
- * const { data, isLoading, error, refetch } = useGetPool({
- * params: {
- * tokens: [token0, token1],
- * fee: FeeTier.MEDIUM,
- * hooks: "0x0000000000000000000000000000000000000000"
- * },
- * chainId: 1,
- * queryOptions: {
- * enabled: true,
- * staleTime: 30000,
- * gcTime: 300000,
- * retry: 3,
- * onSuccess: (data) => console.log('Pool data received:', data)
- * }
- * });
- * ```
- */
-
-export function useGetPool({
- params,
- chainId,
- queryOptions = {},
-}: UseGetPoolOptions) {
- return useQuery({
- queryKey: [
- "pool",
- params.fee,
- params.tokens,
- params.hooks,
- params.tickSpacing,
- chainId,
- ],
- queryFn: () => getPool(params, chainId),
- ...queryOptions,
- });
-}
diff --git a/src/hooks/useGetPoolKeyFromPoolId.ts b/src/hooks/useGetPoolKeyFromPoolId.ts
deleted file mode 100644
index be87c67..0000000
--- a/src/hooks/useGetPoolKeyFromPoolId.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { UseGetPoolKeyFromPoolIdOptions } from "@/types/hooks/useGetPoolKeyFromPoolId";
-import { getPoolKeyFromPoolId } from "@/utils/getPoolKeyFromPoolId";
-import { useQuery } from "@tanstack/react-query";
-
-/**
- * React hook for fetching Uniswap V4 pool key information using React Query.
- * Handles caching, loading states, and error handling automatically.
- *
- * @param options - Configuration options for the hook
- * @returns Query result containing pool key data, loading state, error state, and refetch function
- *
- * @example
- * ```tsx
- * const { data, isLoading, error, refetch } = useGetPoolKeyFromPoolId({
- * poolId: "0x1234...",
- * chainId: 1,
- * queryOptions: {
- * enabled: true,
- * staleTime: 30000,
- * gcTime: 300000,
- * retry: 3,
- * onSuccess: (data) => console.log('Pool key data received:', data)
- * }
- * });
- * ```
- */
-export function useGetPoolKeyFromPoolId({
- poolId,
- chainId,
- queryOptions = {},
-}: UseGetPoolKeyFromPoolIdOptions) {
- return useQuery({
- queryKey: ["poolKey", poolId, chainId],
- queryFn: () => getPoolKeyFromPoolId({ poolId, chainId }),
- ...queryOptions,
- });
-}
diff --git a/src/hooks/useGetPosition.ts b/src/hooks/useGetPosition.ts
deleted file mode 100644
index 4f9a0b1..0000000
--- a/src/hooks/useGetPosition.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import type {
- PositionResult,
- UseGetPositionOptions,
-} from "@/types/hooks/useGetPosition";
-import { getPosition } from "@/utils/getPosition";
-import { useQuery } from "@tanstack/react-query";
-
-/**
- * React hook for fetching Uniswap V4 position data using React Query.
- * Handles caching, loading states, and error handling automatically.
- *
- * @param options - Configuration options for the hook
- * @returns Query result containing position data, loading state, error state, and refetch function
- *
- * @example
- * ```tsx
- * const { data, isLoading, error, refetch } = useGetPosition({
- * tokenId: 123,
- * chainId: 1,
- * queryOptions: {
- * enabled: true,
- * staleTime: 30000,
- * gcTime: 300000,
- * retry: 3,
- * onSuccess: (data) => console.log('Position data received:', data)
- * }
- * });
- * ```
- */
-export function useGetPosition({
- tokenId,
- chainId,
- queryOptions = {},
-}: UseGetPositionOptions = {}) {
- if (!tokenId) throw new Error("No tokenId provided");
-
- return useQuery<
- PositionResult | undefined,
- Error,
- PositionResult | undefined,
- unknown[]
- >({
- queryKey: ["position", tokenId, chainId],
- queryFn: () => getPosition({ tokenId }, chainId),
- ...queryOptions,
- });
-}
diff --git a/src/hooks/useGetQuote.ts b/src/hooks/useGetQuote.ts
deleted file mode 100644
index 315e410..0000000
--- a/src/hooks/useGetQuote.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { UseGetQuoteOptions } from "@/types/hooks/useGetQuote";
-import type { QuoteParams, QuoteResponse } from "@/types/utils/getQuote";
-import { getQuote } from "@/utils/getQuote";
-import { useQuery } from "@tanstack/react-query";
-
-/**
- * React hook for fetching quotes from Uniswap V4 using React Query.
- * Handles caching, loading states, and error handling automatically.
- *
- * @param options - Configuration options for the hook
- * @returns Query result containing quote data, loading state, error state, and refetch function
- *
- * @example
- * ```tsx
- * const { data, isLoading, error, refetch } = useGetQuote({
- * params: {
- * tokens: [token0, token1],
- * feeTier: 3000,
- * tickSpacing: 60,
- * amountIn: parseEther("1"),
- * zeroForOne: true
- * },
- * queryOptions: {
- * enabled: true,
- * staleTime: 30000,
- * gcTime: 300000,
- * retry: 3,
- * onSuccess: (data) => console.log('Quote received:', data)
- * }
- * });
- * ```
- */
-function serializeParams(params?: QuoteParams) {
- if (!params) return undefined;
- return {
- ...params,
- amountIn: params.amountIn?.toString(),
- };
-}
-
-export function useGetQuote({
- params,
- chainId,
- queryOptions = {},
-}: UseGetQuoteOptions = {}) {
- if (!params) throw new Error("No params provided");
- return useQuery({
- queryKey: ["quote", serializeParams(params), chainId],
- queryFn: () => getQuote(params, chainId),
- ...queryOptions,
- });
-}
diff --git a/src/index.ts b/src/index.ts
index c485721..c462011 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,21 +1,3 @@
-// Core
export * from "@/core/uniDevKitV4";
-export * from "@/core/uniDevKitV4Factory";
-
-// Hooks
-export * from "@/hooks/useGetPool";
-export * from "@/hooks/useGetPoolKeyFromPoolId";
-export * from "@/hooks/useGetPosition";
-export * from "@/hooks/useGetQuote";
-
-// Utils
-export * from "@/utils/getPoolKeyFromPoolId";
-export * from "@/utils/getQuote";
-export * from "@/utils/getTokens";
-
-// Types
-export * from "@/types";
-export * from "@/types/core";
-
-// Errors
export * from "@/errors";
+export * from "@/types";
diff --git a/src/test/core/uniDevKitV4.test.ts b/src/test/core/uniDevKitV4.test.ts
index 07d9e48..d74d4a6 100644
--- a/src/test/core/uniDevKitV4.test.ts
+++ b/src/test/core/uniDevKitV4.test.ts
@@ -43,7 +43,7 @@ describe("UniDevKitV4", () => {
chainId: 8453,
rpcUrl: "https://base-rpc.com",
};
- sdk.updateConfig(newConfig);
+ sdk = new UniDevKitV4(newConfig);
expect(sdk.getChainId()).toBe(newConfig.chainId);
});
diff --git a/src/test/core/uniDevKitV4Factory.test.ts b/src/test/core/uniDevKitV4Factory.test.ts
deleted file mode 100644
index de261ce..0000000
--- a/src/test/core/uniDevKitV4Factory.test.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import {
- createInstance,
- getInstance,
- listChainIds,
- removeInstance,
- resetInstances,
-} from "@/core/uniDevKitV4Factory";
-import type { UniDevKitV4Config } from "@/types";
-import { afterEach, beforeEach, describe, expect, it } from "vitest";
-
-describe("UniDevKitV4Factory", () => {
- let config: UniDevKitV4Config;
-
- beforeEach(() => {
- config = {
- chainId: 1,
- rpcUrl: "https://eth.llamarpc.com",
- contracts: {
- poolManager: "0x1234567890123456789012345678901234567890",
- positionDescriptor: "0x1234567890123456789012345678901234567890",
- positionManager: "0x1234567890123456789012345678901234567890",
- quoter: "0x1234567890123456789012345678901234567890",
- stateView: "0x1234567890123456789012345678901234567890",
- universalRouter: "0x1234567890123456789012345678901234567890",
- permit2: "0x1234567890123456789012345678901234567890",
- },
- };
- });
-
- afterEach(() => {
- resetInstances();
- });
-
- it("should create and get instance", () => {
- const instance = createInstance(config);
- const retrievedInstance = getInstance(config.chainId);
- expect(retrievedInstance).toBe(instance);
- });
-
- it("should reuse existing instance", () => {
- const instance1 = createInstance(config);
- const instance2 = createInstance(config);
- expect(instance1).toBe(instance2);
- });
-
- it("should list chain IDs", () => {
- createInstance(config);
- const chainIds = listChainIds();
- expect(chainIds).toContain(config.chainId);
- });
-
- it("should remove instance", () => {
- createInstance(config);
- removeInstance(config.chainId);
- expect(() => getInstance(config.chainId)).toThrow();
- });
-
- it("should reset all instances", () => {
- createInstance(config);
- resetInstances();
- expect(() => getInstance(config.chainId)).toThrow();
- });
-
- it("should throw when getting non-existent instance", () => {
- expect(() => getInstance(999)).toThrow();
- });
-
- it("should get default instance when only one chain is configured", () => {
- const instance = createInstance(config);
- const defaultInstance = getInstance();
- expect(defaultInstance).toBe(instance);
- });
-
- it("should throw when getting default instance with multiple chains", () => {
- createInstance(config);
- createInstance({ ...config, chainId: 8453 });
- expect(() => getInstance()).toThrow(
- "Multiple instances found. Please specify a chain ID. Available chains: 1, 8453",
- );
- });
-
- describe("Multiple chain configurations", () => {
- it("should handle multiple chains with different RPCs", () => {
- const mainnet = createInstance(config);
- const base = createInstance({
- ...config,
- chainId: 8453,
- rpcUrl: "https://base-rpc.com",
- });
- const arbitrum = createInstance({
- ...config,
- chainId: 42161,
- rpcUrl: "https://arbitrum-rpc.com",
- });
-
- expect(getInstance(1)).toBe(mainnet);
- expect(getInstance(8453)).toBe(base);
- expect(getInstance(42161)).toBe(arbitrum);
- });
-
- it("should handle multiple chains with different native currencies", () => {
- const mainnet = createInstance(config);
- const polygon = createInstance({
- ...config,
- chainId: 137,
- });
-
- expect(getInstance(1)).toBe(mainnet);
- expect(getInstance(137)).toBe(polygon);
- });
-
- it("should list all configured chain IDs", () => {
- createInstance(config); // mainnet
- createInstance({ ...config, chainId: 8453 }); // base
- createInstance({ ...config, chainId: 137 }); // polygon
-
- const chainIds = listChainIds();
- expect(chainIds).toHaveLength(3);
- expect(chainIds).toContain(1);
- expect(chainIds).toContain(8453);
- expect(chainIds).toContain(137);
- });
-
- it("should remove specific chain instance", () => {
- createInstance(config); // mainnet
- createInstance({ ...config, chainId: 8453 }); // base
- createInstance({ ...config, chainId: 137 }); // polygon
-
- removeInstance(8453);
- const chainIds = listChainIds();
- expect(chainIds).toHaveLength(2);
- expect(chainIds).not.toContain(8453);
- expect(() => getInstance(8453)).toThrow();
- });
- });
-});
diff --git a/src/test/helpers/sdkInstance.ts b/src/test/helpers/sdkInstance.ts
new file mode 100644
index 0000000..3d9a4ca
--- /dev/null
+++ b/src/test/helpers/sdkInstance.ts
@@ -0,0 +1,31 @@
+import type { UniDevKitV4Instance } from "@/types/core";
+import { http, createPublicClient } from "viem";
+import { mainnet } from "viem/chains";
+import { vi } from "vitest";
+
+export const createMockSdkInstance = (
+ overrides?: Partial,
+): UniDevKitV4Instance => {
+ const client = createPublicClient({
+ chain: mainnet,
+ transport: http(),
+ });
+
+ // Mock the multicall function
+ vi.spyOn(client, "multicall").mockImplementation(async () => []);
+
+ return {
+ client,
+ chain: mainnet,
+ contracts: {
+ poolManager: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8",
+ positionDescriptor: "0x91a40C733c97c6Dc441A0071F8FbF4907dd13151",
+ positionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
+ quoter: "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
+ stateView: "0x4eD4C8B7eF27d4d242c4D1267E1B1E39c14b9E73",
+ universalRouter: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
+ permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
+ },
+ ...overrides,
+ };
+};
diff --git a/src/test/hooks/useGetPool.test.ts b/src/test/hooks/useGetPool.test.ts
deleted file mode 100644
index c4b0137..0000000
--- a/src/test/hooks/useGetPool.test.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { useGetPool } from "@/hooks/useGetPool";
-import { getPool } from "@/utils/getPool";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { renderHook, waitFor } from "@testing-library/react";
-import { Token } from "@uniswap/sdk-core";
-import { jsx as _jsx } from "react/jsx-runtime";
-import type { Mock } from "vitest";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-
-// Mock the getPool function
-vi.mock("@/utils/getPool", () => ({
- getPool: vi.fn(),
-}));
-
-// Mock tokens
-const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as const;
-const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" as const;
-
-describe("useGetPool", () => {
- let queryClient: QueryClient;
-
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
- vi.clearAllMocks();
- });
-
- const wrapper = ({ children }: { children: React.ReactNode }) =>
- _jsx(QueryClientProvider, { client: queryClient, children });
-
- it("should fetch pool data successfully", async () => {
- const mockPool = {
- token0: new Token(1, USDC, 6, "USDC", "USD Coin"),
- token1: new Token(1, WETH, 18, "WETH", "Wrapped Ether"),
- fee: 3000,
- tickSpacing: 60,
- };
-
- (getPool as Mock).mockReturnValue(mockPool);
-
- const { result } = renderHook(
- () =>
- useGetPool({
- params: {
- tokens: [USDC, WETH],
- fee: 3000,
- },
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPool);
- expect(result.current.error).toBeNull();
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("success");
- expect(getPool).toHaveBeenCalledWith(
- {
- tokens: [USDC, WETH],
- fee: 3000,
- },
- 1,
- );
- });
-
- it("should handle errors", async () => {
- const error = new Error("Failed to fetch pool");
- (getPool as Mock).mockImplementation(() => {
- throw error;
- });
-
- const { result } = renderHook(
- () =>
- useGetPool({
- params: {
- tokens: [USDC, WETH],
- fee: 3000,
- },
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toBeUndefined();
- expect(result.current.error).toBe(error);
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("error");
- });
-
- it("should handle custom query options", async () => {
- const mockPool = {
- token0: new Token(1, USDC, 6, "USDC", "USD Coin"),
- token1: new Token(1, WETH, 18, "WETH", "Wrapped Ether"),
- fee: 3000,
- tickSpacing: 60,
- };
-
- (getPool as Mock).mockReturnValue(mockPool);
-
- const { result } = renderHook(
- () =>
- useGetPool({
- params: {
- tokens: [USDC, WETH],
- fee: 3000,
- },
- chainId: 1,
- queryOptions: {
- enabled: true,
- staleTime: 5000,
- },
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPool);
- expect(result.current.error).toBeNull();
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("success");
- });
-});
diff --git a/src/test/hooks/useGetPoolKeyFromPoolId.test.ts b/src/test/hooks/useGetPoolKeyFromPoolId.test.ts
deleted file mode 100644
index 27d3f01..0000000
--- a/src/test/hooks/useGetPoolKeyFromPoolId.test.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useGetPoolKeyFromPoolId } from "@/hooks/useGetPoolKeyFromPoolId";
-import { getPoolKeyFromPoolId } from "@/utils/getPoolKeyFromPoolId";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { renderHook, waitFor } from "@testing-library/react";
-import { jsx as _jsx } from "react/jsx-runtime";
-import type { Mock } from "vitest";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-
-// Mock the getPoolKeyFromPoolId function
-vi.mock("@/utils/getPoolKeyFromPoolId", () => ({
- getPoolKeyFromPoolId: vi.fn(),
-}));
-
-describe("useGetPoolKeyFromPoolId", () => {
- let queryClient: QueryClient;
-
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
- vi.clearAllMocks();
- });
-
- const wrapper = ({ children }: { children: React.ReactNode }) =>
- _jsx(QueryClientProvider, { client: queryClient, children });
-
- it("should fetch pool key data successfully", async () => {
- const mockPoolKey = {
- currency0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- currency1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- fee: 3000,
- tickSpacing: 60,
- hooks: "0x0000000000000000000000000000000000000000",
- };
-
- (getPoolKeyFromPoolId as Mock).mockResolvedValue(mockPoolKey);
-
- const { result } = renderHook(
- () =>
- useGetPoolKeyFromPoolId({
- poolId: "0x1234",
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPoolKey);
- expect(result.current.error).toBeNull();
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("success");
- expect(getPoolKeyFromPoolId).toHaveBeenCalledWith({
- poolId: "0x1234",
- chainId: 1,
- });
- });
-
- it("should handle errors", async () => {
- const error = new Error("Failed to fetch pool key");
- (getPoolKeyFromPoolId as Mock).mockRejectedValue(error);
-
- const { result } = renderHook(
- () =>
- useGetPoolKeyFromPoolId({
- poolId: "0x1234",
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toBeUndefined();
- expect(result.current.error).toBe(error);
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("error");
- });
-
- it("should handle custom query options", async () => {
- const mockPoolKey = {
- currency0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- currency1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- fee: 3000,
- tickSpacing: 60,
- hooks: "0x0000000000000000000000000000000000000000",
- };
-
- (getPoolKeyFromPoolId as Mock).mockResolvedValue(mockPoolKey);
-
- const { result } = renderHook(
- () =>
- useGetPoolKeyFromPoolId({
- poolId: "0x1234",
- chainId: 1,
- queryOptions: {
- enabled: true,
- staleTime: 5000,
- },
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPoolKey);
- expect(result.current.error).toBeNull();
- expect(result.current.isLoading).toBe(false);
- expect(result.current.status).toBe("success");
- });
-});
diff --git a/src/test/hooks/useGetPosition.test.ts b/src/test/hooks/useGetPosition.test.ts
deleted file mode 100644
index 258a9d7..0000000
--- a/src/test/hooks/useGetPosition.test.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import { useGetPosition } from "@/hooks/useGetPosition";
-import { getPosition } from "@/utils/getPosition";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { renderHook, waitFor } from "@testing-library/react";
-import { Token } from "@uniswap/sdk-core";
-import { jsx as _jsx } from "react/jsx-runtime";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-
-// Mock getPosition
-vi.mock("@/utils/getPosition", () => ({
- getPosition: vi.fn(),
-}));
-
-describe("useGetPosition", () => {
- let queryClient: QueryClient;
-
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
- vi.resetAllMocks();
- });
-
- const wrapper = ({ children }: { children: React.ReactNode }) =>
- _jsx(QueryClientProvider, { client: queryClient, children });
-
- it("should fetch position data successfully", async () => {
- const mockPosition = {
- token0: new Token(
- 1,
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- 6,
- "USDC",
- "USD Coin",
- ),
- token1: new Token(
- 1,
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- 18,
- "WETH",
- "Wrapped Ether",
- ),
- position: {
- amounts: {
- amount0: "1000000",
- amount1: "1000000000000000000",
- },
- tickLower: -100,
- tickUpper: 100,
- liquidity: BigInt("1000000000000000000"),
- },
- pool: {},
- poolId: "0x1234567890123456789012345678901234567890",
- tokenId: "123",
- };
-
- (getPosition as unknown as ReturnType).mockResolvedValueOnce(
- mockPosition,
- );
-
- const { result } = renderHook(
- () =>
- useGetPosition({
- tokenId: "123",
- chainId: 1,
- }),
- { wrapper },
- );
-
- expect(result.current.isLoading).toBe(true);
- expect(result.current.data).toBeUndefined();
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPosition);
- expect(getPosition).toHaveBeenCalledWith({ tokenId: "123" }, 1);
- });
-
- it("should handle errors", async () => {
- const error = new Error("Failed to fetch position");
- (getPosition as unknown as ReturnType).mockRejectedValueOnce(
- error,
- );
-
- const { result } = renderHook(
- () =>
- useGetPosition({
- tokenId: "123",
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.error).toEqual(error);
- });
-
- it("should throw error if no tokenId provided", () => {
- expect(() => {
- renderHook(() => useGetPosition(), { wrapper });
- }).toThrow("No tokenId provided");
- });
-
- it("should handle custom query options", async () => {
- const mockPosition = {
- token0: new Token(
- 1,
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- 6,
- "USDC",
- "USD Coin",
- ),
- token1: new Token(
- 1,
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- 18,
- "WETH",
- "Wrapped Ether",
- ),
- position: {
- amounts: {
- amount0: "1000000",
- amount1: "1000000000000000000",
- },
- tickLower: -100,
- tickUpper: 100,
- liquidity: BigInt("1000000000000000000"),
- },
- pool: {},
- poolId: "0x1234567890123456789012345678901234567890",
- tokenId: "123",
- };
-
- (getPosition as unknown as ReturnType).mockResolvedValueOnce(
- mockPosition,
- );
-
- const { result } = renderHook(
- () =>
- useGetPosition({
- tokenId: "123",
- chainId: 1,
- queryOptions: {
- enabled: true,
- staleTime: 30000,
- },
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockPosition);
- });
-});
diff --git a/src/test/hooks/useGetQuote.test.ts b/src/test/hooks/useGetQuote.test.ts
deleted file mode 100644
index 65bc1bb..0000000
--- a/src/test/hooks/useGetQuote.test.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import { useGetQuote } from "@/hooks/useGetQuote";
-import { FeeTier } from "@/types/utils/getPool";
-import { getQuote } from "@/utils/getQuote";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { renderHook, waitFor } from "@testing-library/react";
-import { jsx as _jsx } from "react/jsx-runtime";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-
-// Mock getQuote
-vi.mock("@/utils/getQuote", () => ({
- getQuote: vi.fn(),
-}));
-
-// Real token addresses on Ethereum mainnet
-const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
-const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
-
-describe("useGetQuote", () => {
- let queryClient: QueryClient;
-
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
- });
- vi.resetAllMocks();
- });
-
- const wrapper = ({ children }: { children: React.ReactNode }) =>
- _jsx(QueryClientProvider, { client: queryClient, children: children });
-
- it("should fetch quote data successfully", async () => {
- const mockQuote = {
- amountOut: BigInt("1000000000000000000"),
- estimatedGasUsed: BigInt("100000"),
- timestamp: Date.now(),
- };
-
- (getQuote as unknown as ReturnType).mockResolvedValueOnce(
- mockQuote,
- );
-
- const { result } = renderHook(
- () =>
- useGetQuote({
- params: {
- tokens: [USDC, WETH],
- feeTier: FeeTier.MEDIUM,
- tickSpacing: 60,
- amountIn: BigInt("1000000"),
- zeroForOne: true,
- },
- chainId: 1,
- }),
- { wrapper },
- );
-
- expect(result.current.isLoading).toBe(true);
- expect(result.current.data).toBeUndefined();
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockQuote);
- expect(getQuote).toHaveBeenCalledWith(
- {
- tokens: [USDC, WETH],
- feeTier: FeeTier.MEDIUM,
- tickSpacing: 60,
- amountIn: BigInt("1000000"),
- zeroForOne: true,
- },
- 1,
- );
- });
-
- it("should handle errors", async () => {
- const error = new Error("Failed to fetch quote");
- (getQuote as unknown as ReturnType).mockRejectedValueOnce(
- error,
- );
-
- const { result } = renderHook(
- () =>
- useGetQuote({
- params: {
- tokens: [USDC, WETH],
- feeTier: FeeTier.MEDIUM,
- tickSpacing: 60,
- amountIn: BigInt("1000000"),
- zeroForOne: true,
- },
- chainId: 1,
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.error).toEqual(error);
- });
-
- it("should throw error if no params provided", () => {
- expect(() => {
- renderHook(() => useGetQuote(), { wrapper });
- }).toThrow("No params provided");
- });
-
- it("should handle custom query options", async () => {
- const mockQuote = {
- amountOut: BigInt("1000000000000000000"),
- estimatedGasUsed: BigInt("100000"),
- timestamp: Date.now(),
- };
-
- (getQuote as unknown as ReturnType).mockResolvedValueOnce(
- mockQuote,
- );
-
- const { result } = renderHook(
- () =>
- useGetQuote({
- params: {
- tokens: [USDC, WETH],
- feeTier: FeeTier.MEDIUM,
- tickSpacing: 60,
- amountIn: BigInt("1000000"),
- zeroForOne: true,
- },
- chainId: 1,
- queryOptions: {
- enabled: true,
- staleTime: 30000,
- },
- }),
- { wrapper },
- );
-
- await waitFor(() => {
- expect(result.current.isLoading).toBe(false);
- });
-
- expect(result.current.data).toEqual(mockQuote);
- });
-});
diff --git a/src/test/utils/getPool.test.ts b/src/test/utils/getPool.test.ts
index ebf849f..b3f957c 100644
--- a/src/test/utils/getPool.test.ts
+++ b/src/test/utils/getPool.test.ts
@@ -1,17 +1,17 @@
+import { createMockSdkInstance } from "@/test/helpers/sdkInstance";
import { FeeTier } from "@/types/utils/getPool";
import { getPool } from "@/utils/getPool";
import { Token } from "@uniswap/sdk-core";
+import { Pool } from "@uniswap/v4-sdk";
import { type Address, zeroAddress } from "viem";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mockGetInstance = vi.fn();
const mockGetTokens = vi.fn();
-const mockGetClient = vi.fn();
const mockUseReadContracts = vi.fn();
vi.mock("@/core/uniDevKitV4Factory", () => ({
getInstance: () => mockGetInstance(),
- getClient: () => mockGetClient(),
}));
vi.mock("@/utils/getTokens", () => ({
@@ -22,38 +22,33 @@ vi.mock("wagmi", () => ({
useReadContracts: () => mockUseReadContracts(),
}));
-describe("useV4Pool", () => {
+describe("getPool", () => {
// USDC and WETH on Mainnet
const mockTokens: [Address, Address] = [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
];
- const mockChainId = 1;
+
+ const mockDeps = createMockSdkInstance();
beforeEach(() => {
vi.resetAllMocks();
});
- it("should throw error if SDK instance not found", async () => {
- mockGetInstance.mockReturnValueOnce(undefined);
- await expect(
- getPool(
- {
- tokens: mockTokens,
- fee: FeeTier.MEDIUM,
- },
- mockChainId,
- ),
- ).rejects.toThrow("SDK not found");
- });
+ it("should throw error if pool does not exist", async () => {
+ const mockTokenInstances = [
+ new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
+ new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
+ ];
- it("should throw error if token instances not found", async () => {
- mockGetInstance.mockReturnValueOnce({
- client: { multicall: vi.fn() },
- getClient: vi.fn(),
- getContractAddress: vi.fn(),
- });
- mockGetTokens.mockResolvedValueOnce(null);
+ const mockPoolData = [
+ [mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 0, zeroAddress], // poolKeys with 0 tickSpacing
+ null, // slot0
+ null, // liquidity
+ ];
+
+ mockGetTokens.mockResolvedValueOnce(mockTokenInstances);
+ vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData);
await expect(
getPool(
@@ -61,12 +56,12 @@ describe("useV4Pool", () => {
tokens: mockTokens,
fee: FeeTier.MEDIUM,
},
- mockChainId,
+ mockDeps,
),
- ).rejects.toThrow("Failed to fetch token instances");
+ ).rejects.toThrow("Pool does not exist");
});
- it("should return pool data when pool exists", async () => {
+ it("should return pool when it exists", async () => {
const mockTokenInstances = [
new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
@@ -80,61 +75,22 @@ describe("useV4Pool", () => {
"1000000000000000000",
];
- const mockClient = {
- multicall: vi.fn().mockResolvedValueOnce(mockPoolData),
- };
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
-
mockGetTokens.mockResolvedValueOnce(mockTokenInstances);
- mockUseReadContracts.mockReturnValueOnce({
- data: mockPoolData,
- isLoading: false,
- });
+ vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData);
const result = await getPool(
{
tokens: mockTokens,
fee: FeeTier.MEDIUM,
},
- mockChainId,
+ mockDeps,
);
expect(result).toBeDefined();
+ expect(result).toBeInstanceOf(Pool);
});
- it("should return undefined data when pool does not exist", async () => {
- const mockTokenInstances = [
- new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
- new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
- ];
-
- const mockClient = { multicall: vi.fn() };
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
-
- mockGetTokens.mockResolvedValueOnce(mockTokenInstances);
- mockUseReadContracts.mockReturnValueOnce({
- data: null,
- isLoading: false,
- });
-
- const result = await getPool(
- {
- tokens: mockTokens,
- fee: FeeTier.MEDIUM,
- },
- mockChainId,
- );
-
- expect(result).toBeUndefined();
- });
-
- it("should handle pool creation error", async () => {
+ it("should throw error if pool creation fails", async () => {
const mockTokenInstances = [
new Token(1, mockTokens[0], 18, "TOKEN0", "Token 0"),
new Token(1, mockTokens[1], 18, "TOKEN1", "Token 1"),
@@ -142,32 +98,21 @@ describe("useV4Pool", () => {
const mockPoolData = [
[mockTokens[0], mockTokens[1], FeeTier.MEDIUM, 60, zeroAddress],
- ["invalid", 0, 0, 0, 0, 0],
+ ["invalid", 0, 0, 0, 0, 0], // invalid sqrtPriceX96
"1000000000000000000",
];
- const mockClient = {
- multicall: vi.fn().mockResolvedValueOnce(mockPoolData),
- };
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
-
mockGetTokens.mockResolvedValueOnce(mockTokenInstances);
- mockUseReadContracts.mockReturnValueOnce({
- data: mockPoolData,
- isLoading: false,
- });
+ vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockPoolData);
- const result = await getPool(
- {
- tokens: mockTokens,
- fee: FeeTier.MEDIUM,
- },
- mockChainId,
- );
-
- expect(result).toBeUndefined();
+ await expect(
+ getPool(
+ {
+ tokens: mockTokens,
+ fee: FeeTier.MEDIUM,
+ },
+ mockDeps,
+ ),
+ ).rejects.toThrow("Error creating pool instance");
});
});
diff --git a/src/test/utils/getPoolKeyFromPoolId.test.ts b/src/test/utils/getPoolKeyFromPoolId.test.ts
index f51a425..6bb985a 100644
--- a/src/test/utils/getPoolKeyFromPoolId.test.ts
+++ b/src/test/utils/getPoolKeyFromPoolId.test.ts
@@ -1,129 +1,56 @@
-import type { UniDevKitV4 } from "@/core/uniDevKitV4";
-import { getInstance } from "@/core/uniDevKitV4Factory";
+import { createMockSdkInstance } from "@/test/helpers/sdkInstance";
import { getPoolKeyFromPoolId } from "@/utils/getPoolKeyFromPoolId";
import { describe, expect, it, vi } from "vitest";
-// Mock the SDK instance
-vi.mock("@/core/uniDevKitV4Factory", () => ({
- getInstance: vi.fn(),
-}));
-
describe("getPoolKeyFromPoolId", () => {
- const mockPoolId =
- "0x1234567890123456789012345678901234567890123456789012345678901234";
- const mockChainId = 1;
- const expectedPoolId25Bytes =
- "0x12345678901234567890123456789012345678901234567890";
-
it("should throw error if SDK instance not found", async () => {
- vi.mocked(getInstance).mockReturnValue(undefined as unknown as UniDevKitV4);
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.readContract = vi
+ .fn()
+ .mockRejectedValueOnce(new Error("SDK not initialized"));
await expect(
- getPoolKeyFromPoolId({ poolId: mockPoolId, chainId: mockChainId }),
+ getPoolKeyFromPoolId({ poolId: "0x123" }, mockDeps),
).rejects.toThrow("SDK not initialized");
});
it("should return pool key when SDK instance exists", async () => {
const mockPoolKey = [
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- 3000,
- 60,
- "0x0000000000000000000000000000000000000000",
+ "0x123", // currency0
+ "0x456", // currency1
+ 3000, // fee
+ 60, // tickSpacing
+ "0x789", // hooks
];
- const mockClient = {
- readContract: vi.fn().mockResolvedValue(mockPoolKey),
- };
-
- const mockSdk = {
- getClient: vi.fn().mockReturnValue(mockClient),
- getContractAddress: vi.fn().mockReturnValue("0xPositionManager"),
- instance: {},
- createInstance: vi.fn(),
- createClient: vi.fn(),
- getChainId: vi.fn(),
- getContract: vi.fn(),
- } as unknown as UniDevKitV4;
-
- vi.mocked(getInstance).mockReturnValue(mockSdk);
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.readContract = vi.fn().mockResolvedValueOnce(mockPoolKey);
- const result = await getPoolKeyFromPoolId({
- poolId: mockPoolId,
- chainId: mockChainId,
- });
+ const result = await getPoolKeyFromPoolId({ poolId: "0x123" }, mockDeps);
expect(result).toEqual({
- currency0: mockPoolKey[0],
- currency1: mockPoolKey[1],
- fee: mockPoolKey[2],
- tickSpacing: mockPoolKey[3],
- hooks: mockPoolKey[4],
+ currency0: "0x123",
+ currency1: "0x456",
+ fee: 3000,
+ tickSpacing: 60,
+ hooks: "0x789",
});
- expect(mockClient.readContract).toHaveBeenCalledWith({
- address: "0xPositionManager",
+ expect(mockDeps.client.readContract).toHaveBeenCalledWith({
+ address: mockDeps.contracts.positionManager,
abi: expect.any(Object),
functionName: "poolKeys",
- args: [expectedPoolId25Bytes],
+ args: ["0x123"],
});
});
it("should handle contract read errors", async () => {
- const mockClient = {
- readContract: vi
- .fn()
- .mockRejectedValue(new Error("Contract read failed")),
- };
-
- const mockSdk = {
- getClient: vi.fn().mockReturnValue(mockClient),
- getContractAddress: vi.fn().mockReturnValue("0xPositionManager"),
- instance: {},
- createInstance: vi.fn(),
- createClient: vi.fn(),
- getChainId: vi.fn(),
- getContract: vi.fn(),
- } as unknown as UniDevKitV4;
-
- vi.mocked(getInstance).mockReturnValue(mockSdk);
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.readContract = vi
+ .fn()
+ .mockRejectedValueOnce(new Error("Contract read failed"));
await expect(
- getPoolKeyFromPoolId({ poolId: mockPoolId, chainId: mockChainId }),
+ getPoolKeyFromPoolId({ poolId: "0x123" }, mockDeps),
).rejects.toThrow("Contract read failed");
});
-
- it("should correctly convert poolId from 32 bytes to 25 bytes", async () => {
- const mockPoolKey = [
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- 3000,
- 60,
- "0x0000000000000000000000000000000000000000",
- ];
-
- const mockClient = {
- readContract: vi.fn().mockResolvedValue(mockPoolKey),
- };
-
- const mockSdk = {
- getClient: vi.fn().mockReturnValue(mockClient),
- getContractAddress: vi.fn().mockReturnValue("0xPositionManager"),
- instance: {},
- createInstance: vi.fn(),
- createClient: vi.fn(),
- getChainId: vi.fn(),
- getContract: vi.fn(),
- } as unknown as UniDevKitV4;
-
- vi.mocked(getInstance).mockReturnValue(mockSdk);
-
- await getPoolKeyFromPoolId({ poolId: mockPoolId, chainId: mockChainId });
-
- // Verify that the poolId was correctly converted
- expect(mockClient.readContract).toHaveBeenCalledWith(
- expect.objectContaining({
- args: [expectedPoolId25Bytes],
- }),
- );
- });
});
diff --git a/src/test/utils/getPosition.test.ts b/src/test/utils/getPosition.test.ts
index 6eb5d3a..28fdd1c 100644
--- a/src/test/utils/getPosition.test.ts
+++ b/src/test/utils/getPosition.test.ts
@@ -1,20 +1,17 @@
+import { createMockSdkInstance } from "@/test/helpers/sdkInstance";
import { getPosition } from "@/utils/getPosition";
import { Token } from "@uniswap/sdk-core";
-import { type Address, zeroAddress } from "viem";
+import type { Address } from "viem";
import { beforeEach, describe, expect, it, vi } from "vitest";
-// Mock the SDK instance
-vi.mock("@/core/uniDevKitV4Factory", () => ({
- getInstance: () => mockGetInstance(),
-}));
-
-// Mock getTokens
vi.mock("@/utils/getTokens", () => ({
- getTokens: () => mockGetTokens(),
+ getTokens: vi.fn(),
}));
-const mockGetInstance = vi.fn();
-const mockGetTokens = vi.fn();
+// Mock decodePositionInfo para devolver ticks vΓ‘lidos
+vi.mock("@/helpers/positions", () => ({
+ decodePositionInfo: () => ({ tickLower: -887220, tickUpper: 887220 }),
+}));
describe("getPosition", () => {
// USDC and WETH on Mainnet
@@ -22,129 +19,99 @@ describe("getPosition", () => {
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
];
- const mockTokenId = "123";
- const mockChainId = 1;
+ const validHooks = "0x000000000000000000000000000000000000dead";
beforeEach(() => {
vi.resetAllMocks();
});
it("should throw error if SDK instance not found", async () => {
- mockGetInstance.mockReturnValueOnce(undefined);
- await expect(
- getPosition({ tokenId: mockTokenId }, mockChainId),
- ).rejects.toThrow("SDK not found. Please create an instance first.");
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.multicall = vi
+ .fn()
+ .mockRejectedValueOnce(new Error("SDK not initialized"));
+
+ await expect(getPosition({ tokenId: "1" }, mockDeps)).rejects.toThrow(
+ "SDK not initialized",
+ );
});
it("should throw error if tokens not found", async () => {
- const mockClient = {
- multicall: vi.fn().mockResolvedValueOnce([
- [
- {
- currency0: mockTokens[0],
- currency1: mockTokens[1],
- fee: 3000,
- tickSpacing: 60,
- hooks: zeroAddress,
- },
- "0x",
- ],
- BigInt("1000000000000000000"),
- ]),
- };
-
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
- mockGetTokens.mockResolvedValueOnce(null);
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.multicall = vi.fn().mockResolvedValueOnce([
+ [
+ {
+ currency0: "0x123",
+ currency1: "0x456",
+ fee: 3000,
+ tickSpacing: 60,
+ hooks: validHooks,
+ },
+ {},
+ ],
+ 1000000n,
+ ]);
- await expect(
- getPosition({ tokenId: mockTokenId }, mockChainId),
- ).rejects.toThrow("Tokens not found");
+ await expect(getPosition({ tokenId: "1" }, mockDeps)).rejects.toThrow(
+ "Tokens not found",
+ );
});
it("should throw error if liquidity is 0", async () => {
- const mockClient = {
- multicall: vi.fn().mockResolvedValueOnce([
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.multicall = vi.fn().mockResolvedValueOnce([
+ [
+ {
+ currency0: "0x123",
+ currency1: "0x456",
+ fee: 3000,
+ tickSpacing: 60,
+ hooks: validHooks,
+ },
+ {},
+ ],
+ 0n,
+ ]);
+
+ await expect(getPosition({ tokenId: "1" }, mockDeps)).rejects.toThrow(
+ "Liquidity is 0",
+ );
+ });
+
+ it("should return position data when position exists", async () => {
+ const mockDeps = createMockSdkInstance();
+ // Primer multicall: [poolAndPositionInfo, liquidity]
+ mockDeps.client.multicall = vi
+ .fn()
+ .mockResolvedValueOnce([
[
{
currency0: mockTokens[0],
currency1: mockTokens[1],
fee: 3000,
tickSpacing: 60,
- hooks: zeroAddress,
+ hooks: validHooks,
},
- "0x",
+ 1n,
],
- BigInt(0),
- ]),
- };
-
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
-
- mockGetTokens.mockResolvedValueOnce([
- new Token(1, mockTokens[0], 6, "USDC", "USD Coin"),
- new Token(1, mockTokens[1], 18, "WETH", "Wrapped Ether"),
- ]);
-
- await expect(
- getPosition({ tokenId: mockTokenId }, mockChainId),
- ).rejects.toThrow("Liquidity is 0");
- });
-
- it("should return position data when position exists", async () => {
- const mockTokenInstances = [
- new Token(1, mockTokens[0], 6, "USDC", "USD Coin"),
- new Token(1, mockTokens[1], 18, "WETH", "Wrapped Ether"),
- ];
-
- // Compose a valid rawInfo: hasSubscriber=1, tickLower=0, tickUpper=60, poolId=0n
- const hasSubscriber = 1n;
- const tickLower = 0n;
- const tickUpper = 60n;
- const poolId = 0n;
- const rawInfo =
- hasSubscriber | (tickLower << 8n) | (tickUpper << 32n) | (poolId << 56n);
- const mockPoolKey = {
- currency0: mockTokens[0],
- currency1: mockTokens[1],
- fee: 3000,
- tickSpacing: 60,
- hooks: zeroAddress,
- };
-
- const mockClient = {
- multicall: vi
- .fn()
- .mockResolvedValueOnce([
- [mockPoolKey, rawInfo],
- BigInt("1000000000000000000"),
- ])
- .mockResolvedValueOnce([
- [BigInt("79228162514264337593543950336"), 0],
- BigInt("1000000000000000000"),
- ]),
- };
-
- mockGetInstance.mockReturnValueOnce({
- getClient: () => mockClient,
- getContractAddress: vi.fn(() => "0xMockAddress"),
- });
-
- mockGetTokens.mockResolvedValueOnce(mockTokenInstances);
-
- const result = await getPosition({ tokenId: mockTokenId }, mockChainId);
-
- expect(result).toBeDefined();
- expect(result?.token0).toEqual(mockTokenInstances[0]);
- expect(result?.token1).toEqual(mockTokenInstances[1]);
- expect(result?.position).toBeDefined();
- expect(result?.pool).toBeDefined();
- expect(result?.poolId).toBeDefined();
- expect(result?.tokenId).toBe(mockTokenId);
+ 1000000n,
+ ])
+ // Segundo multicall: [slot0, poolLiquidity]
+ .mockResolvedValueOnce([[79228162514264337593543950336n, 0], 1000000n]);
+
+ // Mock getTokens para devolver instancias reales de Token
+ const mockToken0 = new Token(1, mockTokens[0], 6, "USDC", "USD Coin");
+ const mockToken1 = new Token(1, mockTokens[1], 18, "WETH", "Wrapped Ether");
+ const { getTokens } = await import("@/utils/getTokens");
+ vi.mocked(getTokens).mockResolvedValueOnce([mockToken0, mockToken1]);
+
+ const result = await getPosition({ tokenId: "1" }, mockDeps);
+
+ expect(result).toHaveProperty("position");
+ expect(result).toHaveProperty("pool");
+ expect(result).toHaveProperty("token0");
+ expect(result).toHaveProperty("token1");
+ expect(result).toHaveProperty("poolId");
+ expect(result).toHaveProperty("tokenId", "1");
});
});
diff --git a/src/test/utils/getQuote.test.ts b/src/test/utils/getQuote.test.ts
index c9555e3..f0ffcad 100644
--- a/src/test/utils/getQuote.test.ts
+++ b/src/test/utils/getQuote.test.ts
@@ -1,65 +1,49 @@
+import { createMockSdkInstance } from "@/test/helpers/sdkInstance";
import { getQuote } from "@/utils/getQuote";
+import type { Abi } from "viem";
+import type { SimulateContractReturnType } from "viem/actions";
import { describe, expect, it, vi } from "vitest";
-const mockGetInstance = vi.fn();
-
-vi.mock("@/core/uniDevKitV4Factory", () => ({
- getInstance: () => mockGetInstance(),
-}));
-
describe("getQuote", () => {
it("should throw error if SDK instance not found", async () => {
- mockGetInstance.mockReturnValueOnce(undefined);
- await expect(
- getQuote({
- tokens: ["0x123", "0x456"],
- feeTier: 3000,
- tickSpacing: 60,
- amountIn: BigInt(1000000),
- zeroForOne: true,
- }),
- ).rejects.toThrow();
- });
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.simulateContract = vi
+ .fn()
+ .mockRejectedValueOnce(new Error("SDK not found"));
- it("should sort tokens consistently", async () => {
- mockGetInstance.mockReturnValueOnce({
- getClient: () => ({
- simulateContract: vi
- .fn()
- .mockResolvedValue({ result: [BigInt(1000000), BigInt(21000)] }),
- }),
- getContractAddress: vi.fn(() => "0xMockQuoterAddress"),
- });
- const result = await getQuote({
- tokens: ["0x123", "0x456"],
- feeTier: 3000,
- tickSpacing: 60,
- amountIn: BigInt(1000000),
- zeroForOne: true,
- });
- expect(result).toHaveProperty("amountOut", BigInt(1000000));
- expect(result).toHaveProperty("estimatedGasUsed", BigInt(21000));
- expect(result).toHaveProperty("timestamp");
+ await expect(
+ getQuote(
+ {
+ tokens: ["0x123", "0x456"],
+ zeroForOne: true,
+ amountIn: BigInt(1000000),
+ feeTier: 3000,
+ },
+ mockDeps,
+ ),
+ ).rejects.toThrow("SDK not found");
});
it("should handle quote simulation", async () => {
- mockGetInstance.mockReturnValueOnce({
- getClient: () => ({
- simulateContract: vi
- .fn()
- .mockResolvedValue({ result: [BigInt(1000000), BigInt(21000)] }),
- }),
- getContractAddress: vi.fn(() => "0xMockQuoterAddress"),
- });
- const result = await getQuote({
- tokens: ["0x123", "0x456"],
- feeTier: 3000,
- tickSpacing: 60,
- amountIn: BigInt(1000000),
- zeroForOne: true,
+ const mockDeps = createMockSdkInstance();
+ mockDeps.client.simulateContract = vi.fn().mockResolvedValueOnce({
+ result: [BigInt(1000000), BigInt(21000)],
+ } as SimulateContractReturnType);
+
+ const result = await getQuote(
+ {
+ tokens: ["0x123", "0x456"],
+ zeroForOne: true,
+ amountIn: BigInt(1000000),
+ feeTier: 3000,
+ },
+ mockDeps,
+ );
+
+ expect(result).toEqual({
+ amountOut: BigInt(1000000),
+ estimatedGasUsed: BigInt(21000),
+ timestamp: expect.any(Number),
});
- expect(result).toHaveProperty("amountOut", BigInt(1000000));
- expect(result).toHaveProperty("estimatedGasUsed", BigInt(21000));
- expect(result).toHaveProperty("timestamp");
});
});
diff --git a/src/test/utils/getTokens.test.ts b/src/test/utils/getTokens.test.ts
index bc93b18..d0a81d7 100644
--- a/src/test/utils/getTokens.test.ts
+++ b/src/test/utils/getTokens.test.ts
@@ -1,7 +1,7 @@
-import { getInstance } from "@/core/uniDevKitV4Factory";
+import { createMockSdkInstance } from "@/test/helpers/sdkInstance";
import { getTokens } from "@/utils/getTokens";
import { Token } from "@uniswap/sdk-core";
-import { type Address, erc20Abi, zeroAddress } from "viem";
+import { type Address, zeroAddress } from "viem";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Mock the SDK instance
@@ -20,144 +20,79 @@ vi.mock("@/constants/chains", () => ({
}));
describe("getTokens", () => {
- const mockClient = {
- multicall: vi.fn(),
- };
+ // USDC and WETH on Mainnet
+ const mockTokens: [Address, ...Address[]] = [
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ ];
- const mockSdk = {
- getClient: () => mockClient,
- getChainId: () => 1,
- };
+ let mockDeps: ReturnType;
beforeEach(() => {
- vi.clearAllMocks();
- (getInstance as unknown as ReturnType).mockReturnValue(
- mockSdk,
- );
+ vi.resetAllMocks();
+ mockDeps = createMockSdkInstance();
});
- it("should return token instances for valid addresses", async () => {
- const addresses: [Address, ...Address[]] = [
- "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" as Address, // UNI
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" as Address, // WETH
- ];
+ it("should throw error if multicall fails", async () => {
+ vi.mocked(mockDeps.client.multicall).mockRejectedValueOnce(
+ new Error("Multicall failed"),
+ );
+
+ await expect(
+ getTokens(
+ {
+ addresses: mockTokens,
+ },
+ mockDeps,
+ ),
+ ).rejects.toThrow("Failed to fetch token data: Multicall failed");
+ });
+ it("should handle native currency (zero address)", async () => {
const mockResults = [
- "UNI", // symbol for UNI
- "Uniswap", // name for UNI
- 18, // decimals for UNI
- "WETH", // symbol for WETH
- "Wrapped Ether", // name for WETH
- 18, // decimals for WETH
+ "USDC", // symbol for first token
+ "USD Coin", // name for first token
+ 6, // decimals for first token
];
- mockClient.multicall.mockResolvedValueOnce(mockResults);
+ vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockResults);
- const result = await getTokens({
- addresses,
- chainId: 1,
- });
+ const result = await getTokens(
+ {
+ addresses: [mockTokens[0], zeroAddress],
+ },
+ mockDeps,
+ );
- expect(result).not.toBeNull();
expect(result).toHaveLength(2);
- expect(result?.[0]).toBeInstanceOf(Token);
- expect(result?.[1]).toBeInstanceOf(Token);
-
- // Verify UNI token
- expect(result?.[0].symbol).toBe("UNI");
- expect(result?.[0].name).toBe("Uniswap");
- expect(result?.[0].decimals).toBe(18);
- expect(result?.[0].chainId).toBe(1);
- expect(result?.[0].address).toBe(addresses[0]);
-
- // Verify WETH token
- expect(result?.[1].symbol).toBe("WETH");
- expect(result?.[1].name).toBe("Wrapped Ether");
- expect(result?.[1].decimals).toBe(18);
- expect(result?.[1].chainId).toBe(1);
- expect(result?.[1].address).toBe(addresses[1]);
-
- // Verify multicall was called with correct parameters
- expect(mockClient.multicall).toHaveBeenCalledWith({
- contracts: [
- { address: addresses[0], abi: erc20Abi, functionName: "symbol" },
- { address: addresses[0], abi: erc20Abi, functionName: "name" },
- { address: addresses[0], abi: erc20Abi, functionName: "decimals" },
- { address: addresses[1], abi: erc20Abi, functionName: "symbol" },
- { address: addresses[1], abi: erc20Abi, functionName: "name" },
- { address: addresses[1], abi: erc20Abi, functionName: "decimals" },
- ],
- allowFailure: false,
- });
+ expect(result[0]).toBeInstanceOf(Token);
+ expect(result[1]).toBeInstanceOf(Token);
+ expect(result[1].address).toBe(zeroAddress);
});
- it("should handle native currency (zeroAddress)", async () => {
- const addresses: [Address, ...Address[]] = [
- zeroAddress,
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" as Address, // WETH
- ];
-
+ it("should return token instances for valid addresses", async () => {
const mockResults = [
- "WETH", // symbol for WETH
- "Wrapped Ether", // name for WETH
- 18, // decimals for WETH
+ "USDC", // symbol for first token
+ "USD Coin", // name for first token
+ 6, // decimals for first token
+ "WETH", // symbol for second token
+ "Wrapped Ether", // name for second token
+ 18, // decimals for second token
];
- mockClient.multicall.mockResolvedValueOnce(mockResults);
+ vi.mocked(mockDeps.client.multicall).mockResolvedValueOnce(mockResults);
- const result = await getTokens({
- addresses,
- });
+ const result = await getTokens(
+ {
+ addresses: mockTokens,
+ },
+ mockDeps,
+ );
- expect(result).not.toBeNull();
expect(result).toHaveLength(2);
- expect(result?.[0]).toBeInstanceOf(Token);
- expect(result?.[1]).toBeInstanceOf(Token);
-
- // Verify native token
- expect(result?.[0].symbol).toBe("ETH");
- expect(result?.[0].name).toBe("Ethereum");
- expect(result?.[0].decimals).toBe(18);
- expect(result?.[0].chainId).toBe(1);
- expect(result?.[0].address).toBe(zeroAddress);
-
- // Verify WETH token
- expect(result?.[1].symbol).toBe("WETH");
- expect(result?.[1].name).toBe("Wrapped Ether");
- expect(result?.[1].decimals).toBe(18);
- expect(result?.[1].chainId).toBe(1);
- expect(result?.[1].address).toBe(addresses[1]);
-
- // Verify multicall was called only for non-native token
- expect(mockClient.multicall).toHaveBeenCalledWith({
- contracts: [
- { address: addresses[1], abi: erc20Abi, functionName: "symbol" },
- { address: addresses[1], abi: erc20Abi, functionName: "name" },
- { address: addresses[1], abi: erc20Abi, functionName: "decimals" },
- ],
- allowFailure: false,
- });
- });
-
- it("should throw error when SDK instance is not found", async () => {
- (getInstance as unknown as ReturnType).mockReturnValue(null);
-
- await expect(
- getTokens({
- addresses: ["0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" as Address],
- chainId: 1,
- }),
- ).rejects.toThrow("SDK not found. Please create an instance first.");
- });
-
- it("should return null when multicall fails", async () => {
- mockClient.multicall.mockRejectedValueOnce(new Error("Multicall failed"));
-
- const result = await getTokens({
- addresses: ["0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" as Address],
- chainId: 1,
- });
-
- expect(result).toBeNull();
+ expect(result[0]).toBeInstanceOf(Token);
+ expect(result[1]).toBeInstanceOf(Token);
+ expect(result[0].symbol).toBe("USDC");
+ expect(result[1].symbol).toBe("WETH");
});
});
diff --git a/src/types/core.ts b/src/types/core.ts
index 0210c0b..4beb1c1 100644
--- a/src/types/core.ts
+++ b/src/types/core.ts
@@ -1,4 +1,4 @@
-import type { Address, PublicClient } from "viem";
+import type { Address, Chain, PublicClient } from "viem";
/**
* Configuration for V4 contracts.
@@ -41,8 +41,8 @@ export type UniDevKitV4Config = {
export type UniDevKitV4Instance = {
/** Viem public client */
client: PublicClient;
- /** Chain ID */
- chainId: number;
+ /** Chain */
+ chain: Chain;
/** Contract addresses */
contracts: V4Contracts;
};
diff --git a/src/types/hooks/index.ts b/src/types/hooks/index.ts
deleted file mode 100644
index c21a71a..0000000
--- a/src/types/hooks/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "@/types/hooks/useGetPool";
-export * from "@/types/hooks/useGetPoolKeyFromPoolId";
-export * from "@/types/hooks/useGetPosition";
-export * from "@/types/hooks/useGetQuote";
diff --git a/src/types/hooks/useGetPool.ts b/src/types/hooks/useGetPool.ts
deleted file mode 100644
index 8cbdd0f..0000000
--- a/src/types/hooks/useGetPool.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { PoolParams } from "@/types/utils/getPool";
-import type { UseQueryOptions } from "@tanstack/react-query";
-import type { Pool } from "@uniswap/v4-sdk";
-
-/**
- * Configuration options for the useGetPool hook.
- */
-export type UseGetPoolOptions = {
- /** Initial pool parameters */
- params: PoolParams;
- /** Chain ID */
- chainId?: number;
- /** React Query options */
- queryOptions?: Omit<
- UseQueryOptions,
- "queryKey"
- >;
-};
diff --git a/src/types/hooks/useGetPoolKeyFromPoolId.ts b/src/types/hooks/useGetPoolKeyFromPoolId.ts
deleted file mode 100644
index 59e8f6a..0000000
--- a/src/types/hooks/useGetPoolKeyFromPoolId.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { PoolKey } from "@/types/utils/getPoolKeyFromPoolId";
-import type { UseQueryOptions } from "@tanstack/react-query";
-
-/**
- * Configuration options for the useGetPoolKeyFromPoolId hook.
- */
-export type UseGetPoolKeyFromPoolIdOptions = {
- /** The 32-byte pool ID in hex format (0x...) */
- poolId: `0x${string}`;
- /** Chain ID */
- chainId?: number;
- /** React Query options */
- queryOptions?: Omit<
- UseQueryOptions,
- "queryKey"
- >;
-};
diff --git a/src/types/hooks/useGetPosition.ts b/src/types/hooks/useGetPosition.ts
deleted file mode 100644
index 115741a..0000000
--- a/src/types/hooks/useGetPosition.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { UseQueryOptions } from "@tanstack/react-query";
-import type { Token } from "@uniswap/sdk-core";
-import type { Pool, Position } from "@uniswap/v4-sdk";
-
-/**
- * Result type for position data
- */
-export interface PositionResult {
- /** The position instance */
- position: Position;
- /** The pool instance associated with the position */
- pool: Pool;
- /** First token in the pair */
- token0: Token;
- /** Second token in the pair */
- token1: Token;
- /** Unique identifier for the pool */
- poolId: `0x${string}`;
- /** The unique identifier of the position */
- tokenId: string;
-}
-
-/**
- * Configuration options for the useGetPosition hook
- */
-export interface UseGetPositionOptions {
- /** Token ID of the position */
- tokenId?: string;
- /** Chain ID to use */
- chainId?: number;
- /** React Query options */
- queryOptions?: Omit<
- UseQueryOptions<
- PositionResult | undefined,
- Error,
- PositionResult | undefined,
- unknown[]
- >,
- "queryKey"
- >;
-}
diff --git a/src/types/hooks/useGetQuote.ts b/src/types/hooks/useGetQuote.ts
deleted file mode 100644
index fa7c0a1..0000000
--- a/src/types/hooks/useGetQuote.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { QuoteParams, QuoteResponse } from "@/types/utils/getQuote";
-import type { UseQueryOptions } from "@tanstack/react-query";
-
-/**
- * Configuration options for the useGetQuote hook.
- */
-export type UseGetQuoteOptions = {
- /** Initial quote parameters */
- params?: QuoteParams;
- /** Chain ID to use */
- chainId?: number;
- /** React Query options */
- queryOptions?: Omit<
- UseQueryOptions,
- "queryKey"
- >;
-};
diff --git a/src/types/index.ts b/src/types/index.ts
index f0911fb..f5bd8a2 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,3 +1,2 @@
export * from "@/types/core";
-export * from "@/types/hooks";
export * from "@/types/utils";
diff --git a/src/types/utils/getPool.ts b/src/types/utils/getPool.ts
index 9571278..2cf3652 100644
--- a/src/types/utils/getPool.ts
+++ b/src/types/utils/getPool.ts
@@ -31,6 +31,6 @@ export interface PoolParams {
fee?: FeeTier;
/** Optional tick spacing for the pool (default: derived from fee tier) */
tickSpacing?: number;
- /** Optional hooks contract address (default: zero address) */
+ /** Optional hooks contract address (default: DEFAULT_HOOKS) */
hooks?: `0x${string}`;
}
diff --git a/src/types/utils/getPoolKeyFromPoolId.ts b/src/types/utils/getPoolKeyFromPoolId.ts
index 803e691..db67ccd 100644
--- a/src/types/utils/getPoolKeyFromPoolId.ts
+++ b/src/types/utils/getPoolKeyFromPoolId.ts
@@ -4,8 +4,6 @@
export interface GetPoolKeyFromPoolIdParams {
/** The 32-byte pool ID in hex format (0x...) */
poolId: `0x${string}`;
- /** Optional chain ID where the pool exists */
- chainId?: number;
}
/**
diff --git a/src/types/utils/getTokens.ts b/src/types/utils/getTokens.ts
new file mode 100644
index 0000000..0ade3fe
--- /dev/null
+++ b/src/types/utils/getTokens.ts
@@ -0,0 +1,9 @@
+import type { Address } from "viem";
+
+/**
+ * Parameters for getTokens function
+ */
+export interface GetTokensParams {
+ /** Array of token contract addresses (at least one) */
+ addresses: [Address, ...Address[]];
+}
diff --git a/src/utils/getPool.ts b/src/utils/getPool.ts
index 185a1ee..2800158 100644
--- a/src/utils/getPool.ts
+++ b/src/utils/getPool.ts
@@ -1,8 +1,8 @@
import { V4PositionManagerAbi } from "@/constants/abis/V4PositionMananger";
import { V4StateViewAbi } from "@/constants/abis/V4StateView";
-import { getInstance } from "@/core/uniDevKitV4Factory";
import { getTickSpacingForFee } from "@/helpers/fees";
import { sortTokens } from "@/helpers/tokens";
+import type { UniDevKitV4Instance } from "@/types/core";
import { FeeTier, type PoolParams } from "@/types/utils/getPool";
import { getTokens } from "@/utils/getTokens";
import { Pool } from "@uniswap/v4-sdk";
@@ -13,19 +13,16 @@ const DEFAULT_HOOKS = zeroAddress;
/**
* Retrieves a Uniswap V4 pool instance for a given token pair, fee tier, tick spacing, and hooks configuration.
* @param params Pool parameters including tokens, fee tier, tick spacing, and hooks configuration
- * @param chainId The chain ID where the pool exists (optional)
- * @returns Promise resolving to pool data, loading state, and any errors
+ * @param instance UniDevKitV4Instance
+ * @returns Promise resolving to pool data
* @throws Error if SDK instance or token instances are not found or if pool data is not found
*/
export async function getPool(
params: PoolParams,
- chainId?: number,
-): Promise {
- const sdk = getInstance(chainId);
- if (!sdk) {
- throw new Error("SDK not found. Please create an instance first.");
- }
- const client = sdk.getClient();
+ instance: UniDevKitV4Instance,
+): Promise {
+ const { client, contracts } = instance;
+ const { positionManager, stateView } = contracts;
const {
tokens,
@@ -38,16 +35,12 @@ export async function getPool(
const finalTickSpacing = tickSpacing ?? getTickSpacingForFee(fee);
const [token0, token1] = sortTokens(tokens[0], tokens[1]);
- const tokenInstances = await getTokens({
- addresses: [token0, token1],
- chainId,
- });
-
- console.log("tokenInstances", tokenInstances);
-
- if (!tokenInstances) {
- throw new Error("Failed to fetch token instances");
- }
+ const tokenInstances = await getTokens(
+ {
+ addresses: [token0, token1],
+ },
+ instance,
+ );
const poolId32Bytes = Pool.getPoolId(
tokenInstances[0],
@@ -57,27 +50,25 @@ export async function getPool(
hooks,
) as `0x${string}`;
- console.log("poolId32Bytes", poolId32Bytes);
-
const poolId25Bytes = slice(poolId32Bytes, 0, 25) as `0x${string}`;
const poolData = await client.multicall({
allowFailure: false,
contracts: [
{
- address: sdk.getContractAddress("positionManager"),
+ address: positionManager,
abi: V4PositionManagerAbi,
functionName: "poolKeys",
args: [poolId25Bytes],
},
{
- address: sdk.getContractAddress("stateView"),
+ address: stateView,
abi: V4StateViewAbi,
functionName: "getSlot0",
args: [poolId32Bytes],
},
{
- address: sdk.getContractAddress("stateView"),
+ address: stateView,
abi: V4StateViewAbi,
functionName: "getLiquidity",
args: [poolId32Bytes],
@@ -86,7 +77,7 @@ export async function getPool(
});
if (!poolData) {
- return undefined;
+ throw new Error("Failed to fetch pool data");
}
const [poolKeysData, slot0Data, liquidityData] = poolData;
@@ -94,7 +85,7 @@ export async function getPool(
poolKeysData && Number(poolKeysData[3]) > 0 && slot0Data && liquidityData;
if (!poolExists) {
- return undefined;
+ throw new Error("Pool does not exist");
}
try {
@@ -111,7 +102,8 @@ export async function getPool(
return pool;
} catch (error) {
- console.error("Error fetching pool:", error);
- return undefined;
+ throw new Error(
+ `Error creating pool instance: ${(error as Error).message}`,
+ );
}
}
diff --git a/src/utils/getPoolKeyFromPoolId.ts b/src/utils/getPoolKeyFromPoolId.ts
index 294543f..f1123d5 100644
--- a/src/utils/getPoolKeyFromPoolId.ts
+++ b/src/utils/getPoolKeyFromPoolId.ts
@@ -1,5 +1,5 @@
import { V4PositionManagerAbi } from "@/constants/abis/V4PositionMananger";
-import { getInstance } from "@/core/uniDevKitV4Factory";
+import type { UniDevKitV4Instance } from "@/types/core";
import type {
GetPoolKeyFromPoolIdParams,
PoolKey,
@@ -7,21 +7,18 @@ import type {
/**
* Retrieves the pool key information for a given pool ID.
- * @param params Parameters containing the pool ID and optional chain ID
+ * @param params Parameters containing the pool ID
* @returns Promise resolving to the pool key containing currency0, currency1, fee, tickSpacing, and hooks
* @throws Error if SDK instance is not found
*/
-export async function getPoolKeyFromPoolId({
- poolId,
- chainId,
-}: GetPoolKeyFromPoolIdParams): Promise {
- const sdk = getInstance(chainId);
- if (!sdk) throw new Error("SDK not initialized");
+export async function getPoolKeyFromPoolId(
+ params: GetPoolKeyFromPoolIdParams,
+ instance: UniDevKitV4Instance,
+): Promise {
+ const { client, contracts } = instance;
+ const { positionManager } = contracts;
- const client = sdk.getClient();
- const positionManager = sdk.getContractAddress("positionManager");
-
- const poolId25Bytes = `0x${poolId.slice(2, 52)}` as `0x${string}`;
+ const poolId25Bytes = `0x${params.poolId.slice(2, 52)}` as `0x${string}`;
const [currency0, currency1, fee, tickSpacing, hooks] =
await client.readContract({
diff --git a/src/utils/getPosition.ts b/src/utils/getPosition.ts
index 5a5324b..dc3554d 100644
--- a/src/utils/getPosition.ts
+++ b/src/utils/getPosition.ts
@@ -1,7 +1,7 @@
import { V4PositionManagerAbi } from "@/constants/abis/V4PositionMananger";
import { V4StateViewAbi } from "@/constants/abis/V4StateView";
-import { getInstance } from "@/core/uniDevKitV4Factory";
import { decodePositionInfo } from "@/helpers/positions";
+import type { UniDevKitV4Instance } from "@/types/core";
import type {
GetPositionParams,
GetPositionResponse,
@@ -12,22 +12,17 @@ import { Pool, Position as V4Position } from "@uniswap/v4-sdk";
/**
* Retrieves a Uniswap V4 position instance for a given token ID.
* @param params Position parameters including token ID
- * @param chainId Optional chain ID where the position exists
+ * @param instance UniDevKitV4Instance
* @returns Promise resolving to position data
* @throws Error if SDK instance is not found or if position data is invalid
*/
export async function getPosition(
params: GetPositionParams,
- chainId?: number,
+ instance: UniDevKitV4Instance,
): Promise {
- const sdk = getInstance(chainId);
- if (!sdk) {
- throw new Error("SDK not found. Please create an instance first.");
- }
+ const { client, contracts } = instance;
- const client = sdk.getClient();
- const positionManager = sdk.getContractAddress("positionManager");
- const stateView = sdk.getContractAddress("stateView");
+ const { positionManager, stateView } = contracts;
// Fetch poolKey and raw position info
const [poolAndPositionInfo, liquidity] = await client.multicall({
@@ -55,10 +50,12 @@ export async function getPosition(
throw new Error("Liquidity is 0");
}
- const tokens = await getTokens({
- addresses: [currency0, currency1],
- chainId,
- });
+ const tokens = await getTokens(
+ {
+ addresses: [currency0, currency1],
+ },
+ instance,
+ );
if (!tokens) {
throw new Error("Tokens not found");
diff --git a/src/utils/getQuote.ts b/src/utils/getQuote.ts
index ad3daec..e4a3dac 100644
--- a/src/utils/getQuote.ts
+++ b/src/utils/getQuote.ts
@@ -1,6 +1,6 @@
import { V4QuoterAbi } from "@/constants/abis/V4Quoter";
-import { getInstance } from "@/core/uniDevKitV4Factory";
import { sortTokens } from "@/helpers/tokens";
+import type { UniDevKitV4Instance } from "@/types/core";
import { FeeTier, TICK_SPACING_BY_FEE } from "@/types/utils/getPool";
import type { QuoteParams, QuoteResponse } from "@/types/utils/getQuote";
import { zeroAddress } from "viem";
@@ -20,14 +20,10 @@ import { zeroAddress } from "viem";
*/
export async function getQuote(
params: QuoteParams,
- chainId?: number,
+ instance: UniDevKitV4Instance,
): Promise {
- const sdk = getInstance(chainId);
- if (!sdk) {
- throw new Error("SDK not found. Please create an instance first.");
- }
- const client = sdk.getClient();
- const quoterAddress = sdk.getContractAddress("quoter");
+ const { client, contracts } = instance;
+ const { quoter } = contracts;
try {
// Sort tokens to ensure consistent pool key ordering
@@ -59,7 +55,7 @@ export async function getQuote(
// Simulate the quote to estimate the amount out
const simulation = await client.simulateContract({
- address: quoterAddress,
+ address: quoter,
abi: V4QuoterAbi,
functionName: "quoteExactInputSingle",
args: [quoteParams],
diff --git a/src/utils/getTokens.ts b/src/utils/getTokens.ts
index 5312f7b..b94b143 100644
--- a/src/utils/getTokens.ts
+++ b/src/utils/getTokens.ts
@@ -1,32 +1,21 @@
-import { getChainById } from "@/constants/chains";
-import { getInstance } from "@/core/uniDevKitV4Factory";
+import type { UniDevKitV4Instance } from "@/types/core";
+import type { GetTokensParams } from "@/types/utils/getTokens";
import { Token } from "@uniswap/sdk-core";
-import { type Address, erc20Abi, zeroAddress } from "viem";
+import { erc20Abi, zeroAddress } from "viem";
/**
* Retrieves Token instances for a list of token addresses on a specific chain.
- * @param {Object} params - The parameters object
- * @param {[Address, ...Address[]]} params.addresses - Array of token contract addresses (at least one)
- * @param {number} params.chainId - The chain ID where the tokens exist (optional)
- * @returns {Promise} Array of Token instances or null if the operation fails
- * @throws {Error} If SDK instance is not found
+ * @param params Parameters including token addresses
+ * @param instance UniDevKitV4Instance
+ * @returns Promise resolving to array of Token instances
+ * @throws Error if token data cannot be fetched
*/
-export async function getTokens({
- addresses,
- chainId,
-}: {
- addresses: [Address, ...Address[]];
- chainId?: number;
-}): Promise {
- const sdk = getInstance(chainId);
- const currentChainId = chainId || sdk.getChainId();
- const chain = getChainById(currentChainId);
-
- if (!sdk) {
- throw new Error("SDK not found. Please create an instance first.");
- }
-
- const client = sdk.getClient();
+export async function getTokens(
+ params: GetTokensParams,
+ instance: UniDevKitV4Instance,
+): Promise {
+ const { addresses } = params;
+ const { client, chain } = instance;
const calls = addresses
.filter((address) => address !== zeroAddress) // filter out native currency
@@ -51,7 +40,7 @@ export async function getTokens({
const nativeCurrency = chain.nativeCurrency;
tokens.push(
new Token(
- currentChainId,
+ chain.id,
address,
nativeCurrency.decimals,
nativeCurrency.symbol,
@@ -63,13 +52,12 @@ export async function getTokens({
const symbol = results[resultIndex++] as string;
const name = results[resultIndex++] as string;
const decimals = results[resultIndex++] as number;
- tokens.push(new Token(currentChainId, address, decimals, symbol, name));
+ tokens.push(new Token(chain.id, address, decimals, symbol, name));
}
}
return tokens;
} catch (err) {
- console.error("getTokens failed:", err);
- return null;
+ throw new Error(`Failed to fetch token data: ${(err as Error).message}`);
}
}