@sodax/dapp-kit 2.0.0-rc.2 → 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.
@@ -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>`.
@@ -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.
@@ -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.
@@ -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 |
@@ -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
@@ -27,7 +27,9 @@ Pair: [`../../integration/features/swap.md`](../../integration/features/swap.md)
27
27
  + if (!walletProvider) return;
28
28
  - const result = await swap.mutateAsync({ params: intentParams });
29
29
  - if (result.ok) {
30
- - const { intent, intentDeliveryInfo } = result.value;
30
+ - // v1: result.value was a TUPLE — destructure positionally
31
+ - const [executionResponse] = result.value;
32
+ - const txHash = executionResponse.intent_hash;
31
33
  - /* ... */
32
34
  - } else {
33
35
  - toast.error(result.error.message);
@@ -37,12 +39,25 @@ Pair: [`../../integration/features/swap.md`](../../integration/features/swap.md)
37
39
  + toast.error(result.error instanceof Error ? result.error.message : 'Swap failed');
38
40
  + return;
39
41
  + }
40
- + const { intent, intentDeliveryInfo } = result.value;
42
+ + // v2: result.value is a NAMED OBJECT (SwapResponse) — destructure by name
43
+ + const { solverExecutionResponse, intent, intentDeliveryInfo } = result.value;
44
+ + const intentHash = solverExecutionResponse.intent_hash; // protocol-level intent id (v1 ≈ executionResponse.intent_hash)
45
+ + const spokeTxHash = intentDeliveryInfo.srcTxHash; // on-chain tx hash on srcChainKey — use for tx-history display
41
46
  + /* ... */
42
47
  };
43
48
  }
44
49
  ```
45
50
 
51
+ #### `SwapResponse` field map (`result.value`)
52
+
53
+ | Field | Type | Use it for |
54
+ |---|---|---|
55
+ | `solverExecutionResponse` | `{ answer: 'OK'; intent_hash: Hex }` | Protocol-level intent identifier. **`solverExecutionResponse.intent_hash` is the v2 equivalent of v1's `executionResponse.intent_hash`.** Pass it to `useStatus({ params: { intentTxHash } })` to poll execution status. |
56
+ | `intent` | `Intent` | Intent metadata (`intentId: bigint`, `creator`, tokens, amounts, `srcChain`/`dstChain` as `IntentRelayChainId` bigints). **No transaction hashes on this object** — don't read `intent.intent_hash` (does not exist) or `intent.tx_hash`. |
57
+ | `intentDeliveryInfo` | `{ srcChainKey, srcTxHash, srcAddress, dstChainKey, dstTxHash, dstAddress }` | **On-chain transaction hashes.** `srcTxHash` is the spoke-chain tx on `srcChainKey` (the one that typically feeds a user-facing transaction history); `dstTxHash` is the delivery tx on `dstChainKey`. |
58
+
59
+ **Porting `[executionResponse] = result.value` (v1 tuple) → v2:** rename to `{ solverExecutionResponse } = result.value`. Any `executionResponse.intent_hash` read becomes `solverExecutionResponse.intent_hash`. If the value was being used as the **transaction hash** in a transaction-history row (not as the intent identifier), switch to `intentDeliveryInfo.srcTxHash` instead — `intent_hash` and `srcTxHash` are not interchangeable.
60
+
46
61
  ### `useSwapApprove` — return shape
47
62
 
48
63
  ```diff
@@ -42,28 +42,25 @@ for (const file of project.getSourceFiles('src/**/*.{ts,tsx}')) {
42
42
  await project.save();
43
43
  ```
44
44
 
45
- ## Codemod 2: useSpokeProvider deletion
45
+ ## Codemod 2: useSpokeProvider deletion (2-arg → 1-arg)
46
46
 
47
- `useSpokeProvider` is gone in v2. Delete the import + usage; replace with `useWalletProvider` from `@sodax/wallet-sdk-react`.
47
+ `useSpokeProvider` is gone in v2. Replace with `useWalletProvider` from `@sodax/wallet-sdk-react`.
48
48
 
49
- ```bash
50
- # 1. Find all usages first.
51
- grep -rE '\buseSpokeProvider\b' src/
52
-
53
- # 2. Manual delete + rewrite each (no safe sed for this — context varies).
54
- ```
55
-
56
- Per call site, the rewrite:
49
+ v1 had **two positional args**: `useSpokeProvider(spokeChainId, walletProvider)`. v2 collapses to a single options object: `useWalletProvider({ xChainId })`. The second v1 argument is **dropped entirely** — v2 resolves the wallet provider from the wallet-sdk-react store internally based on `xChainId`. If your v1 code constructed a wallet provider separately to pass in, delete that construction code too.
57
50
 
