@sodax/dapp-kit 2.0.0-rc.1 → 2.0.0-rc.3

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.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Project
6
6
 
7
- `@sodax/dapp-kit` is a React hooks library that wraps `@sodax/sdk` with React Query. It provides ~95 hooks across 11 feature domains (swap, money market, staking, bridge, dex, migration, partner, recovery, bitcoin/Radfi, backend queries, shared) for consumer dApps. It is **React-only** — Node.js scripts and backend services use `@sodax/sdk` directly.
7
+ `@sodax/dapp-kit` is a React hooks library that wraps `@sodax/sdk` with React Query. It provides hooks across 11 feature domains (swap, money market, staking, bridge, dex, migration, partner, recovery, bitcoin/Radfi, backend queries, shared) for consumer dApps. It is **React-only** — Node.js scripts and backend services use `@sodax/sdk` directly.
8
8
 
9
9
  This package is **v2**. v2 was a deep canonicalization pass over v1's hook shapes — single-object params, mandatory `mutateAsyncSafe`, hook-owned invalidations, throw-on-`Result.!ok` inside `mutationFn`, canonical queryKey/mutationKey conventions. Plus the entire SDK underneath was reshaped (chain-key-driven routing, `Result<T>` everywhere, `WalletProviderSlot<K, Raw>`). Code written against v1 dapp-kit will not compile against v2.
10
10
 
@@ -39,6 +39,7 @@ DO / DO NOT / workflow / stop conditions for AI agents writing v2 dapp-kit code.
39
39
  - **DO NOT** recreate the v1 `invalidateMmQueries(...)` utility or anything similar. Each mutation hook invalidates the relevant keys in its own `onSuccess`. Add cross-feature invalidations via consumer `onSuccess`.
40
40
  - **DO NOT** destructure cross-chain mutation results as arrays — `[a, b] = result.value` is wrong. The shape is `TxHashPair = { srcChainTxHash, dstChainTxHash }` (object). This applies to `useBridge`, `useStake`/`useUnstake`/etc., `useDexDeposit`/`useDexWithdraw`, all four MM mutations, and all four migration mutations.
41
41
  - **DO NOT** use legacy chain-id constants (`BSC_MAINNET_CHAIN_ID`, etc.). They're gone in v2 — use `ChainKeys.X_MAINNET`.
42
+ - **DO NOT** reach for `as any` / `as IEvmWalletProvider` casts when wiring `useWalletProvider({ xChainId })` into mutation `mutate(vars)`. v2 supports the broad-union case structurally; the cast is not needed. See `recipes/wallet-connectivity.md` § "No type cast is needed".
42
43
 
43
44
  ## Stop conditions (defer to user)
44
45
 
@@ -33,6 +33,8 @@ Every v2 design concept the hooks rest on, in one TOC-navigable file. Read it on
33
33
 
34
34
  `SodaxProvider` does **not** depend on `@sodax/wallet-sdk-react` — wallet state is wired side-by-side. Backend / non-React consumers (Node scripts, bots) bypass dapp-kit entirely and use `@sodax/sdk` directly with their own wallet implementation.
35
35
 
