@b3dotfun/sdk 0.0.82-alpha.3 → 0.0.83-test.0

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 (53) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +3 -0
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +3 -0
  3. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +177 -47
  4. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +3 -0
  5. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  6. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +73 -0
  7. package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +57 -12
  8. package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
  9. package/dist/cjs/global-account/react/hooks/index.js +3 -1
  10. package/dist/cjs/global-account/react/hooks/useAuth.d.ts +76 -0
  11. package/dist/cjs/global-account/react/hooks/useAuth.js +338 -0
  12. package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +2 -2
  13. package/dist/cjs/global-account/react/hooks/useAuthentication.js +72 -63
  14. package/dist/cjs/global-account/react/hooks/useTWAuth.d.ts +3 -0
  15. package/dist/cjs/global-account/react/hooks/useTWAuth.js +8 -0
  16. package/dist/cjs/global-account/react/hooks/useTurnkeyAuth.js +54 -24
  17. package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +1 -1
  18. package/dist/esm/anyspend/react/components/AnySpend.js +3 -0
  19. package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -0
  20. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +175 -45
  21. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +3 -0
  22. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  23. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +70 -0
  24. package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +57 -12
  25. package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
  26. package/dist/esm/global-account/react/hooks/index.js +1 -0
  27. package/dist/esm/global-account/react/hooks/useAuth.d.ts +76 -0
  28. package/dist/esm/global-account/react/hooks/useAuth.js +332 -0
  29. package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +2 -2
  30. package/dist/esm/global-account/react/hooks/useAuthentication.js +72 -63
  31. package/dist/esm/global-account/react/hooks/useTWAuth.d.ts +3 -0
  32. package/dist/esm/global-account/react/hooks/useTWAuth.js +8 -0
  33. package/dist/esm/global-account/react/hooks/useTurnkeyAuth.js +54 -24
  34. package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +1 -1
  35. package/dist/styles/index.css +1 -1
  36. package/dist/types/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  37. package/dist/types/global-account/react/hooks/index.d.ts +1 -0
  38. package/dist/types/global-account/react/hooks/useAuth.d.ts +76 -0
  39. package/dist/types/global-account/react/hooks/useAuthentication.d.ts +2 -2
  40. package/dist/types/global-account/react/hooks/useTWAuth.d.ts +3 -0
  41. package/dist/types/global-account/react/hooks/useUserQuery.d.ts +1 -1
  42. package/package.json +1 -1
  43. package/src/anyspend/react/components/AnySpend.tsx +4 -0
  44. package/src/anyspend/react/components/AnySpendCustom.tsx +4 -0
  45. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +253 -22
  46. package/src/anyspend/react/hooks/useAnyspendFlow.ts +4 -0
  47. package/src/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.ts +80 -0
  48. package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +77 -22
  49. package/src/global-account/react/hooks/index.ts +1 -0
  50. package/src/global-account/react/hooks/useAuth.ts +380 -0
  51. package/src/global-account/react/hooks/useAuthentication.ts +88 -85
  52. package/src/global-account/react/hooks/useTWAuth.tsx +10 -0
  53. package/src/global-account/react/hooks/useTurnkeyAuth.ts +59 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.82-alpha.3",
3
+ "version": "0.0.83-test.0",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -38,6 +38,7 @@ import { parseUnits } from "viem";
38
38
  import { base, mainnet } from "viem/chains";
39
39
  import { components } from "../../types/api";
40
40
  import { useAutoSelectCryptoPaymentMethod } from "../hooks/useAutoSelectCryptoPaymentMethod";
41
+ import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
41
42
  import { useConnectedWalletDisplay } from "../hooks/useConnectedWalletDisplay";
42
43
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
43
44
  import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
