@sodax/dapp-kit 1.5.7-beta → 2.0.0-rc.2

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 (202) hide show
  1. package/README.md +300 -422
  2. package/ai-exported/AGENTS.md +134 -0
  3. package/ai-exported/integration/README.md +49 -0
  4. package/ai-exported/integration/ai-rules.md +79 -0
  5. package/ai-exported/integration/architecture.md +274 -0
  6. package/ai-exported/integration/features/README.md +29 -0
  7. package/ai-exported/integration/features/auxiliary-services.md +169 -0
  8. package/ai-exported/integration/features/bitcoin.md +87 -0
  9. package/ai-exported/integration/features/bridge.md +91 -0
  10. package/ai-exported/integration/features/dex.md +152 -0
  11. package/ai-exported/integration/features/migration.md +118 -0
  12. package/ai-exported/integration/features/money-market.md +116 -0
  13. package/ai-exported/integration/features/staking.md +123 -0
  14. package/ai-exported/integration/features/swap.md +101 -0
  15. package/ai-exported/integration/quickstart.md +187 -0
  16. package/ai-exported/integration/recipes/README.md +136 -0
  17. package/ai-exported/integration/recipes/backend-queries.md +157 -0
  18. package/ai-exported/integration/recipes/bitcoin.md +193 -0
  19. package/ai-exported/integration/recipes/bridge.md +174 -0
  20. package/ai-exported/integration/recipes/dex.md +204 -0
  21. package/ai-exported/integration/recipes/invalidations.md +115 -0
  22. package/ai-exported/integration/recipes/migration.md +212 -0
  23. package/ai-exported/integration/recipes/money-market.md +206 -0
  24. package/ai-exported/integration/recipes/mutation-error-handling.md +118 -0
  25. package/ai-exported/integration/recipes/observability.md +93 -0
  26. package/ai-exported/integration/recipes/setup.md +144 -0
  27. package/ai-exported/integration/recipes/staking.md +202 -0
  28. package/ai-exported/integration/recipes/swap.md +272 -0
  29. package/ai-exported/integration/recipes/wallet-connectivity.md +101 -0
  30. package/ai-exported/integration/reference/README.md +12 -0
  31. package/ai-exported/integration/reference/glossary.md +188 -0
  32. package/ai-exported/integration/reference/hooks-index.md +190 -0
  33. package/ai-exported/integration/reference/public-api.md +110 -0
  34. package/ai-exported/integration/reference/querykey-conventions.md +179 -0
  35. package/ai-exported/migration/README.md +60 -0
  36. package/ai-exported/migration/ai-rules.md +81 -0
  37. package/ai-exported/migration/breaking-changes/hook-signatures.md +233 -0
  38. package/ai-exported/migration/breaking-changes/querykey-conventions.md +108 -0
  39. package/ai-exported/migration/breaking-changes/result-handling.md +211 -0
  40. package/ai-exported/migration/breaking-changes/sdk-leakage.md +165 -0
  41. package/ai-exported/migration/checklist.md +89 -0
  42. package/ai-exported/migration/features/README.md +34 -0
  43. package/ai-exported/migration/features/auxiliary-services.md +114 -0
  44. package/ai-exported/migration/features/bitcoin.md +88 -0
  45. package/ai-exported/migration/features/bridge.md +123 -0
  46. package/ai-exported/migration/features/dex.md +101 -0
  47. package/ai-exported/migration/features/migration.md +120 -0
  48. package/ai-exported/migration/features/money-market.md +97 -0
  49. package/ai-exported/migration/features/staking.md +109 -0
  50. package/ai-exported/migration/features/swap.md +118 -0
  51. package/ai-exported/migration/recipes.md +188 -0
  52. package/ai-exported/migration/reference/README.md +15 -0
  53. package/ai-exported/migration/reference/deleted-hooks.md +110 -0
  54. package/ai-exported/migration/reference/error-shape-crosswalk.md +144 -0
  55. package/ai-exported/migration/reference/renamed-hooks.md +66 -0
  56. package/dist/index.cjs +2642 -0
  57. package/dist/index.cjs.map +1 -0
  58. package/dist/index.d.cts +1550 -0
  59. package/dist/index.d.ts +1020 -2051
  60. package/dist/index.mjs +1581 -1531
  61. package/dist/index.mjs.map +1 -1
  62. package/package.json +21 -11
  63. package/src/contexts/index.ts +0 -3
  64. package/src/hooks/_mutationContract.test.ts +99 -0
  65. package/src/hooks/backend/README.md +2 -2
  66. package/src/hooks/backend/index.ts +13 -13
  67. package/src/hooks/backend/unwrapResult.ts +1 -0
  68. package/src/hooks/backend/useBackendAllMoneyMarketAssets.ts +13 -45
  69. package/src/hooks/backend/useBackendAllMoneyMarketBorrowers.ts +29 -59
  70. package/src/hooks/backend/useBackendIntentByHash.ts +21 -47
  71. package/src/hooks/backend/useBackendIntentByTxHash.ts +23 -50
  72. package/src/hooks/backend/useBackendMoneyMarketAsset.ts +21 -54
  73. package/src/hooks/backend/useBackendMoneyMarketAssetBorrowers.ts +30 -57
  74. package/src/hooks/backend/useBackendMoneyMarketAssetSuppliers.ts +31 -58
  75. package/src/hooks/backend/useBackendMoneyMarketPosition.ts +22 -38
  76. package/src/hooks/backend/useBackendOrderbook.ts +27 -49
  77. package/src/hooks/backend/useBackendSubmitSwapTx.ts +30 -36
  78. package/src/hooks/backend/useBackendSubmitSwapTxStatus.ts +38 -58
  79. package/src/hooks/backend/useBackendUserIntents.ts +25 -63
  80. package/src/hooks/bitcoin/index.ts +9 -8
  81. package/src/hooks/bitcoin/useBitcoinBalance.ts +20 -5
  82. package/src/hooks/bitcoin/useExpiredUtxos.ts +26 -16
  83. package/src/hooks/bitcoin/useFundTradingWallet.ts +33 -30
  84. package/src/hooks/bitcoin/useRadfiAuth.ts +43 -40
  85. package/src/hooks/bitcoin/useRadfiSession.ts +53 -59
  86. package/src/hooks/bitcoin/useRadfiWithdraw.ts +35 -53
  87. package/src/hooks/bitcoin/useRenewUtxos.ts +30 -50
  88. package/src/hooks/bitcoin/useTradingWallet.ts +1 -1
  89. package/src/hooks/bitcoin/useTradingWalletBalance.ts +25 -14
  90. package/src/hooks/bridge/index.ts +5 -5
  91. package/src/hooks/bridge/useBridge.ts +29 -55
  92. package/src/hooks/bridge/useBridgeAllowance.ts +38 -38
  93. package/src/hooks/bridge/useBridgeApprove.ts +32 -57
  94. package/src/hooks/bridge/useGetBridgeableAmount.ts +23 -37
  95. package/src/hooks/bridge/useGetBridgeableTokens.ts +27 -50
  96. package/src/hooks/dex/index.ts +16 -16
  97. package/src/hooks/dex/useClaimRewards.ts +35 -54
  98. package/src/hooks/dex/useCreateDecreaseLiquidityParams.ts +7 -20
  99. package/src/hooks/dex/useCreateDepositParams.ts +7 -21
  100. package/src/hooks/dex/useCreateSupplyLiquidityParams.ts +13 -28
  101. package/src/hooks/dex/useCreateWithdrawParams.ts +7 -20
  102. package/src/hooks/dex/useDecreaseLiquidity.ts +40 -66
  103. package/src/hooks/dex/useDexAllowance.ts +29 -75
  104. package/src/hooks/dex/useDexApprove.ts +32 -43
  105. package/src/hooks/dex/useDexDeposit.ts +42 -49
  106. package/src/hooks/dex/useDexWithdraw.ts +32 -43
  107. package/src/hooks/dex/useLiquidityAmounts.ts +13 -82
  108. package/src/hooks/dex/usePoolBalances.ts +50 -72
  109. package/src/hooks/dex/usePoolData.ts +17 -43
  110. package/src/hooks/dex/usePools.ts +11 -38
  111. package/src/hooks/dex/usePositionInfo.ts +27 -62
  112. package/src/hooks/dex/useSupplyLiquidity.ts +80 -75
  113. package/src/hooks/index.ts +12 -10
  114. package/src/hooks/migrate/index.ts +13 -4
  115. package/src/hooks/migrate/useMigrateBaln.ts +42 -0
  116. package/src/hooks/migrate/useMigrateIcxToSoda.ts +44 -0
  117. package/src/hooks/migrate/useMigratebnUSD.ts +47 -0
  118. package/src/hooks/migrate/useMigrationAllowance.ts +76 -0
  119. package/src/hooks/migrate/useMigrationApprove.ts +66 -0
  120. package/src/hooks/migrate/useRevertMigrateSodaToIcx.ts +39 -0
  121. package/src/hooks/mm/index.ts +14 -12
  122. package/src/hooks/mm/useAToken.ts +25 -41
  123. package/src/hooks/mm/useATokensBalances.ts +29 -60
  124. package/src/hooks/mm/useBorrow.ts +38 -56
  125. package/src/hooks/mm/useMMAllowance.ts +37 -73
  126. package/src/hooks/mm/useMMApprove.ts +36 -43
  127. package/src/hooks/mm/useRepay.ts +33 -53
  128. package/src/hooks/mm/useReservesData.ts +12 -38
  129. package/src/hooks/mm/useReservesHumanized.ts +12 -31
  130. package/src/hooks/mm/useReservesList.ts +11 -31
  131. package/src/hooks/mm/useReservesUsdFormat.ts +15 -35
  132. package/src/hooks/mm/useSupply.ts +45 -51
  133. package/src/hooks/mm/useUserFormattedSummary.ts +32 -84
  134. package/src/hooks/mm/useUserReservesData.ts +27 -77
  135. package/src/hooks/mm/useWithdraw.ts +38 -54
  136. package/src/hooks/partner/index.ts +6 -0
  137. package/src/hooks/partner/useApproveToken.ts +42 -0
  138. package/src/hooks/partner/useFeeClaimSwap.ts +38 -0
  139. package/src/hooks/partner/useFetchAssetsBalances.ts +37 -0
  140. package/src/hooks/partner/useGetAutoSwapPreferences.ts +37 -0
  141. package/src/hooks/partner/useIsTokenApproved.ts +39 -0
  142. package/src/hooks/partner/useSetSwapPreference.ts +50 -0
  143. package/src/hooks/provider/index.ts +1 -2
  144. package/src/hooks/provider/useHubProvider.ts +1 -1
  145. package/src/hooks/recovery/index.ts +2 -0
  146. package/src/hooks/recovery/useHubAssetBalances.ts +43 -0
  147. package/src/hooks/recovery/useWithdrawHubAsset.ts +48 -0
  148. package/src/hooks/shared/index.ts +10 -6
  149. package/src/hooks/shared/types.ts +77 -0
  150. package/src/hooks/shared/unwrapResult.ts +19 -0
  151. package/src/hooks/shared/useDeriveUserWalletAddress.ts +22 -40
  152. package/src/hooks/shared/useEstimateGas.ts +18 -15
  153. package/src/hooks/shared/useGetUserHubWalletAddress.ts +25 -26
  154. package/src/hooks/shared/useRequestTrustline.ts +28 -61
  155. package/src/hooks/shared/useSafeMutation.test.ts +43 -0
  156. package/src/hooks/shared/useSafeMutation.ts +68 -0
  157. package/src/hooks/shared/useSodaxContext.ts +1 -1
  158. package/src/hooks/shared/useStellarTrustlineCheck.ts +30 -64
  159. package/src/hooks/shared/useXBalances.test.ts +113 -0
  160. package/src/hooks/shared/useXBalances.ts +61 -0
  161. package/src/hooks/staking/index.ts +18 -18
  162. package/src/hooks/staking/useCancelUnstake.ts +30 -41
  163. package/src/hooks/staking/useClaim.ts +27 -36
  164. package/src/hooks/staking/useConvertedAssets.ts +24 -34
  165. package/src/hooks/staking/useInstantUnstake.ts +33 -40
  166. package/src/hooks/staking/useInstantUnstakeAllowance.ts +37 -45
  167. package/src/hooks/staking/useInstantUnstakeApprove.ts +42 -42
  168. package/src/hooks/staking/useInstantUnstakeRatio.ts +24 -41
  169. package/src/hooks/staking/useStake.ts +32 -37
  170. package/src/hooks/staking/useStakeAllowance.ts +30 -43
  171. package/src/hooks/staking/useStakeApprove.ts +40 -40
  172. package/src/hooks/staking/useStakeRatio.ts +24 -40
  173. package/src/hooks/staking/useStakingConfig.ts +14 -27
  174. package/src/hooks/staking/useStakingInfo.ts +30 -38
  175. package/src/hooks/staking/useUnstake.ts +29 -43
  176. package/src/hooks/staking/useUnstakeAllowance.ts +37 -44
  177. package/src/hooks/staking/useUnstakeApprove.ts +40 -43
  178. package/src/hooks/staking/useUnstakingInfo.ts +29 -41
  179. package/src/hooks/staking/useUnstakingInfoWithPenalty.ts +31 -47
  180. package/src/hooks/swap/index.ts +8 -8
  181. package/src/hooks/swap/useCancelLimitOrder.ts +24 -41
  182. package/src/hooks/swap/useCancelSwap.ts +24 -33
  183. package/src/hooks/swap/useCreateLimitOrder.ts +29 -62
  184. package/src/hooks/swap/useQuote.ts +17 -43
  185. package/src/hooks/swap/useStatus.ts +22 -29
  186. package/src/hooks/swap/useSwap.ts +31 -49
  187. package/src/hooks/swap/useSwapAllowance.ts +38 -35
  188. package/src/hooks/swap/useSwapApprove.ts +48 -57
  189. package/src/index.ts +5 -3
  190. package/src/providers/SodaxProvider.tsx +17 -11
  191. package/src/providers/createSodaxQueryClient.ts +96 -0
  192. package/src/providers/index.ts +2 -1
  193. package/src/utils/dex-utils.ts +27 -5
  194. package/src/utils/index.ts +1 -1
  195. package/dist/index.d.mts +0 -2581
  196. package/dist/index.js +0 -2574
  197. package/dist/index.js.map +0 -1
  198. package/src/hooks/migrate/types.ts +0 -15
  199. package/src/hooks/migrate/useMigrate.tsx +0 -110
  200. package/src/hooks/migrate/useMigrationAllowance.tsx +0 -79
  201. package/src/hooks/migrate/useMigrationApprove.tsx +0 -129
  202. package/src/hooks/provider/useSpokeProvider.ts +0 -172
