@b3dotfun/sdk 0.1.70-alpha.12 → 0.1.70-alpha.14

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 (37) 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/B3Provider/B3ConfigProvider.d.ts +11 -0
  8. package/dist/cjs/global-account/react/components/B3Provider/B3ConfigProvider.js +12 -0
  9. package/dist/cjs/global-account/react/components/index.d.ts +1 -0
  10. package/dist/cjs/global-account/react/components/index.js +5 -3
  11. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +14 -0
  12. package/dist/esm/anyspend/react/components/AnySpend.d.ts +7 -0
  13. package/dist/esm/anyspend/react/components/AnySpend.js +30 -17
  14. package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
  15. package/dist/esm/anyspend/react/components/AnySpendDeposit.js +51 -14
  16. package/dist/esm/anyspend/react/components/QRDeposit.d.ts +14 -3
  17. package/dist/esm/anyspend/react/components/QRDeposit.js +25 -16
  18. package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +11 -0
  19. package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.js +11 -0
  20. package/dist/esm/global-account/react/components/index.d.ts +1 -0
  21. package/dist/esm/global-account/react/components/index.js +1 -0
  22. package/dist/esm/global-account/react/stores/useModalStore.d.ts +14 -0
  23. package/dist/styles/index.css +1 -1
  24. package/dist/types/anyspend/react/components/AnySpend.d.ts +7 -0
  25. package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
  26. package/dist/types/anyspend/react/components/QRDeposit.d.ts +14 -3
  27. package/dist/types/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +11 -0
  28. package/dist/types/global-account/react/components/index.d.ts +1 -0
  29. package/dist/types/global-account/react/stores/useModalStore.d.ts +14 -0
  30. package/package.json +1 -1
  31. package/src/anyspend/react/components/AnySpend.tsx +42 -16
  32. package/src/anyspend/react/components/AnySpendDeposit.tsx +87 -12
  33. package/src/anyspend/react/components/QRDeposit.tsx +46 -18
  34. package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +256 -0
  35. package/src/global-account/react/components/B3Provider/B3ConfigProvider.tsx +12 -0
  36. package/src/global-account/react/components/index.ts +1 -0
  37. package/src/global-account/react/stores/useModalStore.ts +14 -0
@@ -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;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { USDC_BASE } from "../../../anyspend/index.js";
2
3
  import { Skeleton, useAccountWallet, useSimBalance, useTokenData } from "../../../global-account/react/index.js";
3
4
  import { cn } from "../../../shared/utils/cn.js";
4
5
  import { NetworkArbitrumOne, NetworkBase, NetworkBinanceSmartChain, NetworkEthereum, NetworkOptimism, NetworkPolygonPos, } from "@web3icons/react";
5
6
  import { ChevronRight } from "lucide-react";
6
- import { useEffect, useMemo, useState } from "react";
7
+ import { useCallback, useEffect, useMemo, useState } from "react";
7
8
  import { AnySpend } from "./AnySpend.js";
8
9
  import { AnySpendCustomExactIn } from "./AnySpendCustomExactIn.js";
9
10
  import { ChainWarningText } from "./common/WarningText.js";
@@ -21,6 +22,19 @@ const DEFAULT_SUPPORTED_CHAINS = [
21
22
  ];
22
23
  // Minimum pool size to filter out low liquidity tokens
23
24
  const DEFAULT_MIN_POOL_SIZE = 1000000;
25
+ /**
26
+ * Self-described fallback metadata for the default funding token(s), keyed by
27
+ * `${chainId}:${lowercased address}`. Used when `useTokenData` misses (returns
28
+ * null) so the destination token's decimals/symbol are correct WITHOUT depending
29
+ * on the network — critical in pure-transfer mode where there's no server-side
30
+ * correction and the wrong decimals would make `useWatchTransfer` show a wrong amount.
31
+ */
32
+ const KNOWN_FUNDING_TOKENS = {
33
+ [`${USDC_BASE.chainId}:${USDC_BASE.address.toLowerCase()}`]: USDC_BASE,
34
+ };
35
+ function getKnownFundingToken(chainId, address) {
36
+ return KNOWN_FUNDING_TOKENS[`${chainId}:${address.toLowerCase()}`];
37
+ }
24
38
  function formatUsd(value) {
25
39
  return new Intl.NumberFormat("en-US", {
26
40
  style: "currency",
@@ -94,7 +108,7 @@ function ChainIcon({ chainId, className }) {
94
108
  * onSuccess={(amount) => console.log(`Deposited ${amount}`)}
95
109
  * />
96
110
  */
97
- export 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, }) {
111
+ export 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, }) {
98
112
  // Extract deposit-specific classes for convenience
99
113
  const depositClasses = classes?.deposit;
100
114
  const { connectedEOAWallet } = useAccountWallet();
@@ -111,16 +125,22 @@ export function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, p
111
125
  }
112
126
  }, [showFiatOption, paymentType]);
