@sodax/wallet-sdk-react 2.0.0-rc.2 → 2.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +12 -5
  2. package/dist/{chunk-BKJB527E.mjs → chunk-3QETHO6P.mjs} +1 -3
  3. package/dist/{chunk-PJLEJVAU.mjs → chunk-42LTUHMZ.mjs} +1 -3
  4. package/dist/{chunk-NY7U7OJW.mjs → chunk-7V7O3Q7Y.mjs} +0 -2
  5. package/dist/{chunk-BXJLBR4G.mjs → chunk-C6M34IVL.mjs} +2 -4
  6. package/dist/{chunk-XZ7CHO2S.mjs → chunk-FSOGMSJH.mjs} +2 -4
  7. package/dist/{chunk-X2MHIWXO.mjs → chunk-IFXZQW4C.mjs} +0 -2
  8. package/dist/{chunk-7ULB6DW4.mjs → chunk-JQ4H4GJ5.mjs} +3 -5
  9. package/dist/{chunk-N5A2TMF6.mjs → chunk-LKSSME2J.mjs} +2 -4
  10. package/dist/{chunk-PLCA4ZDJ.mjs → chunk-LUKR7YKV.mjs} +54 -30
  11. package/dist/{chunk-MXZVF5HR.mjs → chunk-NAKCAL2M.mjs} +0 -2
  12. package/dist/chunk-QMXBY3UI.mjs +1 -0
  13. package/dist/{chunk-MAQ47Q52.mjs → chunk-TACW7Z4D.mjs} +0 -2
  14. package/dist/{chunk-2BOUGCJ7.mjs → chunk-WPZOLGVB.mjs} +4 -6
  15. package/dist/{chunk-66BAUK56.mjs → chunk-X7BHR7WS.mjs} +2 -4
  16. package/dist/{chunk-E5IAZ7E6.mjs → chunk-Z5GXDHGL.mjs} +9 -5
  17. package/dist/{config-OlnzyEUE.d.ts → config-GVKK8IfY.d.ts} +6 -1
  18. package/dist/index.d.ts +4 -4
  19. package/dist/index.mjs +20 -31
  20. package/dist/xchains/bitcoin/index.mjs +14 -16
  21. package/dist/xchains/evm/index.d.ts +1 -1
  22. package/dist/xchains/evm/index.mjs +3 -5
  23. package/dist/xchains/icon/index.mjs +5 -7
  24. package/dist/xchains/injective/index.mjs +3 -5
  25. package/dist/xchains/near/index.mjs +4 -6
  26. package/dist/xchains/solana/index.mjs +5 -7
  27. package/dist/xchains/stacks/index.mjs +3 -5
  28. package/dist/xchains/stellar/index.mjs +4 -6
  29. package/dist/xchains/sui/index.mjs +5 -7
  30. package/docs/ADDING_A_NEW_CHAIN.md +1 -1
  31. package/docs/SUB_PATH_EXPORTS.md +14 -42
  32. package/package.json +32 -23
  33. package/ai-exported/AGENTS.md +0 -122
  34. package/ai-exported/integration/README.md +0 -102
  35. package/ai-exported/integration/ai-rules.md +0 -136
  36. package/ai-exported/integration/architecture.md +0 -181
  37. package/ai-exported/integration/examples/01-minimal-evm.tsx +0 -75
  38. package/ai-exported/integration/examples/02-multi-chain-modal.tsx +0 -169
  39. package/ai-exported/integration/examples/03-nextjs-app-router.tsx +0 -99
  40. package/ai-exported/integration/examples/04-walletconnect-setup.tsx +0 -89
  41. package/ai-exported/integration/examples/README.md +0 -29
  42. package/ai-exported/integration/recipes/batch-operations.md +0 -223
  43. package/ai-exported/integration/recipes/bridge-to-sdk.md +0 -164
  44. package/ai-exported/integration/recipes/chain-detection.md +0 -254
  45. package/ai-exported/integration/recipes/connect-button.md +0 -156
  46. package/ai-exported/integration/recipes/multi-chain-modal.md +0 -199
  47. package/ai-exported/integration/recipes/setup.md +0 -158
  48. package/ai-exported/integration/recipes/sign-message.md +0 -137
  49. package/ai-exported/integration/recipes/sub-path-imports.md +0 -95
  50. package/ai-exported/integration/recipes/switch-chain.md +0 -141
  51. package/ai-exported/integration/recipes/walletconnect-setup.md +0 -139
  52. package/ai-exported/integration/reference/api-surface.md +0 -175
  53. package/ai-exported/integration/reference/chain-support.md +0 -78
  54. package/ai-exported/integration/reference/connectors.md +0 -74
  55. package/ai-exported/integration/reference/hooks.md +0 -204
  56. package/ai-exported/integration/reference/wallet-brands.md +0 -106
  57. package/ai-exported/migration/README.md +0 -49
  58. package/ai-exported/migration/ai-rules.md +0 -144
  59. package/ai-exported/migration/breaking-changes.md +0 -305
  60. package/ai-exported/migration/checklist.md +0 -159
  61. package/ai-exported/migration/recipes/connect-button.md +0 -166
  62. package/ai-exported/migration/recipes/multi-chain-modal.md +0 -244
  63. package/ai-exported/migration/recipes/ssr-setup.md +0 -162
  64. package/ai-exported/migration/recipes/walletconnect-migration.md +0 -168
  65. package/ai-exported/migration/reference/components.md +0 -73
  66. package/ai-exported/migration/reference/config.md +0 -307
  67. package/ai-exported/migration/reference/hooks.md +0 -278
  68. package/ai-exported/migration/reference/imports.md +0 -157
  69. package/dist/chunk-2BOUGCJ7.mjs.map +0 -1
  70. package/dist/chunk-66BAUK56.mjs.map +0 -1
  71. package/dist/chunk-7ULB6DW4.mjs.map +0 -1
  72. package/dist/chunk-BKJB527E.mjs.map +0 -1
  73. package/dist/chunk-BXJLBR4G.mjs.map +0 -1
  74. package/dist/chunk-E5IAZ7E6.mjs.map +0 -1
  75. package/dist/chunk-MAQ47Q52.mjs.map +0 -1
  76. package/dist/chunk-MXZVF5HR.mjs.map +0 -1
  77. package/dist/chunk-N5A2TMF6.mjs.map +0 -1
  78. package/dist/chunk-NY7U7OJW.mjs.map +0 -1
  79. package/dist/chunk-PJLEJVAU.mjs.map +0 -1
  80. package/dist/chunk-PLCA4ZDJ.mjs.map +0 -1
  81. package/dist/chunk-TZMKDXFA.mjs +0 -3
  82. package/dist/chunk-TZMKDXFA.mjs.map +0 -1
  83. package/dist/chunk-X2MHIWXO.mjs.map +0 -1
  84. package/dist/chunk-XZ7CHO2S.mjs.map +0 -1
  85. package/dist/index.cjs +0 -3337
  86. package/dist/index.cjs.map +0 -1
  87. package/dist/index.mjs.map +0 -1
  88. package/dist/xchains/bitcoin/index.cjs +0 -1927
  89. package/dist/xchains/bitcoin/index.cjs.map +0 -1
  90. package/dist/xchains/bitcoin/index.mjs.map +0 -1
  91. package/dist/xchains/evm/index.cjs +0 -316
  92. package/dist/xchains/evm/index.cjs.map +0 -1
  93. package/dist/xchains/evm/index.mjs.map +0 -1
  94. package/dist/xchains/icon/index.cjs +0 -311
  95. package/dist/xchains/icon/index.cjs.map +0 -1
  96. package/dist/xchains/icon/index.mjs.map +0 -1
  97. package/dist/xchains/injective/index.cjs +0 -223
  98. package/dist/xchains/injective/index.cjs.map +0 -1
  99. package/dist/xchains/injective/index.mjs.map +0 -1
  100. package/dist/xchains/near/index.cjs +0 -190
  101. package/dist/xchains/near/index.cjs.map +0 -1
  102. package/dist/xchains/near/index.mjs.map +0 -1
  103. package/dist/xchains/solana/index.cjs +0 -186
  104. package/dist/xchains/solana/index.cjs.map +0 -1
  105. package/dist/xchains/solana/index.mjs.map +0 -1
  106. package/dist/xchains/stacks/index.cjs +0 -240
  107. package/dist/xchains/stacks/index.cjs.map +0 -1
  108. package/dist/xchains/stacks/index.mjs.map +0 -1
  109. package/dist/xchains/stellar/index.cjs +0 -322
  110. package/dist/xchains/stellar/index.cjs.map +0 -1
  111. package/dist/xchains/stellar/index.mjs.map +0 -1
  112. package/dist/xchains/sui/index.cjs +0 -248
  113. package/dist/xchains/sui/index.cjs.map +0 -1
  114. package/dist/xchains/sui/index.mjs.map +0 -1
  115. package/skills/SKILLS.md +0 -84
  116. package/skills/bridge-to-sdk.md +0 -148
  117. package/skills/connect-button.md +0 -116
  118. package/skills/evm-only-walletconnect.md +0 -111
  119. package/skills/multi-chain-modal.md +0 -178
  120. package/skills/setup.md +0 -107