@@ -1,14 +1,16 @@
1
+ // packages/dapp-kit/src/hooks/bitcoin/useRadfiSession.ts
1
2
  import { useState, useEffect, useRef, useCallback } from 'react';
2
- import type { BitcoinSpokeProvider } from '@sodax/sdk';
3
+ import type { IBitcoinWalletProvider } from '@sodax/sdk';
3
4
  import {
4
5
  useRadfiAuth,
5
6
  loadRadfiSession,
6
7
  saveRadfiSession,
7
8
  clearRadfiSession,
8
9
  type RadfiSession,
9
- } from './useRadfiAuth';
10
+ } from './useRadfiAuth.js';
11
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
10
12
 
11
- const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 min — refresh before access token expires (10 min TTL)
13
+ const REFRESH_INTERVAL = 5 * 60 * 1000;
12
14
 
13
15
  export type UseRadfiSessionReturn = {
14
16
  walletAddress: string | undefined;
@@ -18,94 +20,86 @@ export type UseRadfiSessionReturn = {
18
20
  isLoginPending: boolean;
19
21
  };
20
22
 
21
- /**
22
- * Manages the full Radfi session lifecycle:
23
- * - On mount / wallet switch: refreshes token to validate session
24
- * - Single interval (~5 min): refreshes access token. If refresh fails → clears session, isAuthed=false
25
- * - ensureRadfiAccessToken (SDK layer) acts as safety net before swap/bridge
26
- * - Exposes login() and isAuthed for UI
27
- */
28
- export function useRadfiSession(
29
- spokeProvider: BitcoinSpokeProvider | undefined,
30
- ): UseRadfiSessionReturn {
23
+ export function useRadfiSession(walletProvider: IBitcoinWalletProvider | undefined): UseRadfiSessionReturn {
24
+ const { sodax } = useSodaxContext();
31
25
  const [walletAddress, setWalletAddress] = useState<string | undefined>();
32
26
  const [isAuthed, setIsAuthed] = useState(false);
33
27
  const [tradingAddress, setTradingAddress] = useState<string | undefined>();
34
28
  const isRefreshingRef = useRef(false);
35
29
 
36
- // ── Silent refresh helper ────────────────────────────────────────────────
37
- const silentRefresh = useCallback(async (address: string) => {
38
- if (!spokeProvider || isRefreshingRef.current) return;
39
- isRefreshingRef.current = true;
40
-
41
- try {
42
- const session = loadRadfiSession(address);
43
- if (!session?.refreshToken) {
30
+ const silentRefresh = useCallback(
31
+ async (address: string) => {
32
+ if (!walletProvider || isRefreshingRef.current) return;
33
+ isRefreshingRef.current = true;
34
+
35
+ try {
36
+ const session = loadRadfiSession(address);
37
+ if (!session?.refreshToken) {
38
+ setIsAuthed(false);
39
+ return;
40
+ }
41
+
42
+ const radfi = sodax.spoke.bitcoin.radfi;
43
+ const { accessToken, refreshToken } = await radfi.refreshAccessToken(session.refreshToken);
44
+ const updated: RadfiSession = { ...session, accessToken, refreshToken };
45
+
46
+ saveRadfiSession(address, updated);
47
+ radfi.setRadfiAccessToken(accessToken, refreshToken);
48
+ setIsAuthed(true);
49
+ setTradingAddress(updated.tradingAddress || undefined);
50
+ } catch {
51
+ clearRadfiSession(address);
52
+ sodax.spoke.bitcoin.radfi.setRadfiAccessToken('', '');
44
53
  setIsAuthed(false);
45
- return;
54
+ setTradingAddress(undefined);
55
+ } finally {
56
+ isRefreshingRef.current = false;
46
57
  }
58
+ },
59
+ [walletProvider, sodax],
60
+ );
47
61
 
48
- const { accessToken, refreshToken } = await spokeProvider.radfi.refreshAccessToken(session.refreshToken);
49
- const updated: RadfiSession = {
50
- ...session,
51
- accessToken,
52
- refreshToken,
53
- };
54
-
55
- saveRadfiSession(address, updated);
56
- spokeProvider.setRadfiAccessToken(accessToken, refreshToken);
57
- setIsAuthed(true);
58
- setTradingAddress(updated.tradingAddress || undefined);
59
- } catch {
60
- clearRadfiSession(address);
61
- spokeProvider.setRadfiAccessToken('', '');
62
- setIsAuthed(false);
63
- setTradingAddress(undefined);
64
- } finally {
65
- isRefreshingRef.current = false;
66
- }
67
- }, [spokeProvider]);
68
-
69
- // ── On mount / wallet switch: reset state + refresh to validate session ──
70
62
  useEffect(() => {
71
- if (!spokeProvider) return;
63
+ if (!walletProvider) return;
72
64
 
73
- // Reset state immediately to avoid stale data from previous wallet
74
65
  setIsAuthed(false);
75
66
  setTradingAddress(undefined);
76
67
  setWalletAddress(undefined);
77
68
 
78
- spokeProvider.walletProvider.getWalletAddress()
79
- .then((addr) => {
69
+ walletProvider
70
+ .getWalletAddress()
71
+ .then((addr: string) => {
80
72
  setWalletAddress(addr);
81
73
  const session = loadRadfiSession(addr);
82
74
  if (!session?.refreshToken) return;
83
-
84
- // Always refresh on mount to validate the session is actually valid
85
75
  silentRefresh(addr);
86
76
  })
87
77
  .catch(() => {});
88
- }, [spokeProvider, silentRefresh]);
78
+ }, [walletProvider, silentRefresh]);
89
79
 
90
- // ── Interval: refresh token every 5 min to keep access token fresh ──────
91
80
  useEffect(() => {
92
- if (!walletAddress || !spokeProvider) return;
81
+ if (!walletAddress || !walletProvider) return;
93
82
 
94
83
  const id = setInterval(() => {
95
84
  silentRefresh(walletAddress);
96
85
  }, REFRESH_INTERVAL);
97
86
 
98
87
  return () => clearInterval(id);
99
- }, [walletAddress, spokeProvider, silentRefresh]);
88
+ }, [walletAddress, walletProvider, silentRefresh]);
100
89
 
101
- // ── Login ────────────────────────────────────────────────────────────────
102
- const { mutateAsync: loginMutate, isPending: isLoginPending } = useRadfiAuth(spokeProvider);
90
+ const { mutateAsyncSafe: loginMutateSafe, isPending: isLoginPending } = useRadfiAuth();
103
91
 
104
92
  const login = useCallback(async () => {
105
- const result = await loginMutate();
93
+ if (!walletProvider) {
94
+ return;
95
+ }
96
+ const result = await loginMutateSafe({ walletProvider });
97
+ if (!result.ok) {
98
+ return;
99
+ }
106
100
  setIsAuthed(true);
107
- setTradingAddress(result.tradingAddress || undefined);
108
- }, [loginMutate]);
101
+ setTradingAddress(result.value.tradingAddress || undefined);
102
+ }, [loginMutateSafe, walletProvider]);
109
103
 