113
127
  // Fetch destination token data
114
- const { data: destinationTokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
115
- // Construct full destination token object
116
- const destinationToken = useMemo(() => ({
117
- address: destinationTokenAddress,
118
- chainId: destinationTokenChainId,
119
- symbol: destinationTokenData?.symbol ?? "",
120
- name: destinationTokenData?.name ?? "",
121
- decimals: destinationTokenData?.decimals ?? 18,
122
- metadata: { logoURI: destinationTokenData?.logoURI },
123
- }), [destinationTokenAddress, destinationTokenChainId, destinationTokenData]);
128
+ const { data: destinationTokenData, isLoading: isDestinationTokenLoading } = useTokenData(destinationTokenChainId, destinationTokenAddress);
129
+ // Construct full destination token object. When `useTokenData` misses (it returns
130
+ // null on an API miss, not an error), fall back to a known-token entry so the
131
+ // decimals/symbol invariant holds regardless of the fetch — e.g. Base USDC is 6
132
+ // decimals, never the generic 18 fallback.
133
+ const destinationToken = useMemo(() => {
134
+ const known = getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
135
+ return {
136
+ address: destinationTokenAddress,
137
+ chainId: destinationTokenChainId,
138
+ symbol: destinationTokenData?.symbol ?? known?.symbol ?? "",
139
+ name: destinationTokenData?.name ?? known?.name ?? "",
140
+ decimals: destinationTokenData?.decimals ?? known?.decimals ?? 18,
141
+ metadata: { logoURI: destinationTokenData?.logoURI ?? known?.metadata?.logoURI },
142
+ };
143
+ }, [destinationTokenAddress, destinationTokenChainId, destinationTokenData]);
124
144
  // Fetch balances for EOA wallet (use senderAddress as fallback for pre-filled balance display)
125
145
  const effectiveBalanceAddress = senderAddress || eoaAddress;
126
146
  const { data: balanceData, isLoading: isBalanceLoading } = useSimBalance(shouldShowChainSelection ? effectiveBalanceAddress : undefined, supportedChains.map(c => c.id));
@@ -162,6 +182,9 @@ export function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, p
162
182
  const totalBalance = useMemo(() => {
163
183
  return Object.values(chainBalances).reduce((sum, chain) => sum + chain.totalUsdValue, 0);
164
184
  }, [chainBalances]);
185
+ const handleQRDepositSuccess = useCallback((txHash) => {
186
+ onSuccess?.(txHash ?? "");
187
+ }, [onSuccess]);
165
188
  if (!recipientAddress)
166
189
  return null;
167
190
  const tokenSymbol = destinationToken.symbol || "TOKEN";
@@ -208,10 +231,24 @@ export function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, p
208
231
  }
209
232
  // QR Deposit view
