@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.
- package/dist/cjs/anyspend/react/components/AnySpend.js +3 -0
- package/dist/cjs/anyspend/react/components/AnySpendCustom.js +3 -0
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +177 -47
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +3 -0
- package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
- package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +73 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +57 -12
- package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/index.js +3 -1
- package/dist/cjs/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/cjs/global-account/react/hooks/useAuth.js +338 -0
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +72 -63
- package/dist/cjs/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/dist/cjs/global-account/react/hooks/useTWAuth.js +8 -0
- package/dist/cjs/global-account/react/hooks/useTurnkeyAuth.js +54 -24
- package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +3 -0
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -0
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +175 -45
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +3 -0
- package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
- package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +70 -0
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +57 -12
- package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/index.js +1 -0
- package/dist/esm/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/esm/global-account/react/hooks/useAuth.js +332 -0
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/esm/global-account/react/hooks/useAuthentication.js +72 -63
- package/dist/esm/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/dist/esm/global-account/react/hooks/useTWAuth.js +8 -0
- package/dist/esm/global-account/react/hooks/useTurnkeyAuth.js +54 -24
- package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
- package/dist/types/global-account/react/hooks/index.d.ts +1 -0
- package/dist/types/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/types/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/types/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/dist/types/global-account/react/hooks/useUserQuery.d.ts +1 -1
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +4 -0
- package/src/anyspend/react/components/AnySpendCustom.tsx +4 -0
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +253 -22
- package/src/anyspend/react/hooks/useAnyspendFlow.ts +4 -0
- package/src/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.ts +80 -0
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +77 -22
- package/src/global-account/react/hooks/index.ts +1 -0
- package/src/global-account/react/hooks/useAuth.ts +380 -0
- package/src/global-account/react/hooks/useAuthentication.ts +88 -85
- package/src/global-account/react/hooks/useTWAuth.tsx +10 -0
- package/src/global-account/react/hooks/useTurnkeyAuth.ts +59 -26
package/package.json
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
-
//
|
|
44
|
-
const
|
|
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
|
-
//
|
|
47
|
-
if (
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
154
|
+
} else {
|
|
155
|
+
// Fallback: show modal for manual wallet selection
|
|
156
|
+
setShowWalletModal(true);
|
|
56
157
|
}
|
|
57
158
|
} catch (error) {
|
|
58
|
-
console.error("Failed to
|
|
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
|
-
|
|
167
|
+
toast.error("Account selection cancelled");
|
|
67
168
|
} else {
|
|
68
|
-
toast.error("Failed to
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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={
|
|
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
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
//
|
|
368
|
-
|
|
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";
|