@b3dotfun/sdk 0.1.69-alpha.11 → 0.1.69-alpha.13

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 (59) hide show
  1. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.d.ts +3 -1
  2. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +5 -1
  3. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
  4. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +8 -12
  5. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.native.js +6 -9
  6. package/dist/cjs/global-account/react/utils/createWagmiConfig.d.ts +4 -13
  7. package/dist/cjs/global-account/react/utils/createWagmiConfig.js +5 -7
  8. package/dist/cjs/wallet/react/components/ConnectWallet.d.ts +11 -0
  9. package/dist/cjs/wallet/react/components/ConnectWallet.js +467 -0
  10. package/dist/cjs/wallet/react/components/WalletProvider.d.ts +35 -0
  11. package/dist/cjs/wallet/react/components/WalletProvider.js +20 -0
  12. package/dist/cjs/wallet/react/hooks/useWalletDisconnect.d.ts +13 -0
  13. package/dist/cjs/wallet/react/hooks/useWalletDisconnect.js +22 -0
  14. package/dist/cjs/wallet/react/hooks/useWalletState.d.ts +31 -0
  15. package/dist/cjs/wallet/react/hooks/useWalletState.js +63 -0
  16. package/dist/cjs/wallet/react/index.d.ts +5 -0
  17. package/dist/cjs/wallet/react/index.js +16 -0
  18. package/dist/cjs/wallet/utils/createWalletConfig.d.ts +21 -0
  19. package/dist/cjs/wallet/utils/createWalletConfig.js +24 -0
  20. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.d.ts +3 -1
  21. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +5 -1
  22. package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
  23. package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +3 -7
  24. package/dist/esm/global-account/react/components/B3Provider/B3Provider.native.js +2 -5
  25. package/dist/esm/global-account/react/utils/createWagmiConfig.d.ts +4 -13
  26. package/dist/esm/global-account/react/utils/createWagmiConfig.js +5 -7
  27. package/dist/esm/wallet/react/components/ConnectWallet.d.ts +11 -0
  28. package/dist/esm/wallet/react/components/ConnectWallet.js +431 -0
  29. package/dist/esm/wallet/react/components/WalletProvider.d.ts +35 -0
  30. package/dist/esm/wallet/react/components/WalletProvider.js +17 -0
  31. package/dist/esm/wallet/react/hooks/useWalletDisconnect.d.ts +13 -0
  32. package/dist/esm/wallet/react/hooks/useWalletDisconnect.js +19 -0
  33. package/dist/esm/wallet/react/hooks/useWalletState.d.ts +31 -0
  34. package/dist/esm/wallet/react/hooks/useWalletState.js +60 -0
  35. package/dist/esm/wallet/react/index.d.ts +5 -0
  36. package/dist/esm/wallet/react/index.js +8 -0
  37. package/dist/esm/wallet/utils/createWalletConfig.d.ts +21 -0
  38. package/dist/esm/wallet/utils/createWalletConfig.js +21 -0
  39. package/dist/types/anyspend/react/components/checkout/AnySpendCheckout.d.ts +3 -1
  40. package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +4 -1
  41. package/dist/types/global-account/react/utils/createWagmiConfig.d.ts +4 -13
  42. package/dist/types/wallet/react/components/ConnectWallet.d.ts +11 -0
  43. package/dist/types/wallet/react/components/WalletProvider.d.ts +35 -0
  44. package/dist/types/wallet/react/hooks/useWalletDisconnect.d.ts +13 -0
  45. package/dist/types/wallet/react/hooks/useWalletState.d.ts +31 -0
  46. package/dist/types/wallet/react/index.d.ts +5 -0
  47. package/dist/types/wallet/utils/createWalletConfig.d.ts +21 -0
  48. package/package.json +12 -1
  49. package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +6 -0
  50. package/src/global-account/react/components/B3Provider/B3Provider.native.tsx +14 -20
  51. package/src/global-account/react/components/B3Provider/B3Provider.tsx +41 -45
  52. package/src/global-account/react/utils/createWagmiConfig.tsx +6 -7
  53. package/src/wallet/__tests__/createWalletConfig.test.ts +39 -0
  54. package/src/wallet/react/components/ConnectWallet.tsx +665 -0
  55. package/src/wallet/react/components/WalletProvider.tsx +64 -0
  56. package/src/wallet/react/hooks/useWalletDisconnect.ts +22 -0
  57. package/src/wallet/react/hooks/useWalletState.ts +93 -0
  58. package/src/wallet/react/index.ts +10 -0
  59. package/src/wallet/utils/createWalletConfig.ts +39 -0