58
51
  ```diff
52
+ - // v1 — 2 positional args (chainKey + walletProvider)
59
53
  - import { useSpokeProvider } from '@sodax/dapp-kit';
60
- - const spokeProvider = useSpokeProvider({ chainId: BSC_MAINNET_CHAIN_ID });
54
+ - const spokeProvider = useSpokeProvider(srcChainId, walletProvider);
55
+
56
+ + // v2 — 1 options arg, no external walletProvider
61
57
  + import { useWalletProvider } from '@sodax/wallet-sdk-react';
62
- + import { ChainKeys } from '@sodax/sdk';
63
- + const walletProvider = useWalletProvider({ xChainId: ChainKeys.BSC_MAINNET });
58
+ + const walletProvider = useWalletProvider({ xChainId: srcChainId });
64
59
  ```
65
60
 
66
- Then update consumers of `spokeProvider` to use `walletProvider` instead usually inside `mutate(vars)` payloads, sometimes inside query hook params.
61
+ A naive single-pass regex over `useSpokeProvider\(([^)]+)\)` eats both args and produces invalid `useWalletProvider({ xChainId: chainKey, walletProvider })`. Do the rewrite in two passes: first the call shape (drop the second positional arg), then the import (from `@sodax/dapp-kit` `@sodax/wallet-sdk-react`). A small number of v1 callers used the single-arg form `useSpokeProvider(chainId)` handle those separately since they don't have a second arg to drop.
62
+
63
+ Update downstream consumers of `spokeProvider` to use `walletProvider` instead — usually inside `mutate(vars)` payloads or query hook params. The field name on payloads also renamed `spokeProvider:` → `walletProvider:` (see `@sodax/sdk/ai-exported/migration/checklist.md` step 7b).
67
64
 
68
65
  ## Codemod 3: invalidate*Queries utilities deletion
69
66
 
@@ -29,6 +29,8 @@ These keep their name but their TypeScript signature is different. Usually requi
29
29
  | `useMMAllowance` | Query params: `{ params: { payload: MoneyMarketParams<K> } }`. For `'borrow'` / `'withdraw'` actions: `enabled: false` — `data` stays `undefined`. |
30
30
  | `useUserReservesData` | Param key rename: `address → userAddress`; chain key is `spokeChainKey`. |
31
31
  | `useUserFormattedSummary` | Same. |
32
+ | `useATokensBalances` | Params: `{ aTokens, spokeProvider, userAddress } → { params: { aTokens, spokeChainKey, userAddress } }`. `spokeProvider` dropped; chain key is **`spokeChainKey`** (NOT `srcChainKey`); user field is **`userAddress`** (NOT `address`). Data: `Map<Address, bigint> \| undefined` (already unwrapped). |
33
+ | `useAToken` | Unaffected by the position-hook renames — takes only `{ aToken }`. Data: `Erc20Token & { chainKey }` (`hubAsset` / `vault` NOT included). |
32
34
  | All staking mutations | Same as MM. Plus dedicated approve hooks per token (`useStakeApprove` ↔ `useUnstakeApprove` ↔ `useInstantUnstakeApprove`). |
33
35
  | `useStakeRatio` | Return: `Result<bigint> → [xSodaAmount, previewDepositAmount]` (unwrapped tuple — NOT Result-wrapped in v2). |
34
36
  | `useStakingInfo` / `useUnstakingInfo` / `useUnstakingInfoWithPenalty` / `useStakingConfig` / `useInstantUnstakeRatio` / `useConvertedAssets` | All unwrapped in v2 (hooks throw on SDK `!ok`). Branch on `isError`/`error`, not `data?.ok`. |
package/dist/index.cjs CHANGED
@@ -2484,10 +2484,9 @@ function useClaimRewards({
2484
2484
  });
2485
2485
  }
2486
2486
  var SodaxProvider = ({ children, config }) => {
2487
- const configRef = react.useRef(config);
2488
- const frozen = configRef.current;
2489
- const sodax = react.useMemo(() => new sdk.Sodax(frozen), [frozen]);
2490
- return /* @__PURE__ */ jsxRuntime.jsx(SodaxContext.Provider, { value: { sodax }, children });
2487
+ const sodax = react.useMemo(() => new sdk.Sodax(config), [config]);
2488
+ const contextValue = react.useMemo(() => ({ sodax }), [sodax]);
2489
+ return /* @__PURE__ */ jsxRuntime.jsx(SodaxContext.Provider, { value: contextValue, children });
2491
2490
  };
2492
2491
  var defaultOnMutationError = (error) => {
2493
2492
  console.error("[sodax] Mutation error:", error);