@sodax/dapp-kit 2.0.0-rc.2 → 2.0.0-rc.20

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 (129) hide show
  1. package/README.md +29 -74
  2. package/dist/index.d.ts +780 -58
  3. package/dist/index.mjs +841 -143
  4. package/package.json +31 -19
  5. package/src/hooks/_mutationContract.test.ts +15 -2
  6. package/src/hooks/backend/index.ts +1 -5
  7. package/src/hooks/bitcoin/index.ts +2 -0
  8. package/src/hooks/bitcoin/resolveBtcReadAddress.test.ts +54 -0
  9. package/src/hooks/bitcoin/resolveBtcReadAddress.ts +21 -0
  10. package/src/hooks/bitcoin/useBitcoinTradingSetup.ts +45 -0
  11. package/src/hooks/bitcoin/useEnsureRadfiAccessToken.ts +57 -0
  12. package/src/hooks/bitcoin/useFundTradingWallet.ts +1 -1
  13. package/src/hooks/bitcoin/useRadfiAuth.ts +2 -2
  14. package/src/hooks/bitcoin/useRadfiWithdraw.ts +2 -2
  15. package/src/hooks/bitcoin/useRenewUtxos.ts +2 -2
  16. package/src/hooks/bitcoin/useTradingWallet.ts +1 -1
  17. package/src/hooks/index.ts +2 -0
  18. package/src/hooks/leverageYield/index.ts +9 -0
  19. package/src/hooks/leverageYield/useLeverageYieldDeposit.ts +40 -0
  20. package/src/hooks/leverageYield/useLeverageYieldEffectiveApr.ts +42 -0
  21. package/src/hooks/leverageYield/useLeverageYieldNotifySolver.ts +34 -0
  22. package/src/hooks/leverageYield/useLeverageYieldPosition.ts +40 -0
  23. package/src/hooks/leverageYield/useLeverageYieldPreviewRedeem.ts +43 -0
  24. package/src/hooks/leverageYield/useLeverageYieldShareBalances.ts +74 -0
  25. package/src/hooks/leverageYield/useLeverageYieldTotalAssets.ts +36 -0
  26. package/src/hooks/leverageYield/useLeverageYieldVaultSwap.ts +50 -0
  27. package/src/hooks/leverageYield/useLeverageYieldWithdraw.ts +40 -0
  28. package/src/hooks/mm/useATokensBalances.ts +7 -1
  29. package/src/hooks/mm/useUserFormattedSummary.ts +5 -1
  30. package/src/hooks/mm/useUserReservesData.ts +5 -1
  31. package/src/hooks/partner/index.ts +4 -0
  32. package/src/hooks/partner/useFeeClaimWithdraw.ts +88 -0
  33. package/src/hooks/partner/useGetIntentDetails.ts +39 -0
  34. package/src/hooks/partner/useGetUserIntent.ts +35 -0
  35. package/src/hooks/partner/usePartnerCancelIntent.ts +52 -0
  36. package/src/hooks/shared/index.ts +4 -0
  37. package/src/hooks/shared/isUserRejectedError.test.ts +42 -0
  38. package/src/hooks/shared/isUserRejectedError.ts +30 -0
  39. package/src/hooks/shared/useGetUserHubWalletAddress.ts +5 -1
  40. package/src/hooks/shared/useNearStorageCheck.ts +45 -0
  41. package/src/hooks/shared/useNearStorageGate.ts +60 -0
  42. package/src/hooks/shared/useRegisterNearStorage.ts +46 -0
  43. package/src/hooks/swapsApi/index.ts +41 -0
  44. package/src/hooks/swapsApi/isTerminalSwapIntentStatus.test.ts +24 -0
  45. package/src/hooks/swapsApi/isTerminalSwapIntentStatus.ts +11 -0
  46. package/src/hooks/swapsApi/useSwapsApiAllowance.ts +40 -0
  47. package/src/hooks/swapsApi/useSwapsApiApprove.ts +42 -0
  48. package/src/hooks/swapsApi/useSwapsApiCancelIntent.ts +41 -0
  49. package/src/hooks/swapsApi/useSwapsApiCreateIntent.ts +41 -0
  50. package/src/hooks/swapsApi/useSwapsApiCreateLimitOrder.ts +41 -0
  51. package/src/hooks/swapsApi/useSwapsApiDeadline.ts +36 -0
  52. package/src/hooks/swapsApi/useSwapsApiEstimateGas.ts +40 -0
  53. package/src/hooks/swapsApi/useSwapsApiFilledIntent.ts +41 -0
  54. package/src/hooks/swapsApi/useSwapsApiIntent.ts +40 -0
  55. package/src/hooks/swapsApi/useSwapsApiIntentExtraData.ts +41 -0
  56. package/src/hooks/swapsApi/useSwapsApiIntentHash.ts +40 -0
  57. package/src/hooks/swapsApi/useSwapsApiIntentPacket.ts +43 -0
  58. package/src/hooks/swapsApi/useSwapsApiPartnerFee.ts +40 -0
  59. package/src/hooks/swapsApi/useSwapsApiQuote.ts +55 -0
  60. package/src/hooks/swapsApi/useSwapsApiSolverFee.ts +40 -0
  61. package/src/hooks/swapsApi/useSwapsApiStatus.ts +45 -0
  62. package/src/hooks/swapsApi/useSwapsApiSubmitIntent.ts +43 -0
  63. package/src/hooks/{backend/useBackendSubmitSwapTx.ts → swapsApi/useSwapsApiSubmitTx.ts} +16 -17
  64. package/src/hooks/swapsApi/useSwapsApiSubmitTxStatus.ts +55 -0
  65. package/src/hooks/swapsApi/useSwapsApiTokens.ts +34 -0
  66. package/src/hooks/swapsApi/useSwapsApiTokensByChain.ts +40 -0
  67. package/src/providers/SodaxProvider.tsx +17 -14
  68. package/src/utils/index.ts +1 -0
  69. package/src/utils/nearStorageGate.test.ts +46 -0
  70. package/src/utils/nearStorageGate.ts +39 -0
  71. package/ai-exported/AGENTS.md +0 -134
  72. package/ai-exported/integration/README.md +0 -49
  73. package/ai-exported/integration/ai-rules.md +0 -79
  74. package/ai-exported/integration/architecture.md +0 -274
  75. package/ai-exported/integration/features/README.md +0 -29
  76. package/ai-exported/integration/features/auxiliary-services.md +0 -169
  77. package/ai-exported/integration/features/bitcoin.md +0 -87
  78. package/ai-exported/integration/features/bridge.md +0 -91
  79. package/ai-exported/integration/features/dex.md +0 -152
  80. package/ai-exported/integration/features/migration.md +0 -118
  81. package/ai-exported/integration/features/money-market.md +0 -116
  82. package/ai-exported/integration/features/staking.md +0 -123
  83. package/ai-exported/integration/features/swap.md +0 -101
  84. package/ai-exported/integration/quickstart.md +0 -187
  85. package/ai-exported/integration/recipes/README.md +0 -136
  86. package/ai-exported/integration/recipes/backend-queries.md +0 -157
  87. package/ai-exported/integration/recipes/bitcoin.md +0 -193
  88. package/ai-exported/integration/recipes/bridge.md +0 -174
  89. package/ai-exported/integration/recipes/dex.md +0 -204
  90. package/ai-exported/integration/recipes/invalidations.md +0 -115
  91. package/ai-exported/integration/recipes/migration.md +0 -212
  92. package/ai-exported/integration/recipes/money-market.md +0 -206
  93. package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
  94. package/ai-exported/integration/recipes/observability.md +0 -93
  95. package/ai-exported/integration/recipes/setup.md +0 -144
  96. package/ai-exported/integration/recipes/staking.md +0 -202
  97. package/ai-exported/integration/recipes/swap.md +0 -272
  98. package/ai-exported/integration/recipes/wallet-connectivity.md +0 -101
  99. package/ai-exported/integration/reference/README.md +0 -12
  100. package/ai-exported/integration/reference/glossary.md +0 -188
  101. package/ai-exported/integration/reference/hooks-index.md +0 -190
  102. package/ai-exported/integration/reference/public-api.md +0 -110
  103. package/ai-exported/integration/reference/querykey-conventions.md +0 -179
  104. package/ai-exported/migration/README.md +0 -60
  105. package/ai-exported/migration/ai-rules.md +0 -81
  106. package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
  107. package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
  108. package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
  109. package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -165
  110. package/ai-exported/migration/checklist.md +0 -89
  111. package/ai-exported/migration/features/README.md +0 -34
  112. package/ai-exported/migration/features/auxiliary-services.md +0 -114
  113. package/ai-exported/migration/features/bitcoin.md +0 -88
  114. package/ai-exported/migration/features/bridge.md +0 -123
  115. package/ai-exported/migration/features/dex.md +0 -101
  116. package/ai-exported/migration/features/migration.md +0 -120
  117. package/ai-exported/migration/features/money-market.md +0 -97
  118. package/ai-exported/migration/features/staking.md +0 -109
  119. package/ai-exported/migration/features/swap.md +0 -118
  120. package/ai-exported/migration/recipes.md +0 -188
  121. package/ai-exported/migration/reference/README.md +0 -15
  122. package/ai-exported/migration/reference/deleted-hooks.md +0 -110
  123. package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
  124. package/ai-exported/migration/reference/renamed-hooks.md +0 -66
  125. package/dist/index.cjs +0 -2642
  126. package/dist/index.cjs.map +0 -1
  127. package/dist/index.d.cts +0 -1550
  128. package/dist/index.mjs.map +0 -1
  129. package/src/hooks/backend/useBackendSubmitSwapTxStatus.ts +0 -59
