@b3dotfun/sdk 0.1.2-alpha.2 → 0.1.2-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.d.ts +1 -0
  2. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +110 -38
  3. package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
  4. package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.js +2 -2
  5. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +4 -6
  6. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -7
  7. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
  8. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +118 -15
  9. package/dist/cjs/anyspend/react/hooks/useRecipientAddressState.js +1 -1
  10. package/dist/cjs/anyspend/utils/format.js +12 -2
  11. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +2 -0
  12. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.d.ts +1 -0
  13. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +111 -39
  14. package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
  15. package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.js +2 -2
  16. package/dist/esm/anyspend/react/components/common/OrderDetails.js +4 -6
  17. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -7
  18. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
  19. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +118 -16
  20. package/dist/esm/anyspend/react/hooks/useRecipientAddressState.js +1 -1
  21. package/dist/esm/anyspend/utils/format.js +12 -2
  22. package/dist/esm/global-account/react/stores/useModalStore.d.ts +2 -0
  23. package/dist/types/anyspend/react/components/AnySpendCustomExactIn.d.ts +1 -0
  24. package/dist/types/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
  25. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
  26. package/dist/types/global-account/react/stores/useModalStore.d.ts +2 -0
  27. package/package.json +1 -1
  28. package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +125 -41
  29. package/src/anyspend/react/components/AnySpendStakeUpsideExactIn.tsx +3 -0
  30. package/src/anyspend/react/components/common/OrderDetails.tsx +4 -6
  31. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +7 -6
  32. package/src/anyspend/react/hooks/useAnyspendFlow.ts +140 -17
  33. package/src/anyspend/react/hooks/useRecipientAddressState.ts +1 -1
  34. package/src/anyspend/utils/format.ts +13 -2
  35. package/src/global-account/react/stores/useModalStore.ts +2 -0
@@ -18,6 +18,7 @@ export interface AnySpendCustomExactInProps {
18
18
  sourceTokenChainId?: number;
19
19
  destinationToken: components["schemas"]["Token"];
20
20
  destinationChainId: number;
21
+ destinationTokenAmount?: string;
21
22
  onSuccess?: (amount: string) => void;
22
23
  onOpenCustomModal?: () => void;
23
24
  mainFooter?: React.ReactNode;
@@ -33,13 +33,13 @@ function AnySpendCustomExactIn(props) {
33
33
  const fingerprintConfig = (0, AnySpendFingerprintWrapper_1.getFingerprintConfig)();
34
34
  return ((0, jsx_runtime_1.jsx)(AnySpendFingerprintWrapper_1.AnySpendFingerprintWrapper, { fingerprint: fingerprintConfig, children: (0, jsx_runtime_1.jsx)(AnySpendCustomExactInInner, { ...props }) }));
35
35
  }
36
- function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddress, paymentType = "crypto", sourceTokenAddress, sourceTokenChainId, destinationToken, destinationChainId, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, customExactInConfig, orderType = "custom_exact_in", minDestinationAmount, header, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, }) {
36
+ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddress, paymentType = "crypto", sourceTokenAddress, sourceTokenChainId, destinationToken, destinationChainId, onSuccess, onOpenCustomModal, mainFooter, onTokenSelect, customUsdInputValues, preferEoa, customExactInConfig, destinationTokenAmount, orderType = "custom_exact_in", minDestinationAmount, header, returnToHomeUrl, customRecipientLabel, returnHomeLabel, classes, }) {
37
37
  const actionLabel = customExactInConfig?.action ?? "Custom Execution";
38
38
  const DESTINATION_TOKEN_DETAILS = {
39
39
  SYMBOL: destinationToken.symbol ?? "TOKEN",
40
40
  LOGO_URI: destinationToken.metadata?.logoURI ?? "",
41
41
  };
42
- const { activePanel, setActivePanel, orderId, setOrderId, oat, selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, selectedDstToken, selectedDstChainId, srcAmount, setSrcAmount, dstAmount, isSrcInputDirty, setIsSrcInputDirty, selectedCryptoPaymentMethod, effectiveCryptoPaymentMethod, setSelectedCryptoPaymentMethod, selectedFiatPaymentMethod, setSelectedFiatPaymentMethod, selectedRecipientAddress, setSelectedRecipientAddress, recipientName, globalAddress, hasEnoughBalance, isBalanceLoading, anyspendQuote, isLoadingAnyspendQuote, activeInputAmountInWei, geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support, createOrder, isCreatingOrder, createOnrampOrder, isCreatingOnrampOrder, } = (0, useAnyspendFlow_1.useAnyspendFlow)({
42
+ const { activePanel, setActivePanel, orderId, setOrderId, oat, selectedSrcChainId, setSelectedSrcChainId, selectedSrcToken, setSelectedSrcToken, selectedDstToken, selectedDstChainId, srcAmount, setSrcAmount, dstAmount, dstAmountInput, setDstAmountInput, isSrcInputDirty, setIsSrcInputDirty, tradeType, selectedCryptoPaymentMethod, effectiveCryptoPaymentMethod, setSelectedCryptoPaymentMethod, selectedFiatPaymentMethod, setSelectedFiatPaymentMethod, selectedRecipientAddress, setSelectedRecipientAddress, recipientName, globalAddress, hasEnoughBalance, isBalanceLoading, anyspendQuote, isLoadingAnyspendQuote, isQuoteLoading, activeInputAmountInWei, activeOutputAmountInWei, geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support, createOrder, isCreatingOrder, createOnrampOrder, isCreatingOnrampOrder, } = (0, useAnyspendFlow_1.useAnyspendFlow)({
43
43
  paymentType,
44
44
  recipientAddress,
45
45
  loadOrder,
@@ -51,6 +51,7 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
51
51
  slippage: SLIPPAGE_PERCENT,
52
52
  disableUrlParamManagement: true,
53
53
  orderType,
54
+ customExactInConfig,
54
55
  });
55
56
  const { connectedEOAWallet } = (0, react_2.useAccountWallet)();
56
57
  const setActiveWallet = (0, react_5.useSetActiveWallet)();
@@ -65,6 +66,17 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
65
66
  }
66
67
  }
67
68
  }, [preferEoa, connectedEOAWallet, setActiveWallet]);
