@b3dotfun/sdk 0.1.68-alpha.7 → 0.1.68-alpha.9
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 -10
- package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +1 -1
- package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.js +32 -3
- package/dist/cjs/anyspend/react/components/checkout/KycGate.js +32 -10
- package/dist/cjs/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +7 -3
- package/dist/cjs/anyspend/react/hooks/useKycStatus.d.ts +5 -0
- package/dist/cjs/anyspend/react/hooks/useKycStatus.js +11 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/SignIn.js +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +13 -5
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Privy.js +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStep.js +21 -24
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepCustom.js +1 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +3 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +51 -7
- package/dist/cjs/global-account/react/hooks/useGetAllTWSigners.js +2 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +3 -10
- package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +1 -1
- package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.js +34 -5
- package/dist/esm/anyspend/react/components/checkout/KycGate.js +35 -13
- package/dist/esm/anyspend/react/hooks/useAnyspendCreateOnrampOrder.js +8 -4
- package/dist/esm/anyspend/react/hooks/useKycStatus.d.ts +5 -0
- package/dist/esm/anyspend/react/hooks/useKycStatus.js +10 -0
- package/dist/esm/global-account/react/components/SignInWithB3/SignIn.js +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +13 -5
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Privy.js +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStep.js +21 -24
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepCustom.js +1 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +3 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.js +51 -7
- package/dist/esm/global-account/react/hooks/useGetAllTWSigners.js +2 -1
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/hooks/useKycStatus.d.ts +5 -0
- package/dist/types/global-account/react/components/SignInWithB3/steps/LoginStep.d.ts +1 -1
- package/dist/types/global-account/react/hooks/useAuthentication.d.ts +3 -1
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +3 -11
- package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +4 -13
- package/src/anyspend/react/components/checkout/FiatCheckoutPanel.tsx +62 -4
- package/src/anyspend/react/components/checkout/KycGate.tsx +61 -25
- package/src/anyspend/react/hooks/useAnyspendCreateOnrampOrder.ts +8 -4
- package/src/anyspend/react/hooks/useKycStatus.ts +10 -0
- package/src/global-account/react/components/SignInWithB3/SignIn.tsx +1 -1
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +13 -5
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Privy.tsx +1 -1
- package/src/global-account/react/components/SignInWithB3/steps/LoginStep.tsx +35 -25
- package/src/global-account/react/components/SignInWithB3/steps/LoginStepCustom.tsx +1 -1
- package/src/global-account/react/hooks/useAuthentication.ts +51 -8
- package/src/global-account/react/hooks/useGetAllTWSigners.tsx +2 -1
|
@@ -38,7 +38,6 @@ const GasIndicator_1 = require("./common/GasIndicator");
|
|
|
38
38
|
const OrderDetails_1 = require("./common/OrderDetails");
|
|
39
39
|
const OrderHistory_1 = require("./common/OrderHistory");
|
|
40
40
|
const KycGate_1 = require("./checkout/KycGate");
|
|
41
|
-
const useKycStatus_1 = require("../hooks/useKycStatus");
|
|
42
41
|
const LoginStep_1 = require("../../../global-account/react/components/SignInWithB3/steps/LoginStep");
|
|
43
42
|
const PanelOnramp_1 = require("./common/PanelOnramp");
|
|
44
43
|
const PanelOnrampPayment_1 = require("./common/PanelOnrampPayment");
|
|
@@ -82,10 +81,6 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
82
81
|
// in the same frame that onStatusResolved sets it (setState is async).
|
|
83
82
|
// When kycEnabled is false (default), pre-approve so the KYC gate is skipped.
|
|
84
83
|
const kycApprovedRef = (0, react_4.useRef)(!kycEnabled);
|
|
85
|
-
// Pre-warm wallet auth headers inside user-gesture context (button click)
|
|
86
|
-
// so the signing prompt fires before we navigate away — browsers block
|
|
87
|
-
// wallet popups triggered from async/non-gesture contexts (React Query queryFn).
|
|
88
|
-
const { getHeaders: getKycHeaders } = (0, useKycStatus_1.useWalletAuthHeaders)();
|
|
89
84
|
// Determine if we're in "buy mode" based on whether destination token props are provided
|
|
90
85
|
const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId);
|
|
91
86
|
// Add refs to track URL state
|
|
@@ -852,11 +847,9 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
852
847
|
return;
|
|
853
848
|
}
|
|
854
849
|
if (!kycApprovedRef.current) {
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
// (non-gesture) contexts, which is exactly what React Query uses.
|
|
859
|
-
await getKycHeaders().catch(() => { });
|
|
850
|
+
// Navigate to KYC gate. KycGate shows an explicit "Continue to Verify"
|
|
851
|
+
// CTA and pre-signs from that user-gesture context before enabling
|
|
852
|
+
// the useKycStatus query — so no surprise signature popup here.
|
|
860
853
|
navigateToPanel(PanelView.FIAT_KYC, "forward");
|
|
861
854
|
return;
|
|
862
855
|
}
|
|
@@ -320,5 +320,5 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
320
320
|
? "Creating order..."
|
|
321
321
|
: isSendingDeposit
|
|
322
322
|
? "Confirm in wallet..."
|
|
323
|
-
: "Confirming transaction..."] })) : (buttonText) })), isMobile && hasWalletConnector ? ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setQrExpanded(prev => !prev), className: "flex w-full items-center gap-3 py-1", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" }), (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1 text-xs font-medium text-gray-400 dark:text-gray-500", children: [qrExpanded ? ("or send directly") : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.QrCode, { className: "h-3 w-3" }), " or send with QR code"] })), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: (0, cn_1.cn)("h-3 w-3 transition-transform", qrExpanded && "rotate-180") })] }), (0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 py-1", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-gray-400 dark:text-gray-500", children: "or send directly" }), (0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" })] })), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { initial: false, children: qrExpanded && ((0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: "auto", opacity: 1 }, exit: { height: 0, opacity: 0 }, transition: { duration: 0.25, ease: "easeInOut" }, className: "overflow-hidden", children: isCreatingQrOrder && !globalAddress ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center py-8", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }), (0, jsx_runtime_1.jsx)("span", { className: "ml-2 text-sm text-gray-500 dark:text-gray-400", children: "Creating deposit address..." })] })) : globalAddress ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "shrink-0 rounded-xl bg-white p-2.5 shadow-sm ring-1 ring-gray-100 dark:bg-white dark:ring-gray-200", children: (0, jsx_runtime_1.jsx)(qrcode_react_1.QRCodeSVG, { value: qrValue, size: 132, level: "M", marginSize: 0 }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2.5", children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: ["Send", " ", (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol] }), " ", "on", " ", (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold text-gray-900 dark:text-gray-100", children: [chainLogoUrl && (
|
|
323
|
+
: "Confirming transaction..."] })) : (buttonText) })), isMobile && hasWalletConnector ? ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setQrExpanded(prev => !prev), className: "flex w-full items-center gap-3 py-1", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" }), (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1 text-xs font-medium text-gray-400 dark:text-gray-500", children: [qrExpanded ? ("or send directly") : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_1.QrCode, { className: "h-3 w-3" }), " or send with QR code"] })), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: (0, cn_1.cn)("h-3 w-3 transition-transform", qrExpanded && "rotate-180") })] }), (0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 py-1", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-gray-400 dark:text-gray-500", children: "or send directly" }), (0, jsx_runtime_1.jsx)("div", { className: "h-px flex-1 bg-gray-200 dark:bg-neutral-700" })] })), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { initial: false, children: qrExpanded && ((0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { height: 0, opacity: 0 }, animate: { height: "auto", opacity: 1 }, exit: { height: 0, opacity: 0 }, transition: { duration: 0.25, ease: "easeInOut" }, className: "overflow-hidden", children: isCreatingQrOrder && !globalAddress ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center py-8", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }), (0, jsx_runtime_1.jsx)("span", { className: "ml-2 text-sm text-gray-500 dark:text-gray-400", children: "Creating deposit address..." })] })) : globalAddress ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "shrink-0 rounded-xl bg-white p-2.5 shadow-sm ring-1 ring-gray-100 dark:bg-white dark:ring-gray-200", children: (0, jsx_runtime_1.jsx)(qrcode_react_1.QRCodeSVG, { value: qrValue, size: 132, level: "M", marginSize: 0 }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2.5", children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: ["Send", " ", (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol] }), " ", "on", " ", (0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1 rounded-full bg-gray-100 px-1.5 py-0.5 align-middle font-semibold text-gray-900 dark:bg-white/10 dark:text-gray-100", children: [chainLogoUrl && (0, jsx_runtime_1.jsx)("img", { src: chainLogoUrl, alt: "", className: "h-3.5 w-3.5 rounded-full" }), chainName] }), " ", "to:"] }), (0, jsx_runtime_1.jsxs)("button", { onClick: handleCopyAddress, className: "group flex items-start gap-1.5 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 text-left transition-colors hover:border-gray-300 hover:bg-gray-100 dark:border-neutral-700 dark:bg-neutral-800/60 dark:hover:border-neutral-600 dark:hover:bg-neutral-800", children: [(0, jsx_runtime_1.jsx)("span", { className: "min-w-0 break-all font-mono text-xs leading-relaxed text-gray-800 dark:text-gray-200", children: globalAddress }), (0, jsx_runtime_1.jsx)("span", { className: "mt-0.5 shrink-0 text-gray-400 transition-colors group-hover:text-gray-600 dark:group-hover:text-gray-300", children: copied ? (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "h-3.5 w-3.5 text-green-500" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "h-3.5 w-3.5" }) })] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs leading-snug text-orange-500/80 dark:text-orange-400/80", children: ["Only send ", selectedSrcToken?.symbol, " on ", (0, jsx_runtime_1.jsx)("span", { className: "font-semibold", children: chainName }), ". Sending other tokens or using a different network may result in loss of funds."] })] })] })) : null }, "qr-section")) })] }));
|
|
324
324
|
}
|
|
@@ -9,17 +9,26 @@ const cn_1 = require("../../../../shared/utils/cn");
|
|
|
9
9
|
const number_1 = require("../../../../shared/utils/number");
|
|
10
10
|
const payment_utils_1 = require("../../../../shared/utils/payment.utils");
|
|
11
11
|
const react_2 = require("../../../../global-account/react");
|
|
12
|
+
const b3Chain_1 = require("../../../../shared/constants/chains/b3Chain");
|
|
12
13
|
const react_stripe_js_1 = require("@stripe/react-stripe-js");
|
|
13
14
|
const lucide_react_1 = require("lucide-react");
|
|
14
15
|
const react_3 = require("motion/react");
|
|
15
16
|
const react_4 = require("react");
|
|
17
|
+
const wagmi_1 = require("wagmi");
|
|
16
18
|
const KycGate_1 = require("./KycGate");
|
|
17
19
|
function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinationTokenChainId, totalAmount, themeColor, onSuccess, onOrderCreated, onError, callbackMetadata, classes, feeOnTop, kycEnabled = false, }) {
|
|
18
20
|
// Stable refs for callback props to avoid re-triggering effects
|
|
19
21
|
const onErrorRef = (0, react_4.useRef)(onError);
|
|
20
22
|
onErrorRef.current = onError;
|
|
23
|
+
const { address } = (0, wagmi_1.useAccount)();
|
|
24
|
+
const setB3ModalOpen = (0, react_2.useModalStore)(state => state.setB3ModalOpen);
|
|
25
|
+
const setB3ModalContentType = (0, react_2.useModalStore)(state => state.setB3ModalContentType);
|
|
21
26
|
const { data: tokenData } = (0, react_2.useTokenData)(destinationTokenChainId, destinationTokenAddress);
|
|
22
|
-
const { theme, stripePublishableKey } = (0, react_2.useB3Config)();
|
|
27
|
+
const { theme, stripePublishableKey, partnerId } = (0, react_2.useB3Config)();
|
|
28
|
+
const handleConnectWallet = (0, react_4.useCallback)(() => {
|
|
29
|
+
setB3ModalContentType({ type: "signInWithB3", showBackButton: false, chain: b3Chain_1.thirdwebB3Chain, partnerId });
|
|
30
|
+
setB3ModalOpen(true);
|
|
31
|
+
}, [setB3ModalContentType, setB3ModalOpen, partnerId]);
|
|
23
32
|
// Clean decimal string for API calls (no commas, no subscripts)
|
|
24
33
|
const formattedAmount = (0, react_4.useMemo)(() => {
|
|
25
34
|
const decimals = tokenData?.decimals || 18;
|
|
@@ -76,13 +85,28 @@ function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinat
|
|
|
76
85
|
}
|
|
77
86
|
},
|
|
78
87
|
onError: (error) => {
|
|
79
|
-
|
|
88
|
+
// Backend requires KYC even when kycEnabled=false — reset to show KycGate
|
|
89
|
+
if (error.message?.includes("KYC verification required")) {
|
|
90
|
+
setKycApproved(false);
|
|
91
|
+
orderCreatedRef.current = false;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
setOrderError(error.message || "Failed to create payment order.");
|
|
95
|
+
}
|
|
80
96
|
onErrorRef.current?.(error);
|
|
81
97
|
},
|
|
82
98
|
});
|
|
99
|
+
// Reset order error when wallet connects so the user gets a clean state
|
|
100
|
+
(0, react_4.useEffect)(() => {
|
|
101
|
+
if (address && orderError) {
|
|
102
|
+
setOrderError(null);
|
|
103
|
+
orderCreatedRef.current = false;
|
|
104
|
+
}
|
|
105
|
+
}, [address]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
83
106
|
// Auto-create onramp order when Stripe Web2 is supported, KYC approved, and all data is ready
|
|
84
107
|
(0, react_4.useEffect)(() => {
|
|
85
|
-
if (
|
|
108
|
+
if (address &&
|
|
109
|
+
!isLoadingGeo &&
|
|
86
110
|
(!isStablecoin ? !isLoadingAnyspendQuote : true) &&
|
|
87
111
|
usdAmount &&
|
|
88
112
|
parseFloat(usdAmount) > 0 &&
|
|
@@ -123,6 +147,7 @@ function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinat
|
|
|
123
147
|
});
|
|
124
148
|
}
|
|
125
149
|
}, [
|
|
150
|
+
address,
|
|
126
151
|
isLoadingGeo,
|
|
127
152
|
isStablecoin,
|
|
128
153
|
isLoadingAnyspendQuote,
|
|
@@ -156,6 +181,10 @@ function FiatCheckoutPanel({ recipientAddress, destinationTokenAddress, destinat
|
|
|
156
181
|
if (!kycApproved) {
|
|
157
182
|
return (0, jsx_runtime_1.jsx)(KycGate_1.KycGate, { themeColor: themeColor, classes: classes, enabled: true, onStatusResolved: handleKycResolved });
|
|
158
183
|
}
|
|
184
|
+
// No wallet connected — prompt to connect before attempting card payment
|
|
185
|
+
if (!address) {
|
|
186
|
+
return ((0, jsx_runtime_1.jsxs)(react_3.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("anyspend-fiat-connect flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "h-8 w-8 text-gray-400" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Connect wallet to pay with card" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "A wallet connection is required to complete card payment." })] }), (0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleConnectWallet, children: (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "h-4 w-4" }), "Connect Wallet"] }) })] }));
|
|
187
|
+
}
|
|
159
188
|
// Order creation error - show with retry
|
|
160
189
|
if (orderError) {
|
|
161
190
|
return ((0, jsx_runtime_1.jsxs)(react_3.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("anyspend-fiat-error flex flex-col items-center gap-3 py-4", classes?.fiatPanel), children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm text-red-500", children: orderError }), (0, jsx_runtime_1.jsx)("button", { onClick: () => {
|
|
@@ -42,19 +42,29 @@ const b3Chain_1 = require("../../../../shared/constants/chains/b3Chain");
|
|
|
42
42
|
const lucide_react_1 = require("lucide-react");
|
|
43
43
|
const react_2 = require("motion/react");
|
|
44
44
|
const react_3 = require("react");
|
|
45
|
+
const wagmi_1 = require("wagmi");
|
|
45
46
|
const useKycStatus_1 = require("../../hooks/useKycStatus");
|
|
46
47
|
function KycGate({ themeColor, classes, enabled = false, onStatusResolved }) {
|
|
47
|
-
const {
|
|
48
|
-
const {
|
|
48
|
+
const { address } = (0, wagmi_1.useAccount)();
|
|
49
|
+
const { partnerId } = (0, react_1.useB3Config)();
|
|
50
|
+
// Gate the status fetch behind explicit user consent so the wallet
|
|
51
|
+
// signature prompt doesn't fire automatically on tab open.
|
|
52
|
+
const [userInitiated, setUserInitiated] = (0, react_3.useState)(false);
|
|
53
|
+
const { getHeaders: preCacheKycHeaders } = (0, useKycStatus_1.useWalletAuthHeaders)();
|
|
54
|
+
const { kycStatus, isLoadingKycStatus, refetchKycStatus } = (0, useKycStatus_1.useKycStatus)(enabled && userInitiated);
|
|
49
55
|
const { createInquiry, isCreatingInquiry } = (0, useKycStatus_1.useCreateKycInquiry)();
|
|
50
56
|
const { verifyKyc, isVerifying } = (0, useKycStatus_1.useVerifyKyc)();
|
|
51
57
|
const setB3ModalOpen = (0, react_1.useModalStore)(state => state.setB3ModalOpen);
|
|
52
58
|
const setB3ModalContentType = (0, react_1.useModalStore)(state => state.setB3ModalContentType);
|
|
53
|
-
const { partnerId } = (0, react_1.useB3Config)();
|
|
54
59
|
const [personaOpen, setPersonaOpen] = (0, react_3.useState)(false);
|
|
55
60
|
const [personaError, setPersonaError] = (0, react_3.useState)(null);
|
|
56
61
|
const [personaCancelled, setPersonaCancelled] = (0, react_3.useState)(false);
|
|
57
62
|
const prevStatusRef = (0, react_3.useRef)(null);
|
|
63
|
+
// Reset consent gate when wallet changes so the signature prompt isn't
|
|
64
|
+
// skipped for a different (or reconnected) wallet with no cached headers.
|
|
65
|
+
(0, react_3.useEffect)(() => {
|
|
66
|
+
setUserInitiated(false);
|
|
67
|
+
}, [address]);
|
|
58
68
|
// Notify parent when status resolves
|
|
59
69
|
(0, react_3.useEffect)(() => {
|
|
60
70
|
if (!kycStatus)
|
|
@@ -133,7 +143,7 @@ function KycGate({ themeColor, classes, enabled = false, onStatusResolved }) {
|
|
|
133
143
|
setPersonaError(error instanceof Error ? error.message : "Failed to start verification");
|
|
134
144
|
}
|
|
135
145
|
}, [createInquiry, kycStatus, openPersonaFlow]);
|
|
136
|
-
const
|
|
146
|
+
const handleConnectWallet = (0, react_3.useCallback)(() => {
|
|
137
147
|
setB3ModalContentType({ type: "signInWithB3", showBackButton: false, chain: b3Chain_1.thirdwebB3Chain, partnerId });
|
|
138
148
|
setB3ModalOpen(true);
|
|
139
149
|
}, [setB3ModalContentType, setB3ModalOpen, partnerId]);
|
|
@@ -148,13 +158,25 @@ function KycGate({ themeColor, classes, enabled = false, onStatusResolved }) {
|
|
|
148
158
|
environment: kycStatus.config?.environment,
|
|
149
159
|
});
|
|
150
160
|
}, [kycStatus, openPersonaFlow]);
|
|
151
|
-
//
|
|
152
|
-
if (
|
|
153
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.
|
|
161
|
+
// No wallet connected — prompt to connect wallet (same pattern as crypto tab)
|
|
162
|
+
if (!address) {
|
|
163
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("anyspend-kyc-auth flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ShieldCheck, { className: "h-8 w-8 text-gray-400" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Connect wallet to pay with card" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "A wallet connection is required to complete identity verification." })] }), (0, jsx_runtime_1.jsx)(react_1.ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: handleConnectWallet, children: (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "h-4 w-4" }), "Connect Wallet"] }) })] }));
|
|
154
164
|
}
|
|
155
|
-
//
|
|
156
|
-
if (!
|
|
157
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("anyspend-kyc-
|
|
165
|
+
// Wallet connected but user hasn't kicked off the KYC check yet
|
|
166
|
+
if (!userInitiated) {
|
|
167
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("anyspend-kyc-prompt flex flex-col items-center gap-4 py-2", classes?.fiatPanel), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ShieldCheck, { className: "h-8 w-8 text-blue-500" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Identity verification required" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "Card payments require a one-time identity check. This takes about 2 minutes." })] }), (0, jsx_runtime_1.jsx)(react_1.ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", className: "w-full", textClassName: "text-white", onClick: async () => {
|
|
168
|
+
// Pre-sign in user-gesture context so React Query's queryFn
|
|
169
|
+
// can reuse the cached headers without a second popup.
|
|
170
|
+
// Only enable the query on success — if the user rejects the
|
|
171
|
+
// signature, leave userInitiated=false and stay on this screen.
|
|
172
|
+
try {
|
|
173
|
+
await preCacheKycHeaders();
|
|
174
|
+
setUserInitiated(true);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// User rejected signature — stay on consent screen
|
|
178
|
+
}
|
|
179
|
+
}, children: (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ShieldCheck, { className: "h-4 w-4" }), "Continue to Verify"] }) })] }));
|
|
158
180
|
}
|
|
159
181
|
// Loading KYC status state
|
|
160
182
|
if (isLoadingKycStatus) {
|
|
@@ -10,6 +10,7 @@ const react_query_1 = require("@tanstack/react-query");
|
|
|
10
10
|
const react_2 = require("react");
|
|
11
11
|
const viem_1 = require("viem");
|
|
12
12
|
const chains_1 = require("viem/chains");
|
|
13
|
+
const wagmi_1 = require("wagmi");
|
|
13
14
|
const useKycStatus_1 = require("./useKycStatus");
|
|
14
15
|
const useValidatedClientReferenceId_1 = require("./useValidatedClientReferenceId");
|
|
15
16
|
/**
|
|
@@ -19,7 +20,7 @@ const useValidatedClientReferenceId_1 = require("./useValidatedClientReferenceId
|
|
|
19
20
|
function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
20
21
|
// Get B3 context values
|
|
21
22
|
const { partnerId } = (0, react_1.useB3Config)();
|
|
22
|
-
const {
|
|
23
|
+
const { address } = (0, wagmi_1.useAccount)();
|
|
23
24
|
// Get validated client reference ID from B3 context
|
|
24
25
|
const createValidatedClientReferenceId = (0, useValidatedClientReferenceId_1.useValidatedClientReferenceId)();
|
|
25
26
|
// Get fingerprint data
|
|
@@ -43,9 +44,12 @@ function useAnyspendCreateOnrampOrder({ onSuccess, onError } = {}) {
|
|
|
43
44
|
const srcAmountOnRampInWei = (0, viem_1.parseUnits)(srcFiatAmount, constants_1.USDC_BASE.decimals);
|
|
44
45
|
// For card payments, include wallet auth headers so the backend can verify
|
|
45
46
|
// KYC by the signing wallet address (may differ from the B3 JWT address).
|
|
47
|
+
// Only use already-cached headers — never trigger a fresh wallet signature
|
|
48
|
+
// here, as that would prompt the user without their explicit consent.
|
|
49
|
+
// KycGate pre-caches the headers in the "Continue to Verify" user-gesture.
|
|
46
50
|
let kycWalletHeaders;
|
|
47
|
-
if (onramp.vendor === "stripe-web2") {
|
|
48
|
-
kycWalletHeaders =
|
|
51
|
+
if (onramp.vendor === "stripe-web2" && address) {
|
|
52
|
+
kycWalletHeaders = (0, useKycStatus_1.getCachedWalletHeaders)(address);
|
|
49
53
|
}
|
|
50
54
|
return await anyspend_1.anyspendService.createOrder({
|
|
51
55
|
recipientAddress: (0, utils_1.normalizeAddress)(recipientAddress),
|
|
@@ -17,6 +17,11 @@ interface KycInquiryResponse {
|
|
|
17
17
|
interface KycVerifyResponse {
|
|
18
18
|
status: string;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns cached wallet auth headers without triggering a wallet signature prompt.
|
|
22
|
+
* Returns undefined if no valid cache exists for the given address.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCachedWalletHeaders(address: string): Record<string, string> | undefined;
|
|
20
25
|
/**
|
|
21
26
|
* Returns a function that builds the wallet-signature auth headers.
|
|
22
27
|
* Caches signatures for 4 minutes (server allows 5-minute window).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
"use client";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getCachedWalletHeaders = getCachedWalletHeaders;
|
|
4
5
|
exports.useWalletAuthHeaders = useWalletAuthHeaders;
|
|
5
6
|
exports.useKycStatus = useKycStatus;
|
|
6
7
|
exports.useCreateKycInquiry = useCreateKycInquiry;
|
|
@@ -14,6 +15,16 @@ function buildWalletAuthMessage(walletAddress, timestamp) {
|
|
|
14
15
|
}
|
|
15
16
|
/** Module-level signature cache to avoid repeated wallet prompts within the 5-minute window. */
|
|
16
17
|
const headerCache = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Returns cached wallet auth headers without triggering a wallet signature prompt.
|
|
20
|
+
* Returns undefined if no valid cache exists for the given address.
|
|
21
|
+
*/
|
|
22
|
+
function getCachedWalletHeaders(address) {
|
|
23
|
+
const cached = headerCache.get(address.toLowerCase());
|
|
24
|
+
if (cached && Date.now() < cached.expiresAt)
|
|
25
|
+
return cached.headers;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
17
28
|
/**
|
|
18
29
|
* Returns a function that builds the wallet-signature auth headers.
|
|
19
30
|
* Caches signatures for 4 minutes (server allows 5-minute window).
|
|
@@ -19,7 +19,7 @@ function SignIn(props) {
|
|
|
19
19
|
const { address: globalAddress, ensName, connectedSmartWallet, connectedEOAWallet, isActiveSmartWallet, isActiveEOAWallet, smartWalletIcon, } = (0, react_1.useAccountWallet)();
|
|
20
20
|
const { data: walletImage } = (0, react_4.useWalletImage)(connectedEOAWallet?.id);
|
|
21
21
|
const isMobile = (0, react_1.useIsMobile)();
|
|
22
|
-
const { logout } = (0, react_1.useAuthentication)(partnerId);
|
|
22
|
+
const { logout } = (0, react_1.useAuthentication)(partnerId, { skipAutoConnect: true });
|
|
23
23
|
const onDisconnect = async () => {
|
|
24
24
|
await logout();
|
|
25
25
|
};
|
|
@@ -17,16 +17,23 @@ const MAX_REFETCH_ATTEMPTS = 20;
|
|
|
17
17
|
*/
|
|
18
18
|
function SignInWithB3Flow({ strategies, onLoginSuccess, onSessionKeySuccess, onError, chain, sessionKeyAddress, partnerId, closeAfterLogin = false, source = "signInWithB3Button", signersEnabled = false, }) {
|
|
19
19
|
const { automaticallySetFirstEoa } = (0, react_1.useB3Config)();
|
|
20
|
-
|
|
21
|
-
//
|
|
20
|
+
// skipAutoConnect: this component intentionally logs out on mount to show a fresh login screen.
|
|
21
|
+
// AuthenticationProvider is the sole owner of useAutoConnect to avoid competing auth cycles.
|
|
22
|
+
const { user, logout } = (0, react_1.useAuthentication)(partnerId, { skipAutoConnect: true });
|
|
23
|
+
// Tracks whether the pre-login logout has finished.
|
|
24
|
+
// We must not render ConnectEmbed until logout (wallet disconnect) completes,
|
|
25
|
+
// otherwise the wallet state disrupts ConnectEmbed causing a blank modal.
|
|
26
|
+
const [readyToShowLogin, setReadyToShowLogin] = (0, react_2.useState)(source === "requestPermissions");
|
|
22
27
|
const hasLoggedOutRef = (0, react_2.useRef)(false);
|
|
23
28
|
(0, react_2.useEffect)(() => {
|
|
24
29
|
if (hasLoggedOutRef.current)
|
|
25
30
|
return;
|
|
26
31
|
if (source !== "requestPermissions") {
|
|
27
32
|
debug("Logging out before login");
|
|
28
|
-
logout();
|
|
29
33
|
hasLoggedOutRef.current = true;
|
|
34
|
+
logout().finally(() => {
|
|
35
|
+
setReadyToShowLogin(true);
|
|
36
|
+
});
|
|
30
37
|
}
|
|
31
38
|
}, [source, logout]);
|
|
32
39
|
const [step, setStep] = (0, react_2.useState)(source === "requestPermissions" ? null : "login");
|
|
@@ -231,8 +238,9 @@ function SignInWithB3Flow({ strategies, onLoginSuccess, onSessionKeySuccess, onE
|
|
|
231
238
|
content = ((0, jsx_runtime_1.jsx)(LoginStep_1.LoginStepContainer, { partnerId: partnerId, children: (0, jsx_runtime_1.jsx)("div", { className: "p-4 text-center text-red-500", children: refetchError }) }));
|
|
232
239
|
}
|
|
233
240
|
else if (step === "login") {
|
|
234
|
-
// Show loading spinner
|
|
235
|
-
|
|
241
|
+
// Show loading spinner while: authenticating, waiting for pre-login logout to finish,
|
|
242
|
+
// or fetching signers.
|
|
243
|
+
if (!readyToShowLogin || isAuthenticating || isFetchingSigners) {
|
|
236
244
|
content = ((0, jsx_runtime_1.jsx)(LoginStep_1.LoginStepContainer, { partnerId: partnerId, children: (0, jsx_runtime_1.jsx)("div", { className: "my-8 flex min-h-[350px] items-center justify-center", children: (0, jsx_runtime_1.jsx)(react_1.Loading, { variant: "white", size: "lg" }) }) }));
|
|
237
245
|
}
|
|
238
246
|
else {
|
|
@@ -11,7 +11,7 @@ function SignInWithB3Privy({ onSuccess, onError, chain }) {
|
|
|
11
11
|
const { isLoading, connectTw, fullToken } = (0, react_1.useHandleConnectWithPrivy)(chain, onSuccess);
|
|
12
12
|
const setIsAuthenticating = (0, react_1.useAuthStore)(state => state.setIsAuthenticating);
|
|
13
13
|
const setIsAuthenticated = (0, react_1.useAuthStore)(state => state.setIsAuthenticated);
|
|
14
|
-
const { logout } = (0, react_1.useAuthentication)(partnerId);
|
|
14
|
+
const { logout } = (0, react_1.useAuthentication)(partnerId, { skipAutoConnect: true });
|
|
15
15
|
debug("@@SignInWithB3Privy", {
|
|
16
16
|
isLoading,
|
|
17
17
|
fullToken,
|
|
@@ -19,5 +19,5 @@ interface LoginStepContainerProps {
|
|
|
19
19
|
partnerId?: string;
|
|
20
20
|
}
|
|
21
21
|
export declare function LoginStepContainer({ children, partnerId }: LoginStepContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
-
export declare function LoginStep({ onSuccess, chain }: LoginStepProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function LoginStep({ onSuccess, chain }: LoginStepProps): import("react/jsx-runtime").JSX.Element | null;
|
|
23
23
|
export {};
|
|
@@ -7,6 +7,7 @@ const react_1 = require("../../../../../global-account/react");
|
|
|
7
7
|
const constants_1 = require("../../../../../shared/constants");
|
|
8
8
|
const thirdweb_1 = require("../../../../../shared/utils/thirdweb");
|
|
9
9
|
const react_2 = require("thirdweb/react");
|
|
10
|
+
const react_3 = require("react");
|
|
10
11
|
const wallets_1 = require("thirdweb/wallets");
|
|
11
12
|
function LoginStepContainer({ children, partnerId }) {
|
|
12
13
|
const { data: partner } = (0, react_1.useQueryB3)("global-accounts-partners", "find", {
|
|
@@ -18,30 +19,17 @@ function LoginStepContainer({ children, partnerId }) {
|
|
|
18
19
|
const partnerLogo = partner?.data?.[0]?.loginCustomization?.logoUrl;
|
|
19
20
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-react-background flex flex-col items-center justify-center pt-6", children: [partnerLogo && ((0, jsx_runtime_1.jsx)("img", { src: partnerLogo, alt: "Partner Logo", className: "partner-logo mb-6 h-12 w-auto object-contain" })), children] }));
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
: (0, react_2.darkTheme)({
|
|
34
|
-
colors: {
|
|
35
|
-
modalBg: "hsl(var(--b3-react-background))",
|
|
36
|
-
},
|
|
37
|
-
}), style: {
|
|
38
|
-
width: "100%",
|
|
39
|
-
height: "100%",
|
|
40
|
-
border: 0,
|
|
41
|
-
}, header: {
|
|
42
|
-
title: "Sign in with B3",
|
|
43
|
-
titleIcon: "https://cdn.b3.fun/b3_logo.svg",
|
|
44
|
-
}, className: "b3-login-step", onConnect: async (wallet, allConnectedWallets) => {
|
|
22
|
+
/** Inner component that only mounts when partnerId is a non-empty string.
|
|
23
|
+
* Keeps all hooks unconditional without calling useAuthentication(""). */
|
|
24
|
+
function LoginStepContent({ onSuccess, chain, partnerId, theme, }) {
|
|
25
|
+
const wallet = (0, react_3.useMemo)(() => (0, wallets_1.ecosystemWallet)(constants_1.ecosystemWalletId, { partnerId }), [partnerId]);
|
|
26
|
+
// skipAutoConnect: AuthenticationProvider already owns the auto-connect instance.
|
|
27
|
+
// Creating another here would cause a second authentication cycle (another 401 attempt)
|
|
28
|
+
// that makes the modal flash between spinner and blank before finally showing the login form.
|
|
29
|
+
const { onConnect } = (0, react_1.useAuthentication)(partnerId, { skipAutoConnect: true });
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)(LoginStepContainer, { partnerId: partnerId, children: (0, jsx_runtime_1.jsx)(react_2.ConnectEmbed, { showThirdwebBranding: false, autoConnect: false, client: thirdweb_1.client, chain: chain, wallets: [wallet], theme: theme === "light"
|
|
31
|
+
? (0, react_2.lightTheme)({ colors: { modalBg: "hsl(var(--b3-react-background))" } })
|
|
32
|
+
: (0, react_2.darkTheme)({ colors: { modalBg: "hsl(var(--b3-react-background))" } }), style: { width: "100%", height: "100%", border: 0 }, header: { title: "Sign in with B3", titleIcon: "https://cdn.b3.fun/b3_logo.svg" }, className: "b3-login-step", onConnect: async (wallet, allConnectedWallets) => {
|
|
45
33
|
await onConnect(wallet, allConnectedWallets);
|
|
46
34
|
const account = wallet.getAccount();
|
|
47
35
|
if (!account)
|
|
@@ -49,3 +37,12 @@ function LoginStep({ onSuccess, chain }) {
|
|
|
49
37
|
await onSuccess(account);
|
|
50
38
|
} }) }));
|
|
51
39
|
}
|
|
40
|
+
function LoginStep({ onSuccess, chain }) {
|
|
41
|
+
const { partnerId, theme } = (0, react_1.useB3Config)();
|
|
42
|
+
// partnerId may be undefined during the brief B3Provider hydration window.
|
|
43
|
+
// Return null rather than rendering ConnectEmbed with an invalid ecosystem
|
|
44
|
+
// wallet config (which causes a blank screen).
|
|
45
|
+
if (!partnerId)
|
|
46
|
+
return null;
|
|
47
|
+
return (0, jsx_runtime_1.jsx)(LoginStepContent, { onSuccess: onSuccess, chain: chain, partnerId: partnerId, theme: theme });
|
|
48
|
+
}
|
|
@@ -16,7 +16,7 @@ function LoginStepCustom({ onSuccess, onError, chain, strategies, maxInitialWall
|
|
|
16
16
|
const { connect } = (0, react_1.useConnect)(partnerId, chain);
|
|
17
17
|
const setIsAuthenticating = (0, react_1.useAuthStore)(state => state.setIsAuthenticating);
|
|
18
18
|
const setIsAuthenticated = (0, react_1.useAuthStore)(state => state.setIsAuthenticated);
|
|
19
|
-
const { logout } = (0, react_1.useAuthentication)(partnerId);
|
|
19
|
+
const { logout } = (0, react_1.useAuthentication)(partnerId, { skipAutoConnect: true });
|
|
20
20
|
const { connect: connectTW } = (0, react_3.useConnect)();
|
|
21
21
|
// Split strategies into auth and wallet types
|
|
22
22
|
const authStrategies = strategies.filter(s => !(0, react_1.isWalletType)(s));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Wallet } from "thirdweb/wallets";
|
|
2
2
|
import { preAuthenticate } from "thirdweb/wallets/in-app";
|
|
3
|
-
export declare function useAuthentication(partnerId: string
|
|
3
|
+
export declare function useAuthentication(partnerId: string, { skipAutoConnect }?: {
|
|
4
|
+
skipAutoConnect?: boolean;
|
|
5
|
+
}): {
|
|
4
6
|
logout: (callback?: () => void) => Promise<void>;
|
|
5
7
|
isAuthenticated: boolean;
|
|
6
8
|
isReady: boolean;
|
|
@@ -21,11 +21,26 @@ const createWagmiConfig_1 = require("../utils/createWagmiConfig");
|
|
|
21
21
|
const useTWAuth_1 = require("./useTWAuth");
|
|
22
22
|
const useUserQuery_1 = require("./useUserQuery");
|
|
23
23
|
const debug = (0, debug_1.debugB3React)("useAuthentication");
|
|
24
|
-
function useAuthentication(partnerId) {
|
|
24
|
+
function useAuthentication(partnerId, { skipAutoConnect = false } = {}) {
|
|
25
25
|
const { onConnectCallback, onLogoutCallback } = (0, react_2.useContext)(LocalSDKProvider_1.LocalSDKContext);
|
|
26
26
|
const { disconnect } = (0, react_3.useDisconnect)();
|
|
27
27
|
const wallets = (0, react_3.useConnectedWallets)();
|
|
28
|
+
// Keep refs so logout() always disconnects current wallets, not stale closure values.
|
|
29
|
+
// autoConnectCore captures onConnect (and thus logout) from the first render before wallets
|
|
30
|
+
// are populated — without these refs, logout() would capture wallets=[] and disconnect nothing.
|
|
31
|
+
const walletsRef = (0, react_2.useRef)(wallets);
|
|
32
|
+
(0, react_2.useEffect)(() => {
|
|
33
|
+
walletsRef.current = wallets;
|
|
34
|
+
}, [wallets]);
|
|
28
35
|
const activeWallet = (0, react_3.useActiveWallet)();
|
|
36
|
+
// Track the active wallet by ref so logout() can disconnect the exact reference
|
|
37
|
+
// stored in thirdweb's activeWalletStore. walletsRef.current (from useConnectedWallets)
|
|
38
|
+
// may hold a different object reference than what thirdweb considers "active",
|
|
39
|
+
// causing the identity check in onWalletDisconnect to fail silently.
|
|
40
|
+
const activeWalletRef = (0, react_2.useRef)(activeWallet);
|
|
41
|
+
(0, react_2.useEffect)(() => {
|
|
42
|
+
activeWalletRef.current = activeWallet;
|
|
43
|
+
}, [activeWallet]);
|
|
29
44
|
const isAuthenticated = (0, react_1.useAuthStore)(state => state.isAuthenticated);
|
|
30
45
|
const setIsAuthenticated = (0, react_1.useAuthStore)(state => state.setIsAuthenticated);
|
|
31
46
|
const setIsConnected = (0, react_1.useAuthStore)(state => state.setIsConnected);
|
|
@@ -133,13 +148,26 @@ function useAuthentication(partnerId) {
|
|
|
133
148
|
}, [activeWallet, partnerId, authenticate, setIsAuthenticated, setIsAuthenticating, setUser, setHasStartedConnecting]);
|
|
134
149
|
const logout = (0, react_2.useCallback)(async (callback) => {
|
|
135
150
|
// Only disconnect ecosystem/smart wallets, preserve EOA wallets (e.g. MetaMask)
|
|
136
|
-
// so they remain available after re-login
|
|
137
|
-
|
|
151
|
+
// so they remain available after re-login.
|
|
152
|
+
// Use walletsRef.current (not the stale closure value) so we always get current wallets —
|
|
153
|
+
// autoConnectCore captures logout from the first render when wallets is still [].
|
|
154
|
+
walletsRef.current.forEach(wallet => {
|
|
138
155
|
debug("@@logout:wallet", wallet.id);
|
|
139
156
|
if (wallet.id.startsWith("ecosystem.") || wallet.id === "smart") {
|
|
140
157
|
disconnect(wallet);
|
|
141
158
|
}
|
|
142
159
|
});
|
|
160
|
+
// Also disconnect the active wallet using the exact reference from thirdweb's
|
|
161
|
+
// activeWalletStore. The wallets in walletsRef (from useConnectedWallets) may be
|
|
162
|
+
// different object references than what thirdweb holds as "active". Thirdweb's
|
|
163
|
+
// onWalletDisconnect uses strict identity (===) to decide whether to clear
|
|
164
|
+
// activeAccountStore — if the reference doesn't match, activeAccount stays set
|
|
165
|
+
// and ConnectEmbed renders show=false (blank).
|
|
166
|
+
if (activeWalletRef.current &&
|
|
167
|
+
(activeWalletRef.current.id.startsWith("ecosystem.") || activeWalletRef.current.id === "smart")) {
|
|
168
|
+
debug("@@logout:disconnecting active wallet", activeWalletRef.current.id);
|
|
169
|
+
disconnect(activeWalletRef.current);
|
|
170
|
+
}
|
|
143
171
|
// Clear user-specific storage but preserve wallet connection state
|
|
144
172
|
// so EOA wallets (e.g. MetaMask) can auto-reconnect on next login
|
|
145
173
|
if (typeof localStorage !== "undefined") {
|
|
@@ -150,12 +178,19 @@ function useAuthentication(partnerId) {
|
|
|
150
178
|
debug("@@logout:loggedOut");
|
|
151
179
|
setIsAuthenticated(false);
|
|
152
180
|
setIsConnected(false);
|
|
181
|
+
// Reset isAuthenticating so any in-flight page-load auto-connect that set it true
|
|
182
|
+
// does not keep the login modal spinner stuck after logout() is called.
|
|
183
|
+
setIsAuthenticating(false);
|
|
153
184
|
setUser();
|
|
154
185
|
callback?.();
|
|
155
186
|
if (onLogoutCallback) {
|
|
156
187
|
await onLogoutCallback();
|
|
157
188
|
}
|
|
158
|
-
},
|
|
189
|
+
},
|
|
190
|
+
// wallets intentionally omitted — we use walletsRef.current so this callback stays stable
|
|
191
|
+
// and always operates on current wallets even when captured in stale closures.
|
|
192
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
193
|
+
[disconnect, setIsAuthenticated, setIsAuthenticating, setUser, setIsConnected, onLogoutCallback]);
|
|
159
194
|
const onConnect = (0, react_2.useCallback)(async (_walleAutoConnectedWith, allConnectedWallets) => {
|
|
160
195
|
debug("@@useAuthentication:onConnect", { _walleAutoConnectedWith, allConnectedWallets });
|
|
161
196
|
try {
|
|
@@ -203,23 +238,32 @@ function useAuthentication(partnerId) {
|
|
|
203
238
|
]);
|
|
204
239
|
const { isLoading: useAutoConnectLoading } = (0, react_3.useAutoConnect)({
|
|
205
240
|
client: thirdweb_1.client,
|
|
206
|
-
|
|
241
|
+
// When skipAutoConnect is true (e.g. LoginStepContent, SignInWithB3Flow), pass an empty
|
|
242
|
+
// wallets array so useAutoConnect completes immediately without firing onConnect.
|
|
243
|
+
// Only AuthenticationProvider (the primary instance) should own auto-connect.
|
|
244
|
+
wallets: skipAutoConnect ? [] : [wallet],
|
|
207
245
|
onConnect,
|
|
208
246
|
onTimeout: () => {
|
|
247
|
+
if (skipAutoConnect)
|
|
248
|
+
return;
|
|
209
249
|
logout().catch(error => {
|
|
210
250
|
debug("@@useAuthentication:logout on timeout failed", { error });
|
|
211
251
|
});
|
|
212
252
|
},
|
|
213
253
|
});
|
|
214
254
|
/**
|
|
215
|
-
* useAutoConnectLoading starts as false
|
|
255
|
+
* useAutoConnectLoading starts as false.
|
|
256
|
+
* Only the primary (non-skip) instance manages isAuthenticating via this effect
|
|
257
|
+
* to avoid race conditions when multiple useAuthentication instances are mounted.
|
|
216
258
|
*/
|
|
217
259
|
(0, react_2.useEffect)(() => {
|
|
260
|
+
if (skipAutoConnect)
|
|
261
|
+
return;
|
|
218
262
|
if (!useAutoConnectLoading && useAutoConnectLoadingPrevious.current && !hasStartedConnecting) {
|
|
219
263
|
setIsAuthenticating(false);
|
|
220
264
|
}
|
|
221
265
|
useAutoConnectLoadingPrevious.current = useAutoConnectLoading;
|
|
222
|
-
}, [useAutoConnectLoading, hasStartedConnecting, setIsAuthenticating]);
|
|
266
|
+
}, [useAutoConnectLoading, hasStartedConnecting, setIsAuthenticating, skipAutoConnect]);
|
|
223
267
|
const isReady = isAuthenticated && !isAuthenticating;
|
|
224
268
|
return {
|
|
225
269
|
logout,
|
|
@@ -68,7 +68,8 @@ function useGetAllTWSigners({ chain, accountAddress, queryOptions }) {
|
|
|
68
68
|
});
|
|
69
69
|
return result;
|
|
70
70
|
},
|
|
71
|
-
enabled
|
|
71
|
+
// Respect queryOptions.enabled if explicitly set (e.g. signersEnabled=false from SignInWithB3Flow)
|
|
72
|
+
enabled: queryOptions?.enabled !== false && Boolean(chain && accountAddress),
|
|
72
73
|
refetchOnMount: true,
|
|
73
74
|
refetchOnWindowFocus: true,
|
|
74
75
|
refetchOnReconnect: true,
|
|
@@ -31,7 +31,6 @@ import { GasIndicator } from "./common/GasIndicator.js";
|
|
|
31
31
|
import { OrderDetails, OrderDetailsLoadingView } from "./common/OrderDetails.js";
|
|
32
32
|
import { OrderHistory } from "./common/OrderHistory.js";
|
|
33
33
|
import { KycGate } from "./checkout/KycGate.js";
|
|
34
|
-
import { useWalletAuthHeaders } from "../hooks/useKycStatus.js";
|
|
35
34
|
import { LoginStep } from "../../../global-account/react/components/SignInWithB3/steps/LoginStep.js";
|
|
36
35
|
import { PanelOnramp } from "./common/PanelOnramp.js";
|
|
37
36
|
import { PanelOnrampPayment } from "./common/PanelOnrampPayment.js";
|
|
@@ -75,10 +74,6 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
75
74
|
// in the same frame that onStatusResolved sets it (setState is async).
|
|
76
75
|
// When kycEnabled is false (default), pre-approve so the KYC gate is skipped.
|
|
77
76
|
const kycApprovedRef = useRef(!kycEnabled);
|
|
78
|
-
// Pre-warm wallet auth headers inside user-gesture context (button click)
|
|
79
|
-
// so the signing prompt fires before we navigate away — browsers block
|
|
80
|
-
// wallet popups triggered from async/non-gesture contexts (React Query queryFn).
|
|
81
|
-
const { getHeaders: getKycHeaders } = useWalletAuthHeaders();
|
|
82
77
|
// Determine if we're in "buy mode" based on whether destination token props are provided
|
|
83
78
|
const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId);
|
|
84
79
|
// Add refs to track URL state
|
|
@@ -845,11 +840,9 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
845
840
|
return;
|
|
846
841
|
}
|
|
847
842
|
if (!kycApprovedRef.current) {
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
// (non-gesture) contexts, which is exactly what React Query uses.
|
|
852
|
-
await getKycHeaders().catch(() => { });
|
|
843
|
+
// Navigate to KYC gate. KycGate shows an explicit "Continue to Verify"
|
|
844
|
+
// CTA and pre-signs from that user-gesture context before enabling
|
|
845
|
+
// the useKycStatus query — so no surprise signature popup here.
|
|
853
846
|
navigateToPanel(PanelView.FIAT_KYC, "forward");
|
|
854
847
|
return;
|
|
855
848
|
}
|