@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
@@ -0,0 +1,206 @@
1
+ # Recipe: Money Market
2
+
3
+ Cross-chain lending (supply) and borrowing.
4
+
5
+ **Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
6
+
7
+ ## Hooks
8
+
9
+ | Hook | Type | Purpose |
10
+ |------|------|---------|
11
+ | `useSupply` | Mutation | Supply tokens as collateral |
12
+ | `useBorrow` | Mutation | Borrow against collateral |
13
+ | `useWithdraw` | Mutation | Withdraw supplied tokens |
14
+ | `useRepay` | Mutation | Repay borrowed tokens |
15
+ | `useMMAllowance` | Query | Check approval (auto-skips for borrow/withdraw) |
16
+ | `useMMApprove` | Mutation | Approve tokens |
17
+ | `useReservesData` | Query | All reserve data |
18
+ | `useReservesHumanized` | Query | Reserves in human-readable (decimal-normalized) format |
19
+ | `useReservesList` | Query | List of reserve asset addresses |
20
+ | `useReservesUsdFormat` | Query | Reserves with USD values |
21
+ | `useUserFormattedSummary` | Query | User portfolio summary (health factor, collateral, debt) |
22
+ | `useUserReservesData` | Query | User reserve positions |
23
+ | `useAToken` | Query | aToken metadata |
24
+ | `useATokensBalances` | Query | aToken balances |
25
+
26
+ ## Hook shape
27
+
28
+ All mutation hooks follow the **zero-domain-param** policy — the hook itself takes only an optional `mutationOptions` slot; ALL domain inputs (`params`, `walletProvider`, etc.) flow through `mutate(vars)`:
29
+
30
+ ```ts
31
+ // @ai-snippets-skip
32
+ const { mutateAsync: supply } = useSupply();
33
+ await supply({ params: { srcChainKey, srcAddress, token, amount, action: 'supply' }, walletProvider });
34
+ ```
35
+
36
+ On SDK failure, `mutationFn` throws — `mutation.error` and `onError` engage natively. Use `mutateAsyncSafe` to get `Promise<Result<T>>` that never rejects.
37
+
38
+ ## Display Reserves
39
+
40
+ ```tsx
41
+ import { useReservesData } from '@sodax/dapp-kit';
42
+
43
+ function ReservesList() {
44
+ const { data, isLoading } = useReservesData();
45
+ if (isLoading || !data) return <div>Loading...</div>;
46
+ const [reserves] = data;
47
+ return (
48
+ <table>
49
+ <thead><tr><th>Asset</th><th>Supply Rate</th><th>Borrow Rate</th></tr></thead>
50
+ <tbody>
51
+ {reserves.map((r) => (
52
+ <tr key={r.underlyingAsset}>
53
+ <td>{r.symbol}</td>
54
+ <td>{r.liquidityRate.toString()}</td>
55
+ <td>{r.variableBorrowRate.toString()}</td>
56
+ </tr>
57
+ ))}
58
+ </tbody>
59
+ </table>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## User Position
65
+
66
+ ```tsx
67
+ import { useUserFormattedSummary } from '@sodax/dapp-kit';
68
+ import type { SpokeChainKey } from '@sodax/sdk';
69
+
70
+ function UserPosition({ spokeChainKey, userAddress }: { spokeChainKey: SpokeChainKey; userAddress: string }) {
71
+ const { data: summary } = useUserFormattedSummary({ params: { spokeChainKey, userAddress } });
72
+ if (!summary) return null;
73
+ return (
74
+ <div>
75
+ <p>Collateral: ${summary.totalCollateralUSD}</p>
76
+ <p>Debt: ${summary.totalBorrowsUSD}</p>
77
+ <p>Health Factor: {summary.healthFactor}</p>
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ## Check Allowance + Approve
84
+
85
+ ```tsx
86
+ import { useMMAllowance, useMMApprove } from '@sodax/dapp-kit';
87
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
88
+ import { ChainKeys, type MoneyMarketSupplyParams } from '@sodax/sdk';
89
+
90
+ function MMApproval({ params }: { params: MoneyMarketSupplyParams<typeof ChainKeys.BASE_MAINNET> }) {
91
+ const walletProvider = useWalletProvider({ xChainId: ChainKeys.BASE_MAINNET });
92
+ // useMMAllowance wraps the request under params.payload.
93
+ // Auto-returns true for borrow/withdraw — no unnecessary RPC calls.
94
+ const { data: isApproved } = useMMAllowance({ params: { payload: params } });
95
+ const { mutateAsync: approve, isPending } = useMMApprove();
96
+
97
+ if (isApproved) return null;
98
+ return (
99
+ <button
100
+ onClick={() => walletProvider && approve({ params, walletProvider })}
101
+ disabled={isPending || !walletProvider}
102
+ >
103
+ {isPending ? 'Approving...' : 'Approve'}
104
+ </button>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ## Supply
110
+
111
+ ```tsx
112
+ import { useSupply } from '@sodax/dapp-kit';
113
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
114
+ import { ChainKeys } from '@sodax/sdk';
115
+
116
+ function SupplyButton({ srcAddress }: { srcAddress: string }) {
117
+ const chainKey = ChainKeys.BASE_MAINNET;
118
+ const walletProvider = useWalletProvider({ xChainId: chainKey });
119
+ const { mutateAsync: supply, isPending } = useSupply();
120
+
121
+ const handleSupply = async () => {
122
+ if (!walletProvider) return;
123
+ try {
124
+ const txHashPair = await supply({
125
+ params: { srcChainKey: chainKey, srcAddress, token: '0x...', amount: 1_000_000n, action: 'supply' },
126
+ walletProvider,
127
+ });
128
+ console.log('Supplied (spoke, hub):', txHashPair);
129
+ } catch (e) {
130
+ console.error(e);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <button onClick={handleSupply} disabled={isPending || !walletProvider}>
136
+ {isPending ? 'Supplying...' : 'Supply'}
137
+ </button>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ## Borrow / Withdraw / Repay
143
+
144
+ ```tsx
145
+ import { useBorrow, useWithdraw, useRepay } from '@sodax/dapp-kit';
146
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
147
+ import { ChainKeys } from '@sodax/sdk';
148
+
149
+ function MMActions({ srcAddress }: { srcAddress: `0x${string}` }) {
150
+ const chainKey = ChainKeys.BASE_MAINNET;
151
+ const walletProvider = useWalletProvider({ xChainId: chainKey });
152
+
153
+ const { mutateAsync: borrow } = useBorrow();
154
+ const { mutateAsync: withdraw } = useWithdraw();
155
+ const { mutateAsync: repay } = useRepay();
156
+
157
+ const handleBorrow = async () => {
158
+ if (!walletProvider) return;
159
+ await borrow({
160
+ params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 500_000n, action: 'borrow' },
161
+ walletProvider,
162
+ });
163
+ };
164
+
165
+ const handleWithdraw = async () => {
166
+ if (!walletProvider) return;
167
+ await withdraw({
168
+ params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 1_000_000n, action: 'withdraw' },
169
+ walletProvider,
170
+ });
171
+ };
172
+
173
+ const handleRepay = async () => {
174
+ if (!walletProvider) return;
175
+ await repay({
176
+ params: { srcChainKey: chainKey, srcAddress, token: '0x0000000000000000000000000000000000000000', amount: 500_000n, action: 'repay' },
177
+ walletProvider,
178
+ });
179
+ };
180
+
181
+ return null;
182
+ }
183
+ ```
184
+
185
+ ## Types
186
+
187
+ ```typescript
188
+ type MoneyMarketSupplyParams<K extends SpokeChainKey = SpokeChainKey> = {
189
+ srcChainKey: K;
190
+ srcAddress: string;
191
+ token: string;
192
+ amount: bigint;
193
+ action: 'supply';
194
+ toChainId?: SpokeChainKey;
195
+ toAddress?: string;
196
+ };
197
+
198
+ // Borrow / Withdraw / Repay follow the same shape with their respective `action` literal.
199
+ ```
200
+
201
+ ## Notes
202
+
203
+ - **Borrow/withdraw skip approval** — `useMMAllowance` returns `true` automatically for these actions.
204
+ - **Health factor < 1.0** means liquidation risk.
205
+ - All operations support optional `toChainId` / `toAddress` (and `fromChainId` / `fromAddress` on borrow) for cross-chain delivery.
206
+ - Mutations throw on SDK failure — use `mutateAsyncSafe` for `Result<T>` ergonomics without `try/catch`.
@@ -0,0 +1,118 @@
1
+ # Recipe: Mutation error handling
2
+
3
+ Every dapp-kit mutation hook returns three ways to invoke the mutation. Pick by call shape — they all share the same React Query state under the hood (`isError`, `error`, `data`, devtools all work).
4
+
5
+ ## The three call shapes
6
+
7
+ | Method | Returns | Rejects on `!ok`? | When to use |
8
+ |---|---|---|---|
9
+ | `mutate(vars)` | `void` (fire-and-forget) | Never | Button-click handlers where you read `isPending` / `isError` / `error` from the hook in render. |
10
+ | `mutateAsync(vars)` | `Promise<TData>` | **Yes** | Imperative chains where you only need the success value. **MUST be wrapped in `try/catch`** or you'll leak unhandled rejections on user-rejects. |
11
+ | `mutateAsyncSafe(vars)` | `Promise<Result<TData>>` | **Never** | Imperative chains where you want explicit branching without exception flow. |
12
+
13
+ `mutateAsyncSafe` is the **recommended default** for sequenced flows like `if (!hasAllowance) await approve(); await action();` — the user-reject case is the modal failure mode in dApps, not exceptional, and `Result<T>`-style branching reads cleaner than exception flow.
14
+
15
+ ## fire-and-forget — `mutate`
16
+
17
+ Best for buttons that just kick off a mutation and let render reflect state.
18
+
19
+ ```tsx
20
+ import { useSwap } from '@sodax/dapp-kit';
21
+
22
+ function SwapButton({ params, walletProvider }) {
23
+ const m = useSwap();
24
+
25
+ return (
26
+ <>
27
+ <button onClick={() => m.mutate({ params, walletProvider })} disabled={m.isPending}>
28
+ {m.isPending ? 'Swapping...' : 'Swap'}
29
+ </button>
30
+ {m.isError && <p>Error: {m.error.message}</p>}
31
+ {m.isSuccess && <p>Done!</p>}
32
+ </>
33
+ );
34
+ }
35
+ ```
36
+
37
+ ## throws — `mutateAsync`
38
+
39
+ Use when you want exception flow control. **Always wrap in `try/catch`.**
40
+
41
+ ```tsx
42
+ import { useSwap } from '@sodax/dapp-kit';
43
+
44
+ function MyFlow({ params, walletProvider }) {
45
+ const { mutateAsync: swap } = useSwap();
46
+
47
+ const handleClick = async () => {
48
+ try {
49
+ const result = await swap({ params, walletProvider });
50
+ navigate('/done');
51
+ } catch (e) {
52
+ toast.error(e instanceof Error ? e.message : 'Swap failed');
53
+ }
54
+ };
55
+ }
56
+ ```
57
+
58
+ If you forget `try/catch`, an unhandled rejection lands in the global handler — most apps treat that as a fatal error.
59
+
60
+ ## safe — `mutateAsyncSafe`
61
+
62
+ Recommended. Branches on `Result<T>`. Never rejects.
63
+
64
+ ```tsx
65
+ // @ai-snippets-skip — Intent.intentHash placeholder
66
+ import { useSwap } from '@sodax/dapp-kit';
67
+
68
+ function MyFlow({ params, walletProvider }) {
69
+ const { mutateAsyncSafe: swap } = useSwap();
70
+
71
+ const handleClick = async () => {
72
+ const result = await swap({ params, walletProvider });
73
+ if (!result.ok) {
74
+ toast.error(result.error instanceof Error ? result.error.message : 'Swap failed');
75
+ return;
76
+ }
77
+ const { intent, intentDeliveryInfo } = result.value;
78
+ navigate(`/done?intent=${intent.intentHash}`);
79
+ };
80
+ }
81
+ ```
82
+
83
+ Pairs well with sequenced flows:
84
+
85
+ ```tsx
86
+ // @ai-snippets-skip — illustrative sequenced-flow pattern; `approve`, `swap`, `hasAllowance`
87
+ // are assumed from enclosing context (see recipes/swap.md for the full flow).
88
+ const handleSwap = async () => {
89
+ if (!hasAllowance) {
90
+ const a = await approve({ params, walletProvider });
91
+ if (!a.ok) { toast.error('Approve failed'); return; }
92
+ }
93
+ const r = await swap({ params, walletProvider });
94
+ if (!r.ok) { toast.error('Swap failed'); return; }
95
+ // success
96
+ };
97
+ ```
98
+
99
+ ## Why `mutationFn` throws on SDK `!ok`
100
+
101
+ Inside the hook, `mutationFn` calls `unwrapResult` on the SDK's `Result<T>`:
102
+
103
+ - On `Result.ok === true` → returns the unwrapped `value` (TData).
104
+ - On `Result.ok === false` → throws `result.error`.
105
+
106
+ Why throw? Because React Query's error model (`isError`, `error`, `onError`, `retry`, `throwOnError`, devtools) keys off `mutationFn` throwing. With `Result<T>` returned as success, none of those engaged on SDK failure. Throwing makes them work out of the box.
107
+
108
+ The `mutateAsyncSafe` shim re-packs the throw back into a `Result<T>` so you can have it both ways: React Query's native error machinery in render + `Result<T>` ergonomics imperatively.
109
+
110
+ ## Common pitfall — never call `useMutation` directly
111
+
112
+ Every dapp-kit mutation hook calls `useSafeMutation` (a thin wrapper). When you write your own wrapper hooks around dapp-kit mutations, do the same — call dapp-kit's hooks, not React Query's `useMutation`. Otherwise consumers won't get `mutateAsyncSafe`.
113
+
114
+ ## Cross-references
115
+
116
+ - [`observability.md`](observability.md) — global `onMutationError` for logging/Sentry.
117
+ - [`invalidations.md`](invalidations.md) — composing your own `onSuccess` after dapp-kit's hook-owned invalidations.
118
+ - [`../architecture.md`](../architecture.md) — full design rationale for `useSafeMutation` / `unwrapResult`.
@@ -0,0 +1,93 @@
1
+ # Recipe: Observability — global mutation error hook
2
+
3
+ `createSodaxQueryClient` returns a `QueryClient` pre-wired with a `MutationCache.onError` hook that gives you a single observability seam for every mutation failure across the app. Optional opt-in — if you construct your own `QueryClient`, nothing changes.
4
+
5
+ ## Default behavior
6
+
7
+ Logs every mutation failure to console as `[sodax] Mutation error: <error>`:
8
+
9
+ ```tsx
10
+ import { createSodaxQueryClient } from '@sodax/dapp-kit';
11
+
12
+ const queryClient = createSodaxQueryClient();
13
+ ```
14
+
15
+ ## Wire to your own logger
16
+
17
+ Sentry, Datadog, Pino — any error sink:
18
+
19
+ ```tsx
20
+ // @ai-snippets-skip — Sentry 3rd-party import
21
+ import { createSodaxQueryClient } from '@sodax/dapp-kit';
22
+ import * as Sentry from '@sentry/react';
23
+
24
+ const queryClient = createSodaxQueryClient({
25
+ onMutationError: (error) => Sentry.captureException(error),
26
+ });
27
+ ```
28
+
29
+ ## Disable the default
30
+
31
+ If you wire per-hook `onError` callbacks plus a React error boundary, you may not want a duplicated console log:
32
+
33
+ ```tsx
34
+ import { createSodaxQueryClient } from '@sodax/dapp-kit';
35
+
36
+ const queryClient = createSodaxQueryClient({ onMutationError: () => {} });
37
+ console.log(queryClient);
38
+ ```
39
+
40
+ ## Per-mutation opt-out — `meta.silent`
41
+
42
+ When a single mutation handles its error locally (e.g. its own toast in `onError`) and you don't want a duplicate `[sodax] Mutation error:` log, pass `meta: { silent: true }` on that mutation:
43
+
44
+ ```tsx
45
+ import { useSwap } from '@sodax/dapp-kit';
46
+
47
+ const swap = useSwap({
48
+ mutationOptions: {
49
+ meta: { silent: true },
50
+ onError: (e) => toast.error(e.message),
51
+ },
52
+ });
53
+ console.log(swap);
54
+ ```
55
+
56
+ The global `onMutationError` skips this mutation; everything else still fires through it.
57
+
58
+ ## Bring your own `MutationCache`
59
+
60
+ If you pass `config.mutationCache`, the factory keeps your cache instance (preserving any `onError` you set on it) and *additionally* subscribes to its event stream to dispatch `onMutationError`. Both handlers fire — neither replaces the other. `meta.silent` is honored in both branches.
61
+
62
+ ```tsx
63
+ import { MutationCache } from '@tanstack/react-query';
64
+ import { createSodaxQueryClient } from '@sodax/dapp-kit';
65
+
66
+ const myCache = new MutationCache({ onError: myOwnErrorHandler });
67
+ const queryClient = createSodaxQueryClient({ config: { mutationCache: myCache } });
68
+ // myOwnErrorHandler runs; sodax onMutationError ALSO runs (unless meta.silent).
69
+ ```
70
+
71
+ ## Important: this is observability, not prevention
72
+
73
+ The global hook fires for **every** mutation failure regardless of:
74
+ - Whether the consumer caught the rejection with `try/catch`
75
+ - Whether the consumer branched on `mutateAsyncSafe`'s `Result.ok`
76
+ - Whether the consumer registered a per-hook `onError`
77
+
78
+ It does **not** detect "unhandled" rejections. If you want to prevent unhandled rejections at the call site, use `mutateAsyncSafe` (see [`mutation-error-handling.md`](mutation-error-handling.md)).
79
+
80
+ ## When to use
81
+
82
+ - **Sentry/Datadog integration** — single point of capture for all SDK mutation failures.
83
+ - **Global error toast** — render-side error toast on top of per-hook handling. Simpler than per-hook `onError`.
84
+ - **Debug console mode** — keep the default during local dev for quick visibility.
85
+
86
+ ## When NOT to use
87
+
88
+ - **You only handle errors per-hook.** The global hook adds noise (duplicate logs). Either disable it entirely or use `meta.silent`.
89
+
90
+ ## Cross-references
91
+
92
+ - [`mutation-error-handling.md`](mutation-error-handling.md) — call-site error handling (preventing unhandled rejections).
93
+ - [`../architecture.md`](../architecture.md) — full design notes on `createSodaxQueryClient`.
@@ -0,0 +1,144 @@
1
+ # Recipe: Setup
2
+
3
+ Install and wire `@sodax/dapp-kit` into a React project.
4
+
5
+ **Depends on:** None
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ # Required
11
+ pnpm add @sodax/dapp-kit @tanstack/react-query
12
+
13
+ # Optional (only if you want built-in wallet connectivity hooks + providers)
14
+ pnpm add @sodax/wallet-sdk-react
15
+ ```
16
+
17
+ ## Wire Providers
18
+
19
+ RPC URLs are injected via `config.chains` — each chain entry takes `{ rpcUrl: string }`.
20
+
21
+ ```tsx
22
+ // providers.tsx
23
+ import { QueryClientProvider } from '@tanstack/react-query';
24
+ import { SodaxProvider, createSodaxQueryClient } from '@sodax/dapp-kit';
25
+ import { ChainKeys, type DeepPartial, type SodaxConfig } from '@sodax/sdk';
26
+
27
+ const queryClient = createSodaxQueryClient();
28
+
29
+ const sodaxConfig: DeepPartial<SodaxConfig> = {
30
+ chains: {
31
+ [ChainKeys.ARBITRUM_MAINNET]: { rpcUrl: 'https://arb1.arbitrum.io/rpc' },
32
+ [ChainKeys.BASE_MAINNET]: { rpcUrl: 'https://mainnet.base.org' },
33
+ [ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
34
+ [ChainKeys.POLYGON_MAINNET]: { rpcUrl: 'https://polygon-rpc.com' },
35
+ // Add chains your dApp needs
36
+ },
37
+ };
38
+
39
+ export function Providers({ children }: { children: React.ReactNode }) {
40
+ return (
41
+ <SodaxProvider config={sodaxConfig}>
42
+ <QueryClientProvider client={queryClient}>
43
+ {children}
44
+ </QueryClientProvider>
45
+ </SodaxProvider>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ### Optional: Add Wallet Provider
51
+
52
+ If you want to use `@sodax/wallet-sdk-react` for wallet connectivity, wrap `SodaxWalletProvider` inside `QueryClientProvider`:
53
+
54
+ ```tsx
55
+ // @ai-snippets-skip
56
+ import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
57
+
58
+ const walletConfig: SodaxWalletConfig = {
59
+ EVM: {
60
+ chains: {
61
+ [ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
62
+ [ChainKeys.BASE_MAINNET]: { rpcUrl: 'https://mainnet.base.org' },
63
+ },
64
+ },
65
+ };
66
+
67
+ export function Providers({ children }: { children: React.ReactNode }) {
68
+ return (
69
+ <SodaxProvider config={sodaxConfig}>
70
+ <QueryClientProvider client={queryClient}>
71
+ <SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
72
+ </QueryClientProvider>
73
+ </SodaxProvider>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ## `createSodaxQueryClient` (optional)
79
+
80
+ `createSodaxQueryClient` returns a `QueryClient` pre-wired with a `MutationCache.onError` hook for global mutation observability. Use it instead of `new QueryClient()`:
81
+
82
+ ```tsx
83
+ // @ai-snippets-skip — illustrative — multiple createSodaxQueryClient variations
84
+ import { createSodaxQueryClient } from '@sodax/dapp-kit';
85
+
86
+ // Default: logs every mutation failure as `[sodax] Mutation error: <error>`
87
+ const queryClient = createSodaxQueryClient();
88
+
89
+ // Wire to your own logger
90
+ const queryClient = createSodaxQueryClient({
91
+ onMutationError: (e) => Sentry.captureException(e),
92
+ });
93
+
94
+ // Silence a specific mutation locally via meta.silent
95
+ const swap = useSwap({
96
+ mutationOptions: { meta: { silent: true }, onError: (e) => toast.error(e.message) },
97
+ });
98
+ ```
99
+
100
+ ## Initialize SDK (Optional)
101
+
102
+ For dynamic config (latest tokens/chains from backend API):
103
+
104
+ ```tsx
105
+ import { useEffect } from 'react';
106
+ import { useSodaxContext } from '@sodax/dapp-kit';
107
+
108
+ export function useInitializeSodax() {
109
+ const { sodax } = useSodaxContext();
110
+
111
+ useEffect(() => {
112
+ sodax.initialize().then((result) => {
113
+ if (!result.ok) console.error('Failed to initialize Sodax:', result.error);
114
+ });
115
+ }, [sodax]);
116
+ }
117
+ ```
118
+
119
+ ## Chain Key Constants
120
+
121
+ ```tsx
122
+ import { ChainKeys } from '@sodax/sdk';
123
+
124
+ // Examples (full list lives in @sodax/sdk's reference):
125
+ ChainKeys.SONIC_MAINNET; // 'sonic' (hub)
126
+ ChainKeys.ARBITRUM_MAINNET; // '0xa4b1.arbitrum'
127
+ ChainKeys.BASE_MAINNET; // '0x2105.base'
128
+ ChainKeys.BSC_MAINNET; // '0x38.bsc'
129
+ ChainKeys.ETHEREUM_MAINNET; // '0x1.ethereum'
130
+ ChainKeys.POLYGON_MAINNET; // '0x89.polygon'
131
+ ChainKeys.OPTIMISM_MAINNET; // '0xa.optimism'
132
+ ChainKeys.AVALANCHE_MAINNET; // '0xa86a.avax'
133
+ ChainKeys.SUI_MAINNET; // 'sui'
134
+ ChainKeys.STELLAR_MAINNET; // 'stellar'
135
+ ChainKeys.SOLANA_MAINNET; // 'solana'
136
+ ChainKeys.ICON_MAINNET; // '0x1.icon'
137
+ ChainKeys.INJECTIVE_MAINNET; // 'injective-1'
138
+ ChainKeys.NEAR_MAINNET; // 'near'
139
+ ChainKeys.STACKS_MAINNET; // 'stacks'
140
+ ChainKeys.BITCOIN_MAINNET; // 'bitcoin'
141
+ // HyperEVM, Lightlink, Redbelly, Kaia also available.
142
+ ```
143
+
144
+ **v1 → v2:** the legacy `*_MAINNET_CHAIN_ID` constants (e.g. `BSC_MAINNET_CHAIN_ID`) are gone. Use `ChainKeys.X_MAINNET` namespace access.