69
+ // Prefill destination amount if provided (for EXACT_OUTPUT mode)
70
+ const appliedDestinationAmount = (0, react_4.useRef)(false);
71
+ (0, react_4.useEffect)(() => {
72
+ if (destinationTokenAmount && !appliedDestinationAmount.current) {
73
+ appliedDestinationAmount.current = true;
74
+ // Convert wei to human-readable format
75
+ const formattedAmount = (0, number_1.formatUnits)(destinationTokenAmount, destinationToken.decimals);
76
+ setDstAmountInput(formattedAmount);
77
+ setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
78
+ }
79
+ }, [destinationTokenAmount, destinationToken.decimals, setDstAmountInput, setIsSrcInputDirty]);
68
80
  const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
69
81
  const expectedDstAmountRaw = anyspendQuote?.data?.currencyOut?.amount ?? "0";
70
82
  const buildCustomPayload = (_recipient) => {
@@ -89,12 +101,14 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
89
101
  };
90
102
  };
91
103
  const btnInfo = (0, react_4.useMemo)(() => {
92
- if (activeInputAmountInWei === "0")
104
+ // Check for empty amount based on trade type
105
+ const isAmountEmpty = tradeType === "EXACT_OUTPUT" ? !dstAmountInput || dstAmountInput === "0" : activeInputAmountInWei === "0";
106
+ if (isAmountEmpty)
93
107
  return { text: "Enter an amount", disable: true, error: false, loading: false };
94
108
  if (orderType === "hype_duel" && selectedSrcToken?.address?.toLowerCase() === constants_1.B3_TOKEN.address.toLowerCase()) {
95
109
  return { text: "Convert to HYPE using B3", disable: false, error: false, loading: false };
96
110
  }
97
- if (isLoadingAnyspendQuote)
111
+ if (isQuoteLoading)
98
112
  return { text: "Loading quote...", disable: true, error: false, loading: true };
99
113
  if (isCreatingOrder || isCreatingOnrampOrder)
100
114
  return { text: "Creating order...", disable: true, error: false, loading: true };
@@ -142,7 +156,7 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
142
156
  return { text: "Continue", disable: false, error: false, loading: false };
143
157
  }, [
144
158
  activeInputAmountInWei,
145
- isLoadingAnyspendQuote,
159
+ isQuoteLoading,
146
160
  isCreatingOrder,
147
161
  isCreatingOnrampOrder,
148
162
  selectedRecipientOrDefault,
@@ -157,6 +171,8 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
157
171
  DESTINATION_TOKEN_DETAILS.SYMBOL,
158
172
  orderType,
159
173
  selectedSrcToken,
174
+ tradeType,
175
+ dstAmountInput,
160
176
  ]);
