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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +7 -0
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +30 -17
  3. package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
  4. package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +50 -13
  5. package/dist/cjs/anyspend/react/components/QRDeposit.d.ts +14 -3
  6. package/dist/cjs/anyspend/react/components/QRDeposit.js +24 -15
  7. package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +3 -1
  8. package/dist/cjs/global-account/react/components/SignInWithB3/components/AuthButton.js +2 -1
  9. package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +1 -0
  10. package/dist/cjs/global-account/react/components/SignInWithB3/utils/signInUtils.js +2 -0
  11. package/dist/cjs/global-account/react/hooks/useBetterAuth.d.ts +1 -1
  12. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +14 -0
  13. package/dist/esm/anyspend/react/components/AnySpend.d.ts +7 -0
  14. package/dist/esm/anyspend/react/components/AnySpend.js +30 -17
  15. package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
  16. package/dist/esm/anyspend/react/components/AnySpendDeposit.js +51 -14
  17. package/dist/esm/anyspend/react/components/QRDeposit.d.ts +14 -3
  18. package/dist/esm/anyspend/react/components/QRDeposit.js +25 -16
  19. package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +3 -1
  20. package/dist/esm/global-account/react/components/SignInWithB3/components/AuthButton.js +2 -1
  21. package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +1 -0
  22. package/dist/esm/global-account/react/components/SignInWithB3/utils/signInUtils.js +2 -0
  23. package/dist/esm/global-account/react/hooks/useBetterAuth.d.ts +1 -1
  24. package/dist/esm/global-account/react/stores/useModalStore.d.ts +14 -0
  25. package/dist/styles/index.css +1 -1
  26. package/dist/types/anyspend/react/components/AnySpend.d.ts +7 -0
  27. package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
  28. package/dist/types/anyspend/react/components/QRDeposit.d.ts +14 -3
  29. package/dist/types/global-account/react/hooks/useBetterAuth.d.ts +1 -1
  30. package/dist/types/global-account/react/stores/useModalStore.d.ts +14 -0
  31. package/package.json +1 -1
  32. package/src/anyspend/react/components/AnySpend.tsx +42 -16
  33. package/src/anyspend/react/components/AnySpendDeposit.tsx +87 -12
  34. package/src/anyspend/react/components/QRDeposit.tsx +46 -18
  35. package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +256 -0
  36. package/src/global-account/react/components/SignInWithB3/BetterAuthSignIn.tsx +12 -1
  37. package/src/global-account/react/components/SignInWithB3/components/AuthButton.tsx +9 -1
  38. package/src/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.tsx +1 -0
  39. package/src/global-account/react/components/SignInWithB3/utils/signInUtils.ts +2 -0
  40. package/src/global-account/react/hooks/useBetterAuth.ts +1 -1
  41. package/src/global-account/react/stores/useModalStore.ts +14 -0
@@ -28,6 +28,13 @@ export declare function AnySpend(props: {
28
28
  sourceChainId?: number;
29
29
  destinationTokenAddress?: string;
30
30
  destinationTokenChainId?: number;
31
+ /**
32
+ * When false, the destination token is user-selectable even if a default destination token is
33
+ * provided (the provided destination is used only as the initial/default value). Used by the
34
+ * wallet-funding deposit flow so the user can change the receive token away from the default
35
+ * (Base USDC). Defaults to true (locked buy-mode display, current behavior).
36
+ */
37
+ lockDestinationToken?: boolean;
31
38
  recipientAddress?: string;
32
39
  loadOrder?: string;
33
40
  hideTransactionHistoryButton?: boolean;
@@ -67,7 +67,7 @@ function AnySpend(props) {
67
67
  const fingerprintConfig = (0, AnySpendFingerprintWrapper_1.getFingerprintConfig)();
68
68
  return ((0, jsx_runtime_1.jsx)(AnySpendFingerprintWrapper_1.AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: (0, jsx_runtime_1.jsx)(AnySpendCustomizationContext_1.AnySpendCustomizationProvider, { slots: props.slots, content: props.content, theme: props.theme, children: (0, jsx_runtime_1.jsx)(AnySpendInner, { ...props }) }) }));
69
69
  }
70
- function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, }) {
70
+ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, lockDestinationToken = true, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, }) {
71
71
  const { slots, content } = (0, AnySpendCustomizationContext_1.useAnySpendCustomization)();
72
72
  const searchParams = (0, react_2.useSearchParamsSSR)();
73
73
  const router = (0, react_2.useRouter)();
@@ -81,8 +81,10 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
81
81
  // in the same frame that onStatusResolved sets it (setState is async).
82
82
  // When kycEnabled is false (default), pre-approve so the KYC gate is skipped.
83
83
  const kycApprovedRef = (0, react_4.useRef)(!kycEnabled);
84
- // Determine if we're in "buy mode" based on whether destination token props are provided
85
- const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId);
84
+ // Determine if we're in "buy mode" based on whether destination token props are provided.
85
+ // When lockDestinationToken is false, the provided destination is only a default and the user
86
+ // can change the receive token, so we stay out of buy mode (selectable swap mode).
87
+ const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId) && lockDestinationToken;
86
88
  // Add refs to track URL state
87
89
  const initialUrlProcessed = (0, react_4.useRef)(false);
88
90
  const lastUrlUpdate = (0, react_4.useRef)(null);
@@ -142,9 +144,14 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
142
144
  (0, react_4.useEffect)(() => {
143
145
  sessionStorage.setItem("anyspend_fiat_method", selectedFiatPaymentMethod);
144
146
  }, [selectedFiatPaymentMethod]);
147
+ // Whether a default destination token was provided. When lockDestinationToken is false this is
148
+ // used only as the INITIAL/default value (the user can still change the receive token), so the
149
+ // wallet-funding deposit flow defaults to Base USDC but stays selectable.
150
+ const hasProvidedDestination = !!(destinationTokenAddress && destinationTokenChainId);
145
151
  // Get initial chain IDs from URL or defaults
146
152
  const initialSrcChainId = sourceChainId || parseInt(searchParams.get("fromChainId") || "0") || chains_1.mainnet.id;
147
- const initialDstChainId = parseInt(searchParams.get("toChainId") || "0") || (isBuyMode ? destinationTokenChainId : chains_1.base.id);
153
+ const initialDstChainId = parseInt(searchParams.get("toChainId") || "0") ||
154
+ (isBuyMode ? destinationTokenChainId : hasProvidedDestination ? destinationTokenChainId : chains_1.base.id);
148
155
  // State for source chain/token selection