@@ -1,181 +0,0 @@
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.
@@ -1,75 +0,0 @@
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
- }
@@ -1,169 +0,0 @@
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
- }
@@ -1,99 +0,0 @@
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
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * WalletConnect setup — adds the WalletConnect connector to the EVM modal.
3
- * For dApps that target users on mobile-only or enterprise custody wallets
4
- * (Ledger Live, Safe, Fireblocks, ...) which cannot install browser extensions.
5
- *
6
- * Get a projectId at https://cloud.walletconnect.com
7
- *
8
- * To narrow the WalletConnect modal to ONE specific wallet (e.g. an enterprise
9
- * custody integration), see the commented `qrModalOptions` block below — fill
10
- * in the target wallet id from https://walletconnect.com/explorer.
11
- */
12
-
13
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
14
- import {
15
- SodaxWalletProvider,
16
- type SodaxWalletConfig,
17
- useWalletModal,
18
- useXConnectors,
19
- useXAccount,
20
- useXDisconnect,
21
- } from '@sodax/wallet-sdk-react';
22
- import { ChainKeys } from '@sodax/types';
23
-
24
- const queryClient = new QueryClient();
25
-
26
- const walletConfig: SodaxWalletConfig = {
27
- EVM: {
28
- ssr: true,
29
- chains: {
30
- [ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
31
- [ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
32
- [ChainKeys.ARBITRUM_MAINNET]: { rpcUrl: 'https://arb1.arbitrum.io/rpc' },
33
- },
34
- walletConnect: {
35
- projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID ?? '',
36
- // Optional — restrict the WalletConnect QR modal to a specific wallet:
37
- //
38
- // qrModalOptions: {
39
- // explorerRecommendedWalletIds: ['<target-wallet-id-from-walletconnect-explorer>'],
40
- // explorerExcludedWalletIds: 'ALL', // hide everything except recommended
41
- // },
42
- },
43
- },
44
- };
45
-
46
- export function App() {
47
- return (
48
- <QueryClientProvider client={queryClient}>
49
- <SodaxWalletProvider config={walletConfig}>
50
- <ConnectWithWalletConnect />
51
- </SodaxWalletProvider>
52
- </QueryClientProvider>
53
- );
54
- }
55
-
56
- function ConnectWithWalletConnect() {
57
- const modal = useWalletModal();
58
- const connectors = useXConnectors({ xChainType: 'EVM' });
59
- const account = useXAccount({ xChainType: 'EVM' });
60
- const disconnect = useXDisconnect();
61
-
62
- const wcConnector = connectors.find((c) => c.id === 'walletConnect');
63
-
64
- // Hide the modal while wagmi/WalletConnect's QR modal owns the screen
65
- if (modal.state.kind === 'connecting' && modal.state.connector.id === 'walletConnect') {
66
- return null;
67
- }
68
-
69
- if (account.address) {
70
- return (
71
- <div>
72
- <code>{account.address}</code>
73
- <button type="button" onClick={() => disconnect({ xChainType: 'EVM' })}>
74
- Disconnect
75
- </button>
76
- </div>
77
- );
78
- }
79
-
80
- if (!wcConnector) {
81
- return <p>WalletConnect not configured — set NEXT_PUBLIC_WC_PROJECT_ID</p>;
82
- }
83
-
84
- return (
85
- <button type="button" onClick={() => modal.selectWallet(wcConnector)}>
86
- Connect via WalletConnect
87
- </button>
88
- );
89
- }
@@ -1,29 +0,0 @@
1
- # Examples
2
-
3
- Copy-paste-runnable code examples. Each file is complete — drop it into a fresh React 19 project with `@sodax/wallet-sdk-react` + `@tanstack/react-query` installed and it works.
4
-
5
- For narrative + step-by-step explanation, see [`../recipes/`](../recipes/). Examples here are pure code with minimal comments.
6
-
7
- | File | Use case | Lines |
8
- |---|---|---|
9
- | [`01-minimal-evm.tsx`](./01-minimal-evm.tsx) | Smallest working setup — provider + 1 connect button | ~50 |
10
- | [`02-multi-chain-modal.tsx`](./02-multi-chain-modal.tsx) | Headless wallet modal with chain → connector → connecting flow | ~120 |
11
- | [`03-nextjs-app-router.tsx`](./03-nextjs-app-router.tsx) | Next.js 15 App Router setup (SSR + `'use client'`) | ~60 |
12
- | [`04-walletconnect-setup.tsx`](./04-walletconnect-setup.tsx) | WalletConnect connector setup (with optional single-wallet filter) | ~50 |
13
-
14
- ## Conventions
15
-
16
- - All files use TypeScript (`.tsx`).
17
- - All consumer components have `'use client'` (Next.js compatible).
18
- - `walletConfig` is defined as a **module-level constant** — never created inside a component (the provider freezes config on first render).
19
- - `QueryClient` is also a module-level constant — one client per app, shared across providers.
20
- - Examples use `<button>`, `<select>`, `<dialog>` etc. directly — bring your own UI library.
21
-
22
- ## Running an example
23
-
24
- ```bash
25
- pnpm add @sodax/wallet-sdk-react @tanstack/react-query
26
- # also: react@>=19, react-dom@>=19
27
- ```
28
-
29
- Drop the file into your app, mount the provider component at the root, and use the consumer component anywhere below it.