161
177
  const onMainButtonClick = async () => {
162
178
  if (orderType === "hype_duel" && selectedSrcToken?.address?.toLowerCase() === constants_1.B3_TOKEN.address.toLowerCase()) {
@@ -187,9 +203,9 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
187
203
  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." })] }) }));
188
204
  const mainView = ((0, jsx_runtime_1.jsxs)("div", { className: classes?.container ||
189
205
  "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 ||
190
- "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: true, effectiveRecipientAddress: selectedRecipientOrDefault, recipientName: recipientName || undefined, customRecipientLabel: customRecipientLabel, onSelectRecipient: () => setActivePanel(useAnyspendFlow_1.PanelView.RECIPIENT_SELECTION), dstAmount: dstAmount, dstToken: selectedDstToken, dstTokenSymbol: DESTINATION_TOKEN_DETAILS.SYMBOL, dstTokenLogoURI: DESTINATION_TOKEN_DETAILS.LOGO_URI, selectedDstChainId: selectedDstChainId, setSelectedDstChainId: () => { }, setSelectedDstToken: () => { }, isSrcInputDirty: isSrcInputDirty, onChangeDstAmount: value => {
206
+ "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 => {
191
207
  setIsSrcInputDirty(false);
192
- setSrcAmount(value);
208
+ setDstAmountInput(value);
193
209
  }, anyspendQuote: anyspendQuote, onShowPointsDetail: () => setActivePanel(useAnyspendFlow_1.PanelView.POINTS_DETAIL), onShowFeeDetail: () => 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) ||
194
210
  (btnInfo.disable && classes?.mainButtonDisabled) ||
195
211
  classes?.mainButton ||
@@ -198,20 +214,49 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
198
214
  try {
199
215
  (0, invariant_1.default)(anyspendQuote, "Relay price is not found");
200
216
  (0, invariant_1.default)(selectedRecipientOrDefault, "Recipient address is not found");
201
- const srcAmountBigInt = BigInt(activeInputAmountInWei);
202
- const payload = buildCustomPayload(selectedRecipientOrDefault);
203
- createOrder({
204
- recipientAddress: selectedRecipientOrDefault,
205
- orderType,
206
- srcChain: selectedSrcChainId,
207
- dstChain: selectedDstChainId,
208
- srcToken: selectedSrcToken,
209
- dstToken: selectedDstToken,
210
- srcAmount: srcAmountBigInt.toString(),
211
- expectedDstAmount: expectedDstAmountRaw,
212
- creatorAddress: globalAddress,
213
- payload,
214
- });
217
+ if (tradeType === "EXACT_OUTPUT") {
218
+ // EXACT_OUTPUT mode: create a custom order (like AnySpendStakeUpside)
219
+ const srcAmountFromQuote = anyspendQuote.data?.currencyIn?.amount;
220
+ (0, invariant_1.default)(srcAmountFromQuote, "Source amount from quote is not found");
221
+ const expectedDstAmount = anyspendQuote.data?.currencyOut?.amount ?? "0";
222
+ const encodedData = (0, useAnyspendFlow_1.generateEncodedData)(customExactInConfig, activeOutputAmountInWei);
223
+ createOrder({
224
+ recipientAddress: selectedRecipientOrDefault,
225
+ orderType: "custom",
226
+ srcChain: selectedSrcChainId,
227
+ dstChain: selectedDstChainId,
228
+ srcToken: selectedSrcToken,
229
+ dstToken: selectedDstToken,
230
+ srcAmount: srcAmountFromQuote,
231
+ expectedDstAmount,
232
+ creatorAddress: globalAddress,
233
+ payload: {
234
+ amount: activeOutputAmountInWei,
235
+ data: encodedData,
236
+ to: customExactInConfig ? (0, utils_1.normalizeAddress)(customExactInConfig.to) : undefined,
237
+ spenderAddress: customExactInConfig?.spenderAddress
238
+ ? (0, utils_1.normalizeAddress)(customExactInConfig.spenderAddress)
239
+ : undefined,
240
+ },
241
+ });
242
+ }
243
+ else {
244
+ // EXACT_INPUT mode: create custom_exact_in order (original behavior)
245
+ const srcAmountBigInt = BigInt(activeInputAmountInWei);
246
+ const payload = buildCustomPayload(selectedRecipientOrDefault);
247
+ createOrder({
248
+ recipientAddress: selectedRecipientOrDefault,
249
+ orderType,
250
+ srcChain: selectedSrcChainId,
251
+ dstChain: selectedDstChainId,
252
+ srcToken: selectedSrcToken,
253
+ dstToken: selectedDstToken,
254
+ srcAmount: srcAmountBigInt.toString(),
255
+ expectedDstAmount: expectedDstAmountRaw,
256
+ creatorAddress: globalAddress,
257
+ payload,
258
+ });
259
+ }
215
260
  }
216
261
  catch (err) {
217
262
  console.error(err);
@@ -247,23 +292,50 @@ function AnySpendCustomExactInInner({ loadOrder, mode = "modal", recipientAddres
247
292
  react_2.toast.error("Please select a payment method");
248
293
  return;
249
294
  }
250
- const payload = buildCustomPayload(selectedRecipientOrDefault);
251
- createOnrampOrder({
252
- recipientAddress: selectedRecipientOrDefault,
253
- orderType,
254
- dstChain: selectedDstChainId,
255
- dstToken: selectedDstToken,
256
- srcFiatAmount: srcAmount,
257
- onramp: {
258
- vendor,
259
- paymentMethod: paymentMethodString,
260
- country: geoData?.country || "US",
261
- redirectUrl: window.location.origin,
262
- },
263
- expectedDstAmount: expectedDstAmountRaw,
264
- creatorAddress: globalAddress,
265
- payload,
266
- });
295
+ const onrampOptions = {
296
+ vendor,
297
+ paymentMethod: paymentMethodString,
298
+ country: geoData?.country || "US",
299
+ redirectUrl: window.location.origin,
300
+ };
301
+ if (tradeType === "EXACT_OUTPUT") {
302
+ // EXACT_OUTPUT mode: create a custom order (like AnySpendStakeUpside)
303
+ const expectedDstAmount = anyspendQuote.data?.currencyOut?.amount ?? "0";
304
+ const encodedData = (0, useAnyspendFlow_1.generateEncodedData)(customExactInConfig, activeOutputAmountInWei);
305
+ createOnrampOrder({
306
+ recipientAddress: selectedRecipientOrDefault,
307
+ orderType: "custom",
308
+ dstChain: selectedDstChainId,
309
+ dstToken: selectedDstToken,
310
+ srcFiatAmount: srcAmount,
311
+ onramp: onrampOptions,
312
+ expectedDstAmount,
313
+ creatorAddress: globalAddress,
314
+ payload: {
315
+ amount: activeOutputAmountInWei,
316
+ data: encodedData,
317
+ to: customExactInConfig ? (0, utils_1.normalizeAddress)(customExactInConfig.to) : undefined,
318
+ spenderAddress: customExactInConfig?.spenderAddress
319
+ ? (0, utils_1.normalizeAddress)(customExactInConfig.spenderAddress)
320
+ : undefined,
321
+ },
322
+ });
323
+ }
324
+ else {
325
+ // EXACT_INPUT mode: create custom_exact_in order (original behavior)
326
+ const payload = buildCustomPayload(selectedRecipientOrDefault);
327
+ createOnrampOrder({
328
+ recipientAddress: selectedRecipientOrDefault,
329
+ orderType,
330
+ dstChain: selectedDstChainId,
331
+ dstToken: selectedDstToken,
332
+ srcFiatAmount: srcAmount,
333
+ onramp: onrampOptions,
334
+ expectedDstAmount: expectedDstAmountRaw,
335
+ creatorAddress: globalAddress,
336
+ payload,
337
+ });
338
+ }
267
339
  }
268
340
  catch (err) {
269
341
  console.error(err);
@@ -1,5 +1,5 @@
1
1
  import { components } from "../../../anyspend/types/api";
2
- export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientAddress, sourceTokenAddress, sourceTokenChainId, stakingContractAddress, token, onSuccess, }: {
2
+ export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientAddress, sourceTokenAddress, sourceTokenChainId, destinationTokenAmount, stakingContractAddress, token, onSuccess, }: {
3
3
  loadOrder?: string;
4
4
  mode?: "modal" | "page";
5
5
  recipientAddress: string;
@@ -7,5 +7,6 @@ export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientA
7
7
  sourceTokenChainId?: number;
8
8
  stakingContractAddress: string;
9
9
  token: components["schemas"]["Token"];
10
+ destinationTokenAmount?: string;
10
11
  onSuccess?: (amount: string) => void;
11
12
  }): import("react/jsx-runtime").JSX.Element | null;
@@ -17,7 +17,7 @@ const STAKE_FOR_FUNCTION_ABI = JSON.stringify([
17
17
  outputs: [],
18
18
  },
19
19
  ]);
20
- function AnySpendStakeUpsideExactIn({ loadOrder, mode = "modal", recipientAddress, sourceTokenAddress, sourceTokenChainId, stakingContractAddress, token, onSuccess, }) {
20
+ function AnySpendStakeUpsideExactIn({ loadOrder, mode = "modal", recipientAddress, sourceTokenAddress, sourceTokenChainId, destinationTokenAmount, stakingContractAddress, token, onSuccess, }) {
21
21
  if (!recipientAddress)
22
22
  return null;
23
23
  const header = () => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { className: "from-b3-react-background to-as-on-surface-1 w-full rounded-t-lg bg-gradient-to-t", children: (0, jsx_runtime_1.jsx)("div", { className: "mb-1 flex w-full flex-col items-center gap-2", children: (0, jsx_runtime_1.jsxs)("span", { className: "font-sf-rounded text-2xl font-semibold", children: ["Swap & Stake ", token.symbol, " (Exact In)"] }) }) }) }));