@@ -0,0 +1,40 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { IntentHashResponseV2, IntentRequestV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiIntentHashParams = ReadHookParams<
8
+ IntentHashResponseV2 | undefined,
9
+ {
10
+ intent: IntentRequestV2 | undefined;
11
+ apiConfig?: RequestOverrideConfig;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * React hook to compute the keccak256 hash of an Intent struct via the swaps API —
17
+ * `sodax.api.swaps.getIntentHash`. The `intent` carries `bigint` numerics. Returns `{ hash }`.
18
+ *
19
+ * @example
20
+ * const { data } = useSwapsApiIntentHash({ params: { intent } });
21
+ */
22
+ export const useSwapsApiIntentHash = ({
23
+ params,
24
+ queryOptions,
25
+ }: UseSwapsApiIntentHashParams = {}): UseQueryResult<IntentHashResponseV2 | undefined, Error> => {
26
+ const { sodax } = useSodaxContext();
27
+ const intent = params?.intent;
28
+ const apiConfig = params?.apiConfig;
29
+
30
+ return useQuery({
31
+ queryKey: ['swapsApi', 'intentHash', intent?.intentId?.toString()],
32
+ queryFn: async (): Promise<IntentHashResponseV2 | undefined> => {
33
+ if (!intent) return undefined;
34
+ return unwrapResult(await sodax.api.swaps.getIntentHash({ intent }, apiConfig));
35
+ },
36
+ enabled: !!intent,
37
+ retry: 3,
38
+ ...queryOptions,
39
+ });
40
+ };
@@ -0,0 +1,43 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { IntentPacketRequestV2, IntentPacketResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiIntentPacketParams = ReadHookParams<
8
+ IntentPacketResponseV2 | undefined,
9
+ {
10
+ body: IntentPacketRequestV2 | undefined;
11
+ apiConfig?: RequestOverrideConfig;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * React hook to long-poll the relayer until the fill packet lands on the destination chain via the
17
+ * swaps API — `sodax.api.swaps.getSolvedIntentPacket`. The request is held open server-side until
18
+ * the packet arrives (or `body.timeout` ms elapses), so no client-side `refetchInterval` is set.
19
+ *
20
+ * @example
21
+ * const { data: packet } = useSwapsApiIntentPacket({
22
+ * params: { body: { chainId: '146', fillTxHash: '0x123...' } },
23
+ * });
24
+ */
25
+ export const useSwapsApiIntentPacket = ({
26
+ params,
27
+ queryOptions,
28
+ }: UseSwapsApiIntentPacketParams = {}): UseQueryResult<IntentPacketResponseV2 | undefined, Error> => {
29
+ const { sodax } = useSodaxContext();
30
+ const body = params?.body;
31
+ const apiConfig = params?.apiConfig;
32
+
33
+ return useQuery({
34
+ queryKey: ['swapsApi', 'intentPacket', body?.chainId, body?.fillTxHash],
35
+ queryFn: async (): Promise<IntentPacketResponseV2 | undefined> => {
36
+ if (!body) return undefined;
37
+ return unwrapResult(await sodax.api.swaps.getSolvedIntentPacket(body, apiConfig));
38
+ },
39
+ enabled: !!body,
40
+ retry: 3,
41
+ ...queryOptions,
42
+ });
43
+ };
@@ -0,0 +1,40 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { FeeResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiPartnerFeeParams = ReadHookParams<
8
+ FeeResponseV2 | undefined,
9
+ {
10
+ amount: string | undefined;
11
+ apiConfig?: RequestOverrideConfig;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * React hook to compute the partner fee for a given input amount via the swaps API —
17
+ * `sodax.api.swaps.getPartnerFee`. Returns `{ fee }` (decimal string).
18
+ *
19
+ * @example
20
+ * const { data } = useSwapsApiPartnerFee({ params: { amount: '1000000' } });
21
+ */
22
+ export const useSwapsApiPartnerFee = ({
23
+ params,
24
+ queryOptions,
25
+ }: UseSwapsApiPartnerFeeParams = {}): UseQueryResult<FeeResponseV2 | undefined, Error> => {
26
+ const { sodax } = useSodaxContext();
27
+ const amount = params?.amount;
28
+ const apiConfig = params?.apiConfig;
29
+
30
+ return useQuery({
31
+ queryKey: ['swapsApi', 'partnerFee', amount],
32
+ queryFn: async (): Promise<FeeResponseV2 | undefined> => {
33
+ if (!amount) return undefined;
34
+ return unwrapResult(await sodax.api.swaps.getPartnerFee({ amount }, apiConfig));
35
+ },
36
+ enabled: !!amount && amount.length > 0,
37
+ retry: 3,
38
+ ...queryOptions,
39
+ });
40
+ };
@@ -0,0 +1,55 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { QuoteQueryV2, QuoteRequestV2, QuoteResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiQuoteParams = ReadHookParams<
8
+ QuoteResponseV2 | undefined,
9
+ {
10
+ body: QuoteRequestV2 | undefined;
11
+ query?: QuoteQueryV2;
12
+ apiConfig?: RequestOverrideConfig;
13
+ }
14
+ >;
15
+
16
+ /**
17
+ * React hook to get a solver quote for a cross-chain swap via the swaps API —
18
+ * `sodax.api.swaps.getQuote`. Pass `query.includeTxData = true` to also build an unsigned
19
+ * create-intent transaction (`txData`); `srcAddress`/`dstAddress` are then required in the body.
20
+ *
21
+ * @example
22
+ * const { data: quote } = useSwapsApiQuote({
23
+ * params: {
24
+ * body: {
25
+ * tokenSrc: '0x...', tokenSrcChainKey: '0xa4b1.arbitrum',
26
+ * tokenDst: '0x...', tokenDstChainKey: 'sonic',
27
+ * amount: '1000000', quoteType: 'exact_input',
28
+ * },
29
+ * },
30
+ * });
31
+ */
32
+ export const useSwapsApiQuote = ({
33
+ params,
34
+ queryOptions,
35
+ }: UseSwapsApiQuoteParams = {}): UseQueryResult<QuoteResponseV2 | undefined, Error> => {
36
+ const { sodax } = useSodaxContext();
37
+ const body = params?.body;
38
+ const query = params?.query;
39
+ const apiConfig = params?.apiConfig;
40
+
41
+ return useQuery({
42
+ // Hash the whole request body so every quote input — including partnerFee and the other
43
+ // SwapExtrasV2 fields — is a cache dimension; a partnerFee change alters the quote and must
44
+ // miss the cache. QuoteRequestV2 is bigint-free (compile-time guaranteed), so React Query's
45
+ // default key hashing handles the object directly.
46
+ queryKey: ['swapsApi', 'quote', body, query?.includeTxData ?? false],
47
+ queryFn: async (): Promise<QuoteResponseV2 | undefined> => {
48
+ if (!body) return undefined;
49
+ return unwrapResult(await sodax.api.swaps.getQuote(body, query, apiConfig));
50
+ },
51
+ enabled: !!body,
52
+ retry: 3,
53
+ ...queryOptions,
54
+ });
55
+ };
@@ -0,0 +1,40 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { FeeResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiSolverFeeParams = ReadHookParams<
8
+ FeeResponseV2 | undefined,
9
+ {
10
+ amount: string | undefined;
11
+ apiConfig?: RequestOverrideConfig;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * React hook to compute the protocol (solver) fee for a given input amount via the swaps API —
17
+ * `sodax.api.swaps.getSolverFee`. Returns `{ fee }` (decimal string).
18
+ *
19
+ * @example
20
+ * const { data } = useSwapsApiSolverFee({ params: { amount: '1000000' } });
21
+ */
22
+ export const useSwapsApiSolverFee = ({
23
+ params,
24
+ queryOptions,
25
+ }: UseSwapsApiSolverFeeParams = {}): UseQueryResult<FeeResponseV2 | undefined, Error> => {
26
+ const { sodax } = useSodaxContext();
27
+ const amount = params?.amount;
28
+ const apiConfig = params?.apiConfig;
29
+
30
+ return useQuery({
31
+ queryKey: ['swapsApi', 'solverFee', amount],
32
+ queryFn: async (): Promise<FeeResponseV2 | undefined> => {
33
+ if (!amount) return undefined;
34
+ return unwrapResult(await sodax.api.swaps.getSolverFee({ amount }, apiConfig));
35
+ },
36
+ enabled: !!amount && amount.length > 0,
37
+ retry: 3,
38
+ ...queryOptions,
39
+ });
40
+ };
@@ -0,0 +1,45 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { RequestOverrideConfig, StatusResponseV2 } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+ import { isTerminalSwapIntentStatus } from './isTerminalSwapIntentStatus.js';
7
+
8
+ export type UseSwapsApiStatusParams = ReadHookParams<
9
+ StatusResponseV2 | undefined,
10
+ {
11
+ intentTxHash: string | undefined;
12
+ apiConfig?: RequestOverrideConfig;
13
+ }
14
+ >;
15
+
16
+ /**
17
+ * React hook for polling the solver intent status by hub-chain intent tx hash via the swaps API —
18
+ * `sodax.api.swaps.getStatus`. Returns `{ status, fillTxHash? }` (`fillTxHash` set when `status === 3`).
19
+ *
20
+ * @example
21
+ * const { data } = useSwapsApiStatus({ params: { intentTxHash: '0x123...' } });
22
+ *
23
+ * @remarks
24
+ * - Default refetch interval is 1 second; stops once `status` is `3` (SOLVED) or `4` (FAILED).
25
+ */
26
+ export const useSwapsApiStatus = ({
27
+ params,
28
+ queryOptions,
29
+ }: UseSwapsApiStatusParams = {}): UseQueryResult<StatusResponseV2 | undefined, Error> => {
30
+ const { sodax } = useSodaxContext();
31
+ const intentTxHash = params?.intentTxHash;
32
+ const apiConfig = params?.apiConfig;
33
+
34
+ return useQuery({
35
+ queryKey: ['swapsApi', 'status', intentTxHash],
36
+ queryFn: async (): Promise<StatusResponseV2 | undefined> => {
37
+ if (!intentTxHash) return undefined;
38
+ return unwrapResult(await sodax.api.swaps.getStatus({ intentTxHash }, apiConfig));
39
+ },
40
+ enabled: !!intentTxHash && intentTxHash.length > 0,
41
+ retry: 3,
42
+ refetchInterval: query => (isTerminalSwapIntentStatus(query.state.data?.status) ? false : 1000),
43
+ ...queryOptions,
44
+ });
45
+ };
@@ -0,0 +1,43 @@
1
+ import type { RequestOverrideConfig, SubmitIntentRequestV2, SubmitIntentResponseV2 } from '@sodax/sdk';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import { unwrapResult } from '../shared/unwrapResult.js';
4
+ import type { MutationHookParams } from '../shared/types.js';
5
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
6
+
7
+ /**
8
+ * Mutation variables for {@link useSwapsApiSubmitIntent}. The per-request `apiConfig` override
9
+ * belongs here rather than at the hook level.
10
+ */
11
+ export type UseSwapsApiSubmitIntentVars = {
12
+ body: SubmitIntentRequestV2;
13
+ apiConfig?: RequestOverrideConfig;
14
+ };
15
+
16
+ /**
17
+ * React hook to submit the broadcast intent tx to the relay via the swaps API —
18
+ * `sodax.api.swaps.submitIntent`. Returns `{ result }` (opaque relay response).
19
+ *
20
+ * No auto-retry (`retry: false`): submitting is non-idempotent — a retry after a lost response
21
+ * could double-submit to the relay. Override via `mutationOptions.retry` if your relay dedupes.
22
+ *
23
+ * @example
24
+ * const { mutateAsync: submitIntent } = useSwapsApiSubmitIntent();
25
+ * const { result } = await submitIntent({ body: { chainId: '146', txHash: '0x123...' } });
26
+ */
27
+ export const useSwapsApiSubmitIntent = ({
28
+ mutationOptions,
29
+ }: MutationHookParams<SubmitIntentResponseV2, UseSwapsApiSubmitIntentVars> = {}): SafeUseMutationResult<
30
+ SubmitIntentResponseV2,
31
+ Error,
32
+ UseSwapsApiSubmitIntentVars
33
+ > => {
34
+ const { sodax } = useSodaxContext();
35
+
36
+ return useSafeMutation<SubmitIntentResponseV2, Error, UseSwapsApiSubmitIntentVars>({
37
+ mutationKey: ['swapsApi', 'submitIntent'],
38
+ retry: false,
39
+ ...mutationOptions,
40
+ mutationFn: async ({ body, apiConfig }): Promise<SubmitIntentResponseV2> =>
41
+ unwrapResult(await sodax.api.swaps.submitIntent(body, apiConfig)),
42
+ });
43
+ };
@@ -1,49 +1,48 @@
1
- // packages/dapp-kit/src/hooks/backend/useBackendSubmitSwapTx.ts
2
- import type { RequestOverrideConfig, SubmitSwapTxRequest, SubmitSwapTxResponse } from '@sodax/sdk';
1
+ import type { RequestOverrideConfig, SubmitTxRequestV2, SubmitTxResponseV2 } from '@sodax/sdk';
3
2
  import { useSodaxContext } from '../shared/useSodaxContext.js';
4
- import { unwrapResult } from './unwrapResult.js';
3
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
4
  import type { MutationHookParams } from '../shared/types.js';
6
5
  import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
7
6
 
8
7
  /**
9
- * Mutation variables for {@link useBackendSubmitSwapTx}. The per-request `apiConfig` override
8
+ * Mutation variables for {@link useSwapsApiSubmitTx}. The per-request `apiConfig` override
10
9
  * (e.g. base URL) belongs here rather than at the hook level — different submissions in the same
11
10
  * component can target different endpoints without re-rendering.
12
11
  */
13
- export type UseBackendSubmitSwapTxVars = {
14
- request: SubmitSwapTxRequest;
12
+ export type UseSwapsApiSubmitTxVars = {
13
+ request: SubmitTxRequestV2;
15
14
  apiConfig?: RequestOverrideConfig;
16
15
  };
17
16
 
18
17
  /**
19
- * React hook for submitting a swap transaction to be processed by the backend (relay, post
20
- * execution to solver, etc.).
18
+ * React hook for submitting a swap transaction to be processed (relay, post execution to the
19
+ * solver, etc.) via the swaps API — `sodax.api.swaps.submitTx`.
21
20
  *
22
21
  * Pure mutation: pass `{ request, apiConfig? }` to `mutate({...})`. Default `retry: 3` is applied
23
22
  * at the hook level — consumers can override via `mutationOptions.retry`.
24
23
  *
25
24
  * @example
26
- * const { mutateAsync: submitSwapTx, isPending, error } = useBackendSubmitSwapTx();
25
+ * const { mutateAsync: submitSwapTx, isPending, error } = useSwapsApiSubmitTx();
27
26
  *
28
27
  * const result = await submitSwapTx({
29
28
  * request: { txHash: '0x123...', srcChainKey: 'sonic', walletAddress: '0xabc...', intent: { ... }, relayData: '0x...' },
30
29
  * apiConfig: { baseURL: 'https://...' },
31
30
  * });
32
31
  */
33
- export const useBackendSubmitSwapTx = ({
32
+ export const useSwapsApiSubmitTx = ({
34
33
  mutationOptions,
35
- }: MutationHookParams<SubmitSwapTxResponse, UseBackendSubmitSwapTxVars> = {}): SafeUseMutationResult<
36
- SubmitSwapTxResponse,
34
+ }: MutationHookParams<SubmitTxResponseV2, UseSwapsApiSubmitTxVars> = {}): SafeUseMutationResult<
35
+ SubmitTxResponseV2,
37
36
  Error,
38
- UseBackendSubmitSwapTxVars
37
+ UseSwapsApiSubmitTxVars
39
38
  > => {
40
39
  const { sodax } = useSodaxContext();
41
40
 
42
- return useSafeMutation<SubmitSwapTxResponse, Error, UseBackendSubmitSwapTxVars>({
43
- mutationKey: ['backend', 'submitSwapTx'],
41
+ return useSafeMutation<SubmitTxResponseV2, Error, UseSwapsApiSubmitTxVars>({
42
+ mutationKey: ['swapsApi', 'submitTx'],
44
43
  retry: 3,
45
44
  ...mutationOptions,
46
- mutationFn: async ({ request, apiConfig }): Promise<SubmitSwapTxResponse> =>
47
- unwrapResult(await sodax.backendApi.submitSwapTx(request, apiConfig)),
45
+ mutationFn: async ({ request, apiConfig }): Promise<SubmitTxResponseV2> =>
46
+ unwrapResult(await sodax.api.swaps.submitTx(request, apiConfig)),
48
47
  });
49
48
  };
@@ -0,0 +1,55 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { RequestOverrideConfig, SubmitTxStatusResponseV2 } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiSubmitTxStatusParams = ReadHookParams<
8
+ SubmitTxStatusResponseV2 | undefined,
9
+ {
10
+ txHash: string | undefined;
11
+ srcChainKey?: string;
12
+ apiConfig?: RequestOverrideConfig;
13
+ }
14
+ >;
15
+
16
+ /**
17
+ * React hook for polling the processing status of a submitted swap transaction via the swaps API —
18
+ * `sodax.api.swaps.getSubmitTxStatus`. Both `txHash` and `srcChainKey` are required for the query
19
+ * to run (the swaps API v2 status query requires the source chain key).
20
+ *
21
+ * @example
22
+ * const { data: status } = useSwapsApiSubmitTxStatus({
23
+ * params: { txHash: '0x123...', srcChainKey: 'sonic' },
24
+ * });
25
+ *
26
+ * @remarks
27
+ * - Default refetch interval is 1 second; stops on 'solved' or 'failed' status.
28
+ */
29
+ export const useSwapsApiSubmitTxStatus = ({
30
+ params,
31
+ queryOptions,
32
+ }: UseSwapsApiSubmitTxStatusParams = {}): UseQueryResult<SubmitTxStatusResponseV2 | undefined, Error> => {
33
+ const { sodax } = useSodaxContext();
34
+ const txHash = params?.txHash;
35
+ const srcChainKey = params?.srcChainKey;
36
+ const apiConfig = params?.apiConfig;
37
+
38
+ return useQuery({
39
+ queryKey: ['swapsApi', 'submitTx', 'status', txHash, srcChainKey],
40
+ queryFn: async (): Promise<SubmitTxStatusResponseV2 | undefined> => {
41
+ // `srcChainKey` is required by the swaps API v2 status query. The `enabled` gate below
42
+ // ensures both are present; this guard also narrows the types for the call.
43
+ if (!txHash || !srcChainKey) return undefined;
44
+ return unwrapResult(await sodax.api.swaps.getSubmitTxStatus({ txHash, srcChainKey }, apiConfig));
45
+ },
46
+ enabled: !!txHash && txHash.length > 0 && !!srcChainKey,
47
+ retry: 3,
48
+ refetchInterval: query => {
49
+ const status = query.state.data?.data?.status;
50
+ if (status === 'solved' || status === 'failed') return false;
51
+ return 1000;
52
+ },
53
+ ...queryOptions,
54
+ });
55
+ };
@@ -0,0 +1,34 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { GetSwapTokensResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiTokensParams = ReadHookParams<
8
+ GetSwapTokensResponseV2,
9
+ {
10
+ apiConfig?: RequestOverrideConfig;
11
+ }
12
+ >;
13
+
14
+ /**
15
+ * React hook to fetch all supported swap tokens grouped by SpokeChainKey via the swaps API —
16
+ * `sodax.api.swaps.getTokens`.
17
+ *
18
+ * @example
19
+ * const { data: tokensByChain } = useSwapsApiTokens();
20
+ */
21
+ export const useSwapsApiTokens = ({
22
+ params,
23
+ queryOptions,
24
+ }: UseSwapsApiTokensParams = {}): UseQueryResult<GetSwapTokensResponseV2, Error> => {
25
+ const { sodax } = useSodaxContext();
26
+ const apiConfig = params?.apiConfig;
27
+
28
+ return useQuery<GetSwapTokensResponseV2, Error>({
29
+ queryKey: ['swapsApi', 'tokens'],
30
+ queryFn: async (): Promise<GetSwapTokensResponseV2> => unwrapResult(await sodax.api.swaps.getTokens(apiConfig)),
31
+ retry: 3,
32
+ ...queryOptions,
33
+ });
34
+ };
@@ -0,0 +1,40 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import type { GetSwapTokensByChainResponseV2, RequestOverrideConfig } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import { unwrapResult } from '../shared/unwrapResult.js';
5
+ import type { ReadHookParams } from '../shared/types.js';
6
+
7
+ export type UseSwapsApiTokensByChainParams = ReadHookParams<
8
+ GetSwapTokensByChainResponseV2 | undefined,
9
+ {
10
+ chainKey: string | undefined;
11
+ apiConfig?: RequestOverrideConfig;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * React hook to fetch supported swap tokens for a single SpokeChainKey via the swaps API —
17
+ * `sodax.api.swaps.getTokensByChain`.
18
+ *
19
+ * @example
20
+ * const { data: tokens } = useSwapsApiTokensByChain({ params: { chainKey: '0xa4b1.arbitrum' } });
21
+ */
22
+ export const useSwapsApiTokensByChain = ({
23
+ params,
24
+ queryOptions,
25
+ }: UseSwapsApiTokensByChainParams = {}): UseQueryResult<GetSwapTokensByChainResponseV2 | undefined, Error> => {
26
+ const { sodax } = useSodaxContext();
27
+ const chainKey = params?.chainKey;
28
+ const apiConfig = params?.apiConfig;
29
+
30
+ return useQuery({
31
+ queryKey: ['swapsApi', 'tokens', chainKey],
32
+ queryFn: async (): Promise<GetSwapTokensByChainResponseV2 | undefined> => {
33
+ if (!chainKey) return undefined;
34
+ return unwrapResult(await sodax.api.swaps.getTokensByChain(chainKey, apiConfig));
35
+ },
36
+ enabled: !!chainKey && chainKey.length > 0,
37
+ retry: 3,
38
+ ...queryOptions,
39
+ });
40
+ };
@@ -1,25 +1,28 @@
1
- import { useMemo, useRef, type ReactNode, type ReactElement } from 'react';
2
- import { Sodax, type SodaxConfig } from '@sodax/sdk';
1
+ import { useMemo, type ReactNode, type ReactElement } from 'react';
2
+ import { Sodax, type SodaxOptions } from '@sodax/sdk';
3
3
  import { SodaxContext } from '@/contexts/index.js';
4
- import type { DeepPartial } from '@sodax/sdk';
5
4
 
6
5
  interface SodaxProviderProps {
7
6
  children: ReactNode;
8
7
  /**
9
- * Sodax config (overrides defaults including rpcUrls). **Read-once at mount**
10
- * changes after first render are ignored to prevent re-instantiating the SDK
11
- * from unstable parent references. To switch config (e.g. testnet mainnet),
12
- * unmount/remount the provider.
8
+ * Sodax SDK config. Tracked by **reference** - a new identity re-instantiates the
9
+ * SDK. Hoist to a module constant or wrap in `useMemo`; include any value the SDK
10
+ * should react to (e.g. solver env) in the deps. Inline `{...}` re-creates the SDK
11
+ * every parent render and resets every `useSodaxContext` consumer.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const config = useMemo(() => ({ solver: solverMap[env] }), [env]);
16
+ * <SodaxProvider config={config}>...</SodaxProvider>
17
+ * ```
13
18
  */
14
- config?: DeepPartial<SodaxConfig>;
19
+ config?: SodaxOptions;
15
20
  }
16
21
 
22
+ /** Root provider for `@sodax/dapp-kit`. Must be paired with `QueryClientProvider`. */
17
23
  export const SodaxProvider = ({ children, config }: SodaxProviderProps): ReactElement => {
18
- // Freeze config on first render so the SDK instance and consumers share one
19
- // snapshot (matches SodaxWalletProvider semantic).
20
- const configRef = useRef<DeepPartial<SodaxConfig> | undefined>(config);
21
- const frozen = configRef.current;
22
- const sodax = useMemo(() => new Sodax(frozen), [frozen]);
24
+ const sodax = useMemo(() => new Sodax(config), [config]);
25
+ const contextValue = useMemo(() => ({ sodax }), [sodax]);
23
26
 
24
- return <SodaxContext.Provider value={{ sodax }}>{children}</SodaxContext.Provider>;
27
+ return <SodaxContext.Provider value={contextValue}>{children}</SodaxContext.Provider>;
25
28
  };
@@ -1 +1,2 @@
1
1
  export * from './dex-utils.js';
2
+ export * from './nearStorageGate.js';
@@ -0,0 +1,46 @@
1
+ import { ChainKeys } from '@sodax/sdk';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { resolveNearStorageGate } from './nearStorageGate.js';
4
+
5
+ describe('resolveNearStorageGate', () => {
6
+ it('destination not NEAR → never gates', () => {
7
+ expect(resolveNearStorageGate(ChainKeys.POLYGON_MAINNET, { isLoading: false, data: false })).toEqual({
8
+ isNear: false,
9
+ needsRegistration: false,
10
+ blocksAction: false,
11
+ });
12
+ });
13
+
14
+ it('NEAR, check in flight → blocks action but does not yet prompt registration', () => {
15
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: true, data: undefined })).toEqual({
16
+ isNear: true,
17
+ needsRegistration: false,
18
+ blocksAction: true,
19
+ });
20
+ });
21
+
22
+ it('NEAR, check resolved, not registered → prompt registration and block action', () => {
23
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: false })).toEqual({
24
+ isNear: true,
25
+ needsRegistration: true,
26
+ blocksAction: true,
27
+ });
28
+ });
29
+
30
+ it('NEAR, check resolved, registered → no gate', () => {
31
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: true })).toEqual({
32
+ isNear: true,
33
+ needsRegistration: false,
34
+ blocksAction: false,
35
+ });
36
+ });
37
+
38
+ it('NEAR, query disabled (unresolved, not checking) → does not block', () => {
39
+ // isLoading is false for a disabled query; data stays undefined → action must not be blocked.
40
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: undefined })).toEqual({
41
+ isNear: true,
42
+ needsRegistration: false,
43
+ blocksAction: false,
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,39 @@
1
+ import { ChainKeys, type SpokeChainKey } from '@sodax/sdk';
2
+ import type { UseQueryResult } from '@tanstack/react-query';
3
+
4
+ /** The subset of a `useNearStorageCheck` result the gate reads. A full `UseQueryResult<boolean>` satisfies it. */
5
+ export type NearStorageCheckResult = Pick<UseQueryResult<boolean>, 'isLoading' | 'data'>;
6
+
7
+ /** UI gate state for NEP-141 storage registration on NEAR. */
8
+ export interface NearStorageGateState {
9
+ /** Destination is NEAR — the only chain with a NEP-141 storage-registration prerequisite. */
10
+ isNear: boolean;
11
+ /** Show the "register storage" action: the check resolved and the recipient is not registered. */
12
+ needsRegistration: boolean;
13
+ /**
14
+ * Keep the downstream action (swap/bridge/borrow/withdraw) disabled: the NEAR gate is unresolved
15
+ * (still checking) or unmet (needs registration).
16
+ */
17
+ blocksAction: boolean;
18
+ }
19
+
20
+ /**
21
+ * Derives the NEP-141 storage-registration UI gate state for a flow that delivers a token to a user
22
+ * on NEAR — NEAR's analogue of the Stellar trustline gate. Unwrapped (no hook): pass the destination
23
+ * `chainKey` and the `useNearStorageCheck` result; the util owns the `=== NEAR` test and reads
24
+ * `isLoading` (NOT `isPending`, which stays `true` for a disabled query and would block forever).
25
+ *
26
+ * `blocksAction` deliberately also covers the in-flight check window so the action can't be
27
+ * triggered before registration status is known; `needsRegistration` only flips once the check has
28
+ * resolved (so the "register" button isn't shown speculatively while still checking).
29
+ */
30
+ export function resolveNearStorageGate(chainKey: SpokeChainKey, check: NearStorageCheckResult): NearStorageGateState {
31
+ const isNear = chainKey === ChainKeys.NEAR_MAINNET;
32
+ const isChecking = check.isLoading;
33
+ const isRegistered = check.data;
34
+ return {
35
+ isNear,
36
+ needsRegistration: isNear && !isChecking && isRegistered === false,
37
+ blocksAction: isNear && (isChecking || isRegistered === false),
38
+ };
39
+ }