210
233
  if (step === "qr-deposit") {
211
- return (_jsx(QRDeposit, { mode: mode, recipientAddress: recipientAddress, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, depositContractConfig: depositContractConfig, onBack: handleBack, onClose: onClose ?? handleBack, classes: classes?.qrDeposit }));
234
+ // In pure-transfer mode QRDeposit captures `destinationToken` as its initial
235
+ // SOURCE token via useState, so it must be fully resolved (correct symbol +
236
+ // decimals) before QRDeposit mounts. Known funding tokens (e.g. Base USDC) are
237
+ // already fully resolved via the self-described fallback, so only show a spinner
238
+ // for unknown tokens whose metadata still has to load over the network.
239
+ const hasKnownDestination = !!getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
240
+ // NOTE: all current Fund Wallet callers derive `destinationTokenAddress` from
241
+ // `getDefaultDepositDestination`, which always returns Base USDC — exactly a
242
+ // `KNOWN_FUNDING_TOKENS` key — so `hasKnownDestination` is always true and this
243
+ // branch never fires for them. The guard is defensive for FUTURE callers that pass
244
+ // an unknown token together with `pureTransferOnly=true`.
245
+ if (pureTransferOnly && !hasKnownDestination && !destinationTokenData && isDestinationTokenLoading) {
246
+ return (_jsx("div", { className: 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: _jsxs("div", { className: "anyspend-deposit-qr-loading-content flex flex-col items-center justify-center gap-4 py-12", children: [_jsx(Skeleton, { className: "h-8 w-8 rounded-full" }), _jsx(Skeleton, { className: "h-4 w-40" })] }) }));
247
+ }
248
+ return (_jsx(QRDeposit, { mode: mode, recipientAddress: recipientAddress, destinationToken: destinationToken, destinationChainId: destinationTokenChainId, pureTransferOnly: pureTransferOnly, depositContractConfig: depositContractConfig, onBack: handleBack, onClose: onClose ?? handleBack, onSuccess: handleQRDepositSuccess, classes: classes?.qrDeposit }));
212
249
  }
213
250
  // Deposit view