@@ -29,5 +29,5 @@ function AnySpendStakeUpsideExactIn({ loadOrder, mode = "modal", recipientAddres
29
29
  spenderAddress: stakingContractAddress,
30
30
  action: `stake ${token.symbol}`,
31
31
  };
32
- return ((0, jsx_runtime_1.jsx)(AnySpendCustomExactIn_1.AnySpendCustomExactIn, { loadOrder: loadOrder, mode: mode, recipientAddress: recipientAddress, sourceTokenAddress: sourceTokenAddress, sourceTokenChainId: sourceTokenChainId, destinationToken: token, destinationChainId: chains_1.base.id, customExactInConfig: customExactInConfig, header: header, onSuccess: onSuccess }));
32
+ return ((0, jsx_runtime_1.jsx)(AnySpendCustomExactIn_1.AnySpendCustomExactIn, { loadOrder: loadOrder, mode: mode, recipientAddress: recipientAddress, sourceTokenAddress: sourceTokenAddress, sourceTokenChainId: sourceTokenChainId, destinationToken: token, destinationChainId: chains_1.base.id, destinationTokenAmount: destinationTokenAmount, customExactInConfig: customExactInConfig, header: header, onSuccess: onSuccess }));
33
33
  }
@@ -354,13 +354,11 @@ exports.OrderDetails = (0, react_5.memo)(function OrderDetails({ mode = "modal",
354
354
  if (!srcToken || !dstToken) {
355
355
  return (0, jsx_runtime_1.jsx)("div", { children: "Loading..." });
356
356
  }
357
- const expectedDstAmount = order.type === "mint_nft" ||
358
- order.type === "join_tournament" ||
359
- order.type === "fund_tournament" ||
360
- order.type === "custom" ||
361
- order.type === "deposit_first"
357
+ const expectedDstAmount = order.type === "mint_nft" || order.type === "join_tournament" || order.type === "fund_tournament"
362
358
  ? "0"
363
- : order.payload.expectedDstAmount.toString();
359
+ : order.type === "custom" || order.type === "deposit_first"
360
+ ? order.payload.amount?.toString() || "0"
361
+ : order.payload.expectedDstAmount.toString();
364
362
  const formattedExpectedDstAmount = (0, number_1.formatTokenAmount)(BigInt(expectedDstAmount), dstToken.decimals);
365
363
  const actualDstAmount = order.settlement?.actualDstAmount;
366
364
  const formattedActualDstAmount = actualDstAmount
@@ -22,13 +22,12 @@ exports.OrderDetailsCollapsible = (0, react_3.memo)(function OrderDetailsCollaps
22
22
  const showOrderDetails = isOpen !== undefined ? isOpen : internalOpen;
23
23
  const setShowOrderDetails = onOpenChange || setInternalOpen;
24
24
  // Calculate expected amount if not provided
25
- const expectedDstAmount = order.type === "mint_nft" ||
26
- order.type === "join_tournament" ||
27
- order.type === "fund_tournament" ||
28
- order.type === "custom" ||
29
- order.type === "deposit_first"
25
+ // For custom orders, use payload.amount as the expected destination amount
26
+ const expectedDstAmount = order.type === "mint_nft" || order.type === "join_tournament" || order.type === "fund_tournament"
30
27
  ? "0"
31
- : order.payload.expectedDstAmount.toString();
28
+ : order.type === "custom" || order.type === "deposit_first"
29
+ ? order.payload.amount?.toString() || "0"
30
+ : order.payload.expectedDstAmount.toString();
32
31
  const finalFormattedExpectedDstAmount = formattedExpectedDstAmount || (0, number_1.formatTokenAmount)(BigInt(expectedDstAmount), dstToken.decimals);
33
32
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)(classes?.container ||
34
33
  "order-details-collapsible bg-as-surface-secondary border-as-border-secondary rounded-xl border px-4 py-2", className), children: showOrderDetails ? ((0, jsx_runtime_1.jsx)(react_2.motion.div, { className: "order-details-expanded w-full", 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.jsxs)("div", { className: "order-details-content flex w-full flex-col items-center gap-3 whitespace-nowrap py-2 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "order-details-recipient-section flex w-full justify-between gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-recipient-label text-as-tertiarry", children: "Recipient" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-recipient-info flex flex-col items-end gap-1", children: [recipientName && ((0, jsx_runtime_1.jsx)("div", { className: "order-details-recipient-name text-as-primary font-semibold", children: recipientName })), (0, jsx_runtime_1.jsx)(react_1.CopyToClipboard, { text: order.recipientAddress, onCopy: () => {
@@ -46,5 +45,5 @@ exports.OrderDetailsCollapsible = (0, react_3.memo)(function OrderDetailsCollaps
46
45
  ? order.metadata.action
47
46
  ? (0, anyspend_1.capitalizeFirstLetter)(order.metadata.action)
48
47
  : "Contract execution"
49
- : "" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-expected-value flex flex-wrap items-center justify-end gap-2", children: [order.type === "swap" || order.type === "deposit_first" ? ((0, jsx_runtime_1.jsx)("span", { className: "order-details-amount-text", children: `~${finalFormattedExpectedDstAmount} ${dstToken.symbol}` })) : order.type === "mint_nft" ? ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-nft-info flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: nft?.imageUrl, alt: nft?.name || "NFT", className: "order-details-nft-image h-5 w-5" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-nft-name", children: nft?.name || "NFT" })] })) : order.type === "join_tournament" || order.type === "fund_tournament" ? ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-tournament-info flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: tournament?.imageUrl, alt: tournament?.name || "Tournament", className: "order-details-tournament-image h-5 w-5" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-tournament-name", children: tournament?.name || "Tournament" })] })) : order.type === "hype_duel" ? ((0, jsx_runtime_1.jsx)("div", { className: "order-details-hype-info flex items-center gap-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "order-details-hype-amount", children: [(0, number_1.formatTokenAmount)(BigInt(order.payload.expectedDstAmount), dstToken.decimals), " HYPE"] }) })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "order-details-chain-info text-as-primary/50 flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("span", { className: "order-details-chain-text", children: ["on ", order.dstChain !== chains_1.b3.id && (0, anyspend_1.getChainName)(order.dstChain)] }), (0, jsx_runtime_1.jsx)("img", { src: anyspend_1.ALL_CHAINS[order.dstChain].logoUrl, alt: (0, anyspend_1.getChainName)(order.dstChain), className: (0, utils_1.cn)("order-details-chain-logo h-3", order.dstChain !== chains_1.b3.id && "w-3 rounded-full", order.dstChain === chains_1.b3.id && "h-4") })] })] })] }), points !== undefined && points !== null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-divider divider w-full" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-points-section flex w-full justify-between gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-points-label text-as-tertiarry", children: "Points" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-points-value text-as-brand font-semibold", children: ["+", (0, formatNumber_1.formatNumber)(points), " pts"] })] })] })), (0, jsx_runtime_1.jsx)("div", { className: "order-details-divider divider w-full" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-id-total-section flex w-full justify-between gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-id-total-label text-as-tertiarry", children: showTotal ? "Total (included fee)" : "Order ID" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-id-total-value text-as-primary overflow-hidden text-ellipsis whitespace-nowrap", children: showTotal && totalAmount ? totalAmount : order.id })] })] }) })) : ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-collapsed flex w-full items-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-collapsed-divider divider w-full" }), (0, jsx_runtime_1.jsx)("button", { className: "order-details-collapsed-button whitespace-nowrap text-sm", onClick: () => setShowOrderDetails(true), children: "Order Details" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "order-details-collapsed-chevron text-as-primary mx-1 h-4 min-h-4 w-4 min-w-4" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-collapsed-divider divider w-full" })] })) }));
48
+ : "" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-expected-value flex flex-wrap items-center justify-end gap-2", children: [order.type === "swap" || order.type === "deposit_first" ? ((0, jsx_runtime_1.jsx)("span", { className: "order-details-amount-text", children: `~${finalFormattedExpectedDstAmount} ${dstToken.symbol}` })) : order.type === "mint_nft" ? ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-nft-info flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: nft?.imageUrl, alt: nft?.name || "NFT", className: "order-details-nft-image h-5 w-5" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-nft-name", children: nft?.name || "NFT" })] })) : order.type === "join_tournament" || order.type === "fund_tournament" ? ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-tournament-info flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: tournament?.imageUrl, alt: tournament?.name || "Tournament", className: "order-details-tournament-image h-5 w-5" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-tournament-name", children: tournament?.name || "Tournament" })] })) : order.type === "hype_duel" ? ((0, jsx_runtime_1.jsx)("div", { className: "order-details-hype-info flex items-center gap-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "order-details-hype-amount", children: [(0, number_1.formatTokenAmount)(BigInt(order.payload.expectedDstAmount), dstToken.decimals), " HYPE"] }) })) : order.type === "custom" || order.type === "custom_exact_in" ? ((0, jsx_runtime_1.jsx)("span", { className: "order-details-amount-text", children: `~${finalFormattedExpectedDstAmount} ${dstToken.symbol}` })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "order-details-chain-info text-as-primary/50 flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("span", { className: "order-details-chain-text", children: ["on ", order.dstChain !== chains_1.b3.id && (0, anyspend_1.getChainName)(order.dstChain)] }), (0, jsx_runtime_1.jsx)("img", { src: anyspend_1.ALL_CHAINS[order.dstChain].logoUrl, alt: (0, anyspend_1.getChainName)(order.dstChain), className: (0, utils_1.cn)("order-details-chain-logo h-3", order.dstChain !== chains_1.b3.id && "w-3 rounded-full", order.dstChain === chains_1.b3.id && "h-4") })] })] })] }), points !== undefined && points !== null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-divider divider w-full" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-points-section flex w-full justify-between gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-points-label text-as-tertiarry", children: "Points" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-points-value text-as-brand font-semibold", children: ["+", (0, formatNumber_1.formatNumber)(points), " pts"] })] })] })), (0, jsx_runtime_1.jsx)("div", { className: "order-details-divider divider w-full" }), (0, jsx_runtime_1.jsxs)("div", { className: "order-details-id-total-section flex w-full justify-between gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-id-total-label text-as-tertiarry", children: showTotal ? "Total (included fee)" : "Order ID" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-id-total-value text-as-primary overflow-hidden text-ellipsis whitespace-nowrap", children: showTotal && totalAmount ? totalAmount : order.id })] })] }) })) : ((0, jsx_runtime_1.jsxs)("div", { className: "order-details-collapsed flex w-full items-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "order-details-collapsed-divider divider w-full" }), (0, jsx_runtime_1.jsx)("button", { className: "order-details-collapsed-button whitespace-nowrap text-sm", onClick: () => setShowOrderDetails(true), children: "Order Details" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "order-details-collapsed-chevron text-as-primary mx-1 h-4 min-h-4 w-4 min-w-4" }), (0, jsx_runtime_1.jsx)("div", { className: "order-details-collapsed-divider divider w-full" })] })) }));
50
49
  });
