@sodax/wallet-sdk-react 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.
- package/README.md +12 -5
- package/dist/{chunk-BKJB527E.mjs → chunk-3QETHO6P.mjs} +1 -3
- package/dist/{chunk-PJLEJVAU.mjs → chunk-42LTUHMZ.mjs} +1 -3
- package/dist/{chunk-NY7U7OJW.mjs → chunk-7V7O3Q7Y.mjs} +0 -2
- package/dist/{chunk-BXJLBR4G.mjs → chunk-C6M34IVL.mjs} +2 -4
- package/dist/{chunk-XZ7CHO2S.mjs → chunk-FSOGMSJH.mjs} +2 -4
- package/dist/{chunk-X2MHIWXO.mjs → chunk-IFXZQW4C.mjs} +0 -2
- package/dist/{chunk-7ULB6DW4.mjs → chunk-JQ4H4GJ5.mjs} +3 -5
- package/dist/{chunk-N5A2TMF6.mjs → chunk-LKSSME2J.mjs} +2 -4
- package/dist/{chunk-PLCA4ZDJ.mjs → chunk-LUKR7YKV.mjs} +54 -30
- package/dist/{chunk-MXZVF5HR.mjs → chunk-NAKCAL2M.mjs} +0 -2
- package/dist/chunk-QMXBY3UI.mjs +1 -0
- package/dist/{chunk-MAQ47Q52.mjs → chunk-TACW7Z4D.mjs} +0 -2
- package/dist/{chunk-2BOUGCJ7.mjs → chunk-WPZOLGVB.mjs} +4 -6
- package/dist/{chunk-66BAUK56.mjs → chunk-X7BHR7WS.mjs} +2 -4
- package/dist/{chunk-E5IAZ7E6.mjs → chunk-Z5GXDHGL.mjs} +9 -5
- package/dist/{config-OlnzyEUE.d.ts → config-GVKK8IfY.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +20 -31
- package/dist/xchains/bitcoin/index.mjs +14 -16
- package/dist/xchains/evm/index.d.ts +1 -1
- package/dist/xchains/evm/index.mjs +3 -5
- package/dist/xchains/icon/index.mjs +5 -7
- package/dist/xchains/injective/index.mjs +3 -5
- package/dist/xchains/near/index.mjs +4 -6
- package/dist/xchains/solana/index.mjs +5 -7
- package/dist/xchains/stacks/index.mjs +3 -5
- package/dist/xchains/stellar/index.mjs +4 -6
- package/dist/xchains/sui/index.mjs +5 -7
- package/docs/ADDING_A_NEW_CHAIN.md +1 -1
- package/docs/SUB_PATH_EXPORTS.md +14 -42
- package/package.json +32 -24
- package/ai-exported/AGENTS.md +0 -122
- package/ai-exported/integration/README.md +0 -102
- package/ai-exported/integration/ai-rules.md +0 -136
- package/ai-exported/integration/architecture.md +0 -181
- package/ai-exported/integration/examples/01-minimal-evm.tsx +0 -75
- package/ai-exported/integration/examples/02-multi-chain-modal.tsx +0 -169
- package/ai-exported/integration/examples/03-nextjs-app-router.tsx +0 -99
- package/ai-exported/integration/examples/04-walletconnect-setup.tsx +0 -89
- package/ai-exported/integration/examples/README.md +0 -29
- package/ai-exported/integration/recipes/batch-operations.md +0 -223
- package/ai-exported/integration/recipes/bridge-to-sdk.md +0 -164
- package/ai-exported/integration/recipes/chain-detection.md +0 -254
- package/ai-exported/integration/recipes/connect-button.md +0 -156
- package/ai-exported/integration/recipes/multi-chain-modal.md +0 -199
- package/ai-exported/integration/recipes/setup.md +0 -160
- package/ai-exported/integration/recipes/sign-message.md +0 -137
- package/ai-exported/integration/recipes/sub-path-imports.md +0 -95
- package/ai-exported/integration/recipes/switch-chain.md +0 -141
- package/ai-exported/integration/recipes/walletconnect-setup.md +0 -139
- package/ai-exported/integration/reference/api-surface.md +0 -175
- package/ai-exported/integration/reference/chain-support.md +0 -78
- package/ai-exported/integration/reference/connectors.md +0 -74
- package/ai-exported/integration/reference/hooks.md +0 -204
- package/ai-exported/integration/reference/wallet-brands.md +0 -106
- package/ai-exported/migration/README.md +0 -49
- package/ai-exported/migration/ai-rules.md +0 -144
- package/ai-exported/migration/breaking-changes.md +0 -305
- package/ai-exported/migration/checklist.md +0 -159
- package/ai-exported/migration/recipes/connect-button.md +0 -166
- package/ai-exported/migration/recipes/multi-chain-modal.md +0 -244
- package/ai-exported/migration/recipes/ssr-setup.md +0 -164
- package/ai-exported/migration/recipes/walletconnect-migration.md +0 -168
- package/ai-exported/migration/reference/components.md +0 -73
- package/ai-exported/migration/reference/config.md +0 -325
- package/ai-exported/migration/reference/hooks.md +0 -323
- package/ai-exported/migration/reference/imports.md +0 -157
- package/dist/chunk-2BOUGCJ7.mjs.map +0 -1
- package/dist/chunk-66BAUK56.mjs.map +0 -1
- package/dist/chunk-7ULB6DW4.mjs.map +0 -1
- package/dist/chunk-BKJB527E.mjs.map +0 -1
- package/dist/chunk-BXJLBR4G.mjs.map +0 -1
- package/dist/chunk-E5IAZ7E6.mjs.map +0 -1
- package/dist/chunk-MAQ47Q52.mjs.map +0 -1
- package/dist/chunk-MXZVF5HR.mjs.map +0 -1
- package/dist/chunk-N5A2TMF6.mjs.map +0 -1
- package/dist/chunk-NY7U7OJW.mjs.map +0 -1
- package/dist/chunk-PJLEJVAU.mjs.map +0 -1
- package/dist/chunk-PLCA4ZDJ.mjs.map +0 -1
- package/dist/chunk-TZMKDXFA.mjs +0 -3
- package/dist/chunk-TZMKDXFA.mjs.map +0 -1
- package/dist/chunk-X2MHIWXO.mjs.map +0 -1
- package/dist/chunk-XZ7CHO2S.mjs.map +0 -1
- package/dist/index.cjs +0 -3337
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/xchains/bitcoin/index.cjs +0 -1927
- package/dist/xchains/bitcoin/index.cjs.map +0 -1
- package/dist/xchains/bitcoin/index.mjs.map +0 -1
- package/dist/xchains/evm/index.cjs +0 -316
- package/dist/xchains/evm/index.cjs.map +0 -1
- package/dist/xchains/evm/index.mjs.map +0 -1
- package/dist/xchains/icon/index.cjs +0 -311
- package/dist/xchains/icon/index.cjs.map +0 -1
- package/dist/xchains/icon/index.mjs.map +0 -1
- package/dist/xchains/injective/index.cjs +0 -223
- package/dist/xchains/injective/index.cjs.map +0 -1
- package/dist/xchains/injective/index.mjs.map +0 -1
- package/dist/xchains/near/index.cjs +0 -190
- package/dist/xchains/near/index.cjs.map +0 -1
- package/dist/xchains/near/index.mjs.map +0 -1
- package/dist/xchains/solana/index.cjs +0 -186
- package/dist/xchains/solana/index.cjs.map +0 -1
- package/dist/xchains/solana/index.mjs.map +0 -1
- package/dist/xchains/stacks/index.cjs +0 -240
- package/dist/xchains/stacks/index.cjs.map +0 -1
- package/dist/xchains/stacks/index.mjs.map +0 -1
- package/dist/xchains/stellar/index.cjs +0 -322
- package/dist/xchains/stellar/index.cjs.map +0 -1
- package/dist/xchains/stellar/index.mjs.map +0 -1
- package/dist/xchains/sui/index.cjs +0 -248
- package/dist/xchains/sui/index.cjs.map +0 -1
- package/dist/xchains/sui/index.mjs.map +0 -1
- package/skills/SKILLS.md +0 -84
- package/skills/bridge-to-sdk.md +0 -148
- package/skills/connect-button.md +0 -116
- package/skills/evm-only-walletconnect.md +0 -111
- package/skills/multi-chain-modal.md +0 -178
- package/skills/setup.md +0 -107
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
# Recipe: Migrate a Multi-Chain Wallet Modal
|
|
2
|
-
|
|
3
|
-
Replaces a hand-rolled v1 modal (chain picker → connector picker → connect) with v2's headless `useWalletModal` state machine. Self-contained — apply this recipe without reading other files.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## When to use this recipe
|
|
8
|
-
|
|
9
|
-
Apply when the user's v1 code:
|
|
10
|
-
|
|
11
|
-
- Maintains its own state for "which chain is the user picking"
|
|
12
|
-
- Calls `useXConnectors(<chain>)` per chain in a loop
|
|
13
|
-
- Calls `useXConnect` directly from a click handler
|
|
14
|
-
- Tracks a `connecting` / `success` / `error` state by hand
|
|
15
|
-
|
|
16
|
-
v2 ships these primitives so the consumer focuses only on UI rendering.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Before (v1, simplified)
|
|
21
|
-
|
|
22
|
-
```diff
|
|
23
|
-
- 'use client';
|
|
24
|
-
-
|
|
25
|
-
- import { useState } from 'react';
|
|
26
|
-
- import {
|
|
27
|
-
- useXConnectors,
|
|
28
|
-
- useXConnect,
|
|
29
|
-
- useXDisconnect,
|
|
30
|
-
- type ChainType,
|
|
31
|
-
- } from '@sodax/wallet-sdk-react';
|
|
32
|
-
-
|
|
33
|
-
- const SUPPORTED_CHAINS: ChainType[] = ['EVM', 'SUI', 'SOLANA', 'ICON'];
|
|
34
|
-
-
|
|
35
|
-
- export function WalletModal({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
36
|
-
- const [pickedChain, setPickedChain] = useState<ChainType | null>(null);
|
|
37
|
-
- const [connectingId, setConnectingId] = useState<string | null>(null);
|
|
38
|
-
- const [error, setError] = useState<Error | null>(null);
|
|
39
|
-
-
|
|
40
|
-
- const evmConnectors = useXConnectors('EVM');
|
|
41
|
-
- const suiConnectors = useXConnectors('SUI');
|
|
42
|
-
- const solanaConnectors = useXConnectors('SOLANA');
|
|
43
|
-
- const iconConnectors = useXConnectors('ICON');
|
|
44
|
-
-
|
|
45
|
-
- const { mutateAsync: connect } = useXConnect();
|
|
46
|
-
- const disconnect = useXDisconnect();
|
|
47
|
-
-
|
|
48
|
-
- const connectorsByChain: Record<ChainType, ReturnType<typeof useXConnectors>> = {
|
|
49
|
-
- EVM: evmConnectors,
|
|
50
|
-
- SUI: suiConnectors,
|
|
51
|
-
- SOLANA: solanaConnectors,
|
|
52
|
-
- ICON: iconConnectors,
|
|
53
|
-
- };
|
|
54
|
-
-
|
|
55
|
-
- if (!open) return null;
|
|
56
|
-
-
|
|
57
|
-
- if (!pickedChain) {
|
|
58
|
-
- return (
|
|
59
|
-
- <Dialog onClose={onClose}>
|
|
60
|
-
- <h2>Select a chain</h2>
|
|
61
|
-
- {SUPPORTED_CHAINS.map((chain) => (
|
|
62
|
-
- <button key={chain} onClick={() => setPickedChain(chain)}>
|
|
63
|
-
- {chain}
|
|
64
|
-
- </button>
|
|
65
|
-
- ))}
|
|
66
|
-
- </Dialog>
|
|
67
|
-
- );
|
|
68
|
-
- }
|
|
69
|
-
-
|
|
70
|
-
- const connectors = connectorsByChain[pickedChain] ?? [];
|
|
71
|
-
-
|
|
72
|
-
- return (
|
|
73
|
-
- <Dialog onClose={onClose}>
|
|
74
|
-
- <button onClick={() => setPickedChain(null)}>← back</button>
|
|
75
|
-
- <h2>Connect to {pickedChain}</h2>
|
|
76
|
-
- {error && <div>Error: {error.message}</div>}
|
|
77
|
-
- {connectors.map((connector) => (
|
|
78
|
-
- <button
|
|
79
|
-
- key={connector.id}
|
|
80
|
-
- disabled={connectingId !== null}
|
|
81
|
-
- onClick={async () => {
|
|
82
|
-
- setConnectingId(connector.id);
|
|
83
|
-
- setError(null);
|
|
84
|
-
- try {
|
|
85
|
-
- await connect(connector);
|
|
86
|
-
- onClose();
|
|
87
|
-
- } catch (err) {
|
|
88
|
-
- setError(err as Error);
|
|
89
|
-
- } finally {
|
|
90
|
-
- setConnectingId(null);
|
|
91
|
-
- }
|
|
92
|
-
- }}
|
|
93
|
-
- >
|
|
94
|
-
- {connector.name}
|
|
95
|
-
- {connectingId === connector.id && ' (connecting...)'}
|
|
96
|
-
- </button>
|
|
97
|
-
- ))}
|
|
98
|
-
- </Dialog>
|
|
99
|
-
- );
|
|
100
|
-
- }
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
(Most v1 modals look like this — manual chain list, manual loop over connectors, manual try/catch for state.)
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## After (v2)
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
'use client';
|
|
111
|
-
|
|
112
|
-
import {
|
|
113
|
-
useWalletModal,
|
|
114
|
-
useChainGroups,
|
|
115
|
-
sortConnectors,
|
|
116
|
-
} from '@sodax/wallet-sdk-react';
|
|
117
|
-
|
|
118
|
-
export function WalletModal({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
119
|
-
const modal = useWalletModal({
|
|
120
|
-
onConnected: () => onClose(),
|
|
121
|
-
});
|
|
122
|
-
const chainGroups = useChainGroups();
|
|
123
|
-
|
|
124
|
-
if (!open) return null;
|
|
125
|
-
|
|
126
|
-
switch (modal.state.kind) {
|
|
127
|
-
case 'closed':
|
|
128
|
-
case 'chainSelect':
|
|
129
|
-
return (
|
|
130
|
-
<Dialog onClose={onClose}>
|
|
131
|
-
<h2>Select a chain</h2>
|
|
132
|
-
{chainGroups.map((group) => (
|
|
133
|
-
<button
|
|
134
|
-
key={group.id}
|
|
135
|
-
onClick={() => modal.openChain(group.xChainType)}
|
|
136
|
-
>
|
|
137
|
-
{group.label}
|
|
138
|
-
</button>
|
|
139
|
-
))}
|
|
140
|
-
</Dialog>
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
case 'walletSelect': {
|
|
144
|
-
const sorted = sortConnectors(modal.state.connectors, {
|
|
145
|
-
preferred: ['MetaMask', 'Phantom'],
|
|
146
|
-
});
|
|
147
|
-
return (
|
|
148
|
-
<Dialog onClose={onClose}>
|
|
149
|
-
<button onClick={() => modal.back()}>← back</button>
|
|
150
|
-
<h2>Connect to {modal.state.xChainType}</h2>
|
|
151
|
-
{sorted.map((connector) => (
|
|
152
|
-
<button
|
|
153
|
-
key={connector.id}
|
|
154
|
-
disabled={!connector.isInstalled}
|
|
155
|
-
onClick={() => modal.connect(connector)}
|
|
156
|
-
>
|
|
157
|
-
{connector.name}
|
|
158
|
-
{!connector.isInstalled && (
|
|
159
|
-
<a href={connector.installUrl} target="_blank" rel="noreferrer">
|
|
160
|
-
Install
|
|
161
|
-
</a>
|
|
162
|
-
)}
|
|
163
|
-
</button>
|
|
164
|
-
))}
|
|
165
|
-
</Dialog>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
case 'connecting':
|
|
170
|
-
return (
|
|
171
|
-
<Dialog onClose={onClose}>
|
|
172
|
-
<p>Connecting to {modal.state.connector.name}…</p>
|
|
173
|
-
</Dialog>
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
case 'error':
|
|
177
|
-
return (
|
|
178
|
-
<Dialog onClose={onClose}>
|
|
179
|
-
<p>Error: {modal.state.error.message}</p>
|
|
180
|
-
<button onClick={() => modal.retry()}>Retry</button>
|
|
181
|
-
</Dialog>
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
case 'success':
|
|
185
|
-
return null; // onConnected handler closes
|
|
186
|
-
|
|
187
|
-
default:
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## What changed
|
|
196
|
-
|
|
197
|
-
| Concern | v1 (manual) | v2 (primitive) |
|
|
198
|
-
|---|---|---|
|
|
199
|
-
| Track which chain is picked | `useState<ChainType \| null>` | `useWalletModal().state.kind === 'walletSelect'` |
|
|
200
|
-
| List enabled chains | hardcoded `SUPPORTED_CHAINS` | `useChainGroups()` (driven by `walletConfig`) |
|
|
201
|
-
| List connectors per chain | one `useXConnectors` per chain | `modal.state.connectors` when `kind === 'walletSelect'` |
|
|
202
|
-
| Connecting / error state | `useState` for `connectingId`, `error` | `modal.state.kind` with discriminated union |
|
|
203
|
-
| Retry on error | re-trigger click manually | `modal.retry()` |
|
|
204
|
-
| Filter to installed wallets | n/a | `connector.isInstalled` + `connector.installUrl` |
|
|
205
|
-
| Sort connectors | manual `array.sort` | `sortConnectors(xs, { preferred })` |
|
|
206
|
-
| Close on success | manual `onClose()` after `connect` | `useWalletModal({ onConnected })` callback |
|
|
207
|
-
| EVM treated as one | per-network confusion | `useChainGroups` collapses EVM into one row |
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## Migration steps
|
|
212
|
-
|
|
213
|
-
1. **Find the v1 modal file.** Search for `useState<ChainType` or multiple `useXConnectors` calls in the same component.
|
|
214
|
-
2. **Replace state-tracking with `useWalletModal`.** Remove `useState` for picked chain, connectingId, error.
|
|
215
|
-
3. **Replace hardcoded chain list with `useChainGroups`.** Use the `xChainType` field on each group entry.
|
|
216
|
-
4. **Switch on `modal.state.kind`.** Render branches: `'closed'` / `'chainSelect'` / `'walletSelect'` / `'connecting'` / `'error'` / `'success'`.
|
|
217
|
-
5. **Use connector metadata.** `connector.isInstalled` / `connector.installUrl` for install CTA. `sortConnectors` to rank installed wallets first.
|
|
218
|
-
6. **Keep your existing `<Dialog>` primitive.** v2 is **headless** — bring your own UI components.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Verification
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# 1. Type check
|
|
226
|
-
pnpm checkTs
|
|
227
|
-
|
|
228
|
-
# 2. Confirm no manual state is tracking modal flow
|
|
229
|
-
grep -nE "useState<ChainType" <user-modal-file>
|
|
230
|
-
grep -nE "useState.*connectingId|useState.*pickedChain" <user-modal-file>
|
|
231
|
-
# expect empty
|
|
232
|
-
|
|
233
|
-
# 3. Manual — open modal, walk through chain → wallet → connecting → success
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Edge cases
|
|
239
|
-
|
|
240
|
-
- **Modal opens on a specific chain (skip chain select).** Call `modal.openChain('EVM')` directly in your trigger button instead of going through chain select. The state machine starts at `'walletSelect'` for that chain.
|
|
241
|
-
- **App needs to know which chain the user picked even before connecting.** Read `modal.state.xChainType` inside the `'walletSelect'` / `'connecting'` branches.
|
|
242
|
-
- **Multiple modal trigger buttons (e.g. chain-specific CTAs).** Reuse one `useWalletModal` instance — do not create one per button.
|
|
243
|
-
|
|
244
|
-
For a full integration walkthrough (without v1 baggage), see [`../../integration/recipes/multi-chain-modal.md`](../../integration/recipes/multi-chain-modal.md).
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# Recipe: Migrate Next.js SSR Setup
|
|
2
|
-
|
|
3
|
-
Migrates the SSR-specific provider setup. Both `initialState` and `reconnectOnMount` move **into the `EVM` slot** of the new `config` prop — they are not removed in v2. Self-contained — apply this recipe without reading other files.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## When to use this recipe
|
|
8
|
-
|
|
9
|
-
Apply when the user's v1 code:
|
|
10
|
-
|
|
11
|
-
- Passes `initialState={...}` to `SodaxWalletProvider`
|
|
12
|
-
- Has a server component or page that calls `cookieToInitialState` (wagmi)
|
|
13
|
-
- Sets `options.wagmi.ssr: true` and/or `options.wagmi.reconnectOnMount`
|
|
14
|
-
|
|
15
|
-
If the project is **not** Next.js (Vite, CRA), only the prop-shape rewrite applies; `initialState` plumbing is not needed.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Before (v1)
|
|
20
|
-
|
|
21
|
-
```diff
|
|
22
|
-
- // app/layout.tsx — v1 ❌
|
|
23
|
-
- import { headers } from 'next/headers';
|
|
24
|
-
- import { cookieToInitialState } from 'wagmi';
|
|
25
|
-
- import { SodaxWalletProvider } from '@sodax/wallet-sdk-react';
|
|
26
|
-
- import { createWagmiConfig } from '@sodax/wallet-sdk-react'; // hypothetical helper consumers used
|
|
27
|
-
-
|
|
28
|
-
- const rpcConfig = {
|
|
29
|
-
- 'sonic': 'https://rpc.soniclabs.com',
|
|
30
|
-
- '0x1.eth': 'https://ethereum-rpc.publicnode.com',
|
|
31
|
-
- };
|
|
32
|
-
-
|
|
33
|
-
- export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
34
|
-
- const headersList = await headers();
|
|
35
|
-
- const cookie = headersList.get('cookie');
|
|
36
|
-
-
|
|
37
|
-
- // Build wagmi config the same way the package built it internally to derive initialState
|
|
38
|
-
- const wagmiConfig = createWagmiConfig(rpcConfig, { ssr: true });
|
|
39
|
-
- const initialState = cookieToInitialState(wagmiConfig, cookie);
|
|
40
|
-
-
|
|
41
|
-
- return (
|
|
42
|
-
- <html>
|
|
43
|
-
- <body>
|
|
44
|
-
- <SodaxWalletProvider
|
|
45
|
-
- rpcConfig={rpcConfig}
|
|
46
|
-
- options={{ wagmi: { ssr: true, reconnectOnMount: true } }}
|
|
47
|
-
- initialState={initialState}
|
|
48
|
-
- >
|
|
49
|
-
- {children}
|
|
50
|
-
- </SodaxWalletProvider>
|
|
51
|
-
- </body>
|
|
52
|
-
- </html>
|
|
53
|
-
- );
|
|
54
|
-
- }
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## After (v2)
|
|
60
|
-
|
|
61
|
-
```tsx
|
|
62
|
-
// app/layout.tsx — v2 ✅
|
|
63
|
-
import { headers } from 'next/headers';
|
|
64
|
-
import { cookieToInitialState } from 'wagmi';
|
|
65
|
-
import { ChainKeys } from '@sodax/types';
|
|
66
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
67
|
-
import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
|
|
68
|
-
import { createWagmiConfig } from '@sodax/wallet-sdk-react/xchains/evm';
|
|
69
|
-
|
|
70
|
-
const walletConfig: SodaxWalletConfig = {
|
|
71
|
-
EVM: {
|
|
72
|
-
ssr: true,
|
|
73
|
-
reconnectOnMount: true,
|
|
74
|
-
chains: {
|
|
75
|
-
[ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
|
|
76
|
-
[ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
// Add other chain slots as needed
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const queryClient = new QueryClient();
|
|
83
|
-
|
|
84
|
-
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
85
|
-
const cookie = (await headers()).get('cookie');
|
|
86
|
-
// Re-derive initialState from cookies to avoid disconnect-flash on first render.
|
|
87
|
-
const wagmiConfig = createWagmiConfig(walletConfig.EVM!);
|
|
88
|
-
const initialState = cookieToInitialState(wagmiConfig, cookie);
|
|
89
|
-
|
|
90
|
-
const evmConfig = { ...walletConfig.EVM!, initialState };
|
|
91
|
-
const config: SodaxWalletConfig = { ...walletConfig, EVM: evmConfig };
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<html>
|
|
95
|
-
<body>
|
|
96
|
-
<QueryClientProvider client={queryClient}>
|
|
97
|
-
<SodaxWalletProvider config={config}>{children}</SodaxWalletProvider>
|
|
98
|
-
</QueryClientProvider>
|
|
99
|
-
</body>
|
|
100
|
-
</html>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
If you don't need to avoid the first-render disconnect flash, drop the cookie / `initialState` plumbing entirely — `EVM.ssr: true` alone is enough for the typical SSR app.
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## What changed
|
|
110
|
-
|
|
111
|
-
| Concern | v1 | v2 |
|
|
112
|
-
|---|---|---|
|
|
113
|
-
| `rpcConfig` flat dict | top-level prop | nested under `EVM.chains[<ChainKey>].rpcUrl` |
|
|
114
|
-
| `options.wagmi.ssr` | nested under `options.wagmi` | `EVM.ssr: true` |
|
|
115
|
-
| `options.wagmi.reconnectOnMount` | nested under `options.wagmi` | `EVM.reconnectOnMount` (still supported, default `false`) |
|
|
116
|
-
| `initialState` | top-level prop | `EVM.initialState` (still supported — pass `cookieToInitialState(...)` for Next.js cookie hydration) |
|
|
117
|
-
| `QueryClient` | created internally by `SodaxWalletProvider` | created by caller, wrapped with `QueryClientProvider` |
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Migration steps
|
|
122
|
-
|
|
123
|
-
1. **Move per-chain RPC URLs.** Move `rpcConfig['sonic']` etc. into `EVM.chains[ChainKeys.SONIC_MAINNET].rpcUrl`. Use `ChainKeys` constants from `@sodax/types`.
|
|
124
|
-
2. **Collapse `options.wagmi.*` into `EVM.*`.** `options.wagmi.ssr` → `EVM.ssr`, `options.wagmi.reconnectOnMount` → `EVM.reconnectOnMount`.
|
|
125
|
-
3. **Move `initialState` into `EVM.initialState`.** v2 still accepts wagmi cookie state; the prop name and location changed, not the feature.
|
|
126
|
-
4. **Add `QueryClientProvider`.** Create a `QueryClient` (top-level module constant — singleton across renders) and wrap `SodaxWalletProvider` with `<QueryClientProvider>`.
|
|
127
|
-
5. **Make sure `@tanstack/react-query` is a direct dep.** Add it if missing:
|
|
128
|
-
```bash
|
|
129
|
-
pnpm add @tanstack/react-query
|
|
130
|
-
```
|
|
131
|
-
6. **Confirm consumer components are `'use client'`.** Hooks (`useXAccount`, `useXConnect`, etc.) only run on the client — any component that calls them needs the directive.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Verification
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
# 1. Type check
|
|
139
|
-
pnpm checkTs
|
|
140
|
-
|
|
141
|
-
# 2. No top-level v1 props remain on SodaxWalletProvider
|
|
142
|
-
grep -rnE "SodaxWalletProvider[^>]*\b(rpcConfig|options|initialState)\s*=" <user-src>
|
|
143
|
-
# expect empty
|
|
144
|
-
|
|
145
|
-
# 3. EVM.ssr: true is set
|
|
146
|
-
grep -rnE "EVM:\s*\{[^}]*\bssr:\s*true" <user-src>
|
|
147
|
-
# expect at least one match
|
|
148
|
-
|
|
149
|
-
# 4. QueryClientProvider wraps SodaxWalletProvider
|
|
150
|
-
# (manual — open layout.tsx, confirm nesting)
|
|
151
|
-
|
|
152
|
-
# 5. Manual — pnpm build && pnpm start, load page, confirm no hydration mismatch warnings in console
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## Common pitfalls
|
|
158
|
-
|
|
159
|
-
- **Forgetting `'use client'` on consumer components.** If you call `useXAccount` / `useXConnect` in a Server Component, you'll get "useEffect cannot be called from a server component". Wrap the consumer in a client component.
|
|
160
|
-
- **Creating `QueryClient` inside the layout component.** Each render creates a new client and React Query loses its cache. Define `queryClient` as a **module-level constant**, or use `useState(() => new QueryClient())` inside a client component.
|
|
161
|
-
- **Pages Router project.** This recipe assumes App Router. For Pages Router, mount the providers in `_app.tsx` instead — the API is the same.
|
|
162
|
-
- **Dynamic RPC URLs from env vars.** If `rpcUrl` comes from `process.env.NEXT_PUBLIC_*`, ensure the env var is available at module init time (it is, by Next.js convention). Don't compute the config inside a hook — `SodaxWalletProvider` freezes config on first render, so config must be stable.
|
|
163
|
-
|
|
164
|
-
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Recipe: Adopt WalletConnect (v2 only)
|
|
2
|
-
|
|
3
|
-
WalletConnect support is **new in v2** — there is no v1 equivalent to migrate from. This recipe is here for users who tried to bolt WalletConnect onto v1 by hand-rolling a wagmi config and now want to use the v2 first-class option.
|
|
4
|
-
|
|
5
|
-
If you didn't have WalletConnect in v1, **this recipe is opt-in, not required**. Skip if not relevant.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## When to use this recipe
|
|
10
|
-
|
|
11
|
-
Apply when the user's v1 code:
|
|
12
|
-
|
|
13
|
-
- Built a custom wagmi config that injected `walletConnect()` connector
|
|
14
|
-
- Mounted a separate `WagmiProvider` outside `SodaxWalletProvider`
|
|
15
|
-
- Patched the v1 internal `createWagmiConfig` helper to include WalletConnect
|
|
16
|
-
|
|
17
|
-
Or when the user simply wants to **add WalletConnect for the first time** while migrating to v2.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Why apps add WalletConnect
|
|
22
|
-
|
|
23
|
-
EIP-6963 (the default v2 EVM discovery mechanism) only finds **browser-extension wallets**. Partners using enterprise custody — Fireblocks, Ledger Live, mobile-only wallets — cannot install browser extensions. WalletConnect is the protocol that lets these wallets connect via QR / deep link.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Before (v1, hand-rolled — typical workaround)
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
// v1 ❌ — bypassed the package's wagmi config
|
|
31
|
-
import { createConfig, WagmiProvider } from 'wagmi';
|
|
32
|
-
import { walletConnect } from 'wagmi/connectors';
|
|
33
|
-
import { mainnet, sonic } from 'wagmi/chains';
|
|
34
|
-
|
|
35
|
-
const wagmiConfig = createConfig({
|
|
36
|
-
chains: [mainnet, sonic],
|
|
37
|
-
connectors: [
|
|
38
|
-
walletConnect({ projectId: 'wc-cloud-project-id' }),
|
|
39
|
-
// ...
|
|
40
|
-
],
|
|
41
|
-
// ...
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
export function Providers({ children }) {
|
|
45
|
-
return (
|
|
46
|
-
<WagmiProvider config={wagmiConfig}>
|
|
47
|
-
{/* SodaxWalletProvider mounted alongside, with conflicting wagmi state */}
|
|
48
|
-
<SodaxWalletProvider rpcConfig={...} options={...}>
|
|
49
|
-
{children}
|
|
50
|
-
</SodaxWalletProvider>
|
|
51
|
-
</WagmiProvider>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
(This pattern caused two parallel wagmi states and was never officially supported.)
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## After (v2 — first-class)
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
// v2 ✅
|
|
64
|
-
import { ChainKeys } from '@sodax/types';
|
|
65
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
66
|
-
import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
|
|
67
|
-
|
|
68
|
-
const walletConfig: SodaxWalletConfig = {
|
|
69
|
-
EVM: {
|
|
70
|
-
ssr: true,
|
|
71
|
-
walletConnect: {
|
|
72
|
-
projectId: 'wc-cloud-project-id', // from cloud.walletconnect.com
|
|
73
|
-
},
|
|
74
|
-
chains: {
|
|
75
|
-
[ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
|
|
76
|
-
[ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const queryClient = new QueryClient();
|
|
82
|
-
|
|
83
|
-
export function Providers({ children }: { children: React.ReactNode }) {
|
|
84
|
-
return (
|
|
85
|
-
<QueryClientProvider client={queryClient}>
|
|
86
|
-
<SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
|
|
87
|
-
</QueryClientProvider>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
The WalletConnect connector is added to wagmi automatically. `useXConnectors({ xChainType: 'EVM' })` will surface it alongside any EIP-6963 wallets.
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## What changed
|
|
97
|
-
|
|
98
|
-
| Concern | v1 (hand-rolled) | v2 (first-class) |
|
|
99
|
-
|---|---|---|
|
|
100
|
-
| WagmiProvider mount | manual, outside SodaxWalletProvider | internal to SodaxWalletProvider |
|
|
101
|
-
| wagmi config | hand-built via `createConfig` | derived from `walletConfig` |
|
|
102
|
-
| WalletConnect projectId | passed to `walletConnect()` connector | passed to `EVM.walletConnect.projectId` |
|
|
103
|
-
| Two parallel wagmi states | yes — common bug | no — single source of truth |
|
|
104
|
-
| Connector list in modal | mixed (some via custom connectors, some via SodaxWalletProvider) | unified via `useXConnectors({ xChainType: 'EVM' })` |
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Restricting the modal to specific wallets
|
|
109
|
-
|
|
110
|
-
`EVM.walletConnect` extends wagmi's `WalletConnectParameters` — full options available. To show only one specific wallet (e.g. when integrating with a single enterprise custody provider) and hide the rest:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
EVM: {
|
|
114
|
-
walletConnect: {
|
|
115
|
-
projectId: 'wc-cloud-project-id',
|
|
116
|
-
qrModalOptions: {
|
|
117
|
-
explorerRecommendedWalletIds: ['<target-wallet-id>'],
|
|
118
|
-
explorerExcludedWalletIds: 'ALL',
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Wallet IDs come from the [WalletConnect Explorer](https://cloud.walletconnect.com/sign-in). Filter options:
|
|
125
|
-
|
|
126
|
-
| Option | Effect |
|
|
127
|
-
|---|---|
|
|
128
|
-
| `explorerRecommendedWalletIds` | Prioritized at the top of the QR modal. |
|
|
129
|
-
| `explorerExcludedWalletIds: 'ALL'` | Hides every wallet not in `explorerRecommendedWalletIds`. |
|
|
130
|
-
| `explorerExcludedWalletIds: ['id1', 'id2']` | Hides specific wallets only. |
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Migration steps
|
|
135
|
-
|
|
136
|
-
1. **Remove the hand-rolled `WagmiProvider` and `createConfig`.** v2 builds wagmi config internally.
|
|
137
|
-
2. **Move `projectId`** into `walletConfig.EVM.walletConnect.projectId`.
|
|
138
|
-
3. **Move any `qrModalOptions`** (e.g. wallet filters) into `walletConfig.EVM.walletConnect.qrModalOptions`.
|
|
139
|
-
4. **Verify** that `useXConnectors({ xChainType: 'EVM' })` returns both EIP-6963 connectors and the WalletConnect entry.
|
|
140
|
-
5. **Drop** any custom hooks / state that bridged between the two wagmi configs in v1.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Verification
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
# 1. Type check
|
|
148
|
-
pnpm checkTs
|
|
149
|
-
|
|
150
|
-
# 2. Confirm no manual WagmiProvider remains
|
|
151
|
-
grep -rn "WagmiProvider" <user-src>
|
|
152
|
-
# expect empty (or only inside the wallet-sdk-react package itself if the user has a deep import — flag and remove)
|
|
153
|
-
|
|
154
|
-
# 3. Confirm walletConnect config is in walletConfig
|
|
155
|
-
grep -rn "walletConnect" <user-src> | grep -i "projectId"
|
|
156
|
-
# expect at least one match in the provider file
|
|
157
|
-
|
|
158
|
-
# 4. Manual — load app, open connect modal, confirm WalletConnect option appears
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Common pitfalls
|
|
164
|
-
|
|
165
|
-
- **Missing projectId.** Without `projectId`, the WalletConnect connector won't initialize. Get one free at [cloud.walletconnect.com](https://cloud.walletconnect.com).
|
|
166
|
-
- **Bundling EIP-6963 + WalletConnect.** The two are additive — `useXConnectors` returns both. UI should let users pick.
|
|
167
|
-
- **`qrModalOptions.explorerExcludedWalletIds` typo.** It's `'ALL'` (string), not `true` or `'all'`.
|
|
168
|
-
- **Multiple SodaxWalletProvider mounts.** Don't mount one with WalletConnect and another without — define `walletConfig` once at the app root.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Reference: Component / Provider Map
|
|
2
|
-
|
|
3
|
-
Components and exported providers between v1 and v2. See [`../breaking-changes.md`](../breaking-changes.md) for the WHY.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## `SodaxWalletProvider`
|
|
8
|
-
|
|
9
|
-
Same name in v1 and v2. **Props changed** — see [`config.md`](./config.md) for the full shape map.
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
// v1 ❌
|
|
13
|
-
<SodaxWalletProvider
|
|
14
|
-
rpcConfig={rpcConfig}
|
|
15
|
-
options={{ wagmi, solana, sui }}
|
|
16
|
-
initialState={wagmiState}
|
|
17
|
-
>
|
|
18
|
-
{children}
|
|
19
|
-
</SodaxWalletProvider>
|
|
20
|
-
|
|
21
|
-
// v2 ✅
|
|
22
|
-
<SodaxWalletProvider config={walletConfig}>
|
|
23
|
-
{children}
|
|
24
|
-
</SodaxWalletProvider>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Provider-stack order
|
|
28
|
-
|
|
29
|
-
| Position | v1 | v2 |
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| Outermost (app entry) | `SodaxWalletProvider` (creates internal QueryClient) | `QueryClientProvider` (caller-provided QueryClient) |
|
|
32
|
-
| Middle | (none) | `SodaxWalletProvider` |
|
|
33
|
-
| Children | app | app |
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
// v1 ❌ — SodaxWalletProvider internal QueryClient
|
|
37
|
-
<SodaxWalletProvider rpcConfig={...} options={...}>
|
|
38
|
-
{children}
|
|
39
|
-
</SodaxWalletProvider>
|
|
40
|
-
|
|
41
|
-
// v2 ✅ — caller wraps with QueryClientProvider
|
|
42
|
-
<QueryClientProvider client={queryClient}>
|
|
43
|
-
<SodaxWalletProvider config={walletConfig}>
|
|
44
|
-
{children}
|
|
45
|
-
</SodaxWalletProvider>
|
|
46
|
-
</QueryClientProvider>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Internal-only components (do not import directly)
|
|
52
|
-
|
|
53
|
-
These are mounted **inside** `SodaxWalletProvider` and are not part of the public API in either v1 or v2. Listed for awareness — if you imported them in v1 you should not have, and v2 may not expose them.
|
|
54
|
-
|
|
55
|
-
| Component | v1 | v2 | Note |
|
|
56
|
-
|---|---|---|---|
|
|
57
|
-
| `Hydrate` | exported (file `Hydrate.ts`) | replaced by per-chain `EvmHydrator`, `SolanaHydrator`, `SuiHydrator` (internal) | If you imported `Hydrate` from v1, **remove the import** — v2 does not export an equivalent. State hydration is automatic. |
|
|
58
|
-
| `WagmiProvider`, `SuiClientProvider`, `SolanaWalletProvider` (re-exports) | not re-exported | not re-exported | Use the original packages (`wagmi`, `@mysten/dapp-kit`, `@solana/wallet-adapter-react`) only if you need direct access — most apps should not. |
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## `XService` and `XConnector` abstract classes
|
|
63
|
-
|
|
64
|
-
| | v1 | v2 |
|
|
65
|
-
|---|---|---|
|
|
66
|
-
| Import path | `@sodax/wallet-sdk-react` (barrel) | `@sodax/wallet-sdk-react` (barrel — types only) |
|
|
67
|
-
| Runtime class export | yes | yes |
|
|
68
|
-
| Abstract method signature | `abstract connect(): Promise<XAccount \| undefined>` | unchanged |
|
|
69
|
-
| Properties | `id`, `name`, `icon`, `xChainType` | unchanged |
|
|
70
|
-
|
|
71
|
-
The abstract contract is **stable across v1 → v2**. If you have a custom subclass, it should compile against v2 with no changes — but verify, and check `getBalance` / `getBalances` defaults if you override them.
|
|
72
|
-
|
|
73
|
-
> **Stop condition:** if the user has a custom `XConnector` or `XService` subclass, the migration agent must stop and ask. The abstract surface is stable but downstream usage (e.g. how the subclass is instantiated, registered) has changed.
|