149
156
  const [selectedSrcChainId, setSelectedSrcChainId] = (0, react_4.useState)(initialSrcChainId);
150
157
  const defaultSrcToken = (0, anyspend_1.getDefaultToken)(selectedSrcChainId);
@@ -181,19 +188,25 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
181
188
  });
182
189
  // Helper to check if address is Hyperliquid USDC (supports both 34-char and 42-char formats)
183
190
  const isHyperliquidUSDCAddress = (address) => (0, anyspend_1.eqci)(address, anyspend_1.HYPERLIQUID_USDC_ADDRESS) || (0, anyspend_1.eqci)(address, anyspend_1.ZERO_ADDRESS);
184
- const defaultDstToken = isBuyMode
185
- ? // Special case: Hyperliquid uses zero address for USDC
186
- destinationTokenChainId === anyspend_1.HYPERLIQUID_CHAIN_ID && isHyperliquidUSDCAddress(destinationTokenAddress)
187
- ? (0, anyspend_1.getHyperliquidUSDCToken)()
188
- : {
189
- symbol: "",
190
- chainId: destinationTokenChainId,
191
- address: destinationTokenAddress,
192
- name: "",
193
- decimals: 18,
194
- metadata: {},
195
- }
196
- : (0, anyspend_1.getDefaultToken)(selectedDstChainId);
191
+ // Build a token object from the provided destination props (handles the Hyperliquid USDC special case).
192
+ // The inline truthiness check lets TypeScript narrow both props to non-null, so it is `undefined`
193
+ // unless both props are provided — i.e. only meaningful when hasProvidedDestination/isBuyMode is true.
194
+ const providedDstToken = destinationTokenChainId && destinationTokenAddress
195
+ ? destinationTokenChainId === anyspend_1.HYPERLIQUID_CHAIN_ID && isHyperliquidUSDCAddress(destinationTokenAddress)
196
+ ? (0, anyspend_1.getHyperliquidUSDCToken)()
197
+ : {
198
+ symbol: "",
199
+ chainId: destinationTokenChainId,
200
+ address: destinationTokenAddress,
201
+ name: "",
202
+ decimals: 18,
203
+ metadata: {},
204
+ }
205
+ : undefined;
206
+ // In buy mode the provided destination is locked. Otherwise, if a default destination was provided
207
+ // (selectable deposit flow) seed it as the default; falling back to the chain's default token only
208
+ // when no destination prop is given (standard swap mode).
209
+ const defaultDstToken = (isBuyMode || hasProvidedDestination) && providedDstToken ? providedDstToken : (0, anyspend_1.getDefaultToken)(selectedDstChainId);
197
210
  const dstTokenFromUrl = (0, react_2.useTokenFromUrl)({
198
211
  defaultToken: defaultDstToken,
199
212
  prefix: "to",
@@ -41,6 +41,13 @@ export interface AnySpendDepositProps {
41
41
  destinationTokenAddress: string;
42
42
  /** The destination chain ID */
43
43
  destinationTokenChainId: number;
44
+ /**
45
+ * When false, the destination token is user-selectable even though a default destination token is
46
+ * provided (the provided destination is used only as the initial/default value). Used by the
47
+ * wallet-funding deposit flow so the user can change the receive token away from the default
48
+ * (Base USDC). Defaults to true (locked destination, current behavior). Does not affect the
49
+ * QR/pure-transfer path. */
50
+ lockDestinationToken?: boolean;
44
51
  /** Callback when deposit succeeds */
45
52
  onSuccess?: (amount: string) => void;
46
53
  /** Callback for opening a custom modal (e.g., for special token handling) */
@@ -102,6 +109,13 @@ export interface AnySpendDepositProps {
102
109
  classes?: AnySpendAllClasses;
103
110
  /** When true, allows direct transfer without swap if source and destination token/chain are the same */
104
111
  allowDirectTransfer?: boolean;
112
+ /**
113
+ * When true, the QR-deposit path is a PURE TRANSFER: the destination token/chain
114
+ * mirror whatever source token the user selects, so the exact token they send lands
115
+ * in their wallet on the same chain (no swap/bridge). Forwarded to QRDeposit.
116
+ * Defaults to false.
117
+ */
118
+ pureTransferOnly?: boolean;
105
119
  /** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
106
120
  destinationTokenAmount?: string;
107
121
  /** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
@@ -150,4 +164,4 @@ export interface AnySpendDepositProps {
150
164
  * onSuccess={(amount) => console.log(`Deposited ${amount}`)}
151
165
  * />
152
166
  */
153
- export declare function AnySpendDeposit({ loadOrder, mode, recipientAddress, paymentType: initialPaymentType, sourceTokenAddress, sourceTokenChainId: initialSourceChainId, destinationTokenAddress, destinationTokenChainId, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, minDestinationAmount, header, orderType, depositContractConfig, showChainSelection, showFiatOption, supportedChains, minPoolSize, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
167
+ export declare function AnySpendDeposit({ loadOrder, mode, recipientAddress, paymentType: initialPaymentType, sourceTokenAddress, sourceTokenChainId: initialSourceChainId, destinationTokenAddress, destinationTokenChainId, lockDestinationToken, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, minDestinationAmount, header, orderType, depositContractConfig, showChainSelection, showFiatOption, supportedChains, minPoolSize, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, pureTransferOnly, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AnySpendDeposit = AnySpendDeposit;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const anyspend_1 = require("../../../anyspend");
5
6
  const react_1 = require("../../../global-account/react");
6
7
  const cn_1 = require("../../../shared/utils/cn");
7
8
  const react_2 = require("@web3icons/react");
@@ -24,6 +25,19 @@ const DEFAULT_SUPPORTED_CHAINS = [
24
25
  ];
25
26
  // Minimum pool size to filter out low liquidity tokens
26
27
  const DEFAULT_MIN_POOL_SIZE = 1000000;
28
+ /**
29
+ * Self-described fallback metadata for the default funding token(s), keyed by
30
+ * `${chainId}:${lowercased address}`. Used when `useTokenData` misses (returns
31
+ * null) so the destination token's decimals/symbol are correct WITHOUT depending
32
+ * on the network — critical in pure-transfer mode where there's no server-side
33
+ * correction and the wrong decimals would make `useWatchTransfer` show a wrong amount.
34
+ */
35
+ const KNOWN_FUNDING_TOKENS = {
36
+ [`${anyspend_1.USDC_BASE.chainId}:${anyspend_1.USDC_BASE.address.toLowerCase()}`]: anyspend_1.USDC_BASE,
37
+ };
38
+ function getKnownFundingToken(chainId, address) {
39
+ return KNOWN_FUNDING_TOKENS[`${chainId}:${address.toLowerCase()}`];
40
+ }
27
41
  function formatUsd(value) {
28
42
  return new Intl.NumberFormat("en-US", {
29
43
  style: "currency",
@@ -97,7 +111,7 @@ function ChainIcon({ chainId, className }) {
97
111
  * onSuccess={(amount) => console.log(`Deposited ${amount}`)}
98
112
  * />
99
113
  */
100
- function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentType: initialPaymentType, sourceTokenAddress, sourceTokenChainId: initialSourceChainId, destinationTokenAddress, destinationTokenChainId, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, minDestinationAmount, header, orderType, depositContractConfig, showChainSelection, showFiatOption = true, supportedChains = DEFAULT_SUPPORTED_CHAINS, minPoolSize = DEFAULT_MIN_POOL_SIZE, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
114
+ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentType: initialPaymentType, sourceTokenAddress, sourceTokenChainId: initialSourceChainId, destinationTokenAddress, destinationTokenChainId, lockDestinationToken = true, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, minDestinationAmount, header, orderType, depositContractConfig, showChainSelection, showFiatOption = true, supportedChains = DEFAULT_SUPPORTED_CHAINS, minPoolSize = DEFAULT_MIN_POOL_SIZE, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, pureTransferOnly = false, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
101
115
  // Extract deposit-specific classes for convenience
102
116
  const depositClasses = classes?.deposit;
103
117
  const { connectedEOAWallet } = (0, react_1.useAccountWallet)();
@@ -114,16 +128,22 @@ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentT
114
128
  }
115
129
  }, [showFiatOption, paymentType]);
116
130
  // Fetch destination token data
117
- const { data: destinationTokenData } = (0, react_1.useTokenData)(destinationTokenChainId, destinationTokenAddress);
118
- // Construct full destination token object
119
- const destinationToken = (0, react_3.useMemo)(() => ({
120
- address: destinationTokenAddress,
121
- chainId: destinationTokenChainId,
122
- symbol: destinationTokenData?.symbol ?? "",
123
- name: destinationTokenData?.name ?? "",
124
- decimals: destinationTokenData?.decimals ?? 18,
125
- metadata: { logoURI: destinationTokenData?.logoURI },
126
- }), [destinationTokenAddress, destinationTokenChainId, destinationTokenData]);
131
+ const { data: destinationTokenData, isLoading: isDestinationTokenLoading } = (0, react_1.useTokenData)(destinationTokenChainId, destinationTokenAddress);
132
+ // Construct full destination token object. When `useTokenData` misses (it returns
133
+ // null on an API miss, not an error), fall back to a known-token entry so the
134
+ // decimals/symbol invariant holds regardless of the fetch — e.g. Base USDC is 6
135
+ // decimals, never the generic 18 fallback.
136
+ const destinationToken = (0, react_3.useMemo)(() => {
137
+ const known = getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
138
+ return {
139
+ address: destinationTokenAddress,
140
+ chainId: destinationTokenChainId,
141
+ symbol: destinationTokenData?.symbol ?? known?.symbol ?? "",
142
+ name: destinationTokenData?.name ?? known?.name ?? "",
143
+ decimals: destinationTokenData?.decimals ?? known?.decimals ?? 18,
144
+ metadata: { logoURI: destinationTokenData?.logoURI ?? known?.metadata?.logoURI },
145
+ };
146
+ }, [destinationTokenAddress, destinationTokenChainId, destinationTokenData]);
127
147
  // Fetch balances for EOA wallet (use senderAddress as fallback for pre-filled balance display)
128
148
  const effectiveBalanceAddress = senderAddress || eoaAddress;
129
149
  const { data: balanceData, isLoading: isBalanceLoading } = (0, react_1.useSimBalance)(shouldShowChainSelection ? effectiveBalanceAddress : undefined, supportedChains.map(c => c.id));
@@ -165,6 +185,9 @@ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentT
165
185
  const totalBalance = (0, react_3.useMemo)(() => {
166
186
  return Object.values(chainBalances).reduce((sum, chain) => sum + chain.totalUsdValue, 0);
167
187
  }, [chainBalances]);
188
+ const handleQRDepositSuccess = (0, react_3.useCallback)((txHash) => {
189
+ onSuccess?.(txHash ?? "");
190
+ }, [onSuccess]);
168
191
  if (!recipientAddress)
169
192
  return null;
170
193
  const tokenSymbol = destinationToken.symbol || "TOKEN";
@@ -211,10 +234,24 @@ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentT
211
234
  }
212
235
  // QR Deposit view
213
236
  if (step === "qr-deposit") {
214
- return ((0, jsx_runtime_1.jsx)(QRDeposit_1.QRDeposit, { mode: mode, recipientAddress: recipientAddress, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, depositContractConfig: depositContractConfig, onBack: handleBack, onClose: onClose ?? handleBack, classes: classes?.qrDeposit }));
237
+ // In pure-transfer mode QRDeposit captures `destinationToken` as its initial
238
+ // SOURCE token via useState, so it must be fully resolved (correct symbol +
239
+ // decimals) before QRDeposit mounts. Known funding tokens (e.g. Base USDC) are
240
+ // already fully resolved via the self-described fallback, so only show a spinner
241
+ // for unknown tokens whose metadata still has to load over the network.
242
+ const hasKnownDestination = !!getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
243
+ // NOTE: all current Fund Wallet callers derive `destinationTokenAddress` from
244
+ // `getDefaultDepositDestination`, which always returns Base USDC — exactly a
245
+ // `KNOWN_FUNDING_TOKENS` key — so `hasKnownDestination` is always true and this
246
+ // branch never fires for them. The guard is defensive for FUTURE callers that pass
247
+ // an unknown token together with `pureTransferOnly=true`.
248
+ if (pureTransferOnly && !hasKnownDestination && !destinationTokenData && isDestinationTokenLoading) {
249
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("anyspend-deposit anyspend-deposit-qr-loading font-inter bg-as-surface-primary mx-auto w-full max-w-[460px] p-6", mode === "page" && "border-as-border-secondary overflow-hidden rounded-2xl border shadow-xl"), children: (0, jsx_runtime_1.jsxs)("div", { className: "anyspend-deposit-qr-loading-content flex flex-col items-center justify-center gap-4 py-12", children: [(0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-8 w-8 rounded-full" }), (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-4 w-40" })] }) }));
250
+ }
251
+ return ((0, jsx_runtime_1.jsx)(QRDeposit_1.QRDeposit, { mode: mode, recipientAddress: recipientAddress, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, pureTransferOnly: pureTransferOnly, depositContractConfig: depositContractConfig, onBack: handleBack, onClose: onClose ?? handleBack, onSuccess: handleQRDepositSuccess, classes: classes?.qrDeposit }));
215
252
  }
216
253
  // Deposit view
217
254
  return ((0, jsx_runtime_1.jsxs)("div", { className: depositClasses?.form || "anyspend-deposit anyspend-deposit-form relative", children: [shouldShowChainSelection && ((0, jsx_runtime_1.jsxs)("button", { onClick: handleBack, className: depositClasses?.backButton ||
218
255
  "anyspend-deposit-back-button text-as-secondary hover:text-as-primary absolute left-4 top-4 z-10 flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("svg", { className: depositClasses?.backIcon || "anyspend-deposit-back-icon h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }), (0, jsx_runtime_1.jsx)("span", { className: depositClasses?.backText || "anyspend-deposit-back-text text-sm", children: "Back" })] })), onClose && ((0, jsx_runtime_1.jsx)("button", { onClick: onClose, className: depositClasses?.closeButton ||
219
- "anyspend-deposit-close-button text-as-secondary hover:text-as-primary absolute right-4 top-4 z-10", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })), (0, jsx_runtime_1.jsx)("div", { className: depositClasses?.formContent || (0, cn_1.cn)("anyspend-deposit-form-content", shouldShowChainSelection && "pt-8"), children: isCustomDeposit ? ((0, jsx_runtime_1.jsx)(AnySpendCustomExactIn_1.AnySpendCustomExactIn, { loadOrder: loadOrder, mode: mode, recipientAddress: recipientAddress, paymentType: paymentType, sourceTokenAddress: sourceTokenAddress, sourceTokenChainId: selectedChainId, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, orderType: effectiveOrderType, minDestinationAmount: minDestinationAmount, header: header ?? defaultHeader, onSuccess: onSuccess, onOpenCustomModal: onOpenCustomModal, mainFooter: mainFooter, onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, preferEoa: preferEoa, customExactInConfig: depositContractConfig, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.customExactIn, allowDirectTransfer: allowDirectTransfer, destinationTokenAmount: destinationTokenAmount, callbackMetadata: callbackMetadata, senderAddress: senderAddress, showFiatOption: showFiatOption, slots: slots, content: content, theme: theme }, selectedChainId)) : ((0, jsx_runtime_1.jsx)(AnySpend_1.AnySpend, { loadOrder: loadOrder, mode: mode, defaultActiveTab: paymentType, recipientAddress: recipientAddress, sourceChainId: selectedChainId, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, onSuccess: txHash => onSuccess?.(txHash ?? ""), onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, hideHeader: true, hideBottomNavigation: true, disableUrlParamManagement: true, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.anySpend, allowDirectTransfer: allowDirectTransfer, destinationTokenAmount: destinationTokenAmount, callbackMetadata: callbackMetadata, senderAddress: senderAddress, showFiatOption: showFiatOption, slots: slots, content: content, theme: theme }, selectedChainId)) }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
256
+ "anyspend-deposit-close-button text-as-secondary hover:text-as-primary absolute right-4 top-4 z-10", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })), (0, jsx_runtime_1.jsx)("div", { className: depositClasses?.formContent || (0, cn_1.cn)("anyspend-deposit-form-content", shouldShowChainSelection && "pt-8"), children: isCustomDeposit ? ((0, jsx_runtime_1.jsx)(AnySpendCustomExactIn_1.AnySpendCustomExactIn, { loadOrder: loadOrder, mode: mode, recipientAddress: recipientAddress, paymentType: paymentType, sourceTokenAddress: sourceTokenAddress, sourceTokenChainId: selectedChainId, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, orderType: effectiveOrderType, minDestinationAmount: minDestinationAmount, header: header ?? defaultHeader, onSuccess: onSuccess, onOpenCustomModal: onOpenCustomModal, mainFooter: mainFooter, onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, preferEoa: preferEoa, customExactInConfig: depositContractConfig, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.customExactIn, allowDirectTransfer: allowDirectTransfer, destinationTokenAmount: destinationTokenAmount, callbackMetadata: callbackMetadata, senderAddress: senderAddress, showFiatOption: showFiatOption, slots: slots, content: content, theme: theme }, selectedChainId)) : ((0, jsx_runtime_1.jsx)(AnySpend_1.AnySpend, { loadOrder: loadOrder, mode: mode, defaultActiveTab: paymentType, recipientAddress: recipientAddress, sourceChainId: selectedChainId, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, lockDestinationToken: lockDestinationToken, onSuccess: txHash => onSuccess?.(txHash ?? ""), onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, hideHeader: true, hideBottomNavigation: true, disableUrlParamManagement: true, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.anySpend, allowDirectTransfer: allowDirectTransfer, destinationTokenAmount: destinationTokenAmount, callbackMetadata: callbackMetadata, senderAddress: senderAddress, showFiatOption: showFiatOption, slots: slots, content: content, theme: theme }, selectedChainId)) }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
220
257
  }
@@ -14,6 +14,14 @@ export interface QRDepositProps {
14
14
  destinationToken: components["schemas"]["Token"];
15
15
  /** The destination chain ID */
16
16
  destinationChainId: number;
17
+ /**
18
+ * When true, the deposit is a PURE TRANSFER: the destination token/chain mirror
19
+ * the user's currently selected source token/chain, so the exact token the user
20
+ * sends lands in their wallet on the same chain — no swap/bridge order is created.
21
+ * The `destinationToken`/`destinationChainId` props are then used only as the
22
+ * initial source selection (they must be fully resolved before mount). Defaults to false.
23
+ */
24
+ pureTransferOnly?: boolean;
17
25
  /** Creator address (optional) */
18
26
  creatorAddress?: string;
19
27
  /** Contract config for custom execution after deposit */
@@ -24,8 +32,11 @@ export interface QRDepositProps {
24
32
  onClose?: () => void;
25
33
  /** Callback when order is created successfully */
26
34
  onOrderCreated?: (orderId: string) => void;
27
- /** Callback when deposit is completed */
28
- onSuccess?: (txHash?: string) => void;
35
+ /**
36
+ * Callback when deposit is completed. The argument carries the deposited amount string
37
+ * (e.g. "200.00") on the pure-transfer path, or the order tx hash otherwise.
38
+ */
39
+ onSuccess?: (amountOrTxHash?: string) => void;
29
40
  /** Custom classes for styling */
30
41
  classes?: QRDepositClasses;
31
42
  }
@@ -43,4 +54,4 @@ export interface QRDepositProps {
43
54
  * onSuccess={(txHash) => console.log("Deposit complete:", txHash)}
44
55
  * />
45
56
  */
46
- export declare function QRDeposit({ mode, recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }: QRDepositProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function QRDeposit({ mode, recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, pureTransferOnly, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }: QRDepositProps): import("react/jsx-runtime").JSX.Element;
@@ -42,28 +42,35 @@ const DEFAULT_ETH_ON_BASE = {
42
42
  * onSuccess={(txHash) => console.log("Deposit complete:", txHash)}
43
43
  * />
44
44
  */
45
- function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }) {
45
+ function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, pureTransferOnly = false, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }) {
46
46
  const [copied, setCopied] = (0, react_2.useState)(false);
47
47
  const [orderId, setOrderId] = (0, react_2.useState)();
48
48
  const [globalAddress, setGlobalAddress] = (0, react_2.useState)();
49
49
  const orderCreatedRef = (0, react_2.useRef)(false);
50
50
  const [transferResult, setTransferResult] = (0, react_2.useState)(null);
51
- // Source token/chain as state (can be changed by user)
52
- const [sourceChainId, setSourceChainId] = (0, react_2.useState)(sourceChainIdProp ?? 8453);
53
- const [sourceToken, setSourceToken] = (0, react_2.useState)(sourceTokenProp ?? DEFAULT_ETH_ON_BASE);
51
+ // Source token/chain as state (can be changed by user).
52
+ // In pure-transfer mode the initial source is the passed destination (caller sets
53
+ // it to the desired default funding token), so the deposit mirrors the user's selection.
54
+ const [sourceChainId, setSourceChainId] = (0, react_2.useState)(sourceChainIdProp ?? (pureTransferOnly ? destinationChainId : 8453));
55
+ const [sourceToken, setSourceToken] = (0, react_2.useState)(sourceTokenProp ?? (pureTransferOnly ? destinationToken : DEFAULT_ETH_ON_BASE));
56
+ // In pure-transfer mode the effective destination mirrors the selected source,
57
+ // forcing isPureTransfer = true (no swap/bridge order is created).
58
+ const effectiveDestinationToken = pureTransferOnly ? sourceToken : destinationToken;
59
+ const effectiveDestinationChainId = pureTransferOnly ? sourceChainId : destinationChainId;
54
60
  // Check if this is a pure transfer (same chain and token)
55
- const isPureTransfer = (0, anyspend_1.isSameChainAndToken)(sourceChainId, sourceToken.address, destinationChainId, destinationToken.address);
61
+ const isPureTransfer = (0, anyspend_1.isSameChainAndToken)(sourceChainId, sourceToken.address, effectiveDestinationChainId, effectiveDestinationToken.address);
62
+ const handleTransferDetected = (0, react_2.useCallback)((result) => {
63
+ setTransferResult(result);
64
+ onSuccess?.(result.formattedAmount);
65
+ }, [onSuccess]);
56
66
  // Watch for pure transfers (same chain and token)
57
- const { isWatching: isWatchingTransfer } = (0, useWatchTransfer_1.useWatchTransfer)({
67
+ const { isWatching: isWatchingTransfer, reset: resetWatchTransfer } = (0, useWatchTransfer_1.useWatchTransfer)({
58
68
  address: recipientAddress,
59
69
  chainId: sourceChainId,
60
70
  tokenAddress: sourceToken.address,
61
71
  tokenDecimals: sourceToken.decimals,
62
72
  enabled: isPureTransfer && !transferResult,
63
- onTransferDetected: result => {
64
- setTransferResult(result);
65
- onSuccess?.();
66
- },
73
+ onTransferDetected: handleTransferDetected,
67
74
  });
68
75
  // Handle token selection from TokenSelector
69
76
  const handleTokenSelect = (newToken) => {
@@ -80,6 +87,8 @@ function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenP
80
87
  setGlobalAddress(undefined);
81
88
  orderCreatedRef.current = false;
82
89
  setTransferResult(null);
90
+ // Reset the balance watcher baseline so the new token isn't compared against the old token's balance
91
+ resetWatchTransfer();
83
92
  // Update token and chain
84
93
  setSourceChainId(newToken.chainId);
85
94
  setSourceToken(token);
@@ -110,18 +119,18 @@ function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenP
110
119
  createOrder({
111
120
  recipientAddress,
112
121
  srcChain: sourceChainId,
113
- dstChain: destinationChainId,
122
+ dstChain: effectiveDestinationChainId,
114
123
  srcToken: sourceToken,
115
- dstToken: destinationToken,
124
+ dstToken: effectiveDestinationToken,
116
125
  creatorAddress,
117
126
  contractConfig: depositContractConfig,
118
127
  });
119
128
  }, [
120
129
  recipientAddress,
121
130
  sourceChainId,
122
- destinationChainId,
131
+ effectiveDestinationChainId,
123
132
  sourceToken,
124
- destinationToken,
133
+ effectiveDestinationToken,
125
134
  creatorAddress,
126
135
  depositContractConfig,
127
136
  createOrder,
@@ -166,7 +175,7 @@ function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenP
166
175
  return ((0, jsx_runtime_1.jsx)("div", { className: classes?.container ||
167
176
  (0, cn_1.cn)("anyspend-container anyspend-qr-deposit font-inter bg-as-surface-primary mx-auto w-full max-w-[460px] p-6", mode === "page" && "border-as-border-secondary overflow-hidden rounded-2xl border shadow-xl"), children: (0, jsx_runtime_1.jsxs)("div", { className: classes?.content || "anyspend-qr-deposit-content flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: classes?.header || "anyspend-qr-header flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("button", { onClick: handleBack, className: classes?.backButton || "anyspend-qr-back-button text-as-secondary hover:text-as-primary", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }), (0, jsx_runtime_1.jsx)("h2", { className: classes?.title || "anyspend-qr-title text-as-primary text-base font-semibold", children: "Deposit" }), onClose ? ((0, jsx_runtime_1.jsx)("button", { onClick: handleClose, className: classes?.closeButton || "anyspend-qr-close-button text-as-secondary hover:text-as-primary", children: (0, jsx_runtime_1.jsx)("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-5" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.tokenSelectorContainer || "anyspend-qr-token-selector flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)("label", { className: classes?.tokenSelectorLabel || "anyspend-qr-token-label text-as-secondary text-sm", children: "Send" }), (0, jsx_runtime_1.jsx)(relay_kit_ui_1.TokenSelector, { chainIdsFilter: (0, anyspend_1.getAvailableChainIds)("from"), context: "from", fromChainWalletVMSupported: true, isValidAddress: true, lockedChainIds: (0, anyspend_1.getAvailableChainIds)("from"), multiWalletSupportEnabled: true, onAnalyticEvent: undefined, setToken: handleTokenSelect, supportedWalletVMs: ["evm"], token: undefined, trigger: (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", role: "combobox", className: classes?.tokenSelectorTrigger ||
168
177
  "anyspend-qr-token-trigger border-as-stroke bg-as-surface-secondary flex h-auto w-full items-center justify-between gap-2 rounded-xl border px-3 py-2.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [sourceToken.metadata?.logoURI ? ((0, jsx_runtime_1.jsx)(ChainTokenIcon_1.ChainTokenIcon, { chainUrl: anyspend_1.ALL_CHAINS[sourceChainId]?.logoUrl, tokenUrl: sourceToken.metadata.logoURI, className: "h-8 min-h-8 w-8 min-w-8" })) : ((0, jsx_runtime_1.jsx)("div", { className: "h-8 w-8 rounded-full bg-gray-700" })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-start gap-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary font-semibold", children: sourceToken.symbol }), (0, jsx_runtime_1.jsx)("div", { className: "text-as-primary/70 text-xs", children: anyspend_1.ALL_CHAINS[sourceChainId]?.name ?? "Unknown" })] })] }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-70" })] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.qrContent || "anyspend-qr-content border-as-stroke flex items-start gap-4 rounded-xl border p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: classes?.qrCodeContainer || "anyspend-qr-code-container flex flex-col items-center gap-2", children: [(0, jsx_runtime_1.jsx)("div", { className: classes?.qrCode || "anyspend-qr-code rounded-lg bg-white p-2", children: (0, jsx_runtime_1.jsx)(qrcode_react_1.QRCodeSVG, { value: qrValue, size: 120, level: "M", marginSize: 0 }) }), (0, jsx_runtime_1.jsxs)("span", { className: classes?.qrScanHint || "anyspend-qr-scan-hint text-as-secondary text-xs", children: ["SCAN WITH ", (0, jsx_runtime_1.jsx)("span", { className: "inline-block", children: "\uD83E\uDD8A" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.addressContainer || "anyspend-qr-address-container flex flex-1 flex-col gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: classes?.addressLabel || "anyspend-qr-address-label text-as-secondary text-sm", children: "Deposit address:" }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.addressRow || "anyspend-qr-address-row flex items-start gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: classes?.address || "anyspend-qr-address text-as-primary break-all font-mono text-sm leading-relaxed", children: displayAddress }), (0, jsx_runtime_1.jsx)("button", { onClick: handleCopyAddress, className: classes?.addressCopyIcon ||
169
- "anyspend-qr-copy-icon text-as-secondary hover:text-as-primary mt-0.5 shrink-0", children: copied ? (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "h-4 w-4" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "h-4 w-4" }) })] })] })] }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: destinationChainId }), (0, jsx_runtime_1.jsxs)(WarningText_1.WarningText, { children: ["Only send ", sourceToken.symbol, " on ", anyspend_1.ALL_CHAINS[sourceChainId]?.name ?? "the specified chain", ". Other tokens will not be converted."] }), isPureTransfer && isWatchingTransfer && ((0, jsx_runtime_1.jsxs)("div", { className: classes?.watchingIndicator ||
178
+ "anyspend-qr-copy-icon text-as-secondary hover:text-as-primary mt-0.5 shrink-0", children: copied ? (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "h-4 w-4" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "h-4 w-4" }) })] })] })] }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: effectiveDestinationChainId }), (0, jsx_runtime_1.jsxs)(WarningText_1.WarningText, { children: ["Only send ", sourceToken.symbol, " on ", anyspend_1.ALL_CHAINS[sourceChainId]?.name ?? "the specified chain", ". Other tokens will not be converted."] }), isPureTransfer && isWatchingTransfer && ((0, jsx_runtime_1.jsxs)("div", { className: classes?.watchingIndicator ||
170
179
  "anyspend-qr-watching flex items-center justify-center gap-2 rounded-lg bg-blue-500/10 p-3", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-4 w-4 animate-spin text-blue-500" }), (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-blue-500", children: "Watching for incoming transfer..." })] })), (0, jsx_runtime_1.jsx)("button", { onClick: handleCopyAddress, className: classes?.copyButton ||
171
180
  "anyspend-qr-copy-button flex w-full items-center justify-center gap-2 rounded-xl bg-blue-500 py-3.5 font-medium text-white transition-all hover:bg-blue-600", children: "Copy deposit address" })] }) }));