@@ -11,6 +11,19 @@ export declare enum PanelView {
11
11
  POINTS_DETAIL = 6,
12
12
  FEE_DETAIL = 7
13
13
  }
14
+ export type CustomExactInConfig = {
15
+ functionAbi: string;
16
+ functionName: string;
17
+ functionArgs: string[];
18
+ to: string;
19
+ spenderAddress?: string;
20
+ action?: string;
21
+ };
22
+ /**
23
+ * Generates encoded function data for custom contract calls.
24
+ * Handles amount placeholder replacement and BigInt conversion.
25
+ */
26
+ export declare function generateEncodedData(config: CustomExactInConfig | undefined, amountInWei: string): string | undefined;
14
27
  interface UseAnyspendFlowProps {
15
28
  paymentType?: "crypto" | "fiat";
16
29
  recipientAddress?: string;
@@ -25,8 +38,9 @@ interface UseAnyspendFlowProps {
25
38
  slippage?: number;
26
39
  disableUrlParamManagement?: boolean;
27
40
  orderType?: "hype_duel" | "custom_exact_in" | "swap";
41
+ customExactInConfig?: CustomExactInConfig;
28
42
  }
29
- export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage, disableUrlParamManagement, orderType, }: UseAnyspendFlowProps): {
43
+ export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage, disableUrlParamManagement, orderType, customExactInConfig, }: UseAnyspendFlowProps): {
30
44
  activePanel: PanelView;
31
45
  setActivePanel: import("react").Dispatch<import("react").SetStateAction<PanelView>>;
32
46
  orderId: string | undefined;
@@ -91,8 +105,11 @@ export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrd
91
105
  setSrcAmount: import("react").Dispatch<import("react").SetStateAction<string>>;
92
106
  dstAmount: string;
93
107
  setDstAmount: import("react").Dispatch<import("react").SetStateAction<string>>;
108
+ dstAmountInput: string;
109
+ setDstAmountInput: import("react").Dispatch<import("react").SetStateAction<string>>;
94
110
  isSrcInputDirty: boolean;
95
111
  setIsSrcInputDirty: import("react").Dispatch<import("react").SetStateAction<boolean>>;
112
+ tradeType: string;
96
113
  cryptoPaymentMethod: CryptoPaymentMethodType;
97
114
  setCryptoPaymentMethod: (method: CryptoPaymentMethodType) => void;
98
115
  selectedCryptoPaymentMethod: CryptoPaymentMethodType;
@@ -176,8 +193,10 @@ export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrd
176
193
  statusCode: number;
177
194
  } | undefined;