@@ -0,0 +1,431 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
3
+ import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletRabby, WalletRainbow, WalletWalletConnect, } from "@web3icons/react";
4
+ import { truncateAddress } from "../../../shared/utils/index.js";
5
+ import { AlertCircle, Check, ChevronDown, Copy, Loader2, LogOut, Wallet, X } from "lucide-react";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+ import { useWalletDisconnect } from "../hooks/useWalletDisconnect.js";
8
+ import { useWalletState } from "../hooks/useWalletState.js";
9
+ /** Injects the spinner keyframe once into the document head (idempotent via DOM id). */
10
+ function SpinnerKeyframes() {
11
+ useEffect(() => {
12
+ if (document.getElementById("b3-spin-keyframes"))
13
+ return;
14
+ const style = document.createElement("style");
15
+ style.id = "b3-spin-keyframes";
16
+ style.textContent = "@keyframes b3-spin { to { transform: rotate(360deg) } }";
17
+ document.head.appendChild(style);
18
+ }, []);
19
+ return null;
20
+ }
21
+ const WALLET_DOWNLOAD_URLS = {
22
+ metamask: "https://metamask.io/download/",
23
+ "coinbase wallet": "https://www.coinbase.com/wallet/downloads",
24
+ phantom: "https://phantom.app/download",
25
+ rabby: "https://rabby.io/",
26
+ rainbow: "https://rainbow.me/download",
27
+ walletconnect: "https://walletconnect.com/",
28
+ };
29
+ function getWalletIcon(name, size = 24) {
30
+ switch (name.toLowerCase()) {
31
+ case "metamask":
32
+ return _jsx(WalletMetamask, { width: size, height: size });
33
+ case "coinbase wallet":
34
+ return _jsx(WalletCoinbase, { width: size, height: size });
35
+ case "phantom":
36
+ return _jsx(WalletPhantom, { width: size, height: size });
37
+ case "walletconnect":
38
+ return _jsx(WalletWalletConnect, { width: size, height: size });
39
+ case "rabby wallet":
40
+ case "rabby":
41
+ return _jsx(WalletRabby, { width: size, height: size });
42
+ case "rainbow":
43
+ return _jsx(WalletRainbow, { width: size, height: size });
44
+ default:
45
+ return _jsx(Wallet, { width: size, height: size });
46
+ }
47
+ }
48
+ // B3 brand colors
49
+ const B3_BLUE = "#3368ef";
50
+ const B3_BLUE_HOVER = "#2554d4";
51
+ const theme = {
52
+ light: {
53
+ bg: "#ffffff",
54
+ cardBg: "#ffffff",
55
+ text: "#1a1a2e",
56
+ textSecondary: "#64748b",
57
+ textTertiary: "#94a3b8",
58
+ border: "#D1D1D6",
59
+ borderLight: "#e2e8f0",
60
+ hoverBg: "#f1f5f9",
61
+ iconBg: "#f1f5f9",
62
+ shadow: "0 20px 24px -4px rgba(10,13,18,0.08), 0 8px 8px -4px rgba(10,13,18,0.03), 0 3px 3px -1.5px rgba(10,13,18,0.04)",
63
+ popoverShadow: "0 10px 30px -5px rgba(10,13,18,0.1), 0 4px 6px -4px rgba(10,13,18,0.05)",
64
+ overlayBg: "rgba(0,0,0,0.8)",
65
+ badgeInstalledBg: "#ecfdf5",
66
+ badgeInstalledText: "#059669",
67
+ badgeInstallBg: "#f1f5f9",
68
+ badgeInstallText: "#64748b",
69
+ connectedDot: "#10b981",
70
+ disconnectBorder: "#fca5a5",
71
+ disconnectText: "#dc2626",
72
+ disconnectHoverBg: "#fef2f2",
73
+ copyHoverBg: "#f1f5f9",
74
+ },
75
+ dark: {
76
+ bg: "#0f0f14",
77
+ cardBg: "#1a1a24",
78
+ text: "#f1f5f9",
79
+ textSecondary: "#94a3b8",
80
+ textTertiary: "#64748b",
81
+ border: "#2a2a3a",
82
+ borderLight: "#2a2a3a",
83
+ hoverBg: "#22222e",
84
+ iconBg: "#22222e",
85
+ shadow: "0 20px 24px -4px rgba(0,0,0,0.3), 0 8px 8px -4px rgba(0,0,0,0.2)",
86
+ popoverShadow: "0 10px 30px -5px rgba(0,0,0,0.4)",
87
+ overlayBg: "rgba(0,0,0,0.85)",
88
+ badgeInstalledBg: "#052e16",
89
+ badgeInstalledText: "#34d399",
90
+ badgeInstallBg: "#22222e",
91
+ badgeInstallText: "#64748b",
92
+ connectedDot: "#34d399",
93
+ disconnectBorder: "#7f1d1d",
94
+ disconnectText: "#f87171",
95
+ disconnectHoverBg: "#1c1017",
96
+ copyHoverBg: "#22222e",
97
+ },
98
+ };
99
+ /**
100
+ * Drop-in wallet connection component styled to match the B3 design system.
101
+ * Uses inline styles — works in any app without tailwind configuration.
102
+ */
103
+ export function ConnectWallet({ theme: themeProp = "light", buttonLabel = "Connect Wallet" }) {
104
+ const { address, isConnected, chain, connectors } = useWalletState();
105
+ const { disconnect } = useWalletDisconnect();
106
+ const [modalOpen, setModalOpen] = useState(false);
107
+ const [popoverOpen, setPopoverOpen] = useState(false);
108
+ const [copied, setCopied] = useState(false);
109
+ const [hoveredUid, setHoveredUid] = useState(null);
110
+ const [btnHover, setBtnHover] = useState(false);
111
+ const [disconnectHover, setDisconnectHover] = useState(false);
112
+ const [copyHover, setCopyHover] = useState(false);
113
+ const [connectingUid, setConnectingUid] = useState(null);
114
+ const [connectError, setConnectError] = useState(null);
115
+ const [connectSuccess, setConnectSuccess] = useState(false);
116
+ const t = theme[themeProp];
117
+ const successTimerRef = useRef(null);
118
+ const resetConnectState = useCallback(() => {
119
+ setConnectingUid(null);
120
+ setConnectError(null);
121
+ setConnectSuccess(false);
122
+ if (successTimerRef.current) {
123
+ clearTimeout(successTimerRef.current);
124
+ successTimerRef.current = null;
125
+ }
126
+ }, []);
127
+ // Cleanup success timer on unmount
128
+ useEffect(() => {
129
+ return () => {
130
+ if (successTimerRef.current)
131
+ clearTimeout(successTimerRef.current);
132
+ };
133
+ }, []);
134
+ const handleConnect = useCallback(async (connector) => {
135
+ if (!connector.isInstalled) {
136
+ const url = WALLET_DOWNLOAD_URLS[connector.name.toLowerCase()];
137
+ if (url)
138
+ window.open(url, "_blank", "noopener,noreferrer");
139
+ return;
140
+ }
141
+ setConnectingUid(connector.uid);
142
+ setConnectError(null);
143
+ setConnectSuccess(false);
144
+ try {
145
+ await connector.connect();
146
+ setConnectSuccess(true);
147
+ successTimerRef.current = setTimeout(() => {
148
+ setModalOpen(false);
149
+ resetConnectState();
150
+ }, 800);
151
+ }
152
+ catch (err) {
153
+ const message = err instanceof Error ? err.message : "Connection failed";
154
+ if (message.includes("rejected") || message.includes("denied") || message.includes("User rejected")) {
155
+ setConnectError("Connection rejected");
156
+ }
157
+ else {
158
+ setConnectError(message.length > 60 ? message.slice(0, 60) + "..." : message);
159
+ }
160
+ setConnectingUid(null);
161
+ }
162
+ }, [resetConnectState]);
163
+ const handleDisconnect = useCallback(() => {
164
+ disconnect();
165
+ setPopoverOpen(false);
166
+ }, [disconnect]);
167
+ const handleCopyAddress = useCallback(async () => {
168
+ if (address) {
169
+ try {
170
+ await navigator.clipboard.writeText(address);
171
+ setCopied(true);
172
+ }
173
+ catch {
174
+ // Clipboard API may be denied
175
+ }
176
+ }
177
+ }, [address]);
178
+ useEffect(() => {
179
+ if (!copied)
180
+ return;
181
+ const timer = setTimeout(() => setCopied(false), 2000);
182
+ return () => clearTimeout(timer);
183
+ }, [copied]);
184
+ // Close popover on Escape key
185
+ useEffect(() => {
186
+ if (!popoverOpen)
187
+ return;
188
+ const handleKeyDown = (e) => {
189
+ if (e.key === "Escape")
190
+ setPopoverOpen(false);
191
+ };
192
+ document.addEventListener("keydown", handleKeyDown);
193
+ return () => document.removeEventListener("keydown", handleKeyDown);
194
+ }, [popoverOpen]);
195
+ // --- Connected state ---
196
+ if (isConnected && address) {
197
+ return (_jsxs("div", { style: {
198
+ position: "relative",
199
+ display: "inline-block",
200
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
201
+ }, children: [_jsxs("button", { onClick: () => setPopoverOpen(!popoverOpen), "aria-expanded": popoverOpen, "aria-haspopup": "true", style: {
202
+ display: "flex",
203
+ alignItems: "center",
204
+ gap: 10,
205
+ padding: "10px 16px",
206
+ borderRadius: 12,
207
+ border: `1.5px solid ${t.border}`,
208
+ background: t.cardBg,
209
+ color: t.text,
210
+ fontSize: 14,
211
+ fontWeight: 600,
212
+ cursor: "pointer",
213
+ letterSpacing: "-0.01em",
214
+ transition: "all 150ms ease",
215
+ }, children: [_jsx("span", { style: {
216
+ width: 8,
217
+ height: 8,
218
+ borderRadius: "50%",
219
+ background: t.connectedDot,
220
+ display: "inline-block",
221
+ boxShadow: `0 0 6px ${t.connectedDot}`,
222
+ } }), _jsx("span", { style: { fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", fontSize: 13 }, children: truncateAddress(address) }), _jsx(ChevronDown, { size: 14, color: t.textTertiary, style: { transition: "transform 200ms", transform: popoverOpen ? "rotate(180deg)" : "none" } })] }), popoverOpen && (_jsxs(_Fragment, { children: [_jsx("div", { style: { position: "fixed", inset: 0, zIndex: 40 }, onClick: () => setPopoverOpen(false) }), _jsxs("div", { role: "dialog", "aria-label": "Wallet details", onClick: e => e.stopPropagation(), style: {
223
+ position: "absolute",
224
+ right: 0,
225
+ top: "100%",
226
+ marginTop: 8,
227
+ width: 320,
228
+ borderRadius: 16,
229
+ border: `1.5px solid ${t.border}`,
230
+ background: t.cardBg,
231
+ boxShadow: t.popoverShadow,
232
+ zIndex: 50,
233
+ overflow: "hidden",
234
+ }, children: [_jsxs("div", { style: {
235
+ padding: "16px 20px 14px",
236
+ borderBottom: `1px solid ${t.borderLight}`,
237
+ display: "flex",
238
+ alignItems: "center",
239
+ justifyContent: "space-between",
240
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: {
241
+ width: 8,
242
+ height: 8,
243
+ borderRadius: "50%",
244
+ background: t.connectedDot,
245
+ display: "inline-block",
246
+ boxShadow: `0 0 6px ${t.connectedDot}`,
247
+ } }), _jsx("span", { style: { fontSize: 13, fontWeight: 600, color: t.text, letterSpacing: "-0.01em" }, children: "Connected" })] }), chain && (_jsx("span", { style: {
248
+ fontSize: 11,
249
+ fontWeight: 600,
250
+ padding: "3px 10px",
251
+ borderRadius: 99,
252
+ background: t.iconBg,
253
+ color: t.textSecondary,
254
+ letterSpacing: "0.02em",
255
+ textTransform: "uppercase",
256
+ }, children: chain.name }))] }), _jsxs("div", { style: { padding: "12px 12px 16px" }, children: [_jsxs("button", { onClick: handleCopyAddress, onMouseEnter: () => setCopyHover(true), onMouseLeave: () => setCopyHover(false), style: {
257
+ display: "flex",
258
+ alignItems: "center",
259
+ gap: 10,
260
+ width: "100%",
261
+ padding: "10px 12px",
262
+ borderRadius: 10,
263
+ border: "none",
264
+ background: copyHover ? t.copyHoverBg : "transparent",
265
+ textAlign: "left",
266
+ fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
267
+ fontSize: 12,
268
+ color: t.text,
269
+ cursor: "pointer",
270
+ marginBottom: 8,
271
+ wordBreak: "break-all",
272
+ lineHeight: 1.5,
273
+ transition: "background 150ms ease",
274
+ }, children: [_jsx("span", { style: { flex: 1 }, children: address }), copied ? (_jsx(Check, { size: 14, color: t.connectedDot, style: { flexShrink: 0 } })) : (_jsx(Copy, { size: 14, color: t.textTertiary, style: { flexShrink: 0 } }))] }), _jsxs("button", { onClick: handleDisconnect, onMouseEnter: () => setDisconnectHover(true), onMouseLeave: () => setDisconnectHover(false), style: {
275
+ display: "flex",
276
+ alignItems: "center",
277
+ justifyContent: "center",
278
+ gap: 8,
279
+ width: "100%",
280
+ padding: "10px 16px",
281
+ borderRadius: 10,
282
+ border: `1.5px solid ${t.disconnectBorder}`,
283
+ background: disconnectHover ? t.disconnectHoverBg : "transparent",
284
+ color: t.disconnectText,
285
+ fontSize: 13,
286
+ fontWeight: 600,
287
+ cursor: "pointer",
288
+ transition: "all 150ms ease",
289
+ }, children: [_jsx(LogOut, { size: 15 }), "Disconnect"] })] })] })] }))] }));
290
+ }
291
+ // --- Disconnected state ---
292
+ return (_jsxs(DialogPrimitive.Root, { open: modalOpen, onOpenChange: open => {
293
+ setModalOpen(open);
294
+ if (!open)
295
+ resetConnectState();
296
+ }, children: [_jsx(DialogPrimitive.Trigger, { asChild: true, children: _jsx("button", { onMouseEnter: () => setBtnHover(true), onMouseLeave: () => setBtnHover(false), style: {
297
+ padding: "12px 24px",
298
+ borderRadius: 12,
299
+ border: "none",
300
+ background: btnHover ? B3_BLUE_HOVER : B3_BLUE,
301
+ color: "#ffffff",
302
+ fontSize: 14,
303
+ fontWeight: 600,
304
+ cursor: "pointer",
305
+ letterSpacing: "-0.01em",
306
+ transition: "all 150ms ease",
307
+ boxShadow: btnHover ? `0 4px 12px ${B3_BLUE}40, 0 1px 3px rgba(0,0,0,0.1)` : `0 2px 8px ${B3_BLUE}30`,
308
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
309
+ }, children: buttonLabel }) }), _jsx(SpinnerKeyframes, {}), _jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { style: {
310
+ position: "fixed",
311
+ inset: 0,
312
+ zIndex: 50,
313
+ background: t.overlayBg,
314
+ backdropFilter: "blur(20px)",
315
+ WebkitBackdropFilter: "blur(20px)",
316
+ } }), _jsxs(DialogPrimitive.Content, { style: {
317
+ position: "fixed",
318
+ left: "50%",
319
+ top: "50%",
320
+ transform: "translate(-50%, -50%)",
321
+ zIndex: 50,
322
+ width: "calc(100% - 32px)",
323
+ maxWidth: 400,
324
+ borderRadius: 20,
325
+ border: `1.5px solid ${t.border}`,
326
+ background: t.cardBg,
327
+ color: t.text,
328
+ boxShadow: t.shadow,
329
+ overflow: "hidden",
330
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
331
+ }, children: [_jsxs("div", { style: {
332
+ display: "flex",
333
+ alignItems: "center",
334
+ justifyContent: "space-between",
335
+ padding: "20px 24px 16px",
336
+ }, children: [_jsxs("div", { children: [_jsx(DialogPrimitive.Title, { style: {
337
+ fontSize: 18,
338
+ fontWeight: 700,
339
+ margin: 0,
340
+ letterSpacing: "-0.02em",
341
+ color: t.text,
342
+ }, children: "Connect Wallet" }), _jsx("p", { style: { fontSize: 13, color: t.textSecondary, margin: "4px 0 0", letterSpacing: "-0.01em" }, children: "Choose your preferred wallet" })] }), _jsx(DialogPrimitive.Close, { style: {
343
+ background: t.iconBg,
344
+ border: "none",
345
+ cursor: "pointer",
346
+ padding: 8,
347
+ borderRadius: 10,
348
+ color: t.textSecondary,
349
+ display: "flex",
350
+ alignItems: "center",
351
+ justifyContent: "center",
352
+ transition: "all 150ms ease",
353
+ }, children: _jsx(X, { size: 16 }) })] }), connectError && (_jsxs("div", { style: {
354
+ margin: "0 12px 8px",
355
+ padding: "10px 14px",
356
+ borderRadius: 10,
357
+ background: themeProp === "dark" ? "#1c1017" : "#fef2f2",
358
+ border: `1px solid ${t.disconnectBorder}`,
359
+ display: "flex",
360
+ alignItems: "center",
361
+ gap: 8,
362
+ fontSize: 13,
363
+ color: t.disconnectText,
364
+ }, children: [_jsx(AlertCircle, { size: 16, style: { flexShrink: 0 } }), _jsx("span", { style: { flex: 1 }, children: connectError }), _jsx("button", { onClick: () => setConnectError(null), style: { background: "none", border: "none", cursor: "pointer", color: t.disconnectText, padding: 2 }, children: _jsx(X, { size: 14 }) })] })), _jsx("div", { style: { padding: "0 12px 16px" }, children: connectors.length === 0 ? (_jsx("p", { style: { padding: "40px 0", textAlign: "center", fontSize: 14, color: t.textSecondary }, children: "No wallets detected" })) : (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: connectors.map(connector => {
365
+ const isLoading = connectingUid === connector.uid;
366
+ const isSuccess = connectSuccess && connectingUid === connector.uid;
367
+ const isDisabled = connectingUid !== null && connectingUid !== connector.uid;
368
+ return (_jsxs("button", { onClick: () => !connectingUid && handleConnect(connector), onMouseEnter: () => setHoveredUid(connector.uid), onMouseLeave: () => setHoveredUid(null), style: {
369
+ display: "flex",
370
+ alignItems: "center",
371
+ gap: 14,
372
+ width: "100%",
373
+ padding: "14px 16px",
374
+ borderRadius: 14,
375
+ border: isLoading
376
+ ? `1.5px solid ${B3_BLUE}`
377
+ : isSuccess
378
+ ? `1.5px solid ${t.connectedDot}`
379
+ : hoveredUid === connector.uid && !isDisabled
380
+ ? `1.5px solid ${B3_BLUE}40`
381
+ : "1.5px solid transparent",
382
+ background: isLoading
383
+ ? themeProp === "dark"
384
+ ? "#1a1a30"
385
+ : "#eff6ff"
386
+ : isSuccess
387
+ ? themeProp === "dark"
388
+ ? "#0a1f15"
389
+ : "#ecfdf5"
390
+ : hoveredUid === connector.uid && !isDisabled
391
+ ? t.hoverBg
392
+ : "transparent",
393
+ textAlign: "left",
394
+ cursor: isDisabled ? "default" : "pointer",
395
+ color: t.text,
396
+ opacity: isDisabled ? 0.4 : 1,
397
+ transition: "all 150ms ease",
398
+ }, children: [_jsx("div", { style: {
399
+ width: 44,
400
+ height: 44,
401
+ borderRadius: 12,
402
+ background: t.iconBg,
403
+ display: "flex",
404
+ alignItems: "center",
405
+ justifyContent: "center",
406
+ flexShrink: 0,
407
+ border: `1px solid ${t.borderLight}`,
408
+ }, children: getWalletIcon(connector.name, 24) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontSize: 14, fontWeight: 600, letterSpacing: "-0.01em" }, children: connector.name }), isLoading && (_jsx("div", { style: { fontSize: 12, color: B3_BLUE, marginTop: 2 }, children: "Waiting for approval..." })), isSuccess && (_jsx("div", { style: { fontSize: 12, color: t.connectedDot, marginTop: 2 }, children: "Connected" }))] }), isLoading && (_jsx(Loader2, { size: 18, color: B3_BLUE, style: { flexShrink: 0, animation: "b3-spin 1s linear infinite" } })), isSuccess && _jsx(Check, { size: 18, color: t.connectedDot, style: { flexShrink: 0 } }), !isLoading && !isSuccess && !connector.isInstalled && (_jsx("span", { style: {
409
+ fontSize: 11,
410
+ fontWeight: 600,
411
+ padding: "4px 10px",
412
+ borderRadius: 99,
413
+ background: t.badgeInstallBg,
414
+ color: t.badgeInstallText,
415
+ letterSpacing: "0.01em",
416
+ }, children: "Install" }))] }, connector.uid));
417
+ }) })) }), _jsxs("div", { style: {
418
+ padding: "12px 24px 16px",
419
+ borderTop: `1px solid ${t.borderLight}`,
420
+ display: "flex",
421
+ alignItems: "center",
422
+ justifyContent: "center",
423
+ gap: 6,
424
+ }, children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", width: 16, height: 16 }), _jsx("span", { style: {
425
+ fontSize: 11,
426
+ fontWeight: 700,
427
+ color: B3_BLUE,
428
+ letterSpacing: "0.06em",
429
+ textTransform: "uppercase",
430
+ }, children: "Powered by B3" })] })] })] })] }));
431
+ }
@@ -0,0 +1,35 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import { type Config, type CreateConnectorFn } from "wagmi";
3
+ import type { Chain } from "viem";
4
+ export interface WalletProviderProps {
5
+ children: React.ReactNode;
6
+ /**
7
+ * Chains to support. Defaults to SDK's supportedChains.
8
+ * Must be memoized or defined outside the component — inline arrays cause the
9
+ * wagmi config to rebuild on every render, losing wallet connection state.
10
+ */
11
+ chains?: Chain[];
12
+ /**
13
+ * Wagmi connectors. Must be memoized or defined outside the component — inline
14
+ * arrays cause the wagmi config to rebuild on every render, losing wallet connection state.
15
+ */
16
+ connectors?: CreateConnectorFn[];
17
+ /**
18
+ * Optional RPC URL overrides per chain ID.
19
+ * Must be memoized or defined outside the component — inline objects cause the
20
+ * wagmi config to rebuild on every render, losing wallet connection state.
21
+ */
22
+ rpcUrls?: Record<number, string>;
23
+ /** Escape hatch: pass a fully custom wagmi config. Overrides chains/connectors/rpcUrls. */
24
+ wagmiConfig?: Config;
25
+ /** Provide your own QueryClient for React Query. Defaults to an internal instance. */
26
+ queryClient?: QueryClient;
27
+ /** Whether to auto-reconnect the last wallet on mount. Defaults to false. */
28
+ reconnectOnMount?: boolean;
29
+ }
30
+ /**
31
+ * Standalone wallet connection provider.
32
+ * Wraps ThirdwebProvider, WagmiProvider, and QueryClientProvider.
33
+ * Use wagmi hooks (useAccount, useConnect, useDisconnect) directly inside.
34
+ */
35
+ export declare function WalletProvider({ children, chains, connectors, rpcUrls, wagmiConfig: wagmiConfigProp, queryClient: queryClientProp, reconnectOnMount, }: WalletProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+ import { useMemo, useState } from "react";
4
+ import { ThirdwebProvider } from "thirdweb/react";
5
+ import { WagmiProvider } from "wagmi";
6
+ import { createWalletConfig } from "../../utils/createWalletConfig.js";
7
+ /**
8
+ * Standalone wallet connection provider.
9
+ * Wraps ThirdwebProvider, WagmiProvider, and QueryClientProvider.
10
+ * Use wagmi hooks (useAccount, useConnect, useDisconnect) directly inside.
11
+ */
12
+ export function WalletProvider({ children, chains, connectors, rpcUrls, wagmiConfig: wagmiConfigProp, queryClient: queryClientProp, reconnectOnMount = false, }) {
13
+ const [defaultQueryClient] = useState(() => new QueryClient());
14
+ const queryClient = queryClientProp ?? defaultQueryClient;
15
+ const wagmiConfig = useMemo(() => wagmiConfigProp ?? createWalletConfig({ chains, connectors, rpcUrls }), [wagmiConfigProp, chains, connectors, rpcUrls]);
16
+ return (_jsx(ThirdwebProvider, { children: _jsx(WagmiProvider, { config: wagmiConfig, reconnectOnMount: reconnectOnMount, children: _jsx(QueryClientProvider, { client: queryClient, children: children }) }) }));
17
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Disconnect the current wallet via wagmi.
3
+ * Consumers should use this instead of wagmi's useDisconnect directly
4
+ * to ensure a consistent disconnect API across the wallet module.
5
+ *
6
+ * Note: this only disconnects the wagmi layer. When used inside B3Provider,
7
+ * the thirdweb auth session is managed separately by B3Provider's own logout
8
+ * flow (useAuthentication). ConnectWallet is intended for standalone
9
+ * WalletProvider usage where thirdweb auth is not involved.
10
+ */
11
+ export declare function useWalletDisconnect(): {
12
+ disconnect: () => void;
13
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback } from "react";
2
+ import { useDisconnect } from "wagmi";
3
+ /**
4
+ * Disconnect the current wallet via wagmi.
5
+ * Consumers should use this instead of wagmi's useDisconnect directly
6
+ * to ensure a consistent disconnect API across the wallet module.
7
+ *
8
+ * Note: this only disconnects the wagmi layer. When used inside B3Provider,
9
+ * the thirdweb auth session is managed separately by B3Provider's own logout
10
+ * flow (useAuthentication). ConnectWallet is intended for standalone
11
+ * WalletProvider usage where thirdweb auth is not involved.
12
+ */
13
+ export function useWalletDisconnect() {
14
+ const { disconnect: wagmiDisconnect } = useDisconnect();
15
+ const disconnect = useCallback(() => {
16
+ wagmiDisconnect();
17
+ }, [wagmiDisconnect]);
18
+ return { disconnect };
19
+ }
@@ -0,0 +1,31 @@
1
+ import type { Chain } from "viem";
2
+ import type { Connector } from "wagmi";
3
+ export interface WalletConnector {
4
+ /** Display name of the wallet */
5
+ name: string;
6
+ /** Unique identifier for this connector instance */
7
+ uid: string;
8
+ /** Whether the wallet extension/app is detected in the browser */
9
+ isInstalled: boolean;
10
+ /** Connect using this connector. Returns a promise that resolves on success or rejects on failure/rejection. */
11
+ connect: () => Promise<void>;
12
+ /** Raw wagmi connector for advanced use */
13
+ connector: Connector;
14
+ }
15
+ export interface WalletState {
16
+ /** Connected wallet address, or undefined if not connected */
17
+ address: string | undefined;
18
+ /** Whether a wallet is currently connected */
19
+ isConnected: boolean;
20
+ /** Current chain the wallet is connected to */
21
+ chain: Chain | undefined;
22
+ /** Whether a connection attempt is in progress */
23
+ isPending: boolean;
24
+ /** Available wallet connectors, deduplicated by name */
25
+ connectors: WalletConnector[];
26
+ }
27
+ /**
28
+ * Unified wallet state from both thirdweb and wagmi layers.
29
+ * Provides available connectors with install status and connect functions.
30
+ */
31
+ export declare function useWalletState(): WalletState;
@@ -0,0 +1,60 @@
1
+ import { useEffect, useMemo, useRef } from "react";
2
+ import { useAccount, useConnect, useConnectors } from "wagmi";
3
+ /**
4
+ * Unified wallet state from both thirdweb and wagmi layers.
5
+ * Provides available connectors with install status and connect functions.
6
+ */
7
+ export function useWalletState() {
8
+ const { address, isConnected, chain } = useAccount();
9
+ const { connectAsync, isPending } = useConnect();
10
+ const wagmiConnectors = useConnectors();
11
+ // Ref to avoid connectAsync in useMemo deps — wagmi returns a new reference on state
12
+ // changes, which would recreate the connectors array on every render.
13
+ const connectAsyncRef = useRef(connectAsync);
14
+ useEffect(() => {
15
+ connectAsyncRef.current = connectAsync;
16
+ });
17
+ const connectors = useMemo(() => {
18
+ const seen = new Set();
19
+ const mapped = wagmiConnectors
20
+ .filter(c => {
21
+ // Hide generic "Injected" — it duplicates the actual wallet (MetaMask, Rabby, etc.)
22
+ if (c.name === "Injected")
23
+ return false;
24
+ if (seen.has(c.name))
25
+ return false;
26
+ seen.add(c.name);
27
+ return true;
28
+ })
29
+ .map(connector => ({
30
+ name: connector.name,
31
+ uid: connector.uid,
32
+ isInstalled: isConnectorInstalled(connector),
33
+ connect: async () => {
34
+ await connectAsyncRef.current({ connector });
35
+ },
36
+ connector,
37
+ }));
38
+ // Sort: installed connectors first
39
+ return mapped.sort((a, b) => (a.isInstalled === b.isInstalled ? 0 : a.isInstalled ? -1 : 1));
40
+ }, [wagmiConnectors]);
41
+ return { address, isConnected, chain, isPending, connectors };
42
+ }
43
+ /** Check if a wallet connector's provider is available in the browser. */
44
+ function isConnectorInstalled(connector) {
45
+ // WalletConnect is always "available" (uses QR code, no extension needed)
46
+ if (connector.id === "walletConnect")
47
+ return true;
48
+ // For injected connectors, check if any provider exists. This may report
49
+ // false positives (e.g., Rabby shows as installed when only MetaMask is)
50
+ // because multiple injected connectors share window.ethereum. This is
51
+ // intentional — we surface all options and let the wallet handle conflicts.
52
+ if (connector.type === "injected") {
53
+ return typeof window !== "undefined" && !!window.ethereum;
54
+ }
55
+ // Coinbase Wallet SDK works without extension (has mobile/QR fallback)
56
+ if (connector.id === "coinbaseWalletSDK")
57
+ return true;
58
+ // Default: assume available (better UX than hiding connectors)
59
+ return true;
60
+ }
@@ -0,0 +1,5 @@
1
+ export { WalletProvider, type WalletProviderProps } from "./components/WalletProvider";
2
+ export { createWalletConfig, type CreateWalletConfigOptions } from "../utils/createWalletConfig";
3
+ export { useWalletDisconnect } from "./hooks/useWalletDisconnect";
4
+ export { useWalletState, type WalletConnector, type WalletState } from "./hooks/useWalletState";
5
+ export { ConnectWallet, type ConnectWalletProps } from "./components/ConnectWallet";
@@ -0,0 +1,8 @@
1
+ // Provider
2
+ export { WalletProvider } from "./components/WalletProvider.js";
3
+ export { createWalletConfig } from "../utils/createWalletConfig.js";
4
+ // Hooks
5
+ export { useWalletDisconnect } from "./hooks/useWalletDisconnect.js";
6
+ export { useWalletState } from "./hooks/useWalletState.js";
7
+ // Components
8
+ export { ConnectWallet } from "./components/ConnectWallet.js";
@@ -0,0 +1,21 @@
1
+ import { type CreateConnectorFn } from "wagmi";
2
+ import type { Chain } from "viem";
3
+ export interface CreateWalletConfigOptions {
4
+ /** Chains to support. Defaults to SDK's supportedChains. Must be non-empty. */
5
+ chains?: Chain[];
6
+ /**
7
+ * Wagmi connectors. Defaults to injected + Coinbase Wallet.
8
+ * Must be memoized if passed to WalletProvider — inline arrays
9
+ * will cause the config to rebuild on every render, losing wallet connection state.
10
+ */
11
+ connectors?: CreateConnectorFn[];
12
+ /** Optional RPC URL overrides per chain ID. */
13
+ rpcUrls?: Record<number, string>;
14
+ }
15
+ /**
16
+ * Creates a generic wagmi config with sensible defaults.
17
+ * No ecosystem wallet, no B3-specific concerns.
18
+ */
19
+ export declare function createWalletConfig(options?: CreateWalletConfigOptions): import("wagmi").Config<readonly [Chain, ...Chain[]], {
20
+ [k: string]: import("viem").HttpTransport<undefined, false>;
21
+ }, CreateConnectorFn[]>;