@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.
- package/README.md +36 -0
- package/dist/index.d.mts +581 -11
- package/dist/index.d.ts +581 -11
- package/dist/index.js +691 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +672 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/hooks/bitcoin/index.ts +1 -0
- package/src/hooks/bitcoin/useRadfiAuth.ts +7 -25
- package/src/hooks/bitcoin/useRadfiSession.ts +25 -47
- package/src/hooks/bitcoin/useRadfiWithdraw.ts +85 -0
- package/src/hooks/bitcoin/useRenewUtxos.ts +1 -1
- package/src/hooks/dex/index.ts +16 -0
- package/src/hooks/dex/useClaimRewards.ts +68 -0
- package/src/hooks/dex/useCreateDecreaseLiquidityParams.ts +41 -0
- package/src/hooks/dex/useCreateDepositParams.ts +43 -0
- package/src/hooks/dex/useCreateSupplyLiquidityParams.ts +84 -0
- package/src/hooks/dex/useCreateWithdrawParams.ts +44 -0
- package/src/hooks/dex/useDecreaseLiquidity.ts +78 -0
- package/src/hooks/dex/useDexAllowance.ts +87 -0
- package/src/hooks/dex/useDexApprove.ts +55 -0
- package/src/hooks/dex/useDexDeposit.ts +64 -0
- package/src/hooks/dex/useDexWithdraw.ts +54 -0
- package/src/hooks/dex/useLiquidityAmounts.ts +187 -0
- package/src/hooks/dex/usePoolBalances.ts +90 -0
- package/src/hooks/dex/usePoolData.ts +57 -0
- package/src/hooks/dex/usePools.ts +48 -0
- package/src/hooks/dex/usePositionInfo.ts +88 -0
- package/src/hooks/dex/useSupplyLiquidity.ts +91 -0
- package/src/hooks/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/utils/dex-utils.ts +177 -0
- package/src/utils/index.ts +1 -0
- 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
|
+
}
|