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

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 (63) hide show
  1. package/README.md +9 -63
  2. package/dist/index.d.ts +11 -4
  3. package/dist/index.mjs +3 -6
  4. package/package.json +31 -19
  5. package/src/providers/SodaxProvider.tsx +15 -11
  6. package/ai-exported/AGENTS.md +0 -134
  7. package/ai-exported/integration/README.md +0 -49
  8. package/ai-exported/integration/ai-rules.md +0 -79
  9. package/ai-exported/integration/architecture.md +0 -274
  10. package/ai-exported/integration/features/README.md +0 -29
  11. package/ai-exported/integration/features/auxiliary-services.md +0 -169
  12. package/ai-exported/integration/features/bitcoin.md +0 -87
  13. package/ai-exported/integration/features/bridge.md +0 -91
  14. package/ai-exported/integration/features/dex.md +0 -152
  15. package/ai-exported/integration/features/migration.md +0 -118
  16. package/ai-exported/integration/features/money-market.md +0 -116
  17. package/ai-exported/integration/features/staking.md +0 -123
  18. package/ai-exported/integration/features/swap.md +0 -101
  19. package/ai-exported/integration/quickstart.md +0 -187
  20. package/ai-exported/integration/recipes/README.md +0 -136
  21. package/ai-exported/integration/recipes/backend-queries.md +0 -157
  22. package/ai-exported/integration/recipes/bitcoin.md +0 -193
  23. package/ai-exported/integration/recipes/bridge.md +0 -174
  24. package/ai-exported/integration/recipes/dex.md +0 -204
  25. package/ai-exported/integration/recipes/invalidations.md +0 -115
  26. package/ai-exported/integration/recipes/migration.md +0 -212
  27. package/ai-exported/integration/recipes/money-market.md +0 -206
  28. package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
  29. package/ai-exported/integration/recipes/observability.md +0 -93
  30. package/ai-exported/integration/recipes/setup.md +0 -144
  31. package/ai-exported/integration/recipes/staking.md +0 -202
  32. package/ai-exported/integration/recipes/swap.md +0 -272
  33. package/ai-exported/integration/recipes/wallet-connectivity.md +0 -101
  34. package/ai-exported/integration/reference/README.md +0 -12
  35. package/ai-exported/integration/reference/glossary.md +0 -188
  36. package/ai-exported/integration/reference/hooks-index.md +0 -190
  37. package/ai-exported/integration/reference/public-api.md +0 -110
  38. package/ai-exported/integration/reference/querykey-conventions.md +0 -179
  39. package/ai-exported/migration/README.md +0 -60
  40. package/ai-exported/migration/ai-rules.md +0 -81
  41. package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
  42. package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
  43. package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
  44. package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -165
  45. package/ai-exported/migration/checklist.md +0 -89
  46. package/ai-exported/migration/features/README.md +0 -34
  47. package/ai-exported/migration/features/auxiliary-services.md +0 -114
  48. package/ai-exported/migration/features/bitcoin.md +0 -88
  49. package/ai-exported/migration/features/bridge.md +0 -123
  50. package/ai-exported/migration/features/dex.md +0 -101
  51. package/ai-exported/migration/features/migration.md +0 -120
  52. package/ai-exported/migration/features/money-market.md +0 -97
  53. package/ai-exported/migration/features/staking.md +0 -109
  54. package/ai-exported/migration/features/swap.md +0 -118
  55. package/ai-exported/migration/recipes.md +0 -188
  56. package/ai-exported/migration/reference/README.md +0 -15
  57. package/ai-exported/migration/reference/deleted-hooks.md +0 -110
  58. package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
  59. package/ai-exported/migration/reference/renamed-hooks.md +0 -66
  60. package/dist/index.cjs +0 -2642
  61. package/dist/index.cjs.map +0 -1
  62. package/dist/index.d.cts +0 -1550
  63. package/dist/index.mjs.map +0 -1
