@sodax/wallet-sdk-react 1.5.6-beta → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +103 -145
  2. package/ai-exported/AGENTS.md +122 -0
  3. package/ai-exported/integration/README.md +102 -0
  4. package/ai-exported/integration/ai-rules.md +136 -0
  5. package/ai-exported/integration/architecture.md +181 -0
  6. package/ai-exported/integration/examples/01-minimal-evm.tsx +75 -0
  7. package/ai-exported/integration/examples/02-multi-chain-modal.tsx +169 -0
  8. package/ai-exported/integration/examples/03-nextjs-app-router.tsx +99 -0
  9. package/ai-exported/integration/examples/04-walletconnect-setup.tsx +89 -0
  10. package/ai-exported/integration/examples/README.md +29 -0
  11. package/ai-exported/integration/recipes/batch-operations.md +223 -0
  12. package/ai-exported/integration/recipes/bridge-to-sdk.md +164 -0
  13. package/ai-exported/integration/recipes/chain-detection.md +254 -0
  14. package/ai-exported/integration/recipes/connect-button.md +156 -0
  15. package/ai-exported/integration/recipes/multi-chain-modal.md +199 -0
  16. package/ai-exported/integration/recipes/setup.md +158 -0
  17. package/ai-exported/integration/recipes/sign-message.md +137 -0
  18. package/ai-exported/integration/recipes/sub-path-imports.md +95 -0
  19. package/ai-exported/integration/recipes/switch-chain.md +141 -0
  20. package/ai-exported/integration/recipes/walletconnect-setup.md +139 -0
  21. package/ai-exported/integration/reference/api-surface.md +175 -0
  22. package/ai-exported/integration/reference/chain-support.md +78 -0
  23. package/ai-exported/integration/reference/connectors.md +74 -0
  24. package/ai-exported/integration/reference/hooks.md +204 -0
  25. package/ai-exported/integration/reference/wallet-brands.md +106 -0
  26. package/ai-exported/migration/README.md +49 -0
  27. package/ai-exported/migration/ai-rules.md +144 -0
  28. package/ai-exported/migration/breaking-changes.md +305 -0
  29. package/ai-exported/migration/checklist.md +159 -0
  30. package/ai-exported/migration/recipes/connect-button.md +166 -0
  31. package/ai-exported/migration/recipes/multi-chain-modal.md +244 -0
  32. package/ai-exported/migration/recipes/ssr-setup.md +162 -0
  33. package/ai-exported/migration/recipes/walletconnect-migration.md +168 -0
  34. package/ai-exported/migration/reference/components.md +73 -0
  35. package/ai-exported/migration/reference/config.md +307 -0
  36. package/ai-exported/migration/reference/hooks.md +278 -0
  37. package/ai-exported/migration/reference/imports.md +157 -0
  38. package/dist/XConnector-B9YQTVJ4.d.ts +146 -0
  39. package/dist/chunk-2BOUGCJ7.mjs +150 -0
  40. package/dist/chunk-2BOUGCJ7.mjs.map +1 -0
  41. package/dist/chunk-66BAUK56.mjs +202 -0
  42. package/dist/chunk-66BAUK56.mjs.map +1 -0
  43. package/dist/chunk-7ULB6DW4.mjs +102 -0
  44. package/dist/chunk-7ULB6DW4.mjs.map +1 -0
  45. package/dist/chunk-BKJB527E.mjs +125 -0
  46. package/dist/chunk-BKJB527E.mjs.map +1 -0
  47. package/dist/chunk-BXJLBR4G.mjs +88 -0
  48. package/dist/chunk-BXJLBR4G.mjs.map +1 -0
  49. package/dist/chunk-E5IAZ7E6.mjs +186 -0
  50. package/dist/chunk-E5IAZ7E6.mjs.map +1 -0
  51. package/dist/chunk-MAQ47Q52.mjs +33 -0
  52. package/dist/chunk-MAQ47Q52.mjs.map +1 -0
  53. package/dist/chunk-MXZVF5HR.mjs +34 -0
  54. package/dist/chunk-MXZVF5HR.mjs.map +1 -0
  55. package/dist/chunk-N5A2TMF6.mjs +33 -0
  56. package/dist/chunk-N5A2TMF6.mjs.map +1 -0
  57. package/dist/chunk-NY7U7OJW.mjs +64 -0
  58. package/dist/chunk-NY7U7OJW.mjs.map +1 -0
  59. package/dist/chunk-PJLEJVAU.mjs +140 -0
  60. package/dist/chunk-PJLEJVAU.mjs.map +1 -0
  61. package/dist/chunk-PLCA4ZDJ.mjs +1585 -0
  62. package/dist/chunk-PLCA4ZDJ.mjs.map +1 -0
  63. package/dist/chunk-TZMKDXFA.mjs +3 -0
  64. package/dist/chunk-TZMKDXFA.mjs.map +1 -0
  65. package/dist/chunk-X2MHIWXO.mjs +100 -0
  66. package/dist/chunk-X2MHIWXO.mjs.map +1 -0
  67. package/dist/chunk-XZ7CHO2S.mjs +41 -0
  68. package/dist/chunk-XZ7CHO2S.mjs.map +1 -0
  69. package/dist/config-OlnzyEUE.d.ts +146 -0
  70. package/dist/index.cjs +2784 -1594
  71. package/dist/index.cjs.map +1 -1
  72. package/dist/index.d.ts +768 -1498
  73. package/dist/index.mjs +463 -2004
  74. package/dist/index.mjs.map +1 -1
  75. package/dist/xchains/bitcoin/index.cjs +1927 -0
  76. package/dist/xchains/bitcoin/index.cjs.map +1 -0
  77. package/dist/xchains/bitcoin/index.d.ts +125 -0
  78. package/dist/xchains/bitcoin/index.mjs +16 -0
  79. package/dist/xchains/bitcoin/index.mjs.map +1 -0
  80. package/dist/xchains/evm/index.cjs +316 -0
  81. package/dist/xchains/evm/index.cjs.map +1 -0
  82. package/dist/xchains/evm/index.d.ts +39 -0
  83. package/dist/xchains/evm/index.mjs +5 -0
  84. package/dist/xchains/evm/index.mjs.map +1 -0
  85. package/dist/xchains/icon/index.cjs +311 -0
  86. package/dist/xchains/icon/index.cjs.map +1 -0
  87. package/dist/xchains/icon/index.d.ts +37 -0
  88. package/dist/xchains/icon/index.mjs +7 -0
  89. package/dist/xchains/icon/index.mjs.map +1 -0
  90. package/dist/xchains/injective/index.cjs +223 -0
  91. package/dist/xchains/injective/index.cjs.map +1 -0
  92. package/dist/xchains/injective/index.d.ts +35 -0
  93. package/dist/xchains/injective/index.mjs +5 -0
  94. package/dist/xchains/injective/index.mjs.map +1 -0
  95. package/dist/xchains/near/index.cjs +190 -0
  96. package/dist/xchains/near/index.cjs.map +1 -0
  97. package/dist/xchains/near/index.d.ts +34 -0
  98. package/dist/xchains/near/index.mjs +6 -0
  99. package/dist/xchains/near/index.mjs.map +1 -0
  100. package/dist/xchains/solana/index.cjs +186 -0
  101. package/dist/xchains/solana/index.cjs.map +1 -0
  102. package/dist/xchains/solana/index.d.ts +26 -0
  103. package/dist/xchains/solana/index.mjs +7 -0
  104. package/dist/xchains/solana/index.mjs.map +1 -0
  105. package/dist/xchains/stacks/index.cjs +240 -0
  106. package/dist/xchains/stacks/index.cjs.map +1 -0
  107. package/dist/xchains/stacks/index.d.ts +36 -0
  108. package/dist/xchains/stacks/index.mjs +5 -0
  109. package/dist/xchains/stacks/index.mjs.map +1 -0
  110. package/dist/xchains/stellar/index.cjs +322 -0
  111. package/dist/xchains/stellar/index.cjs.map +1 -0
  112. package/dist/xchains/stellar/index.d.ts +44 -0
  113. package/dist/xchains/stellar/index.mjs +6 -0
  114. package/dist/xchains/stellar/index.mjs.map +1 -0
  115. package/dist/xchains/sui/index.cjs +248 -0
  116. package/dist/xchains/sui/index.cjs.map +1 -0
  117. package/dist/xchains/sui/index.d.ts +37 -0
  118. package/dist/xchains/sui/index.mjs +7 -0
  119. package/dist/xchains/sui/index.mjs.map +1 -0
  120. package/docs/ADDING_A_NEW_CHAIN.md +440 -0
  121. package/docs/ARCHITECTURE.md +291 -0
  122. package/docs/BATCH_OPERATIONS.md +267 -0
  123. package/docs/CHAIN_DETECTION.md +216 -0
  124. package/docs/CONFIGURE_PROVIDER.md +360 -0
  125. package/docs/CONNECTORS.md +247 -0
  126. package/docs/CONNECT_FLOW.md +276 -0
  127. package/docs/EVM_SWITCH_CHAIN.md +161 -0
  128. package/docs/SIGN_MESSAGE.md +213 -0
  129. package/docs/SUB_PATH_EXPORTS.md +246 -0
  130. package/docs/WALLETCONNECT.md +154 -0
  131. package/docs/WALLET_MODAL.md +331 -0
  132. package/docs/WALLET_PROVIDER_BRIDGE.md +226 -0
  133. package/package.json +34 -9
  134. package/skills/SKILLS.md +84 -0
  135. package/skills/bridge-to-sdk.md +148 -0
  136. package/skills/connect-button.md +116 -0
  137. package/skills/evm-only-walletconnect.md +111 -0
  138. package/skills/multi-chain-modal.md +178 -0
  139. package/skills/setup.md +107 -0
  140. package/dist/index.d.cts +0 -1579
  141. package/src/Hydrate.ts +0 -65
  142. package/src/SodaxWalletProvider.tsx +0 -97
  143. package/src/actions/getXChainType.ts +0 -8
  144. package/src/actions/getXService.ts +0 -33
  145. package/src/actions/index.ts +0 -2
  146. package/src/assets/wallets/hana.svg +0 -6
  147. package/src/assets/wallets/havah.svg +0 -76
  148. package/src/assets/wallets/keplr.svg +0 -30
  149. package/src/assets/wallets/metamask.svg +0 -60
  150. package/src/assets/wallets/phantom.svg +0 -4
  151. package/src/assets/wallets/sui.svg +0 -20
  152. package/src/core/XConnector.ts +0 -54
  153. package/src/core/XService.ts +0 -85
  154. package/src/core/index.ts +0 -2
  155. package/src/hooks/index.ts +0 -11
  156. package/src/hooks/useEthereumChainId.ts +0 -44
  157. package/src/hooks/useEvmSwitchChain.ts +0 -91
  158. package/src/hooks/useWalletProvider.ts +0 -206
  159. package/src/hooks/useXAccount.ts +0 -51
  160. package/src/hooks/useXAccounts.ts +0 -56
  161. package/src/hooks/useXBalances.ts +0 -65
  162. package/src/hooks/useXConnect.ts +0 -118
  163. package/src/hooks/useXConnection.ts +0 -72
  164. package/src/hooks/useXConnectors.ts +0 -72
  165. package/src/hooks/useXDisconnect.ts +0 -73
  166. package/src/hooks/useXService.ts +0 -8
  167. package/src/hooks/useXSignMessage.ts +0 -82
  168. package/src/index.ts +0 -19
  169. package/src/types/index.ts +0 -22
  170. package/src/useXWagmiStore.ts +0 -116
  171. package/src/utils/index.ts +0 -21
  172. package/src/xchains/bitcoin/BitcoinXConnector.ts +0 -34
  173. package/src/xchains/bitcoin/BitcoinXService.ts +0 -40
  174. package/src/xchains/bitcoin/OKXXConnector.ts +0 -117
  175. package/src/xchains/bitcoin/UnisatXConnector.ts +0 -117
  176. package/src/xchains/bitcoin/XverseXConnector.ts +0 -232
  177. package/src/xchains/bitcoin/index.ts +0 -7
  178. package/src/xchains/bitcoin/useBitcoinXConnectors.ts +0 -14
  179. package/src/xchains/evm/EvmXConnector.ts +0 -27
  180. package/src/xchains/evm/EvmXService.ts +0 -211
  181. package/src/xchains/evm/index.ts +0 -3
  182. package/src/xchains/icon/IconHanaXConnector.ts +0 -39
  183. package/src/xchains/icon/IconXService.ts +0 -117
  184. package/src/xchains/icon/actions.ts +0 -28
  185. package/src/xchains/icon/iconex/index.tsx +0 -46
  186. package/src/xchains/icon/index.ts +0 -2
  187. package/src/xchains/injective/InjectiveXConnector.ts +0 -60
  188. package/src/xchains/injective/InjectiveXService.ts +0 -62
  189. package/src/xchains/injective/actions.ts +0 -32
  190. package/src/xchains/injective/index.ts +0 -2
  191. package/src/xchains/near/NearXConnector.ts +0 -42
  192. package/src/xchains/near/NearXService.ts +0 -46
  193. package/src/xchains/near/useNearXConnectors.ts +0 -23
  194. package/src/xchains/solana/SolanaXConnector.ts +0 -26
  195. package/src/xchains/solana/SolanaXService.ts +0 -46
  196. package/src/xchains/solana/index.ts +0 -2
  197. package/src/xchains/stacks/StacksXConnector.ts +0 -63
  198. package/src/xchains/stacks/StacksXService.ts +0 -59
  199. package/src/xchains/stacks/constants.ts +0 -42
  200. package/src/xchains/stacks/index.ts +0 -4
  201. package/src/xchains/stacks/useStacksXConnectors.ts +0 -7
  202. package/src/xchains/stellar/CustomSorobanServer.ts +0 -93
  203. package/src/xchains/stellar/StellarWalletsKitXConnector.ts +0 -53
  204. package/src/xchains/stellar/StellarXService.ts +0 -93
  205. package/src/xchains/stellar/actions.ts +0 -24
  206. package/src/xchains/stellar/index.tsx +0 -2
  207. package/src/xchains/stellar/useStellarXConnectors.ts +0 -21
  208. package/src/xchains/stellar/utils.ts +0 -49
  209. package/src/xchains/sui/SuiXConnector.ts +0 -28
  210. package/src/xchains/sui/SuiXService.ts +0 -66
  211. package/src/xchains/sui/index.ts +0 -2