@@ -460,6 +461,9 @@ function AnySpendInner({
460
461
 
461
462
  const globalWalletImage = useAccountWalletImage();
462
463
 
464
+ // Auto-set active wallet from wagmi
465
+ useAutoSetActiveWalletFromWagmi();
466
+
463
467
  // Get wallet address based on selected payment method
464
468
  const { walletAddress } = useConnectedWalletDisplay(effectiveCryptoPaymentMethod);
465
469
 
@@ -44,6 +44,7 @@ import { motion } from "motion/react";
44
44
  import React, { useCallback, useEffect, useMemo, useState } from "react";
45
45
 
46
46
  import { base } from "viem/chains";
47
+ import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
47
48
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
48
49
  import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
49
50
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
@@ -246,6 +247,9 @@ function AnySpendCustomInner({
246
247
  const searchParams = useSearchParamsSSR();
247
248
  const router = useRouter();
248
249
 
250
+ // Auto-set active wallet from wagmi
251
+ useAutoSetActiveWalletFromWagmi();
252
+
249
253
  const [activePanel, setActivePanel] = useState<PanelView>(
250
254
  loadOrder ? PanelView.ORDER_DETAILS : PanelView.CONFIRM_ORDER,
251
255
  );
@@ -4,8 +4,13 @@ import { toast, useAccountWallet, WalletImage } from "@b3dotfun/sdk/global-accou
4
4
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
5
5
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
6
6
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
7
+ import { WalletCoinbase, WalletMetamask, WalletRainbow, WalletWalletConnect } from "@web3icons/react";
7
8
  import { ChevronLeft, ChevronRightCircle, Wallet, X, ZapIcon } from "lucide-react";
8
- import { useConnectModal, useDisconnect, useWalletInfo } from "thirdweb/react";
9
+ import { useState } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { useSetActiveWallet, useWalletInfo } from "thirdweb/react";
12
+ import { createWallet, WalletId } from "thirdweb/wallets";
13
+ import { useAccount, useConnect, useDisconnect, useWalletClient } from "wagmi";
9
14
  import { useConnectedWalletDisplay } from "../../hooks/useConnectedWalletDisplay";
10
15
 
11
16
  export enum CryptoPaymentMethodType {
@@ -30,32 +35,128 @@ export function CryptoPaymentMethod({
30
35
  onBack,
31
36
  onSelectPaymentMethod,
32
37
  }: CryptoPaymentMethodProps) {
33
- const { connectedEOAWallet, connectedSmartWallet } = useAccountWallet();
38
+ const { connectedEOAWallet: connectedEOAWallet, connectedSmartWallet: connectedSmartWallet } = useAccountWallet();
39
+ const { connector, address } = useAccount();
40
+ const { connect, connectors, isPending } = useConnect();
34
41
  const { disconnect } = useDisconnect();
35
- const { connect: openConnectModal } = useConnectModal();
42
+ const { data: walletClient } = useWalletClient();
43
+ const [showWalletModal, setShowWalletModal] = useState(false);
44
+ const setActiveWallet = useSetActiveWallet();
36
45
  const { data: eoaWalletInfo } = useWalletInfo(connectedEOAWallet?.id);
37
46
 
47
+ const isConnected = !!connectedEOAWallet;
38
48
  const globalAddress = connectedSmartWallet?.getAccount()?.address;
39
49
 
40
50
  // Use custom hook to determine wallet display logic
41
- const { shouldShowConnectedEOA } = useConnectedWalletDisplay(selectedPaymentMethod);
51
+ const { shouldShowConnectedEOA, shouldShowWagmiWallet } = useConnectedWalletDisplay(selectedPaymentMethod);
52
+ console.log("shouldShowWagmiWallet :", shouldShowWagmiWallet);
42
53
 
43
- // Handle wallet connection using thirdweb modal
44
- const handleConnectWallet = async () => {
54
+ // Map wagmi connector names to thirdweb wallet IDs
55
+ const getThirdwebWalletId = (connectorName: string): WalletId | null => {
56
+ const walletMap: Record<string, WalletId> = {
57
+ MetaMask: "io.metamask",
58
+ "Coinbase Wallet": "com.coinbase.wallet",
59
+ Rainbow: "me.rainbow",
60
+ WalletConnect: "walletConnect",
61
+ Phantom: "app.phantom",
62
+ };
63
+ return walletMap[connectorName] || null;
64
+ };
65
+
66
+ // Create thirdweb wallet from wagmi connector
67
+ const createThirdwebWalletFromConnector = async (connectorName: string) => {
68
+ const walletId = getThirdwebWalletId(connectorName);
69
+ if (!walletId) {
70
+ console.warn(`No thirdweb wallet ID found for connector: ${connectorName}`);
71
+ return null;
72
+ }
73
+
74
+ try {
75
+ const thirdwebWallet = createWallet(walletId);
76
+ // Connect the wallet to sync with the existing wagmi connection
77
+ await thirdwebWallet.connect({ client });
78
+ return thirdwebWallet;
79
+ } catch (error) {
80
+ console.error(`Failed to create thirdweb wallet for ${connectorName}:`, error);
81
+ return null;
82
+ }
83
+ };
84
+
85
+ // Define available wallet connectors
86
+ const availableConnectors = connectors.filter(connector =>
87
+ ["MetaMask", "WalletConnect", "Coinbase Wallet", "Rainbow", "Phantom"].includes(connector.name),
88
+ );
89
+
90
+ // Define wallet options with icons and info
91
+ const walletOptions = [
92
+ {
93
+ id: "metamask",
94
+ name: "MetaMask",
95
+ icon: <WalletMetamask size={48} />,
96
+ description: "Connect using MetaMask browser extension",
97
+ connector: availableConnectors.find(c => c.name === "MetaMask"),
98
+ },
99
+ {
100
+ id: "coinbase",
101
+ name: "Coinbase Wallet",
102
+ icon: <WalletCoinbase size={48} />,
103
+ description: "Connect using Coinbase Wallet",
104
+ connector: availableConnectors.find(c => c.name === "Coinbase Wallet"),
105
+ },
106
+ {
107
+ id: "rainbow",
108
+ name: "Rainbow",
109
+ icon: <WalletRainbow size={48} />,
110
+ description: "Connect using Rainbow wallet",
111
+ connector: availableConnectors.find(c => c.name === "Rainbow"),
112
+ },
113
+ {
114
+ id: "walletconnect",
115
+ name: "WalletConnect",
116
+ icon: <WalletWalletConnect size={48} />,
117
+ description: "Connect using WalletConnect protocol",
118
+ connector: availableConnectors.find(c => c.name === "WalletConnect"),
119
+ },
120
+ ].filter(wallet => wallet.connector); // Only show wallets that have available connectors
121
+
122
+ // Reset modal state when closing
123
+ const handleCloseModal = () => {
124
+ setShowWalletModal(false);
125
+ };
126
+
127
+ // Function to request wallet permissions for specific wallet
128
+ const requestWalletPermissions = async (walletConnector?: any) => {
45
129
  try {
46
- // Disconnect current wallet before connecting a new one
47
- if (connectedEOAWallet) {
48
- await disconnect(connectedEOAWallet);
130
+ // If a specific wallet connector is provided and it's different from current
131
+ if (walletConnector && connector?.name !== walletConnector.name) {
132
+ // Disconnect current and connect to the selected wallet
133
+ // if (isConnected) {
134
+ // disconnect();
135
+ // // Small delay to ensure disconnection
136
+ // await new Promise(resolve => setTimeout(resolve, 100));
137
+ // }
138
+ await connect({ connector: walletConnector });
139
+ setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
140
+ onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
141
+ toast.success(`Connected to ${walletConnector.name}`);
142
+ return;
49
143
  }
50
- const wallet = await openConnectModal({ client, setActive: false });
51
- if (wallet) {
52
- // setActiveWallet(wallet);
144
+
145
+ // If same wallet or no specific wallet, request permissions for account switching
146
+ if (walletClient && "request" in walletClient) {
147
+ await walletClient.request({
148
+ method: "wallet_requestPermissions",
149
+ params: [{ eth_accounts: {} }],
150
+ });
151
+ toast.success("Account selection completed");
53
152
  setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
54
153
  onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
55
- toast.success("Wallet connected");
154
+ } else {
155
+ // Fallback: show modal for manual wallet selection
156
+ setShowWalletModal(true);
56
157
  }
57
158
  } catch (error) {
58
- console.error("Failed to connect wallet:", error);
159
+ console.error("Failed to request wallet permissions:", error);
59
160
  if (error && typeof error === "object" && "message" in error) {
60
161
  const errorMessage = (error as any).message.toLowerCase();
61
162
  if (
@@ -63,10 +164,12 @@ export function CryptoPaymentMethod({
63
164
  errorMessage.includes("denied") ||
64
165
  errorMessage.includes("cancelled")
65
166
  ) {
66
- // User cancelled - no toast needed
167
+ toast.error("Account selection cancelled");
67
168
  } else {
68
- toast.error("Failed to connect wallet");
169
+ toast.error("Failed to open account selection");
69
170
  }
171
+ } else {
172
+ toast.error("Failed to open account selection");
70
173
  }
71
174
  }
72
175
  };
@@ -133,7 +236,7 @@ export function CryptoPaymentMethod({
133
236
  {/* Payment Methods */}
134
237
  <div className="crypto-payment-methods flex flex-col gap-4">
135
238
  {/* Installed Wallets Section */}
136
- {(shouldShowConnectedEOA || globalAddress) && (
239
+ {(shouldShowConnectedEOA || shouldShowWagmiWallet || globalAddress) && (
137
240
  <div className="installed-wallets">
138
241
  <h3 className="text-as-primary/80 mb-3 text-sm font-medium">Connected wallets</h3>
139
242
  <div className="space-y-2">
@@ -144,7 +247,10 @@ export function CryptoPaymentMethod({
144
247
  onClick={() => {
145
248
  setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
146
249
  onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
147
- toast.success(`Selected ${eoaWalletInfo?.name || "wallet"}`);
250
+ if (connectedEOAWallet) {
251
+ setActiveWallet(connectedEOAWallet);
252
+ }
253
+ toast.success(`Selected ${eoaWalletInfo?.name || connector?.name || "wallet"}`);
148
254
  }}
149
255
  className={cn(
150
256
  "crypto-payment-method-connect-wallet eoa-wallet w-full rounded-xl border p-4 text-left transition-all hover:shadow-md",
@@ -160,7 +266,7 @@ export function CryptoPaymentMethod({
160
266
  </div>
161
267
  <div className="flex flex-col">
162
268
  <span className="text-as-primary font-semibold">
163
- {eoaWalletInfo?.name || "Connected Wallet"}
269
+ {eoaWalletInfo?.name || connector?.name || "Connected Wallet"}
164
270
  </span>
165
271
  <span className="text-as-primary/60 text-sm">
166
272
  {shortenAddress(connectedEOAWallet?.getAccount()?.address || "")}
@@ -174,9 +280,62 @@ export function CryptoPaymentMethod({
174
280
  <button
175
281
  onClick={e => {
176
282
  e.stopPropagation();
177
- if (connectedEOAWallet) {
178
- disconnect(connectedEOAWallet);
283
+ disconnect();
284
+ toast.success("Wallet disconnected");
285
+ if (selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET) {
286
+ setSelectedPaymentMethod(CryptoPaymentMethodType.NONE);
179
287
  }
288
+ }}
289
+ className="text-as-primary/60 hover:text-as-primary/80 rounded-lg p-1.5 transition-colors"
290
+ >
291
+ <X className="h-4 w-4" />
292
+ </button>
293
+ </div>
294
+ </div>
295
+ </button>
296
+ )}
297
+
298
+ {shouldShowWagmiWallet && (
299
+ <button
300
+ onClick={async () => {
301
+ setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
302
+ onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
303
+
304
+ // Create thirdweb wallet from wagmi connector
305
+ if (connector?.name) {
306
+ const thirdwebWallet = await createThirdwebWalletFromConnector(connector.name);
307
+ if (thirdwebWallet) {
308
+ setActiveWallet(thirdwebWallet);
309
+ }
310
+ }
311
+
312
+ toast.success(`Selected ${connector?.name || "wallet"}`);
313
+ }}
314
+ className={cn(
315
+ "crypto-payment-method-connect-wallet wagmi-wallet w-full rounded-xl border p-4 text-left transition-all hover:shadow-md",
316
+ selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET
317
+ ? "connected-wallet border-as-brand bg-as-brand/5"
318
+ : "border-as-border-secondary bg-as-surface-primary hover:border-as-secondary/80",
319
+ )}
320
+ >
321
+ <div className="flex items-center justify-between">
322
+ <div className="flex items-center gap-3">
323
+ <div className="wallet-icon flex h-10 w-10 items-center justify-center rounded-full bg-blue-100">
324
+ <Wallet className="h-5 w-5 text-blue-600" />
325
+ </div>
326
+ <div className="flex flex-col">
327
+ <span className="text-as-primary font-semibold">{connector?.name || "Connected Wallet"}</span>
328
+ <span className="text-as-primary/60 text-sm">{shortenAddress(address || "")}</span>
329
+ </div>
330
+ </div>
331
+ <div className="flex items-center gap-2">
332
+ {selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET && (
333
+ <div className="h-2 w-2 rounded-full bg-green-500"></div>
334
+ )}
335
+ <button
336
+ onClick={e => {
337
+ e.stopPropagation();
338
+ disconnect();
180
339
  toast.success("Wallet disconnected");
181
340
  if (selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET) {
182
341
  setSelectedPaymentMethod(CryptoPaymentMethodType.NONE);
@@ -239,7 +398,10 @@ export function CryptoPaymentMethod({
239
398
  <div className="space-y-3">
240
399
  {/* Connect Wallet Section */}
241
400
  <button
242
- onClick={handleConnectWallet}
401
+ onClick={() => {
402
+ // Always show wallet selection modal first
403
+ setShowWalletModal(true);
404
+ }}
243
405
  className="crypto-payment-method-connect-wallet bg-as-surface-primary border-as-border-secondary hover:border-as-secondary/80 group flex w-full items-center justify-between gap-4 rounded-xl border px-4 py-3.5 transition-all duration-200 hover:shadow-md"
244
406
  >
245
407
  <div className="flex items-center gap-3">
@@ -276,6 +438,75 @@ export function CryptoPaymentMethod({
276
438
  </div>
277
439
  </div>
278
440
  </div>
441
+
442
+ {/* Wallet Connection Modal */}
443
+ {showWalletModal &&
444
+ createPortal(
445
+ <div className="wallet-connection-modal pointer-events-auto fixed inset-0 z-[9999] flex items-center justify-center bg-black/50">
446
+ <div className="max-h-[80vh] w-[400px] max-w-[90vw] overflow-auto rounded-xl bg-white p-6 dark:bg-gray-900">
447
+ <div className="mb-4 flex items-center justify-between">
448
+ <h3 className="text-lg font-semibold text-gray-900 dark:text-white">
449
+ {isConnected ? "Switch wallet or account" : "Choose wallet to connect"}
450
+ </h3>
451
+ <button
452
+ onClick={handleCloseModal}
453
+ className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
454
+ >
455
+ <X className="h-5 w-5" />
456
+ </button>
457
+ </div>
458
+
459
+ <div className="space-y-4">
460
+ {/* Custom wallet options */}
461
+ <div className="space-y-3">
462
+ {walletOptions.map(walletOption => {
463
+ const isCurrentWallet = isConnected && connector?.name === walletOption.connector?.name;
464
+
465
+ return (
466
+ <button
467
+ key={walletOption.id}
468
+ onClick={async () => {
469
+ handleCloseModal();
470
+ await requestWalletPermissions(walletOption.connector);
471
+ }}
472
+ disabled={isPending}
473
+ className={`wallet-option w-full rounded-xl border p-4 text-left transition-all hover:shadow-md disabled:opacity-50 ${
474
+ isCurrentWallet
475
+ ? "wallet-option--active border-blue-500 bg-blue-50 dark:bg-blue-900/20"
476
+ : "border-gray-200 bg-white hover:border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:hover:border-gray-500"
477
+ }`}
478
+ >
479
+ <div className="flex items-center justify-between">
480
+ <div className="flex items-center gap-3">
481
+ {walletOption.icon}
482
+
483
+ <div>
484
+ <div className="wallet-option-name flex items-center gap-2">
485
+ <div className="text-sm font-semibold text-gray-900 dark:text-white">
486
+ {walletOption.name}
487
+ </div>
488
+ {isCurrentWallet && (
489
+ <span className="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-800 dark:text-blue-200">
490
+ Connected
491
+ </span>
492
+ )}
493
+ </div>
494
+ <div className="text-xs text-gray-500 dark:text-gray-400">
495
+ {isCurrentWallet ? "Switch account or reconnect" : walletOption.description}
496
+ </div>
497
+ </div>
498
+ </div>
499
+ <ChevronRightCircle className="h-5 w-5 text-gray-400" />
500
+ </div>
501
+ </button>
502
+ );
503
+ })}
504
+ </div>
505
+ </div>
506
+ </div>
507
+ </div>,
508
+ typeof window !== "undefined" ? document.getElementById("b3-root") || document.body : document.body,
509
+ )}
279
510
  </div>
280
511
  );
281
512
  }
@@ -24,6 +24,7 @@ import { components } from "../../types/api";
24
24
  import { CryptoPaymentMethodType } from "../components/common/CryptoPaymentMethod";
25
25
  import { FiatPaymentMethod } from "../components/common/FiatPaymentMethod";
26
26
  import { useAutoSelectCryptoPaymentMethod } from "./useAutoSelectCryptoPaymentMethod";
27
+ import { useAutoSetActiveWalletFromWagmi } from "./useAutoSetActiveWalletFromWagmi";
27
28
  import { useConnectedWalletDisplay } from "./useConnectedWalletDisplay";
28
29
  import { useCryptoPaymentMethodState } from "./useCryptoPaymentMethodState";
29
30
  import { useRecipientAddressState } from "./useRecipientAddressState";
@@ -110,6 +111,9 @@ export function useAnyspendFlow({
110
111
  const { address: globalAddress } = useAccountWallet();
111
112
  const { walletAddress } = useConnectedWalletDisplay(effectiveCryptoPaymentMethod);
112
113
 
114
+ // Auto-set active wallet from wagmi
115
+ useAutoSetActiveWalletFromWagmi();
116
+
113
117
  // Recipient address state - hook automatically manages priority: props > user selection > wallet/global
114
118
  const { setSelectedRecipientAddress, effectiveRecipientAddress } = useRecipientAddressState({
115
119
  recipientAddressFromProps: recipientAddress,
@@ -0,0 +1,80 @@
1
+ import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ import { useSetActiveWallet } from "thirdweb/react";
4
+ import { WalletId, createWallet } from "thirdweb/wallets";
5
+ import { useAccount } from "wagmi";
6
+
7
+ /**
8
+ * Hook that automatically sets the active thirdweb wallet when a wagmi wallet connects.
9
+ *
10
+ * This is useful for syncing wagmi wallet connections with thirdweb's wallet system,
11
+ * ensuring that when users connect via wagmi, the active wallet is properly set.
12
+ *
13
+ * Place this hook in components that stay mounted throughout the user flow
14
+ * (not in components that unmount during navigation).
15
+ */
16
+ export function useAutoSetActiveWalletFromWagmi() {
17
+ const { address: wagmiAddress, connector: wagmiConnector } = useAccount();
18
+ const setActiveWallet = useSetActiveWallet();
19
+ const prevWagmiAddress = useRef<string | undefined>(undefined);
20
+
21
+ // Map wagmi connector names to thirdweb wallet IDs
22
+ const getThirdwebWalletId = useCallback((connectorName: string): WalletId | null => {
23
+ const walletMap: Record<string, WalletId> = {
24
+ MetaMask: "io.metamask",
25
+ "Coinbase Wallet": "com.coinbase.wallet",
26
+ Rainbow: "me.rainbow",
27
+ WalletConnect: "walletConnect",
28
+ Phantom: "app.phantom",
29
+ };
30
+ return walletMap[connectorName] || null;
31
+ }, []);
32
+
33
+ // Create thirdweb wallet from wagmi connector
34
+ const createThirdwebWalletFromConnector = useCallback(
35
+ async (connectorName: string) => {
36
+ const walletId = getThirdwebWalletId(connectorName);
37
+ if (!walletId) {
38
+ console.warn(`No thirdweb wallet ID found for connector: ${connectorName}`);
39
+ return null;
40
+ }
41
+
42
+ try {
43
+ const thirdwebWallet = createWallet(walletId);
44
+ await thirdwebWallet.connect({ client });
45
+ return thirdwebWallet;
46
+ } catch (error) {
47
+ console.error(`Failed to create thirdweb wallet for ${connectorName}:`, error);
48
+ return null;
49
+ }
50
+ },
51
+ [getThirdwebWalletId],
52
+ );
53
+
54
+ // Listen for wagmi wallet connections and automatically set active wallet
55
+ useEffect(() => {
56
+ const isNewConnection = wagmiAddress && wagmiAddress !== prevWagmiAddress.current;
57
+
58
+ if (isNewConnection && wagmiConnector?.name) {
59
+ prevWagmiAddress.current = wagmiAddress;
60
+
61
+ const setupThirdwebWallet = async () => {
62
+ try {
63
+ const thirdwebWallet = await createThirdwebWalletFromConnector(wagmiConnector.name);
64
+ if (thirdwebWallet) {
65
+ setActiveWallet(thirdwebWallet);
66
+ console.log(`Auto-set active wallet for ${wagmiConnector.name}`);
67
+ }
68
+ } catch (error) {
69
+ console.error("Failed to auto-set active wallet:", error);
70
+ }
71
+ };
72
+
73
+ setupThirdwebWallet();
74
+ }
75
+
76
+ if (!wagmiAddress) {
77
+ prevWagmiAddress.current = undefined;
78
+ }
79
+ }, [wagmiAddress, wagmiConnector?.name, setActiveWallet, createThirdwebWalletFromConnector]);
80
+ }
@@ -10,6 +10,7 @@ import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
10
10
  import { useCallback, useEffect, useState } from "react";
11
11
  import { useActiveAccount } from "thirdweb/react";
12
12
  import { Account } from "thirdweb/wallets";
13
+ import { TurnkeyAuthModal } from "../TurnkeyAuthModal";
13
14
  import { SignInWithB3Privy } from "./SignInWithB3Privy";
14
15
  import { LoginStep, LoginStepContainer } from "./steps/LoginStep";
15
16
  import { LoginStepCustom } from "./steps/LoginStepCustom";
@@ -40,7 +41,9 @@ export function SignInWithB3Flow({
40
41
  const account = useActiveAccount();
41
42
  const isAuthenticating = useAuthStore(state => state.isAuthenticating);
42
43
  const isAuthenticated = useAuthStore(state => state.isAuthenticated);
44
+ const setIsAuthenticated = useAuthStore(state => state.setIsAuthenticated);
43
45
  const isConnected = useAuthStore(state => state.isConnected);
46
+ const setIsConnected = useAuthStore(state => state.setIsConnected);
44
47
  const setJustCompletedLogin = useAuthStore(state => state.setJustCompletedLogin);
45
48
  const [refetchCount, setRefetchCount] = useState(0);
46
49
  const [refetchError, setRefetchError] = useState<string | null>(null);
@@ -154,6 +157,11 @@ export function SignInWithB3Flow({
154
157
  await refetchUser();
155
158
  debug("User refetched successfully");
156
159
 
160
+ // Set authentication and connection state so UI updates properly
161
+ setIsAuthenticated(true);
162
+ setIsConnected(true);
163
+ setJustCompletedLogin(true);
164
+
157
165
  // After user data is refreshed, close Turnkey modal and go back to sign-in flow
158
166
  debug("Switching back to signInWithB3 modal");
159
167
  setB3ModalContentType({
@@ -184,6 +192,9 @@ export function SignInWithB3Flow({
184
192
  closeAfterLogin,
185
193
  source,
186
194
  signersEnabled,
195
+ setIsAuthenticated,
196
+ setIsConnected,
197
+ setJustCompletedLogin,
187
198
  ],
188
199
  );
189
200
 
@@ -340,32 +351,76 @@ export function SignInWithB3Flow({
340
351
  <div className="p-4 text-center text-red-500">{refetchError}</div>
341
352
  </LoginStepContainer>
342
353
  );
343
- } else if (isAuthenticating || (isFetchingSigners && step === "login") || source === "requestPermissions") {
344
- content = (
345
- <LoginStepContainer partnerId={partnerId}>
346
- <div className="my-8 flex min-h-[350px] items-center justify-center">
347
- <Loading variant="white" size="lg" />
348
- </div>
349
- </LoginStepContainer>
350
- );
351
354
  } else if (step === "login") {
352
- // Custom strategy
353
- if (strategies?.[0] === "privy") {
354
- content = <SignInWithB3Privy onSuccess={handleLoginSuccess} chain={chain} />;
355
- } else if (strategies) {
356
- // Strategies are explicitly provided
355
+ // PRIORITY: If Turnkey is enabled, show Turnkey modal FIRST as the primary authentication option
356
+ // Show Turnkey when enabled and not already completed in this session
357
+ const shouldShowTurnkeyFirst = enableTurnkey && !turnkeyAuthCompleted;
358
+
359
+ if (shouldShowTurnkeyFirst) {
360
+ // Don't show loading spinner for Turnkey - let the modal handle its own loading state
361
+ // This prevents the infinite loop where isAuthenticating causes the modal to be replaced
362
+ debug("Showing Turnkey as primary authentication option", {
363
+ enableTurnkey,
364
+ turnkeyAuthCompleted,
365
+ isAuthenticated,
366
+ });
367
+ // Show Turnkey authentication as primary option
357
368
  content = (
358
- <LoginStepCustom
359
- strategies={strategies}
360
- chain={chain}
361
- onSuccess={handleLoginSuccess}
362
- onError={onError}
363
- automaticallySetFirstEoa={!!automaticallySetFirstEoa}
364
- />
369
+ <LoginStepContainer partnerId={partnerId}>
370
+ <TurnkeyAuthModal
371
+ onSuccess={async (authenticatedUser: any) => {
372
+ debug("Turnkey authentication successful in primary flow", { authenticatedUser });
373
+ setTurnkeyAuthCompleted(true);
374
+ // After Turnkey auth, refetch user to get the full user object
375
+ await refetchUser();
376
+ // User is now authenticated via Turnkey
377
+ // Set both isAuthenticated and isConnected to true so UI updates properly
378
+ // Wallet connection is optional and can happen later for signing transactions
379
+ setIsAuthenticated(true);
380
+ setIsConnected(true);
381
+ setJustCompletedLogin(true);
382
+ // Call the login success callback
383
+ onLoginSuccess?.({} as Account);
384
+ }}
385
+ onClose={() => {
386
+ // If user closes Turnkey modal, they can still use wallet connection as fallback
387
+ setTurnkeyAuthCompleted(true);
388
+ }}
389
+ initialEmail=""
390
+ skipToOtp={false}
391
+ />
392
+ </LoginStepContainer>
365
393
  );
366
394
  } else {
367
- // Default to handle all strategies we support
368
- content = <LoginStep chain={chain} onSuccess={handleLoginSuccess} onError={onError} />;
395
+ // Show loading spinner only if not in Turnkey flow
396
+ if (isAuthenticating || (isFetchingSigners && step === "login") || source === "requestPermissions") {
397
+ content = (
398
+ <LoginStepContainer partnerId={partnerId}>
399
+ <div className="my-8 flex min-h-[350px] items-center justify-center">
400
+ <Loading variant="white" size="lg" />
401
+ </div>
402
+ </LoginStepContainer>
403
+ );
404
+ } else {
405
+ // Custom strategy
406
+ if (strategies?.[0] === "privy") {
407
+ content = <SignInWithB3Privy onSuccess={handleLoginSuccess} chain={chain} />;
408
+ } else if (strategies) {
409
+ // Strategies are explicitly provided
410
+ content = (
411
+ <LoginStepCustom
412
+ strategies={strategies}
413
+ chain={chain}
414
+ onSuccess={handleLoginSuccess}
415
+ onError={onError}
416
+ automaticallySetFirstEoa={!!automaticallySetFirstEoa}
417
+ />
418
+ );
419
+ } else {
420
+ // Default to handle all strategies we support
421
+ content = <LoginStep chain={chain} onSuccess={handleLoginSuccess} onError={onError} />;
422
+ }
423
+ }
369
424
  }
370
425
  }
371
426
 
@@ -3,6 +3,7 @@ export { useAccountAssets } from "./useAccountAssets";
3
3
  export { useAccountWallet } from "./useAccountWallet";
4
4
  export { useAddTWSessionKey } from "./useAddTWSessionKey";
5
5
  export { useAnalytics } from "./useAnalytics";
6
+ export { useAuth } from "./useAuth";
6
7
  export { useAuthentication } from "./useAuthentication";
7
8
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
8
9
  export { useB3EnsName } from "./useB3EnsName";