@sodax/dapp-kit 1.3.1-beta-rc3 → 1.3.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +36 -0
  2. package/dist/index.d.mts +581 -11
  3. package/dist/index.d.ts +581 -11
  4. package/dist/index.js +691 -62
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +672 -62
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +3 -3
  9. package/src/hooks/bitcoin/index.ts +1 -0
  10. package/src/hooks/bitcoin/useRadfiAuth.ts +7 -25
  11. package/src/hooks/bitcoin/useRadfiSession.ts +25 -47
  12. package/src/hooks/bitcoin/useRadfiWithdraw.ts +85 -0
  13. package/src/hooks/bitcoin/useRenewUtxos.ts +1 -1
  14. package/src/hooks/dex/index.ts +16 -0
  15. package/src/hooks/dex/useClaimRewards.ts +68 -0
  16. package/src/hooks/dex/useCreateDecreaseLiquidityParams.ts +41 -0
  17. package/src/hooks/dex/useCreateDepositParams.ts +43 -0
  18. package/src/hooks/dex/useCreateSupplyLiquidityParams.ts +84 -0
  19. package/src/hooks/dex/useCreateWithdrawParams.ts +44 -0
  20. package/src/hooks/dex/useDecreaseLiquidity.ts +78 -0
  21. package/src/hooks/dex/useDexAllowance.ts +87 -0
  22. package/src/hooks/dex/useDexApprove.ts +55 -0
  23. package/src/hooks/dex/useDexDeposit.ts +64 -0
  24. package/src/hooks/dex/useDexWithdraw.ts +54 -0
  25. package/src/hooks/dex/useLiquidityAmounts.ts +187 -0
  26. package/src/hooks/dex/usePoolBalances.ts +90 -0
  27. package/src/hooks/dex/usePoolData.ts +57 -0
  28. package/src/hooks/dex/usePools.ts +48 -0
  29. package/src/hooks/dex/usePositionInfo.ts +88 -0
  30. package/src/hooks/dex/useSupplyLiquidity.ts +91 -0
  31. package/src/hooks/index.ts +1 -0
  32. package/src/index.ts +1 -0
  33. package/src/utils/dex-utils.ts +177 -0
  34. package/src/utils/index.ts +1 -0
  35. package/src/hooks/bitcoin/radfiConstants.ts +0 -2