@@ -0,0 +1,136 @@
1
+ # AI Rules — Fresh Integration
2
+
3
+ You are integrating `@sodax/wallet-sdk-react` into a user's React app for the first time. Follow this protocol exactly.
4
+
5
+ ---
6
+
7
+ ## Workflow (do these in order)
8
+
9
+ ### 1. Survey the project
10
+
11
+ Before changing anything, gather context:
12
+
13
+ ```bash
14
+ # Framework — Next.js (App Router or Pages)? Vite? CRA?
15
+ cat <user>/package.json | grep -E '"next|"vite|"react-scripts'
16
+
17
+ # React version — must be >= 19
18
+ cat <user>/package.json | grep '"react"'
19
+
20
+ # React Query already installed?
21
+ cat <user>/package.json | grep '"@tanstack/react-query'
22
+
23
+ # Existing wallet libraries that may conflict
24
+ grep -l "wagmi\|@solana/wallet-adapter\|@mysten/dapp-kit" <user>/package.json
25
+ ```
26
+
27
+ **If React < 19**, stop and tell the user — this package requires React 19.
28
+
29
+ **If wagmi / @solana/wallet-adapter / @mysten/dapp-kit are already direct dependencies**, stop and ask the user — this package mounts those internally; running both will cause duplicate provider context errors.
30
+
31
+ ### 2. Always start with `setup.md`
32
+
33
+ Read [`recipes/setup.md`](./recipes/setup.md) and apply it. **Do not skip ahead** — every subsequent recipe assumes `SodaxWalletProvider` is already mounted.
34
+
35
+ ### 3. Pick exactly one connect UX
36
+
37
+ Connect UX is **either / or**, not additive:
38
+
39
+ - [`recipes/connect-button.md`](./recipes/connect-button.md) — pick this when the app needs to connect one chain at a time, and the UX is a single button per chain.
40
+ - [`recipes/multi-chain-modal.md`](./recipes/multi-chain-modal.md) — pick this when the app needs a chain-picker → connector-picker modal, or when one wallet should connect multiple chains in sequence.
41
+
42
+ Ask the user which UX they want before applying. If unsure, default to `connect-button.md` (simpler).
43
+
44
+ ### 4. Add advanced features as separate steps
45
+
46
+ Only after the connect UX works should you add:
47
+
48
+ - Sign-message flows ([`recipes/sign-message.md`](./recipes/sign-message.md))
49
+ - Bridge to `@sodax/sdk` calls ([`recipes/bridge-to-sdk.md`](./recipes/bridge-to-sdk.md))
50
+ - Switch active EVM network ([`recipes/switch-chain.md`](./recipes/switch-chain.md))
51
+ - Chain & wallet detection patterns ([`recipes/chain-detection.md`](./recipes/chain-detection.md))
52
+ - WalletConnect ([`recipes/walletconnect-setup.md`](./recipes/walletconnect-setup.md))
53
+ - Batch connect / disconnect ([`recipes/batch-operations.md`](./recipes/batch-operations.md))
54
+
55
+ Each is independent — apply only what the user asked for.
56
+
57
+ ### Copy-paste templates
58
+
59
+ If the user wants a full working starter file rather than narrative steps, point them at [`examples/`](./examples/):
60
+
61
+ - `01-minimal-evm.tsx` — provider + 1 connect button
62
+ - `02-multi-chain-modal.tsx` — full headless modal
63
+ - `03-nextjs-app-router.tsx` — Next.js 15 SSR setup
64
+ - `04-walletconnect-setup.tsx` — WalletConnect setup (optional single-wallet filter)
65
+
66
+ Always combine with the narrative recipe — examples are skeletons, recipes explain why each piece exists.
67
+
68
+ ---
69
+
70
+ ## DO
71
+
72
+ - **DO** put `SodaxWalletProvider` at the app root, inside `QueryClientProvider`. Order matters.
73
+ - **DO** include only the chain slots the user actually needs in `walletConfig`. Each slot mounts a real React provider — unused slots waste bundle size.
74
+ - **DO** use the chain-typed hooks: `useXAccount({ xChainType: 'EVM' })`, not `useXAccount({ xChainId: someId })` unless the user specifically needs per-chain-id resolution.
75
+ - **DO** gate UI on `useConnectedChains().status === 'ready'` whenever you render based on connection state — prevents hydration flicker on Next.js.
76
+ - **DO** use the headless primitives (`useWalletModal`, `useChainGroups`) and let the user style the UI. Do not hand-roll a hidden chain registry.
77
+ - **DO** if Next.js, set `EVM.ssr: true` in `walletConfig`.
78
+
79
+ ---
80
+
81
+ ## DO NOT
82
+
83
+ - **DO NOT** import from `@sodax/wallet-sdk-react/xchains/<chain>` unless you specifically need a concrete class (e.g. `instanceof XverseXConnector`). Default to barrel imports.
84
+ - **DO NOT** instantiate `XService` or `XConnector` subclasses manually — `SodaxWalletProvider` does this via the chain registry.
85
+ - **DO NOT** call `useXConnect` outside a React component / custom hook.
86
+ - **DO NOT** read from `useXWalletStore` directly. Use the public hooks. The store shape is internal.
87
+ - **DO NOT** stack multiple `SodaxWalletProvider` instances. One per app.
88
+ - **DO NOT** pass changing config references on every render — `SodaxWalletProvider` freezes config on first render. To swap config at runtime, remount with a new `key`.
89
+ - **DO NOT** mock `useXConnect` / `useXAccount` in tests by stubbing the store. Use the real provider with a test config.
90
+
91
+ ---
92
+
93
+ ## Stop conditions (defer to user)
94
+
95
+ | Signal | Why stop |
96
+ |---|---|
97
+ | User asks for a chain not in the [chain support list](./reference/chain-support.md) | Adding a new chain requires package-level changes (see `../../CLAUDE.md`), not user-app integration. |
98
+ | User wants to use `@sodax/sdk` for swaps / lending / etc. | This package only handles wallet connectivity. Direct user to also install `@sodax/dapp-kit`. |
99
+ | User wants Server Components to use `useXAccount` directly | Hooks are client-only. Must wrap consumer in `'use client'`. |
100
+ | User mentions Next.js Pages Router | Setup differs slightly — confirm before applying App Router defaults. |
101
+ | User asks "how do I connect wallet X" but the connector isn't built in | Check [`reference/connectors.md`](./reference/connectors.md). If absent, the connector doesn't exist yet — flag as out of scope. |
102
+ | User wants custom chains (e.g. EVM testnet not in the default list) | `EVM.chains` accepts custom chain configs, but ChainKey enum is fixed. Confirm before extending. |
103
+
104
+ ---
105
+
106
+ ## Verification protocol (after every recipe)
107
+
108
+ ```bash
109
+ # 1. Type check passes
110
+ pnpm checkTs
111
+
112
+ # 2. Provider is mounted exactly once
113
+ grep -rn "SodaxWalletProvider" <user-src> # expect one match
114
+
115
+ # 3. QueryClientProvider wraps SodaxWalletProvider
116
+ # (manual — open the provider file, confirm nesting order)
117
+
118
+ # 4. Connect/disconnect works in dev
119
+ # (manual — start dev server, click connect, verify localStorage has `xwagmi-store` key after success)
120
+ ```
121
+
122
+ If steps 1–3 pass and the user confirms step 4, the recipe is done.
123
+
124
+ ---
125
+
126
+ ## Done criteria
127
+
128
+ The integration is complete when:
129
+
130
+ - [ ] `pnpm checkTs` exits clean.
131
+ - [ ] `SodaxWalletProvider` is mounted at the app root with the user's chosen `walletConfig`.
132
+ - [ ] `QueryClientProvider` wraps `SodaxWalletProvider`.
133
+ - [ ] At least one connect UX (button or modal) is wired and works in the user's dev environment.
134
+ - [ ] If using `@sodax/sdk` features: `useWalletProvider` is used to bridge to SDK calls (see `recipes/setup.md` § "Pair with `@sodax/sdk`").
135
+
136
+ Do not declare integration done before all of the above are true.
@@ -0,0 +1,181 @@
1
+ # Architecture — Mental Model
2
+
3
+ This file explains **why** `@sodax/wallet-sdk-react` is shaped the way it is. Read it once before applying recipes — knowing the model lets you handle ambiguous user code without inventing migrations.
4
+
5
+ If you are looking for "how do I do X", go to [`recipes/`](./recipes/). This file is purely conceptual.
6
+
7
+ ---
8
+
9
+ ## The shape of the package
10
+
11
+ `@sodax/wallet-sdk-react` is a **uniform React layer over heterogeneous chain wallet libraries**. Each chain family ships a different React adapter (wagmi, `@solana/wallet-adapter-react`, `@mysten/dapp-kit`) or no React adapter at all (Bitcoin, Stellar, ICON, Injective, NEAR, Stacks). The package wraps all nine families behind one set of hooks so consumer code does not branch on chain family.
12
+
13
+ Two abstractions make this work:
14
+
15
+ - **`XConnector`** — represents a *wallet* on a *chain family* (e.g. MetaMask on EVM, Phantom on Solana, Xverse on Bitcoin). Knows how to `connect()`, `disconnect()`, expose `id` / `name` / `icon` / `isInstalled`.
16
+ - **`XService`** — represents a *chain family* (one per `ChainType`). Holds the list of available connectors, the current active connection, and exposes a chain-family-specific `IXxxWalletProvider` once connected.
17
+
18
+ Hooks always operate on `XService` / `XConnector` — never directly on a chain library's primitives. Consumers do not import `useAccount` from wagmi; they call `useXAccount({ xChainType: 'EVM' })` and get back a uniform `XAccount` shape.
19
+
20
+ ---
21
+
22
+ ## The provider mount tree
23
+
24
+ `<SodaxWalletProvider config={...}>` mounts only the chain-type slots present in `config`. The internal nesting order is fixed:
25
+
26
+ ```
27
+ <WalletConfigProvider value={frozenConfig}>
28
+ <EvmProvider config={config.EVM}> ← only if EVM slot present (mounts wagmi.WagmiProvider)
29
+ <SuiProvider config={config.SUI}> ← only if SUI slot present (mounts dapp-kit providers)
30
+ <SolanaProvider config={config.SOLANA}> ← only if SOLANA slot present (mounts solana-wallet-adapter)
31
+ {children}
32
+ </SolanaProvider>
33
+ </SuiProvider>
34
+ </EvmProvider>
35
+ </WalletConfigProvider>
36
+ ```
37
+
38
+ Three things to internalize:
39
+
40
+ 1. **Slot opt-in is real.** Omitting `config.SOLANA` means `@solana/wallet-adapter-react` is **not** in the React tree at all — zero runtime cost, smaller bundle. `useXConnectors({ xChainType: 'SOLANA' })` returns `[]` and logs a one-time warning. Apps that only use EVM should pass only `{ EVM: {...} }`.
41
+ 2. **Provider-managed vs registered chains.** EVM/Solana/Sui have native React adapters and need a provider tree node. Bitcoin/Stellar/ICON/Injective/NEAR/Stacks register their `XService` at mount time via `useInitChainServices` — no provider node, no React context for the underlying SDK.
42
+ 3. **`QueryClientProvider` must wrap `<SodaxWalletProvider>`.** v2 does *not* mount a `QueryClient` internally (v1 did). `useXConnect`, `useXSignMessage`, batch hooks all depend on React Query — without an outer `QueryClientProvider` they throw at runtime, not at type-check.
43
+
44
+ ---
45
+
46
+ ## Config is captured once
47
+
48
+ `<SodaxWalletProvider config>` freezes `config` with `useRef` on first render. Subsequent renders with a new reference have **no effect**. Reasons:
49
+
50
+ - **Wagmi config-object identity matters.** wagmi reconnects if the config object identity changes; reactive configs caused reconnect storms in v1.
51
+ - **Predictable startup.** Apps should compose chain config once at module scope, not derive it dynamically from props.
52
+
53
+ To swap config at runtime, remount with a new `key`:
54
+
55
+ ```tsx
56
+ <SodaxWalletProvider key={configVersion} config={walletConfig}>
57
+ {children}
58
+ </SodaxWalletProvider>
59
+ ```
60
+
61
+ This is the only supported way to change config — there is no `setConfig` API.
62
+
63
+ ---
64
+
65
+ ## State: Zustand store + adapter sync
66
+
67
+ The package keeps connection state in a Zustand store keyed by `ChainType`. The store hook **is not exported** — consumers read state through public hooks (`useXAccount`, `useXService`, `useWalletProvider`, …), each of which subscribes to the relevant slice internally. The shape below is informational only:
68
+
69
+ ```ts
70
+ // internal shape — do NOT import
71
+ {
72
+ xServices: Partial<Record<ChainType, IXService>>,
73
+ xConnections: Partial<Record<ChainType, XConnection>>,
74
+ // …plus internal mutations
75
+ }
76
+ ```
77
+
78
+ Two things matter to consumers:
79
+
80
+ 1. **Provider-managed chains sync state from the adapter.** EVM/Solana/Sui each have a `<Hydrator>` that watches its native adapter (wagmi `useAccount`, etc.) and writes the active connection into `xConnections`. Hooks read from the Zustand store, so they see one consistent view across all chains.
81
+ 2. **Persistence: `xwagmi-store` localStorage key, kept stable from v1.** Renaming would log out every existing user. Hydration runs lazily — the first render after refresh has `xConnections = {}` even if storage has data. Hooks expose this via `useConnectedChains().status === 'ready'` — gate UI on that to avoid hydration flicker on Next.js.
82
+
83
+ You should **not** read the store directly. Use the public hooks. The store hook itself is not exported from v2 — see [`reference/api-surface.md`](./reference/api-surface.md) § "Not exported".
84
+
85
+ ---
86
+
87
+ ## Wallet discovery (EIP-6963 + custom)
88
+
89
+ For EVM, available wallets are discovered via [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) — browser extensions broadcast their metadata and `useXConnectors({ xChainType: 'EVM' })` builds the list at runtime. No hardcoded wallet registry; new wallets installed by the user appear without an SDK update.
90
+
91
+ Other chains ship a curated connector list (Bitcoin: Xverse / Unisat / OKX, Sui: dapp-kit's standard connectors, Solana: wallet-adapter's standard connectors, etc.). For each connector, `isInstalled` reflects whether the underlying extension/wallet is detected. `sortConnectors(list, { preferred })` puts installed connectors first — call it once in your render path; the result is stable per render.
92
+
93
+ ---
94
+
95
+ ## Chain identifiers — `xChainType` vs `xChainId`
96
+
97
+ Two parallel addressing schemes:
98
+
99
+ - **`xChainType: ChainType`** — family-level (`'EVM'`, `'SOLANA'`, `'BITCOIN'`, …). 9 values total.
100
+ - **`xChainId: SpokeChainKey`** — chain-specific key from `@sodax/types` (`ChainKeys.ETHEREUM_MAINNET`, `ChainKeys.BSC_MAINNET`, …). 20 values today.
101
+
102
+ Hooks that accept both enforce **exactly one** at runtime *and* at the type level. Passing both, neither, or `undefined` for both throws.
103
+
104
+ ```ts
105
+ useXAccount({ xChainType: 'EVM' }); // ✅ family-level
106
+ useXAccount({ xChainId: ChainKeys.BSC_MAINNET }); // ✅ key-level
107
+ useXAccount({ xChainType: 'EVM', xChainId: ChainKeys.BSC }); // ❌ throws
108
+ useXAccount({}); // ❌ throws
109
+ ```
110
+
111
+ When to pick which:
112
+
113
+ - **Use `xChainType`** for family-level reads (e.g. "is the user connected to *any* EVM chain?"). One connection covers every configured EVM network.
114
+ - **Use `xChainId`** when you need the narrowest TypeScript inference (e.g. `useWalletProvider({ xChainId: ChainKeys.BSC_MAINNET })` returns `IEvmWalletProvider | undefined`, not the broader `IWalletProvider`).
115
+
116
+ The chain-id form is required when handing off to `@sodax/sdk` — SDK calls take a chain-narrowed wallet provider.
117
+
118
+ ---
119
+
120
+ ## EVM is one connection across every configured EVM network
121
+
122
+ A subtle but important model choice: wagmi treats every configured EVM chain as one connector session. v2 reflects this directly — `useChainGroups()` collapses EVM into a single row, and `useXAccount({ xChainType: 'EVM' })` returns the same `address` regardless of which chain the user is currently on.
123
+
124
+ Consequences:
125
+
126
+ - **No per-EVM-network connect/disconnect UI.** "Connect to Ethereum" and "Connect to BSC" are the same operation.
127
+ - **Switching networks does not re-connect.** `useEvmSwitchChain({ xChainId })` calls wagmi's `switchChain` and fires the `chainChanged` EIP-1193 event. The connector stays connected.
128
+ - **`useEvmSwitchChain` owns the "wrong network" comparison.** Pass the *expected* `xChainId`; the hook returns `{ isWrongChain, handleSwitchChain }`. Don't recompute this in UI code.
129
+ - **Injective + MetaMask special case.** `useEvmSwitchChain` auto-switches to Ethereum mainnet when the user connects to Injective via MetaMask (Injective has a separate native chain but uses MetaMask's EVM signing for some flows). Transparent to consumers.
130
+
131
+ Other chain families do **not** have this single-connection model — Bitcoin connects to one address per connector, Solana to one wallet, etc.
132
+
133
+ ---
134
+
135
+ ## Bridging to `@sodax/sdk`
136
+
137
+ The `IXxxWalletProvider` interface is the contract between this package and `@sodax/sdk`. Consumers call `useWalletProvider({ xChainId })` to get a chain-narrowed provider, then pass it to SDK methods:
138
+
139
+ ```ts
140
+ import { useWalletProvider } from '@sodax/wallet-sdk-react';
141
+ import { useSwap } from '@sodax/dapp-kit';
142
+ import { ChainKeys } from '@sodax/types';
143
+
144
+ const evm = useWalletProvider({ xChainId: ChainKeys.ETHEREUM_MAINNET });
145
+ // evm: IEvmWalletProvider | undefined
146
+
147
+ const swap = useSwap();
148
+ if (evm) await swap.mutateAsync({ ..., walletProvider: evm });
149
+ ```
150
+
151
+ The wallet provider is `undefined` until the user is connected on that chain family. SDK calls should always guard on `!= undefined`. See [`recipes/bridge-to-sdk.md`](./recipes/bridge-to-sdk.md) for the full pattern.
152
+
153
+ ---
154
+
155
+ ## What lives where (file-system tour)
156
+
157
+ ```
158
+ src/
159
+ ├── SodaxWalletProvider.tsx # Root provider — mounts slots conditionally
160
+ ├── core/ # XService + XConnector abstract base classes
161
+ ├── context/ # WalletConfigContext (frozen config injection)
162
+ ├── hooks/ # All public `useXxx` hooks (read-only)
163
+ ├── actions/ # Imperative helpers (getXService, getXChainType)
164
+ ├── utils/ # sortConnectors, walletRpcConfig, isNativeToken
165
+ ├── types/ # Public types — config, interfaces, chainActions
166
+ ├── providers/ # Per-chain-type React provider implementations
167
+ └── xchains/<chain>/ # Per-chain XService + XConnector implementations
168
+ │ # Sub-path import: '@sodax/wallet-sdk-react/xchains/<chain>'
169
+ └── ... # Auto-discovered by tsup; not re-exported from barrel
170
+ ```
171
+
172
+ Adding a new chain = create `src/xchains/<chain>/index.ts`. tsup picks it up automatically; the package barrel does not need editing.
173
+
174
+ ---
175
+
176
+ ## Things that look weird until you know why
177
+
178
+ - **`useXAccount` returns an object with `address: undefined` when disconnected**, not `undefined` itself. Avoids `account?.address` chains everywhere.
179
+ - **`useXConnect` resolves to `undefined` for provider-managed chains** (EVM/Solana/Sui). The mutation kicks off the adapter's connect flow; the address arrives via the adapter's state, not the mutation result. Read it via `useXAccount` after the mutation lands.
180
+ - **`useEvmSwitchChain` returns `{ isWrongChain: false, handleSwitchChain: noop }` if EVM is not in `walletConfig`.** Same shape, no-op behavior — so UI code does not need to branch on "is EVM enabled".
181
+ - **Concrete chain classes are not on the barrel.** `XverseXConnector`, `EvmXService`, etc. live behind `@sodax/wallet-sdk-react/xchains/<chain>`. Default to barrel imports; opt into deep imports only when you need `instanceof` or to extend a class.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Minimal EVM setup — provider + single connect button.
3
+ * Smallest working integration of @sodax/wallet-sdk-react.
4
+ *
5
+ * Run: drop this file in a Vite/CRA app, render <App /> at the root.
6
+ */
7
+
8
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
9
+ import {
10
+ SodaxWalletProvider,
11
+ type SodaxWalletConfig,
12
+ useXAccount,
13
+ useXConnect,
14
+ useXConnectors,
15
+ useXDisconnect,
16
+ } from '@sodax/wallet-sdk-react';
17
+ import { ChainKeys } from '@sodax/types';
18
+
19
+ // Module-level constants — never recreate these inside a component.
20
+ const queryClient = new QueryClient();
21
+
22
+ const walletConfig: SodaxWalletConfig = {
23
+ EVM: {
24
+ ssr: false,
25
+ chains: {
26
+ [ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
27
+ [ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
28
+ [ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
29
+ },
30
+ },
31
+ };
32
+
33
+ export function App() {
34
+ return (
35
+ <QueryClientProvider client={queryClient}>
36
+ <SodaxWalletProvider config={walletConfig}>
37
+ <ConnectButton />
38
+ </SodaxWalletProvider>
39
+ </QueryClientProvider>
40
+ );
41
+ }
42
+
43
+ function ConnectButton() {
44
+ const connectors = useXConnectors({ xChainType: 'EVM' });
45
+ const { mutateAsync: connect, isPending } = useXConnect();
46
+ const account = useXAccount({ xChainType: 'EVM' });
47
+ const disconnect = useXDisconnect();
48
+
49
+ if (account.address) {
50
+ return (
51
+ <div>
52
+ <code>{account.address}</code>
53
+ <button type="button" onClick={() => disconnect({ xChainType: 'EVM' })}>
54
+ Disconnect
55
+ </button>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <div>
62
+ {connectors.map((connector) => (
63
+ <button
64
+ type="button"
65
+ key={connector.id}
66
+ onClick={() => connect(connector).catch(() => {})}
67
+ disabled={isPending || !connector.isInstalled}
68
+ >
69
+ {connector.name}
70
+ {!connector.isInstalled && ' (not installed)'}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ );
75
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Multi-chain headless wallet modal.
3
+ * Renders chain picker → wallet picker → connecting → success/error using `useWalletModal`.
4
+ *
5
+ * UI uses native <dialog> — replace with shadcn/Radix/your dialog primitive.
6
+ */
7
+
8
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
9
+ import {
10
+ SodaxWalletProvider,
11
+ type SodaxWalletConfig,
12
+ useWalletModal,
13
+ useChainGroups,
14
+ useXConnectors,
15
+ sortConnectors,
16
+ type IXConnector,
17
+ } from '@sodax/wallet-sdk-react';
18
+ import type { ChainType } from '@sodax/types';
19
+ import { ChainKeys } from '@sodax/types';
20
+
21
+ const queryClient = new QueryClient();
22
+
23
+ const walletConfig: SodaxWalletConfig = {
24
+ EVM: {
25
+ chains: {
26
+ [ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
27
+ [ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
28
+ },
29
+ },
30
+ SOLANA: {
31
+ chains: { [ChainKeys.SOLANA_MAINNET]: { rpcUrl: 'https://api.mainnet-beta.solana.com' } },
32
+ },
33
+ SUI: { network: 'mainnet' },
34
+ BITCOIN: {},
35
+ ICON: {
36
+ chains: { [ChainKeys.ICON_MAINNET]: { rpcUrl: 'https://ctz.solidwallet.io/api/v3' } },
37
+ },
38
+ };
39
+
40
+ export function App() {
41
+ return (
42
+ <QueryClientProvider client={queryClient}>
43
+ <SodaxWalletProvider config={walletConfig}>
44
+ <WalletModalRoot />
45
+ </SodaxWalletProvider>
46
+ </QueryClientProvider>
47
+ );
48
+ }
49
+
50
+ function WalletModalRoot() {
51
+ const modal = useWalletModal({
52
+ onConnected: (chainType, account) => {
53
+ console.log('connected', chainType, account.address);
54
+ },
55
+ });
56
+
57
+ switch (modal.state.kind) {
58
+ case 'closed':
59
+ return (
60
+ <button type="button" onClick={modal.open}>
61
+ Connect Wallet
62
+ </button>
63
+ );
64
+
65
+ case 'chainSelect':
66
+ return <ChainPicker onPick={modal.selectChain} onClose={modal.close} />;
67
+
68
+ case 'walletSelect':
69
+ return (
70
+ <WalletPicker
71
+ chainType={modal.state.chainType}
72
+ onPick={modal.selectWallet}
73
+ onBack={modal.back}
74
+ onClose={modal.close}
75
+ />
76
+ );
77
+
78
+ case 'connecting':
79
+ // Hide modal while wagmi's QR modal owns the screen for WalletConnect
80
+ if (modal.state.connector.id === 'walletConnect') return null;
81
+ return (
82
+ <Dialog onClose={modal.close}>
83
+ <p>Approve in {modal.state.connector.name}…</p>
84
+ <button type="button" onClick={modal.back}>
85
+ Cancel
86
+ </button>
87
+ </Dialog>
88
+ );
89
+
90
+ case 'error':
91
+ return (
92
+ <Dialog onClose={modal.close}>
93
+ <p>Error: {modal.state.error.message}</p>
94
+ {!modal.state.connector.isInstalled && modal.state.connector.installUrl && (
95
+ <a href={modal.state.connector.installUrl} target="_blank" rel="noreferrer">
96
+ Install {modal.state.connector.name}
97
+ </a>
98
+ )}
99
+ <button type="button" onClick={modal.retry}>
100
+ Retry
101
+ </button>
102
+ <button type="button" onClick={modal.back}>
103
+ Pick another wallet
104
+ </button>
105
+ </Dialog>
106
+ );
107
+
108
+ case 'success':
109
+ // onConnected fired; close the modal
110
+ queueMicrotask(modal.close);
111
+ return null;
112
+
113
+ default:
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function ChainPicker({ onPick, onClose }: { onPick: (c: ChainType) => void; onClose: () => void }) {
119
+ const groups = useChainGroups({ order: ['EVM', 'SOLANA', 'SUI', 'BITCOIN', 'ICON'] });
120
+ return (
121
+ <Dialog onClose={onClose}>
122
+ <h2>Select a chain</h2>
123
+ {groups.map(group => (
124
+ <button type="button" key={group.chainType} onClick={() => onPick(group.chainType)}>
125
+ <span>{group.displayName}</span>
126
+ {group.isConnected && ' ✓'}
127
+ </button>
128
+ ))}
129
+ </Dialog>
130
+ );
131
+ }
132
+
133
+ const PREFERRED = ['hana', 'metamask', 'phantom'] as const;
134
+
135
+ function WalletPicker({
136
+ chainType,
137
+ onPick,
138
+ onBack,
139
+ onClose,
140
+ }: {
141
+ chainType: ChainType;
142
+ onPick: (c: IXConnector) => void;
143
+ onBack: () => void;
144
+ onClose: () => void;
145
+ }) {
146
+ const connectors = sortConnectors(useXConnectors({ xChainType: chainType }), { preferred: PREFERRED });
147
+ return (
148
+ <Dialog onClose={onClose}>
149
+ <button type="button" onClick={onBack}>
150
+ ← Back
151
+ </button>
152
+ <h2>Connect to {chainType}</h2>
153
+ {connectors.map(connector => (
154
+ <button type="button" key={connector.id} onClick={() => onPick(connector)} disabled={!connector.isInstalled}>
155
+ {connector.name}
156
+ {!connector.isInstalled && ' (not installed)'}
157
+ </button>
158
+ ))}
159
+ </Dialog>
160
+ );
161
+ }
162
+
163
+ function Dialog({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
164
+ return (
165
+ <dialog open onClose={onClose} style={{ padding: 16 }}>
166
+ {children}
167
+ </dialog>
168
+ );
169
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Next.js 15 App Router setup with SSR-safe hydration.
3
+ *
4
+ * This file shows three separate files merged into one for reference. In your
5
+ * project, split them into the matching paths:
6
+ *
7
+ * app/
8
+ * ├── layout.tsx ← server component (RootLayout below — make it the file's `export default`)
9
+ * ├── providers.tsx ← client component (Providers below — needs `'use client'` at top of file)
10
+ * └── page.tsx ← client component (Home below — needs `'use client'` at top of file)
11
+ *
12
+ * Key SSR points:
13
+ * - `EVM.ssr: true` enables wagmi cookie-based hydration.
14
+ * - `'use client'` on every component that calls a hook from this package.
15
+ * - QueryClient as a module constant — one client per app, never recreated.
16
+ * - SodaxWalletProvider config also a module constant — frozen on first render.
17
+ *
18
+ * NOTE: this combined file uses named exports only so it lints clean. Each of
19
+ * `RootLayout`, `Providers`, and `Home` should be the `export default` of its
20
+ * own file in your real project.
21
+ */
22
+
23
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
24
+ import { SodaxWalletProvider, type SodaxWalletConfig, useConnectedChains } from '@sodax/wallet-sdk-react';
25
+ import { ChainKeys } from '@sodax/types';
26
+ import type { ReactNode } from 'react';
27
+
28
+ // ============================================================
29
+ // app/providers.tsx — client component
30
+ // (top of file: `'use client';`)
31
+ // ============================================================
32
+
33
+ const queryClient = new QueryClient({
34
+ defaultOptions: {
35
+ queries: { staleTime: 60 * 1000 },
36
+ },
37
+ });
38
+
39
+ const walletConfig: SodaxWalletConfig = {
40
+ EVM: {
41
+ ssr: true, // ← required for Next.js — enables wagmi cookies hydration
42
+ chains: {
43
+ [ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
44
+ [ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
45
+ [ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
46
+ },
47
+ },
48
+ };
49
+
50
+ export function Providers({ children }: { children: ReactNode }) {
51
+ return (
52
+ <QueryClientProvider client={queryClient}>
53
+ <SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
54
+ </QueryClientProvider>
55
+ );
56
+ }
57
+
58
+ // ============================================================
59
+ // app/layout.tsx — server component
60
+ // (no `'use client'`; in your real file, change `export function` to `export default function`)
61
+ // ============================================================
62
+
63
+ export function RootLayout({ children }: { children: ReactNode }) {
64
+ return (
65
+ <html lang="en">
66
+ <body>
67
+ <Providers>{children}</Providers>
68
+ </body>
69
+ </html>
70
+ );
71
+ }
72
+
73
+ // ============================================================
74
+ // app/page.tsx — client component (because we use a hook)
75
+ // (top of file: `'use client';`; change `export function` to `export default function`)
76
+ // ============================================================
77
+
78
+ export function Home() {
79
+ // Gate UI on hydration to prevent reload flicker
80
+ const { chains, status } = useConnectedChains();
81
+
82
+ if (status === 'loading') {
83
+ return <p>Loading…</p>;
84
+ }
85
+
86
+ if (chains.length === 0) {
87
+ return <p>No wallets connected</p>;
88
+ }
89
+
90
+ return (
91
+ <ul>
92
+ {chains.map((c) => (
93
+ <li key={c.chainType}>
94
+ {c.chainType}: <code>{c.account.address}</code>
95
+ </li>
96
+ ))}
97
+ </ul>
98
+ );
99
+ }