@b3dotfun/sdk 0.1.70-alpha.21 → 0.1.70-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/anyspend/react/components/AnySpend.d.ts +19 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +45 -15
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +9 -1
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +6 -2
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +6 -2
- package/dist/cjs/global-account/react/components/B3Provider/BetterAuthProvider.js +36 -12
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +19 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +45 -15
- package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +9 -1
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +6 -2
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +6 -2
- package/dist/esm/global-account/react/components/B3Provider/BetterAuthProvider.js +36 -12
- package/dist/types/anyspend/react/components/AnySpend.d.ts +19 -0
- package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +9 -1
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +66 -14
- package/src/anyspend/react/components/AnySpendDeposit.tsx +14 -0
- package/src/anyspend/react/components/__tests__/AnySpend.seedEffect.test.tsx +191 -0
- package/src/anyspend/react/components/__tests__/AnySpendDeposit.isDepositMode.test.tsx +117 -0
- package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +23 -2
- package/src/anyspend/react/components/common/__tests__/CryptoReceiveSection.test.tsx +110 -0
- package/src/global-account/react/components/B3Provider/BetterAuthProvider.tsx +35 -11
|
@@ -64,6 +64,13 @@ export declare function AnySpend(props: {
|
|
|
64
64
|
allowDirectTransfer?: boolean;
|
|
65
65
|
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
66
66
|
destinationTokenAmount?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Editable default destination token amount (in wei/smallest unit). Seeds the same
|
|
69
|
+
* destination-amount input as `destinationTokenAmount`, but leaves it user-editable. Ignored
|
|
70
|
+
* when `destinationTokenAmount` is also set — that prop locks the field and wins.
|
|
71
|
+
* Seeds the field once on first apply; a later change to this value does not re-fill it, so it never overwrites what the user typed.
|
|
72
|
+
*/
|
|
73
|
+
defaultDestinationTokenAmount?: string;
|
|
67
74
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
68
75
|
callbackMetadata?: Record<string, unknown>;
|
|
69
76
|
/** Optional sender (payer) address — pre-fills token balances when the user address is known ahead of time */
|
|
@@ -78,4 +85,16 @@ export declare function AnySpend(props: {
|
|
|
78
85
|
kycEnabled?: boolean;
|
|
79
86
|
/** Whether to show the "Pay with Fiat" tab. Defaults to true. When false, the fiat tab is hidden and activeTab is forced to "crypto". */
|
|
80
87
|
showFiatOption?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Internal: true when `AnySpendDeposit` has an amount to prefill (`defaultDestinationTokenAmount`
|
|
90
|
+
* set), as opposed to any other `AnySpend`/`AnySpendDeposit` usage. Only changes
|
|
91
|
+
* `CryptoReceiveSection`'s fixed-destination branch: it lets the receive amount become an
|
|
92
|
+
* editable input (still gated by `disableAmountInput`) instead of a read-only display. Scoped
|
|
93
|
+
* this narrowly on purpose — `AnySpendDeposit` backs 15+ funding modals that are source-exact-in
|
|
94
|
+
* (the user sets the "pay with" amount; the receive side is an intended computed display), so
|
|
95
|
+
* this must stay off for all of them and only turn on for the one page that actually prefills a
|
|
96
|
+
* receive amount. Not intended for external callers — `AnySpendDeposit` derives this itself.
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
isDepositMode?: boolean;
|
|
81
100
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -69,7 +69,7 @@ function AnySpend(props) {
|
|
|
69
69
|
const fingerprintConfig = (0, AnySpendFingerprintWrapper_1.getFingerprintConfig)();
|
|
70
70
|
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 }) }) }));
|
|
71
71
|
}
|
|
72
|
-
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, }) {
|
|
72
|
+
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, defaultDestinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, isDepositMode = false, }) {
|
|
73
73
|
const { slots, content } = (0, AnySpendCustomizationContext_1.useAnySpendCustomization)();
|
|
74
74
|
const searchParams = (0, react_2.useSearchParamsSSR)();
|
|
75
75
|
const router = (0, react_2.useRouter)();
|
|
@@ -237,6 +237,21 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
237
237
|
sessionStorage.setItem("anyspend_dst_token", JSON.stringify(selectedDstToken));
|
|
238
238
|
}, [selectedDstToken, isBuyMode]);
|
|
239
239
|
const [isSrcInputDirty, setIsSrcInputDirty] = (0, react_4.useState)(true);
|
|
240
|
+
// Synchronous mirror of `isSrcInputDirty`. `setIsSrcInputDirty` is async relative to OTHER
|
|
241
|
+
// effects in the same commit — the quote effect (declared later, same commit) would otherwise
|
|
242
|
+
// read a pre-flip closure value and clear a just-seeded receive amount before it ever paints.
|
|
243
|
+
// Every flip goes through `markSrcDirty`, which writes this ref in the same statement, so
|
|
244
|
+
// same-commit siblings always see the latest intent. Deliberately NOT kept in sync by its own
|
|
245
|
+
// effect: an effect reading `isSrcInputDirty` from its closure would itself go stale across
|
|
246
|
+
// React 18 StrictMode's dev-mode double-invoke of mount effects (same closure re-run twice)
|
|
247
|
+
// and re-stomp this ref back to the pre-flip value between the two passes.
|
|
248
|
+
const isSrcInputDirtyRef = (0, react_4.useRef)(isSrcInputDirty);
|
|
249
|
+
// Single writer for the direction flag: keeps state and its synchronous ref mirror in lockstep
|
|
250
|
+
// so no call site — parent or child (CryptoPaySection) — can flip one without the other.
|
|
251
|
+
const markSrcDirty = (0, react_4.useCallback)((next) => {
|
|
252
|
+
setIsSrcInputDirty(next);
|
|
253
|
+
isSrcInputDirtyRef.current = next;
|
|
254
|
+
}, []);
|
|
240
255
|
// Add refs to track if we've applied metadata
|
|
241
256
|
const appliedSrcMetadataRef = (0, react_4.useRef)(false);
|
|
242
257
|
const appliedDstMetadataRef = (0, react_4.useRef)(false);
|
|
@@ -289,17 +304,24 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
289
304
|
(0, react_4.useEffect)(() => {
|
|
290
305
|
appliedDstMetadataRef.current = false;
|
|
291
306
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
292
|
-
// Prefill destination amount if provided
|
|
307
|
+
// Prefill destination amount if provided — fixed (locking) mode wins over the editable default,
|
|
308
|
+
// since `destinationTokenAmount` also disables the input (see `disableAmountInput` below).
|
|
293
309
|
const appliedDestinationAmount = (0, react_4.useRef)(false);
|
|
310
|
+
const seedDestinationAmount = destinationTokenAmount ?? defaultDestinationTokenAmount;
|
|
294
311
|
(0, react_4.useEffect)(() => {
|
|
295
312
|
// Only apply when we have real metadata (not default decimals)
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
313
|
+
if (seedDestinationAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
314
|
+
try {
|
|
315
|
+
const formattedAmount = (0, viem_1.formatUnits)(BigInt(seedDestinationAmount), dstTokenMetadata.decimals);
|
|
316
|
+
appliedDestinationAmount.current = true;
|
|
317
|
+
setDstAmount(formattedAmount);
|
|
318
|
+
markSrcDirty(false); // Switch to EXACT_OUTPUT mode
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// invalid seed value — no-op rather than throw into the effect (fail-closed)
|
|
322
|
+
}
|
|
301
323
|
}
|
|
302
|
-
}, [
|
|
324
|
+
}, [seedDestinationAmount, dstTokenMetadata, markSrcDirty]);
|
|
303
325
|
// Load swap configuration from URL on initial render
|
|
304
326
|
(0, react_4.useEffect)(() => {
|
|
305
327
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -322,7 +344,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
322
344
|
}
|
|
323
345
|
else {
|
|
324
346
|
setSrcAmount(fromAmountParam);
|
|
325
|
-
|
|
347
|
+
markSrcDirty(true);
|
|
326
348
|
}
|
|
327
349
|
}
|
|
328
350
|
}
|
|
@@ -336,7 +358,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
336
358
|
}
|
|
337
359
|
// Mark that we've processed the initial URL
|
|
338
360
|
initialUrlProcessed.current = true;
|
|
339
|
-
}, [searchParams, loadOrder, disableUrlParamManagement]);
|
|
361
|
+
}, [searchParams, loadOrder, disableUrlParamManagement, markSrcDirty]);
|
|
340
362
|
// Update URL when swap configuration changes - but not on initial load
|
|
341
363
|
const updateSwapParamsInURL = (0, react_4.useCallback)(() => {
|
|
342
364
|
// Skip if:
|
|
@@ -558,12 +580,20 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
558
580
|
}, [globalAddress, effectiveRecipientAddress, customRecipients.length, setSelectedRecipientAddress]);
|
|
559
581
|
// Update dependent amount when relay price changes
|
|
560
582
|
(0, react_4.useEffect)(() => {
|
|
583
|
+
// Read the ref (not the closed-over `isSrcInputDirty` param) for the branch decision: the
|
|
584
|
+
// SEED effect (declared above, same commit when it fires) writes to the ref synchronously
|
|
585
|
+
// when it flips this to exact-out mode, so this effect sees that flip immediately even
|
|
586
|
+
// though `setIsSrcInputDirty`'s state update won't be visible in THIS render's closure until
|
|
587
|
+
// the next commit. Using the stale closure value here was the actual bug — it caused this
|
|
588
|
+
// effect to still treat a freshly-seeded exact-out amount as exact-in-with-no-data and clear
|
|
589
|
+
// it before it ever painted. The dependency array below stays on the real state, so this
|
|
590
|
+
// effect still re-runs correctly on every genuine transition.
|
|
561
591
|
if (anyspendQuote?.data &&
|
|
562
592
|
anyspendQuote.data.currencyIn?.amount &&
|
|
563
593
|
anyspendQuote.data.currencyIn?.currency?.decimals &&
|
|
564
594
|
anyspendQuote.data.currencyOut?.amount &&
|
|
565
595
|
anyspendQuote.data.currencyOut?.currency?.decimals) {
|
|
566
|
-
if (
|
|
596
|
+
if (isSrcInputDirtyRef.current) {
|
|
567
597
|
// The user is driving the SOURCE (exact-in): reflect the quote's OUTPUT even
|
|
568
598
|
// when a fixed destinationTokenAmount pre-filled the receive. That lock only
|
|
569
599
|
// governs the INITIAL exact-out state (untouched source → compute the pay for
|
|
@@ -581,7 +611,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
581
611
|
}
|
|
582
612
|
}
|
|
583
613
|
else {
|
|
584
|
-
if (
|
|
614
|
+
if (isSrcInputDirtyRef.current) {
|
|
585
615
|
setDstAmount("");
|
|
586
616
|
}
|
|
587
617
|
else {
|
|
@@ -1067,7 +1097,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
1067
1097
|
// Reset payment methods when switching tabs
|
|
1068
1098
|
resetPaymentMethods();
|
|
1069
1099
|
setSelectedFiatPaymentMethod(FiatPaymentMethod_1.FiatPaymentMethod.NONE);
|
|
1070
|
-
}, setSelectedCryptoPaymentMethod: setSelectedCryptoPaymentMethod, setSelectedFiatPaymentMethod: setSelectedFiatPaymentMethod })), (0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [activeTab === "crypto" ? ((0, jsx_runtime_1.jsx)(CryptoPaySection_1.CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty:
|
|
1100
|
+
}, setSelectedCryptoPaymentMethod: setSelectedCryptoPaymentMethod, setSelectedFiatPaymentMethod: setSelectedFiatPaymentMethod })), (0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [activeTab === "crypto" ? ((0, jsx_runtime_1.jsx)(CryptoPaySection_1.CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: markSrcDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => navigateToPanel(PanelView.CRYPTO_PAYMENT_METHOD, "forward"), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, onShowFeeDetail: () => navigateToPanel(PanelView.FEE_DETAIL, "forward") })) : ((0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0, ease: "easeInOut" }, children: (0, jsx_runtime_1.jsx)(PanelOnramp_1.PanelOnramp, { srcAmountOnRamp: srcAmountOnRamp, setSrcAmountOnRamp: setSrcAmountOnRamp, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: (panelIndex) => {
|
|
1071
1101
|
// Map panel index to navigation with direction
|
|
1072
1102
|
const panelsWithForwardNav = [PanelView.FIAT_PAYMENT_METHOD, PanelView.RECIPIENT_SELECTION];
|
|
1073
1103
|
if (panelsWithForwardNav.includes(panelIndex)) {
|
|
@@ -1094,8 +1124,8 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
1094
1124
|
// Swap amounts
|
|
1095
1125
|
setSrcAmount(dstAmount);
|
|
1096
1126
|
setDstAmount(srcAmount);
|
|
1097
|
-
}, children: (0, jsx_runtime_1.jsx)("div", { className: "relative flex items-center justify-center transition-opacity", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) })), activeTab === "crypto" && ((0, jsx_runtime_1.jsx)(CryptoReceiveSection_1.CryptoReceiveSection, { isDepositMode:
|
|
1098
|
-
|
|
1127
|
+
}, children: (0, jsx_runtime_1.jsx)("div", { className: "relative flex items-center justify-center transition-opacity", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) })), activeTab === "crypto" && ((0, jsx_runtime_1.jsx)(CryptoReceiveSection_1.CryptoReceiveSection, { isDepositMode: isDepositMode, isBuyMode: isBuyMode, effectiveRecipientAddress: effectiveRecipientAddress, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => navigateToPanel(PanelView.RECIPIENT_SELECTION, "forward"), dstAmount: dstAmount, dstToken: selectedDstToken, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: setSelectedDstChainId, setSelectedDstToken: setSelectedDstToken, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
1128
|
+
markSrcDirty(false);
|
|
1099
1129
|
setDstAmount(value);
|
|
1100
1130
|
}, disableAmountInput: !!destinationTokenAmount, anyspendQuote: isDirectTransfer ? undefined : anyspendQuote, onShowPointsDetail: isDirectTransfer ? undefined : () => navigateToPanel(PanelView.POINTS_DETAIL, "forward"), onShowFeeDetail: isDirectTransfer ? undefined : () => navigateToPanel(PanelView.FEE_DETAIL, "forward") }))] }), gasPriceData && !isLoadingGas && activeTab === "crypto" && !isDirectTransfer && ((0, jsx_runtime_1.jsx)(GasIndicator_1.GasIndicator, { gasPrice: gasPriceData, className: classes?.gasIndicator || "mt-2 w-full" })), (0, jsx_runtime_1.jsxs)(react_3.motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0.2, ease: "easeInOut" }, className: (0, cn_1.cn)("mt-4 flex w-full max-w-[460px] flex-col gap-2"), children: [slots.actionButton ? (slots.actionButton({
|
|
1101
1131
|
onClick: onMainButtonClick,
|
|
@@ -116,6 +116,14 @@ export interface AnySpendDepositProps {
|
|
|
116
116
|
pureTransferOnly?: boolean;
|
|
117
117
|
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
118
118
|
destinationTokenAmount?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Editable default destination token amount (in wei/smallest unit). Seeds the same
|
|
121
|
+
* destination-amount input as `destinationTokenAmount`, but leaves it user-editable. Ignored
|
|
122
|
+
* when `destinationTokenAmount` is also set — that prop locks the field and wins. Applies to
|
|
123
|
+
* the standard deposit flow only; custom-function deposits ignore it.
|
|
124
|
+
* Seeds the field once on first apply; a later change to this value does not re-fill it, so it never overwrites what the user typed.
|
|
125
|
+
*/
|
|
126
|
+
defaultDestinationTokenAmount?: string;
|
|
119
127
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
120
128
|
callbackMetadata?: Record<string, unknown>;
|
|
121
129
|
/** Optional sender (payer) address — pre-fills token balances when the user address is known ahead of time */
|
|
@@ -162,4 +170,4 @@ export interface AnySpendDepositProps {
|
|
|
162
170
|
* onSuccess={(amount) => console.log(`Deposited ${amount}`)}
|
|
163
171
|
* />
|
|
164
172
|
*/
|
|
165
|
-
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, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, pureTransferOnly, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
|
|
173
|
+
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, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, pureTransferOnly, destinationTokenAmount, defaultDestinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -110,7 +110,7 @@ function ChainIcon({ chainId, className }) {
|
|
|
110
110
|
* onSuccess={(amount) => console.log(`Deposited ${amount}`)}
|
|
111
111
|
* />
|
|
112
112
|
*/
|
|
113
|
-
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, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, pureTransferOnly = false, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
|
|
113
|
+
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, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, pureTransferOnly = false, destinationTokenAmount, defaultDestinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
|
|
114
114
|
// Extract deposit-specific classes for convenience
|
|
115
115
|
const depositClasses = classes?.deposit;
|
|
116
116
|
const { connectedEOAWallet } = (0, react_1.useAccountWallet)();
|
|
@@ -269,5 +269,9 @@ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentT
|
|
|
269
269
|
// Deposit view
|
|
270
270
|
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 ||
|
|
271
271
|
"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 ||
|
|
272
|
-
"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,
|
|
272
|
+
"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,
|
|
273
|
+
// Scoped to ONLY the prefill case (not every AnySpendDeposit caller — 15+ other
|
|
274
|
+
// funding modals rely on the source-exact-in "computed display" behavior this would
|
|
275
|
+
// otherwise change): editable destination amount only when there's a value to seed.
|
|
276
|
+
isDepositMode: !!defaultDestinationTokenAmount, 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, defaultDestinationTokenAmount: defaultDestinationTokenAmount, 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" } })] }));
|
|
273
277
|
}
|
|
@@ -14,8 +14,12 @@ function CryptoReceiveSection({ isDepositMode = false, isBuyMode = false, effect
|
|
|
14
14
|
return ((0, jsx_runtime_1.jsxs)(react_1.motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0.1, ease: "easeInOut" }, className: classes?.container ||
|
|
15
15
|
"receive-section bg-as-surface-secondary border-as-border-secondary relative flex w-full flex-col gap-2 rounded-2xl border p-4 sm:p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex w-full items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { className: classes?.label || "text-as-primary/50 flex h-7 items-center gap-1.5 text-sm", children: [isDepositMode ? "Deposit" : "Receive", isSrcInputDirty && anyspendQuote?.data?.fee && onShowFeeDetail && ((0, jsx_runtime_1.jsx)("button", { onClick: onShowFeeDetail, className: "text-as-primary/40 hover:text-as-primary/60 transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "h-4 w-4" }) }))] }), effectiveRecipientAddress ? ((0, jsx_runtime_1.jsx)("button", { className: classes?.recipientButton || "text-as-tertiary flex h-7 items-center gap-2 rounded-lg", onClick: onSelectRecipient, children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: classes?.recipientValue || "text-as-tertiary flex items-center gap-1 text-sm", children: customRecipientLabel ||
|
|
16
16
|
(recipientName ? (0, utils_1.formatUsername)(recipientName) : (0, formatAddress_1.shortenAddress)(effectiveRecipientAddress || "")) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "h-4 w-4" })] }) })) : ((0, jsx_runtime_1.jsx)("button", { className: classes?.recipientButton || "text-as-primary/70 flex items-center gap-1 rounded-lg", onClick: onSelectRecipient, children: (0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium", children: "Select recipient" }) }))] }), isBuyMode || isDepositMode ? (
|
|
17
|
-
// Fixed destination
|
|
18
|
-
|
|
17
|
+
// Fixed destination TOKEN for buy mode and deposit mode. The AMOUNT differs: buy mode
|
|
18
|
+
// always derives the receive amount from the source pay-in (read-only here), but deposit
|
|
19
|
+
// mode is the flow where the user states how much to receive directly — so when it's not
|
|
20
|
+
// locked (no destinationTokenAmount from the caller), render an editable amount input
|
|
21
|
+
// while keeping the token side fixed/non-interactive (hideTokenSelect).
|
|
22
|
+
(0, jsx_runtime_1.jsxs)("div", { className: classes?.inputContainer || "flex items-center justify-between", children: [isDepositMode && !disableAmountInput ? ((0, jsx_runtime_1.jsx)(OrderTokenAmount_1.OrderTokenAmount, { address: effectiveRecipientAddress, context: "to", inputValue: dstAmount, onChangeInput: onChangeDstAmount || (() => { }), chainId: selectedDstChainId || dstToken.chainId, setChainId: () => { }, token: dstToken, setToken: () => { }, hideTokenSelect: true, className: "w-auto flex-1 gap-0 rounded-none border-0 p-0", innerClassName: "gap-0", amountClassName: classes?.input || "text-as-primary text-2xl font-bold" })) : ((0, jsx_runtime_1.jsx)("div", { className: classes?.input || "text-as-primary text-2xl font-bold", children: dstAmount || "0" })), (0, jsx_runtime_1.jsxs)("div", { className: classes?.tokenSelector ||
|
|
19
23
|
"bg-as-brand/10 border-as-brand/30 flex items-center gap-3 rounded-xl border px-4 py-3", children: [(dstTokenLogoURI || dstToken.metadata?.logoURI) && ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("img", { src: dstTokenLogoURI || dstToken.metadata?.logoURI, alt: dstTokenSymbol || dstToken.symbol, className: classes?.tokenIcon || "h-8 w-8 rounded-full" }), anyspend_1.ALL_CHAINS[dstToken.chainId]?.logoUrl && ((0, jsx_runtime_1.jsx)("img", { src: anyspend_1.ALL_CHAINS[dstToken.chainId].logoUrl, alt: "Chain", className: classes?.chainBadge || "absolute -bottom-1 -right-1 h-4 w-4 rounded-full border border-white" }))] })), (0, jsx_runtime_1.jsx)("span", { className: classes?.tokenSymbol || "text-as-brand text-lg font-bold", children: dstTokenSymbol || dstToken.symbol })] })] })) : (
|
|
20
24
|
// Token selection for regular swap mode
|
|
21
25
|
(0, jsx_runtime_1.jsx)("div", { className: classes?.inputContainer, children: (0, jsx_runtime_1.jsx)(OrderTokenAmount_1.OrderTokenAmount, { address: effectiveRecipientAddress, context: "to", inputValue: dstAmount, onChangeInput: onChangeDstAmount || (() => { }), chainId: selectedDstChainId || dstToken.chainId, setChainId: setSelectedDstChainId || (() => { }), token: dstToken, setToken: setSelectedDstToken || (() => { }), disabled: disableAmountInput }) })), (0, jsx_runtime_1.jsxs)("div", { className: classes?.feeRow || "text-as-primary/50 flex h-5 items-center justify-start gap-2 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, number_1.formatDisplayNumber)(anyspendQuote?.data?.currencyOut?.amountUsd, {
|
|
@@ -64,20 +64,27 @@ const BetterAuthProvider = ({ partnerId }) => {
|
|
|
64
64
|
hasAttemptedRestore.current = true;
|
|
65
65
|
const restoreSession = async () => {
|
|
66
66
|
debug("Attempting session restore");
|
|
67
|
+
// A _ba_token in the URL OUTRANKS a cached Feathers JWT: the user just
|
|
68
|
+
// arrived through a live sign-in link (magic link / OAuth callback), and
|
|
69
|
+
// restoring a previous identity first would strand them as the wrong
|
|
70
|
+
// user with the token silently dropped.
|
|
71
|
+
const incomingBaToken = new URLSearchParams(window.location.search).get("_ba_token");
|
|
67
72
|
// 1. Try existing Feathers JWT first (fastest — no network call to Better Auth)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
if (!incomingBaToken) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await app_1.default.reAuthenticate();
|
|
76
|
+
if (response?.user) {
|
|
77
|
+
debug("Feathers JWT restored", { userId: response.user.userId });
|
|
78
|
+
setUser(response.user);
|
|
79
|
+
setIsAuthenticated(true);
|
|
80
|
+
setIsConnected(true);
|
|
81
|
+
setIsAuthenticating(false);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
debug("No existing Feathers JWT");
|
|
77
87
|
}
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
debug("No existing Feathers JWT");
|
|
81
88
|
}
|
|
82
89
|
// 2. Check for _ba_token in URL (OAuth callback with third-party cookie bypass).
|
|
83
90
|
// When the API and frontend are on different domains, browsers with third-party
|
|
@@ -117,6 +124,23 @@ const BetterAuthProvider = ({ partnerId }) => {
|
|
|
117
124
|
}
|
|
118
125
|
catch (err) {
|
|
119
126
|
debug("_ba_token exchange failed", err);
|
|
127
|
+
// A dead injected token (expired link param, revoked session) must
|
|
128
|
+
// not strand a user who HAS a valid cached session — fall back to
|
|
129
|
+
// the reAuthenticate lane the token's presence skipped.
|
|
130
|
+
try {
|
|
131
|
+
const cached = await app_1.default.reAuthenticate();
|
|
132
|
+
if (cached?.user) {
|
|
133
|
+
debug("Fell back to cached Feathers JWT after _ba_token failure", { userId: cached.user.userId });
|
|
134
|
+
setUser(cached.user);
|
|
135
|
+
setIsAuthenticated(true);
|
|
136
|
+
setIsConnected(true);
|
|
137
|
+
setIsAuthenticating(false);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
debug("No cached session to fall back to");
|
|
143
|
+
}
|
|
120
144
|
}
|
|
121
145
|
}
|
|
122
146
|
// 3. Check for a Better Auth session via cookie (works when API and frontend
|
|
@@ -64,6 +64,13 @@ export declare function AnySpend(props: {
|
|
|
64
64
|
allowDirectTransfer?: boolean;
|
|
65
65
|
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
66
66
|
destinationTokenAmount?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Editable default destination token amount (in wei/smallest unit). Seeds the same
|
|
69
|
+
* destination-amount input as `destinationTokenAmount`, but leaves it user-editable. Ignored
|
|
70
|
+
* when `destinationTokenAmount` is also set — that prop locks the field and wins.
|
|
71
|
+
* Seeds the field once on first apply; a later change to this value does not re-fill it, so it never overwrites what the user typed.
|
|
72
|
+
*/
|
|
73
|
+
defaultDestinationTokenAmount?: string;
|
|
67
74
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
68
75
|
callbackMetadata?: Record<string, unknown>;
|
|
69
76
|
/** Optional sender (payer) address — pre-fills token balances when the user address is known ahead of time */
|
|
@@ -78,4 +85,16 @@ export declare function AnySpend(props: {
|
|
|
78
85
|
kycEnabled?: boolean;
|
|
79
86
|
/** Whether to show the "Pay with Fiat" tab. Defaults to true. When false, the fiat tab is hidden and activeTab is forced to "crypto". */
|
|
80
87
|
showFiatOption?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Internal: true when `AnySpendDeposit` has an amount to prefill (`defaultDestinationTokenAmount`
|
|
90
|
+
* set), as opposed to any other `AnySpend`/`AnySpendDeposit` usage. Only changes
|
|
91
|
+
* `CryptoReceiveSection`'s fixed-destination branch: it lets the receive amount become an
|
|
92
|
+
* editable input (still gated by `disableAmountInput`) instead of a read-only display. Scoped
|
|
93
|
+
* this narrowly on purpose — `AnySpendDeposit` backs 15+ funding modals that are source-exact-in
|
|
94
|
+
* (the user sets the "pay with" amount; the receive side is an intended computed display), so
|
|
95
|
+
* this must stay off for all of them and only turn on for the one page that actually prefills a
|
|
96
|
+
* receive amount. Not intended for external callers — `AnySpendDeposit` derives this itself.
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
isDepositMode?: boolean;
|
|
81
100
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -62,7 +62,7 @@ export function AnySpend(props) {
|
|
|
62
62
|
const fingerprintConfig = getFingerprintConfig();
|
|
63
63
|
return (_jsx(AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: _jsx(AnySpendCustomizationProvider, { slots: props.slots, content: props.content, theme: props.theme, children: _jsx(AnySpendInner, { ...props }) }) }));
|
|
64
64
|
}
|
|
65
|
-
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, }) {
|
|
65
|
+
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, defaultDestinationTokenAmount, callbackMetadata, senderAddress, kycEnabled = false, showFiatOption = true, isDepositMode = false, }) {
|
|
66
66
|
const { slots, content } = useAnySpendCustomization();
|
|
67
67
|
const searchParams = useSearchParamsSSR();
|
|
68
68
|
const router = useRouter();
|
|
@@ -230,6 +230,21 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
230
230
|
sessionStorage.setItem("anyspend_dst_token", JSON.stringify(selectedDstToken));
|
|
231
231
|
}, [selectedDstToken, isBuyMode]);
|
|
232
232
|
const [isSrcInputDirty, setIsSrcInputDirty] = useState(true);
|
|
233
|
+
// Synchronous mirror of `isSrcInputDirty`. `setIsSrcInputDirty` is async relative to OTHER
|
|
234
|
+
// effects in the same commit — the quote effect (declared later, same commit) would otherwise
|
|
235
|
+
// read a pre-flip closure value and clear a just-seeded receive amount before it ever paints.
|
|
236
|
+
// Every flip goes through `markSrcDirty`, which writes this ref in the same statement, so
|
|
237
|
+
// same-commit siblings always see the latest intent. Deliberately NOT kept in sync by its own
|
|
238
|
+
// effect: an effect reading `isSrcInputDirty` from its closure would itself go stale across
|
|
239
|
+
// React 18 StrictMode's dev-mode double-invoke of mount effects (same closure re-run twice)
|
|
240
|
+
// and re-stomp this ref back to the pre-flip value between the two passes.
|
|
241
|
+
const isSrcInputDirtyRef = useRef(isSrcInputDirty);
|
|
242
|
+
// Single writer for the direction flag: keeps state and its synchronous ref mirror in lockstep
|
|
243
|
+
// so no call site — parent or child (CryptoPaySection) — can flip one without the other.
|
|
244
|
+
const markSrcDirty = useCallback((next) => {
|
|
245
|
+
setIsSrcInputDirty(next);
|
|
246
|
+
isSrcInputDirtyRef.current = next;
|
|
247
|
+
}, []);
|
|
233
248
|
// Add refs to track if we've applied metadata
|
|
234
249
|
const appliedSrcMetadataRef = useRef(false);
|
|
235
250
|
const appliedDstMetadataRef = useRef(false);
|
|
@@ -282,17 +297,24 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
282
297
|
useEffect(() => {
|
|
283
298
|
appliedDstMetadataRef.current = false;
|
|
284
299
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
285
|
-
// Prefill destination amount if provided
|
|
300
|
+
// Prefill destination amount if provided — fixed (locking) mode wins over the editable default,
|
|
301
|
+
// since `destinationTokenAmount` also disables the input (see `disableAmountInput` below).
|
|
286
302
|
const appliedDestinationAmount = useRef(false);
|
|
303
|
+
const seedDestinationAmount = destinationTokenAmount ?? defaultDestinationTokenAmount;
|
|
287
304
|
useEffect(() => {
|
|
288
305
|
// Only apply when we have real metadata (not default decimals)
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
306
|
+
if (seedDestinationAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
307
|
+
try {
|
|
308
|
+
const formattedAmount = formatUnits(BigInt(seedDestinationAmount), dstTokenMetadata.decimals);
|
|
309
|
+
appliedDestinationAmount.current = true;
|
|
310
|
+
setDstAmount(formattedAmount);
|
|
311
|
+
markSrcDirty(false); // Switch to EXACT_OUTPUT mode
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// invalid seed value — no-op rather than throw into the effect (fail-closed)
|
|
315
|
+
}
|
|
294
316
|
}
|
|
295
|
-
}, [
|
|
317
|
+
}, [seedDestinationAmount, dstTokenMetadata, markSrcDirty]);
|
|
296
318
|
// Load swap configuration from URL on initial render
|
|
297
319
|
useEffect(() => {
|
|
298
320
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -315,7 +337,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
315
337
|
}
|
|
316
338
|
else {
|
|
317
339
|
setSrcAmount(fromAmountParam);
|
|
318
|
-
|
|
340
|
+
markSrcDirty(true);
|
|
319
341
|
}
|
|
320
342
|
}
|
|
321
343
|
}
|
|
@@ -329,7 +351,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
329
351
|
}
|
|
330
352
|
// Mark that we've processed the initial URL
|
|
331
353
|
initialUrlProcessed.current = true;
|
|
332
|
-
}, [searchParams, loadOrder, disableUrlParamManagement]);
|
|
354
|
+
}, [searchParams, loadOrder, disableUrlParamManagement, markSrcDirty]);
|
|
333
355
|
// Update URL when swap configuration changes - but not on initial load
|
|
334
356
|
const updateSwapParamsInURL = useCallback(() => {
|
|
335
357
|
// Skip if:
|
|
@@ -551,12 +573,20 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
551
573
|
}, [globalAddress, effectiveRecipientAddress, customRecipients.length, setSelectedRecipientAddress]);
|
|
552
574
|
// Update dependent amount when relay price changes
|
|
553
575
|
useEffect(() => {
|
|
576
|
+
// Read the ref (not the closed-over `isSrcInputDirty` param) for the branch decision: the
|
|
577
|
+
// SEED effect (declared above, same commit when it fires) writes to the ref synchronously
|
|
578
|
+
// when it flips this to exact-out mode, so this effect sees that flip immediately even
|
|
579
|
+
// though `setIsSrcInputDirty`'s state update won't be visible in THIS render's closure until
|
|
580
|
+
// the next commit. Using the stale closure value here was the actual bug — it caused this
|
|
581
|
+
// effect to still treat a freshly-seeded exact-out amount as exact-in-with-no-data and clear
|
|
582
|
+
// it before it ever painted. The dependency array below stays on the real state, so this
|
|
583
|
+
// effect still re-runs correctly on every genuine transition.
|
|
554
584
|
if (anyspendQuote?.data &&
|
|
555
585
|
anyspendQuote.data.currencyIn?.amount &&
|
|
556
586
|
anyspendQuote.data.currencyIn?.currency?.decimals &&
|
|
557
587
|
anyspendQuote.data.currencyOut?.amount &&
|
|
558
588
|
anyspendQuote.data.currencyOut?.currency?.decimals) {
|
|
559
|
-
if (
|
|
589
|
+
if (isSrcInputDirtyRef.current) {
|
|
560
590
|
// The user is driving the SOURCE (exact-in): reflect the quote's OUTPUT even
|
|
561
591
|
// when a fixed destinationTokenAmount pre-filled the receive. That lock only
|
|
562
592
|
// governs the INITIAL exact-out state (untouched source → compute the pay for
|
|
@@ -574,7 +604,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
574
604
|
}
|
|
575
605
|
}
|
|
576
606
|
else {
|
|
577
|
-
if (
|
|
607
|
+
if (isSrcInputDirtyRef.current) {
|
|
578
608
|
setDstAmount("");
|
|
579
609
|
}
|
|
580
610
|
else {
|
|
@@ -1060,7 +1090,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
1060
1090
|
// Reset payment methods when switching tabs
|
|
1061
1091
|
resetPaymentMethods();
|
|
1062
1092
|
setSelectedFiatPaymentMethod(FiatPaymentMethod.NONE);
|
|
1063
|
-
}, setSelectedCryptoPaymentMethod: setSelectedCryptoPaymentMethod, setSelectedFiatPaymentMethod: setSelectedFiatPaymentMethod })), _jsxs("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [activeTab === "crypto" ? (_jsx(CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty:
|
|
1093
|
+
}, setSelectedCryptoPaymentMethod: setSelectedCryptoPaymentMethod, setSelectedFiatPaymentMethod: setSelectedFiatPaymentMethod })), _jsxs("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [activeTab === "crypto" ? (_jsx(CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: markSrcDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => navigateToPanel(PanelView.CRYPTO_PAYMENT_METHOD, "forward"), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, onShowFeeDetail: () => navigateToPanel(PanelView.FEE_DETAIL, "forward") })) : (_jsx(motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0, ease: "easeInOut" }, children: _jsx(PanelOnramp, { srcAmountOnRamp: srcAmountOnRamp, setSrcAmountOnRamp: setSrcAmountOnRamp, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: (panelIndex) => {
|
|
1064
1094
|
// Map panel index to navigation with direction
|
|
1065
1095
|
const panelsWithForwardNav = [PanelView.FIAT_PAYMENT_METHOD, PanelView.RECIPIENT_SELECTION];
|
|
1066
1096
|
if (panelsWithForwardNav.includes(panelIndex)) {
|
|
@@ -1087,8 +1117,8 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
1087
1117
|
// Swap amounts
|
|
1088
1118
|
setSrcAmount(dstAmount);
|
|
1089
1119
|
setDstAmount(srcAmount);
|
|
1090
|
-
}, children: _jsx("div", { className: "relative flex items-center justify-center transition-opacity", children: _jsx(ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) })), activeTab === "crypto" && (_jsx(CryptoReceiveSection, { isDepositMode:
|
|
1091
|
-
|
|
1120
|
+
}, children: _jsx("div", { className: "relative flex items-center justify-center transition-opacity", children: _jsx(ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) })), activeTab === "crypto" && (_jsx(CryptoReceiveSection, { isDepositMode: isDepositMode, isBuyMode: isBuyMode, effectiveRecipientAddress: effectiveRecipientAddress, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => navigateToPanel(PanelView.RECIPIENT_SELECTION, "forward"), dstAmount: dstAmount, dstToken: selectedDstToken, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: setSelectedDstChainId, setSelectedDstToken: setSelectedDstToken, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
1121
|
+
markSrcDirty(false);
|
|
1092
1122
|
setDstAmount(value);
|
|
1093
1123
|
}, disableAmountInput: !!destinationTokenAmount, anyspendQuote: isDirectTransfer ? undefined : anyspendQuote, onShowPointsDetail: isDirectTransfer ? undefined : () => navigateToPanel(PanelView.POINTS_DETAIL, "forward"), onShowFeeDetail: isDirectTransfer ? undefined : () => navigateToPanel(PanelView.FEE_DETAIL, "forward") }))] }), gasPriceData && !isLoadingGas && activeTab === "crypto" && !isDirectTransfer && (_jsx(GasIndicator, { gasPrice: gasPriceData, className: classes?.gasIndicator || "mt-2 w-full" })), _jsxs(motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0.2, ease: "easeInOut" }, className: cn("mt-4 flex w-full max-w-[460px] flex-col gap-2"), children: [slots.actionButton ? (slots.actionButton({
|
|
1094
1124
|
onClick: onMainButtonClick,
|
|
@@ -116,6 +116,14 @@ export interface AnySpendDepositProps {
|
|
|
116
116
|
pureTransferOnly?: boolean;
|
|
117
117
|
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
118
118
|
destinationTokenAmount?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Editable default destination token amount (in wei/smallest unit). Seeds the same
|
|
121
|
+
* destination-amount input as `destinationTokenAmount`, but leaves it user-editable. Ignored
|
|
122
|
+
* when `destinationTokenAmount` is also set — that prop locks the field and wins. Applies to
|
|
123
|
+
* the standard deposit flow only; custom-function deposits ignore it.
|
|
124
|
+
* Seeds the field once on first apply; a later change to this value does not re-fill it, so it never overwrites what the user typed.
|
|
125
|
+
*/
|
|
126
|
+
defaultDestinationTokenAmount?: string;
|
|
119
127
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
120
128
|
callbackMetadata?: Record<string, unknown>;
|
|
121
129
|
/** Optional sender (payer) address — pre-fills token balances when the user address is known ahead of time */
|
|
@@ -162,4 +170,4 @@ export interface AnySpendDepositProps {
|
|
|
162
170
|
* onSuccess={(amount) => console.log(`Deposited ${amount}`)}
|
|
163
171
|
* />
|
|
164
172
|
*/
|
|
165
|
-
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, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, pureTransferOnly, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
|
|
173
|
+
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, topChainsCount, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit, classes, allowDirectTransfer, pureTransferOnly, destinationTokenAmount, defaultDestinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }: AnySpendDepositProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -107,7 +107,7 @@ function ChainIcon({ chainId, className }) {
|
|
|
107
107
|
* onSuccess={(amount) => console.log(`Deposited ${amount}`)}
|
|
108
108
|
* />
|
|
109
109
|
*/
|
|
110
|
-
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, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, pureTransferOnly = false, destinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
|
|
110
|
+
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, topChainsCount = 3, onClose, returnToHomeUrl, customRecipientLabel, returnHomeLabel, isCustomDeposit = false, classes, allowDirectTransfer = false, pureTransferOnly = false, destinationTokenAmount, defaultDestinationTokenAmount, callbackMetadata, senderAddress, slots, content, theme, }) {
|
|
111
111
|
// Extract deposit-specific classes for convenience
|
|
112
112
|
const depositClasses = classes?.deposit;
|
|
113
113
|
const { connectedEOAWallet } = useAccountWallet();
|
|
@@ -266,5 +266,9 @@ export function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, p
|
|
|
266
266
|
// Deposit view
|
|
267
267
|
return (_jsxs("div", { className: depositClasses?.form || "anyspend-deposit anyspend-deposit-form relative", children: [shouldShowChainSelection && (_jsxs("button", { onClick: handleBack, className: depositClasses?.backButton ||
|
|
268
268
|
"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 ||
|
|
269
|
-
"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,
|
|
269
|
+
"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,
|
|
270
|
+
// Scoped to ONLY the prefill case (not every AnySpendDeposit caller — 15+ other
|
|
271
|
+
// funding modals rely on the source-exact-in "computed display" behavior this would
|
|
272
|
+
// otherwise change): editable destination amount only when there's a value to seed.
|
|
273
|
+
isDepositMode: !!defaultDestinationTokenAmount, 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, defaultDestinationTokenAmount: defaultDestinationTokenAmount, 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" } })] }));
|
|
270
274
|
}
|
|
@@ -11,8 +11,12 @@ export function CryptoReceiveSection({ isDepositMode = false, isBuyMode = false,
|
|
|
11
11
|
return (_jsxs(motion.div, { initial: { opacity: 0, y: 20, filter: "blur(10px)" }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, transition: { duration: 0.3, delay: 0.1, ease: "easeInOut" }, className: classes?.container ||
|
|
12
12
|
"receive-section bg-as-surface-secondary border-as-border-secondary relative flex w-full flex-col gap-2 rounded-2xl border p-4 sm:p-6", children: [_jsxs("div", { className: "flex w-full items-center justify-between", children: [_jsxs("div", { className: classes?.label || "text-as-primary/50 flex h-7 items-center gap-1.5 text-sm", children: [isDepositMode ? "Deposit" : "Receive", isSrcInputDirty && anyspendQuote?.data?.fee && onShowFeeDetail && (_jsx("button", { onClick: onShowFeeDetail, className: "text-as-primary/40 hover:text-as-primary/60 transition-colors", children: _jsx(Info, { className: "h-4 w-4" }) }))] }), effectiveRecipientAddress ? (_jsx("button", { className: classes?.recipientButton || "text-as-tertiary flex h-7 items-center gap-2 rounded-lg", onClick: onSelectRecipient, children: _jsxs(_Fragment, { children: [_jsx("span", { className: classes?.recipientValue || "text-as-tertiary flex items-center gap-1 text-sm", children: customRecipientLabel ||
|
|
13
13
|
(recipientName ? formatUsername(recipientName) : shortenAddress(effectiveRecipientAddress || "")) }), _jsx(ChevronRight, { className: "h-4 w-4" })] }) })) : (_jsx("button", { className: classes?.recipientButton || "text-as-primary/70 flex items-center gap-1 rounded-lg", onClick: onSelectRecipient, children: _jsx("div", { className: "text-sm font-medium", children: "Select recipient" }) }))] }), isBuyMode || isDepositMode ? (
|
|
14
|
-
// Fixed destination
|
|
15
|
-
|
|
14
|
+
// Fixed destination TOKEN for buy mode and deposit mode. The AMOUNT differs: buy mode
|
|
15
|
+
// always derives the receive amount from the source pay-in (read-only here), but deposit
|
|
16
|
+
// mode is the flow where the user states how much to receive directly — so when it's not
|
|
17
|
+
// locked (no destinationTokenAmount from the caller), render an editable amount input
|
|
18
|
+
// while keeping the token side fixed/non-interactive (hideTokenSelect).
|
|
19
|
+
_jsxs("div", { className: classes?.inputContainer || "flex items-center justify-between", children: [isDepositMode && !disableAmountInput ? (_jsx(OrderTokenAmount, { address: effectiveRecipientAddress, context: "to", inputValue: dstAmount, onChangeInput: onChangeDstAmount || (() => { }), chainId: selectedDstChainId || dstToken.chainId, setChainId: () => { }, token: dstToken, setToken: () => { }, hideTokenSelect: true, className: "w-auto flex-1 gap-0 rounded-none border-0 p-0", innerClassName: "gap-0", amountClassName: classes?.input || "text-as-primary text-2xl font-bold" })) : (_jsx("div", { className: classes?.input || "text-as-primary text-2xl font-bold", children: dstAmount || "0" })), _jsxs("div", { className: classes?.tokenSelector ||
|
|
16
20
|
"bg-as-brand/10 border-as-brand/30 flex items-center gap-3 rounded-xl border px-4 py-3", children: [(dstTokenLogoURI || dstToken.metadata?.logoURI) && (_jsxs("div", { className: "relative", children: [_jsx("img", { src: dstTokenLogoURI || dstToken.metadata?.logoURI, alt: dstTokenSymbol || dstToken.symbol, className: classes?.tokenIcon || "h-8 w-8 rounded-full" }), ALL_CHAINS[dstToken.chainId]?.logoUrl && (_jsx("img", { src: ALL_CHAINS[dstToken.chainId].logoUrl, alt: "Chain", className: classes?.chainBadge || "absolute -bottom-1 -right-1 h-4 w-4 rounded-full border border-white" }))] })), _jsx("span", { className: classes?.tokenSymbol || "text-as-brand text-lg font-bold", children: dstTokenSymbol || dstToken.symbol })] })] })) : (
|
|
17
21
|
// Token selection for regular swap mode
|
|
18
22
|
_jsx("div", { className: classes?.inputContainer, children: _jsx(OrderTokenAmount, { address: effectiveRecipientAddress, context: "to", inputValue: dstAmount, onChangeInput: onChangeDstAmount || (() => { }), chainId: selectedDstChainId || dstToken.chainId, setChainId: setSelectedDstChainId || (() => { }), token: dstToken, setToken: setSelectedDstToken || (() => { }), disabled: disableAmountInput }) })), _jsxs("div", { className: classes?.feeRow || "text-as-primary/50 flex h-5 items-center justify-start gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [formatDisplayNumber(anyspendQuote?.data?.currencyOut?.amountUsd, {
|