178
195
  isLoadingAnyspendQuote: boolean;
196
+ isQuoteLoading: boolean;
179
197
  getAnyspendQuoteError: Error | null;
180
198
  activeInputAmountInWei: string;
199
+ activeOutputAmountInWei: string;
181
200
  geoData: import("../../../anyspend/react").GeoData | undefined;
182
201
  coinbaseAvailablePaymentMethods: {
183
202
  id?: string;
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PanelView = void 0;
4
+ exports.generateEncodedData = generateEncodedData;
4
5
  exports.useAnyspendFlow = useAnyspendFlow;
5
6
  const anyspend_1 = require("../../../anyspend");
6
7
  const react_1 = require("../../../anyspend/react");
7
8
  const anyspend_2 = require("../../../anyspend/services/anyspend");
9
+ const utils_1 = require("../../../anyspend/utils");
8
10
  const react_2 = require("../../../global-account/react");
9
11
  const number_1 = require("../../../shared/utils/number");
10
12
  const react_3 = require("react");
@@ -27,8 +29,50 @@ var PanelView;
27
29
  PanelView[PanelView["POINTS_DETAIL"] = 6] = "POINTS_DETAIL";
28
30
  PanelView[PanelView["FEE_DETAIL"] = 7] = "FEE_DETAIL";
29
31
  })(PanelView || (exports.PanelView = PanelView = {}));
