@b3dotfun/sdk 0.1.63 → 0.1.64
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 +2 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +25 -8
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +1 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
- package/dist/cjs/anyspend/react/components/common/OrderDetails.js +19 -0
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +16 -2
- package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +26 -9
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +1 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +2 -2
- package/dist/esm/anyspend/react/components/common/OrderDetails.js +19 -0
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +16 -2
- package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
- package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +30 -6
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +4 -4
- package/src/anyspend/react/components/AnySpendDeposit.tsx +1 -0
- package/src/anyspend/react/components/common/CryptoPaySection.tsx +4 -0
- package/src/anyspend/react/components/common/OrderDetails.tsx +19 -0
- package/src/anyspend/react/components/common/OrderTokenAmount.tsx +19 -1
- package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +2 -2
|
@@ -51,4 +51,6 @@ export declare function AnySpend(props: {
|
|
|
51
51
|
classes?: AnySpendClasses;
|
|
52
52
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
53
53
|
allowDirectTransfer?: boolean;
|
|
54
|
+
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
55
|
+
destinationTokenAmount?: string;
|
|
54
56
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -61,7 +61,7 @@ function AnySpend(props) {
|
|
|
61
61
|
console.log("[mitch] AnySpend rendered with fingerprintConfig:", props, fingerprintConfig);
|
|
62
62
|
return ((0, jsx_runtime_1.jsx)(AnySpendFingerprintWrapper_1.AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: (0, jsx_runtime_1.jsx)(AnySpendInner, { ...props }) }));
|
|
63
63
|
}
|
|
64
|
-
function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, }) {
|
|
64
|
+
function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, }) {
|
|
65
65
|
const searchParams = (0, react_2.useSearchParamsSSR)();
|
|
66
66
|
const router = (0, react_2.useRouter)();
|
|
67
67
|
const { partnerId } = (0, react_2.useB3Config)();
|
|
@@ -197,6 +197,17 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
197
197
|
(0, react_4.useEffect)(() => {
|
|
198
198
|
appliedDstMetadataRef.current = false;
|
|
199
199
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
200
|
+
// Prefill destination amount if provided (for fixed amount mode)
|
|
201
|
+
const appliedDestinationAmount = (0, react_4.useRef)(false);
|
|
202
|
+
(0, react_4.useEffect)(() => {
|
|
203
|
+
// Only apply when we have real metadata (not default decimals)
|
|
204
|
+
if (destinationTokenAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
205
|
+
appliedDestinationAmount.current = true;
|
|
206
|
+
const formattedAmount = (0, viem_1.formatUnits)(BigInt(destinationTokenAmount), dstTokenMetadata.decimals);
|
|
207
|
+
setDstAmount(formattedAmount);
|
|
208
|
+
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
209
|
+
}
|
|
210
|
+
}, [destinationTokenAmount, dstTokenMetadata]);
|
|
200
211
|
// Load swap configuration from URL on initial render
|
|
201
212
|
(0, react_4.useEffect)(() => {
|
|
202
213
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -457,9 +468,12 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
457
468
|
anyspendQuote.data.currencyOut?.amount &&
|
|
458
469
|
anyspendQuote.data.currencyOut?.currency?.decimals) {
|
|
459
470
|
if (isSrcInputDirty) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
471
|
+
// Don't override dstAmount if we have a fixed destinationTokenAmount
|
|
472
|
+
if (!destinationTokenAmount) {
|
|
473
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
474
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
475
|
+
setDstAmount((0, number_1.formatTokenAmount)(BigInt(amount), decimals, 6, false));
|
|
476
|
+
}
|
|
463
477
|
}
|
|
464
478
|
else {
|
|
465
479
|
const amount = anyspendQuote.data.currencyIn.amount;
|
|
@@ -469,13 +483,16 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
469
483
|
}
|
|
470
484
|
else {
|
|
471
485
|
if (isSrcInputDirty) {
|
|
472
|
-
|
|
486
|
+
// Don't reset dstAmount if we have a fixed destinationTokenAmount
|
|
487
|
+
if (!destinationTokenAmount) {
|
|
488
|
+
setDstAmount("");
|
|
489
|
+
}
|
|
473
490
|
}
|
|
474
491
|
else {
|
|
475
492
|
setSrcAmount("");
|
|
476
493
|
}
|
|
477
494
|
}
|
|
478
|
-
}, [anyspendQuote, isSrcInputDirty]);
|
|
495
|
+
}, [anyspendQuote, isSrcInputDirty, destinationTokenAmount]);
|
|
479
496
|
(0, react_4.useEffect)(() => {
|
|
480
497
|
if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
481
498
|
console.log("Calling onSuccess");
|
|
@@ -874,7 +891,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
874
891
|
// Reset payment methods when switching tabs
|
|
875
892
|
resetPaymentMethods();
|
|
876
893
|
setSelectedFiatPaymentMethod(FiatPaymentMethod_1.FiatPaymentMethod.NONE);
|
|
877
|
-
}, 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: setIsSrcInputDirty, 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) => {
|
|
894
|
+
}, 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: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => navigateToPanel(PanelView.CRYPTO_PAYMENT_METHOD, "forward"), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, onShowFeeDetail: () => navigateToPanel(PanelView.FEE_DETAIL, "forward"), skipAutoMaxOnTokenChange: !!destinationTokenAmount })) : ((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) => {
|
|
878
895
|
// Map panel index to navigation with direction
|
|
879
896
|
const panelsWithForwardNav = [PanelView.FIAT_PAYMENT_METHOD, PanelView.RECIPIENT_SELECTION];
|
|
880
897
|
if (panelsWithForwardNav.includes(panelIndex)) {
|
|
@@ -904,7 +921,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
904
921
|
}, 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: false, 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 => {
|
|
905
922
|
setIsSrcInputDirty(false);
|
|
906
923
|
setDstAmount(value);
|
|
907
|
-
}, 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: [(0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
924
|
+
}, 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: [(0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
908
925
|
(btnInfo.disable && classes?.mainButtonDisabled) ||
|
|
909
926
|
classes?.mainButton ||
|
|
910
927
|
(0, cn_1.cn)("as-main-button relative w-full", btnInfo.error ? "!bg-as-red" : btnInfo.disable ? "!bg-as-on-surface-2" : "!bg-as-brand"), textClassName: (0, cn_1.cn)(btnInfo.error ? "text-white" : btnInfo.disable ? "text-as-secondary" : "text-white"), children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [btnInfo.loading && (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-4 w-4 animate-spin" }), btnInfo.text] }) }), !hideTransactionHistoryButton && (globalAddress || effectiveRecipientAddress) ? ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "link", onClick: onClickHistory, className: classes?.historyButton ||
|
|
@@ -83,14 +83,13 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
|
|
|
83
83
|
// Prefill destination amount if provided (for EXACT_OUTPUT mode)
|
|
84
84
|
const appliedDestinationAmount = (0, react_4.useRef)(false);
|
|
85
85
|
(0, react_4.useEffect)(() => {
|
|
86
|
-
if (destinationTokenAmount && !appliedDestinationAmount.current) {
|
|
86
|
+
if (destinationTokenAmount && destinationToken?.decimals && !appliedDestinationAmount.current) {
|
|
87
87
|
appliedDestinationAmount.current = true;
|
|
88
|
-
// Convert wei to human-readable format
|
|
89
88
|
const formattedAmount = (0, number_1.formatUnits)(destinationTokenAmount, destinationToken.decimals);
|
|
90
89
|
setDstAmountInput(formattedAmount);
|
|
91
90
|
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
92
91
|
}
|
|
93
|
-
}, [destinationTokenAmount, destinationToken
|
|
92
|
+
}, [destinationTokenAmount, destinationToken, setDstAmountInput, setIsSrcInputDirty]);
|
|
94
93
|
const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
|
|
95
94
|
const expectedDstAmountRaw = anyspendQuote?.data?.currencyOut?.amount ?? "0";
|
|
96
95
|
const buildCustomPayload = (_recipient) => {
|
|
@@ -229,8 +228,8 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
|
|
|
229
228
|
};
|
|
230
229
|
const headerContent = header ? (header({ anyspendPrice: anyspendQuote, isLoadingAnyspendPrice: isLoadingAnyspendQuote })) : ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-as-primary text-xl font-bold", children: actionLabel }), (0, jsx_runtime_1.jsx)("p", { className: "text-as-secondary text-sm", children: "Pay from any token to execute a custom exact-in transaction." })] }) }));
|
|
231
230
|
const mainView = ((0, jsx_runtime_1.jsxs)("div", { className: classes?.container ||
|
|
232
|
-
"anyspend-custom-exact-in-container mx-auto flex w-[460px] max-w-full flex-col items-center gap-2", children: [headerContent, (0, jsx_runtime_1.jsx)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [paymentType === "crypto" ? ((0, jsx_runtime_1.jsx)(CryptoPaySection_1.CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => setActivePanel(useAnyspendFlow_1.PanelView.CRYPTO_PAYMENT_METHOD), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect })) : ((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: srcAmount, setSrcAmountOnRamp: setSrcAmount, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: setActivePanel, _recipientAddress: selectedRecipientOrDefault, destinationToken: selectedDstToken, destinationChainId: selectedDstChainId, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, hideDstToken: true, destinationAmount: dstAmount, onDestinationTokenChange: () => { }, onDestinationChainChange: () => { }, fiatPaymentMethodIndex: useAnyspendFlow_1.PanelView.FIAT_PAYMENT_METHOD, recipientSelectionPanelIndex: useAnyspendFlow_1.PanelView.RECIPIENT_SELECTION, anyspendQuote: anyspendQuote, onShowPointsDetail: () => setActivePanel(useAnyspendFlow_1.PanelView.POINTS_DETAIL), onShowFeeDetail: () => setActivePanel(useAnyspendFlow_1.PanelView.FEE_DETAIL), customUsdInputValues: customUsdInputValues, customRecipientLabel: customRecipientLabel }) })), (0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("relative -my-1 flex h-0 items-center justify-center", paymentType === "fiat" && "hidden"), children: (0, jsx_runtime_1.jsx)(react_2.Button, { variant: "ghost", className: classes?.swapDirectionButton ||
|
|
233
|
-
"swap-direction-button border-as-stroke bg-as-surface-primary z-10 h-10 w-10 cursor-default rounded-xl border-2 sm:h-8 sm:w-8 sm:rounded-xl", 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" }) }) }) }), paymentType === "crypto" && ((0, jsx_runtime_1.jsx)(CryptoReceiveSection_1.CryptoReceiveSection, { isDepositMode: false, isBuyMode: false, effectiveRecipientAddress: selectedRecipientOrDefault, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => setActivePanel(useAnyspendFlow_1.PanelView.RECIPIENT_SELECTION), dstAmount: isSrcInputDirty ? dstAmount : dstAmountInput, dstToken: selectedDstToken, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, dstTokenLogoURI: DESTINATION_TOKEN_DETAILS.LOGO_URI, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: () => { }, setSelectedDstToken: () => { }, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
231
|
+
"anyspend-custom-exact-in-container mx-auto flex w-[460px] max-w-full flex-col items-center gap-2", children: [headerContent, (0, jsx_runtime_1.jsx)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [paymentType === "crypto" ? ((0, jsx_runtime_1.jsx)(CryptoPaySection_1.CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => setActivePanel(useAnyspendFlow_1.PanelView.CRYPTO_PAYMENT_METHOD), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, skipAutoMaxOnTokenChange: !!destinationTokenAmount })) : ((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: srcAmount, setSrcAmountOnRamp: setSrcAmount, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: setActivePanel, _recipientAddress: selectedRecipientOrDefault, destinationToken: selectedDstToken, destinationChainId: selectedDstChainId, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, hideDstToken: true, destinationAmount: dstAmount, onDestinationTokenChange: () => { }, onDestinationChainChange: () => { }, fiatPaymentMethodIndex: useAnyspendFlow_1.PanelView.FIAT_PAYMENT_METHOD, recipientSelectionPanelIndex: useAnyspendFlow_1.PanelView.RECIPIENT_SELECTION, anyspendQuote: anyspendQuote, onShowPointsDetail: () => setActivePanel(useAnyspendFlow_1.PanelView.POINTS_DETAIL), onShowFeeDetail: () => setActivePanel(useAnyspendFlow_1.PanelView.FEE_DETAIL), customUsdInputValues: customUsdInputValues, customRecipientLabel: customRecipientLabel }) })), (0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("relative -my-1 flex h-0 items-center justify-center", paymentType === "fiat" && "hidden"), children: (0, jsx_runtime_1.jsx)(react_2.Button, { variant: "ghost", className: classes?.swapDirectionButton ||
|
|
232
|
+
"swap-direction-button border-as-stroke bg-as-surface-primary z-10 h-10 w-10 cursor-default rounded-xl border-2 sm:h-8 sm:w-8 sm:rounded-xl", 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" }) }) }) }), paymentType === "crypto" && ((0, jsx_runtime_1.jsx)(CryptoReceiveSection_1.CryptoReceiveSection, { isDepositMode: false, isBuyMode: false, effectiveRecipientAddress: selectedRecipientOrDefault, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => setActivePanel(useAnyspendFlow_1.PanelView.RECIPIENT_SELECTION), dstAmount: isSrcInputDirty && !destinationTokenAmount ? dstAmount : dstAmountInput, dstToken: selectedDstToken, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, dstTokenLogoURI: DESTINATION_TOKEN_DETAILS.LOGO_URI, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: () => { }, setSelectedDstToken: () => { }, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
234
233
|
setIsSrcInputDirty(false);
|
|
235
234
|
setDstAmountInput(value);
|
|
236
235
|
}, disableAmountInput: !!destinationTokenAmount, anyspendQuote: isDirectTransfer ? undefined : anyspendQuote, onShowPointsDetail: isDirectTransfer ? undefined : () => setActivePanel(useAnyspendFlow_1.PanelView.POINTS_DETAIL), onShowFeeDetail: isDirectTransfer ? undefined : () => setActivePanel(useAnyspendFlow_1.PanelView.FEE_DETAIL) }))] }) }), (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.2, ease: "easeInOut" }, className: (0, cn_1.cn)("mt-4 flex w-full max-w-[460px] flex-col gap-2"), children: (0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
@@ -209,5 +209,5 @@ function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, paymentT
|
|
|
209
209
|
// Deposit view
|
|
210
210
|
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 ||
|
|
211
211
|
"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 ||
|
|
212
|
-
"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 }, selectedChainId)) : ((0, jsx_runtime_1.jsx)(AnySpend_1.AnySpend, { loadOrder: loadOrder, mode: mode, defaultActiveTab: paymentType, recipientAddress: recipientAddress, sourceChainId: selectedChainId, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, onSuccess: txHash => onSuccess?.(txHash ?? ""), onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, hideHeader: true, hideBottomNavigation: true, disableUrlParamManagement: true, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.anySpend, allowDirectTransfer: allowDirectTransfer }, selectedChainId)) }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
|
|
212
|
+
"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 }, selectedChainId)) : ((0, jsx_runtime_1.jsx)(AnySpend_1.AnySpend, { loadOrder: loadOrder, mode: mode, defaultActiveTab: paymentType, recipientAddress: recipientAddress, sourceChainId: selectedChainId, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, onSuccess: txHash => onSuccess?.(txHash ?? ""), onTokenSelect: onTokenSelect, customUsdInputValues: customUsdInputValues, hideHeader: true, hideBottomNavigation: true, disableUrlParamManagement: true, returnToHomeUrl: returnToHomeUrl, customRecipientLabel: customRecipientLabel, returnHomeLabel: returnHomeLabel, classes: classes?.anySpend, allowDirectTransfer: allowDirectTransfer, destinationTokenAmount: destinationTokenAmount }, selectedChainId)) }), (0, jsx_runtime_1.jsx)(WarningText_1.ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
|
|
213
213
|
}
|
|
@@ -18,6 +18,8 @@ interface CryptoPaySectionProps {
|
|
|
18
18
|
}) => void;
|
|
19
19
|
onShowFeeDetail?: () => void;
|
|
20
20
|
classes?: CryptoPaySectionClasses;
|
|
21
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
22
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
21
23
|
}
|
|
22
|
-
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, skipAutoMaxOnTokenChange, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
23
25
|
export {};
|
|
@@ -11,7 +11,7 @@ const useConnectedWalletDisplay_1 = require("../../hooks/useConnectedWalletDispl
|
|
|
11
11
|
const CryptoPaymentMethodDisplay_1 = require("./CryptoPaymentMethodDisplay");
|
|
12
12
|
const OrderTokenAmount_1 = require("./OrderTokenAmount");
|
|
13
13
|
const TokenBalance_1 = require("./TokenBalance");
|
|
14
|
-
function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, }) {
|
|
14
|
+
function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, skipAutoMaxOnTokenChange = false, }) {
|
|
15
15
|
const { data: srcTokenMetadata } = (0, react_1.useTokenData)(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
16
16
|
// Use custom hook to determine wallet address based on payment method
|
|
17
17
|
const { walletAddress } = (0, useConnectedWalletDisplay_1.useConnectedWalletDisplay)(selectedCryptoPaymentMethod);
|
|
@@ -46,7 +46,7 @@ function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedS
|
|
|
46
46
|
"text-as-tertiarry flex h-7 items-center gap-2 text-sm transition-colors focus:!outline-none", onClick: onSelectCryptoPaymentMethod, children: (0, jsx_runtime_1.jsx)(CryptoPaymentMethodDisplay_1.CryptoPaymentMethodDisplay, { paymentMethod: selectedCryptoPaymentMethod, connectedAddress: walletAddress, connectedName: connectedName }) })] }), (0, jsx_runtime_1.jsx)("div", { className: classes?.inputContainer, children: (0, jsx_runtime_1.jsx)(OrderTokenAmount_1.OrderTokenAmount, { address: walletAddress, walletAddress: walletAddress, context: "from", inputValue: srcAmount, onChangeInput: value => {
|
|
47
47
|
setIsSrcInputDirty(true);
|
|
48
48
|
setSrcAmount(value);
|
|
49
|
-
}, chainId: selectedSrcChainId, setChainId: setSelectedSrcChainId, token: selectedSrcToken, setToken: setSelectedSrcToken, onTokenSelect: onTokenSelect }) }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.balanceRow || "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary/50 flex h-5 items-center text-sm", children: (0, number_1.formatDisplayNumber)(anyspendQuote?.data?.currencyIn?.amountUsd, {
|
|
49
|
+
}, chainId: selectedSrcChainId, setChainId: setSelectedSrcChainId, token: selectedSrcToken, setToken: setSelectedSrcToken, onTokenSelect: onTokenSelect, skipAutoMaxOnTokenChange: skipAutoMaxOnTokenChange }) }), (0, jsx_runtime_1.jsxs)("div", { className: classes?.balanceRow || "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary/50 flex h-5 items-center text-sm", children: (0, number_1.formatDisplayNumber)(anyspendQuote?.data?.currencyIn?.amountUsd, {
|
|
50
50
|
style: "currency",
|
|
51
51
|
fallback: "",
|
|
52
52
|
}) }), (0, jsx_runtime_1.jsx)(TokenBalance_1.TokenBalance, { token: selectedSrcToken, walletAddress: walletAddress, onChangeInput: value => {
|
|
@@ -351,6 +351,25 @@ exports.OrderDetails = (0, react_5.memo)(function OrderDetails({ mode = "modal",
|
|
|
351
351
|
handlePayment();
|
|
352
352
|
}
|
|
353
353
|
}, [isPayableState, isComponentReady, handlePayment]);
|
|
354
|
+
// Auto-redirect to redirectUrl when order is executed (for onramp orders)
|
|
355
|
+
(0, react_5.useEffect)(() => {
|
|
356
|
+
if (order.status === "executed" && order.onrampMetadata?.redirectUrl) {
|
|
357
|
+
const baseUrl = order.onrampMetadata.redirectUrl;
|
|
358
|
+
try {
|
|
359
|
+
const url = new URL(baseUrl);
|
|
360
|
+
// Prevent Open Redirect vulnerabilities by ensuring the protocol is http or https
|
|
361
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
362
|
+
console.error(`Attempted redirect to a URL with an invalid protocol: ${url.protocol}`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const redirectUrl = `${baseUrl.replace(/\/$/, "")}/${order.id}`;
|
|
366
|
+
window.location.href = redirectUrl;
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
console.error("Invalid redirect URL provided:", baseUrl, error);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}, [order.status, order.onrampMetadata?.redirectUrl, order.id]);
|
|
354
373
|
if (!srcToken || !dstToken) {
|
|
355
374
|
return (0, jsx_runtime_1.jsx)("div", { children: "Loading..." });
|
|
356
375
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { components } from "../../../../anyspend/types/api";
|
|
2
|
-
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, }: {
|
|
2
|
+
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, skipAutoMaxOnTokenChange, }: {
|
|
3
3
|
disabled?: boolean;
|
|
4
4
|
inputValue: string;
|
|
5
5
|
onChangeInput: (value: string) => void;
|
|
@@ -19,4 +19,6 @@ export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput,
|
|
|
19
19
|
preventDefault: () => void;
|
|
20
20
|
}) => void;
|
|
21
21
|
walletAddress?: string | undefined;
|
|
22
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
23
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
22
24
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -14,7 +14,7 @@ const react_2 = require("../../../../global-account/react");
|
|
|
14
14
|
const utils_1 = require("../../../../shared/utils");
|
|
15
15
|
const relay_kit_ui_1 = require("@relayprotocol/relay-kit-ui");
|
|
16
16
|
const ChainTokenIcon_1 = require("./ChainTokenIcon");
|
|
17
|
-
function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect = false, canEditAmount = true, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, }) {
|
|
17
|
+
function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect = false, canEditAmount = true, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, skipAutoMaxOnTokenChange = false, }) {
|
|
18
18
|
// Track previous token to detect changes
|
|
19
19
|
const prevTokenRef = (0, react_1.useRef)(token.address);
|
|
20
20
|
// Only get token balance when context is "from" (for setting max amount)
|
|
@@ -26,6 +26,11 @@ function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, addres
|
|
|
26
26
|
// Only handle "from" context
|
|
27
27
|
if (context !== "from")
|
|
28
28
|
return;
|
|
29
|
+
// Skip auto-max when in fixed destination amount mode
|
|
30
|
+
if (skipAutoMaxOnTokenChange) {
|
|
31
|
+
prevTokenRef.current = token.address;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
29
34
|
// Check if token changed or if this is the initial load with balance
|
|
30
35
|
const isTokenChanged = prevTokenRef.current !== token.address;
|
|
31
36
|
if (isTokenChanged && rawBalance) {
|
|
@@ -46,7 +51,16 @@ function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, addres
|
|
|
46
51
|
// Update refs
|
|
47
52
|
prevTokenRef.current = token.address;
|
|
48
53
|
}
|
|
49
|
-
}, [
|
|
54
|
+
}, [
|
|
55
|
+
token.address,
|
|
56
|
+
token.chainId,
|
|
57
|
+
token.decimals,
|
|
58
|
+
chainId,
|
|
59
|
+
context,
|
|
60
|
+
onChangeInput,
|
|
61
|
+
rawBalance,
|
|
62
|
+
skipAutoMaxOnTokenChange,
|
|
63
|
+
]);
|
|
50
64
|
const handleTokenSelect = (newToken) => {
|
|
51
65
|
const token = {
|
|
52
66
|
address: newToken.address,
|
|
@@ -95,9 +95,9 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
95
95
|
}
|
|
96
96
|
// At this point TypeScript knows result.paymentIntent exists and error is undefined
|
|
97
97
|
console.log("@@stripe-web2-payment:success:", JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2));
|
|
98
|
-
// Payment succeeded
|
|
98
|
+
// Payment succeeded
|
|
99
99
|
setMessage(null);
|
|
100
|
-
//
|
|
100
|
+
// Stay on page and show waiting state (redirect will happen in OrderDetails when order is executed)
|
|
101
101
|
const currentUrl = new URL(window.location.href);
|
|
102
102
|
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
103
103
|
window.history.replaceState(null, "", currentUrl.toString());
|
|
@@ -51,4 +51,6 @@ export declare function AnySpend(props: {
|
|
|
51
51
|
classes?: AnySpendClasses;
|
|
52
52
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
53
53
|
allowDirectTransfer?: boolean;
|
|
54
|
+
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
55
|
+
destinationTokenAmount?: string;
|
|
54
56
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -12,7 +12,7 @@ import invariant from "invariant";
|
|
|
12
12
|
import { ArrowDown, CheckCircle, HistoryIcon, Loader2 } from "lucide-react";
|
|
13
13
|
import { motion } from "motion/react";
|
|
14
14
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
|
-
import { parseUnits } from "viem";
|
|
15
|
+
import { formatUnits, parseUnits } from "viem";
|
|
16
16
|
import { base, mainnet } from "viem/chains";
|
|
17
17
|
import { useAutoSelectCryptoPaymentMethod } from "../hooks/useAutoSelectCryptoPaymentMethod.js";
|
|
18
18
|
import { useConnectedWalletDisplay } from "../hooks/useConnectedWalletDisplay.js";
|
|
@@ -54,7 +54,7 @@ export function AnySpend(props) {
|
|
|
54
54
|
console.log("[mitch] AnySpend rendered with fingerprintConfig:", props, fingerprintConfig);
|
|
55
55
|
return (_jsx(AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: _jsx(AnySpendInner, { ...props }) }));
|
|
56
56
|
}
|
|
57
|
-
function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, }) {
|
|
57
|
+
function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationTokenChainId, mode = "modal", defaultActiveTab = "crypto", loadOrder, hideTransactionHistoryButton, recipientAddress: recipientAddressFromProps, onTokenSelect, onSuccess, customUsdInputValues, hideHeader, hideBottomNavigation = false, disableUrlParamManagement = false, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, allowDirectTransfer = false, destinationTokenAmount, }) {
|
|
58
58
|
const searchParams = useSearchParamsSSR();
|
|
59
59
|
const router = useRouter();
|
|
60
60
|
const { partnerId } = useB3Config();
|
|
@@ -190,6 +190,17 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
190
190
|
useEffect(() => {
|
|
191
191
|
appliedDstMetadataRef.current = false;
|
|
192
192
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
193
|
+
// Prefill destination amount if provided (for fixed amount mode)
|
|
194
|
+
const appliedDestinationAmount = useRef(false);
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
// Only apply when we have real metadata (not default decimals)
|
|
197
|
+
if (destinationTokenAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
198
|
+
appliedDestinationAmount.current = true;
|
|
199
|
+
const formattedAmount = formatUnits(BigInt(destinationTokenAmount), dstTokenMetadata.decimals);
|
|
200
|
+
setDstAmount(formattedAmount);
|
|
201
|
+
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
202
|
+
}
|
|
203
|
+
}, [destinationTokenAmount, dstTokenMetadata]);
|
|
193
204
|
// Load swap configuration from URL on initial render
|
|
194
205
|
useEffect(() => {
|
|
195
206
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -450,9 +461,12 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
450
461
|
anyspendQuote.data.currencyOut?.amount &&
|
|
451
462
|
anyspendQuote.data.currencyOut?.currency?.decimals) {
|
|
452
463
|
if (isSrcInputDirty) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
464
|
+
// Don't override dstAmount if we have a fixed destinationTokenAmount
|
|
465
|
+
if (!destinationTokenAmount) {
|
|
466
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
467
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
468
|
+
setDstAmount(formatTokenAmount(BigInt(amount), decimals, 6, false));
|
|
469
|
+
}
|
|
456
470
|
}
|
|
457
471
|
else {
|
|
458
472
|
const amount = anyspendQuote.data.currencyIn.amount;
|
|
@@ -462,13 +476,16 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
462
476
|
}
|
|
463
477
|
else {
|
|
464
478
|
if (isSrcInputDirty) {
|
|
465
|
-
|
|
479
|
+
// Don't reset dstAmount if we have a fixed destinationTokenAmount
|
|
480
|
+
if (!destinationTokenAmount) {
|
|
481
|
+
setDstAmount("");
|
|
482
|
+
}
|
|
466
483
|
}
|
|
467
484
|
else {
|
|
468
485
|
setSrcAmount("");
|
|
469
486
|
}
|
|
470
487
|
}
|
|
471
|
-
}, [anyspendQuote, isSrcInputDirty]);
|
|
488
|
+
}, [anyspendQuote, isSrcInputDirty, destinationTokenAmount]);
|
|
472
489
|
useEffect(() => {
|
|
473
490
|
if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
474
491
|
console.log("Calling onSuccess");
|
|
@@ -867,7 +884,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
867
884
|
// Reset payment methods when switching tabs
|
|
868
885
|
resetPaymentMethods();
|
|
869
886
|
setSelectedFiatPaymentMethod(FiatPaymentMethod.NONE);
|
|
870
|
-
}, 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: setIsSrcInputDirty, 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) => {
|
|
887
|
+
}, 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: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => navigateToPanel(PanelView.CRYPTO_PAYMENT_METHOD, "forward"), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, onShowFeeDetail: () => navigateToPanel(PanelView.FEE_DETAIL, "forward"), skipAutoMaxOnTokenChange: !!destinationTokenAmount })) : (_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) => {
|
|
871
888
|
// Map panel index to navigation with direction
|
|
872
889
|
const panelsWithForwardNav = [PanelView.FIAT_PAYMENT_METHOD, PanelView.RECIPIENT_SELECTION];
|
|
873
890
|
if (panelsWithForwardNav.includes(panelIndex)) {
|
|
@@ -897,7 +914,7 @@ function AnySpendInner({ sourceChainId, destinationTokenAddress, destinationToke
|
|
|
897
914
|
}, 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: false, 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 => {
|
|
898
915
|
setIsSrcInputDirty(false);
|
|
899
916
|
setDstAmount(value);
|
|
900
|
-
}, 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: [_jsx(ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
917
|
+
}, 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: [_jsx(ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
901
918
|
(btnInfo.disable && classes?.mainButtonDisabled) ||
|
|
902
919
|
classes?.mainButton ||
|
|
903
920
|
cn("as-main-button relative w-full", btnInfo.error ? "!bg-as-red" : btnInfo.disable ? "!bg-as-on-surface-2" : "!bg-as-brand"), textClassName: cn(btnInfo.error ? "text-white" : btnInfo.disable ? "text-as-secondary" : "text-white"), children: _jsxs("div", { className: "flex items-center justify-center gap-2", children: [btnInfo.loading && _jsx(Loader2, { className: "h-4 w-4 animate-spin" }), btnInfo.text] }) }), !hideTransactionHistoryButton && (globalAddress || effectiveRecipientAddress) ? (_jsxs(Button, { variant: "link", onClick: onClickHistory, className: classes?.historyButton ||
|
|
@@ -77,14 +77,13 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
|
|
|
77
77
|
// Prefill destination amount if provided (for EXACT_OUTPUT mode)
|
|
78
78
|
const appliedDestinationAmount = useRef(false);
|
|
79
79
|
useEffect(() => {
|
|
80
|
-
if (destinationTokenAmount && !appliedDestinationAmount.current) {
|
|
80
|
+
if (destinationTokenAmount && destinationToken?.decimals && !appliedDestinationAmount.current) {
|
|
81
81
|
appliedDestinationAmount.current = true;
|
|
82
|
-
// Convert wei to human-readable format
|
|
83
82
|
const formattedAmount = formatUnits(destinationTokenAmount, destinationToken.decimals);
|
|
84
83
|
setDstAmountInput(formattedAmount);
|
|
85
84
|
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
86
85
|
}
|
|
87
|
-
}, [destinationTokenAmount, destinationToken
|
|
86
|
+
}, [destinationTokenAmount, destinationToken, setDstAmountInput, setIsSrcInputDirty]);
|
|
88
87
|
const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
|
|
89
88
|
const expectedDstAmountRaw = anyspendQuote?.data?.currencyOut?.amount ?? "0";
|
|
90
89
|
const buildCustomPayload = (_recipient) => {
|
|
@@ -223,8 +222,8 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
|
|
|
223
222
|
};
|
|
224
223
|
const headerContent = header ? (header({ anyspendPrice: anyspendQuote, isLoadingAnyspendPrice: isLoadingAnyspendQuote })) : (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsxs("div", { children: [_jsx("h1", { className: "text-as-primary text-xl font-bold", children: actionLabel }), _jsx("p", { className: "text-as-secondary text-sm", children: "Pay from any token to execute a custom exact-in transaction." })] }) }));
|
|
225
224
|
const mainView = (_jsxs("div", { className: classes?.container ||
|
|
226
|
-
"anyspend-custom-exact-in-container mx-auto flex w-[460px] max-w-full flex-col items-center gap-2", children: [headerContent, _jsx("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: _jsxs("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [paymentType === "crypto" ? (_jsx(CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect })) : (_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: srcAmount, setSrcAmountOnRamp: setSrcAmount, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: setActivePanel, _recipientAddress: selectedRecipientOrDefault, destinationToken: selectedDstToken, destinationChainId: selectedDstChainId, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, hideDstToken: true, destinationAmount: dstAmount, onDestinationTokenChange: () => { }, onDestinationChainChange: () => { }, fiatPaymentMethodIndex: PanelView.FIAT_PAYMENT_METHOD, recipientSelectionPanelIndex: PanelView.RECIPIENT_SELECTION, anyspendQuote: anyspendQuote, onShowPointsDetail: () => setActivePanel(PanelView.POINTS_DETAIL), onShowFeeDetail: () => setActivePanel(PanelView.FEE_DETAIL), customUsdInputValues: customUsdInputValues, customRecipientLabel: customRecipientLabel }) })), _jsx("div", { className: cn("relative -my-1 flex h-0 items-center justify-center", paymentType === "fiat" && "hidden"), children: _jsx(Button, { variant: "ghost", className: classes?.swapDirectionButton ||
|
|
227
|
-
"swap-direction-button border-as-stroke bg-as-surface-primary z-10 h-10 w-10 cursor-default rounded-xl border-2 sm:h-8 sm:w-8 sm:rounded-xl", children: _jsx("div", { className: "relative flex items-center justify-center transition-opacity", children: _jsx(ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) }), paymentType === "crypto" && (_jsx(CryptoReceiveSection, { isDepositMode: false, isBuyMode: false, effectiveRecipientAddress: selectedRecipientOrDefault, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => setActivePanel(PanelView.RECIPIENT_SELECTION), dstAmount: isSrcInputDirty ? dstAmount : dstAmountInput, dstToken: selectedDstToken, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, dstTokenLogoURI: DESTINATION_TOKEN_DETAILS.LOGO_URI, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: () => { }, setSelectedDstToken: () => { }, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
225
|
+
"anyspend-custom-exact-in-container mx-auto flex w-[460px] max-w-full flex-col items-center gap-2", children: [headerContent, _jsx("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: _jsxs("div", { className: "relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2", children: [paymentType === "crypto" ? (_jsx(CryptoPaySection, { selectedSrcChainId: selectedSrcChainId, setSelectedSrcChainId: setSelectedSrcChainId, selectedSrcToken: selectedSrcToken, setSelectedSrcToken: setSelectedSrcToken, srcAmount: srcAmount, setSrcAmount: setSrcAmount, isSrcInputDirty: isSrcInputDirty, setIsSrcInputDirty: setIsSrcInputDirty, selectedCryptoPaymentMethod: effectiveCryptoPaymentMethod, onSelectCryptoPaymentMethod: () => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD), anyspendQuote: anyspendQuote, onTokenSelect: onTokenSelect, skipAutoMaxOnTokenChange: !!destinationTokenAmount })) : (_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: srcAmount, setSrcAmountOnRamp: setSrcAmount, selectedPaymentMethod: selectedFiatPaymentMethod, setActivePanel: setActivePanel, _recipientAddress: selectedRecipientOrDefault, destinationToken: selectedDstToken, destinationChainId: selectedDstChainId, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, hideDstToken: true, destinationAmount: dstAmount, onDestinationTokenChange: () => { }, onDestinationChainChange: () => { }, fiatPaymentMethodIndex: PanelView.FIAT_PAYMENT_METHOD, recipientSelectionPanelIndex: PanelView.RECIPIENT_SELECTION, anyspendQuote: anyspendQuote, onShowPointsDetail: () => setActivePanel(PanelView.POINTS_DETAIL), onShowFeeDetail: () => setActivePanel(PanelView.FEE_DETAIL), customUsdInputValues: customUsdInputValues, customRecipientLabel: customRecipientLabel }) })), _jsx("div", { className: cn("relative -my-1 flex h-0 items-center justify-center", paymentType === "fiat" && "hidden"), children: _jsx(Button, { variant: "ghost", className: classes?.swapDirectionButton ||
|
|
226
|
+
"swap-direction-button border-as-stroke bg-as-surface-primary z-10 h-10 w-10 cursor-default rounded-xl border-2 sm:h-8 sm:w-8 sm:rounded-xl", children: _jsx("div", { className: "relative flex items-center justify-center transition-opacity", children: _jsx(ArrowDown, { className: "text-as-primary/50 h-5 w-5" }) }) }) }), paymentType === "crypto" && (_jsx(CryptoReceiveSection, { isDepositMode: false, isBuyMode: false, effectiveRecipientAddress: selectedRecipientOrDefault, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => setActivePanel(PanelView.RECIPIENT_SELECTION), dstAmount: isSrcInputDirty && !destinationTokenAmount ? dstAmount : dstAmountInput, dstToken: selectedDstToken, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, dstTokenLogoURI: DESTINATION_TOKEN_DETAILS.LOGO_URI, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: () => { }, setSelectedDstToken: () => { }, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
|
|
228
227
|
setIsSrcInputDirty(false);
|
|
229
228
|
setDstAmountInput(value);
|
|
230
229
|
}, disableAmountInput: !!destinationTokenAmount, anyspendQuote: isDirectTransfer ? undefined : anyspendQuote, onShowPointsDetail: isDirectTransfer ? undefined : () => setActivePanel(PanelView.POINTS_DETAIL), onShowFeeDetail: isDirectTransfer ? undefined : () => setActivePanel(PanelView.FEE_DETAIL) }))] }) }), _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.2, ease: "easeInOut" }, className: cn("mt-4 flex w-full max-w-[460px] flex-col gap-2"), children: _jsx(ShinyButton, { accentColor: "hsl(var(--as-brand))", disabled: btnInfo.disable, onClick: onMainButtonClick, className: (btnInfo.error && classes?.mainButtonError) ||
|
|
@@ -206,5 +206,5 @@ export function AnySpendDeposit({ loadOrder, mode = "modal", recipientAddress, p
|
|
|
206
206
|
// Deposit view
|
|
207
207
|
return (_jsxs("div", { className: depositClasses?.form || "anyspend-deposit anyspend-deposit-form relative", children: [shouldShowChainSelection && (_jsxs("button", { onClick: handleBack, className: depositClasses?.backButton ||
|
|
208
208
|
"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 ||
|
|
209
|
-
"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 }, 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 }, selectedChainId)) }), _jsx(ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
|
|
209
|
+
"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 }, 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 }, selectedChainId)) }), _jsx(ChainWarningText, { chainId: destinationTokenChainId, classes: classes?.chainWarningText || { root: "px-4 pb-4" } })] }));
|
|
210
210
|
}
|
|
@@ -18,6 +18,8 @@ interface CryptoPaySectionProps {
|
|
|
18
18
|
}) => void;
|
|
19
19
|
onShowFeeDetail?: () => void;
|
|
20
20
|
classes?: CryptoPaySectionClasses;
|
|
21
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
22
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
21
23
|
}
|
|
22
|
-
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, skipAutoMaxOnTokenChange, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
23
25
|
export {};
|
|
@@ -8,7 +8,7 @@ import { useConnectedWalletDisplay } from "../../hooks/useConnectedWalletDisplay
|
|
|
8
8
|
import { CryptoPaymentMethodDisplay } from "./CryptoPaymentMethodDisplay.js";
|
|
9
9
|
import { OrderTokenAmount } from "./OrderTokenAmount.js";
|
|
10
10
|
import { TokenBalance } from "./TokenBalance.js";
|
|
11
|
-
export function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, }) {
|
|
11
|
+
export function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, skipAutoMaxOnTokenChange = false, }) {
|
|
12
12
|
const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
13
13
|
// Use custom hook to determine wallet address based on payment method
|
|
14
14
|
const { walletAddress } = useConnectedWalletDisplay(selectedCryptoPaymentMethod);
|
|
@@ -43,7 +43,7 @@ export function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, se
|
|
|
43
43
|
"text-as-tertiarry flex h-7 items-center gap-2 text-sm transition-colors focus:!outline-none", onClick: onSelectCryptoPaymentMethod, children: _jsx(CryptoPaymentMethodDisplay, { paymentMethod: selectedCryptoPaymentMethod, connectedAddress: walletAddress, connectedName: connectedName }) })] }), _jsx("div", { className: classes?.inputContainer, children: _jsx(OrderTokenAmount, { address: walletAddress, walletAddress: walletAddress, context: "from", inputValue: srcAmount, onChangeInput: value => {
|
|
44
44
|
setIsSrcInputDirty(true);
|
|
45
45
|
setSrcAmount(value);
|
|
46
|
-
}, chainId: selectedSrcChainId, setChainId: setSelectedSrcChainId, token: selectedSrcToken, setToken: setSelectedSrcToken, onTokenSelect: onTokenSelect }) }), _jsxs("div", { className: classes?.balanceRow || "flex items-center justify-between", children: [_jsx("div", { className: "text-as-primary/50 flex h-5 items-center text-sm", children: formatDisplayNumber(anyspendQuote?.data?.currencyIn?.amountUsd, {
|
|
46
|
+
}, chainId: selectedSrcChainId, setChainId: setSelectedSrcChainId, token: selectedSrcToken, setToken: setSelectedSrcToken, onTokenSelect: onTokenSelect, skipAutoMaxOnTokenChange: skipAutoMaxOnTokenChange }) }), _jsxs("div", { className: classes?.balanceRow || "flex items-center justify-between", children: [_jsx("div", { className: "text-as-primary/50 flex h-5 items-center text-sm", children: formatDisplayNumber(anyspendQuote?.data?.currencyIn?.amountUsd, {
|
|
47
47
|
style: "currency",
|
|
48
48
|
fallback: "",
|
|
49
49
|
}) }), _jsx(TokenBalance, { token: selectedSrcToken, walletAddress: walletAddress, onChangeInput: value => {
|
|
@@ -345,6 +345,25 @@ export const OrderDetails = memo(function OrderDetails({ mode = "modal", order,
|
|
|
345
345
|
handlePayment();
|
|
346
346
|
}
|
|
347
347
|
}, [isPayableState, isComponentReady, handlePayment]);
|
|
348
|
+
// Auto-redirect to redirectUrl when order is executed (for onramp orders)
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (order.status === "executed" && order.onrampMetadata?.redirectUrl) {
|
|
351
|
+
const baseUrl = order.onrampMetadata.redirectUrl;
|
|
352
|
+
try {
|
|
353
|
+
const url = new URL(baseUrl);
|
|
354
|
+
// Prevent Open Redirect vulnerabilities by ensuring the protocol is http or https
|
|
355
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
356
|
+
console.error(`Attempted redirect to a URL with an invalid protocol: ${url.protocol}`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const redirectUrl = `${baseUrl.replace(/\/$/, "")}/${order.id}`;
|
|
360
|
+
window.location.href = redirectUrl;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error("Invalid redirect URL provided:", baseUrl, error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}, [order.status, order.onrampMetadata?.redirectUrl, order.id]);
|
|
348
367
|
if (!srcToken || !dstToken) {
|
|
349
368
|
return _jsx("div", { children: "Loading..." });
|
|
350
369
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { components } from "../../../../anyspend/types/api";
|
|
2
|
-
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, }: {
|
|
2
|
+
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, skipAutoMaxOnTokenChange, }: {
|
|
3
3
|
disabled?: boolean;
|
|
4
4
|
inputValue: string;
|
|
5
5
|
onChangeInput: (value: string) => void;
|
|
@@ -19,4 +19,6 @@ export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput,
|
|
|
19
19
|
preventDefault: () => void;
|
|
20
20
|
}) => void;
|
|
21
21
|
walletAddress?: string | undefined;
|
|
22
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
23
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
22
24
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -11,7 +11,7 @@ import { Button, useTokenBalance } from "../../../../global-account/react/index.
|
|
|
11
11
|
import { cn } from "../../../../shared/utils/index.js";
|
|
12
12
|
import { TokenSelector } from "@relayprotocol/relay-kit-ui";
|
|
13
13
|
import { ChainTokenIcon } from "./ChainTokenIcon.js";
|
|
14
|
-
export function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect = false, canEditAmount = true, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, }) {
|
|
14
|
+
export function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect = false, canEditAmount = true, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, skipAutoMaxOnTokenChange = false, }) {
|
|
15
15
|
// Track previous token to detect changes
|
|
16
16
|
const prevTokenRef = useRef(token.address);
|
|
17
17
|
// Only get token balance when context is "from" (for setting max amount)
|
|
@@ -23,6 +23,11 @@ export function OrderTokenAmount({ disabled, inputValue, onChangeInput, context,
|
|
|
23
23
|
// Only handle "from" context
|
|
24
24
|
if (context !== "from")
|
|
25
25
|
return;
|
|
26
|
+
// Skip auto-max when in fixed destination amount mode
|
|
27
|
+
if (skipAutoMaxOnTokenChange) {
|
|
28
|
+
prevTokenRef.current = token.address;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
26
31
|
// Check if token changed or if this is the initial load with balance
|
|
27
32
|
const isTokenChanged = prevTokenRef.current !== token.address;
|
|
28
33
|
if (isTokenChanged && rawBalance) {
|
|
@@ -43,7 +48,16 @@ export function OrderTokenAmount({ disabled, inputValue, onChangeInput, context,
|
|
|
43
48
|
// Update refs
|
|
44
49
|
prevTokenRef.current = token.address;
|
|
45
50
|
}
|
|
46
|
-
}, [
|
|
51
|
+
}, [
|
|
52
|
+
token.address,
|
|
53
|
+
token.chainId,
|
|
54
|
+
token.decimals,
|
|
55
|
+
chainId,
|
|
56
|
+
context,
|
|
57
|
+
onChangeInput,
|
|
58
|
+
rawBalance,
|
|
59
|
+
skipAutoMaxOnTokenChange,
|
|
60
|
+
]);
|
|
47
61
|
const handleTokenSelect = (newToken) => {
|
|
48
62
|
const token = {
|
|
49
63
|
address: newToken.address,
|
|
@@ -89,9 +89,9 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
89
89
|
}
|
|
90
90
|
// At this point TypeScript knows result.paymentIntent exists and error is undefined
|
|
91
91
|
console.log("@@stripe-web2-payment:success:", JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2));
|
|
92
|
-
// Payment succeeded
|
|
92
|
+
// Payment succeeded
|
|
93
93
|
setMessage(null);
|
|
94
|
-
//
|
|
94
|
+
// Stay on page and show waiting state (redirect will happen in OrderDetails when order is executed)
|
|
95
95
|
const currentUrl = new URL(window.location.href);
|
|
96
96
|
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
97
97
|
window.history.replaceState(null, "", currentUrl.toString());
|
|
@@ -51,4 +51,6 @@ export declare function AnySpend(props: {
|
|
|
51
51
|
classes?: AnySpendClasses;
|
|
52
52
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
53
53
|
allowDirectTransfer?: boolean;
|
|
54
|
+
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
55
|
+
destinationTokenAmount?: string;
|
|
54
56
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,6 +18,8 @@ interface CryptoPaySectionProps {
|
|
|
18
18
|
}) => void;
|
|
19
19
|
onShowFeeDetail?: () => void;
|
|
20
20
|
classes?: CryptoPaySectionClasses;
|
|
21
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
22
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
21
23
|
}
|
|
22
|
-
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function CryptoPaySection({ selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, srcAmount, setSrcAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, onSelectCryptoPaymentMethod, anyspendQuote, onTokenSelect, onShowFeeDetail, classes, skipAutoMaxOnTokenChange, }: CryptoPaySectionProps): import("react/jsx-runtime").JSX.Element;
|
|
23
25
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
2
|
-
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, }: {
|
|
2
|
+
export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput, context, address, chainId, setChainId, token, setToken, hideTokenSelect, canEditAmount, className, innerClassName, amountClassName, tokenSelectClassName, onTokenSelect, walletAddress, skipAutoMaxOnTokenChange, }: {
|
|
3
3
|
disabled?: boolean;
|
|
4
4
|
inputValue: string;
|
|
5
5
|
onChangeInput: (value: string) => void;
|
|
@@ -19,4 +19,6 @@ export declare function OrderTokenAmount({ disabled, inputValue, onChangeInput,
|
|
|
19
19
|
preventDefault: () => void;
|
|
20
20
|
}) => void;
|
|
21
21
|
walletAddress?: string | undefined;
|
|
22
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
23
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
22
24
|
}): import("react/jsx-runtime").JSX.Element;
|
package/package.json
CHANGED
|
@@ -45,7 +45,7 @@ import invariant from "invariant";
|
|
|
45
45
|
import { ArrowDown, CheckCircle, HistoryIcon, Loader2 } from "lucide-react";
|
|
46
46
|
import { motion } from "motion/react";
|
|
47
47
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
48
|
-
import { parseUnits } from "viem";
|
|
48
|
+
import { formatUnits, parseUnits } from "viem";
|
|
49
49
|
import { base, mainnet } from "viem/chains";
|
|
50
50
|
import { components } from "../../types/api";
|
|
51
51
|
import { useAutoSelectCryptoPaymentMethod } from "../hooks/useAutoSelectCryptoPaymentMethod";
|
|
@@ -124,6 +124,8 @@ export function AnySpend(props: {
|
|
|
124
124
|
classes?: AnySpendClasses;
|
|
125
125
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
126
126
|
allowDirectTransfer?: boolean;
|
|
127
|
+
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
128
|
+
destinationTokenAmount?: string;
|
|
127
129
|
}) {
|
|
128
130
|
const fingerprintConfig = getFingerprintConfig();
|
|
129
131
|
|
|
@@ -155,6 +157,7 @@ function AnySpendInner({
|
|
|
155
157
|
returnHomeLabel,
|
|
156
158
|
classes,
|
|
157
159
|
allowDirectTransfer = false,
|
|
160
|
+
destinationTokenAmount,
|
|
158
161
|
}: {
|
|
159
162
|
sourceChainId?: number;
|
|
160
163
|
destinationTokenAddress?: string;
|
|
@@ -175,6 +178,7 @@ function AnySpendInner({
|
|
|
175
178
|
returnHomeLabel?: string;
|
|
176
179
|
classes?: AnySpendClasses;
|
|
177
180
|
allowDirectTransfer?: boolean;
|
|
181
|
+
destinationTokenAmount?: string;
|
|
178
182
|
}) {
|
|
179
183
|
const searchParams = useSearchParamsSSR();
|
|
180
184
|
const router = useRouter();
|
|
@@ -368,6 +372,18 @@ function AnySpendInner({
|
|
|
368
372
|
appliedDstMetadataRef.current = false;
|
|
369
373
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
370
374
|
|
|
375
|
+
// Prefill destination amount if provided (for fixed amount mode)
|
|
376
|
+
const appliedDestinationAmount = useRef(false);
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
// Only apply when we have real metadata (not default decimals)
|
|
379
|
+
if (destinationTokenAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
380
|
+
appliedDestinationAmount.current = true;
|
|
381
|
+
const formattedAmount = formatUnits(BigInt(destinationTokenAmount), dstTokenMetadata.decimals);
|
|
382
|
+
setDstAmount(formattedAmount);
|
|
383
|
+
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
384
|
+
}
|
|
385
|
+
}, [destinationTokenAmount, dstTokenMetadata]);
|
|
386
|
+
|
|
371
387
|
// Load swap configuration from URL on initial render
|
|
372
388
|
useEffect(() => {
|
|
373
389
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -665,9 +681,12 @@ function AnySpendInner({
|
|
|
665
681
|
anyspendQuote.data.currencyOut?.currency?.decimals
|
|
666
682
|
) {
|
|
667
683
|
if (isSrcInputDirty) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
684
|
+
// Don't override dstAmount if we have a fixed destinationTokenAmount
|
|
685
|
+
if (!destinationTokenAmount) {
|
|
686
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
687
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
688
|
+
setDstAmount(formatTokenAmount(BigInt(amount), decimals, 6, false));
|
|
689
|
+
}
|
|
671
690
|
} else {
|
|
672
691
|
const amount = anyspendQuote.data.currencyIn.amount;
|
|
673
692
|
const decimals = anyspendQuote.data.currencyIn.currency.decimals;
|
|
@@ -675,12 +694,15 @@ function AnySpendInner({
|
|
|
675
694
|
}
|
|
676
695
|
} else {
|
|
677
696
|
if (isSrcInputDirty) {
|
|
678
|
-
|
|
697
|
+
// Don't reset dstAmount if we have a fixed destinationTokenAmount
|
|
698
|
+
if (!destinationTokenAmount) {
|
|
699
|
+
setDstAmount("");
|
|
700
|
+
}
|
|
679
701
|
} else {
|
|
680
702
|
setSrcAmount("");
|
|
681
703
|
}
|
|
682
704
|
}
|
|
683
|
-
}, [anyspendQuote, isSrcInputDirty]);
|
|
705
|
+
}, [anyspendQuote, isSrcInputDirty, destinationTokenAmount]);
|
|
684
706
|
|
|
685
707
|
useEffect(() => {
|
|
686
708
|
if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
@@ -1205,6 +1227,7 @@ function AnySpendInner({
|
|
|
1205
1227
|
anyspendQuote={anyspendQuote}
|
|
1206
1228
|
onTokenSelect={onTokenSelect}
|
|
1207
1229
|
onShowFeeDetail={() => navigateToPanel(PanelView.FEE_DETAIL, "forward")}
|
|
1230
|
+
skipAutoMaxOnTokenChange={!!destinationTokenAmount}
|
|
1208
1231
|
/>
|
|
1209
1232
|
) : (
|
|
1210
1233
|
<motion.div
|
|
@@ -1306,6 +1329,7 @@ function AnySpendInner({
|
|
|
1306
1329
|
setIsSrcInputDirty(false);
|
|
1307
1330
|
setDstAmount(value);
|
|
1308
1331
|
}}
|
|
1332
|
+
disableAmountInput={!!destinationTokenAmount}
|
|
1309
1333
|
anyspendQuote={isDirectTransfer ? undefined : anyspendQuote}
|
|
1310
1334
|
onShowPointsDetail={
|
|
1311
1335
|
isDirectTransfer ? undefined : () => navigateToPanel(PanelView.POINTS_DETAIL, "forward")
|
|
@@ -222,14 +222,13 @@ function AnySpendCustomExactInInner({
|
|
|
222
222
|
// Prefill destination amount if provided (for EXACT_OUTPUT mode)
|
|
223
223
|
const appliedDestinationAmount = useRef(false);
|
|
224
224
|
useEffect(() => {
|
|
225
|
-
if (destinationTokenAmount && !appliedDestinationAmount.current) {
|
|
225
|
+
if (destinationTokenAmount && destinationToken?.decimals && !appliedDestinationAmount.current) {
|
|
226
226
|
appliedDestinationAmount.current = true;
|
|
227
|
-
// Convert wei to human-readable format
|
|
228
227
|
const formattedAmount = formatUnits(destinationTokenAmount, destinationToken.decimals);
|
|
229
228
|
setDstAmountInput(formattedAmount);
|
|
230
229
|
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
231
230
|
}
|
|
232
|
-
}, [destinationTokenAmount, destinationToken
|
|
231
|
+
}, [destinationTokenAmount, destinationToken, setDstAmountInput, setIsSrcInputDirty]);
|
|
233
232
|
|
|
234
233
|
const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
|
|
235
234
|
|
|
@@ -419,6 +418,7 @@ function AnySpendCustomExactInInner({
|
|
|
419
418
|
onSelectCryptoPaymentMethod={() => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD)}
|
|
420
419
|
anyspendQuote={anyspendQuote}
|
|
421
420
|
onTokenSelect={onTokenSelect}
|
|
421
|
+
skipAutoMaxOnTokenChange={!!destinationTokenAmount}
|
|
422
422
|
/>
|
|
423
423
|
) : (
|
|
424
424
|
<motion.div
|
|
@@ -474,7 +474,7 @@ function AnySpendCustomExactInInner({
|
|
|
474
474
|
recipientName={recipientName || undefined}
|
|
475
475
|
customRecipientLabel={customRecipientLabel}
|
|
476
476
|
onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
|
|
477
|
-
dstAmount={isSrcInputDirty ? dstAmount : dstAmountInput}
|
|
477
|
+
dstAmount={isSrcInputDirty && !destinationTokenAmount ? dstAmount : dstAmountInput}
|
|
478
478
|
dstToken={selectedDstToken}
|
|
479
479
|
dstTokenSymbol={DESTINATION_TOKEN_DETAILS.SYMBOL}
|
|
480
480
|
dstTokenLogoURI={DESTINATION_TOKEN_DETAILS.LOGO_URI}
|
|
@@ -32,6 +32,8 @@ interface CryptoPaySectionProps {
|
|
|
32
32
|
onShowFeeDetail?: () => void;
|
|
33
33
|
// Custom classes for styling
|
|
34
34
|
classes?: CryptoPaySectionClasses;
|
|
35
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
36
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export function CryptoPaySection({
|
|
@@ -49,6 +51,7 @@ export function CryptoPaySection({
|
|
|
49
51
|
onTokenSelect,
|
|
50
52
|
onShowFeeDetail,
|
|
51
53
|
classes,
|
|
54
|
+
skipAutoMaxOnTokenChange = false,
|
|
52
55
|
}: CryptoPaySectionProps) {
|
|
53
56
|
const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
54
57
|
|
|
@@ -134,6 +137,7 @@ export function CryptoPaySection({
|
|
|
134
137
|
token={selectedSrcToken}
|
|
135
138
|
setToken={setSelectedSrcToken}
|
|
136
139
|
onTokenSelect={onTokenSelect}
|
|
140
|
+
skipAutoMaxOnTokenChange={skipAutoMaxOnTokenChange}
|
|
137
141
|
/>
|
|
138
142
|
</div>
|
|
139
143
|
<div className={classes?.balanceRow || "flex items-center justify-between"}>
|
|
@@ -493,6 +493,25 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
493
493
|
}
|
|
494
494
|
}, [isPayableState, isComponentReady, handlePayment]);
|
|
495
495
|
|
|
496
|
+
// Auto-redirect to redirectUrl when order is executed (for onramp orders)
|
|
497
|
+
useEffect(() => {
|
|
498
|
+
if (order.status === "executed" && order.onrampMetadata?.redirectUrl) {
|
|
499
|
+
const baseUrl = order.onrampMetadata.redirectUrl;
|
|
500
|
+
try {
|
|
501
|
+
const url = new URL(baseUrl);
|
|
502
|
+
// Prevent Open Redirect vulnerabilities by ensuring the protocol is http or https
|
|
503
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
504
|
+
console.error(`Attempted redirect to a URL with an invalid protocol: ${url.protocol}`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const redirectUrl = `${baseUrl.replace(/\/$/, "")}/${order.id}`;
|
|
508
|
+
window.location.href = redirectUrl;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error("Invalid redirect URL provided:", baseUrl, error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}, [order.status, order.onrampMetadata?.redirectUrl, order.id]);
|
|
514
|
+
|
|
496
515
|
if (!srcToken || !dstToken) {
|
|
497
516
|
return <div>Loading...</div>;
|
|
498
517
|
}
|
|
@@ -32,6 +32,7 @@ export function OrderTokenAmount({
|
|
|
32
32
|
tokenSelectClassName,
|
|
33
33
|
onTokenSelect,
|
|
34
34
|
walletAddress,
|
|
35
|
+
skipAutoMaxOnTokenChange = false,
|
|
35
36
|
}: {
|
|
36
37
|
disabled?: boolean;
|
|
37
38
|
inputValue: string;
|
|
@@ -50,6 +51,8 @@ export function OrderTokenAmount({
|
|
|
50
51
|
tokenSelectClassName?: string;
|
|
51
52
|
onTokenSelect?: (token: components["schemas"]["Token"], event: { preventDefault: () => void }) => void;
|
|
52
53
|
walletAddress?: string | undefined;
|
|
54
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
55
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
53
56
|
}) {
|
|
54
57
|
// Track previous token to detect changes
|
|
55
58
|
const prevTokenRef = useRef<string>(token.address);
|
|
@@ -64,6 +67,12 @@ export function OrderTokenAmount({
|
|
|
64
67
|
// Only handle "from" context
|
|
65
68
|
if (context !== "from") return;
|
|
66
69
|
|
|
70
|
+
// Skip auto-max when in fixed destination amount mode
|
|
71
|
+
if (skipAutoMaxOnTokenChange) {
|
|
72
|
+
prevTokenRef.current = token.address;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
// Check if token changed or if this is the initial load with balance
|
|
68
77
|
const isTokenChanged = prevTokenRef.current !== token.address;
|
|
69
78
|
|
|
@@ -88,7 +97,16 @@ export function OrderTokenAmount({
|
|
|
88
97
|
// Update refs
|
|
89
98
|
prevTokenRef.current = token.address;
|
|
90
99
|
}
|
|
91
|
-
}, [
|
|
100
|
+
}, [
|
|
101
|
+
token.address,
|
|
102
|
+
token.chainId,
|
|
103
|
+
token.decimals,
|
|
104
|
+
chainId,
|
|
105
|
+
context,
|
|
106
|
+
onChangeInput,
|
|
107
|
+
rawBalance,
|
|
108
|
+
skipAutoMaxOnTokenChange,
|
|
109
|
+
]);
|
|
92
110
|
|
|
93
111
|
const handleTokenSelect = (newToken: any) => {
|
|
94
112
|
const token: components["schemas"]["Token"] = {
|
|
@@ -159,10 +159,10 @@ function StripePaymentForm({
|
|
|
159
159
|
JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2),
|
|
160
160
|
);
|
|
161
161
|
|
|
162
|
-
// Payment succeeded
|
|
162
|
+
// Payment succeeded
|
|
163
163
|
setMessage(null);
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Stay on page and show waiting state (redirect will happen in OrderDetails when order is executed)
|
|
166
166
|
const currentUrl = new URL(window.location.href);
|
|
167
167
|
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
168
168
|
window.history.replaceState(null, "", currentUrl.toString());
|