@sodax/dapp-kit 2.0.0-rc.3 → 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 (61) hide show
  1. package/README.md +9 -63
  2. package/dist/index.mjs +0 -2
  3. package/package.json +31 -20
  4. package/ai-exported/AGENTS.md +0 -134
  5. package/ai-exported/integration/README.md +0 -49
  6. package/ai-exported/integration/ai-rules.md +0 -80
  7. package/ai-exported/integration/architecture.md +0 -276
  8. package/ai-exported/integration/features/README.md +0 -29
  9. package/ai-exported/integration/features/auxiliary-services.md +0 -169
  10. package/ai-exported/integration/features/bitcoin.md +0 -87
  11. package/ai-exported/integration/features/bridge.md +0 -91
  12. package/ai-exported/integration/features/dex.md +0 -152
  13. package/ai-exported/integration/features/migration.md +0 -118
  14. package/ai-exported/integration/features/money-market.md +0 -144
  15. package/ai-exported/integration/features/staking.md +0 -123
  16. package/ai-exported/integration/features/swap.md +0 -101
  17. package/ai-exported/integration/quickstart.md +0 -187
  18. package/ai-exported/integration/recipes/README.md +0 -136
  19. package/ai-exported/integration/recipes/backend-queries.md +0 -157
  20. package/ai-exported/integration/recipes/bitcoin.md +0 -193
  21. package/ai-exported/integration/recipes/bridge.md +0 -174
  22. package/ai-exported/integration/recipes/dex.md +0 -204
  23. package/ai-exported/integration/recipes/invalidations.md +0 -115
  24. package/ai-exported/integration/recipes/migration.md +0 -212
  25. package/ai-exported/integration/recipes/money-market.md +0 -207
  26. package/ai-exported/integration/recipes/mutation-error-handling.md +0 -118
  27. package/ai-exported/integration/recipes/observability.md +0 -93
  28. package/ai-exported/integration/recipes/setup.md +0 -168
  29. package/ai-exported/integration/recipes/staking.md +0 -202
  30. package/ai-exported/integration/recipes/swap.md +0 -272
  31. package/ai-exported/integration/recipes/wallet-connectivity.md +0 -128
  32. package/ai-exported/integration/reference/README.md +0 -12
  33. package/ai-exported/integration/reference/glossary.md +0 -190
  34. package/ai-exported/integration/reference/hooks-index.md +0 -190
  35. package/ai-exported/integration/reference/public-api.md +0 -110
  36. package/ai-exported/integration/reference/querykey-conventions.md +0 -179
  37. package/ai-exported/migration/README.md +0 -60
  38. package/ai-exported/migration/ai-rules.md +0 -81
  39. package/ai-exported/migration/breaking-changes/hook-signatures.md +0 -233
  40. package/ai-exported/migration/breaking-changes/querykey-conventions.md +0 -108
  41. package/ai-exported/migration/breaking-changes/result-handling.md +0 -211
  42. package/ai-exported/migration/breaking-changes/sdk-leakage.md +0 -167
  43. package/ai-exported/migration/checklist.md +0 -89
  44. package/ai-exported/migration/features/README.md +0 -34
  45. package/ai-exported/migration/features/auxiliary-services.md +0 -114
  46. package/ai-exported/migration/features/bitcoin.md +0 -88
  47. package/ai-exported/migration/features/bridge.md +0 -160
  48. package/ai-exported/migration/features/dex.md +0 -101
  49. package/ai-exported/migration/features/migration.md +0 -120
  50. package/ai-exported/migration/features/money-market.md +0 -139
  51. package/ai-exported/migration/features/staking.md +0 -109
  52. package/ai-exported/migration/features/swap.md +0 -133
  53. package/ai-exported/migration/recipes.md +0 -185
  54. package/ai-exported/migration/reference/README.md +0 -15
  55. package/ai-exported/migration/reference/deleted-hooks.md +0 -110
  56. package/ai-exported/migration/reference/error-shape-crosswalk.md +0 -144
  57. package/ai-exported/migration/reference/renamed-hooks.md +0 -68
  58. package/dist/index.cjs +0 -2641
  59. package/dist/index.cjs.map +0 -1
  60. package/dist/index.d.cts +0 -1557
  61. 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,207 +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
- dstChainKey?: SpokeChainKey;
195
- dstAddress?: string;
196
- };
197
-
198
- // Borrow / Withdraw / Repay follow the same shape with their respective `action` literal
199
- // (`'borrow'` / `'withdraw'` / `'repay'`) and the same `src*` / `dst*` field names.
200
- ```
201
-
202
- ## Notes
203
-
204
- - **Borrow/withdraw skip approval** — `useMMAllowance` returns `true` automatically for these actions.
205
- - **Health factor < 1.0** means liquidation risk.
206
- - All four operations (supply / borrow / withdraw / repay) accept optional `dstChainKey` / `dstAddress` for cross-chain delivery — omit both for same-chain. There are no `from*` / `to*` field variants on any action.
207
- - 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`.