@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,48 @@
1
+ import { type QueryObserverOptions, useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { PoolKey } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export type UsePoolsProps = {
6
+ /**
7
+ * Optional react-query QueryObserverOptions for customizing query behavior such as
8
+ * staleTime, refetchInterval, cacheTime, etc. These are merged with sensible defaults.
9
+ */
10
+ queryOptions?: QueryObserverOptions<PoolKey[], Error>;
11
+ };
12
+
13
+ /**
14
+ * Loads and caches the available list of pools from the DEX service's ConcentratedLiquidityService.
15
+ *
16
+ * By default, the query result is cached indefinitely (with `staleTime` set to Infinity), reflecting the
17
+ * assumption that the pools list is mostly static.
18
+ *
19
+ * @param params
20
+ * Optional configuration object:
21
+ * - queryOptions: Partial QueryObserverOptions for react-query (merged with built-in defaults).
22
+ *
23
+ * @returns
24
+ * A UseQueryResult object from @tanstack/react-query containing:
25
+ * - `data`: Array of PoolKey objects or undefined if not loaded or errored.
26
+ * - Status fields: `isLoading`, `isError`, `error`, etc.
27
+ *
28
+ * @example
29
+ * const { data: pools, isLoading, error } = usePools();
30
+ * if (isLoading) return <div>Loading pools...</div>;
31
+ * if (error) return <div>Error: {error.message}</div>;
32
+ * if (pools) pools.forEach((pool, idx) => console.log(pool.id, pool.fee));
33
+ */
34
+ export function usePools(params?: UsePoolsProps): UseQueryResult<PoolKey[], Error> {
35
+ const { sodax } = useSodaxContext();
36
+ const defaultQueryOptions = {
37
+ queryKey: ['dex', 'pools'],
38
+ staleTime: Number.POSITIVE_INFINITY, // Pools list is static, cache indefinitely
39
+ };
40
+ const queryOptions = { ...defaultQueryOptions, ...params?.queryOptions };
41
+
42
+ return useQuery({
43
+ ...queryOptions,
44
+ queryFn: async (): Promise<PoolKey[]> => {
45
+ return sodax.dex.clService.getPools();
46
+ },
47
+ });
48
+ }
@@ -0,0 +1,88 @@
1
+ import { type QueryObserverOptions, useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { ClPositionInfo, PoolKey } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+
5
+ export interface UsePositionInfoResponse {
6
+ positionInfo: ClPositionInfo;
7
+ isValid: boolean;
8
+ }
9
+
10
+ export interface UsePositionInfoProps {
11
+ tokenId: string | null;
12
+ poolKey: PoolKey | null;
13
+ queryOptions?: QueryObserverOptions<UsePositionInfoResponse, Error>;
14
+ }
15
+
16
+ /**
17
+ * React hook to fetch and validate CL position details by position NFT token ID.
18
+ *
19
+ * Fetches position data on-chain for a given tokenId, and checks if it matches the expected PoolKey.
20
+ * This is commonly used in DEX dashboards to show or pre-validate user positions by ID.
21
+ *
22
+ * @param {string | null} tokenId
23
+ * Position NFT token ID to query, as a string. Pass `null` or empty string to disable.
24
+ * @param {PoolKey | null} poolKey
25
+ * PoolKey to match against the position's underlying pool. Pass `null` to disable.
26
+ * @param {QueryObserverOptions<UsePositionInfoResponse, Error>} [queryOptions]
27
+ * Optional react-query options for polling/refresh and config. Merged with sensible defaults.
28
+ *
29
+ * @returns {UseQueryResult<UsePositionInfoResponse, Error>}
30
+ * Standard React Query result object:
31
+ * - `data`: { positionInfo, isValid } if loaded, or undefined if not loaded/error
32
+ * - `isLoading`: boolean (query active)
33
+ * - `isError`: boolean (query failed)
34
+ * - ...other react-query helpers (refetch, status, etc)
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const { data, isLoading, error } = usePositionInfo({ tokenId, poolKey });
39
+ * if (isLoading) return <div>Loading position...</div>;
40
+ * if (error) return <div>Error: {error.message}</div>;
41
+ * if (data) {
42
+ * console.log('Valid for pool:', data.isValid);
43
+ * console.log('Liquidity:', data.positionInfo.liquidity);
44
+ * }
45
+ * ```
46
+ *
47
+ * @remarks
48
+ * - Validates the underlying position's pool definition (currency0, currency1, fee) with the supplied PoolKey.
49
+ * - Returns `isValid: false` if any field mismatches.
50
+ * - Pass `null` as tokenId or poolKey to disable the query.
51
+ * - Defaults: 10s stale, not enabled if missing arguments. Customizable via `queryOptions`.
52
+ * - Throws error if called with invalid/null tokenId or poolKey when enabled.
53
+ */
54
+ export function usePositionInfo({
55
+ tokenId,
56
+ poolKey,
57
+ queryOptions = {
58
+ queryKey: ['dex', 'positionInfo', tokenId, poolKey],
59
+ enabled: tokenId !== null && poolKey !== null && tokenId !== '',
60
+ staleTime: 10000, // Consider data stale after 10 seconds
61
+ },
62
+ }: UsePositionInfoProps): UseQueryResult<UsePositionInfoResponse, Error> {
63
+ const { sodax } = useSodaxContext();
64
+
65
+ return useQuery({
66
+ queryFn: async () => {
67
+ if (!tokenId || !poolKey) {
68
+ throw new Error('Token ID and pool key are required');
69
+ }
70
+
71
+ const tokenIdBigInt = BigInt(tokenId);
72
+ const publicClient = sodax.hubProvider.publicClient;
73
+ const info = await sodax.dex.clService.getPositionInfo(tokenIdBigInt, publicClient);
74
+
75
+ // Validate that position belongs to current pool
76
+ const isValid =
77
+ info.poolKey.currency0.toLowerCase() === poolKey.currency0.toLowerCase() &&
78
+ info.poolKey.currency1.toLowerCase() === poolKey.currency1.toLowerCase() &&
79
+ info.poolKey.fee === poolKey.fee;
80
+
81
+ return {
82
+ positionInfo: info,
83
+ isValid,
84
+ };
85
+ },
86
+ ...queryOptions,
87
+ });
88
+ }
@@ -0,0 +1,91 @@
1
+ import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
2
+ import type { HubTxHash, SpokeTxHash, SpokeProvider } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext';
4
+ import type { UseCreateSupplyLiquidityParamsResult } from './useCreateSupplyLiquidityParams';
5
+
6
+ export type UseSupplyLiquidityProps = {
7
+ params: UseCreateSupplyLiquidityParamsResult;
8
+ spokeProvider: SpokeProvider;
9
+ };
10
+
11
+ /**
12
+ * Hook for supplying liquidity to a pool.
13
+ *
14
+ * This hook handles both minting new positions and increasing liquidity in existing positions.
15
+ * It applies slippage tolerance before calculating liquidity and handles the complete transaction flow.
16
+ *
17
+ * @param {SpokeProvider} spokeProvider - The spoke provider for the chain
18
+ * @returns {UseMutationResult<void, Error, SupplyLiquidityParams>} Mutation result with supply function
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const { mutateAsync: supplyLiquidity, isPending, error } = useSupplyLiquidity(spokeProvider);
23
+ *
24
+ * await supplyLiquidity({
25
+ * poolData,
26
+ * poolKey,
27
+ * minPrice: '100',
28
+ * maxPrice: '200',
29
+ * liquidityToken0Amount: '10',
30
+ * liquidityToken1Amount: '20',
31
+ * slippageTolerance: '0.5',
32
+ * });
33
+ * ```
34
+ */
35
+ export function useSupplyLiquidity(): UseMutationResult<[SpokeTxHash, HubTxHash], Error, UseSupplyLiquidityProps> {
36
+ const { sodax } = useSodaxContext();
37
+ const queryClient = useQueryClient();
38
+
39
+ return useMutation({
40
+ mutationFn: async ({ params, spokeProvider }: UseSupplyLiquidityProps) => {
41
+ // Check if we're increasing an existing position or minting a new one
42
+ if (params.tokenId && params.isValidPosition) {
43
+ // Increase liquidity in existing position
44
+ const increaseResult = await sodax.dex.clService.increaseLiquidity({
45
+ params: {
46
+ poolKey: params.poolKey,
47
+ tokenId: BigInt(params.tokenId),
48
+ tickLower: params.tickLower,
49
+ tickUpper: params.tickUpper,
50
+ liquidity: params.liquidity,
51
+ amount0Max: params.amount0Max,
52
+ amount1Max: params.amount1Max,
53
+ sqrtPriceX96: params.sqrtPriceX96,
54
+ },
55
+ spokeProvider,
56
+ });
57
+
58
+ if (!increaseResult.ok) {
59
+ throw new Error(`Increase liquidity failed: ${increaseResult.error?.code || 'Unknown error'}`);
60
+ }
61
+
62
+ return increaseResult.value;
63
+ }
64
+
65
+ // Mint new position
66
+ const supplyResult = await sodax.dex.clService.supplyLiquidity({
67
+ params: {
68
+ poolKey: params.poolKey,
69
+ tickLower: params.tickLower,
70
+ tickUpper: params.tickUpper,
71
+ liquidity: params.liquidity,
72
+ amount0Max: params.amount0Max,
73
+ amount1Max: params.amount1Max,
74
+ sqrtPriceX96: params.sqrtPriceX96,
75
+ },
76
+ spokeProvider,
77
+ });
78
+
79
+ if (!supplyResult.ok) {
80
+ throw new Error(`Supply liquidity failed: ${supplyResult.error?.code || 'Unknown error'}`);
81
+ }
82
+
83
+ return supplyResult.value;
84
+ },
85
+ onSuccess: () => {
86
+ // Invalidate relevant queries
87
+ queryClient.invalidateQueries({ queryKey: ['dex', 'poolBalances'] });
88
+ queryClient.invalidateQueries({ queryKey: ['dex', 'positionInfo'] });
89
+ },
90
+ });
91
+ }
@@ -7,3 +7,4 @@ export * from './backend';
7
7
  export * from './bridge';
8
8
  export * from './staking';
9
9
  export * from './migrate';
10
+ export * from './dex';
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './hooks';
2
2
  export * from './providers';
3
+ export * from './utils';
@@ -0,0 +1,177 @@
1
+ import type {
2
+ UseCreateDecreaseLiquidityParamsProps,
3
+ UseCreateDepositParamsProps,
4
+ UseCreateSupplyLiquidityParamsProps,
5
+ UseCreateSupplyLiquidityParamsResult,
6
+ UseCreateWithdrawParamsProps,
7
+ } from '@/hooks/dex';
8
+ import {
9
+ ClService,
10
+ type CreateAssetWithdrawParams,
11
+ type ConcentratedLiquidityDecreaseLiquidityParams,
12
+ type CreateAssetDepositParams,
13
+ } from '@sodax/sdk';
14
+ import { parseUnits } from 'viem';
15
+
16
+ export function createDecreaseLiquidityParamsProps({
17
+ poolKey,
18
+ tokenId,
19
+ percentage,
20
+ positionInfo,
21
+ slippageTolerance,
22
+ }: UseCreateDecreaseLiquidityParamsProps): ConcentratedLiquidityDecreaseLiquidityParams {
23
+ const percentageNum = Number.parseFloat(String(percentage));
24
+ const slippage = Number.parseFloat(String(slippageTolerance));
25
+
26
+ if (percentageNum <= 0 || percentageNum > 100) {
27
+ throw new Error('Percentage must be between 0 and 100');
28
+ }
29
+
30
+ if (slippage <= 0 || slippage > 100) {
31
+ throw new Error('Slippage must be between 0 and 100');
32
+ }
33
+
34
+ // Calculate liquidity to remove based on percentage
35
+ const liquidityToRemove =
36
+ percentageNum === 100
37
+ ? positionInfo.liquidity
38
+ : (positionInfo.liquidity * BigInt(Math.floor(percentageNum * 100))) / 10000n;
39
+
40
+ // Calculate expected token amounts from this liquidity
41
+ const expectedAmount0 =
42
+ percentageNum === 100
43
+ ? positionInfo.amount0
44
+ : (positionInfo.amount0 * BigInt(Math.floor(percentageNum * 100))) / 10000n;
45
+ const expectedAmount1 =
46
+ percentageNum === 100
47
+ ? positionInfo.amount1
48
+ : (positionInfo.amount1 * BigInt(Math.floor(percentageNum * 100))) / 10000n;
49
+
50
+ // Apply slippage to minimum amounts
51
+ const slippageMultiplier = BigInt(Math.floor((100 - slippage) * 100));
52
+ const amount0Min = (expectedAmount0 * slippageMultiplier) / 10000n;
53
+ const amount1Min = (expectedAmount1 * slippageMultiplier) / 10000n;
54
+
55
+ return {
56
+ poolKey,
57
+ tokenId: BigInt(tokenId),
58
+ liquidity: liquidityToRemove,
59
+ amount0Min,
60
+ amount1Min,
61
+ };
62
+ }
63
+
64
+ export function createDepositParamsProps({
65
+ tokenIndex,
66
+ amount,
67
+ poolData,
68
+ poolSpokeAssets,
69
+ }: UseCreateDepositParamsProps): CreateAssetDepositParams {
70
+ const amountNum = Number.parseFloat(String(amount));
71
+
72
+ if (!amount || amountNum <= 0) {
73
+ throw new Error('Amount must be greater than 0');
74
+ }
75
+
76
+ const token = tokenIndex === 0 ? poolData.token0 : poolData.token1;
77
+ const originalAsset = tokenIndex === 0 ? poolSpokeAssets.token0 : poolSpokeAssets.token1;
78
+
79
+ return {
80
+ asset: originalAsset.address,
81
+ amount: parseUnits(String(amount), token.decimals),
82
+ poolToken: token.address,
83
+ };
84
+ }
85
+
86
+ export function createSupplyLiquidityParamsProps({
87
+ poolData,
88
+ poolKey,
89
+ minPrice,
90
+ maxPrice,
91
+ liquidityToken0Amount,
92
+ liquidityToken1Amount,
93
+ slippageTolerance,
94
+ positionId,
95
+ isValidPosition,
96
+ }: UseCreateSupplyLiquidityParamsProps): UseCreateSupplyLiquidityParamsResult {
97
+ const slippage = Number.parseFloat(String(slippageTolerance));
98
+ if (slippage <= 0 || slippage > 100) {
99
+ throw new Error('Slippage must be between 0 and 100');
100
+ }
101
+
102
+ const minPriceNum = Number.parseFloat(minPrice);
103
+ const maxPriceNum = Number.parseFloat(maxPrice);
104
+ const amount0 = Number.parseFloat(liquidityToken0Amount);
105
+ const amount1 = Number.parseFloat(liquidityToken1Amount);
106
+
107
+ if (minPriceNum <= 0 || maxPriceNum <= 0 || amount0 <= 0 || amount1 <= 0) {
108
+ throw new Error('All values must be greater than 0');
109
+ }
110
+
111
+ if (minPriceNum >= maxPriceNum) {
112
+ throw new Error('Min price must be less than max price');
113
+ }
114
+
115
+ const amount0BigInt = parseUnits(liquidityToken0Amount, poolData.token0.decimals);
116
+ const amount1BigInt = parseUnits(liquidityToken1Amount, poolData.token1.decimals);
117
+
118
+ // Convert prices to ticks
119
+ const token0 = poolData.token0;
120
+ const token1 = poolData.token1;
121
+ const tickSpacing = poolData.tickSpacing;
122
+
123
+ const tickLower = ClService.priceToTick(minPriceNum, token0, token1, tickSpacing);
124
+ const tickUpper = ClService.priceToTick(maxPriceNum, token0, token1, tickSpacing);
125
+
126
+ // Apply slippage BEFORE calculating liquidity
127
+ const slippageMultiplier = BigInt(Math.floor((100 - slippage) * 100)); // e.g., 0.5% => 9950
128
+
129
+ const amount0ForLiquidity = (amount0BigInt * slippageMultiplier) / 10000n;
130
+ const amount1ForLiquidity = (amount1BigInt * slippageMultiplier) / 10000n;
131
+
132
+ // Calculate liquidity based on reduced amounts (accounting for slippage)
133
+ const liquidity = ClService.calculateLiquidityFromAmounts(
134
+ amount0ForLiquidity,
135
+ amount1ForLiquidity,
136
+ tickLower,
137
+ tickUpper,
138
+ BigInt(poolData.currentTick),
139
+ );
140
+ const tokenId = positionId ? BigInt(positionId) : undefined;
141
+
142
+ return {
143
+ poolKey,
144
+ tickLower,
145
+ tickUpper,
146
+ liquidity,
147
+ amount0Max: amount0BigInt,
148
+ amount1Max: amount1BigInt,
149
+ sqrtPriceX96: poolData.sqrtPriceX96,
150
+ positionId,
151
+ isValidPosition,
152
+ tokenId,
153
+ };
154
+ }
155
+
156
+ export function createWithdrawParamsProps({
157
+ tokenIndex,
158
+ amount,
159
+ poolData,
160
+ poolSpokeAssets,
161
+ dst,
162
+ }: UseCreateWithdrawParamsProps): CreateAssetWithdrawParams {
163
+ const amountNum = Number.parseFloat(String(amount));
164
+ if (!amount || amountNum <= 0) {
165
+ throw new Error('Please enter a valid amount');
166
+ }
167
+
168
+ const token = tokenIndex === 0 ? poolData.token0 : poolData.token1;
169
+ const originalAsset = tokenIndex === 0 ? poolSpokeAssets.token0 : poolSpokeAssets.token1;
170
+
171
+ return {
172
+ asset: originalAsset.address,
173
+ amount: parseUnits(String(amount), token.decimals),
174
+ poolToken: token.address,
175
+ dst,
176
+ };
177
+ }
@@ -0,0 +1 @@
1
+ export * from './dex-utils';
@@ -1,2 +0,0 @@
1
- export const ACCESS_TOKEN_TTL = 10 * 60 * 1000; // 10 minutes
2
- export const REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days