@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.
- package/README.md +103 -145
- package/ai-exported/AGENTS.md +122 -0
- package/ai-exported/integration/README.md +102 -0
- package/ai-exported/integration/ai-rules.md +136 -0
- package/ai-exported/integration/architecture.md +181 -0
- package/ai-exported/integration/examples/01-minimal-evm.tsx +75 -0
- package/ai-exported/integration/examples/02-multi-chain-modal.tsx +169 -0
- package/ai-exported/integration/examples/03-nextjs-app-router.tsx +99 -0
- package/ai-exported/integration/examples/04-walletconnect-setup.tsx +89 -0
- package/ai-exported/integration/examples/README.md +29 -0
- package/ai-exported/integration/recipes/batch-operations.md +223 -0
- package/ai-exported/integration/recipes/bridge-to-sdk.md +164 -0
- package/ai-exported/integration/recipes/chain-detection.md +254 -0
- package/ai-exported/integration/recipes/connect-button.md +156 -0
- package/ai-exported/integration/recipes/multi-chain-modal.md +199 -0
- package/ai-exported/integration/recipes/setup.md +158 -0
- package/ai-exported/integration/recipes/sign-message.md +137 -0
- package/ai-exported/integration/recipes/sub-path-imports.md +95 -0
- package/ai-exported/integration/recipes/switch-chain.md +141 -0
- package/ai-exported/integration/recipes/walletconnect-setup.md +139 -0
- package/ai-exported/integration/reference/api-surface.md +175 -0
- package/ai-exported/integration/reference/chain-support.md +78 -0
- package/ai-exported/integration/reference/connectors.md +74 -0
- package/ai-exported/integration/reference/hooks.md +204 -0
- package/ai-exported/integration/reference/wallet-brands.md +106 -0
- package/ai-exported/migration/README.md +49 -0
- package/ai-exported/migration/ai-rules.md +144 -0
- package/ai-exported/migration/breaking-changes.md +305 -0
- package/ai-exported/migration/checklist.md +159 -0
- package/ai-exported/migration/recipes/connect-button.md +166 -0
- package/ai-exported/migration/recipes/multi-chain-modal.md +244 -0
- package/ai-exported/migration/recipes/ssr-setup.md +162 -0
- package/ai-exported/migration/recipes/walletconnect-migration.md +168 -0
- package/ai-exported/migration/reference/components.md +73 -0
- package/ai-exported/migration/reference/config.md +307 -0
- package/ai-exported/migration/reference/hooks.md +278 -0
- package/ai-exported/migration/reference/imports.md +157 -0
- package/dist/XConnector-B9YQTVJ4.d.ts +146 -0
- package/dist/chunk-2BOUGCJ7.mjs +150 -0
- package/dist/chunk-2BOUGCJ7.mjs.map +1 -0
- package/dist/chunk-66BAUK56.mjs +202 -0
- package/dist/chunk-66BAUK56.mjs.map +1 -0
- package/dist/chunk-7ULB6DW4.mjs +102 -0
- package/dist/chunk-7ULB6DW4.mjs.map +1 -0
- package/dist/chunk-BKJB527E.mjs +125 -0
- package/dist/chunk-BKJB527E.mjs.map +1 -0
- package/dist/chunk-BXJLBR4G.mjs +88 -0
- package/dist/chunk-BXJLBR4G.mjs.map +1 -0
- package/dist/chunk-E5IAZ7E6.mjs +186 -0
- package/dist/chunk-E5IAZ7E6.mjs.map +1 -0
- package/dist/chunk-MAQ47Q52.mjs +33 -0
- package/dist/chunk-MAQ47Q52.mjs.map +1 -0
- package/dist/chunk-MXZVF5HR.mjs +34 -0
- package/dist/chunk-MXZVF5HR.mjs.map +1 -0
- package/dist/chunk-N5A2TMF6.mjs +33 -0
- package/dist/chunk-N5A2TMF6.mjs.map +1 -0
- package/dist/chunk-NY7U7OJW.mjs +64 -0
- package/dist/chunk-NY7U7OJW.mjs.map +1 -0
- package/dist/chunk-PJLEJVAU.mjs +140 -0
- package/dist/chunk-PJLEJVAU.mjs.map +1 -0
- package/dist/chunk-PLCA4ZDJ.mjs +1585 -0
- package/dist/chunk-PLCA4ZDJ.mjs.map +1 -0
- package/dist/chunk-TZMKDXFA.mjs +3 -0
- package/dist/chunk-TZMKDXFA.mjs.map +1 -0
- package/dist/chunk-X2MHIWXO.mjs +100 -0
- package/dist/chunk-X2MHIWXO.mjs.map +1 -0
- package/dist/chunk-XZ7CHO2S.mjs +41 -0
- package/dist/chunk-XZ7CHO2S.mjs.map +1 -0
- package/dist/config-OlnzyEUE.d.ts +146 -0
- package/dist/index.cjs +2784 -1594
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +768 -1498
- package/dist/index.mjs +463 -2004
- package/dist/index.mjs.map +1 -1
- package/dist/xchains/bitcoin/index.cjs +1927 -0
- package/dist/xchains/bitcoin/index.cjs.map +1 -0
- package/dist/xchains/bitcoin/index.d.ts +125 -0
- package/dist/xchains/bitcoin/index.mjs +16 -0
- package/dist/xchains/bitcoin/index.mjs.map +1 -0
- package/dist/xchains/evm/index.cjs +316 -0
- package/dist/xchains/evm/index.cjs.map +1 -0
- package/dist/xchains/evm/index.d.ts +39 -0
- package/dist/xchains/evm/index.mjs +5 -0
- package/dist/xchains/evm/index.mjs.map +1 -0
- package/dist/xchains/icon/index.cjs +311 -0
- package/dist/xchains/icon/index.cjs.map +1 -0
- package/dist/xchains/icon/index.d.ts +37 -0
- package/dist/xchains/icon/index.mjs +7 -0
- package/dist/xchains/icon/index.mjs.map +1 -0
- package/dist/xchains/injective/index.cjs +223 -0
- package/dist/xchains/injective/index.cjs.map +1 -0
- package/dist/xchains/injective/index.d.ts +35 -0
- package/dist/xchains/injective/index.mjs +5 -0
- package/dist/xchains/injective/index.mjs.map +1 -0
- package/dist/xchains/near/index.cjs +190 -0
- package/dist/xchains/near/index.cjs.map +1 -0
- package/dist/xchains/near/index.d.ts +34 -0
- package/dist/xchains/near/index.mjs +6 -0
- package/dist/xchains/near/index.mjs.map +1 -0
- package/dist/xchains/solana/index.cjs +186 -0
- package/dist/xchains/solana/index.cjs.map +1 -0
- package/dist/xchains/solana/index.d.ts +26 -0
- package/dist/xchains/solana/index.mjs +7 -0
- package/dist/xchains/solana/index.mjs.map +1 -0
- package/dist/xchains/stacks/index.cjs +240 -0
- package/dist/xchains/stacks/index.cjs.map +1 -0
- package/dist/xchains/stacks/index.d.ts +36 -0
- package/dist/xchains/stacks/index.mjs +5 -0
- package/dist/xchains/stacks/index.mjs.map +1 -0
- package/dist/xchains/stellar/index.cjs +322 -0
- package/dist/xchains/stellar/index.cjs.map +1 -0
- package/dist/xchains/stellar/index.d.ts +44 -0
- package/dist/xchains/stellar/index.mjs +6 -0
- package/dist/xchains/stellar/index.mjs.map +1 -0
- package/dist/xchains/sui/index.cjs +248 -0
- package/dist/xchains/sui/index.cjs.map +1 -0
- package/dist/xchains/sui/index.d.ts +37 -0
- package/dist/xchains/sui/index.mjs +7 -0
- package/dist/xchains/sui/index.mjs.map +1 -0
- package/docs/ADDING_A_NEW_CHAIN.md +440 -0
- package/docs/ARCHITECTURE.md +291 -0
- package/docs/BATCH_OPERATIONS.md +267 -0
- package/docs/CHAIN_DETECTION.md +216 -0
- package/docs/CONFIGURE_PROVIDER.md +360 -0
- package/docs/CONNECTORS.md +247 -0
- package/docs/CONNECT_FLOW.md +276 -0
- package/docs/EVM_SWITCH_CHAIN.md +161 -0
- package/docs/SIGN_MESSAGE.md +213 -0
- package/docs/SUB_PATH_EXPORTS.md +246 -0
- package/docs/WALLETCONNECT.md +154 -0
- package/docs/WALLET_MODAL.md +331 -0
- package/docs/WALLET_PROVIDER_BRIDGE.md +226 -0
- package/package.json +34 -9
- package/skills/SKILLS.md +84 -0
- package/skills/bridge-to-sdk.md +148 -0
- package/skills/connect-button.md +116 -0
- package/skills/evm-only-walletconnect.md +111 -0
- package/skills/multi-chain-modal.md +178 -0
- package/skills/setup.md +107 -0
- package/dist/index.d.cts +0 -1579
- package/src/Hydrate.ts +0 -65
- package/src/SodaxWalletProvider.tsx +0 -97
- package/src/actions/getXChainType.ts +0 -8
- package/src/actions/getXService.ts +0 -33
- package/src/actions/index.ts +0 -2
- package/src/assets/wallets/hana.svg +0 -6
- package/src/assets/wallets/havah.svg +0 -76
- package/src/assets/wallets/keplr.svg +0 -30
- package/src/assets/wallets/metamask.svg +0 -60
- package/src/assets/wallets/phantom.svg +0 -4
- package/src/assets/wallets/sui.svg +0 -20
- package/src/core/XConnector.ts +0 -54
- package/src/core/XService.ts +0 -85
- package/src/core/index.ts +0 -2
- package/src/hooks/index.ts +0 -11
- package/src/hooks/useEthereumChainId.ts +0 -44
- package/src/hooks/useEvmSwitchChain.ts +0 -91
- package/src/hooks/useWalletProvider.ts +0 -206
- package/src/hooks/useXAccount.ts +0 -51
- package/src/hooks/useXAccounts.ts +0 -56
- package/src/hooks/useXBalances.ts +0 -65
- package/src/hooks/useXConnect.ts +0 -118
- package/src/hooks/useXConnection.ts +0 -72
- package/src/hooks/useXConnectors.ts +0 -72
- package/src/hooks/useXDisconnect.ts +0 -73
- package/src/hooks/useXService.ts +0 -8
- package/src/hooks/useXSignMessage.ts +0 -82
- package/src/index.ts +0 -19
- package/src/types/index.ts +0 -22
- package/src/useXWagmiStore.ts +0 -116
- package/src/utils/index.ts +0 -21
- package/src/xchains/bitcoin/BitcoinXConnector.ts +0 -34
- package/src/xchains/bitcoin/BitcoinXService.ts +0 -40
- package/src/xchains/bitcoin/OKXXConnector.ts +0 -117
- package/src/xchains/bitcoin/UnisatXConnector.ts +0 -117
- package/src/xchains/bitcoin/XverseXConnector.ts +0 -232
- package/src/xchains/bitcoin/index.ts +0 -7
- package/src/xchains/bitcoin/useBitcoinXConnectors.ts +0 -14
- package/src/xchains/evm/EvmXConnector.ts +0 -27
- package/src/xchains/evm/EvmXService.ts +0 -211
- package/src/xchains/evm/index.ts +0 -3
- package/src/xchains/icon/IconHanaXConnector.ts +0 -39
- package/src/xchains/icon/IconXService.ts +0 -117
- package/src/xchains/icon/actions.ts +0 -28
- package/src/xchains/icon/iconex/index.tsx +0 -46
- package/src/xchains/icon/index.ts +0 -2
- package/src/xchains/injective/InjectiveXConnector.ts +0 -60
- package/src/xchains/injective/InjectiveXService.ts +0 -62
- package/src/xchains/injective/actions.ts +0 -32
- package/src/xchains/injective/index.ts +0 -2
- package/src/xchains/near/NearXConnector.ts +0 -42
- package/src/xchains/near/NearXService.ts +0 -46
- package/src/xchains/near/useNearXConnectors.ts +0 -23
- package/src/xchains/solana/SolanaXConnector.ts +0 -26
- package/src/xchains/solana/SolanaXService.ts +0 -46
- package/src/xchains/solana/index.ts +0 -2
- package/src/xchains/stacks/StacksXConnector.ts +0 -63
- package/src/xchains/stacks/StacksXService.ts +0 -59
- package/src/xchains/stacks/constants.ts +0 -42
- package/src/xchains/stacks/index.ts +0 -4
- package/src/xchains/stacks/useStacksXConnectors.ts +0 -7
- package/src/xchains/stellar/CustomSorobanServer.ts +0 -93
- package/src/xchains/stellar/StellarWalletsKitXConnector.ts +0 -53
- package/src/xchains/stellar/StellarXService.ts +0 -93
- package/src/xchains/stellar/actions.ts +0 -24
- package/src/xchains/stellar/index.tsx +0 -2
- package/src/xchains/stellar/useStellarXConnectors.ts +0 -21
- package/src/xchains/stellar/utils.ts +0 -49
- package/src/xchains/sui/SuiXConnector.ts +0 -28
- package/src/xchains/sui/SuiXService.ts +0 -66
- package/src/xchains/sui/index.ts +0 -2
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`@sodax/wallet-sdk-react` is structured around five core ideas: a single Zustand store as source of truth, a chain registry that abstracts over heterogeneous wallet SDKs, a Provider/Hydrator/Actions trio for chains that need React context, async persistence with cleanup for stale connections, and store-first hooks that never call native chain SDK hooks directly.
|
|
4
|
+
|
|
5
|
+
This document covers how these pieces fit together. For consumer-facing API, see [`CONNECT_FLOW.md`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONNECT_FLOW.md) and friends.
|
|
6
|
+
|
|
7
|
+
## Table of contents
|
|
8
|
+
|
|
9
|
+
1. [High-level layout](#high-level-layout)
|
|
10
|
+
2. [Zustand store — single source of truth](#zustand-store--single-source-of-truth)
|
|
11
|
+
3. [Chain registry — abstraction over wallet SDKs](#chain-registry--abstraction-over-wallet-sdks)
|
|
12
|
+
4. [Provider-managed vs non-provider chains](#provider-managed-vs-non-provider-chains)
|
|
13
|
+
5. [Provider/Hydrator/Actions trio](#providerhydratoractions-trio)
|
|
14
|
+
6. [Persistence and hydration](#persistence-and-hydration)
|
|
15
|
+
7. [Store-first hooks](#store-first-hooks)
|
|
16
|
+
8. [Bridge to wallet-sdk-core](#bridge-to-wallet-sdk-core)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## High-level layout
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─ <SodaxWalletProvider config={...}>
|
|
24
|
+
│ ├─ <WalletConfigProvider value={config}> (React context — read by Hydrators)
|
|
25
|
+
│ ├─ <EvmProvider> (if config.EVM) ← provider-managed
|
|
26
|
+
│ │ ├─ <WagmiProvider>
|
|
27
|
+
│ │ │ ├─ <EvmHydrator> ← writes connection + provider to store
|
|
28
|
+
│ │ │ └─ <EvmActions> ← registers ChainActions in store
|
|
29
|
+
│ │ └─ children
|
|
30
|
+
│ ├─ <SolanaProvider> (if config.SOLANA) ← provider-managed
|
|
31
|
+
│ │ └─ <Hydrator>+<Actions>+<adapter>
|
|
32
|
+
│ ├─ <SuiProvider> (if config.SUI) ← provider-managed
|
|
33
|
+
│ │ └─ <Hydrator>+<Actions>+<adapter>
|
|
34
|
+
│ └─ useInitChainServices(config)
|
|
35
|
+
│ ├─ chainRegistry[<chain>].createService(walletConfig)
|
|
36
|
+
│ ├─ chainRegistry[<chain>].defaultConnectors(walletConfig)
|
|
37
|
+
│ ├─ chainRegistry[<chain>].createActions?(...) ← non-provider only
|
|
38
|
+
│ ├─ chainRegistry[<chain>].discoverConnectors? ← async (Stellar)
|
|
39
|
+
│ └─ persist hydration → cleanupDisabledConnections()
|
|
40
|
+
│
|
|
41
|
+
└─ Consumer hooks read from useXWalletStore — never from wagmi/wallet-adapter/dapp-kit directly.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Sources:
|
|
45
|
+
- [`SodaxWalletProvider.tsx`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/SodaxWalletProvider.tsx)
|
|
46
|
+
- [`useXWalletStore.ts`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/useXWalletStore.ts)
|
|
47
|
+
- [`chainRegistry.ts`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/chainRegistry.ts)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Zustand store — single source of truth
|
|
52
|
+
|
|
53
|
+
[`useXWalletStore`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/useXWalletStore.ts) holds **everything** consumers read:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type XWalletStore = {
|
|
57
|
+
xServices: Partial<Record<ChainType, XService>>; // chain service singletons
|
|
58
|
+
xConnections: Partial<Record<ChainType, XConnection>>; // PERSISTED
|
|
59
|
+
xConnectorsByChain: Partial<Record<ChainType, XConnector[]>>;
|
|
60
|
+
enabledChains: ChainType[];
|
|
61
|
+
chainActions: Partial<Record<ChainType, ChainActions>>; // connect/disconnect/signMessage
|
|
62
|
+
walletProviders: Partial<Record<ChainType, IWalletProvider>>; // bridge to wallet-sdk-core
|
|
63
|
+
walletConfig: SodaxWalletConfig | undefined; // user-supplied config snapshot
|
|
64
|
+
|
|
65
|
+
setXConnection(chainType, conn): void;
|
|
66
|
+
unsetXConnection(chainType): void;
|
|
67
|
+
setXConnectors(chainType, conns): void;
|
|
68
|
+
registerChainActions(chainType, actions): void;
|
|
69
|
+
setWalletProvider(chainType, provider): void;
|
|
70
|
+
initChainServices(config): void;
|
|
71
|
+
cleanupDisabledConnections(): void;
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Middleware stack
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
devtools(persist(immer((set, get) => ({...})), { ... }))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Layer | Role |
|
|
82
|
+
|-------|------|
|
|
83
|
+
| `immer` | Lets `set(state => { state.xConnections.EVM = ... })` work without manual spreading |
|
|
84
|
+
| `persist` | Mirrors `xConnections` to `localStorage` (key `'xwagmi-store'`); rehydrates on first mount |
|
|
85
|
+
| `devtools` | Redux DevTools integration for debugging |
|
|
86
|
+
|
|
87
|
+
### What's persisted
|
|
88
|
+
|
|
89
|
+
Only `xConnections`. The rest (services, connectors, actions, wallet providers) is reconstructed on every page load — these contain SDK class instances that don't survive `JSON.stringify`.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
partialize: state => ({ xConnections: state.xConnections })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Storage key is **`'xwagmi-store'`** (kept from v1 for backward compat — existing users don't lose connections on upgrade).
|
|
96
|
+
|
|
97
|
+
### Why one store
|
|
98
|
+
|
|
99
|
+
Earlier iterations had per-chain stores. The single-store design wins because:
|
|
100
|
+
|
|
101
|
+
- `useXAccounts()` / `useXConnections()` / `useChainGroups()` need cross-chain data; per-chain stores would force consumers to subscribe to N stores and fan-in.
|
|
102
|
+
- Persist/hydration semantics are uniform — one store, one rehydrate event, one cleanup pass.
|
|
103
|
+
- Concurrent updates across chains (e.g. user connects EVM and Solana in parallel via `useBatchConnect`) don't race with separate Zustand instances.
|
|
104
|
+
|
|
105
|
+
The `useWalletModalStore` (modal lifecycle) is a separate slice intentionally — modal state is ephemeral UI state, persists nothing, and shares no concerns with connection state.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Chain registry — abstraction over wallet SDKs
|
|
110
|
+
|
|
111
|
+
Each chain family is a single entry in [`chainRegistry`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/chainRegistry.ts):
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type ChainServiceFactory<S extends XService = XService> = {
|
|
115
|
+
createService(walletConfig?): S;
|
|
116
|
+
displayName: string;
|
|
117
|
+
iconUrl?: string;
|
|
118
|
+
defaultConnectors(walletConfig?): XConnector[];
|
|
119
|
+
providerManaged: boolean;
|
|
120
|
+
createActions?(service, getStore): ChainActions;
|
|
121
|
+
createWalletProvider?(service, getStore): IWalletProvider | undefined;
|
|
122
|
+
discoverConnectors?(service, getStore): Promise<void>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const chainRegistry: Record<string, ChainServiceFactory> = {
|
|
126
|
+
EVM: { createService: () => EvmXService.getInstance(), defaultConnectors: () => [], providerManaged: true, ... },
|
|
127
|
+
SOLANA: { ..., providerManaged: true },
|
|
128
|
+
SUI: { ..., providerManaged: true },
|
|
129
|
+
BITCOIN: { ..., providerManaged: false, createActions, createWalletProvider, ... },
|
|
130
|
+
ICON: { ..., providerManaged: false, ... },
|
|
131
|
+
INJECTIVE: { ..., providerManaged: false, createActions, createWalletProvider, ... },
|
|
132
|
+
STELLAR: { ..., providerManaged: false, discoverConnectors, ... },
|
|
133
|
+
NEAR: { ..., providerManaged: false, ... },
|
|
134
|
+
STACKS: { ..., providerManaged: false, ... },
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`createChainServices()` walks the registry, calls `createService` and `defaultConnectors` per enabled chain, registers `ChainActions` for non-provider chains, and triggers `discoverConnectors` for chains that need async wallet detection (Stellar).
|
|
139
|
+
|
|
140
|
+
The registry is the **only** place that imports concrete chain classes. Hooks downstream depend on `IXService` / `IXConnector` interfaces — adding a new chain doesn't ripple through hook code.
|
|
141
|
+
|
|
142
|
+
See [`ADDING_A_NEW_CHAIN.md`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/ADDING_A_NEW_CHAIN.md) for the chain-onboarding workflow.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Provider-managed vs non-provider chains
|
|
147
|
+
|
|
148
|
+
| Property | Provider-managed (EVM/Solana/Sui) | Non-provider (Bitcoin/ICON/Injective/Stellar/NEAR/Stacks) |
|
|
149
|
+
|----------|-----------------------------------|------------------------------------------------------------|
|
|
150
|
+
| Native SDK | wagmi / @solana/wallet-adapter / @mysten/dapp-kit | sats-connect / icon-sdk-js / @injectivelabs/wallet-* / etc. |
|
|
151
|
+
| React provider needed | Yes | No |
|
|
152
|
+
| Connection-state writer | `<Hydrator>` component | Store side-effect inside `setXConnection()` |
|
|
153
|
+
| `ChainActions.connect/disconnect` | Triggers native SDK only — never writes state | `createDefaultActions` reads store, calls connector, writes state |
|
|
154
|
+
| Wallet provider construction | `<Hydrator>` builds & writes | Side-effect of `setXConnection` via `chainRegistry.<chain>.createWalletProvider` |
|
|
155
|
+
| Connector discovery | EIP-6963 / vendor protocol → wagmi/adapter discovers reactively | Static list from `defaultConnectors()` at init time, or async via `discoverConnectors` |
|
|
156
|
+
|
|
157
|
+
The split exists because some wallet ecosystems (EVM, Solana, Sui) have established React libraries with their own context providers — wrapping our store on top of theirs is cheaper than reimplementing connection management. The remaining six chains have lighter-weight SDKs (or no React layer at all), so we own the lifecycle directly.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Provider/Hydrator/Actions trio
|
|
162
|
+
|
|
163
|
+
Provider-managed chains use a 3-component pattern:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
<EvmProvider config={...}>
|
|
167
|
+
<WagmiProvider>
|
|
168
|
+
<EvmHydrator /> ← reactive writes to store
|
|
169
|
+
<EvmActions /> ← register ChainActions
|
|
170
|
+
{children}
|
|
171
|
+
</WagmiProvider>
|
|
172
|
+
</EvmProvider>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Component | Role |
|
|
176
|
+
|-----------|------|
|
|
177
|
+
| `<{Chain}Provider>` | Wraps the native SDK provider (wagmi / wallet-adapter / dapp-kit) |
|
|
178
|
+
| `<{Chain}Hydrator>` | **Sole writer of connection state + wallet providers**. Subscribes to native SDK hooks (`useAccount`, `useConnectors`, `useWalletClient`) and writes through `setXConnection` / `setWalletProvider`. Returns `null` |
|
|
179
|
+
| `<{Chain}Actions>` | Registers `ChainActions.connect/disconnect/signMessage` using a ref to native SDK functions. The registered closures only **trigger** SDK operations; they never write state directly |
|
|
180
|
+
|
|
181
|
+
### Single-writer invariant
|
|
182
|
+
|
|
183
|
+
Only the Hydrator writes connection state for provider-managed chains. The Actions component **does not** call `setXConnection` after a successful native connect — the Hydrator observes the wagmi/adapter status flip and handles it.
|
|
184
|
+
|
|
185
|
+
This is why `useXConnect.mutateAsync(connector)` resolves with `undefined` for EVM/Solana/Sui ([Connect Flow caveat](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONNECT_FLOW.md#provider-managed-chains-caveat)). The mutation kicks off `wagmi.connect()` and resolves; the Hydrator independently observes the status change and writes the account.
|
|
186
|
+
|
|
187
|
+
The split prevents two failure modes:
|
|
188
|
+
|
|
189
|
+
1. **Race conditions** if both Actions and Hydrator wrote on different events — the more recent write wins, which may be the wrong one (e.g. wallet returns address quickly but wagmi is still in `'connecting'`).
|
|
190
|
+
2. **Stale state** if Actions wrote on connect but didn't subscribe to subsequent disconnect events — wagmi `'disconnected'` would never reach the store.
|
|
191
|
+
|
|
192
|
+
Centralizing in the Hydrator means there's exactly one effect tree responsible for keeping the store in sync.
|
|
193
|
+
|
|
194
|
+
### Sui's special concern — `signPersonalMessage` ref
|
|
195
|
+
|
|
196
|
+
The Actions component holds a ref to the native SDK's signing function:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const signMessageRef = useRef(signPersonalMessage);
|
|
200
|
+
useEffect(() => { signMessageRef.current = signPersonalMessage; }, [signPersonalMessage]);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
registerChainActions('SUI', {
|
|
204
|
+
signMessage: async (message) => signMessageRef.current({ message: ... }),
|
|
205
|
+
...
|
|
206
|
+
});
|
|
207
|
+
}, []); // register once on mount
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The registration runs once on mount; calling `signMessageRef.current(...)` always invokes the latest function. This avoids re-registering on every render (which would invalidate downstream `useEffect` deps).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Persistence and hydration
|
|
215
|
+
|
|
216
|
+
Zustand's `persist` middleware writes `xConnections` to `localStorage` synchronously on every change and rehydrates on first mount. The lifecycle:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
mount
|
|
220
|
+
↓
|
|
221
|
+
useInitChainServices(config) called
|
|
222
|
+
↓
|
|
223
|
+
initChainServices(config) — synchronously builds services, connectors, ChainActions
|
|
224
|
+
↓
|
|
225
|
+
register .onFinishHydration(afterHydration) ← wait for persist
|
|
226
|
+
↓
|
|
227
|
+
... persist middleware finishes async hydration ...
|
|
228
|
+
↓
|
|
229
|
+
afterHydration runs:
|
|
230
|
+
├─ cleanupDisabledConnections() ← remove xConnections for chains not in enabledChains
|
|
231
|
+
├─ reconnectIcon() if config.ICON ← reconnect Hana wallet
|
|
232
|
+
├─ reconnectInjective() if config.INJECTIVE
|
|
233
|
+
└─ reconnectStellar() if config.STELLAR
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `cleanupDisabledConnections`
|
|
237
|
+
|
|
238
|
+
Persist restores `xConnections` from `localStorage` blind to the current `enabledChains`. If the user disabled a chain that was previously connected, the persisted entry would otherwise sit forever.
|
|
239
|
+
|
|
240
|
+
`cleanupDisabledConnections()` walks `xConnections` and deletes any entry whose chain isn't in the current `enabledChains` set. Runs once after persist hydration.
|
|
241
|
+
|
|
242
|
+
### Hydration flag for UI
|
|
243
|
+
|
|
244
|
+
`useConnectedChains` exposes `status: 'loading' | 'ready'` derived from `useXWalletStore.persist.hasHydrated()`. Use it to gate "Connect wallet" → "Connected" UIs and avoid first-paint flicker. See [`CHAIN_DETECTION.md`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CHAIN_DETECTION.md#hydration-status--gating-reload-flicker).
|
|
245
|
+
|
|
246
|
+
### Provider-managed reconnect
|
|
247
|
+
|
|
248
|
+
Wagmi/wallet-adapter/dapp-kit have their **own** persistence layers — they store the last connector id and auto-reconnect on `WagmiProvider` mount (gated by `reconnectOnMount`). The Hydrator observes the resulting `'connected'` status and writes to our store. We don't replicate the reconnect logic for provider-managed chains.
|
|
249
|
+
|
|
250
|
+
Non-provider chains (ICON, Injective, Stellar) have no auto-reconnect — `reconnectXxx()` helpers in `useInitChainServices` re-call `connect()` on the previously persisted connector after hydration.
|
|
251
|
+
|
|
252
|
+
**Bitcoin, NEAR, and Stacks** have **no reconnect helper at all**. Bitcoin restores its `walletProvider` from `window.*` + the persisted `XAccount` via `BitcoinXConnector.recreateWalletProvider` (no popup). NEAR and Stacks do not auto-reconnect on reload — the user must re-connect manually.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Store-first hooks
|
|
257
|
+
|
|
258
|
+
Every public hook in `src/hooks/` reads from `useXWalletStore` — **none** call native SDK hooks (`useAccount` from wagmi, `useWallet` from `@solana/wallet-adapter-react`) directly. This is by design:
|
|
259
|
+
|
|
260
|
+
- **Consistent shape** across chains. `useXAccount({ xChainType: 'EVM' })` and `useXAccount({ xChainType: 'SOLANA' })` return the same `XAccount` shape.
|
|
261
|
+
- **Decoupling from native SDKs** — swapping wagmi v3 for v4 is a Hydrator-internal change; consumer code doesn't break.
|
|
262
|
+
- **Single subscription model** — Zustand selector functions deduplicate re-renders (only consumers reading the changed slice re-render).
|
|
263
|
+
|
|
264
|
+
Native SDK hook usage is confined to:
|
|
265
|
+
1. Hydrator components (sole subscribers to wagmi/adapter/dapp-kit state).
|
|
266
|
+
2. Actions components (call wagmi functions inside registered closures).
|
|
267
|
+
3. `useEvmSwitchChain` (special case — wagmi's `useSwitchChain` and `useAccount` for the chain-mismatch check).
|
|
268
|
+
4. `useEthereumChainId` (Injective MetaMask special case).
|
|
269
|
+
|
|
270
|
+
If you find yourself reaching for `useAccount` in app code, prefer `useXAccount` — same data, chain-agnostic.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Bridge to wallet-sdk-core
|
|
275
|
+
|
|
276
|
+
`@sodax/wallet-sdk-react` produces typed `IXxxWalletProvider` instances from `@sodax/wallet-sdk-core` and stores them in `walletProviders`. Consumers retrieve them via `useWalletProvider()`.
|
|
277
|
+
|
|
278
|
+
- **Provider-managed chains**: Hydrator builds e.g. `new EvmWalletProvider({ walletClient, publicClient, defaults })` from wagmi's clients on every relevant change. Memoized to avoid spurious re-renders.
|
|
279
|
+
- **Non-provider chains**: `chainRegistry.<chain>.createWalletProvider(service, getStore)` is invoked as a side-effect of `setXConnection()` — when the user connects, the provider materializes immediately.
|
|
280
|
+
|
|
281
|
+
The provider classes live in `@sodax/wallet-sdk-core`, **not** here. This package's responsibility is wiring up React state + hydration; the providers themselves are framework-agnostic and can be constructed directly in Node.js scripts (see [`packages/sdk/docs/WALLET_PROVIDERS.md`](https://github.com/icon-project/sodax-sdks/blob/main/packages/sdk/docs/WALLET_PROVIDERS.md)).
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Related docs
|
|
286
|
+
|
|
287
|
+
- [Configure SodaxWalletProvider](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONFIGURE_PROVIDER.md) — config schema for the lifecycle described here
|
|
288
|
+
- [Connect Flow](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONNECT_FLOW.md) — consumer-facing API
|
|
289
|
+
- [Wallet Provider Bridge](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/WALLET_PROVIDER_BRIDGE.md) — `useWalletProvider` consumes the `walletProviders` slice
|
|
290
|
+
- [Adding a New Chain](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/ADDING_A_NEW_CHAIN.md) — chain-onboarding workflow
|
|
291
|
+
- [Sub-path Exports](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/SUB_PATH_EXPORTS.md) — barrel vs deep-import boundary
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Batch Operations
|
|
2
|
+
|
|
3
|
+
`useBatchConnect` and `useBatchDisconnect` orchestrate multi-chain wallet operations sequentially using **wallet brand identifiers** (e.g. `'hana'`, `'phantom'`) instead of individual connectors. Pass an identifier and the hooks discover every chain that wallet supports across the registry, then connect/disconnect them in order.
|
|
4
|
+
|
|
5
|
+
Sequential by design — many extensions share popup singletons, so parallel attempts would race a single popup. Errors are collected, not thrown — `run()` always resolves.
|
|
6
|
+
|
|
7
|
+
The pure helpers ([`resolveBatchTargets`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/hooks/useBatchConnect.ts), [`runBatchConnect`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/hooks/useBatchConnect.ts), [`resolveDisconnectTargets`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/hooks/useBatchDisconnect.ts), [`runBatchDisconnect`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/hooks/useBatchDisconnect.ts)) are exported for testability outside React.
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
1. [Identifier matching](#identifier-matching)
|
|
12
|
+
2. [`useBatchConnect`](#usebatchconnect)
|
|
13
|
+
3. [`useBatchDisconnect`](#usebatchdisconnect)
|
|
14
|
+
4. [Progress events](#progress-events)
|
|
15
|
+
5. [Status lifecycle and concurrency](#status-lifecycle-and-concurrency)
|
|
16
|
+
6. [Wallet install detection — `useIsWalletInstalled`](#wallet-install-detection--useiswalletinstalled)
|
|
17
|
+
7. [When to use the lower-level hooks instead](#when-to-use-the-lower-level-hooks-instead)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Identifier matching
|
|
22
|
+
|
|
23
|
+
Both batch hooks (and `useIsWalletInstalled`) take a `connectors: readonly string[]` field. Each entry is a **wallet brand identifier** matched **case-insensitive substring** against `connector.id` and `connector.name`:
|
|
24
|
+
|
|
25
|
+
| Identifier | Matches connectors |
|
|
26
|
+
|------------|--------------------|
|
|
27
|
+
| `'hana'` | Hana on EVM (`io.havah.hana`), Hana on ICON (`hana`), Hana on Sui, Hana on Stellar… |
|
|
28
|
+
| `'phantom'` | Phantom on Solana (`phantom`), Phantom on EVM (`app.phantom`) |
|
|
29
|
+
| `'metamask'` | MetaMask on EVM (`io.metamask`), Injective MetaMask connector |
|
|
30
|
+
| `'xverse'` | Xverse on Bitcoin (`xverse`) |
|
|
31
|
+
|
|
32
|
+
Earlier identifiers in the array win **per chain**. Use this for fallback chains:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Prefer Hana, fall back to Phantom on chains where Hana isn't available
|
|
36
|
+
const { run } = useBatchConnect({ connectors: ['hana', 'phantom'] });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The matcher is implemented by [`matchesConnectorIdentifier`](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/src/utils/matchConnectorIdentifier.ts) — pure, case-insensitive `includes`.
|
|
40
|
+
|
|
41
|
+
To target a specific connector (not a brand), bypass this API and use `useXConnectors({ xChainType }).find(c => c.id === '...')` + `useXConnect` directly.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## `useBatchConnect`
|
|
46
|
+
|
|
47
|
+
Connect every chain where one of the supplied identifiers matches an installed connector. Sequential, never throws, dedupes concurrent runs.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { useBatchConnect } from '@sodax/wallet-sdk-react';
|
|
51
|
+
|
|
52
|
+
function ConnectAllHana() {
|
|
53
|
+
const { run, status, result, reset } = useBatchConnect({
|
|
54
|
+
connectors: ['hana'],
|
|
55
|
+
skipConnected: true, // skip chains already connected at run() time
|
|
56
|
+
onProgress: event => {
|
|
57
|
+
console.log(`[batch] ${event.chainType}: ${event.outcome}`);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
<button onClick={run} disabled={status === 'running'}>
|
|
64
|
+
{status === 'running' ? 'Connecting all Hana chains…' : 'Connect with Hana'}
|
|
65
|
+
</button>
|
|
66
|
+
{status === 'done' && result && (
|
|
67
|
+
<div>
|
|
68
|
+
<p>{result.successful.length} connected</p>
|
|
69
|
+
<p>{result.failed.length} failed</p>
|
|
70
|
+
<p>{result.skipped.length} skipped</p>
|
|
71
|
+
<button onClick={reset}>Reset</button>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Options
|
|
80
|
+
|
|
81
|
+
| Field | Type | Effect |
|
|
82
|
+
|-------|------|--------|
|
|
83
|
+
| `connectors` | `readonly string[]` | Identifiers (required, non-empty) |
|
|
84
|
+
| `skipConnected` | `boolean` | Skip chains already holding an account at `run()` time. Default `false` |
|
|
85
|
+
| `onProgress` | `(event) => void` | Per-target progress event — see [Progress events](#progress-events) |
|
|
86
|
+
|
|
87
|
+
### Result shape
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
type BatchConnectResult = {
|
|
91
|
+
successful: ChainType[];
|
|
92
|
+
failed: Array<{ chainType: ChainType; error: Error }>;
|
|
93
|
+
skipped: ChainType[];
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`run()` returns `Promise<BatchConnectResult>` — never rejects. The same result is also exposed on the hook's `result` field after the batch settles.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## `useBatchDisconnect`
|
|
102
|
+
|
|
103
|
+
Mirror of `useBatchConnect` for disconnect:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { useBatchDisconnect } from '@sodax/wallet-sdk-react';
|
|
107
|
+
|
|
108
|
+
function DisconnectAll() {
|
|
109
|
+
const { run, status, result } = useBatchDisconnect();
|
|
110
|
+
// No `connectors` → disconnect every currently-connected chain regardless of wallet
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<button onClick={run} disabled={status === 'running'}>
|
|
114
|
+
{status === 'running' ? 'Disconnecting…' : 'Disconnect all chains'}
|
|
115
|
+
</button>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function DisconnectHanaOnly() {
|
|
120
|
+
const { run } = useBatchDisconnect({ connectors: ['hana'] });
|
|
121
|
+
// Only disconnect chains whose CURRENTLY ACTIVE connector matches 'hana'
|
|
122
|
+
return <button onClick={run}>Disconnect Hana</button>;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Options
|
|
127
|
+
|
|
128
|
+
| Field | Type | Effect |
|
|
129
|
+
|-------|------|--------|
|
|
130
|
+
| `connectors` | `readonly string[]` (optional) | Filter chains whose **active** connector matches one of these identifiers. Omit to disconnect all currently-connected chains |
|
|
131
|
+
| `onProgress` | `(event) => void` | Per-target progress event |
|
|
132
|
+
|
|
133
|
+
### Scope difference vs `useBatchConnect`
|
|
134
|
+
|
|
135
|
+
`useBatchConnect.connectors` is **mandatory** — it picks **which connector** to use on each chain.
|
|
136
|
+
|
|
137
|
+
`useBatchDisconnect.connectors` is **optional** — it filters **which chains** to disconnect by checking the **currently active** connector against the identifiers. Chains where the active connector doesn't match are left untouched.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Disconnect every chain (regardless of which wallet is active)
|
|
141
|
+
useBatchDisconnect();
|
|
142
|
+
|
|
143
|
+
// Disconnect only chains where Hana is active
|
|
144
|
+
useBatchDisconnect({ connectors: ['hana'] });
|
|
145
|
+
|
|
146
|
+
// Disconnect chains where either Hana or Xverse is active
|
|
147
|
+
useBatchDisconnect({ connectors: ['hana', 'xverse'] });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Result shape
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
type BatchDisconnectResult = {
|
|
154
|
+
successful: ChainType[];
|
|
155
|
+
failed: Array<{ chainType: ChainType; error: Error }>;
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
No `skipped` field — disconnect doesn't have a skip semantic.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Progress events
|
|
164
|
+
|
|
165
|
+
Both hooks accept `onProgress` for live UI updates without waiting for `run()` to resolve:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const { run } = useBatchConnect({
|
|
169
|
+
connectors: ['hana'],
|
|
170
|
+
onProgress: event => {
|
|
171
|
+
switch (event.outcome) {
|
|
172
|
+
case 'success':
|
|
173
|
+
toast(`✓ Connected ${event.chainType}`);
|
|
174
|
+
break;
|
|
175
|
+
case 'failure':
|
|
176
|
+
toast.error(`✗ ${event.chainType}: ${event.error.message}`);
|
|
177
|
+
break;
|
|
178
|
+
case 'skipped':
|
|
179
|
+
toast(`⏭ ${event.chainType} already connected`);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Hook | Event variants |
|
|
187
|
+
|------|----------------|
|
|
188
|
+
| `useBatchConnect` | `{ outcome: 'success' \| 'failure' \| 'skipped', chainType, error? }` |
|
|
189
|
+
| `useBatchDisconnect` | `{ outcome: 'success' \| 'failure', chainType, error? }` |
|
|
190
|
+
|
|
191
|
+
**`onProgress` is isolated from the batch result** — a throwing callback is caught and logged via `console.error`. The batch continues normally. This is intentional: a render-time crash in toast code shouldn't abort a multi-chain disconnect halfway through.
|
|
192
|
+
|
|
193
|
+
`onProgress` is read from a ref each time, so passing an inline arrow function is safe — `run` won't be rebuilt every render.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Status lifecycle and concurrency
|
|
198
|
+
|
|
199
|
+
| `status` | Meaning |
|
|
200
|
+
|----------|---------|
|
|
201
|
+
| `'idle'` | Initial / after `reset()` |
|
|
202
|
+
| `'running'` | A `run()` is in flight |
|
|
203
|
+
| `'done'` | The last `run()` settled — `result` is populated |
|
|
204
|
+
|
|
205
|
+
**Concurrent `run()` calls are deduped** — the second call returns the existing in-flight promise. This protects extensions that share popup singletons:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const { run } = useBatchConnect({ connectors: ['hana'] });
|
|
209
|
+
// Two calls — only one batch executes; both promises resolve to the same result.
|
|
210
|
+
const [a, b] = await Promise.all([run(), run()]);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**`reset()` does NOT abort an in-flight batch** — there is no cancellation signal. It only clears the observable `status` / `result`. When the in-flight batch eventually resolves, `status` flips back to `'done'` and `result` re-populates. Typical usage: call `reset()` only after `status === 'done'`.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Wallet install detection — `useIsWalletInstalled`
|
|
218
|
+
|
|
219
|
+
Companion read hook that uses the same identifier matching to detect whether a wallet brand is installed for any (or specific) chain.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { useIsWalletInstalled } from '@sodax/wallet-sdk-react';
|
|
223
|
+
|
|
224
|
+
// True if any Hana variant is installed across any enabled chain
|
|
225
|
+
const isHanaInstalled = useIsWalletInstalled({ connectors: ['hana'] });
|
|
226
|
+
|
|
227
|
+
// True if any wallet is installed for the given chain
|
|
228
|
+
const hasBitcoinWallet = useIsWalletInstalled({ chainType: 'BITCOIN' });
|
|
229
|
+
|
|
230
|
+
// AND filter — Hana specifically on EVM
|
|
231
|
+
const hanaOnEvm = useIsWalletInstalled({ connectors: ['hana'], chainType: 'EVM' });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The options union enforces at the type level that **at least one** of `connectors` / `chainType` is present — calling with `{}` returns `false` plus a one-time console warning. An empty `connectors: []` is treated as explicit "match nothing".
|
|
235
|
+
|
|
236
|
+
Use this hook to gate an `useBatchConnect` button:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
const isInstalled = useIsWalletInstalled({ connectors: ['hana'] });
|
|
240
|
+
const { run } = useBatchConnect({ connectors: ['hana'] });
|
|
241
|
+
|
|
242
|
+
return isInstalled ? (
|
|
243
|
+
<button onClick={run}>Connect Hana</button>
|
|
244
|
+
) : (
|
|
245
|
+
<a href="https://hana-wallet.com">Install Hana</a>
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## When to use the lower-level hooks instead
|
|
252
|
+
|
|
253
|
+
Reach for `useXConnect` / `useXDisconnect` directly when:
|
|
254
|
+
|
|
255
|
+
- **You need a specific connector**, not a brand. The identifier matcher is intentionally fuzzy; `useXConnectors` lets you pick by exact `id`.
|
|
256
|
+
- **Order is user-driven**, not registry-driven. The batch hooks iterate in the chain registry's order; if the user picked an explicit sequence in your UI, drive `useXConnect` from that sequence yourself.
|
|
257
|
+
- **You need cancellation**. Batch hooks have no cancel signal — drive `useXConnect` directly inside an `AbortController`-aware controller if you need to bail out.
|
|
258
|
+
- **You only have one chain to operate on**. The batch concurrency guard adds no value for single-chain flows.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Related docs
|
|
263
|
+
|
|
264
|
+
- [Connect Flow](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONNECT_FLOW.md) — `useXConnect` / `useXDisconnect` underlying primitives
|
|
265
|
+
- [Wallet Modal](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/WALLET_MODAL.md) — multi-chain modal that internally drives `useBatchConnect` for "connect all" flows
|
|
266
|
+
- [Chain Detection](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CHAIN_DETECTION.md) — `useChainGroups`, `useConnectedChains` for surfacing batch results
|
|
267
|
+
- [Connectors](https://github.com/icon-project/sodax-sdks/blob/main/packages/wallet-sdk-react/docs/CONNECTORS.md) — `IXConnector` shape, exact `id` matching for fine-grained control
|