172
181
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BetterAuthSignIn = BetterAuthSignIn;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("../../../../global-account/react");
6
+ const utils_1 = require("../../../../shared/utils");
6
7
  const debug_1 = require("../../../../shared/utils/debug");
7
8
  const react_2 = require("react");
8
9
  const useBetterAuth_1 = require("../../hooks/useBetterAuth");
@@ -16,6 +17,7 @@ const DEFAULT_SOCIAL_PROVIDERS = [
16
17
  { id: "discord", label: "Discord" },
17
18
  { id: "microsoft", label: "Microsoft" },
18
19
  { id: "slack", label: "Slack" },
20
+ { id: "twitter", label: "X" },
19
21
  ];
20
22
  /**
21
23
  * Standalone inline sign-in component for Better Auth.
@@ -165,7 +167,7 @@ function BetterAuthSignIn({ title, subtitle = "Enter your credentials to access
165
167
  const icon = signInUtils_1.strategyIcons[provider.id];
166
168
  const label = signInUtils_1.strategyLabels[provider.id] || provider.label;
167
169
  const isProviderLoading = loadingProvider === provider.id;
168
- return ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleSocialSignIn(provider.id), disabled: isLoading, style: { paddingTop: "12px", paddingBottom: "12px" }, className: "flex w-full items-center justify-center gap-3 rounded-lg border border-gray-200 bg-white px-4 text-[14px] font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700", children: [isProviderLoading ? ((0, jsx_runtime_1.jsxs)("svg", { className: "h-5 w-5 animate-spin text-gray-400", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })] })) : (icon && (0, jsx_runtime_1.jsx)("img", { src: icon, alt: "", className: "h-5 w-5" })), (0, jsx_runtime_1.jsx)("span", { children: isProviderLoading ? "Redirecting..." : label })] }, provider.id));
170
+ return ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleSocialSignIn(provider.id), disabled: isLoading, style: { paddingTop: "12px", paddingBottom: "12px" }, className: "flex w-full items-center justify-center gap-3 rounded-lg border border-gray-200 bg-white px-4 text-[14px] font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700", children: [isProviderLoading ? ((0, jsx_runtime_1.jsxs)("svg", { className: "h-5 w-5 animate-spin text-gray-400", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })] })) : (icon && ((0, jsx_runtime_1.jsx)("img", { src: icon, alt: "", className: (0, utils_1.cn)("h-5 w-5", (provider.id === "github" || provider.id === "twitter") && "dark:invert") }))), (0, jsx_runtime_1.jsx)("span", { children: isProviderLoading ? "Redirecting..." : label })] }, provider.id));
169
171
  }) })] })), showEmail && mode !== "forgot-password" && ((0, jsx_runtime_1.jsxs)("p", { className: "mt-8 text-center text-[14px] text-gray-500 dark:text-gray-400", children: [mode === "sign-in" ? "Don't have an account? " : "Already have an account? ", (0, jsx_runtime_1.jsx)("button", { onClick: () => {
170
172
  setMode(mode === "sign-in" ? "sign-up" : "sign-in");
171
173
  setError(null);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthButton = AuthButton;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const Button_1 = require("../../custom/Button");
6
+ const utils_1 = require("../../../../../shared/utils");
6
7
  const lucide_react_1 = require("lucide-react");
7
8
  const signInUtils_1 = require("../utils/signInUtils");
8
9
  const fallbackIcons = {
@@ -14,5 +15,5 @@ function AuthButton({ strategy, onClick, isLoading, }) {
14
15
  const strategyLabel = signInUtils_1.strategyLabels[strategy] || strategy;
15
16
  const FallbackIcon = fallbackIcons[strategy];
16
17
  const buttonLabel = `Sign in with ${strategyLabel}`;
17
- return ((0, jsx_runtime_1.jsx)(Button_1.Button, { onClick: onClick, disabled: isLoading, "aria-label": buttonLabel, title: buttonLabel, className: "flex w-full items-center justify-center bg-gray-100 px-2 py-3 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700", children: strategyIcon ? ((0, jsx_runtime_1.jsx)("img", { src: strategyIcon, alt: `${strategyLabel} icon`, className: "h-9 w-9" })) : FallbackIcon ? ((0, jsx_runtime_1.jsx)(FallbackIcon, { className: "h-9 w-9 text-gray-900 dark:text-gray-100" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: strategyLabel.charAt(0) })) }, strategy));
18
+ return ((0, jsx_runtime_1.jsx)(Button_1.Button, { onClick: onClick, disabled: isLoading, "aria-label": buttonLabel, title: buttonLabel, className: "flex w-full items-center justify-center bg-gray-100 px-2 py-3 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700", children: strategyIcon ? ((0, jsx_runtime_1.jsx)("img", { src: strategyIcon, alt: `${strategyLabel} icon`, className: (0, utils_1.cn)("h-9 w-9", (strategy === "github" || strategy === "twitter" || strategy === "x") && "dark:invert") })) : FallbackIcon ? ((0, jsx_runtime_1.jsx)(FallbackIcon, { className: "h-9 w-9 text-gray-900 dark:text-gray-100" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: strategyLabel.charAt(0) })) }, strategy));
18
19
  }
@@ -16,6 +16,7 @@ const SOCIAL_PROVIDERS = [
16
16
  { id: "discord", label: "Discord" },
17
17
  { id: "microsoft", label: "Microsoft" },
18
18
  { id: "slack", label: "Slack" },
19
+ { id: "twitter", label: "X" },
19
20
  ];
20
21
  function LoginStepBetterAuth({ onSuccess, onError, verifyEmailRedirectTo }) {
21
22
  const { partnerId } = (0, react_1.useB3Config)();
@@ -49,6 +49,7 @@ exports.strategyIcons = {
49
49
  google: "https://cdn.b3.fun/google.svg",
50
50
  github: "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg",
51
51
  x: "https://cdn.b3.fun/x.svg?1",
52
+ twitter: "https://cdn.b3.fun/x.svg?1",
52
53
  discord: "https://cdn.b3.fun/discord.svg",
53
54
  apple: "https://cdn.b3.fun/apple.svg",
54
55
  guest: "https://cdn.b3.fun/incognito.svg",
@@ -58,6 +59,7 @@ exports.strategyIcons = {
58
59
  exports.strategyLabels = {
59
60
  google: "Google",
60
61
  x: "X",
62
+ twitter: "X",
61
63
  discord: "Discord",
62
64
  apple: "Apple",
63
65
  guest: "Guest",
@@ -1,4 +1,4 @@
1
- export type BetterAuthSocialProvider = "google" | "discord" | "apple" | "github" | "slack" | "microsoft";
1
+ export type BetterAuthSocialProvider = "google" | "discord" | "apple" | "github" | "slack" | "microsoft" | "twitter";
2
2
  /** Thrown when email verification is required before the user can sign in. */