214
251
  return (_jsxs("div", { className: depositClasses?.form || "anyspend-deposit anyspend-deposit-form relative", children: [shouldShowChainSelection && (_jsxs("button", { onClick: handleBack, className: depositClasses?.backButton ||
215
252
  "anyspend-deposit-back-button text-as-secondary hover:text-as-primary absolute left-4 top-4 z-10 flex items-center gap-1", children: [_jsx("svg", { className: depositClasses?.backIcon || "anyspend-deposit-back-icon h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }), _jsx("span", { className: depositClasses?.backText || "anyspend-deposit-back-text text-sm", children: "Back" })] })), onClose && (_jsx("button", { onClick: onClose, className: depositClasses?.closeButton ||
216
- "anyspend-deposit-close-button text-as-secondary hover:text-as-primary absolute right-4 top-4 z-10", children: _jsx("svg", { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })), _jsx("div", { className: depositClasses?.formContent || cn("anyspend-deposit-form-content", shouldShowChainSelection && "pt-8"), children: isCustomDeposit ? (_jsx(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)) : (_jsx(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)) }), _jsx(ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
253
+ "anyspend-deposit-close-button text-as-secondary hover:text-as-primary absolute right-4 top-4 z-10", children: _jsx("svg", { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })), _jsx("div", { className: depositClasses?.formContent || cn("anyspend-deposit-form-content", shouldShowChainSelection && "pt-8"), children: isCustomDeposit ? (_jsx(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)) : (_jsx(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)) }), _jsx(ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
217
254
  }
@@ -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;
@@ -5,7 +5,7 @@ import { cn } from "../../../shared/utils/cn.js";
5
5
  import { TokenSelector } from "@relayprotocol/relay-kit-ui";
6
6
  import { Check, ChevronsUpDown, Copy, Loader2 } from "lucide-react";
7
7
  import { QRCodeSVG } from "qrcode.react";
8
- import { useEffect, useRef, useState } from "react";
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
9
  import { useAnyspendOrderAndTransactions } from "../hooks/useAnyspendOrderAndTransactions.js";
10
10
  import { useCreateDepositFirstOrder } from "../hooks/useCreateDepositFirstOrder.js";
11
11
  import { useOnOrderSuccess } from "../hooks/useOnOrderSuccess.js";
@@ -39,28 +39,35 @@ const DEFAULT_ETH_ON_BASE = {
39
39
  * onSuccess={(txHash) => console.log("Deposit complete:", txHash)}
40
40
  * />
41
41
  */
42
- export function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }) {
42
+ export function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, pureTransferOnly = false, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }) {
43
43
  const [copied, setCopied] = useState(false);
44
44
  const [orderId, setOrderId] = useState();
45
45
  const [globalAddress, setGlobalAddress] = useState();
46
46
  const orderCreatedRef = useRef(false);
47
47
  const [transferResult, setTransferResult] = useState(null);
48
- // Source token/chain as state (can be changed by user)
49
- const [sourceChainId, setSourceChainId] = useState(sourceChainIdProp ?? 8453);
50
- const [sourceToken, setSourceToken] = useState(sourceTokenProp ?? DEFAULT_ETH_ON_BASE);
48
+ // Source token/chain as state (can be changed by user).
49
+ // In pure-transfer mode the initial source is the passed destination (caller sets
50
+ // it to the desired default funding token), so the deposit mirrors the user's selection.
51
+ const [sourceChainId, setSourceChainId] = useState(sourceChainIdProp ?? (pureTransferOnly ? destinationChainId : 8453));
52
+ const [sourceToken, setSourceToken] = useState(sourceTokenProp ?? (pureTransferOnly ? destinationToken : DEFAULT_ETH_ON_BASE));
53
+ // In pure-transfer mode the effective destination mirrors the selected source,
54
+ // forcing isPureTransfer = true (no swap/bridge order is created).
55
+ const effectiveDestinationToken = pureTransferOnly ? sourceToken : destinationToken;
56
+ const effectiveDestinationChainId = pureTransferOnly ? sourceChainId : destinationChainId;
51
57
  // Check if this is a pure transfer (same chain and token)
52
- const isPureTransfer = isSameChainAndToken(sourceChainId, sourceToken.address, destinationChainId, destinationToken.address);
58
+ const isPureTransfer = isSameChainAndToken(sourceChainId, sourceToken.address, effectiveDestinationChainId, effectiveDestinationToken.address);
59
+ const handleTransferDetected = useCallback((result) => {
60
+ setTransferResult(result);
61
+ onSuccess?.(result.formattedAmount);
62
+ }, [onSuccess]);
53
63
  // Watch for pure transfers (same chain and token)
54
- const { isWatching: isWatchingTransfer } = useWatchTransfer({
64
+ const { isWatching: isWatchingTransfer, reset: resetWatchTransfer } = useWatchTransfer({
55
65
  address: recipientAddress,
56
66
  chainId: sourceChainId,
57
67
  tokenAddress: sourceToken.address,
58
68
  tokenDecimals: sourceToken.decimals,
59
69
  enabled: isPureTransfer && !transferResult,
60
- onTransferDetected: result => {
61
- setTransferResult(result);
62
- onSuccess?.();
63
- },
70
+ onTransferDetected: handleTransferDetected,
64
71
  });
65
72
  // Handle token selection from TokenSelector
66
73
  const handleTokenSelect = (newToken) => {
@@ -77,6 +84,8 @@ export function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourc
77
84
  setGlobalAddress(undefined);
78
85
  orderCreatedRef.current = false;
79
86
  setTransferResult(null);
87
+ // Reset the balance watcher baseline so the new token isn't compared against the old token's balance
88
+ resetWatchTransfer();
80
89
  // Update token and chain
81
90
  setSourceChainId(newToken.chainId);
82
91
  setSourceToken(token);
@@ -107,18 +116,18 @@ export function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourc
107
116
  createOrder({
108
117
  recipientAddress,
109
118
  srcChain: sourceChainId,
110
- dstChain: destinationChainId,
119
+ dstChain: effectiveDestinationChainId,
111
120
  srcToken: sourceToken,
112
- dstToken: destinationToken,
121
+ dstToken: effectiveDestinationToken,
113
122
  creatorAddress,
114
123
  contractConfig: depositContractConfig,
115
124
  });
116
125
  }, [
117
126
  recipientAddress,
118
127
  sourceChainId,
119
- destinationChainId,
128
+ effectiveDestinationChainId,
120
129
  sourceToken,
121
- destinationToken,
130
+ effectiveDestinationToken,
122
131
  creatorAddress,
123
132
  depositContractConfig,
124
133
  createOrder,
@@ -163,7 +172,7 @@ export function QRDeposit({ mode = "modal", recipientAddress, sourceToken: sourc
163
172
  return (_jsx("div", { className: classes?.container ||
164
173
  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: _jsxs("div", { className: classes?.content || "anyspend-qr-deposit-content flex flex-col gap-4", children: [_jsxs("div", { className: classes?.header || "anyspend-qr-header flex items-center justify-between", children: [_jsx("button", { onClick: handleBack, className: classes?.backButton || "anyspend-qr-back-button text-as-secondary hover:text-as-primary", children: _jsx("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }), _jsx("h2", { className: classes?.title || "anyspend-qr-title text-as-primary text-base font-semibold", children: "Deposit" }), onClose ? (_jsx("button", { onClick: handleClose, className: classes?.closeButton || "anyspend-qr-close-button text-as-secondary hover:text-as-primary", children: _jsx("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })) : (_jsx("div", { className: "w-5" }))] }), _jsxs("div", { className: classes?.tokenSelectorContainer || "anyspend-qr-token-selector flex flex-col gap-1.5", children: [_jsx("label", { className: classes?.tokenSelectorLabel || "anyspend-qr-token-label text-as-secondary text-sm", children: "Send" }), _jsx(TokenSelector, { chainIdsFilter: getAvailableChainIds("from"), context: "from", fromChainWalletVMSupported: true, isValidAddress: true, lockedChainIds: getAvailableChainIds("from"), multiWalletSupportEnabled: true, onAnalyticEvent: undefined, setToken: handleTokenSelect, supportedWalletVMs: ["evm"], token: undefined, trigger: _jsxs(Button, { variant: "outline", role: "combobox", className: classes?.tokenSelectorTrigger ||
165
174
  "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: [_jsxs("div", { className: "flex items-center gap-2", children: [sourceToken.metadata?.logoURI ? (_jsx(ChainTokenIcon, { chainUrl: ALL_CHAINS[sourceChainId]?.logoUrl, tokenUrl: sourceToken.metadata.logoURI, className: "h-8 min-h-8 w-8 min-w-8" })) : (_jsx("div", { className: "h-8 w-8 rounded-full bg-gray-700" })), _jsxs("div", { className: "flex flex-col items-start gap-0", children: [_jsx("div", { className: "text-as-primary font-semibold", children: sourceToken.symbol }), _jsx("div", { className: "text-as-primary/70 text-xs", children: ALL_CHAINS[sourceChainId]?.name ?? "Unknown" })] })] }), _jsx(ChevronsUpDown, { className: "h-4 w-4 shrink-0 opacity-70" })] }) })] }), _jsxs("div", { className: classes?.qrContent || "anyspend-qr-content border-as-stroke flex items-start gap-4 rounded-xl border p-4", children: [_jsxs("div", { className: classes?.qrCodeContainer || "anyspend-qr-code-container flex flex-col items-center gap-2", children: [_jsx("div", { className: classes?.qrCode || "anyspend-qr-code rounded-lg bg-white p-2", children: _jsx(QRCodeSVG, { value: qrValue, size: 120, level: "M", marginSize: 0 }) }), _jsxs("span", { className: classes?.qrScanHint || "anyspend-qr-scan-hint text-as-secondary text-xs", children: ["SCAN WITH ", _jsx("span", { className: "inline-block", children: "\uD83E\uDD8A" })] })] }), _jsxs("div", { className: classes?.addressContainer || "anyspend-qr-address-container flex flex-1 flex-col gap-1", children: [_jsx("span", { className: classes?.addressLabel || "anyspend-qr-address-label text-as-secondary text-sm", children: "Deposit address:" }), _jsxs("div", { className: classes?.addressRow || "anyspend-qr-address-row flex items-start gap-1", children: [_jsx("span", { className: classes?.address || "anyspend-qr-address text-as-primary break-all font-mono text-sm leading-relaxed", children: displayAddress }), _jsx("button", { onClick: handleCopyAddress, className: classes?.addressCopyIcon ||
166
- "anyspend-qr-copy-icon text-as-secondary hover:text-as-primary mt-0.5 shrink-0", children: copied ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(Copy, { className: "h-4 w-4" }) })] })] })] }), _jsx(ChainWarningText, { chainId: destinationChainId }), _jsxs(WarningText, { children: ["Only send ", sourceToken.symbol, " on ", ALL_CHAINS[sourceChainId]?.name ?? "the specified chain", ". Other tokens will not be converted."] }), isPureTransfer && isWatchingTransfer && (_jsxs("div", { className: classes?.watchingIndicator ||
175
+ "anyspend-qr-copy-icon text-as-secondary hover:text-as-primary mt-0.5 shrink-0", children: copied ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(Copy, { className: "h-4 w-4" }) })] })] })] }), _jsx(ChainWarningText, { chainId: effectiveDestinationChainId }), _jsxs(WarningText, { children: ["Only send ", sourceToken.symbol, " on ", ALL_CHAINS[sourceChainId]?.name ?? "the specified chain", ". Other tokens will not be converted."] }), isPureTransfer && isWatchingTransfer && (_jsxs("div", { className: classes?.watchingIndicator ||
167
176
  "anyspend-qr-watching flex items-center justify-center gap-2 rounded-lg bg-blue-500/10 p-3", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-blue-500" }), _jsx("span", { className: "text-sm text-blue-500", children: "Watching for incoming transfer..." })] })), _jsx("button", { onClick: handleCopyAddress, className: classes?.copyButton ||
168
177
  "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" })] }) }));
169
178
  }
@@ -30,3 +30,14 @@ export declare function B3ConfigProvider({ children, accountOverride, environmen
30
30
  authStrategy?: AuthStrategy;
31
31
  }): import("react/jsx-runtime").JSX.Element;
32
32
  export declare function useB3Config(): B3ConfigContextType;
33
+ /**
34
+ * Overrides ONLY `partnerId` for a subtree, inheriting every other config value from the parent
35
+ * B3ConfigProvider. Lets a single route attribute its AnySpend orders/quotes to a different partner
36
+ * (e.g. `/b3os-deposit` → the b3os partner) without standing up a second B3Provider — the config
37
+ * context is separate from the wallet/modal machinery, so nothing re-initializes. Must be rendered
38
+ * inside an existing B3ConfigProvider.
39
+ */
40
+ export declare function B3ConfigPartnerOverride({ partnerId, children }: {
41
+ partnerId: string;
42
+ children: React.ReactNode;
43
+ }): import("react/jsx-runtime").JSX.Element;
@@ -31,3 +31,14 @@ export function useB3Config() {
31
31
  }
32
32
  return context;
33
33
  }