32
+ /**
33
+ * Generates encoded function data for custom contract calls.
34
+ * Handles amount placeholder replacement and BigInt conversion.
35
+ */
36
+ function generateEncodedData(config, amountInWei) {
37
+ if (!config || !config.functionAbi || !config.functionName || !config.functionArgs) {
38
+ console.warn("customExactInConfig missing required fields for encoding:", {
39
+ hasConfig: !!config,
40
+ hasFunctionAbi: !!config?.functionAbi,
41
+ hasFunctionName: !!config?.functionName,
42
+ hasFunctionArgs: !!config?.functionArgs,
43
+ });
44
+ return undefined;
45
+ }
46
+ try {
47
+ const abi = JSON.parse(config.functionAbi);
48
+ const processedArgs = config.functionArgs.map(arg => {
49
+ // Replace amount placeholders ({{dstAmount}}, {{amount_out}}, etc.)
50
+ if (arg === "{{dstAmount}}" || arg === "{{amount_out}}") {
51
+ return BigInt(amountInWei);
52
+ }
53
+ // Convert numeric strings to BigInt for uint256 args
54
+ if (/^\d+$/.test(arg)) {
55
+ return BigInt(arg);
56
+ }
57
+ return arg;
58
+ });
59
+ return (0, viem_1.encodeFunctionData)({
60
+ abi,
61
+ functionName: config.functionName,
62
+ args: processedArgs,
63
+ });
64
+ }
65
+ catch (e) {
66
+ console.error("Failed to encode function data:", e, {
67
+ functionAbi: config.functionAbi,
68
+ functionName: config.functionName,
69
+ functionArgs: config.functionArgs,
70
+ });
71
+ return undefined;
72
+ }
73
+ }
30
74
  // This hook serves for order hype_duel and custom_exact_in
31
- function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage = 0, disableUrlParamManagement = false, orderType = "hype_duel", }) {
75
+ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage = 0, disableUrlParamManagement = false, orderType = "hype_duel", customExactInConfig, }) {
32
76
  const searchParams = (0, react_2.useSearchParamsSSR)();
33
77
  const router = (0, react_2.useRouter)();
34
78
  // Panel and order state
@@ -43,6 +87,8 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
43
87
  const [selectedDstToken, setSelectedDstToken] = (0, react_3.useState)(defaultDstToken);
44
88
  const [srcAmount, setSrcAmount] = (0, react_3.useState)(paymentType === "fiat" ? "5" : "0");
45
89
  const [dstAmount, setDstAmount] = (0, react_3.useState)("");
90
+ const [dstAmountInput, setDstAmountInput] = (0, react_3.useState)(""); // User input for destination amount (EXACT_OUTPUT mode)
91
+ const [debouncedDstAmountInput, setDebouncedDstAmountInput] = (0, react_3.useState)(""); // Debounced version for quote requests
46
92
  const [isSrcInputDirty, setIsSrcInputDirty] = (0, react_3.useState)(true);
47
93
  // Derive destination chain ID from token or prop (cannot change)
48
94
  const selectedDstChainId = destinationTokenChainId || selectedDstToken.chainId;
@@ -120,6 +166,16 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
120
166
  };
121
167
  fetchDestinationToken();
122
168
  }, [destinationTokenAddress, destinationTokenChainId]);
169
+ // Check if destination token is ready (matches the expected address from props)
170
+ // This is important for EXACT_OUTPUT mode where we need correct decimals
171
+ const isDestinationTokenReady = !destinationTokenAddress || selectedDstToken.address.toLowerCase() === destinationTokenAddress.toLowerCase();
172
+ // Debounce destination amount input for quote requests (500ms delay)
173
+ (0, react_3.useEffect)(() => {
174
+ const timer = setTimeout(() => {
175
+ setDebouncedDstAmountInput(dstAmountInput);
176
+ }, 500);
177
+ return () => clearTimeout(timer);
178
+ }, [dstAmountInput]);
123
179
  // Helper function for onramp vendor mapping