36
+ **Config reactivity.** `config` is tracked by reference - see [`recipes/setup.md § Config reactivity`](recipes/setup.md#config-reactivity) for the module-const vs `useMemo` patterns.
37
+
36
38
  ### `createSodaxQueryClient`
37
39
 
38
40
  Returns a `QueryClient` pre-wired with a `MutationCache.onError` hook for global mutation observability. Default behavior: logs every mutation failure to console as `[sodax] Mutation error: <error>`.
@@ -237,7 +239,7 @@ queryClient.invalidateQueries({ queryKey: ['dex', 'positionInfo', tokenId, poolK
237
239
 
238
240
  ## Hook organization
239
241
 
240
- ~95 hooks (41 mutations + ~50 queries + utilities) organized by feature domain in `src/hooks/`:
242
+ Hooks organized by feature domain in `src/hooks/`:
241
243
 
242
244
  ```
243
245
  hooks/
@@ -248,13 +250,13 @@ hooks/
248
250
  ├── swap/ # useQuote, useSwap, useStatus, useSwapAllowance, useSwapApprove,
249
251
  │ # useCancelSwap, useCreateLimitOrder, useCancelLimitOrder
250
252
  ├── mm/ # useSupply, useWithdraw, useBorrow, useRepay, useMMAllowance, useMMApprove,
251
- │ # reserves data hooks (13 hooks total)
253
+ │ # reserves data hooks
252
254
  ├── bridge/ # useBridge, useBridgeAllowance, useBridgeApprove, bridgeable amounts/tokens
253
- ├── staking/ # useStake, useUnstake, useInstantUnstake, useClaim, staking info hooks (~18)
254
- ├── dex/ # usePools, useDexDeposit, useDexWithdraw, liquidity hooks (~13)
255
- ├── bitcoin/ # useRadfiSession, fund/withdraw, UTXO management (~8)
256
- ├── backend/ # Intent tracking, swap submission, orderbook, money market position queries (~13)
257
- ├── partner/ # Partner fee claim, auto-swap preferences, token approval (6)
255
+ ├── staking/ # useStake, useUnstake, useInstantUnstake, useClaim, staking info hooks
256
+ ├── dex/ # usePools, useDexDeposit, useDexWithdraw, liquidity hooks
257
+ ├── bitcoin/ # useRadfiSession, fund/withdraw, UTXO management
258
+ ├── backend/ # Intent tracking, swap submission, orderbook, money market position queries
259
+ ├── partner/ # Partner fee claim, auto-swap preferences, token approval
258
260
  ├── recovery/ # useHubAssetBalances, useWithdrawHubAsset
259
261
  └── migrate/ # useMigrateIcxToSoda, useRevertMigrateSodaToIcx, useMigratebnUSD,
260
262
  # useMigrateBaln, useMigrationApprove, useMigrationAllowance
@@ -2,16 +2,16 @@
2
2
 
3
3
  Per-feature reference docs. Each file documents the hooks, params types, return types, and feature-specific gotchas — but doesn't include extended worked examples (those live in [`../recipes/`](../recipes/)).
4
4
 
5
- | File | Hook count | What's covered |
6
- |---|---|---|
7
- | [`swap.md`](swap.md) | 8 | Cross-chain swaps via the intent solver: `useQuote`, `useSwap`, allowance/approve, status polling, limit orders. |
8
- | [`money-market.md`](money-market.md) | 13 | Lending/borrowing on the cross-chain MM: `useSupply`, `useBorrow`, `useWithdraw`, `useRepay`, allowance/approve, reserves data hooks. |
9
- | [`staking.md`](staking.md) | ~18 | SODA → xSODA staking: `useStake`, `useUnstake`, `useInstantUnstake`, `useClaim`, `useCancelUnstake`, allowance/approve, info/ratio reads. |
10
- | [`bridge.md`](bridge.md) | 5 | Cross-chain token bridging: `useBridge`, allowance/approve, bridgeable amount/tokens. |
11
- | [`dex.md`](dex.md) | ~13 | Concentrated liquidity DEX: assets in/out, liquidity supply/decrease, claim rewards, position info, pool reads, param builders. |
12
- | [`migration.md`](migration.md) | 6 | Token migration: `useMigrateIcxToSoda`, `useRevertMigrateSodaToIcx`, `useMigratebnUSD`, `useMigrateBaln`, allowance/approve. |
13
- | [`bitcoin.md`](bitcoin.md) | ~8 | Radfi (dapp-kit-unique): session, trading wallet, fund/withdraw, UTXOs. |
14
- | [`auxiliary-services.md`](auxiliary-services.md) | ~30 | Partner fee claiming, recovery, backend queries (intent tracking, orderbook, MM data), shared utilities (xBalances, gas, trustlines). |
5
+ | File | What's covered |
6
+ |---|---|
7
+ | [`swap.md`](swap.md) | Cross-chain swaps via the intent solver: `useQuote`, `useSwap`, allowance/approve, status polling, limit orders. |
8
+ | [`money-market.md`](money-market.md) | Lending/borrowing on the cross-chain MM: `useSupply`, `useBorrow`, `useWithdraw`, `useRepay`, allowance/approve, reserves data hooks. |
9
+ | [`staking.md`](staking.md) | SODA → xSODA staking: `useStake`, `useUnstake`, `useInstantUnstake`, `useClaim`, `useCancelUnstake`, allowance/approve, info/ratio reads. |
10
+ | [`bridge.md`](bridge.md) | Cross-chain token bridging: `useBridge`, allowance/approve, bridgeable amount/tokens. |
11
+ | [`dex.md`](dex.md) | Concentrated liquidity DEX: assets in/out, liquidity supply/decrease, claim rewards, position info, pool reads, param builders. |
12
+ | [`migration.md`](migration.md) | Token migration: `useMigrateIcxToSoda`, `useRevertMigrateSodaToIcx`, `useMigratebnUSD`, `useMigrateBaln`, allowance/approve. |
13
+ | [`bitcoin.md`](bitcoin.md) | Radfi (dapp-kit-unique): session, trading wallet, fund/withdraw, UTXOs. |
14
+ | [`auxiliary-services.md`](auxiliary-services.md) | Partner fee claiming, recovery, backend queries (intent tracking, orderbook, MM data), shared utilities (xBalances, gas, trustlines). |
15
15
 
16
16
  ## Reference vs recipes
17
17
 
@@ -6,7 +6,7 @@ Pair: [`../../migration/features/auxiliary-services.md`](../../migration/feature
6
6
 
7
7
  ## Partner
8
8
 
9
- Partner fee claiming and auto-swap preferences. 6 hooks.
9
+ Partner fee claiming and auto-swap preferences.
10
10
 
11
11
  ```ts
12
12
  // @ai-snippets-skip
@@ -22,7 +22,7 @@ useFeeClaimSwap({ mutationOptions }); // Claim partner fees via sw
22
22
 
23
23
  ## Recovery
24
24
 
25
- Withdraw stuck hub-wallet assets back to a spoke chain. 2 hooks.
25
+ Withdraw stuck hub-wallet assets back to a spoke chain.
26
26
 
27
27
  ```ts
28
28
  // @ai-snippets-skip
@@ -32,7 +32,7 @@ useWithdrawHubAsset({ mutationOptions });
32
32
 
33
33
  ## Backend queries (read-only data)
34
34
 
35
- 12 hooks. No wallet connection required.
35
+ No wallet connection required.
36
36
 
37
37
  ### Intent tracking
38
38
 
@@ -29,10 +29,38 @@ useUserFormattedSummary({ params, queryOptions }); // Health factor, collate
29
29
  useUserReservesData({ params, queryOptions }); // Per-reserve user position
30
30
 
31
31
  // aTokens
32
- useAToken({ params, queryOptions }); // aToken metadata
33
- useATokensBalances({ params, queryOptions }); // aToken balances
32
+ useAToken({ params: { aToken }, queryOptions }); // aToken metadata (single)
33
+ useATokensBalances({ params: { aTokens, spokeChainKey, userAddress }, queryOptions }); // aToken balances (batched multicall)
34
34
  ```
35
35
 
36
+ ### Read-hook param shapes
37
+
38
+ ```ts
39
+ // @ai-snippets-skip
40
+ // useUserFormattedSummary / useUserReservesData — user-position queries
41
+ type UseUserFormattedSummaryParams = ReadHookParams<FormatUserSummaryResponse, {
42
+ spokeChainKey: SpokeChainKey | undefined;
43
+ userAddress: string | undefined;
44
+ }>;
45
+ // Same shape on useUserReservesData. The hook derives the hub wallet from (spokeChainKey, userAddress) internally.
46
+
47
+ // useAToken — single aToken metadata; FLAT (no chain/user fields)
48
+ type UseATokenParams = ReadHookParams<ATokenData, {
49
+ aToken: Address | string | undefined;
50
+ }>;
51
+ // ATokenData = Erc20Token & { chainKey: ChainKey }
52
+
53
+ // useATokensBalances — batched aToken balances
54
+ type UseATokensBalancesParams = ReadHookParams<Map<Address, bigint>, {
55
+ aTokens: readonly Address[];
56
+ spokeChainKey: SpokeChainKey | undefined;
57
+ userAddress: string | undefined;
58
+ }>;
59
+ // Returns a Map keyed by aToken address. The hook resolves the hub wallet from (spokeChainKey, userAddress).
60
+ ```
61
+
62
+ > **Read-side chain key is `spokeChainKey`, not `srcChainKey`.** Mutation hooks (`useSupply`/`useBorrow`/etc.) use `srcChainKey` because the request crosses chains and needs a source. Read hooks describe a user's position on a single spoke chain — the field is `spokeChainKey`. Applies to `useATokensBalances`, `useUserFormattedSummary`, and `useUserReservesData` (`useAToken` is metadata-only and takes neither). Don't grep-replace one for the other.
63
+
36
64
  ## Mutation params
37
65
 
38
66
  All four user mutations share the same TVars shape (only the `action` literal differs):
@@ -191,16 +191,17 @@ type MoneyMarketSupplyParams<K extends SpokeChainKey = SpokeChainKey> = {
191
191
  token: string;
192
192
  amount: bigint;
193
193
  action: 'supply';
194
- toChainId?: SpokeChainKey;
195
- toAddress?: string;
194
+ dstChainKey?: SpokeChainKey;
195
+ dstAddress?: string;
196
196
  };
197
197
 
198
- // Borrow / Withdraw / Repay follow the same shape with their respective `action` literal.
198
+ // Borrow / Withdraw / Repay follow the same shape with their respective `action` literal
199
+ // (`'borrow'` / `'withdraw'` / `'repay'`) and the same `src*` / `dst*` field names.
199
200
  ```
200
201
 
201
202
  ## Notes
202
203
 
203
204
  - **Borrow/withdraw skip approval** — `useMMAllowance` returns `true` automatically for these actions.
204
205
  - **Health factor < 1.0** means liquidation risk.
205
- - All operations support optional `toChainId` / `toAddress` (and `fromChainId` / `fromAddress` on borrow) for cross-chain delivery.
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.
206
207
  - Mutations throw on SDK failure — use `mutateAsyncSafe` for `Result<T>` ergonomics without `try/catch`.
@@ -47,6 +47,30 @@ export function Providers({ children }: { children: React.ReactNode }) {
47
47
  }
48
48
  ```
49
49
 
50
+ ### Config reactivity
51
+
52
+ `SodaxProvider`'s `config` prop is tracked by **reference**, not by value. The SDK is re-instantiated whenever the prop identity changes - resetting wagmi connection state, in-flight RPC, and any persisted state inside `useSodaxContext` consumers. Choose the pattern that matches your config source:
53
+
54
+ ```tsx
55
+ // @ai-snippets-skip — illustrative; uses placeholder values + JSX without surrounding imports
56
+ // ✅ Static config — module constant (preferred when nothing depends on runtime state).
57
+ const sodaxConfig: DeepPartial<SodaxConfig> = {
58
+ chains: { [ChainKeys.SONIC_MAINNET]: { rpcUrl: '...' } },
59
+ };
60
+
61
+ // ✅ Runtime-switchable config — useMemo with explicit deps.
62
+ // Re-runs only when listed deps change, so the SDK survives unrelated re-renders.
63
+ const sodaxConfig = useMemo(
64
+ () => ({ solver: solverConfigMap[solverEnv], chains: { ... } }),
65
+ [solverEnv], // SDK re-inits when solverEnv switches.
66
+ );
67
+
68
+ // ❌ Inline — new identity every parent render, SDK churns on every render.
69
+ <SodaxProvider config={{ chains: { ... } }}>
70
+ ```
71
+
72
+ Drive runtime config switches (solver env, feature flags, etc.) through `useMemo` deps - never remount `SodaxProvider` for them.
73
+
50
74
  ### Optional: Add Wallet Provider
51
75
 
52
76
  If you want to use `@sodax/wallet-sdk-react` for wallet connectivity, wrap `SodaxWalletProvider` inside `QueryClientProvider`:
@@ -99,3 +99,30 @@ function SwapButton() {
99
99
  ```
100
100
 
101
101
  This pattern is consistent across all features: `useSwap`, `useBridge`, `useSupply`, `useStake`, `useDexDeposit`, etc.
102
+
103
+ ## No type cast is needed — broad-union wiring just works
104
+
105
+ A common anti-pattern is reaching for `as any` / `as IEvmWalletProvider` when the runtime-typed `walletProvider` from `useWalletProvider({ xChainId })` is passed into a mutation hook. **That cast is not needed.** v2 accepts the broad-union wallet-provider type as long as the chain key on the payload and the wallet provider both come from the same runtime `xChainId` value.
106
+
107
+ ```tsx
108
+ // @ai-snippets-skip — illustrative anti-pattern vs correct
109
+ // ❌ ANTI-PATTERN — unnecessary cast
110
+ const walletProvider = useWalletProvider({ xChainId });
111
+ await swap({ params, walletProvider: walletProvider as any }); // don't
112
+ await swap({ params, walletProvider: walletProvider as IEvmWalletProvider }); // don't
113
+
114
+ // ✅ CORRECT — pass directly, TypeScript infers the relationship
115
+ const walletProvider = useWalletProvider({ xChainId });
116
+ if (!walletProvider) return; // narrow undefined first
117
+ await swap({ params, walletProvider });
118
+ ```
119
+
120
+ ### Why this works
121
+
122
+ - `useWalletProvider({ xChainId })` returns `GetWalletProviderType<typeof xChainId> | undefined`. When `xChainId` is a runtime value (e.g. from props/state typed `SpokeChainKey`), the return type is the **broad union** `IWalletProvider | undefined`, not `any`.
123
+ - Mutation hooks like `useSwap<K>()` default `K` to the broad `SpokeChainKey` union. Their `mutate` vars are typed `{ params: SwapParams<SpokeChainKey>, walletProvider: GetWalletProviderType<SpokeChainKey> }` — i.e. `walletProvider` is the same broad union.
124
+ - The two unions are structurally assignable. No cast required.
125
+
126
+ ### When the cast is actually needed
127
+
128
+ If you've narrowed `xChainId` to a literal (e.g. via `chainKey === ChainKeys.BSC_MAINNET` checks in a branch) and the mutation hook is also generic-narrowed, you'll get narrower types on both sides. Even there, the cast is usually unnecessary — TypeScript propagates the narrowed `K` through the hook's generic. Reach for a cast only when you can produce a real TS error message proving it's needed.
@@ -4,7 +4,7 @@ Lookup tables. Read while writing code; not a tutorial.
4
4
 
5
5
  | File | What's in it |
6
6
  |---|---|
7
- | [`hooks-index.md`](hooks-index.md) | Comprehensive hook table (95 hooks, organized by feature, with hook type and purpose). |
7
+ | [`hooks-index.md`](hooks-index.md) | Comprehensive hook table (organized by feature, with hook type and purpose). |
8
8
  | [`querykey-conventions.md`](querykey-conventions.md) | Mandatory queryKey/mutationKey shape rules + per-feature key tables. |
9
9
  | [`public-api.md`](public-api.md) | What `@sodax/dapp-kit` exports + import rules. |
10
10
  | [`glossary.md`](glossary.md) | Type aliases (`ReadHookParams`, `MutationHookParams`, `SafeUseMutationResult`, `MutationHookOptions`, etc.). |
@@ -132,6 +132,8 @@ App-level React component. Provides:
132
132
 
133
133
  Optional config: `<SodaxProvider config={DeepPartial<SodaxConfig>}>`. Without config, SDK uses packaged defaults.
134
134
 
135
+ Config is tracked by **reference** - see [`recipes/setup.md § Config reactivity`](../recipes/setup.md#config-reactivity) for module-const vs `useMemo` patterns.
136
+
135
137
  ### `createSodaxQueryClient`
136
138
 
137
139
  Factory for a `QueryClient` with `MutationCache.onError` pre-wired for global mutation observability. Optional — if you construct your own `QueryClient`, dapp-kit hooks still work; you just don't get the global observability seam.
@@ -1,6 +1,6 @@
1
1
  # Hooks index — `@sodax/dapp-kit` v2
2
2
 
3
- Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you know the feature you're building but don't remember the exact hook name.
3
+ Comprehensive hook table across 11 feature domains. Use this when you know the feature you're building but don't remember the exact hook name.
4
4
 
5
5
  ## Provider + context
6
6
 
@@ -11,7 +11,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
11
11
  | `useSodaxContext` | Utility | Access the `Sodax` SDK instance |
12
12
  | `useHubProvider` | Utility | Hub chain (Sonic) provider |
13
13
 
14
- ## Swap (8)
14
+ ## Swap
15
15
 
16
16
  | Hook | Type | Purpose |
17
17
  |---|---|---|
@@ -24,7 +24,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
24
24
  | `useCreateLimitOrder` | Mutation | Create a limit order (no deadline) |
25
25
  | `useCancelLimitOrder` | Mutation | Cancel a limit order |
26
26
 
27
- ## Money market (13)
27
+ ## Money market
28
28
 
29
29
  | Hook | Type | Purpose |
30
30
  |---|---|---|
@@ -43,7 +43,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
43
43
  | `useAToken` | Query | aToken metadata |
44
44
  | `useATokensBalances` | Query | aToken balances |
45
45
 
46
- ## Bridge (5)
46
+ ## Bridge
47
47
 
48
48
  | Hook | Type | Purpose |
49
49
  |---|---|---|
@@ -53,7 +53,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
53
53
  | `useGetBridgeableAmount` | Query | Max bridgeable amount between two `XToken`s |
54
54
  | `useGetBridgeableTokens` | Query | Tokens bridgeable to a destination chain |
55
55
 
56
- ## Staking (~18)
56
+ ## Staking
57
57
 
58
58
  | Hook | Type | Purpose |
59
59
  |---|---|---|
@@ -76,7 +76,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
76
76
  | `useInstantUnstakeRatio` | Query | Instant unstake rate |
77
77
  | `useConvertedAssets` | Query | xSODA → SODA conversion |
78
78
 
79
- ## DEX (~13)
79
+ ## DEX
80
80
 
81
81
  | Hook | Type | Purpose |
82
82
  |---|---|---|
@@ -97,7 +97,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
97
97
  | `useCreateSupplyLiquidityParams` | Param builder | Build tick-range + liquidity params |
98
98
  | `useCreateDecreaseLiquidityParams` | Param builder | Build decrease params from position state |
99
99
 
100
- ## Migration (6)
100
+ ## Migration
101
101
 
102
102
  | Hook | Type | Purpose |
103
103
  |---|---|---|
@@ -108,7 +108,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
108
108
  | `useMigrationApprove` | Mutation | Approve before migration (action-discriminated) |
109
109
  | `useMigrationAllowance` | Query | Approval check (action-discriminated) |
110
110
 
111
- ## Bitcoin / Radfi (~8)
111
+ ## Bitcoin / Radfi
112
112
 
113
113
  | Hook | Type | Purpose |
114
114
  |---|---|---|
@@ -122,7 +122,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
122
122
  | `useExpiredUtxos` | Query | Expired UTXOs (polls 60s) |
123
123
  | `useRenewUtxos` | Mutation | Renew expired UTXOs |
124
124
 
125
- ## Backend queries (~13)
125
+ ## Backend queries
126
126
 
127
127
  ### Intents
128
128
 
@@ -151,7 +151,7 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
151
151
  | `useBackendMoneyMarketAssetBorrowers` | Borrowers for an asset |
152
152
  | `useBackendAllMoneyMarketBorrowers` | All borrowers |
153
153
 
154
- ## Partner (6)
154
+ ## Partner
155
155
 
156
156
  | Hook | Type | Purpose |
157
157
  |---|---|---|
@@ -162,14 +162,14 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
162
162
  | `useSetSwapPreference` | Mutation | Set swap preference |
163
163
  | `useFeeClaimSwap` | Mutation | Claim partner fees via swap |
164
164
 
165
- ## Recovery (2)
165
+ ## Recovery
166
166
 
167
167
  | Hook | Type | Purpose |
168
168
  |---|---|---|
169
169
  | `useHubAssetBalances` | Query | Hub asset balances |
170
170
  | `useWithdrawHubAsset` | Mutation | Withdraw hub asset |
171
171
 
172
- ## Shared (~9)
172
+ ## Shared
173
173
 
174
174
  | Hook | Type | Purpose |
175
175
  |---|---|---|
@@ -183,10 +183,6 @@ Comprehensive hook table. ~95 hooks across 11 feature domains. Use this when you
183
183
  | `unwrapResult` | Internal | `Result<T>` → throw / return |
184
184
  | `toResult` | Internal | `Promise<T>` → `Result<T>` |
185
185
 
186
- ## Total
187
-
188
- 41 mutations + ~50 queries + utilities + provider = **~95 hooks**.
189
-
190
186
  ## Cross-references
191
187
 
192
188
  - [`../features/`](../features/) — per-feature reference docs (params types, return shapes, gotchas).
@@ -8,14 +8,14 @@ The package's `src/index.ts` re-exports four buckets:
8
8
 
9
9
  ```ts
10
10
  // @ai-snippets-skip
11
- export * from './hooks/index.js'; // ~95 hooks across 11 feature dirs
11
+ export * from './hooks/index.js'; // hooks across 11 feature dirs
12
12
  export * from './providers/index.js'; // SodaxProvider, createSodaxQueryClient
13
13
  export * from './utils/index.js'; // dex-utils param builders
14
14
  export * from '@sodax/sdk'; // FULL @sodax/sdk re-export
15
15
  ```
16
16
 
17
17
  Practical implications:
18
- - All ~95 hooks are importable from the root: `import { useSwap, useSupply, ... } from '@sodax/dapp-kit'`.
18
+ - All hooks are importable from the root: `import { useSwap, useSupply, ... } from '@sodax/dapp-kit'`.
19
19
  - All `@sodax/sdk` types are importable from `@sodax/dapp-kit` directly: `import { ChainKeys, type SodaxConfig, type CreateIntentParams } from '@sodax/dapp-kit'`.
20
20
  - You may also import directly from `@sodax/sdk` — both work, both are stable.
21
21
 
@@ -96,6 +96,8 @@ Full SDK-level detail: [`../../../../sdk/ai-exported/migration/breaking-changes/
96
96
  + }}>
97
97
  ```
98
98
 
99
+ **Config is tracked by reference in v2.** See [`../../integration/recipes/setup.md § Config reactivity`](../../integration/recipes/setup.md#config-reactivity) for the module-const vs `useMemo` patterns. Drive runtime config switches (e.g. solver env) through `useMemo` deps, not by remounting the provider.
100
+
99
101
  Other v1 fields renamed or restructured:
100
102
 
101
103
  | v1 | v2 |
@@ -4,7 +4,7 @@ Pair: [`../../integration/features/auxiliary-services.md`](../../integration/fea
4
4
 
5
5
  Smaller surfaces grouped together: partner, recovery, backend queries, shared utilities. Most changes are mechanical — single-object params, mutateAsyncSafe — same as the other features.
6
6
 
7
- ## Partner (6 hooks)
7
+ ## Partner
8
8
 
9
9
  ```diff
10
10
  - const claim = useFeeClaimSwap(spokeProvider);
@@ -18,7 +18,7 @@ Smaller surfaces grouped together: partner, recovery, backend queries, shared ut
18
18
 
19
19
  `useFetchAssetsBalances`, `useGetAutoSwapPreferences`, `useIsTokenApproved` — convert to single-object query shape.
20
20
 
21
- ## Recovery (2 hooks)
21
+ ## Recovery
22
22
 
23
23
  ```diff
24
24
  - const withdraw = useWithdrawHubAsset(spokeProvider);
@@ -26,7 +26,7 @@ Smaller surfaces grouped together: partner, recovery, backend queries, shared ut
26
26
  + await withdraw({ params, walletProvider });
27
27
  ```
28
28
 
29
- ## Backend queries (~13 hooks)
29
+ ## Backend queries
30
30
 
31
31
  Read-only. No `walletProvider` involved. Convert to single-object query shape.
32
32
 
@@ -9,9 +9,11 @@ Pair: [`../../integration/features/bridge.md`](../../integration/features/bridge
9
9
  1. **`CreateBridgeIntentParams` field renames** (SDK-leakage):
10
10
  - `srcChainId` → `srcChainKey`
11
11
  - `dstChainId` → `dstChainKey`
12
- - `recipient` → `dstAddress`
13
- - **NEW required**: `srcAddress`
14
- 2. **`useBridge` return is `TxHashPair`, not a tuple.** v1 may have returned `[spokeTxHash, hubTxHash]`; v2 returns `{ srcChainTxHash, dstChainTxHash }`. Don't destructure as array.
12
+ - `srcAsset` → `srcToken`
13
+ - `dstAsset` `dstToken`
14
+ - `recipient` is **unchanged** (stays `recipient` in v2 it is NOT renamed to `dstAddress`; the only `dstAddress`-shaped field in v2 lives on money-market params, not bridge)
15
+ - **NEW required**: `srcAddress` (the user's spoke-side sender address, distinct from `recipient` which is the destination)
16
+ 2. **`bridge()` return shape changed.** v1 was `Promise<string>` (a single tx hash that threw on error). v2 returns `Promise<Result<TxHashPair, BridgeOrchestrationError>>` where `TxHashPair = { srcChainTxHash: string; dstChainTxHash: string }`. **This is the shape at the direct-SDK boundary** (`sodax.bridge.bridge(...)`); the `useBridge` hook does not adapt the inner object, it only unwraps the `Result` wrapper (so the hook surfaces `TxHashPair` directly via `data` / `mutateAsync` / `mutateAsyncSafe`'s `value`). Don't destructure as a tuple — there was no `[spokeTxHash, hubTxHash]` form in either v1 or v2.
15
17
  3. **`useGetBridgeableAmount` reshape.** v1 took `(srcChainId, srcAsset, dstChainId, dstAsset)`. v2 takes `{ from: XToken, to: XToken }` — each `XToken` carries its own `chainKey`.
16
18
  4. **`useGetBridgeableAmount` return value is `BridgeLimit`, not bare `bigint`.** Access `.value.amount` and `.value.decimals`.
17
19
  5. **`useGetBridgeableTokens` is sync now** in the SDK. The hook still returns `UseQueryResult` but the underlying SDK call doesn't fire RPC — it's config-derived.
@@ -29,7 +31,8 @@ Pair: [`../../integration/features/bridge.md`](../../integration/features/bridge
29
31
 
30
32
  const handleBridge = async () => {
31
33
  + if (!walletProvider) return;
32
- - const result = await bridge.mutateAsync({
34
+ - // v1: single tx hash, throws on failure
35
+ - const txHash: string = await bridge.mutateAsync({
33
36
  - params: {
34
37
  - srcChainId: BASE_MAINNET_CHAIN_ID,
35
38
  - srcAsset: '0x...',
@@ -39,28 +42,62 @@ Pair: [`../../integration/features/bridge.md`](../../integration/features/bridge
39
42
  - recipient: '0x...',
40
43
  - },
41
44
  - });
42
- - if (result.ok) {
43
- - const [spokeTxHash, hubTxHash] = result.value;
44
- - /* ... */
45
- - }
46
45
  + const result = await bridge({
47
46
  + params: {
48
47
  + srcChainKey: ChainKeys.BASE_MAINNET,
49
- + srcAddress, // NEW: required
50
- + srcAsset: '0x...',
48
+ + srcAddress, // NEW: required (your spoke-side sender)
49
+ + srcToken: '0x...', // RENAMED from `srcAsset`
51
50
  + amount: 1_000_000n,
52
51
  + dstChainKey: ChainKeys.POLYGON_MAINNET,
53
- + dstAsset: '0x...',
54
- + dstAddress: '0x...', // RENAMED from `recipient`
52
+ + dstToken: '0x...', // RENAMED from `dstAsset`
53
+ + recipient: '0x...', // UNCHANGED destination receiver
55
54
  + },
56
55
  + walletProvider,
57
56
  + });
58
57
  + if (!result.ok) return;
59
- + const { srcChainTxHash, dstChainTxHash } = result.value; // OBJECT, not tuple
58
+ + const { srcChainTxHash, dstChainTxHash } = result.value; // TxHashPair object, not [a, b]
60
59
  };
61
60
  }
62
61
  ```
63
62
 
63
+ ### Calling `sodax.bridge.bridge()` directly (no hook)
64
+
65
+ If you call the SDK directly inside a custom `useMutation` (instead of `useBridge`), the return shape is **exactly the same** — the dapp-kit hook only unwraps the `Result` wrapper, it does not reshape `TxHashPair`:
66
+
67
+ ```diff
68
+ - // v1 direct-SDK call:
69
+ - const txHash: string = await sodax.bridge.bridge({ params: { /* v1 shape */ }, spokeProvider });
70
+ + // v2 direct-SDK call:
71
+ + const result = await sodax.bridge.bridge({
72
+ + params: {
73
+ + srcChainKey: ChainKeys.BASE_MAINNET,
74
+ + srcAddress,
75
+ + srcToken: '0x...',
76
+ + amount: 1_000_000n,
77
+ + dstChainKey: ChainKeys.POLYGON_MAINNET,
78
+ + dstToken: '0x...',
79
+ + recipient: '0x...',
80
+ + },
81
+ + raw: false,
82
+ + walletProvider,
83
+ + });
84
+ + // Type: Result<TxHashPair, BridgeOrchestrationError>
85
+ + if (!result.ok) {
86
+ + // result.error is a SodaxError<C> with feature: 'bridge'
87
+ + return;
88
+ + }
89
+ + const { srcChainTxHash, dstChainTxHash } = result.value; // same shape as the hook
90
+ ```
91
+
92
+ The hook equivalence in code:
93
+
94
+ ```ts
95
+ // @ai-snippets-skip
96
+ // What `useBridge` does internally (sketch):
97
+ // mutationFn: async vars => unwrapResult(await sodax.bridge.bridge({ ...vars, raw: false }))
98
+ // `unwrapResult` throws on `!ok` and returns `value` on `ok` — that's the only adapter.
99
+ ```
100
+
64
101
  ### `useGetBridgeableAmount` — params + return reshape
65
102
 
66
103
  ```diff
@@ -10,7 +10,7 @@ Pair: [`../../integration/features/money-market.md`](../../integration/features/
10
10
  2. **`MoneyMarketSupplyParams<K>` (and the four similar action-param types) gained required `srcChainKey` + `srcAddress`** — SDK-leakage.
11
11
  3. **`useMMAllowance` auto-skips on-chain checks for borrow/withdraw** — v1 may have had this too, but v2 returns `true` synchronously for those actions instead of issuing a no-op RPC.
12
12
  4. **`useMMApprove` returns standard `SafeUseMutationResult`** — `isLoading` → `isPending`.
13
- 5. **Reserve data hooks renamed `address` → `userAddress`** in some queries (`useUserFormattedSummary`, `useUserReservesData`).
13
+ 5. **Position / aToken-balance hooks renamed `address` → `userAddress`.** Applies to `useUserFormattedSummary`, `useUserReservesData`, **and `useATokensBalances`** (which also drops `spokeProvider` and adds a required `spokeChainKey` alongside `aTokens` + `userAddress`). `useAToken` is unaffected — it takes only `{ aToken }`.
14
14
 
15
15
  ## Per-method delta
16
16
 
@@ -45,7 +45,25 @@ Pair: [`../../integration/features/money-market.md`](../../integration/features/
45
45
  }
46
46
  ```
47
47
 
48
- `useBorrow`, `useWithdraw`, `useRepay` follow the same pattern with their respective `action` literals (`'borrow'` / `'withdraw'` / `'repay'`). Borrow + repay can specify optional `dstChainKey` / `dstAddress` for cross-chain delivery.
48
+ `useBorrow`, `useWithdraw`, `useRepay` follow the same pattern with their respective `action` literals (`'borrow'` / `'withdraw'` / `'repay'`). **All four MM action params share an identical field shape** — only the `action` literal differs:
49
+
50
+ ```ts
51
+ // @ai-snippets-skip
52
+ // MoneyMarketSupplyParams<K> | MoneyMarketBorrowParams<K> | MoneyMarketWithdrawParams<K> | MoneyMarketRepayParams<K>
53
+ {
54
+ srcChainKey: K; // required — where the user signs / funds come from
55
+ srcAddress: string; // required — user's spoke-side address on srcChainKey
56
+ token: string; // token on srcChainKey (supply/repay) or on dstChainKey (borrow/withdraw)
57
+ amount: bigint;
58
+ action: 'supply' | 'borrow' | 'withdraw' | 'repay';
59
+ dstChainKey?: SpokeChainKey; // optional — defaults to srcChainKey (same-chain)
60
+ dstAddress?: string; // optional — defaults to srcAddress (same-chain)
61
+ }
62
+ ```
63
+
64
+ Cross-chain delivery via `dstChainKey` / `dstAddress` is supported on **all four** actions, not just borrow/repay. Omit both for same-chain operations.
65
+
66
+ > **Porting note** — v2 does NOT use `fromChainKey` / `fromAddress` / `toChainKey` / `toAddress` (or `fromChainId` / `toChainId`) on any MM action. Borrow and repay use the **same** `src*` / `dst*` field names as supply and withdraw — the v2 type system unified the cross-chain shape across all four actions. If your v1 call sites or app types carry `from*` / `to*` naming for the spend-chain vs. debt-chain, rename to `src*` / `dst*` (e.g. `fromChainKey → srcChainKey`, `toChainKey → dstChainKey`). See the SDK migration doc cross-link below for explicit borrow/repay diff examples.
49
67
 
50
68
  ### `useMMAllowance` — auto-skip
51
69
 
@@ -82,11 +100,35 @@ User-position hooks renamed param fields:
82
100
  + const { data } = useUserFormattedSummary({ params: { spokeChainKey, userAddress } });
83
101
  ```
84
102
 
103
+ Same shape on `useUserReservesData`.
104
+
105
+ ### `useATokensBalances`
106
+
107
+ ```diff
108
+ - const { data: balances } = useATokensBalances({ aTokens, spokeProvider, userAddress });
109
+ + const { data: balances } = useATokensBalances({
110
+ + params: {
111
+ + aTokens, // readonly Address[]
112
+ + spokeChainKey, // SpokeChainKey — NOT `srcChainKey`
113
+ + userAddress, // string — spoke-side user address (renamed from `address` if you're porting from any earlier shape)
114
+ + },
115
+ + });
116
+ + // data: Map<Address, bigint> | undefined (already unwrapped — hook throws on SDK !ok)
117
+ ```
118
+
119
+ Three things to verify when porting:
120
+
121
+ - `spokeProvider` is gone — the hook derives the hub wallet internally from `(spokeChainKey, userAddress)` via `EvmHubProvider.getUserHubWalletAddress`.
122
+ - The chain-key field is **`spokeChainKey`**, not `srcChainKey`. `src*` names belong to mutation params (`useSupply`/`useBorrow`/etc.) — read hooks for a single-chain position use `spokeChainKey`.
123
+ - The user-address field is **`userAddress`**, not `address` — same rename as `useUserFormattedSummary` and `useUserReservesData`.
124
+
125
+ `useAToken` (metadata-only) is unaffected by the user/chain renames — it takes only `{ aToken }`.
126
+
85
127
  ## Pitfalls
86
128
 
87
129
  1. **`srcAddress` is the user's spoke-side address**, not the hub address. Hub wallet is derived internally.
88
130
  2. **`useMMAllowance` returns `true` instantly for borrow/withdraw** — don't wait on the query state. Branch on `isApproved` directly.
89
- 3. **Cross-chain borrow/repay**: omit `dstChainKey` / `dstAddress` for same-chain. Don't pass `dstChainKey === srcChainKey` (let the default kick in).
131
+ 3. **Cross-chain delivery (all four actions)**: omit `dstChainKey` / `dstAddress` for same-chain. Don't pass `dstChainKey === srcChainKey` (let the default kick in). The field names are `src*` / `dst*` on **every** MM action — there is no `from*` / `to*` variant.
90
132
  4. **`MoneyMarketSupplyParams<K>` is now generic.** Use `as const` on `srcChainKey` for narrowing: `srcChainKey: ChainKeys.BASE_MAINNET as const`.
91
133
 
92
134
  ## Cross-references