34
+ /**
35
+ * Overrides ONLY `partnerId` for a subtree, inheriting every other config value from the parent
36
+ * B3ConfigProvider. Lets a single route attribute its AnySpend orders/quotes to a different partner
37
+ * (e.g. `/b3os-deposit` → the b3os partner) without standing up a second B3Provider — the config
38
+ * context is separate from the wallet/modal machinery, so nothing re-initializes. Must be rendered
39
+ * inside an existing B3ConfigProvider.
40
+ */
41
+ export function B3ConfigPartnerOverride({ partnerId, children }) {
42
+ const parent = useB3Config();
43
+ return _jsx(B3ConfigContext.Provider, { value: { ...parent, partnerId }, children: children });
44
+ }
@@ -5,6 +5,7 @@ export { RelayKitProviderWrapper } from "./B3Provider/RelayKitProviderWrapper";
5
5
  export { useB3 } from "./B3Provider/useB3";
6
6
  export { useB3Account } from "./B3Provider/useB3Account";
7
7
  export { useB3Config } from "./B3Provider/useB3Config";
8
+ export { B3ConfigPartnerOverride } from "./B3Provider/B3ConfigProvider";
8
9
  export { StyleRoot } from "./StyleRoot";
9
10
  export { BetterAuthResetPassword, type BetterAuthResetPasswordProps } from "./SignInWithB3/BetterAuthResetPassword";
10
11
  export { BetterAuthSignIn, type BetterAuthSignInProps } from "./SignInWithB3/BetterAuthSignIn";
@@ -6,6 +6,7 @@ export { RelayKitProviderWrapper } from "./B3Provider/RelayKitProviderWrapper.js
6
6
  export { useB3 } from "./B3Provider/useB3.js";
7
7
  export { useB3Account } from "./B3Provider/useB3Account.js";
8
8
  export { useB3Config } from "./B3Provider/useB3Config.js";
9
+ export { B3ConfigPartnerOverride } from "./B3Provider/B3ConfigProvider.js";
9
10
  export { StyleRoot } from "./StyleRoot.js";
10
11
  // SignInWithB3 Components
11
12
  export { BetterAuthResetPassword } from "./SignInWithB3/BetterAuthResetPassword.js";
@@ -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
  }