110
104
  return { walletAddress, isAuthed, tradingAddress, login, isLoginPending };
111
105
  }
@@ -1,11 +1,16 @@
1
- import { normalizePsbtToBase64, type BitcoinSpokeProvider } from '@sodax/sdk';
2
- import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
3
- import { loadRadfiSession } from './useRadfiAuth';
1
+ // packages/dapp-kit/src/hooks/bitcoin/useRadfiWithdraw.ts
2
+ import { normalizePsbtToBase64, ChainKeys, type IBitcoinWalletProvider } from '@sodax/sdk';
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { loadRadfiSession } from './useRadfiAuth.js';
5
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
6
+ import type { MutationHookParams } from '../shared/types.js';
7
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
4
8
 
5
- type WithdrawToUserParams = {
9
+ export type UseRadfiWithdrawVars = {
6
10
  amount: string;
7
11
  tokenId: string;
8
12
  withdrawTo: string;
13
+ walletProvider: IBitcoinWalletProvider;
9
14
  };
10
15
 
11
16
  type WithdrawResult = {
@@ -14,72 +19,49 @@ type WithdrawResult = {
14
19
  };
15
20
 
16
21
  /**
17
- * Hook to withdraw BTC from Radfi trading wallet to user's personal wallet.
18
- *
19
- * Flow:
20
- * 1. Build withdraw transaction via Radfi API (returns unsigned PSBT)
21
- * 2. User signs the PSBT with their wallet
22
- * 3. Submit signed PSBT back to Radfi for co-signing and broadcasting
23
- *
24
- * @example
25
- * ```tsx
26
- * const { mutateAsync: withdraw, isPending } = useRadfiWithdraw(spokeProvider);
27
- *
28
- * const handleWithdraw = async () => {
29
- * const result = await withdraw({
30
- * amount: '10000',
31
- * tokenId: '0:0',
32
- * withdrawTo: 'bc1q...', // user's segwit address
33
- * });
34
- * console.log('Withdrawn:', result.txId);
35
- * };
36
- * ```
22
+ * React hook for withdrawing BTC from the user's Radfi trading wallet back to their personal
23
+ * Bitcoin wallet. Pure mutation: pass all inputs (including the wallet provider) to
24
+ * `mutate({...})`.
37
25
  */
38
- export function useRadfiWithdraw(
39
- spokeProvider: BitcoinSpokeProvider | undefined,
40
- ): UseMutationResult<WithdrawResult, Error, WithdrawToUserParams> {
26
+ export function useRadfiWithdraw({
27
+ mutationOptions,
28
+ }: MutationHookParams<WithdrawResult, UseRadfiWithdrawVars> = {}): SafeUseMutationResult<
29
+ WithdrawResult,
30
+ Error,
31
+ UseRadfiWithdrawVars
32
+ > {
33
+ const { sodax } = useSodaxContext();
41
34
  const queryClient = useQueryClient();
42
35
 
43
- return useMutation<WithdrawResult, Error, WithdrawToUserParams>({
44
- mutationFn: async ({ amount, tokenId, withdrawTo }: WithdrawToUserParams) => {
45
- if (!spokeProvider) {
46
- throw new Error('Bitcoin spoke provider not found');
47
- }
36
+ return useSafeMutation<WithdrawResult, Error, UseRadfiWithdrawVars>({
37
+ mutationKey: ['bitcoin', 'radfiWithdraw'],
38
+ ...mutationOptions,
39
+ mutationFn: async ({ amount, tokenId, withdrawTo, walletProvider }) => {
40
+ const radfi = sodax.spoke.bitcoin.radfi;
48
41
 
49
- const userAddress = await spokeProvider.walletProvider.getWalletAddress();
42
+ const userAddress = await walletProvider.getWalletAddress();
50
43
  const session = loadRadfiSession(userAddress);
51
- const accessToken = session?.accessToken || spokeProvider.radfiAccessToken;
44
+ const accessToken = session?.accessToken || radfi.accessToken;
52
45
 
53
46
  if (!accessToken) {
54
47
  throw new Error('Radfi authentication required. Please login first.');
55
48
  }
56
49
 
57
- // Step 1: Build the withdraw transaction
58
- const buildResult = await spokeProvider.radfi.withdrawToUser(
59
- { userAddress, amount, tokenId, withdrawTo },
60
- accessToken,
61
- );
50
+ const buildResult = await radfi.withdrawToUser({ userAddress, amount, tokenId, withdrawTo }, accessToken);
62
51
 
63
- // Step 2: Sign the PSBT with user's wallet
64
- const signedTx = await spokeProvider.walletProvider.signTransaction(
65
- buildResult.base64Psbt,
66
- false,
67
- );
52
+ const signedTx = await walletProvider.signTransaction(buildResult.base64Psbt, false);
68
53
 
69
54
  const signedBase64Tx = normalizePsbtToBase64(signedTx);
70
55
 
71
- // Step 3: Submit to Radfi for co-signing and broadcasting
72
- const txId = await spokeProvider.radfi.signAndBroadcastWithdraw(
73
- { userAddress, signedBase64Tx },
74
- accessToken,
75
- );
56
+ const txId = await radfi.signAndBroadcastWithdraw({ userAddress, signedBase64Tx }, accessToken);
76
57
 
77
58
  return { txId, fee: buildResult.fee.totalFee };
78
59
  },
79
- onSuccess: () => {
80
- queryClient.invalidateQueries({ queryKey: ['trading-wallet-balance'] });
81
- queryClient.invalidateQueries({ queryKey: ['btc-balance'] });
82
- queryClient.invalidateQueries({ queryKey: ['xBalances'] });
60
+ onSuccess: async (data, vars, ctx) => {
61
+ queryClient.invalidateQueries({ queryKey: ['bitcoin', 'tradingWalletBalance'] });
62
+ queryClient.invalidateQueries({ queryKey: ['bitcoin', 'balance'] });
63
+ queryClient.invalidateQueries({ queryKey: ['shared', 'xBalances', ChainKeys.BITCOIN_MAINNET] });
64
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
83
65
  },
84
66
  });
85
67
  }
@@ -1,72 +1,52 @@
1
- import { normalizePsbtToBase64, type BitcoinSpokeProvider } from '@sodax/sdk';
2
- import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query';
3
- import { loadRadfiSession } from './useRadfiAuth';
4
-
5
- type RenewUtxosParams = {
1
+ // packages/dapp-kit/src/hooks/bitcoin/useRenewUtxos.ts
2
+ import { normalizePsbtToBase64, type IBitcoinWalletProvider } from '@sodax/sdk';
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { loadRadfiSession } from './useRadfiAuth.js';
5
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
6
+ import type { MutationHookParams } from '../shared/types.js';
7
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
8
+
9
+ export type UseRenewUtxosVars = {
6
10
  txIdVouts: string[];
11
+ walletProvider: IBitcoinWalletProvider;
7
12
  };
8
13
 
9
14
  /**
10
- * Hook to renew expired UTXOs in the Radfi trading wallet.
11
- *
12
- * Flow:
13
- * 1. Build renew-utxo transaction via Radfi API (returns unsigned PSBT)
14
- * 2. User signs the PSBT with their wallet
15
- * 3. Submit signed PSBT back to Radfi for co-signing and broadcasting
16
- *
17
- * @example
18
- * ```tsx
19
- * const { mutateAsync: renewUtxos, isPending } = useRenewUtxos(spokeProvider);
20
- *
21
- * const handleRenew = async (expiredUtxos: RadfiUtxo[]) => {
22
- * const txIdVouts = expiredUtxos.map(u => u.txidVout);
23
- * const txId = await renewUtxos({ txIdVouts });
24
- * console.log('Renewed:', txId);
25
- * };
26
- * ```
15
+ * React hook for renewing expired UTXOs in the user's Radfi trading wallet. Pure mutation: pass
16
+ * `{ txIdVouts, walletProvider }` to `mutate({...})`.
27
17
  */
28
- export function useRenewUtxos(
29
- spokeProvider: BitcoinSpokeProvider | undefined,
30
- ): UseMutationResult<string, Error, RenewUtxosParams> {
18
+ export function useRenewUtxos({
19
+ mutationOptions,
20
+ }: MutationHookParams<string, UseRenewUtxosVars> = {}): SafeUseMutationResult<string, Error, UseRenewUtxosVars> {
21
+ const { sodax } = useSodaxContext();
31
22
  const queryClient = useQueryClient();
32
23
 
33
- return useMutation<string, Error, RenewUtxosParams>({
34
- mutationFn: async ({ txIdVouts }: RenewUtxosParams) => {
35
- if (!spokeProvider) {
36
- throw new Error('Bitcoin spoke provider not found');
37
- }
24
+ return useSafeMutation<string, Error, UseRenewUtxosVars>({
25
+ mutationKey: ['bitcoin', 'renewUtxos'],
26
+ ...mutationOptions,
27
+ mutationFn: async ({ txIdVouts, walletProvider }) => {
28
+ const radfi = sodax.spoke.bitcoin.radfi;
38
29
 
39
- const userAddress = await spokeProvider.walletProvider.getWalletAddress();
30
+ const userAddress = await walletProvider.getWalletAddress();
40
31
  const session = loadRadfiSession(userAddress);
41
- const accessToken = session?.accessToken || spokeProvider.radfiAccessToken;
32
+ const accessToken = session?.accessToken || radfi.accessToken;
42
33
 
43
34
  if (!accessToken) {
44
35
  throw new Error('Radfi authentication required. Please login first.');
45
36
  }
46
37
 
47
- // Step 1: Build the renew-utxo transaction
48
- const buildResult = await spokeProvider.radfi.buildRenewUtxoTransaction(
49
- { userAddress, txIdVouts },
50
- accessToken,
51
- );
38
+ const buildResult = await radfi.buildRenewUtxoTransaction({ userAddress, txIdVouts }, accessToken);
52
39
 
53
- // Step 2: Sign the PSBT with user's wallet
54
- const signedTx = await spokeProvider.walletProvider.signTransaction(
55
- buildResult.base64Psbt,
56
- false,
57
- );
40
+ const signedTx = await walletProvider.signTransaction(buildResult.base64Psbt, false);
58
41
 
59
42
  const signedBase64Tx = normalizePsbtToBase64(signedTx);
60
43
 
61
- // Step 3: Submit to Radfi for co-signing and broadcasting
62
- return spokeProvider.radfi.signAndBroadcastRenewUtxo(
63
- { userAddress, signedBase64Tx },
64
- accessToken,
65
- );
44
+ return radfi.signAndBroadcastRenewUtxo({ userAddress, signedBase64Tx }, accessToken);
66
45
  },
67
- onSuccess: () => {
68
- queryClient.invalidateQueries({ queryKey: ['expired-utxos'] });
69
- queryClient.invalidateQueries({ queryKey: ['trading-wallet-balance'] });
46
+ onSuccess: async (data, vars, ctx) => {
47
+ queryClient.invalidateQueries({ queryKey: ['bitcoin', 'expiredUtxos'] });
48
+ queryClient.invalidateQueries({ queryKey: ['bitcoin', 'tradingWalletBalance'] });
49
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
70
50
  },
71
51
  });
72
52
  }
@@ -1,4 +1,4 @@
1
- import { loadRadfiSession } from './useRadfiAuth';
1
+ import { loadRadfiSession } from './useRadfiAuth.js';
2
2
 
3
3
  type UseTradingWalletReturn = {
4
4
  tradingAddress: string | undefined;
@@ -1,22 +1,33 @@
1
1
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
- import type { BitcoinSpokeProvider, RadfiWalletBalance } from '@sodax/sdk';
2
+ import type { RadfiWalletBalance, IBitcoinWalletProvider } from '@sodax/sdk';
3
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ export type UseTradingWalletBalanceParams = ReadHookParams<
7
+ RadfiWalletBalance,
8
+ {
9
+ walletProvider: IBitcoinWalletProvider | undefined;
10
+ tradingAddress: string | undefined;
11
+ }
12
+ >;
13
+
14
+ export function useTradingWalletBalance({
15
+ params,
16
+ queryOptions,
17
+ }: UseTradingWalletBalanceParams = {}): UseQueryResult<RadfiWalletBalance, Error> {
18
+ const { sodax } = useSodaxContext();
19
+ const walletProvider = params?.walletProvider;
20
+ const tradingAddress = params?.tradingAddress;
3
21
 
4
- /**
5
- * Hook to fetch trading wallet balance from Radfi API.
6
- * Returns confirmed + pending satoshi balances.
7
- */
8
- export function useTradingWalletBalance(
9
- spokeProvider: BitcoinSpokeProvider | undefined,
10
- tradingAddress: string | undefined,
11
- ): UseQueryResult<RadfiWalletBalance, Error> {
12
22
  return useQuery<RadfiWalletBalance, Error>({
13
- queryKey: ['trading-wallet-balance', tradingAddress],
23
+ queryKey: ['bitcoin', 'tradingWalletBalance', tradingAddress],
14
24
  queryFn: () => {
15
- if (!spokeProvider || !tradingAddress) {
16
- throw new Error('spokeProvider and tradingAddress are required');
25
+ if (!walletProvider || !tradingAddress) {
26
+ throw new Error('walletProvider and tradingAddress are required');
17
27
  }
18
- return spokeProvider.radfi.getBalance(tradingAddress);
28
+ return sodax.spoke.bitcoin.radfi.getBalance(tradingAddress);
19
29
  },
20
- enabled: !!spokeProvider && !!tradingAddress,
30
+ enabled: !!walletProvider && !!tradingAddress,
31
+ ...queryOptions,
21
32
  });
22
33
  }
@@ -1,5 +1,5 @@
1
- export * from './useBridgeAllowance';
2
- export * from './useBridgeApprove';
3
- export * from './useBridge';
4
- export * from './useGetBridgeableAmount';
5
- export * from './useGetBridgeableTokens';
1
+ export * from './useBridge.js';
2
+ export * from './useBridgeAllowance.js';
3
+ export * from './useBridgeApprove.js';
4
+ export * from './useGetBridgeableAmount.js';
5
+ export * from './useGetBridgeableTokens.js';
@@ -1,64 +1,38 @@
1
- import { useSodaxContext } from '../shared/useSodaxContext';
2
- import type {
3
- BridgeError,
4
- BridgeErrorCode,
5
- SpokeTxHash,
6
- HubTxHash,
7
- Result,
8
- CreateBridgeIntentParams,
9
- } from '@sodax/sdk';
10
- import { useMutation, type UseMutationResult } from '@tanstack/react-query';
11
- import type { SpokeProvider } from '@sodax/sdk';
1
+ // packages/dapp-kit/src/hooks/bridge/useBridge.ts
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { BridgeParams, SpokeChainKey, TxHashPair } from '@sodax/sdk';
4
+ import { useQueryClient } from '@tanstack/react-query';
5
+ import type { MutationHookParams } from '../shared/types.js';
6
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
7
+ import { unwrapResult } from '../shared/unwrapResult.js';
12
8
 
13
9
  /**
14
- * Hook for executing bridge transactions to transfer tokens between chains.
15
- * Uses React Query's useMutation for better state management and caching.
16
- *
17
- * @param {SpokeProvider} spokeProvider - The spoke provider to use for the bridge
18
- * @returns {UseMutationResult} Mutation result object containing mutation function and state
19
- *
20
- * @example
21
- * ```typescript
22
- * const { mutateAsync: bridge, isPending } = useBridge(spokeProvider);
23
- *
24
- * const handleBridge = async () => {
25
- * const result = await bridge({
26
- * srcChainId: '0x2105.base',
27
- * srcAsset: '0x...',
28
- * amount: 1000n,
29
- * dstChainId: '0x89.polygon',
30
- * dstAsset: '0x...',
31
- * recipient: '0x...'
32
- * });
10
+ * Mutation variables for {@link useBridge}. Generic over `K extends SpokeChainKey` (defaults to
11
+ * the full union). Sophisticated callers can lock K at the hook call site to narrow the
12
+ * `walletProvider` and `params.srcChainKey` types.
13
+ */
14
+ export type UseBridgeVars<K extends SpokeChainKey = SpokeChainKey> = Omit<BridgeParams<K, false>, 'raw'>;
15
+
16
+ /**
17
+ * React hook for executing a cross-chain bridge transfer.
33
18
  *
34
- * console.log('Bridge transaction hashes:', {
35
- * spokeTxHash: result.spokeTxHash,
36
- * hubTxHash: result.hubTxHash
37
- * });
38
- * };
39
- * ```
19
+ * Throws on SDK failure so React Query's native error model engages (`isError`, `error`,
20
+ * `onError`, `retry`). Returns the unwrapped `TxHashPair` on success.
40
21
  */
41
- export function useBridge(
42
- spokeProvider: SpokeProvider | undefined,
43
- ): UseMutationResult<Result<[SpokeTxHash, HubTxHash], BridgeError<BridgeErrorCode>>, Error, CreateBridgeIntentParams> {
22
+ export function useBridge<K extends SpokeChainKey = SpokeChainKey>({
23
+ mutationOptions,
24
+ }: MutationHookParams<TxHashPair, UseBridgeVars<K>> = {}): SafeUseMutationResult<TxHashPair, Error, UseBridgeVars<K>> {
44
25
  const { sodax } = useSodaxContext();
26
+ const queryClient = useQueryClient();
45
27
 
46
- return useMutation<Result<[SpokeTxHash, HubTxHash], BridgeError<BridgeErrorCode>>, Error, CreateBridgeIntentParams>({
47
- mutationFn: async (params: CreateBridgeIntentParams) => {
48
- if (!spokeProvider) {
49
- throw new Error('Spoke provider not found');
50
- }
51
-
52
- const result = await sodax.bridge.bridge({
53
- params,
54
- spokeProvider,
55
- });
56
-
57
- if (!result.ok) {
58
- throw new Error(`Bridge failed: ${result.error.code}`);
59
- }
60
-
61
- return result;
28
+ return useSafeMutation<TxHashPair, Error, UseBridgeVars<K>>({
29
+ mutationKey: ['bridge'],
30
+ ...mutationOptions,
31
+ mutationFn: async vars => unwrapResult(await sodax.bridge.bridge({ ...vars, raw: false })),
32
+ onSuccess: async (data, vars, ctx) => {
33
+ queryClient.invalidateQueries({ queryKey: ['shared', 'xBalances', vars.params.srcChainKey] });
34
+ queryClient.invalidateQueries({ queryKey: ['shared', 'xBalances', vars.params.dstChainKey] });
35
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
62
36
  },
63
37
  });
64
38
  }
@@ -1,49 +1,49 @@
1
1
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
- import { useSodaxContext } from '../shared/useSodaxContext';
3
- import type { SpokeProvider, CreateBridgeIntentParams } from '@sodax/sdk';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { CreateBridgeIntentParams, GetWalletProviderType, SpokeChainKey } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
4
5
 
5
- /**
6
- * Hook for checking token allowance for bridge operations.
7
- *
8
- * This hook verifies if the user has approved enough tokens for a specific bridge action.
9
- * It automatically queries and tracks the allowance status.
10
- *
11
- * @param {BridgeParams} params - The parameters for the bridge to check allowance for.
12
- * @param {SpokeProvider} spokeProvider - The spoke provider to use for allowance checks
13
- *
14
- * @returns {UseQueryResult<boolean, Error>} A React Query result containing:
15
- * - data: Boolean indicating if allowance is sufficient
16
- * - isLoading: Loading state indicator
17
- * - error: Any error that occurred during the check
18
- *
19
- * @example
20
- * ```typescript
21
- * const { data: hasAllowed, isLoading } = useBridgeAllowance(params, spokeProvider);
22
- * ```
23
- */
24
- export function useBridgeAllowance(
25
- params: CreateBridgeIntentParams | undefined,
26
- spokeProvider: SpokeProvider | undefined,
27
- ): UseQueryResult<boolean, Error> {
6
+ export type UseBridgeAllowanceParams<K extends SpokeChainKey> = ReadHookParams<
7
+ boolean,
8
+ {
9
+ payload: CreateBridgeIntentParams<K> | undefined;
10
+ walletProvider: GetWalletProviderType<K> | undefined;
11
+ }
12
+ >;
13
+
14
+ export function useBridgeAllowance<K extends SpokeChainKey>({
15
+ params,
16
+ queryOptions,
17
+ }: UseBridgeAllowanceParams<K> = {}): UseQueryResult<boolean, Error> {
28
18
  const { sodax } = useSodaxContext();
19
+ const payload = params?.payload;
20
+ const walletProvider = params?.walletProvider;
29
21
 
30
- return useQuery({
31
- queryKey: ['bridge-allowance', params],
22
+ return useQuery<boolean, Error>({
23
+ // Extract the (chain, owner, token, amount) tuple that actually scopes the allowance —
24
+ // raw-object keys break per Rule 4 (bigints) and churn on every render.
25
+ queryKey: [
26
+ 'bridge',
27
+ 'allowance',
28
+ payload?.srcChainKey,
29
+ payload?.srcAddress,
30
+ payload?.srcToken,
31
+ payload?.amount?.toString(),
32
+ ],
32
33
  queryFn: async () => {
33
- if (!spokeProvider || !params) {
34
+ if (!payload || !walletProvider) {
34
35
  return false;
35
36
  }
36
-
37
- const allowance = await sodax.bridge.isAllowanceValid({
38
- params,
39
- spokeProvider,
37
+ const result = await sodax.bridge.isAllowanceValid({
38
+ params: payload,
39
+ raw: false,
40
+ walletProvider,
40
41
  });
41
-
42
- if (allowance.ok) {
43
- return allowance.value;
44
- }
45
- return false;
42
+ return result.ok ? result.value : false;
46
43
  },
47
- enabled: !!spokeProvider && !!params,
44
+ enabled: !!payload && !!walletProvider,
45
+ refetchInterval: 2000,
46
+ gcTime: 0,
47
+ ...queryOptions,
48
48
  });
49
49
  }