3
3
  export declare class EmailVerificationRequiredError extends Error {
4
4
  constructor(message?: string);
@@ -617,8 +617,16 @@ export interface AnySpendDepositModalProps extends BaseModalProps {
617
617
  destinationTokenAddress: string;
618
618
  /** The destination chain ID */
619
619
  destinationTokenChainId: number;
620
+ /**
621
+ * When false, the destination token is user-selectable even though a default destination token is
622
+ * provided (the provided destination is used only as the initial/default value). Used by the
623
+ * wallet-funding deposit flow so the user can change the receive token away from the default
624
+ * (Base USDC). Defaults to true (locked destination). Does not affect the QR/pure-transfer path. */
625
+ lockDestinationToken?: boolean;
620
626
  /** Callback when deposit succeeds */
621
627
  onSuccess?: (amount: string) => void;
628
+ /** Callback when the modal's close control is pressed. */
629
+ onClose?: () => void;
622
630
  /** Callback for opening a custom modal (e.g., for special token handling) */
623
631
  onOpenCustomModal?: () => void;
624
632
  /** Custom footer content */
@@ -661,6 +669,12 @@ export interface AnySpendDepositModalProps extends BaseModalProps {
661
669
  classes?: AnySpendAllClasses;
662
670
  /** Whether to allow direct transfer without swap */
663
671
  allowDirectTransfer?: boolean;
672
+ /**
673
+ * When true, the QR-deposit path is a PURE TRANSFER: the destination token/chain
674
+ * mirror the user's selected source token, so the exact token they send lands in
675
+ * their wallet on the same chain (no swap/bridge).
676
+ */
677
+ pureTransferOnly?: boolean;
664
678
  /** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
665
679
  callbackMetadata?: Record<string, unknown>;
666
680
  }
@@ -28,6 +28,13 @@ export declare function AnySpend(props: {
28
28
  sourceChainId?: number;
29
29
  destinationTokenAddress?: string;
30
30
  destinationTokenChainId?: number;
31
+ /**
32
+ * When false, the destination token is user-selectable even if a default destination token is
33
+ * provided (the provided destination is used only as the initial/default value). Used by the
34
+ * wallet-funding deposit flow so the user can change the receive token away from the default
35
+ * (Base USDC). Defaults to true (locked buy-mode display, current behavior).
36
+ */
37
+ lockDestinationToken?: boolean;
31
38
  recipientAddress?: string;
32
39
  loadOrder?: string;
33
40
  hideTransactionHistoryButton?: boolean;
@@ -60,7 +60,7 @@ export function AnySpend(props) {
60
60
  const fingerprintConfig = getFingerprintConfig();
61
61
  return (_jsx(AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: _jsx(AnySpendCustomizationProvider, { slots: props.slots, content: props.content, theme: props.theme, children: _jsx(AnySpendInner, { ...props }) }) }));
62
62
  }
63
- function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, }) {
63
+ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, lockDestinationToken = true, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, }) {
64
64
  const { slots, content } = useAnySpendCustomization();
65
65
  const searchParams = useSearchParamsSSR();
66
66
  const router = useRouter();
@@ -74,8 +74,10 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
74
74
  // in the same frame that onStatusResolved sets it (setState is async).
75
75
  // When kycEnabled is false (default), pre-approve so the KYC gate is skipped.
76
76
  const kycApprovedRef = useRef(!kycEnabled);
77
- // Determine if we're in "buy mode" based on whether destination token props are provided
78
- const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId);
77
+ // Determine if we're in "buy mode" based on whether destination token props are provided.
78
+ // When lockDestinationToken is false, the provided destination is only a default and the user
79
+ // can change the receive token, so we stay out of buy mode (selectable swap mode).
80
+ const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId) && lockDestinationToken;
79
81
  // Add refs to track URL state