@@ -1,212 +0,0 @@
1
- # Recipe: Migration
2
-
3
- Legacy token migration (ICX, bnUSD, BALN) from the ICON ecosystem to SODAX.
4
-
5
- **Depends on:** [setup.md](setup.md), [wallet-connectivity.md](wallet-connectivity.md)
6
-
7
- ## Hooks
8
-
9
- | Hook | Type | Purpose |
10
- |------|------|---------|
11
- | `useMigrateIcxToSoda` | Mutation | ICX/wICX (ICON) → SODA (Sonic) |
12
- | `useRevertMigrateSodaToIcx` | Mutation | SODA (Sonic) → wICX (ICON) |
13
- | `useMigratebnUSD` | Mutation | Legacy bnUSD ↔ new bnUSD (bidirectional, any spoke chain) |
14
- | `useMigrateBaln` | Mutation | BALN (ICON) → SODA (Sonic) with optional lock period |
15
- | `useMigrationApprove` | Mutation | Approve token spending before migration |
16
- | `useMigrationAllowance` | Query | Check if approval is needed |
17
-
18
- ## Migration Paths
19
-
20
- | From | To | Reversible | Approval needed |
21
- |------|----|-----------|----------------|
22
- | ICX/wICX (ICON) | SODA (Sonic) | Yes (use `useRevertMigrateSodaToIcx`) | No (ICON has no ERC-20 allowance) |
23
- | BALN (ICON) | SODA (Sonic) | No | No |
24
- | Legacy bnUSD (EVM/Stellar/ICON) | New bnUSD | Yes (same hook, swap src/dst) | Yes (EVM/Stellar sources) |
25
-
26
- ## ICX to SODA
27
-
28
- ```tsx
29
- import { useMigrateIcxToSoda } from '@sodax/dapp-kit';
30
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
31
- import { ChainKeys, type IconAddress } from '@sodax/sdk';
32
-
33
- function IcxMigration({ srcAddress, dstAddress }: { srcAddress: IconAddress; dstAddress: `0x${string}` }) {
34
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.ICON_MAINNET });
35
- const { mutateAsync: migrate, isPending } = useMigrateIcxToSoda();
36
-
37
- const handleMigrate = async () => {
38
- if (!walletProvider) return;
39
- try {
40
- const txHashPair = await migrate({
41
- params: {
42
- srcChainKey: ChainKeys.ICON_MAINNET,
43
- srcAddress,
44
- // `address` must be the wICX or native-ICX token address — typed as the
45
- // narrow IcxTokenType union from @sodax/sdk's ICON chain config.
46
- address: 'cx3975b43d260fb8ec802cef6e60c2f4d07486f11d', // wICX on ICON mainnet
47
- amount: 1_000_000_000_000_000_000n,
48
- dstAddress, // Sonic recipient address
49
- },
50
- walletProvider,
51
- });
52
- console.log('Migrated:', txHashPair);
53
- } catch (e) {
54
- console.error(e);
55
- }
56
- };
57
-
58
- return (
59
- <button onClick={handleMigrate} disabled={isPending || !walletProvider}>
60
- {isPending ? 'Migrating...' : 'Migrate ICX to SODA'}
61
- </button>
62
- );
63
- }
64
- ```
65
-
66
- ## Revert SODA → wICX
67
-
68
- Requires approval — SODA is an EVM token on Sonic.
69
-
70
- ```tsx
71
- import { useMigrationAllowance, useMigrationApprove, useRevertMigrateSodaToIcx } from '@sodax/dapp-kit';
72
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
73
- import { ChainKeys } from '@sodax/sdk';
74
- import type { IcxCreateRevertMigrationParams } from '@sodax/sdk';
75
-
76
- function RevertMigration({ srcAddress }: { srcAddress: `0x${string}` }) {
77
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.SONIC_MAINNET });
78
-
79
- const revertParams: IcxCreateRevertMigrationParams = {
80
- srcChainKey: ChainKeys.SONIC_MAINNET,
81
- srcAddress,
82
- amount: 1_000_000_000_000_000_000n,
83
- dstAddress: 'hx...', // ICON recipient address
84
- };
85
-
86
- const { data: isApproved } = useMigrationAllowance({
87
- params: { params: revertParams, action: 'revert' },
88
- });
89
- const { mutateAsync: approve, isPending: isApproving } = useMigrationApprove();
90
- const { mutateAsync: revert, isPending: isReverting } = useRevertMigrateSodaToIcx();
91
-
92
- const handleRevert = async () => {
93
- if (!walletProvider) return;
94
- try {
95
- if (!isApproved) {
96
- await approve({ params: revertParams, walletProvider, action: 'revert' });
97
- }
98
- const txHashPair = await revert({ params: revertParams, walletProvider });
99
- console.log('Reverted:', txHashPair);
100
- } catch (e) {
101
- console.error(e);
102
- }
103
- };
104
-
105
- return (
106
- <button onClick={handleRevert} disabled={isReverting || isApproving || !walletProvider}>
107
- {isApproving ? 'Approving...' : isReverting ? 'Reverting...' : 'Revert to wICX'}
108
- </button>
109
- );
110
- }
111
- ```
112
-
113
- ## BALN to SODA (with optional lock period)
114
-
115
- BALN migration does not require approval (ICON chain, no ERC-20 allowance). Longer lock period = higher SODA reward multiplier.
116
-
117
- ```tsx
118
- import { useMigrateBaln } from '@sodax/dapp-kit';
119
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
120
- import { ChainKeys, LockupPeriod, type IconAddress } from '@sodax/sdk';
121
-
122
- function BalnMigration({ srcAddress, dstAddress }: { srcAddress: IconAddress; dstAddress: `0x${string}` }) {
123
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.ICON_MAINNET });
124
- const { mutateAsync: migrateBaln, isPending } = useMigrateBaln();
125
-
126
- const handleMigrate = async () => {
127
- if (!walletProvider) return;
128
- try {
129
- const txHashPair = await migrateBaln({
130
- params: {
131
- srcChainKey: ChainKeys.ICON_MAINNET,
132
- srcAddress,
133
- amount: 100_000_000_000_000_000_000n,
134
- // LockupPeriod is an enum in seconds: NO_LOCKUP (0.5x reward),
135
- // SIX_MONTHS (0.75x), TWELVE_MONTHS (1.0x), EIGHTEEN_MONTHS (1.25x),
136
- // TWENTY_FOUR_MONTHS (1.5x).
137
- lockupPeriod: LockupPeriod.TWELVE_MONTHS,
138
- dstAddress,
139
- stake: true, // Auto-stake the migrated SODA into the xSODA vault
140
- },
141
- walletProvider,
142
- });
143
- console.log('BALN migrated:', txHashPair);
144
- } catch (e) {
145
- console.error(e);
146
- }
147
- };
148
-
149
- return (
150
- <button onClick={handleMigrate} disabled={isPending || !walletProvider}>
151
- {isPending ? 'Migrating...' : 'Migrate BALN to SODA'}
152
- </button>
153
- );
154
- }
155
- ```
156
-
157
- ## bnUSD Migration (bidirectional)
158
-
159
- Works for legacy ↔ new bnUSD across spoke chains. May require approval on EVM/Stellar sources.
160
-
161
- ```tsx
162
- import { useMigratebnUSD, useMigrationAllowance, useMigrationApprove } from '@sodax/dapp-kit';
163
- import { useWalletProvider } from '@sodax/wallet-sdk-react';
164
- import { ChainKeys } from '@sodax/sdk';
165
- import type { UnifiedBnUSDMigrateParams } from '@sodax/sdk';
166
-
167
- function BnUSDMigration({ srcAddress }: { srcAddress: string }) {
168
- const walletProvider = useWalletProvider({ xChainId: ChainKeys.BASE_MAINNET });
169
-
170
- const bnUSDParams: UnifiedBnUSDMigrateParams<typeof ChainKeys.BASE_MAINNET> = {
171
- srcChainKey: ChainKeys.BASE_MAINNET,
172
- srcAddress,
173
- srcbnUSD: '0x...', // legacy bnUSD address on Base
174
- dstChainKey: ChainKeys.ARBITRUM_MAINNET,
175
- dstbnUSD: '0x...', // new bnUSD address on Arbitrum
176
- amount: 1_000_000n, // 6 decimals
177
- dstAddress: srcAddress,
178
- };
179
-
180
- const { data: isApproved } = useMigrationAllowance({
181
- params: { params: bnUSDParams, action: 'migrate' },
182
- });
183
- const { mutateAsync: approve, isPending: isApproving } = useMigrationApprove();
184
- const { mutateAsync: migratebnUSD, isPending: isMigrating } = useMigratebnUSD();
185
-
186
- const handleMigrate = async () => {
187
- if (!walletProvider) return;
188
- try {
189
- if (!isApproved) {
190
- await approve({ params: bnUSDParams, walletProvider, action: 'migrate' });
191
- }
192
- const txHashPair = await migratebnUSD({ params: bnUSDParams, walletProvider });
193
- console.log('bnUSD migrated:', txHashPair);
194
- } catch (e) {
195
- console.error(e);
196
- }
197
- };
198
-
199
- return (
200
- <button onClick={handleMigrate} disabled={isMigrating || isApproving || !walletProvider}>
201
- {isApproving ? 'Approving...' : isMigrating ? 'Migrating...' : 'Migrate bnUSD'}
202
- </button>
203
- );
204
- }
205
- ```
206
-
207
- ## Notes
208
-
209
- - **ICX and BALN forward migrations** don't require approval — ICON has no ERC-20 allowance mechanism.
210
- - **SODA → ICX revert** and **EVM/Stellar bnUSD sources** require approval before migrating.
211
- - **BALN lock periods**: `0` = 0.5x reward, `6` = 0.75x, `12` = 1.0x, `24` = 1.5x (months).
212
- - `useMigratebnUSD` is bidirectional — swap `srcbnUSD`/`dstbnUSD` and `srcChainKey`/`dstChainKey` to go the other direction.
@@ -1,206 +0,0 @@
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`.
@@ -1,118 +0,0 @@
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`.
@@ -1,93 +0,0 @@
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`.