@@ -0,0 +1,78 @@
1
+ import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
2
+ import type {
3
+ ConcentratedLiquidityDecreaseLiquidityParams,
4
+ HubTxHash,
5
+ SpokeProvider,
6
+ SpokeTxHash,
7
+ } from '@sodax/sdk';
8
+ import { useSodaxContext } from '../shared/useSodaxContext';
9
+
10
+ export type UseDecreaseLiquidityParams = {
11
+ params: ConcentratedLiquidityDecreaseLiquidityParams;
12
+ spokeProvider: SpokeProvider;
13
+ };
14
+
15
+ /**
16
+ * React hook that provides a mutation for decreasing liquidity in a concentrated liquidity position.
17
+ *
18
+ * This hook returns a mutation for removing liquidity from a position using the provided
19
+ * `ConcentratedLiquidityDecreaseLiquidityParams` and `SpokeProvider`. The mutation returns a tuple of
20
+ * the spoke transaction hash and the hub transaction hash upon success.
21
+ *
22
+ * @returns {UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDecreaseLiquidityParams>}
23
+ * React Query mutation result:
24
+ * - `mutateAsync({ params, spokeProvider })`: Triggers the decrease liquidity mutation.
25
+ * - On success, returns `[spokeTxHash, hubTxHash]`.
26
+ * - On failure, throws an error.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const { mutateAsync: decreaseLiquidity, isPending, error } = useDecreaseLiquidity();
31
+ *
32
+ * await decreaseLiquidity({
33
+ * params: {
34
+ * poolKey,
35
+ * tokenId: 123n,
36
+ * liquidity: 100000n,
37
+ * amount0Min: 0n,
38
+ * amount1Min: 0n,
39
+ * },
40
+ * spokeProvider,
41
+ * });
42
+ * ```
43
+ *
44
+ * @param {UseDecreaseLiquidityParams} variables
45
+ * - `params`: Parameters for the decrease liquidity operation, matching `ConcentratedLiquidityDecreaseLiquidityParams`.
46
+ * - `spokeProvider`: The provider instance for the target spoke chain.
47
+ *
48
+ * @remarks
49
+ * - After a successful liquidity decrease, the hook will invalidate DEX pool balances and position info queries.
50
+ */
51
+ export function useDecreaseLiquidity(): UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDecreaseLiquidityParams> {
52
+ const { sodax } = useSodaxContext();
53
+ const queryClient = useQueryClient();
54
+
55
+ return useMutation({
56
+ mutationFn: async ({ params, spokeProvider }: UseDecreaseLiquidityParams) => {
57
+ if (!spokeProvider) {
58
+ throw new Error('Spoke provider is required');
59
+ }
60
+
61
+ const decreaseResult = await sodax.dex.clService.decreaseLiquidity({
62
+ params,
63
+ spokeProvider,
64
+ });
65
+
66
+ if (!decreaseResult.ok) {
67
+ throw new Error(`Decrease liquidity failed: ${decreaseResult.error?.code || 'Unknown error'}`);
68
+ }
69
+
70
+ return decreaseResult.value;
71
+ },
72
+ onSuccess: () => {
73
+ // Invalidate relevant queries after successful liquidity decrease
74
+ queryClient.invalidateQueries({ queryKey: ['dex', 'poolBalances'] });
75
+ queryClient.invalidateQueries({ queryKey: ['dex', 'positionInfo'] });
76
+ },
77
+ });
78
+ }
@@ -0,0 +1,87 @@
1
+ import { type QueryObserverOptions, useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { SpokeProvider, CreateAssetDepositParams } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UseDexAllowanceProps = {
6
+ params: CreateAssetDepositParams | undefined;
7
+ spokeProvider: SpokeProvider | null;
8
+ enabled?: boolean;
9
+ queryOptions?: QueryObserverOptions<boolean, Error>;
10
+ };
11
+
12
+ /**
13
+ * Hook to check if the user has approved sufficient token allowance for DEX deposits.
14
+ *
15
+ * This hook automatically queries and tracks the allowance status, indicating whether
16
+ * the user has granted enough allowance to allow a specific deposit to the DEX. It leverages
17
+ * React Query for status, caching, and background refetching.
18
+ *
19
+ * @param {CreateAssetDepositParams | undefined} params
20
+ * The deposit parameters: asset address, poolToken, and raw amount (BigInt), or undefined to disable.
21
+ * @param {SpokeProvider | undefined} spokeProvider
22
+ * The provider interface for the selected chain. When undefined, the query is disabled.
23
+ * @param {boolean} [enabled]
24
+ * Whether the allowance status check is enabled. Defaults to true if both params and spokeProvider are truthy.
25
+ * @param {QueryObserverOptions<boolean, Error>} [queryOptions]
26
+ * Optional react-query options. Any override here (e.g. staleTime, refetchInterval) will merge with defaults.
27
+ *
28
+ * @returns {UseQueryResult<boolean, Error>}
29
+ * React Query result object: `data` is boolean (true if allowance is sufficient), plus `isLoading`, `error`, etc.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const { data: isAllowed, isLoading, error } = useDexAllowance({
34
+ * params: { asset, amount: parseUnits('100', 18), poolToken },
35
+ * spokeProvider,
36
+ * });
37
+ * if (isLoading) return <Spinner />;
38
+ * if (error) return <div>Error: {error.message}</div>;
39
+ * if (isAllowed) { ... }
40
+ * ```
41
+ *
42
+ * @remarks
43
+ * - The allowance is checked every 5 seconds as long as enabled, params, and spokeProvider are all defined.
44
+ * - Returns `false` if allowance cannot be determined or any error occurs in isAllowanceValid.
45
+ * - Suitable for gating UI actions that require token approval before depositing in the DEX.
46
+ */
47
+ export function useDexAllowance({
48
+ params,
49
+ spokeProvider,
50
+ queryOptions = {
51
+ queryKey: [
52
+ 'dex',
53
+ 'allowance',
54
+ params?.asset,
55
+ params?.poolToken,
56
+ params?.amount.toString(),
57
+ spokeProvider?.chainConfig.chain.id,
58
+ ],
59
+ enabled: !!params && !!spokeProvider,
60
+ },
61
+ }: UseDexAllowanceProps): UseQueryResult<boolean, Error> {
62
+ const { sodax } = useSodaxContext();
63
+
64
+ return useQuery({
65
+ ...queryOptions,
66
+ queryFn: async () => {
67
+ if (!params || !spokeProvider) {
68
+ throw new Error('Params and spoke provider are required');
69
+ }
70
+
71
+ const allowanceResult = await sodax.dex.assetService.isAllowanceValid({
72
+ params: {
73
+ asset: params.asset,
74
+ amount: params.amount,
75
+ poolToken: params.poolToken,
76
+ },
77
+ spokeProvider,
78
+ });
79
+
80
+ if (!allowanceResult.ok) {
81
+ return false;
82
+ }
83
+
84
+ return allowanceResult.value;
85
+ },
86
+ });
87
+ }
@@ -0,0 +1,55 @@
1
+ import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
2
+ import type { SpokeProvider, CreateAssetDepositParams, SpokeTxHash } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UseDexApproveParams = {
6
+ params: CreateAssetDepositParams;
7
+ spokeProvider: SpokeProvider;
8
+ };
9
+
10
+ /**
11
+ * React hook for performing a DEX token allowance approval transaction.
12
+ *
13
+ * Returns a mutation object that allows explicitly triggering a token approval
14
+ * for a DEX deposit, using the specified approval parameters and spoke provider.
15
+ * On successful approval, the related allowance query is invalidated and refetched
16
+ * for consistent UI state.
17
+ *
18
+ * @returns {UseMutationResult<SpokeTxHash, Error, UseDexApproveParams>}
19
+ * React Query mutation result for the approval operation. Use `mutateAsync` with
20
+ * an object of shape `{ params, spokeProvider }` to initiate approval.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const { mutateAsync: approve, isPending, error } = useDexApprove();
25
+ * await approve({ params: { asset, amount, poolToken }, spokeProvider });
26
+ * ```
27
+ *
28
+ * @remarks
29
+ * - Throws if called without both a valid `params` and `spokeProvider`.
30
+ * - On approval success, the query for ['dex', 'allowance'] is invalidated/refetched.
31
+ */
32
+ export function useDexApprove(): UseMutationResult<SpokeTxHash, Error, UseDexApproveParams> {
33
+ const { sodax } = useSodaxContext();
34
+ const queryClient = useQueryClient();
35
+
36
+ return useMutation({
37
+ mutationFn: async ({ params, spokeProvider }: UseDexApproveParams) => {
38
+ const approveResult = await sodax.dex.assetService.approve({
39
+ params,
40
+ spokeProvider,
41
+ raw: false,
42
+ });
43
+
44
+ if (!approveResult.ok) {
45
+ throw new Error('Approval failed');
46
+ }
47
+
48
+ return approveResult.value;
49
+ },
50
+ onSuccess: () => {
51
+ // Invalidate allowance query to refetch the new allowance
52
+ queryClient.invalidateQueries({ queryKey: ['dex', 'allowance'] });
53
+ },
54
+ });
55
+ }
@@ -0,0 +1,64 @@
1
+ import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
2
+ import type { SpokeProvider, CreateAssetDepositParams, SpokeTxHash, HubTxHash } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UseDexDepositParams = {
6
+ params: CreateAssetDepositParams;
7
+ spokeProvider: SpokeProvider;
8
+ };
9
+
10
+ /**
11
+ /**
12
+ * React hook that provides a mutation to perform a deposit into a DEX pool using the provided parameters and SpokeProvider.
13
+ *
14
+ * The hook returns a mutation object for executing the deposit (`mutateAsync`), tracking its state (`isPending`), and any resulting error (`error`).
15
+ * On successful deposit, all queries matching ['dex', 'poolBalances'] are invalidated and refetched.
16
+ *
17
+ * @returns {UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDexDepositParams>}
18
+ * React Query mutation result:
19
+ * - `mutateAsync({ params, spokeProvider })`: Triggers the deposit with {@link CreateDepositParams} and the target SpokeProvider.
20
+ * - `isPending`: True while the deposit transaction is pending.
21
+ * - `error`: Error if the mutation fails.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const { mutateAsync: deposit, isPending, error } = useDexDeposit();
26
+ * await deposit({ params: { asset, amount, poolToken }, spokeProvider });
27
+ * ```
28
+ *
29
+ * @remarks
30
+ * - Throws if called with missing `spokeProvider` or `params`.
31
+ * - Upon success, automatically refetches up-to-date pool balances.
32
+ */
33
+ export function useDexDeposit(): UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDexDepositParams> {
34
+ const { sodax } = useSodaxContext();
35
+ const queryClient = useQueryClient();
36
+
37
+ return useMutation({
38
+ mutationFn: async ({ params, spokeProvider }: UseDexDepositParams) => {
39
+ if (!spokeProvider) {
40
+ throw new Error('Spoke provider is required');
41
+ }
42
+
43
+ if (!params) {
44
+ throw new Error('Deposit params are required');
45
+ }
46
+
47
+ // Perform the deposit operation
48
+ const depositResult = await sodax.dex.assetService.deposit({
49
+ params,
50
+ spokeProvider,
51
+ });
52
+
53
+ if (!depositResult.ok) {
54
+ throw new Error(`Deposit failed: ${depositResult.error?.code || 'Unknown error'}`);
55
+ }
56
+
57
+ return depositResult.value;
58
+ },
59
+ onSuccess: () => {
60
+ // Refetch pool balances after a successful deposit
61
+ queryClient.invalidateQueries({ queryKey: ['dex', 'poolBalances'] });
62
+ },
63
+ });
64
+ }
@@ -0,0 +1,54 @@
1
+ import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
2
+ import type { SpokeProvider, SpokeTxHash, HubTxHash, CreateAssetWithdrawParams } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UseDexWithdrawParams = {
6
+ params: CreateAssetWithdrawParams;
7
+ spokeProvider: SpokeProvider;
8
+ };
9
+
10
+ /**
11
+ * React hook to provide a mutation for withdrawing assets from a DEX pool.
12
+ *
13
+ * This hook returns a mutation result object valid for use with React Query.
14
+ * The mutation function expects an object with the withdrawal parameters and a SpokeProvider,
15
+ * and triggers the withdrawal operation on the DEX. On success, it invalidates the relevant
16
+ * ['dex', 'poolBalances'] query to fetch the updated balances.
17
+ *
18
+ * @returns {UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDexWithdrawParams>}
19
+ * Mutation result object. Use its properties to:
20
+ * - Call `mutateAsync({ params, spokeProvider })` to perform the withdrawal.
21
+ * - Track progress with `isPending`.
22
+ * - Access any `error` encountered during the mutation.
23
+ *
24
+ * @example
25
+ * const { mutateAsync: withdraw, isPending, error } = useDexWithdraw();
26
+ * await withdraw({ params, spokeProvider });
27
+ */
28
+ export function useDexWithdraw(): UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseDexWithdrawParams> {
29
+ const { sodax } = useSodaxContext();
30
+ const queryClient = useQueryClient();
31
+
32
+ return useMutation({
33
+ mutationFn: async ({ params, spokeProvider }: UseDexWithdrawParams) => {
34
+ if (!spokeProvider) {
35
+ throw new Error('Spoke provider is required');
36
+ }
37
+ // Execute withdraw
38
+ const withdrawResult = await sodax.dex.assetService.withdraw({
39
+ params,
40
+ spokeProvider,
41
+ });
42
+
43
+ if (!withdrawResult.ok) {
44
+ throw new Error(`Withdraw failed: ${withdrawResult.error.code}`);
45
+ }
46
+
47
+ return withdrawResult.value;
48
+ },
49
+ onSuccess: () => {
50
+ // Invalidate balances query to refetch after withdraw
51
+ queryClient.invalidateQueries({ queryKey: ['dex', 'poolBalances'] });
52
+ },
53
+ });
54
+ }
@@ -0,0 +1,187 @@
1
+ import { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { ClService, type PoolData } from '@sodax/sdk';
3
+
4
+ interface UseLiquidityAmountsResult {
5
+ liquidityToken0Amount: string;
6
+ liquidityToken1Amount: string;
7
+ lastEditedToken: 'token0' | 'token1' | null;
8
+ setLiquidityToken0Amount: (value: string) => void;
9
+ setLiquidityToken1Amount: (value: string) => void;
10
+ handleToken0AmountChange: (value: string) => void;
11
+ handleToken1AmountChange: (value: string) => void;
12
+ }
13
+
14
+ /**
15
+ * Hook for calculating liquidity amounts based on price range.
16
+ *
17
+ * This hook manages the state and calculations for liquidity token amounts.
18
+ * It automatically calculates the corresponding token amount when one is changed,
19
+ * based on the current price range and pool data.
20
+ *
21
+ * @param {string} minPrice - Minimum price for the liquidity range
22
+ * @param {string} maxPrice - Maximum price for the liquidity range
23
+ * @param {PoolData | null} poolData - The pool data containing token information
24
+ * @returns {UseLiquidityAmountsResult} Object containing amounts, state, and handlers
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const {
29
+ * liquidityToken0Amount,
30
+ * liquidityToken1Amount,
31
+ * handleToken0AmountChange,
32
+ * handleToken1AmountChange,
33
+ * } = useLiquidityAmounts(minPrice, maxPrice, poolData);
34
+ *
35
+ * <Input
36
+ * value={liquidityToken0Amount}
37
+ * onChange={(e) => handleToken0AmountChange(e.target.value)}
38
+ * />
39
+ * ```
40
+ */
41
+ export function useLiquidityAmounts(
42
+ minPrice: string,
43
+ maxPrice: string,
44
+ poolData: PoolData | null,
45
+ ): UseLiquidityAmountsResult {
46
+ const [liquidityToken0Amount, setLiquidityToken0Amount] = useState<string>('');
47
+ const [liquidityToken1Amount, setLiquidityToken1Amount] = useState<string>('');
48
+ const [lastEditedToken, setLastEditedToken] = useState<'token0' | 'token1' | null>(null);
49
+
50
+ // Memoize parsed price values to avoid repeated parsing
51
+ const { minPriceNum, maxPriceNum, isValidPriceRange } = useMemo(() => {
52
+ const parsedMin = Number.parseFloat(minPrice);
53
+ const parsedMax = Number.parseFloat(maxPrice);
54
+ const isValid = parsedMin > 0 && parsedMax > 0 && parsedMin < parsedMax;
55
+
56
+ return {
57
+ minPriceNum: parsedMin,
58
+ maxPriceNum: parsedMax,
59
+ isValidPriceRange: isValid,
60
+ };
61
+ }, [minPrice, maxPrice]);
62
+
63
+ // Memoize tick calculations - these only depend on prices and pool data
64
+ const { tickLower, tickUpper, currentTick } = useMemo(() => {
65
+ if (!poolData || !isValidPriceRange) {
66
+ return {
67
+ tickLower: null,
68
+ tickUpper: null,
69
+ currentTick: null,
70
+ };
71
+ }
72
+
73
+ try {
74
+ const lower = ClService.priceToTick(minPriceNum, poolData.token0, poolData.token1, poolData.tickSpacing);
75
+ const upper = ClService.priceToTick(maxPriceNum, poolData.token0, poolData.token1, poolData.tickSpacing);
76
+
77
+ return {
78
+ tickLower: lower,
79
+ tickUpper: upper,
80
+ currentTick: BigInt(poolData.currentTick),
81
+ };
82
+ } catch (err) {
83
+ console.error('Failed to calculate ticks:', err);
84
+ return {
85
+ tickLower: null,
86
+ tickUpper: null,
87
+ currentTick: null,
88
+ };
89
+ }
90
+ }, [minPriceNum, maxPriceNum, poolData, isValidPriceRange]);
91
+
92
+ // Auto-calculate token1 amount when token0 amount changes
93
+ const handleToken0AmountChange = useCallback(
94
+ (value: string): void => {
95
+ setLiquidityToken0Amount(value);
96
+ setLastEditedToken('token0');
97
+
98
+ // Only calculate if we have all required values
99
+ if (!value || !poolData || !tickLower || !tickUpper || !currentTick) {
100
+ return;
101
+ }
102
+
103
+ const amount0 = Number.parseFloat(value);
104
+
105
+ if (amount0 <= 0 || !isValidPriceRange) {
106
+ return;
107
+ }
108
+
109
+ try {
110
+ const amount0BigInt = BigInt(Math.floor(amount0 * 10 ** poolData.token0.decimals));
111
+
112
+ const amount1BigInt = ClService.calculateAmount1FromAmount0(amount0BigInt, tickLower, tickUpper, currentTick);
113
+
114
+ const amount1 = Number(amount1BigInt) / 10 ** poolData.token1.decimals;
115
+ setLiquidityToken1Amount(amount1.toFixed(6));
116
+ } catch (err) {
117
+ console.error('Failed to calculate token1 amount:', err);
118
+ }
119
+ },
120
+ [poolData, tickLower, tickUpper, currentTick, isValidPriceRange],
121
+ );
122
+
123
+ // Auto-calculate token0 amount when token1 amount changes
124
+ const handleToken1AmountChange = useCallback(
125
+ (value: string): void => {
126
+ setLiquidityToken1Amount(value);
127
+ setLastEditedToken('token1');
128
+
129
+ // Only calculate if we have all required values
130
+ if (!value || !poolData || !tickLower || !tickUpper || !currentTick) {
131
+ return;
132
+ }
133
+
134
+ const amount1 = Number.parseFloat(value);
135
+
136
+ if (amount1 <= 0 || !isValidPriceRange) {
137
+ return;
138
+ }
139
+
140
+ try {
141
+ const amount1BigInt = BigInt(Math.floor(amount1 * 10 ** poolData.token1.decimals));
142
+
143
+ const amount0BigInt = ClService.calculateAmount0FromAmount1(amount1BigInt, tickLower, tickUpper, currentTick);
144
+
145
+ const amount0 = Number(amount0BigInt) / 10 ** poolData.token0.decimals;
146
+ setLiquidityToken0Amount(amount0.toFixed(6));
147
+ } catch (err) {
148
+ console.error('Failed to calculate token0 amount:', err);
149
+ }
150
+ },
151
+ [poolData, tickLower, tickUpper, currentTick, isValidPriceRange],
152
+ );
153
+
154
+ // Recalculate amounts when price range changes
155
+ useEffect(() => {
156
+ if (!poolData || !tickLower || !tickUpper || !lastEditedToken || !isValidPriceRange) {
157
+ return;
158
+ }
159
+
160
+ // Recalculate based on which token was last edited
161
+ if (lastEditedToken === 'token0' && liquidityToken0Amount) {
162
+ handleToken0AmountChange(liquidityToken0Amount);
163
+ } else if (lastEditedToken === 'token1' && liquidityToken1Amount) {
164
+ handleToken1AmountChange(liquidityToken1Amount);
165
+ }
166
+ }, [
167
+ poolData,
168
+ lastEditedToken,
169
+ liquidityToken0Amount,
170
+ liquidityToken1Amount,
171
+ handleToken0AmountChange,
172
+ handleToken1AmountChange,
173
+ tickLower,
174
+ tickUpper,
175
+ isValidPriceRange,
176
+ ]);
177
+
178
+ return {
179
+ liquidityToken0Amount,
180
+ liquidityToken1Amount,
181
+ lastEditedToken,
182
+ setLiquidityToken0Amount,
183
+ setLiquidityToken1Amount,
184
+ handleToken0AmountChange,
185
+ handleToken1AmountChange,
186
+ };
187
+ }
@@ -0,0 +1,90 @@
1
+ import { type QueryObserverOptions, useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { PoolData, PoolKey, SpokeProvider } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export interface UsePoolBalancesResponse {
6
+ token0Balance: bigint;
7
+ token1Balance: bigint;
8
+ }
9
+
10
+ export interface UsePoolBalancesProps {
11
+ poolData: PoolData | null;
12
+ poolKey: PoolKey | null;
13
+ spokeProvider: SpokeProvider | null;
14
+ enabled?: boolean;
15
+ queryOptions?: QueryObserverOptions<UsePoolBalancesResponse, Error>;
16
+ }
17
+
18
+ /**
19
+ * React hook to query a user's token balances for a DEX pool.
20
+ *
21
+ * Given the pool data (with token addresses), the pool's key, and a SpokeProvider,
22
+ * fetches the user's protocol balances for both token0 and token1 for the specified pool.
23
+ * Queries are auto-refreshed; advanced options can be customized via `queryOptions`.
24
+ *
25
+ * @param {UsePoolBalancesProps} props
26
+ * Object containing:
27
+ * - `poolData`: {PoolData | null} - Pool info (must include token0 and token1 addresses). Required unless disabling.
28
+ * - `poolKey`: {PoolKey | null} - Unique key for the DEX pool. Required unless disabling.
29
+ * - `spokeProvider`: {SpokeProvider | null} - Provider instance for the chain. Required unless disabling.
30
+ * - `enabled`: {boolean} (optional) - Whether to enable the query. Defaults to `true` if all other arguments are provided.
31
+ * - `queryOptions`: {QueryObserverOptions<UsePoolBalancesResponse, Error>} (optional) - Advanced react-query options.
32
+ *
33
+ * @returns {UseQueryResult<UsePoolBalancesResponse, Error>}
34
+ * React Query result object:
35
+ * - `data`: `{ token0Balance: bigint, token1Balance: bigint }` if loaded, undefined otherwise.
36
+ * - `isLoading`, `isError`, etc. for status handling.
37
+ *
38
+ * @remarks
39
+ * - Throws an error if any of `poolData`, `poolKey`, or `spokeProvider` is missing when enabled.
40
+ * - Suitable for tracking current protocol/wallet balances for both pool tokens.
41
+ * - The hook is designed for use within a React component tree that provides the Sodax context.
42
+ * - Data are automatically refreshed at the provided or default polling interval (default: refetch every 10s).
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const { data, isLoading } = usePoolBalances({ poolData, poolKey, spokeProvider });
47
+ * if (data) {
48
+ * console.log('Balances:', data.token0Balance, data.token1Balance);
49
+ * }
50
+ * ```
51
+ */
52
+ export function usePoolBalances({
53
+ poolData,
54
+ poolKey,
55
+ spokeProvider,
56
+ enabled = true,
57
+ queryOptions = {
58
+ queryKey: [
59
+ 'dex',
60
+ 'poolBalances',
61
+ poolData?.poolKey,
62
+ spokeProvider?.chainConfig.chain.id,
63
+ ],
64
+ enabled: enabled && poolData !== null && poolKey !== null && spokeProvider !== null,
65
+ staleTime: 5000, // Consider data stale after 5 seconds
66
+ refetchInterval: 10000, // Refetch every 10 seconds
67
+ },
68
+ }: UsePoolBalancesProps): UseQueryResult<UsePoolBalancesResponse, Error> {
69
+ const { sodax } = useSodaxContext();
70
+
71
+ return useQuery({
72
+ ...queryOptions,
73
+ queryFn: async () => {
74
+ if (!poolData || !spokeProvider || !poolKey) {
75
+ throw new Error('Pool data, pool key, and spoke provider are required');
76
+ }
77
+
78
+ // Get balances from AssetService
79
+ const [balance0, balance1] = await Promise.all([
80
+ sodax.dex.assetService.getDeposit(poolData.token0.address, spokeProvider),
81
+ sodax.dex.assetService.getDeposit(poolData.token1.address, spokeProvider),
82
+ ]);
83
+
84
+ return {
85
+ token0Balance: balance0,
86
+ token1Balance: balance1,
87
+ };
88
+ },
89
+ });
90
+ }
@@ -0,0 +1,57 @@
1
+ import { type QueryObserverOptions, useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { PoolData, PoolKey } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UsePoolDataProps = {
6
+ poolKey: PoolKey | null;
7
+ enabled?: boolean;
8
+ queryOptions?: QueryObserverOptions<PoolData, Error>;
9
+ };
10
+
11
+ /**
12
+ * React hook to fetch on-chain data for a given DEX pool.
13
+ *
14
+ * @param {UsePoolDataProps} props - The props object:
15
+ * - `poolKey`: PoolKey | null — The unique identifier for the pool to fetch data for. If null, disables the query.
16
+ * - `enabled`: boolean (optional) — Whether the query is enabled. Defaults to enabled if a poolKey is provided, otherwise false.
17
+ * - `queryOptions`: QueryObserverOptions<PoolData, Error> (optional) — Additional React Query options (e.g., staleTime, refetchInterval).
18
+ *
19
+ * @returns {UseQueryResult<PoolData, Error>} React Query result containing pool data (`data`), loading state (`isLoading`), error (`error`), and status fields.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const { data: poolData, isLoading, error } = usePoolData({ poolKey });
24
+ * if (isLoading) return <div>Loading…</div>;
25
+ * if (error) return <div>Error!</div>;
26
+ * if (poolData) {
27
+ * // poolData is available
28
+ * }
29
+ * ```
30
+ *
31
+ * @remarks
32
+ * - Refetches pool data every 30 seconds by default, and may be configured via `queryOptions`.
33
+ * - If `poolKey` is `null`, the query is disabled and no network request is performed.
34
+ * - Throws an error if `poolKey` is missing when the query is enabled.
35
+ */
36
+ export function usePoolData({
37
+ poolKey,
38
+ queryOptions = {
39
+ queryKey: ['dex', 'poolData', poolKey],
40
+ enabled: poolKey !== null,
41
+ staleTime: 10000,
42
+ refetchInterval: 30000,
43
+ },
44
+ }: UsePoolDataProps): UseQueryResult<PoolData, Error> {
45
+ const { sodax } = useSodaxContext();
46
+
47
+ return useQuery({
48
+ ...queryOptions,
49
+ queryFn: async (): Promise<PoolData> => {
50
+ if (!poolKey) {
51
+ throw new Error('Pool key is required');
52
+ }
53
+ return await sodax.dex.clService.getPoolData(poolKey, sodax.hubProvider.publicClient);
54
+ },
55
+ enabled: poolKey !== null,
56
+ });
57
+ }