80
82
  const initialUrlProcessed = useRef(false);
81
83
  const lastUrlUpdate = useRef(null);
@@ -135,9 +137,14 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
135
137
  useEffect(() => {
136
138
  sessionStorage.setItem("anyspend_fiat_method", selectedFiatPaymentMethod);
137
139
  }, [selectedFiatPaymentMethod]);
140
+ // Whether a default destination token was provided. When lockDestinationToken is false this is
141
+ // used only as the INITIAL/default value (the user can still change the receive token), so the
142
+ // wallet-funding deposit flow defaults to Base USDC but stays selectable.
143
+ const hasProvidedDestination = !!(destinationTokenAddress && destinationTokenChainId);
138
144
  // Get initial chain IDs from URL or defaults
139
145
  const initialSrcChainId = sourceChainId || parseInt(searchParams.get("fromChainId") || "0") || mainnet.id;
140
- const initialDstChainId = parseInt(searchParams.get("toChainId") || "0") || (isBuyMode ? destinationTokenChainId : base.id);
146
+ const initialDstChainId = parseInt(searchParams.get("toChainId") || "0") ||
147
+ (isBuyMode ? destinationTokenChainId : hasProvidedDestination ? destinationTokenChainId : base.id);
141
148
  // State for source chain/token selection
142
149
  const [selectedSrcChainId, setSelectedSrcChainId] = useState(initialSrcChainId);