124
180
  const getOnrampVendor = (paymentMethod) => {
125
181
  switch (paymentMethod) {
@@ -133,8 +189,16 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
133
189
  };
134
190
  // Get quote
135
191
  // For fiat payments, always use USDC decimals (6) regardless of selectedSrcToken
136
- const effectiveDecimals = paymentType === "fiat" ? anyspend_1.USDC_BASE.decimals : selectedSrcToken.decimals;
137
- const activeInputAmountInWei = (0, viem_1.parseUnits)(srcAmount.replace(/,/g, ""), effectiveDecimals).toString();
192
+ const effectiveSrcDecimals = paymentType === "fiat" ? anyspend_1.USDC_BASE.decimals : selectedSrcToken.decimals;
193
+ const activeInputAmountInWei = (0, viem_1.parseUnits)(srcAmount.replace(/,/g, ""), effectiveSrcDecimals).toString();
194
+ // Calculate output amount in wei for EXACT_OUTPUT mode
195
+ // Only calculate when destination token is ready (has correct decimals)
196
+ // Use debounced value to reduce quote API calls
197
+ const activeOutputAmountInWei = isDestinationTokenReady
198
+ ? (0, viem_1.parseUnits)(debouncedDstAmountInput.replace(/,/g, "") || "0", selectedDstToken.decimals).toString()
199
+ : "0";
200
+ // Determine trade type based on which input was last edited
201
+ const tradeType = isSrcInputDirty ? "EXACT_INPUT" : "EXACT_OUTPUT";
138
202
  // Build quote request based on order type
139
203
  const quoteRequest = (() => {
140
204
  const baseParams = {
@@ -149,8 +213,8 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
149
213
  return {
150
214
  ...baseParams,
151
215
  type: "swap",
152
- tradeType: "EXACT_INPUT",
153
- amount: activeInputAmountInWei,
216
+ tradeType: tradeType,
217
+ amount: tradeType === "EXACT_INPUT" ? activeInputAmountInWei : activeOutputAmountInWei,
154
218
  };
155
219
  }
156
220
  else if (orderType === "hype_duel") {
@@ -161,6 +225,22 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
161
225
  };
162
226
  }
163
227
  else {
228
+ // custom_exact_in - for EXACT_OUTPUT, use custom type to get the quote
229
+ if (tradeType === "EXACT_OUTPUT") {
230
+ const encodedData = generateEncodedData(customExactInConfig, activeOutputAmountInWei);
231
+ return {
232
+ ...baseParams,
233
+ type: "custom",
234
+ payload: {
235
+ amount: activeOutputAmountInWei,
236
+ data: encodedData || "",
237
+ to: customExactInConfig ? (0, utils_1.normalizeAddress)(customExactInConfig.to) : "",
238
+ spenderAddress: customExactInConfig?.spenderAddress
239
+ ? (0, utils_1.normalizeAddress)(customExactInConfig.spenderAddress)
240
+ : undefined,
241
+ },
242
+ };
243
+ }
164
244
  return {
165
245
  ...baseParams,
166
246
  type: "custom_exact_in",
@@ -169,22 +249,40 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
169
249
  }
170
250
  })();
171
251
  const { anyspendQuote, isLoadingAnyspendQuote, getAnyspendQuoteError } = (0, react_1.useAnyspendQuote)(quoteRequest);
252
+ // Combined loading state: includes debounce waiting period and actual quote fetching
253
+ // For EXACT_OUTPUT mode, also check if we're waiting for debounce
254
+ const isDebouncingDstAmount = tradeType === "EXACT_OUTPUT" && dstAmountInput !== debouncedDstAmountInput;
255
+ const isQuoteLoading = isLoadingAnyspendQuote || isDebouncingDstAmount;
172
256
  // Get geo options for fiat
173
257
  const { geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support } = (0, react_1.useGeoOnrampOptions)(paymentType === "fiat" ? (0, number_1.formatUnits)(activeInputAmountInWei, anyspend_1.USDC_BASE.decimals) : "0");
174
- // Update destination amount when quote changes
258
+ // Update amounts when quote changes based on trade type
175
259
  (0, react_3.useEffect)(() => {
176
- if (anyspendQuote?.data?.currencyOut?.amount && anyspendQuote.data.currencyOut.currency?.decimals) {
177
- const amount = anyspendQuote.data.currencyOut.amount;
178
- const decimals = anyspendQuote.data.currencyOut.currency.decimals;
179
- // Apply slippage (0-100) - reduce amount by slippage percentageFixed slippage value
180
- const amountWithSlippage = (BigInt(amount) * BigInt(100 - slippage)) / BigInt(100);
181
- const formattedAmount = (0, number_1.formatTokenAmount)(amountWithSlippage, decimals, 6, false);
182
- setDstAmount(formattedAmount);
260
+ if (isSrcInputDirty) {
261
+ // EXACT_INPUT mode: update destination amount from quote
262
+ if (anyspendQuote?.data?.currencyOut?.amount && anyspendQuote.data.currencyOut.currency?.decimals) {
263
+ const amount = anyspendQuote.data.currencyOut.amount;
264
+ const decimals = anyspendQuote.data.currencyOut.currency.decimals;
265
+ // Apply slippage (0-100) - reduce amount by slippage percentage
266
+ const amountWithSlippage = (BigInt(amount) * BigInt(100 - slippage)) / BigInt(100);
267
+ const formattedAmount = (0, number_1.formatTokenAmount)(amountWithSlippage, decimals, 6, false);
268
+ setDstAmount(formattedAmount);
269
+ }
270
+ else {
271
+ setDstAmount("");
272
+ }
183
273
  }
184
274
  else {
185
- setDstAmount("");
275
+ // EXACT_OUTPUT mode: update source amount from quote
276
+ if (anyspendQuote?.data?.currencyIn?.amount && anyspendQuote.data.currencyIn.currency?.decimals) {
277
+ const amount = anyspendQuote.data.currencyIn.amount;
278
+ const decimals = anyspendQuote.data.currencyIn.currency.decimals;
279
+ const formattedAmount = (0, number_1.formatTokenAmount)(BigInt(amount), decimals, 6, false);
280
+ setSrcAmount(formattedAmount);
281
+ }
282
+ // Also set the display destination amount from the user input
283
+ setDstAmount(dstAmountInput);
186
284
  }
187
- }, [anyspendQuote, slippage]);
285
+ }, [anyspendQuote, slippage, isSrcInputDirty, dstAmountInput]);
188
286
  // Update useEffect for URL parameter to not override loadOrder
189
287
  (0, react_3.useEffect)(() => {
190
288
  if (loadOrder || disableUrlParamManagement)
@@ -269,8 +367,11 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
269
367
  setSrcAmount,
270
368
  dstAmount,
271
369
  setDstAmount,
370
+ dstAmountInput,
371
+ setDstAmountInput,
272
372
  isSrcInputDirty,
273
373
  setIsSrcInputDirty,
374
+ tradeType,
274
375
  // Payment methods
275
376
  cryptoPaymentMethod,
276
377
  setCryptoPaymentMethod,
@@ -291,8 +392,10 @@ function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder,
291
392
  // Quote data
292
393
  anyspendQuote,
293
394
  isLoadingAnyspendQuote,
395
+ isQuoteLoading, // Combined loading state (includes debounce + quote fetching)
294
396
  getAnyspendQuoteError,
295
397
  activeInputAmountInWei,
398
+ activeOutputAmountInWei, // User's destination amount input in wei (for EXACT_OUTPUT mode)
296
399
  // Geo/onramp data
297
400
  geoData,
298
401
  coinbaseAvailablePaymentMethods,
@@ -38,7 +38,7 @@ function useRecipientAddressState({ recipientAddressFromProps, walletAddress, gl
38
38
  // selectedRecipientAddress: explicitly selected by user (undefined means no explicit selection)
39
39
  const [selectedRecipientAddress, setSelectedRecipientAddress] = (0, react_1.useState)(undefined);
40
40
  // The effective recipient address, derived on each render, respecting priority.
41
- const effectiveRecipientAddress = recipientAddressFromProps || selectedRecipientAddress || walletAddress || globalAddress;
41
+ const effectiveRecipientAddress = selectedRecipientAddress || recipientAddressFromProps || walletAddress || globalAddress;
42
42
  // Helper function to reset user's manual selection.
43
43
  const resetRecipientAddress = () => {
44
44
  setSelectedRecipientAddress(undefined);