@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.
- package/README.md +12 -5
- package/dist/{chunk-BKJB527E.mjs → chunk-3QETHO6P.mjs} +1 -3
- package/dist/{chunk-PJLEJVAU.mjs → chunk-42LTUHMZ.mjs} +1 -3
- package/dist/{chunk-NY7U7OJW.mjs → chunk-7V7O3Q7Y.mjs} +0 -2
- package/dist/{chunk-BXJLBR4G.mjs → chunk-C6M34IVL.mjs} +2 -4
- package/dist/{chunk-XZ7CHO2S.mjs → chunk-FSOGMSJH.mjs} +2 -4
- package/dist/{chunk-X2MHIWXO.mjs → chunk-IFXZQW4C.mjs} +0 -2
- package/dist/{chunk-7ULB6DW4.mjs → chunk-JQ4H4GJ5.mjs} +3 -5
- package/dist/{chunk-N5A2TMF6.mjs → chunk-LKSSME2J.mjs} +2 -4
- package/dist/{chunk-PLCA4ZDJ.mjs → chunk-LUKR7YKV.mjs} +54 -30
- package/dist/{chunk-MXZVF5HR.mjs → chunk-NAKCAL2M.mjs} +0 -2
- package/dist/chunk-QMXBY3UI.mjs +1 -0
- package/dist/{chunk-MAQ47Q52.mjs → chunk-TACW7Z4D.mjs} +0 -2
- package/dist/{chunk-2BOUGCJ7.mjs → chunk-WPZOLGVB.mjs} +4 -6
- package/dist/{chunk-66BAUK56.mjs → chunk-X7BHR7WS.mjs} +2 -4
- package/dist/{chunk-E5IAZ7E6.mjs → chunk-Z5GXDHGL.mjs} +9 -5
- package/dist/{config-OlnzyEUE.d.ts → config-GVKK8IfY.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +20 -31
- package/dist/xchains/bitcoin/index.mjs +14 -16
- package/dist/xchains/evm/index.d.ts +1 -1
- package/dist/xchains/evm/index.mjs +3 -5
- package/dist/xchains/icon/index.mjs +5 -7
- package/dist/xchains/injective/index.mjs +3 -5
- package/dist/xchains/near/index.mjs +4 -6
- package/dist/xchains/solana/index.mjs +5 -7
- package/dist/xchains/stacks/index.mjs +3 -5
- package/dist/xchains/stellar/index.mjs +4 -6
- package/dist/xchains/sui/index.mjs +5 -7
- package/docs/ADDING_A_NEW_CHAIN.md +1 -1
- package/docs/SUB_PATH_EXPORTS.md +14 -42
- package/package.json +32 -23
- package/ai-exported/AGENTS.md +0 -122
- package/ai-exported/integration/README.md +0 -102
- package/ai-exported/integration/ai-rules.md +0 -136
- package/ai-exported/integration/architecture.md +0 -181
- package/ai-exported/integration/examples/01-minimal-evm.tsx +0 -75
- package/ai-exported/integration/examples/02-multi-chain-modal.tsx +0 -169
- package/ai-exported/integration/examples/03-nextjs-app-router.tsx +0 -99
- package/ai-exported/integration/examples/04-walletconnect-setup.tsx +0 -89
- package/ai-exported/integration/examples/README.md +0 -29
- package/ai-exported/integration/recipes/batch-operations.md +0 -223
- package/ai-exported/integration/recipes/bridge-to-sdk.md +0 -164
- package/ai-exported/integration/recipes/chain-detection.md +0 -254
- package/ai-exported/integration/recipes/connect-button.md +0 -156
- package/ai-exported/integration/recipes/multi-chain-modal.md +0 -199
- package/ai-exported/integration/recipes/setup.md +0 -158
- package/ai-exported/integration/recipes/sign-message.md +0 -137
- package/ai-exported/integration/recipes/sub-path-imports.md +0 -95
- package/ai-exported/integration/recipes/switch-chain.md +0 -141
- package/ai-exported/integration/recipes/walletconnect-setup.md +0 -139
- package/ai-exported/integration/reference/api-surface.md +0 -175
- package/ai-exported/integration/reference/chain-support.md +0 -78
- package/ai-exported/integration/reference/connectors.md +0 -74
- package/ai-exported/integration/reference/hooks.md +0 -204
- package/ai-exported/integration/reference/wallet-brands.md +0 -106
- package/ai-exported/migration/README.md +0 -49
- package/ai-exported/migration/ai-rules.md +0 -144
- package/ai-exported/migration/breaking-changes.md +0 -305
- package/ai-exported/migration/checklist.md +0 -159
- package/ai-exported/migration/recipes/connect-button.md +0 -166
- package/ai-exported/migration/recipes/multi-chain-modal.md +0 -244
- package/ai-exported/migration/recipes/ssr-setup.md +0 -162
- package/ai-exported/migration/recipes/walletconnect-migration.md +0 -168
- package/ai-exported/migration/reference/components.md +0 -73
- package/ai-exported/migration/reference/config.md +0 -307
- package/ai-exported/migration/reference/hooks.md +0 -278
- package/ai-exported/migration/reference/imports.md +0 -157
- package/dist/chunk-2BOUGCJ7.mjs.map +0 -1
- package/dist/chunk-66BAUK56.mjs.map +0 -1
- package/dist/chunk-7ULB6DW4.mjs.map +0 -1
- package/dist/chunk-BKJB527E.mjs.map +0 -1
- package/dist/chunk-BXJLBR4G.mjs.map +0 -1
- package/dist/chunk-E5IAZ7E6.mjs.map +0 -1
- package/dist/chunk-MAQ47Q52.mjs.map +0 -1
- package/dist/chunk-MXZVF5HR.mjs.map +0 -1
- package/dist/chunk-N5A2TMF6.mjs.map +0 -1
- package/dist/chunk-NY7U7OJW.mjs.map +0 -1
- package/dist/chunk-PJLEJVAU.mjs.map +0 -1
- package/dist/chunk-PLCA4ZDJ.mjs.map +0 -1
- package/dist/chunk-TZMKDXFA.mjs +0 -3
- package/dist/chunk-TZMKDXFA.mjs.map +0 -1
- package/dist/chunk-X2MHIWXO.mjs.map +0 -1
- package/dist/chunk-XZ7CHO2S.mjs.map +0 -1
- package/dist/index.cjs +0 -3337
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/xchains/bitcoin/index.cjs +0 -1927
- package/dist/xchains/bitcoin/index.cjs.map +0 -1
- package/dist/xchains/bitcoin/index.mjs.map +0 -1
- package/dist/xchains/evm/index.cjs +0 -316
- package/dist/xchains/evm/index.cjs.map +0 -1
- package/dist/xchains/evm/index.mjs.map +0 -1
- package/dist/xchains/icon/index.cjs +0 -311
- package/dist/xchains/icon/index.cjs.map +0 -1
- package/dist/xchains/icon/index.mjs.map +0 -1
- package/dist/xchains/injective/index.cjs +0 -223
- package/dist/xchains/injective/index.cjs.map +0 -1
- package/dist/xchains/injective/index.mjs.map +0 -1
- package/dist/xchains/near/index.cjs +0 -190
- package/dist/xchains/near/index.cjs.map +0 -1
- package/dist/xchains/near/index.mjs.map +0 -1
- package/dist/xchains/solana/index.cjs +0 -186
- package/dist/xchains/solana/index.cjs.map +0 -1
- package/dist/xchains/solana/index.mjs.map +0 -1
- package/dist/xchains/stacks/index.cjs +0 -240
- package/dist/xchains/stacks/index.cjs.map +0 -1
- package/dist/xchains/stacks/index.mjs.map +0 -1
- package/dist/xchains/stellar/index.cjs +0 -322
- package/dist/xchains/stellar/index.cjs.map +0 -1
- package/dist/xchains/stellar/index.mjs.map +0 -1
- package/dist/xchains/sui/index.cjs +0 -248
- package/dist/xchains/sui/index.cjs.map +0 -1
- package/dist/xchains/sui/index.mjs.map +0 -1
- package/skills/SKILLS.md +0 -84
- package/skills/bridge-to-sdk.md +0 -148
- package/skills/connect-button.md +0 -116
- package/skills/evm-only-walletconnect.md +0 -111
- package/skills/multi-chain-modal.md +0 -178
- package/skills/setup.md +0 -107
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
# Recipe: Connect Button
|
|
2
|
-
|
|
3
|
-
Single-chain connect/disconnect button — pick a connector, connect, read the account, disconnect. Self-contained.
|
|
4
|
-
|
|
5
|
-
**Depends on:** [`setup.md`](./setup.md)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Hooks used
|
|
10
|
-
|
|
11
|
-
| Hook | Purpose |
|
|
12
|
-
|------|---------|
|
|
13
|
-
| `useXConnectors({ xChainType })` | List available connectors for the chain family |
|
|
14
|
-
| `useXConnect()` | React Query mutation — `mutate(connector)` |
|
|
15
|
-
| `useXAccount({ xChainType })` | Read connected account (always returns object — `address` is `undefined` when disconnected) |
|
|
16
|
-
| `useXDisconnect()` | Returns `(xChainType) => Promise<void>` |
|
|
17
|
-
| `sortConnectors(list, { preferred })` | Optional — rank installed/preferred wallets first |
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Connect button
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
'use client';
|
|
25
|
-
|
|
26
|
-
import {
|
|
27
|
-
useXConnectors,
|
|
28
|
-
useXConnect,
|
|
29
|
-
useXAccount,
|
|
30
|
-
useXDisconnect,
|
|
31
|
-
sortConnectors,
|
|
32
|
-
type IXConnector,
|
|
33
|
-
} from '@sodax/wallet-sdk-react';
|
|
34
|
-
|
|
35
|
-
const PREFERRED = ['hana', 'metamask'] as const;
|
|
36
|
-
|
|
37
|
-
export function EvmConnectButton() {
|
|
38
|
-
const raw = useXConnectors({ xChainType: 'EVM' });
|
|
39
|
-
const connectors = sortConnectors(raw, { preferred: PREFERRED });
|
|
40
|
-
const { mutateAsync: connect, isPending, error } = useXConnect();
|
|
41
|
-
const account = useXAccount({ xChainType: 'EVM' });
|
|
42
|
-
const disconnect = useXDisconnect();
|
|
43
|
-
|
|
44
|
-
if (account.address) {
|
|
45
|
-
return (
|
|
46
|
-
<div>
|
|
47
|
-
<code>{account.address}</code>
|
|
48
|
-
<button onClick={() => disconnect({ xChainType: 'EVM' })}>Disconnect</button>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<div>
|
|
55
|
-
{connectors.map((connector) => (
|
|
56
|
-
<button
|
|
57
|
-
key={connector.id}
|
|
58
|
-
onClick={() => connect(connector).catch(() => {})}
|
|
59
|
-
disabled={isPending}
|
|
60
|
-
>
|
|
61
|
-
{connector.icon && <img src={connector.icon} alt="" width={20} height={20} />}
|
|
62
|
-
{connector.name}
|
|
63
|
-
{!connector.isInstalled && ' (not installed)'}
|
|
64
|
-
</button>
|
|
65
|
-
))}
|
|
66
|
-
{error && <p style={{ color: 'red' }}>{error.message}</p>}
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Install CTA for missing wallets
|
|
75
|
-
|
|
76
|
-
```tsx
|
|
77
|
-
{connectors.map((connector) =>
|
|
78
|
-
connector.isInstalled ? (
|
|
79
|
-
<button key={connector.id} onClick={() => connect(connector)}>
|
|
80
|
-
{connector.name}
|
|
81
|
-
</button>
|
|
82
|
-
) : (
|
|
83
|
-
<a key={connector.id} href={connector.installUrl} target="_blank" rel="noreferrer">
|
|
84
|
-
Install {connector.name}
|
|
85
|
-
</a>
|
|
86
|
-
),
|
|
87
|
-
)}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
`isInstalled` reads `window.*` at render time (no extra subscription). For batch install detection across wallet brands, use `useIsWalletInstalled` — see [`batch-operations.md`](./batch-operations.md).
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Caveat — provider-managed chains resolve with `undefined`
|
|
95
|
-
|
|
96
|
-
For EVM, Solana, and Sui, `connect(connector)` resolves with `undefined` because connection state is set asynchronously after the native SDK adapter reports `connected`. Always read the account via `useXAccount`, not the mutation's return value:
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
const { mutateAsync: connect } = useXConnect();
|
|
100
|
-
const account = useXAccount({ xChainType: 'EVM' });
|
|
101
|
-
|
|
102
|
-
await connect(connector); // resolves with undefined for EVM
|
|
103
|
-
// account.address is populated on the next render
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Non-provider chains (Bitcoin, ICON, Injective, Stellar, NEAR, Stacks) return the resolved `XAccount` from `connect()` — but reading via `useXAccount` works for both, so default to it.
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Multiple chains, one button
|
|
111
|
-
|
|
112
|
-
For a "connect EVM + Solana + Bitcoin in one click" pattern, use [`batch-operations.md`](./batch-operations.md):
|
|
113
|
-
|
|
114
|
-
```tsx
|
|
115
|
-
import { useBatchConnect } from '@sodax/wallet-sdk-react';
|
|
116
|
-
|
|
117
|
-
const { run, status } = useBatchConnect({ connectors: ['hana'] });
|
|
118
|
-
// Connects every chain Hana supports — sequential, errors collected.
|
|
119
|
-
<button onClick={run} disabled={status === 'running'}>
|
|
120
|
-
Connect Hana on all chains
|
|
121
|
-
</button>;
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Variations
|
|
127
|
-
|
|
128
|
-
### Per-chain-id button (e.g. one button per EVM network)
|
|
129
|
-
|
|
130
|
-
⚠️ Not recommended for EVM — wagmi treats EVM as a **single connection across every configured network**. A per-Ethereum button rarely matches user expectations. If you really need per-chain-id resolution:
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
import { ChainKeys } from '@sodax/types';
|
|
134
|
-
|
|
135
|
-
const account = useXAccount({ xChainId: ChainKeys.ETHEREUM_MAINNET });
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Sui / Solana / etc.
|
|
139
|
-
|
|
140
|
-
Replace `'EVM'` with the chain type the button targets:
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
const connectors = useXConnectors({ xChainType: 'SUI' });
|
|
144
|
-
const { address } = useXAccount({ xChainType: 'SUI' });
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
## Verification
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
# 1. Type check
|
|
153
|
-
pnpm checkTs
|
|
154
|
-
|
|
155
|
-
# 2. Manual — load page, click connect, confirm address renders, reload page, confirm connection survives
|
|
156
|
-
```
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# Recipe: Multi-Chain Wallet Modal
|
|
2
|
-
|
|
3
|
-
Headless wallet-connect modal that walks the user through `chainSelect → walletSelect → connecting → success | error`. Pair with `useChainGroups` for the chain picker and `useXConnectors` for the wallet picker. Render-agnostic — works with any dialog/drawer/inline UI.
|
|
4
|
-
|
|
5
|
-
**Depends on:** [`setup.md`](./setup.md)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Hooks used
|
|
10
|
-
|
|
11
|
-
| Hook | Purpose |
|
|
12
|
-
|------|---------|
|
|
13
|
-
| `useWalletModal({ onConnected? })` | State machine + actions (`open`, `close`, `back`, `selectChain`, `selectWallet`, `retry`) |
|
|
14
|
-
| `useChainGroups({ order? })` | One row per enabled chain family (EVM collapses to one row) |
|
|
15
|
-
| `useXConnectors({ xChainType })` | Wallet list for the chosen chain family |
|
|
16
|
-
| `useXAccount({ xChainType })` | Read the connected account when needed |
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Render switch
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
'use client';
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
useWalletModal,
|
|
27
|
-
useChainGroups,
|
|
28
|
-
useXConnectors,
|
|
29
|
-
type IXConnector,
|
|
30
|
-
} from '@sodax/wallet-sdk-react';
|
|
31
|
-
|
|
32
|
-
export function WalletModalRoot() {
|
|
33
|
-
const modal = useWalletModal({
|
|
34
|
-
onConnected: async (chainType, account) => {
|
|
35
|
-
console.log('connected', chainType, account.address);
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
switch (modal.state.kind) {
|
|
40
|
-
case 'closed':
|
|
41
|
-
return <button onClick={modal.open}>Connect Wallet</button>;
|
|
42
|
-
|
|
43
|
-
case 'chainSelect':
|
|
44
|
-
return <ChainPicker onPick={modal.selectChain} onClose={modal.close} />;
|
|
45
|
-
|
|
46
|
-
case 'walletSelect':
|
|
47
|
-
return (
|
|
48
|
-
<WalletPicker
|
|
49
|
-
chainType={modal.state.chainType}
|
|
50
|
-
onPick={modal.selectWallet}
|
|
51
|
-
onBack={modal.back}
|
|
52
|
-
onClose={modal.close}
|
|
53
|
-
/>
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
case 'connecting':
|
|
57
|
-
// Hide modal while wagmi's QR modal is up for WalletConnect
|
|
58
|
-
if (modal.state.connector.id === 'walletConnect') return null;
|
|
59
|
-
return (
|
|
60
|
-
<Dialog onClose={modal.close}>
|
|
61
|
-
<p>Approve in {modal.state.connector.name}…</p>
|
|
62
|
-
<button onClick={modal.back}>Cancel</button>
|
|
63
|
-
</Dialog>
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
case 'success':
|
|
67
|
-
// onConnected fired; close after a beat
|
|
68
|
-
setTimeout(modal.close, 0);
|
|
69
|
-
return null;
|
|
70
|
-
|
|
71
|
-
case 'error':
|
|
72
|
-
return (
|
|
73
|
-
<Dialog onClose={modal.close}>
|
|
74
|
-
<p>{modal.state.error.message}</p>
|
|
75
|
-
{!modal.state.connector.isInstalled && modal.state.connector.installUrl && (
|
|
76
|
-
<a href={modal.state.connector.installUrl}>Install {modal.state.connector.name}</a>
|
|
77
|
-
)}
|
|
78
|
-
<button onClick={modal.retry}>Retry</button>
|
|
79
|
-
<button onClick={modal.back}>Pick another wallet</button>
|
|
80
|
-
</Dialog>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Render `<WalletModalRoot />` once at the app root — any other component can dispatch `useWalletModal().open()` to show it.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Chain picker
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
import { useChainGroups } from '@sodax/wallet-sdk-react';
|
|
94
|
-
import type { ChainType } from '@sodax/types';
|
|
95
|
-
|
|
96
|
-
function ChainPicker({ onPick, onClose }: { onPick: (c: ChainType) => void; onClose: () => void }) {
|
|
97
|
-
const groups = useChainGroups({ order: ['EVM', 'SOLANA', 'BITCOIN', 'ICON'] });
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<Dialog onClose={onClose}>
|
|
101
|
-
<h2>Select a chain</h2>
|
|
102
|
-
{groups.map((group) => (
|
|
103
|
-
<button key={group.chainType} onClick={() => onPick(group.chainType)}>
|
|
104
|
-
{group.iconUrl && <img src={group.iconUrl} alt="" width={24} height={24} />}
|
|
105
|
-
<span>{group.displayName}</span>
|
|
106
|
-
{group.isConnected && <span>Connected</span>}
|
|
107
|
-
</button>
|
|
108
|
-
))}
|
|
109
|
-
</Dialog>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
EVM collapses to a single group covering every configured EVM network — this matches reality (wagmi maintains one connection across all EVM chains).
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Wallet picker
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import { useXConnectors, sortConnectors, type IXConnector } from '@sodax/wallet-sdk-react';
|
|
122
|
-
import type { ChainType } from '@sodax/types';
|
|
123
|
-
|
|
124
|
-
function WalletPicker({
|
|
125
|
-
chainType,
|
|
126
|
-
onPick,
|
|
127
|
-
onBack,
|
|
128
|
-
onClose,
|
|
129
|
-
}: {
|
|
130
|
-
chainType: ChainType;
|
|
131
|
-
onPick: (c: IXConnector) => void;
|
|
132
|
-
onBack: () => void;
|
|
133
|
-
onClose: () => void;
|
|
134
|
-
}) {
|
|
135
|
-
const connectors = sortConnectors(useXConnectors({ xChainType: chainType }), {
|
|
136
|
-
preferred: ['hana', 'metamask', 'phantom'],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<Dialog onClose={onClose}>
|
|
141
|
-
<button onClick={onBack}>← Back</button>
|
|
142
|
-
<h2>Select a wallet</h2>
|
|
143
|
-
{connectors.map((connector) => (
|
|
144
|
-
<button key={connector.id} onClick={() => onPick(connector)}>
|
|
145
|
-
{connector.icon && <img src={connector.icon} alt="" />}
|
|
146
|
-
{connector.name}
|
|
147
|
-
{!connector.isInstalled && ' (not installed)'}
|
|
148
|
-
</button>
|
|
149
|
-
))}
|
|
150
|
-
</Dialog>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## Concurrency rules
|
|
158
|
-
|
|
159
|
-
- **Same connector double-click** → returns the same in-flight promise (no double popup).
|
|
160
|
-
- **Different connector mid-attempt** → starts a new attempt; previous one's late resolution is dropped.
|
|
161
|
-
- **`back()` / `close()` mid-attempt** → cancellation guard inside the modal layer; the wallet may still approve in the background but `success`/`error` won't fire. To roll back, call `useXDisconnect()(xChainType)` from the same handler.
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## `onConnected` is non-fatal
|
|
166
|
-
|
|
167
|
-
Throwing inside `onConnected` is logged but **does not** downgrade `success` → `error`. The connection is already persisted; the user is genuinely connected.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Non-modal alternative — `useConnectionFlow`
|
|
172
|
-
|
|
173
|
-
For a single-button flow without the multi-step modal:
|
|
174
|
-
|
|
175
|
-
```tsx
|
|
176
|
-
import { useConnectionFlow } from '@sodax/wallet-sdk-react';
|
|
177
|
-
|
|
178
|
-
const { status, error, connect, retry, activeConnector } = useConnectionFlow();
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<button onClick={() => connect(connector)} disabled={status === 'connecting'}>
|
|
182
|
-
{status === 'connecting' ? 'Waiting…' : 'Connect'}
|
|
183
|
-
</button>
|
|
184
|
-
);
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
`connect()` and `retry()` never throw — errors flow into `error` state.
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## Verification
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
# 1. Type check
|
|
195
|
-
pnpm checkTs
|
|
196
|
-
|
|
197
|
-
# 2. Manual — open modal, walk through chain → wallet → connecting → success → close
|
|
198
|
-
# 3. Manual — pick non-installed wallet, confirm install link appears in error state
|
|
199
|
-
```
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# Recipe: Setup
|
|
2
|
-
|
|
3
|
-
Install and wire `@sodax/wallet-sdk-react` into a React project. **Always do this first** — every other recipe assumes `SodaxWalletProvider` is mounted.
|
|
4
|
-
|
|
5
|
-
**Depends on:** None
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pnpm add @sodax/wallet-sdk-react @tanstack/react-query
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Peer dependencies:
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"react": ">=19",
|
|
20
|
-
"@tanstack/react-query": "5.x"
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Wire `SodaxWalletProvider`
|
|
27
|
-
|
|
28
|
-
Top-level keys on `SodaxWalletConfig` are chain-type slots — **omit a slot to skip mounting that adapter**, pass `{}` to mount with SDK defaults. `<QueryClientProvider>` must wrap `<SodaxWalletProvider>`.
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
// providers.tsx
|
|
32
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
33
|
-
import { SodaxWalletProvider, type SodaxWalletConfig } from '@sodax/wallet-sdk-react';
|
|
34
|
-
import { ChainKeys } from '@sodax/types';
|
|
35
|
-
|
|
36
|
-
const queryClient = new QueryClient();
|
|
37
|
-
|
|
38
|
-
const walletConfig: SodaxWalletConfig = {
|
|
39
|
-
EVM: {
|
|
40
|
-
ssr: true, // Next.js — keep true for SSR-safe hydration. Drop for Vite/CRA.
|
|
41
|
-
chains: {
|
|
42
|
-
[ChainKeys.SONIC_MAINNET]: { rpcUrl: 'https://rpc.soniclabs.com' },
|
|
43
|
-
[ChainKeys.ETHEREUM_MAINNET]: { rpcUrl: 'https://ethereum-rpc.publicnode.com' },
|
|
44
|
-
[ChainKeys.BSC_MAINNET]: { rpcUrl: 'https://bsc-dataseed.binance.org' },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
ICON: {
|
|
48
|
-
chains: { [ChainKeys.ICON_MAINNET]: { rpcUrl: 'https://ctz.solidwallet.io/api/v3' } },
|
|
49
|
-
},
|
|
50
|
-
// BITCOIN: {}, // mount with SDK defaults
|
|
51
|
-
// SOLANA: { chains: { [ChainKeys.SOLANA_MAINNET]: { rpcUrl: '...' } } },
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export function Providers({ children }: { children: React.ReactNode }) {
|
|
55
|
-
return (
|
|
56
|
-
<QueryClientProvider client={queryClient}>
|
|
57
|
-
<SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
|
|
58
|
-
</QueryClientProvider>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Mount point per framework
|
|
66
|
-
|
|
67
|
-
| Framework | Where to mount `<Providers>` | `EVM.ssr` |
|
|
68
|
-
|---|---|---|
|
|
69
|
-
| Next.js (App Router) | `app/layout.tsx`, inside `<body>`. Mark the providers file `'use client'`. Pair with [`recipes/ssr-setup.md`](../../migration/recipes/ssr-setup.md) if you need wagmi cookie hydration. | `true` |
|
|
70
|
-
| Vite + React | `main.tsx`, wrap `<App />` directly. | omit (defaults `false`) |
|
|
71
|
-
| Create React App | `index.tsx`, wrap `<App />` directly. | omit |
|
|
72
|
-
| Remix / Tanstack Start | Root route component, marked client-only. Same as Next.js. | `true` |
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Chain-type slots
|
|
77
|
-
|
|
78
|
-
| Slot | React adapter mounted? | Notes |
|
|
79
|
-
|---|---|---|
|
|
80
|
-
| `EVM` | ✅ wagmi | One connection across every configured EVM chain. WalletConnect opt-in via `EVM.walletConnect.projectId`. |
|
|
81
|
-
| `SOLANA` | ✅ `@solana/wallet-adapter-react` | `autoConnect` defaults to `true`. |
|
|
82
|
-
| `SUI` | ✅ `@mysten/dapp-kit` | `network` defaults to `'mainnet'`. |
|
|
83
|
-
| `BITCOIN` / `STELLAR` / `ICON` / `INJECTIVE` / `NEAR` / `STACKS` | ❌ no React adapter | Service auto-registered at mount. Connector list shipped per chain. |
|
|
84
|
-
|
|
85
|
-
For chain key constants and per-chain entry shapes, see [`../reference/chain-support.md`](../reference/chain-support.md).
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Enable WalletConnect (optional)
|
|
90
|
-
|
|
91
|
-
Add WalletConnect support for Fireblocks / Ledger Live / mobile-only EVM wallets by setting `EVM.walletConnect.projectId`:
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
const walletConfig: SodaxWalletConfig = {
|
|
95
|
-
EVM: {
|
|
96
|
-
ssr: true,
|
|
97
|
-
walletConnect: {
|
|
98
|
-
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!,
|
|
99
|
-
// Optional: showQrModal, metadata, qrModalOptions — see WalletConnectParameters
|
|
100
|
-
},
|
|
101
|
-
chains: { /* ... */ },
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Get a project ID from [cloud.walletconnect.com](https://cloud.walletconnect.com). Omit `walletConnect` entirely to disable — v2 falls back to EIP-6963 injected wallets only. Full pattern in [`walletconnect-setup.md`](./walletconnect-setup.md).
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Config is captured once on mount
|
|
111
|
-
|
|
112
|
-
`SodaxWalletProvider` freezes the `config` prop on first render. Subsequent re-renders with a new reference have **no effect**. To swap config at runtime, remount with a new `key`:
|
|
113
|
-
|
|
114
|
-
```tsx
|
|
115
|
-
<SodaxWalletProvider key={configVersion} config={walletConfig}>
|
|
116
|
-
{children}
|
|
117
|
-
</SodaxWalletProvider>
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Pair with `@sodax/dapp-kit` (optional)
|
|
123
|
-
|
|
124
|
-
If you also use `@sodax/dapp-kit` for SDK feature hooks, mount `SodaxProvider` outermost:
|
|
125
|
-
|
|
126
|
-
```tsx
|
|
127
|
-
<SodaxProvider config={sodaxConfig}>
|
|
128
|
-
<QueryClientProvider client={queryClient}>
|
|
129
|
-
<SodaxWalletProvider config={walletConfig}>{children}</SodaxWalletProvider>
|
|
130
|
-
</QueryClientProvider>
|
|
131
|
-
</SodaxProvider>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
`SodaxProvider` (from dapp-kit) wraps the lot. `QueryClientProvider` must wrap `SodaxWalletProvider` because hooks inside use React Query.
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## Verification
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
# 1. Type check
|
|
142
|
-
pnpm checkTs
|
|
143
|
-
|
|
144
|
-
# 2. Provider mounted exactly once
|
|
145
|
-
grep -rn "SodaxWalletProvider" <user-src> --include="*.tsx" | grep -v "import" | wc -l
|
|
146
|
-
# expect 1
|
|
147
|
-
|
|
148
|
-
# 3. QueryClientProvider wraps SodaxWalletProvider
|
|
149
|
-
# (manual — confirm nesting in providers.tsx)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Next steps
|
|
155
|
-
|
|
156
|
-
- [`connect-button.md`](./connect-button.md) — single-chain connect/disconnect button
|
|
157
|
-
- [`multi-chain-modal.md`](./multi-chain-modal.md) — multi-chain headless wallet modal
|
|
158
|
-
- [`bridge-to-sdk.md`](./bridge-to-sdk.md) — pass `walletProvider` to `@sodax/sdk` calls
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Recipe: Sign Message
|
|
2
|
-
|
|
3
|
-
`useXSignMessage` is a single React Query mutation that delegates message signing to the connected wallet's per-chain `signMessage` implementation. The signature shape and encoding rules differ per chain — Bitcoin in particular auto-selects between BIP-322 and ECDSA based on the connected address type.
|
|
4
|
-
|
|
5
|
-
**Depends on:** [`setup.md`](./setup.md), one of [`connect-button.md`](./connect-button.md) / [`multi-chain-modal.md`](./multi-chain-modal.md)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Hook API
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { useXSignMessage } from '@sodax/wallet-sdk-react';
|
|
13
|
-
|
|
14
|
-
const sign = useXSignMessage();
|
|
15
|
-
|
|
16
|
-
const signature = await sign.mutateAsync({
|
|
17
|
-
xChainType: 'EVM',
|
|
18
|
-
message: 'Sign in to MyDApp\nNonce: abc123',
|
|
19
|
-
});
|
|
20
|
-
// signature: `0x${string}` | Uint8Array | string | undefined
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
| Field | Type | Notes |
|
|
24
|
-
|-------|------|-------|
|
|
25
|
-
| `xChainType` | `ChainType` | Which chain to sign with (`'EVM'`, `'BITCOIN'`, …) |
|
|
26
|
-
| `message` | `string` | Plain UTF-8; per-chain wrappers handle encoding |
|
|
27
|
-
|
|
28
|
-
Return type is the discriminated union `` `0x${string}` | Uint8Array | string | undefined `` because each chain returns its native signature shape (hex for EVM, base64 for Stellar, base58 for Solana, etc.). Cast or branch on `xChainType` when consuming.
|
|
29
|
-
|
|
30
|
-
`undefined` is returned (not thrown) when the chain doesn't implement `signMessage` — currently only ICON. A one-time `console.warn` accompanies the `undefined`.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Per-chain support matrix
|
|
35
|
-
|
|
36
|
-
| Chain | Implementation | Signature shape |
|
|
37
|
-
|-------|----------------|-----------------|
|
|
38
|
-
| EVM | `signMessageAsync` from wagmi → personal_sign | `` `0x${string}` `` |
|
|
39
|
-
| Solana | `signMessage` from `@solana/wallet-adapter` | `Uint8Array` |
|
|
40
|
-
| Sui | `signPersonalMessage` from `@mysten/dapp-kit` | `string` (base64 signature + bytes) |
|
|
41
|
-
| Bitcoin | Auto-detect: BIP-322 (P2WPKH/P2TR) or ECDSA (P2SH/P2PKH) | `string` |
|
|
42
|
-
| Stellar | `walletsKit.signMessage` from `@creit.tech/stellar-wallets-kit` | `string` (base64) |
|
|
43
|
-
| Injective | `walletStrategy.signArbitrary` from `@injectivelabs/wallet-base` | `string` |
|
|
44
|
-
| NEAR | NEAR connector's `signMessage` | `string` |
|
|
45
|
-
| Stacks | `signMessage` from `@stacks/connect` | `string` |
|
|
46
|
-
| **ICON** | **Not supported** — Hana wallet does not expose a signing API | `undefined` |
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Bitcoin — BIP-322 vs ECDSA auto-detect
|
|
51
|
-
|
|
52
|
-
Bitcoin's signing flow inspects the connected address and picks the right method automatically:
|
|
53
|
-
|
|
54
|
-
| Address type | Signing method | Connectors that support it |
|
|
55
|
-
|--------------|----------------|----------------------------|
|
|
56
|
-
| P2WPKH (native segwit, `bc1q…`) | BIP-322 | Unisat, Xverse, OKX |
|
|
57
|
-
| P2TR (taproot, `bc1p…`) | BIP-322 | Unisat, Xverse, OKX |
|
|
58
|
-
| P2SH (legacy multi-sig, `3…`) | ECDSA | Unisat, Xverse, OKX |
|
|
59
|
-
| P2PKH (legacy, `1…`) | ECDSA | Unisat, Xverse, OKX |
|
|
60
|
-
|
|
61
|
-
If a custom connector implements only one of the two methods, calling `signMessage` from a wrongly-typed address surfaces the error inline.
|
|
62
|
-
|
|
63
|
-
The same logic mirrors the SDK's `BitcoinSpokeProvider.authenticateWithWallet` — the React layer doesn't reinvent the dispatch.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## ICON not supported
|
|
68
|
-
|
|
69
|
-
Hana wallet on ICON exposes account / transaction APIs but no general-purpose `signMessage` endpoint. `sign.mutateAsync({ xChainType: 'ICON' })` returns `undefined` and logs:
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
[useXSignMessage] signMessage not supported for chain "ICON"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
If you need a signature on ICON for SIWE-style auth, fall back to a transaction-based proof or skip ICON in your auth flow.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Error handling
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
'use client';
|
|
83
|
-
|
|
84
|
-
import { useXSignMessage } from '@sodax/wallet-sdk-react';
|
|
85
|
-
|
|
86
|
-
export function SignButton() {
|
|
87
|
-
const sign = useXSignMessage();
|
|
88
|
-
|
|
89
|
-
const handleSign = async () => {
|
|
90
|
-
try {
|
|
91
|
-
const signature = await sign.mutateAsync({
|
|
92
|
-
xChainType: 'EVM',
|
|
93
|
-
message: 'Sign in',
|
|
94
|
-
});
|
|
95
|
-
if (!signature) {
|
|
96
|
-
// ICON or other unsupported chain
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
submitSignature(signature);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
// User rejection, wallet disconnect mid-sign, address mismatch, etc.
|
|
102
|
-
console.error('sign failed:', error);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<button onClick={handleSign} disabled={sign.isPending}>
|
|
108
|
-
{sign.isPending ? 'Waiting for wallet…' : 'Sign'}
|
|
109
|
-
</button>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Common error messages by chain:
|
|
115
|
-
|
|
116
|
-
| Chain | Typical error |
|
|
117
|
-
|-------|---------------|
|
|
118
|
-
| EVM | `User rejected the request` (MetaMask), `User denied message signature` (Rabby) |
|
|
119
|
-
| Solana | `WalletSignMessageError: User rejected the request` |
|
|
120
|
-
| Sui | `User rejected the signature request` |
|
|
121
|
-
| Bitcoin | `<connector.id> does not support BIP-322 signing` (mismatch with address type), `User canceled the request` |
|
|
122
|
-
| Stellar | `Stellar signature not found` |
|
|
123
|
-
| Injective | `Injective signature not found`, `Injective address not found` |
|
|
124
|
-
|
|
125
|
-
`mutation.error` reflects the latest failure; `mutation.isError` / `mutation.isPending` follow standard React Query semantics.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Verification
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# 1. Type check
|
|
133
|
-
pnpm checkTs
|
|
134
|
-
|
|
135
|
-
# 2. Manual — connect wallet, click sign button, approve in wallet, confirm signature in handler
|
|
136
|
-
# 3. Manual — reject the signature, confirm error renders
|
|
137
|
-
```
|