143
150
  const defaultSrcToken = getDefaultToken(selectedSrcChainId);
@@ -174,19 +181,25 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
174
181
  });
175
182
  // Helper to check if address is Hyperliquid USDC (supports both 34-char and 42-char formats)
176
183
  const isHyperliquidUSDCAddress = (address) => eqci(address, HYPERLIQUID_USDC_ADDRESS) || eqci(address, ZERO_ADDRESS);
177
- const defaultDstToken = isBuyMode
178
- ? // Special case: Hyperliquid uses zero address for USDC
179
- destinationTokenChainId === HYPERLIQUID_CHAIN_ID && isHyperliquidUSDCAddress(destinationTokenAddress)
180
- ? getHyperliquidUSDCToken()
181
- : {
182
- symbol: "",
183
- chainId: destinationTokenChainId,
184
- address: destinationTokenAddress,
185
- name: "",
186
- decimals: 18,
187
- metadata: {},
188
- }
189
- : getDefaultToken(selectedDstChainId);
184
+ // Build a token object from the provided destination props (handles the Hyperliquid USDC special case).
185
+ // The inline truthiness check lets TypeScript narrow both props to non-null, so it is `undefined`
186
+ // unless both props are provided — i.e. only meaningful when hasProvidedDestination/isBuyMode is true.
187
+ const providedDstToken = destinationTokenChainId && destinationTokenAddress
188
+ ? destinationTokenChainId === HYPERLIQUID_CHAIN_ID && isHyperliquidUSDCAddress(destinationTokenAddress)
189
+ ? getHyperliquidUSDCToken()
190
+ : {
191
+ symbol: "",
192
+ chainId: destinationTokenChainId,
193
+ address: destinationTokenAddress,
194
+ name: "",
195
+ decimals: 18,
196
+ metadata: {},
197
+ }
198
+ : undefined;
199
+ // In buy mode the provided destination is locked. Otherwise, if a default destination was provided
200
+ // (selectable deposit flow) seed it as the default; falling back to the chain's default token only
201
+ // when no destination prop is given (standard swap mode).
202
+ const defaultDstToken = (isBuyMode || hasProvidedDestination) && providedDstToken ? providedDstToken : getDefaultToken(selectedDstChainId);
190
203
  const dstTokenFromUrl = useTokenFromUrl({
191
204
  defaultToken: defaultDstToken,
192
205
  prefix: "to",