@shapeshiftoss/swap-widget 0.6.0 → 0.8.0

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/index.js CHANGED
@@ -3,7 +3,8 @@ import { useAppKitAccount as useAppKitAccount3 } from "@reown/appkit/react";
3
3
  import { useCallback as useCallback11, useEffect as useEffect10, useMemo as useMemo12, useRef as useRef7, useState as useState8 } from "react";
4
4
 
5
5
  // src/api/client.ts
6
- var DEFAULT_API_BASE_URL = import.meta.env.VITE_SWAP_WIDGET_API_URL ?? "https://api.shapeshift.com";
6
+ var DEFAULT_API_BASE_URL = "https://api.shapeshift.com";
7
+ var getTradeAmount = (params) => params.buyAmountCryptoBaseUnit !== void 0 ? { buyAmountCryptoBaseUnit: params.buyAmountCryptoBaseUnit } : { sellAmountCryptoBaseUnit: params.sellAmountCryptoBaseUnit };
7
8
  var createApiClient = (config = {}) => {
8
9
  const baseUrl = config.baseUrl ?? DEFAULT_API_BASE_URL;
9
10
  const fetchWithConfig = async (endpoint, params, method = "GET", timeoutMs = 3e4) => {
@@ -41,7 +42,7 @@ var createApiClient = (config = {}) => {
41
42
  getRates: (params) => fetchWithConfig("/v1/swap/rates", {
42
43
  sellAssetId: params.sellAssetId,
43
44
  buyAssetId: params.buyAssetId,
44
- sellAmountCryptoBaseUnit: params.sellAmountCryptoBaseUnit
45
+ ...getTradeAmount(params)
45
46
  }),
46
47
  getSwapStatus: (params) => fetchWithConfig("/v1/swap/status", {
47
48
  quoteId: params.quoteId,
@@ -52,7 +53,7 @@ var createApiClient = (config = {}) => {
52
53
  {
53
54
  sellAssetId: params.sellAssetId,
54
55
  buyAssetId: params.buyAssetId,
55
- sellAmountCryptoBaseUnit: params.sellAmountCryptoBaseUnit,
56
+ ...getTradeAmount(params),
56
57
  sendAddress: params.sendAddress,
57
58
  receiveAddress: params.receiveAddress,
58
59
  swapperName: params.swapperName,
@@ -680,6 +681,7 @@ var formatAmount = (amount, decimals, maxDecimals) => {
680
681
  minimumFractionDigits: 0
681
682
  });
682
683
  };
684
+ var formatAmountForInput = (amount, decimals) => BigAmount.fromBaseUnit({ value: amount, precision: decimals }).toPrecision();
683
685
  var parseAmount = (amount, decimals) => {
684
686
  return BigAmount.fromPrecision({ value: amount, precision: decimals }).toBaseUnit();
685
687
  };
@@ -689,8 +691,11 @@ var truncateAddress = (address, chars = 4) => {
689
691
  };
690
692
 
691
693
  // src/machines/guards.ts
694
+ var isExactOutput = (context) => !!context.buyAmountBaseUnit;
692
695
  var hasValidInput = (context) => {
693
- return !!context.sellAsset && !!context.buyAsset && !!context.sellAmountBaseUnit && context.sellAmountBaseUnit !== "0";
696
+ if (!context.sellAsset || !context.buyAsset) return false;
697
+ const amountBaseUnit = isExactOutput(context) ? context.buyAmountBaseUnit : context.sellAmountBaseUnit;
698
+ return !!amountBaseUnit && amountBaseUnit !== "0";
694
699
  };
695
700
  var hasQuote = (context) => context.quote !== null;
696
701
  var canRetry = (context) => context.retryCount < 3;
@@ -711,6 +716,8 @@ var createInitialContext = (input) => {
711
716
  buyAsset,
712
717
  sellAmount: "",
713
718
  sellAmountBaseUnit: void 0,
719
+ buyAmount: input?.buyAmount ?? "",
720
+ buyAmountBaseUnit: input?.buyAmountBaseUnit,
714
721
  isSellAmountFiat: false,
715
722
  sellAmountFiat: "",
716
723
  selectedRate: null,
@@ -773,12 +780,14 @@ var swapMachine = setup({
773
780
  quote: null
774
781
  };
775
782
  }),
776
- assignBuyAsset: assign(({ event }) => {
783
+ assignBuyAsset: assign(({ context, event }) => {
777
784
  const { asset } = event;
778
785
  const buyChainType = getChainType(asset.chainId);
779
786
  return {
780
787
  buyAsset: asset,
781
788
  isBuyAssetEvm: buyChainType === "evm",
789
+ buyAmount: context.buyAmount,
790
+ buyAmountBaseUnit: context.buyAmount ? parseAmount(context.buyAmount, asset.precision) : void 0,
782
791
  selectedRate: null,
783
792
  quote: null
784
793
  };
@@ -788,7 +797,11 @@ var swapMachine = setup({
788
797
  return {
789
798
  sellAmount: amount,
790
799
  sellAmountBaseUnit: amountBaseUnit,
791
- sellAmountFiat: fiatValue
800
+ sellAmountFiat: fiatValue,
801
+ buyAmount: "",
802
+ buyAmountBaseUnit: void 0,
803
+ selectedRate: null,
804
+ quote: null
792
805
  };
793
806
  }),
794
807
  assignSellFiatMode: assign(({ event }) => {
@@ -832,6 +845,19 @@ var swapMachine = setup({
832
845
  assignReceiveAddress: assign(({ event }) => ({
833
846
  receiveAddress: event.address
834
847
  })),
848
+ assignBuyAmount: assign(({ event }) => {
849
+ const { amount, amountBaseUnit } = event;
850
+ return {
851
+ buyAmount: amount,
852
+ buyAmountBaseUnit: amountBaseUnit,
853
+ sellAmount: "",
854
+ sellAmountBaseUnit: void 0,
855
+ sellAmountFiat: "",
856
+ isSellAmountFiat: false,
857
+ selectedRate: null,
858
+ quote: null
859
+ };
860
+ }),
835
861
  assignChainInfo: assign(({ event }) => {
836
862
  const e = event;
837
863
  return {
@@ -859,6 +885,8 @@ var swapMachine = setup({
859
885
  buyAsset: context.buyAsset,
860
886
  sellAmount: context.sellAmount,
861
887
  sellAmountBaseUnit: context.sellAmountBaseUnit,
888
+ buyAmount: context.buyAmount,
889
+ buyAmountBaseUnit: context.buyAmountBaseUnit,
862
890
  slippage: context.slippage,
863
891
  sendAddress: context.sendAddress,
864
892
  receiveAddress: context.receiveAddress
@@ -877,6 +905,7 @@ var swapMachine = setup({
877
905
  SET_SELL_ASSET: { actions: "assignSellAsset" },
878
906
  SET_BUY_ASSET: { actions: "assignBuyAsset" },
879
907
  SET_SELL_AMOUNT: { actions: "assignSellAmount" },
908
+ SET_BUY_AMOUNT: { actions: "assignBuyAmount" },
880
909
  SET_SELL_FIAT_MODE: { actions: "assignSellFiatMode" },
881
910
  SET_SLIPPAGE: { actions: "assignSlippage" },
882
911
  SELECT_RATE: { actions: "assignSelectedRate" },
@@ -1330,7 +1359,7 @@ var useStatusPolling = ({
1330
1359
 
1331
1360
  // src/hooks/useSwapApproval.ts
1332
1361
  import { useEffect as useEffect3, useRef as useRef2 } from "react";
1333
- import { createPublicClient as createPublicClient2, encodeFunctionData, erc20Abi as erc20Abi2, http as http2 } from "viem";
1362
+ import { createPublicClient as createPublicClient2, http as http2 } from "viem";
1334
1363
 
1335
1364
  // src/constants/chains.ts
1336
1365
  import {
@@ -1443,6 +1472,15 @@ var useSwapApproval = () => {
1443
1472
  });
1444
1473
  return;
1445
1474
  }
1475
+ if (!sellAmountBaseUnit || sellAmountBaseUnit === "0") {
1476
+ actorRef.send({ type: "APPROVAL_ERROR", error: "No sell amount specified" });
1477
+ return;
1478
+ }
1479
+ const approvalTxs = quote.approval.approvalTxs;
1480
+ if (!approvalTxs?.length) {
1481
+ actorRef.send({ type: "APPROVAL_ERROR", error: "No approval transactions in quote" });
1482
+ return;
1483
+ }
1446
1484
  const requiredChainId = getEvmNetworkId(sellAsset.chainId);
1447
1485
  const client = walletClient;
1448
1486
  const currentChainId = await client.getChainId();
@@ -1458,28 +1496,24 @@ var useSwapApproval = () => {
1458
1496
  nativeCurrency,
1459
1497
  rpcUrls: { default: { http: [] } }
1460
1498
  };
1461
- if (!sellAmountBaseUnit || sellAmountBaseUnit === "0") {
1462
- actorRef.send({ type: "APPROVAL_ERROR", error: "No sell amount specified" });
1463
- return;
1464
- }
1465
- const approvalData = encodeFunctionData({
1466
- abi: erc20Abi2,
1467
- functionName: "approve",
1468
- args: [quote.approval.spender, BigInt(sellAmountBaseUnit)]
1469
- });
1470
- const approvalHash = await client.sendTransaction({
1471
- to: sellAssetAddress,
1472
- data: approvalData,
1473
- value: BigInt(0),
1474
- chain,
1475
- account: walletAddress
1476
- });
1477
1499
  const rpcUrl = chain.rpcUrls?.default?.http?.[0];
1478
1500
  const publicClient = createPublicClient2({
1479
1501
  chain,
1480
1502
  transport: rpcUrl ? http2(rpcUrl) : http2()
1481
1503
  });
1482
- await publicClient.waitForTransactionReceipt({ hash: approvalHash });
1504
+ let approvalHash;
1505
+ for (const approvalTx of approvalTxs) {
1506
+ approvalHash = await client.sendTransaction({
1507
+ to: approvalTx.to,
1508
+ data: approvalTx.data,
1509
+ value: BigInt(approvalTx.value),
1510
+ chain,
1511
+ account: walletAddress
1512
+ });
1513
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: approvalHash });
1514
+ if (receipt.status !== "success") throw new Error("Approval transaction reverted");
1515
+ }
1516
+ if (!approvalHash) throw new Error("Expected at least one approval transaction");
1483
1517
  actorRef.send({ type: "APPROVAL_SUCCESS", txHash: approvalHash });
1484
1518
  } catch (error) {
1485
1519
  actorRef.send({
@@ -1623,7 +1657,7 @@ import { useQueries, useQueryClient } from "@tanstack/react-query";
1623
1657
  import { getBalance, readContract } from "@wagmi/core";
1624
1658
  import PQueue from "p-queue";
1625
1659
  import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
1626
- import { erc20Abi as erc20Abi3 } from "viem";
1660
+ import { erc20Abi as erc20Abi2 } from "viem";
1627
1661
  import { useConfig } from "wagmi";
1628
1662
  var CONCURRENCY_LIMIT = 5;
1629
1663
  var DELAY_BETWEEN_BATCHES_MS = 50;
@@ -1823,7 +1857,7 @@ var useMultiChainBalance = (evmAddress, utxoAddress, solanaAddress, assetId, pre
1823
1857
  if (isErc20 && evmParsed.tokenAddress) {
1824
1858
  const result2 = await readContract(config, {
1825
1859
  address: evmParsed.tokenAddress,
1826
- abi: erc20Abi3,
1860
+ abi: erc20Abi2,
1827
1861
  functionName: "balanceOf",
1828
1862
  args: [evmAddress],
1829
1863
  chainId: evmParsed.evmChainId
@@ -1936,7 +1970,7 @@ var useMultiChainBalances = (evmAddress, utxoAddress, solanaAddress, assetIds, a
1936
1970
  if (asset.tokenAddress) {
1937
1971
  const result2 = await readContract(config, {
1938
1972
  address: asset.tokenAddress,
1939
- abi: erc20Abi3,
1973
+ abi: erc20Abi2,
1940
1974
  functionName: "balanceOf",
1941
1975
  args: [evmAddress],
1942
1976
  // @ts-ignore - swap-widget supports more chains than main app's wagmi type registration
@@ -2232,28 +2266,55 @@ var formatUsdValue = (cryptoAmount, precision, usdPrice) => {
2232
2266
  };
2233
2267
 
2234
2268
  // src/hooks/useSwapRates.ts
2235
- import { bnOrZero } from "@shapeshiftoss/utils";
2236
2269
  import { useQuery as useQuery3 } from "@tanstack/react-query";
2270
+
2271
+ // src/utils/rateDisplay.ts
2272
+ import { bnOrZero } from "@shapeshiftoss/utils";
2273
+ var getRateAmountBaseUnit = (rate, isExactOutput2) => (isExactOutput2 ? rate.sellAmountCryptoBaseUnit : rate.buyAmountCryptoBaseUnit) ?? "0";
2274
+ var sortRatesByValue = (rates, isExactOutput2) => [...rates].filter((rate) => !rate.error && getRateAmountBaseUnit(rate, isExactOutput2) !== "0").sort((a, b) => {
2275
+ const aAmount = bnOrZero(getRateAmountBaseUnit(a, isExactOutput2));
2276
+ const bAmount = bnOrZero(getRateAmountBaseUnit(b, isExactOutput2));
2277
+ return isExactOutput2 ? aAmount.minus(bAmount).toNumber() : bAmount.minus(aAmount).toNumber();
2278
+ });
2279
+ var getRateDiffPercent = (bestAmountBaseUnit, amountBaseUnit, isExactOutput2) => {
2280
+ const best = bnOrZero(bestAmountBaseUnit);
2281
+ const current = bnOrZero(amountBaseUnit);
2282
+ if (best.isZero()) return null;
2283
+ const diff = isExactOutput2 ? current.minus(best).div(best).times(100) : best.minus(current).div(best).times(100);
2284
+ return diff.gte("0.01") ? diff.toFixed(2) : null;
2285
+ };
2286
+
2287
+ // src/hooks/useSwapRates.ts
2237
2288
  var ENABLED_SWAPPER_NAMES = new Set(Object.values(SwapperName));
2238
2289
  var useSwapRates = (apiClient, params) => {
2239
2290
  const {
2240
2291
  sellAssetId,
2241
2292
  buyAssetId,
2242
2293
  sellAmountCryptoBaseUnit,
2294
+ buyAmountCryptoBaseUnit,
2243
2295
  enabled = true,
2244
2296
  allowedSwapperNames,
2245
2297
  refetchInterval = 15e3
2246
2298
  } = params;
2299
+ const isExactOutput2 = buyAmountCryptoBaseUnit !== void 0;
2300
+ const amountCryptoBaseUnit = isExactOutput2 ? buyAmountCryptoBaseUnit : sellAmountCryptoBaseUnit;
2247
2301
  return useQuery3({
2248
- queryKey: ["swapRates", sellAssetId, buyAssetId, sellAmountCryptoBaseUnit, allowedSwapperNames],
2302
+ queryKey: [
2303
+ "swapRates",
2304
+ sellAssetId,
2305
+ buyAssetId,
2306
+ amountCryptoBaseUnit,
2307
+ isExactOutput2,
2308
+ allowedSwapperNames
2309
+ ],
2249
2310
  queryFn: async () => {
2250
- if (!sellAssetId || !buyAssetId || !sellAmountCryptoBaseUnit) {
2311
+ if (!sellAssetId || !buyAssetId || !amountCryptoBaseUnit) {
2251
2312
  return [];
2252
2313
  }
2253
2314
  const response = await apiClient.getRates({
2254
2315
  sellAssetId,
2255
2316
  buyAssetId,
2256
- sellAmountCryptoBaseUnit
2317
+ ...isExactOutput2 ? { buyAmountCryptoBaseUnit: amountCryptoBaseUnit } : { sellAmountCryptoBaseUnit: amountCryptoBaseUnit }
2257
2318
  });
2258
2319
  let filteredRates = response.rates.filter(
2259
2320
  (rate) => !rate.error && rate.buyAmountCryptoBaseUnit !== "0" && ENABLED_SWAPPER_NAMES.has(rate.swapperName)
@@ -2261,16 +2322,15 @@ var useSwapRates = (apiClient, params) => {
2261
2322
  if (allowedSwapperNames?.length) {
2262
2323
  filteredRates = filteredRates.filter((rate) => allowedSwapperNames.includes(rate.swapperName));
2263
2324
  }
2264
- return filteredRates.map((rate, index) => ({
2265
- ...rate,
2266
- id: rate.id ?? `${rate.swapperName}-${index}`
2267
- })).sort((a, b) => {
2268
- const aAmount = bnOrZero(a.buyAmountCryptoBaseUnit);
2269
- const bAmount = bnOrZero(b.buyAmountCryptoBaseUnit);
2270
- return bAmount.minus(aAmount).toNumber();
2271
- });
2325
+ return sortRatesByValue(
2326
+ filteredRates.map((rate, index) => ({
2327
+ ...rate,
2328
+ id: rate.id ?? `${rate.swapperName}-${index}`
2329
+ })),
2330
+ isExactOutput2
2331
+ );
2272
2332
  },
2273
- enabled: enabled && !!sellAssetId && !!buyAssetId && !!sellAmountCryptoBaseUnit,
2333
+ enabled: enabled && !!sellAssetId && !!buyAssetId && !!amountCryptoBaseUnit,
2274
2334
  staleTime: 1e4,
2275
2335
  refetchInterval
2276
2336
  });
@@ -2282,18 +2342,23 @@ var useSwapDisplayValues = ({
2282
2342
  allowedSwapperNames,
2283
2343
  ratesRefetchInterval
2284
2344
  }) => {
2285
- const sellAsset = SwapMachineCtx.useSelector((s) => s.context.sellAsset);
2286
- const buyAsset = SwapMachineCtx.useSelector((s) => s.context.buyAsset);
2287
- const sellAmountBaseUnit = SwapMachineCtx.useSelector((s) => s.context.sellAmountBaseUnit);
2288
- const isSellAssetEvm = SwapMachineCtx.useSelector((s) => s.context.isSellAssetEvm);
2289
- const isSellAssetUtxo = SwapMachineCtx.useSelector((s) => s.context.isSellAssetUtxo);
2290
- const isSellAssetSolana = SwapMachineCtx.useSelector((s) => s.context.isSellAssetSolana);
2291
- const selectedRate = SwapMachineCtx.useSelector((s) => s.context.selectedRate);
2292
- const { receiveAddress, evm, bitcoin: bitcoin3, solana: solana3 } = useSwapWallet();
2345
+ const context = SwapMachineCtx.useSelector((s) => s.context);
2346
+ const {
2347
+ sellAsset,
2348
+ buyAsset,
2349
+ buyAmountBaseUnit,
2350
+ isSellAssetEvm,
2351
+ isSellAssetUtxo,
2352
+ isSellAssetSolana,
2353
+ selectedRate
2354
+ } = context;
2355
+ const { receiveAddress, isReceiveAddressBlocked, evm, bitcoin: bitcoin3, solana: solana3 } = useSwapWallet();
2293
2356
  const evmAddress = evm.address;
2294
2357
  const bitcoinAddress = bitcoin3.address;
2295
2358
  const solanaAddress = solana3.address;
2296
2359
  const buyChainType = getChainType(buyAsset.chainId);
2360
+ const isExactOutput2 = !!buyAmountBaseUnit;
2361
+ const amountBaseUnit = isExactOutput2 ? buyAmountBaseUnit : context.sellAmountBaseUnit;
2297
2362
  const {
2298
2363
  data: rates,
2299
2364
  isLoading: isLoadingRates,
@@ -2301,10 +2366,12 @@ var useSwapDisplayValues = ({
2301
2366
  } = useSwapRates(apiClient, {
2302
2367
  sellAssetId: sellAsset.assetId,
2303
2368
  buyAssetId: buyAsset.assetId,
2304
- sellAmountCryptoBaseUnit: sellAmountBaseUnit,
2369
+ sellAmountCryptoBaseUnit: context.sellAmountBaseUnit,
2370
+ buyAmountCryptoBaseUnit: buyAmountBaseUnit,
2305
2371
  allowedSwapperNames,
2306
2372
  refetchInterval: ratesRefetchInterval,
2307
- enabled: !!sellAmountBaseUnit && sellAmountBaseUnit !== "0" && (isSellAssetEvm || isSellAssetUtxo || isSellAssetSolana)
2373
+ // Rates need no destination, but a locked one the buy chain rejects can never be quoted
2374
+ enabled: !!amountBaseUnit && amountBaseUnit !== "0" && !isReceiveAddressBlocked && (isSellAssetEvm || isSellAssetUtxo || isSellAssetSolana)
2308
2375
  });
2309
2376
  const {
2310
2377
  data: sellAssetBalance,
@@ -2338,6 +2405,7 @@ var useSwapDisplayValues = ({
2338
2405
  const { data: buyChainInfo } = useChainInfo(buyAsset.chainId);
2339
2406
  const displayRate = useMemo5(() => selectedRate ?? rates?.[0], [selectedRate, rates]);
2340
2407
  const buyAmount = displayRate?.buyAmountCryptoBaseUnit;
2408
+ const sellAmountBaseUnit = isExactOutput2 ? displayRate?.sellAmountCryptoBaseUnit : context.sellAmountBaseUnit;
2341
2409
  const sellChainNativeAsset = useMemo5(() => getBaseAsset(sellAsset.chainId), [sellAsset.chainId]);
2342
2410
  const assetIdsForPrices = useMemo5(() => {
2343
2411
  const ids = [sellAsset.assetId, buyAsset.assetId];
@@ -2395,6 +2463,8 @@ var useSwapDisplayValues = ({
2395
2463
  buyChainInfo,
2396
2464
  displayRate,
2397
2465
  buyAmount,
2466
+ isExactOutput: isExactOutput2,
2467
+ sellAmountBaseUnit,
2398
2468
  sellChainNativeAsset,
2399
2469
  networkFeeDisplay,
2400
2470
  sellUsdValue,
@@ -2418,6 +2488,8 @@ var useSwapDisplayValues = ({
2418
2488
  buyChainInfo,
2419
2489
  displayRate,
2420
2490
  buyAmount,
2491
+ isExactOutput2,
2492
+ sellAmountBaseUnit,
2421
2493
  sellChainNativeAsset,
2422
2494
  networkFeeDisplay,
2423
2495
  sellUsdValue,
@@ -2548,6 +2620,10 @@ var useSwapExecution = () => {
2548
2620
  });
2549
2621
  return;
2550
2622
  }
2623
+ if (Date.now() >= quote.expiresAt) {
2624
+ actorRef.send({ type: "EXECUTE_ERROR", error: "Quote expired \u2014 please try again" });
2625
+ return;
2626
+ }
2551
2627
  const txData = quote.steps[0]?.transactionData;
2552
2628
  if (!txData) {
2553
2629
  console.error("Quote missing transactionData in steps[0]", quote);
@@ -2657,15 +2733,27 @@ var useSwapHandlers = ({
2657
2733
  },
2658
2734
  [actorRef]
2659
2735
  );
2736
+ const handleBuyAmountChange = useCallback4(
2737
+ (value) => {
2738
+ const { buyAsset } = actorRef.getSnapshot().context;
2739
+ const amountBaseUnit = value ? parseAmount(value, buyAsset.precision) : void 0;
2740
+ actorRef.send({ type: "SET_BUY_AMOUNT", amount: value, amountBaseUnit });
2741
+ },
2742
+ [actorRef]
2743
+ );
2660
2744
  const handleToggleSellFiat = useCallback4(
2661
2745
  (sellAssetUsdPrice) => {
2662
2746
  if (!sellAssetUsdPrice) return;
2663
2747
  const snap = actorRef.getSnapshot();
2664
- const { sellAmount, sellAmountBaseUnit, sellAsset, isSellAmountFiat } = snap.context;
2748
+ const { sellAmount, sellAmountBaseUnit, sellAsset, isSellAmountFiat, buyAmountBaseUnit } = snap.context;
2665
2749
  if (isSellAmountFiat) {
2666
2750
  actorRef.send({ type: "SET_SELL_FIAT_MODE", isFiat: false });
2667
2751
  return;
2668
2752
  }
2753
+ if (buyAmountBaseUnit) {
2754
+ actorRef.send({ type: "SET_SELL_FIAT_MODE", isFiat: true });
2755
+ return;
2756
+ }
2669
2757
  const fiatValue = cryptoToFiat(sellAmountBaseUnit, sellAssetUsdPrice, sellAsset.precision);
2670
2758
  actorRef.send({
2671
2759
  type: "SET_SELL_AMOUNT",
@@ -2741,6 +2829,7 @@ var useSwapHandlers = ({
2741
2829
  handleSellAssetSelect,
2742
2830
  handleBuyAssetSelect,
2743
2831
  handleSellAmountChange,
2832
+ handleBuyAmountChange,
2744
2833
  handleToggleSellFiat,
2745
2834
  handleSelectRate,
2746
2835
  handleSlippageChange,
@@ -2763,9 +2852,12 @@ var useSwapQuoting = ({ apiClient, rates, sellAssetBalance }) => {
2763
2852
  quotingRef.current = true;
2764
2853
  const fetchQuote = async () => {
2765
2854
  try {
2766
- if (sellAssetBalance?.balance && context.sellAmountBaseUnit) {
2855
+ const isExactOutput2 = !!context.buyAmountBaseUnit;
2856
+ const rateToUse = context.selectedRate ?? rates?.[0];
2857
+ const sellAmountBaseUnit = isExactOutput2 ? rateToUse?.sellAmountCryptoBaseUnit : context.sellAmountBaseUnit;
2858
+ if (sellAssetBalance?.balance && sellAmountBaseUnit) {
2767
2859
  const balanceBigInt = BigInt(sellAssetBalance.balance);
2768
- const amountBigInt = BigInt(context.sellAmountBaseUnit);
2860
+ const amountBigInt = BigInt(sellAmountBaseUnit);
2769
2861
  if (amountBigInt > balanceBigInt) {
2770
2862
  actorRef.send({ type: "QUOTE_ERROR", error: "Insufficient balance" });
2771
2863
  return;
@@ -2777,8 +2869,8 @@ var useSwapQuoting = ({ apiClient, rates, sellAssetBalance }) => {
2777
2869
  return;
2778
2870
  }
2779
2871
  const slippageDecimal = (parsedSlippage / 100).toString();
2780
- const rateToUse = context.selectedRate ?? rates?.[0];
2781
- if (!rateToUse || !context.sellAmountBaseUnit) {
2872
+ const amountBaseUnit = isExactOutput2 ? context.buyAmountBaseUnit : context.sellAmountBaseUnit;
2873
+ if (!rateToUse || !amountBaseUnit) {
2782
2874
  actorRef.send({ type: "QUOTE_ERROR", error: "No rate or amount available" });
2783
2875
  return;
2784
2876
  }
@@ -2794,7 +2886,7 @@ var useSwapQuoting = ({ apiClient, rates, sellAssetBalance }) => {
2794
2886
  const response = await apiClient.getQuote({
2795
2887
  sellAssetId: context.sellAsset.assetId,
2796
2888
  buyAssetId: context.buyAsset.assetId,
2797
- sellAmountCryptoBaseUnit: context.sellAmountBaseUnit,
2889
+ ...isExactOutput2 ? { buyAmountCryptoBaseUnit: amountBaseUnit } : { sellAmountCryptoBaseUnit: amountBaseUnit },
2798
2890
  sendAddress,
2799
2891
  receiveAddress: resolvedReceiveAddress,
2800
2892
  swapperName: rateToUse.swapperName,
@@ -2951,6 +3043,19 @@ var getAddressFormatHint = (chainId) => {
2951
3043
  }
2952
3044
  };
2953
3045
 
3046
+ // src/utils/receiveAddress.ts
3047
+ var resolveReceiveAddress = ({
3048
+ isLocked,
3049
+ defaultAddress,
3050
+ customAddress,
3051
+ walletAddress,
3052
+ buyChainId
3053
+ }) => {
3054
+ const isValidForBuyChain = (address) => !!address && validateAddress(address, buyChainId).valid;
3055
+ if (isLocked) return isValidForBuyChain(defaultAddress) ? defaultAddress : void 0;
3056
+ return isValidForBuyChain(customAddress) ? customAddress : walletAddress;
3057
+ };
3058
+
2954
3059
  // src/components/ApprovalStep.tsx
2955
3060
  import { jsx, jsxs } from "react/jsx-runtime";
2956
3061
  var ApprovalStep = () => {
@@ -3112,9 +3217,8 @@ var getSwapperColor = (swapperName) => {
3112
3217
  };
3113
3218
 
3114
3219
  // src/components/QuotesModal.tsx
3115
- import { bnOrZero as bnOrZero2 } from "@shapeshiftoss/utils";
3116
3220
  import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo6 } from "react";
3117
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3221
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3118
3222
  var useLockBodyScroll = (isLocked) => {
3119
3223
  useEffect6(() => {
3120
3224
  if (!isLocked) return;
@@ -3125,13 +3229,6 @@ var useLockBodyScroll = (isLocked) => {
3125
3229
  };
3126
3230
  }, [isLocked]);
3127
3231
  };
3128
- var calculateSavingsPercent = (bestAmount, currentAmount) => {
3129
- const best = bnOrZero2(bestAmount);
3130
- const current = bnOrZero2(currentAmount);
3131
- if (best.isZero()) return null;
3132
- const diff = best.minus(current).div(best).times(100);
3133
- return diff.gt(0.1) ? diff.toFixed(2) : null;
3134
- };
3135
3232
  var QuotesModal = ({
3136
3233
  isOpen,
3137
3234
  onClose,
@@ -3141,6 +3238,9 @@ var QuotesModal = ({
3141
3238
  buyAsset,
3142
3239
  sellAsset,
3143
3240
  sellAmountBaseUnit,
3241
+ buyAmountBaseUnit,
3242
+ isExactOutput: isExactOutput2,
3243
+ sellAssetUsdPrice,
3144
3244
  buyAssetUsdPrice
3145
3245
  }) => {
3146
3246
  useLockBodyScroll(isOpen);
@@ -3159,15 +3259,11 @@ var QuotesModal = ({
3159
3259
  },
3160
3260
  [onSelectRate, onClose]
3161
3261
  );
3162
- const sortedRates = useMemo6(() => {
3163
- return [...rates].filter((r) => !r.error && r.buyAmountCryptoBaseUnit !== "0").sort((a, b) => {
3164
- const aAmount = bnOrZero2(a.buyAmountCryptoBaseUnit);
3165
- const bAmount = bnOrZero2(b.buyAmountCryptoBaseUnit);
3166
- return bAmount.minus(aAmount).toNumber();
3167
- });
3168
- }, [rates]);
3262
+ const sortedRates = useMemo6(() => sortRatesByValue(rates, isExactOutput2), [rates, isExactOutput2]);
3169
3263
  const bestRate = useMemo6(() => sortedRates[0], [sortedRates]);
3170
- const bestBuyAmount = bestRate?.buyAmountCryptoBaseUnit ?? "0";
3264
+ const bestAmountBaseUnit = bestRate ? getRateAmountBaseUnit(bestRate, isExactOutput2) : "0";
3265
+ const asset = isExactOutput2 ? sellAsset : buyAsset;
3266
+ const usdPrice = isExactOutput2 ? sellAssetUsdPrice : buyAssetUsdPrice;
3171
3267
  if (!isOpen) return null;
3172
3268
  return (
3173
3269
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
@@ -3184,14 +3280,20 @@ var QuotesModal = ({
3184
3280
  /* @__PURE__ */ jsxs3("div", { className: "ssw-quotes-modal-header", children: [
3185
3281
  /* @__PURE__ */ jsxs3("div", { className: "ssw-quotes-header-content", children: [
3186
3282
  /* @__PURE__ */ jsx3("h2", { id: "quotes-modal-title", className: "ssw-quotes-modal-title", children: "Select Route" }),
3187
- /* @__PURE__ */ jsxs3("span", { className: "ssw-quotes-modal-subtitle", children: [
3283
+ /* @__PURE__ */ jsx3("span", { className: "ssw-quotes-modal-subtitle", children: isExactOutput2 ? /* @__PURE__ */ jsxs3(Fragment, { children: [
3284
+ sellAsset.symbol,
3285
+ " \u2192 ",
3286
+ formatAmount(buyAmountBaseUnit, buyAsset.precision),
3287
+ " ",
3288
+ buyAsset.symbol
3289
+ ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
3188
3290
  formatAmount(sellAmountBaseUnit, sellAsset.precision),
3189
3291
  " ",
3190
3292
  sellAsset.symbol,
3191
3293
  " \u2192",
3192
3294
  " ",
3193
3295
  buyAsset.symbol
3194
- ] })
3296
+ ] }) })
3195
3297
  ] }),
3196
3298
  /* @__PURE__ */ jsx3("button", { className: "ssw-quotes-modal-close", onClick: onClose, type: "button", children: /* @__PURE__ */ jsx3(
3197
3299
  "svg",
@@ -3207,15 +3309,15 @@ var QuotesModal = ({
3207
3309
  ) })
3208
3310
  ] }),
3209
3311
  /* @__PURE__ */ jsx3("div", { className: "ssw-quotes-modal-list", children: sortedRates.map((rate, index) => {
3210
- const buyAmount = rate.buyAmountCryptoBaseUnit ?? "0";
3312
+ const amountBaseUnit = getRateAmountBaseUnit(rate, isExactOutput2);
3211
3313
  const estimatedTime = rate.estimatedExecutionTimeMs;
3212
3314
  const isBest = index === 0;
3213
3315
  const isSelected = selectedRate?.id === rate.id;
3214
3316
  const swapperIcon = getSwapperIcon(rate.swapperName);
3215
3317
  const swapperColor = getSwapperColor(rate.swapperName);
3216
- const formattedBuyAmount = formatAmount(buyAmount, buyAsset.precision);
3217
- const usdValue = formatUsdValue(buyAmount, buyAsset.precision, buyAssetUsdPrice);
3218
- const savingsPercent = isBest ? null : calculateSavingsPercent(bestBuyAmount, buyAmount);
3318
+ const formattedAmount = formatAmount(amountBaseUnit, asset.precision);
3319
+ const usdValue = formatUsdValue(amountBaseUnit, asset.precision, usdPrice);
3320
+ const diffPercent = isBest ? null : getRateDiffPercent(bestAmountBaseUnit, amountBaseUnit, isExactOutput2);
3219
3321
  const estimatedSeconds = estimatedTime ? Math.round(estimatedTime / 1e3) : 0;
3220
3322
  const hasTime = estimatedSeconds > 0;
3221
3323
  return /* @__PURE__ */ jsxs3(
@@ -3238,9 +3340,9 @@ var QuotesModal = ({
3238
3340
  /* @__PURE__ */ jsxs3("div", { className: "ssw-quote-row-name-row", children: [
3239
3341
  /* @__PURE__ */ jsx3("span", { className: "ssw-quote-row-name", children: rate.swapperName }),
3240
3342
  isBest && /* @__PURE__ */ jsx3("span", { className: "ssw-quote-row-best", children: "Best" }),
3241
- savingsPercent && /* @__PURE__ */ jsxs3("span", { className: "ssw-quote-row-diff", children: [
3242
- "-",
3243
- savingsPercent,
3343
+ diffPercent && /* @__PURE__ */ jsxs3("span", { className: "ssw-quote-row-diff", children: [
3344
+ isExactOutput2 ? "+" : "-",
3345
+ diffPercent,
3244
3346
  "%"
3245
3347
  ] })
3246
3348
  ] }),
@@ -3253,9 +3355,9 @@ var QuotesModal = ({
3253
3355
  ] }),
3254
3356
  /* @__PURE__ */ jsxs3("div", { className: "ssw-quote-row-right", children: [
3255
3357
  /* @__PURE__ */ jsxs3("span", { className: "ssw-quote-row-amount", children: [
3256
- formattedBuyAmount,
3358
+ formattedAmount,
3257
3359
  " ",
3258
- /* @__PURE__ */ jsx3("span", { className: "ssw-quote-row-symbol", children: buyAsset.symbol })
3360
+ /* @__PURE__ */ jsx3("span", { className: "ssw-quote-row-symbol", children: asset.symbol })
3259
3361
  ] }),
3260
3362
  /* @__PURE__ */ jsx3("span", { className: "ssw-quote-row-usd", children: usdValue })
3261
3363
  ] })
@@ -3271,7 +3373,7 @@ var QuotesModal = ({
3271
3373
  };
3272
3374
 
3273
3375
  // src/components/QuoteSelector.tsx
3274
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3376
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3275
3377
  var QuoteSelector = ({
3276
3378
  rates,
3277
3379
  selectedRate,
@@ -3279,7 +3381,10 @@ var QuoteSelector = ({
3279
3381
  buyAsset,
3280
3382
  sellAsset,
3281
3383
  sellAmountBaseUnit,
3384
+ buyAmountBaseUnit,
3385
+ isExactOutput: isExactOutput2,
3282
3386
  isLoading,
3387
+ sellAssetUsdPrice,
3283
3388
  buyAssetUsdPrice
3284
3389
  }) => {
3285
3390
  const [isModalOpen, setIsModalOpen] = useState3(false);
@@ -3309,12 +3414,17 @@ var QuoteSelector = ({
3309
3414
  return null;
3310
3415
  }
3311
3416
  const displayRate = selectedRate ?? bestRate;
3312
- const buyAmount = displayRate.buyAmountCryptoBaseUnit ?? "0";
3313
3417
  const swapperIcon = getSwapperIcon(displayRate.swapperName);
3314
3418
  const swapperColor = getSwapperColor(displayRate.swapperName);
3315
- const formattedBuyAmount = formatAmount(buyAmount, buyAsset.precision);
3316
- const usdValue = formatUsdValue(buyAmount, buyAsset.precision, buyAssetUsdPrice);
3317
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
3419
+ const asset = isExactOutput2 ? sellAsset : buyAsset;
3420
+ const amountBaseUnit = getRateAmountBaseUnit(displayRate, isExactOutput2);
3421
+ const formattedAmount = formatAmount(amountBaseUnit, asset.precision);
3422
+ const usdValue = formatUsdValue(
3423
+ amountBaseUnit,
3424
+ asset.precision,
3425
+ isExactOutput2 ? sellAssetUsdPrice : buyAssetUsdPrice
3426
+ );
3427
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
3318
3428
  /* @__PURE__ */ jsxs4("button", { className: "ssw-quote-selector", onClick: handleOpenModal, type: "button", children: [
3319
3429
  /* @__PURE__ */ jsxs4("div", { className: "ssw-quote-left", children: [
3320
3430
  /* @__PURE__ */ jsxs4("div", { className: "ssw-quote-provider", children: [
@@ -3340,8 +3450,8 @@ var QuoteSelector = ({
3340
3450
  ] }),
3341
3451
  /* @__PURE__ */ jsxs4("div", { className: "ssw-quote-right", children: [
3342
3452
  /* @__PURE__ */ jsxs4("div", { className: "ssw-quote-amount-row", children: [
3343
- /* @__PURE__ */ jsx4("span", { className: "ssw-quote-amount", children: formattedBuyAmount }),
3344
- /* @__PURE__ */ jsx4("span", { className: "ssw-quote-symbol", children: buyAsset.symbol })
3453
+ /* @__PURE__ */ jsx4("span", { className: "ssw-quote-amount", children: formattedAmount }),
3454
+ /* @__PURE__ */ jsx4("span", { className: "ssw-quote-symbol", children: asset.symbol })
3345
3455
  ] }),
3346
3456
  alternativeRatesCount > 0 && /* @__PURE__ */ jsxs4("span", { className: "ssw-quote-more", children: [
3347
3457
  "+",
@@ -3373,6 +3483,9 @@ var QuoteSelector = ({
3373
3483
  buyAsset,
3374
3484
  sellAsset,
3375
3485
  sellAmountBaseUnit,
3486
+ buyAmountBaseUnit,
3487
+ isExactOutput: isExactOutput2,
3488
+ sellAssetUsdPrice,
3376
3489
  buyAssetUsdPrice
3377
3490
  }
3378
3491
  )
@@ -3381,11 +3494,12 @@ var QuoteSelector = ({
3381
3494
 
3382
3495
  // src/components/ReceiveAddressRow.tsx
3383
3496
  import { useCallback as useCallback7, useLayoutEffect, useMemo as useMemo8, useRef as useRef5, useState as useState4 } from "react";
3384
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3497
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3385
3498
  var ReceiveAddressRow = ({
3386
3499
  receiveAddress,
3387
3500
  isResolving,
3388
3501
  buyChainId,
3502
+ isLocked,
3389
3503
  onSetCustomReceiveAddress
3390
3504
  }) => {
3391
3505
  const [isEditing, setIsEditing] = useState4(false);
@@ -3393,7 +3507,7 @@ var ReceiveAddressRow = ({
3393
3507
  const [hasInteracted, setHasInteracted] = useState4(false);
3394
3508
  const inputRef = useRef5(null);
3395
3509
  const needsAddress = !receiveAddress;
3396
- const showInput = needsAddress && !isResolving || isEditing;
3510
+ const showInput = !isLocked && (needsAddress && !isResolving || isEditing);
3397
3511
  const showAttention = needsAddress && !isResolving;
3398
3512
  useLayoutEffect(() => {
3399
3513
  if (isEditing) inputRef.current?.focus();
@@ -3413,6 +3527,7 @@ var ReceiveAddressRow = ({
3413
3527
  [trimmedDraft, buyChainId]
3414
3528
  );
3415
3529
  const formatHint = useMemo8(() => getAddressFormatHint(buyChainId), [buyChainId]);
3530
+ const chainName = useMemo8(() => getChainName(buyChainId), [buyChainId]);
3416
3531
  const startEditing = useCallback7(() => {
3417
3532
  setDraft(receiveAddress ?? "");
3418
3533
  setHasInteracted(false);
@@ -3436,36 +3551,48 @@ var ReceiveAddressRow = ({
3436
3551
  }, [onSetCustomReceiveAddress]);
3437
3552
  const showReset = !!draft || isEditing;
3438
3553
  if (!showInput) {
3439
- return /* @__PURE__ */ jsxs5("div", { className: "ssw-receive-row ssw-receive-row-resolved", children: [
3440
- /* @__PURE__ */ jsx5("span", { className: "ssw-receive-label", children: "Receive address" }),
3441
- /* @__PURE__ */ jsx5("div", { className: "ssw-receive-resolved-value", children: isResolving ? /* @__PURE__ */ jsx5("span", { className: "ssw-balance-skeleton" }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
3442
- /* @__PURE__ */ jsx5("span", { className: "ssw-receive-address", children: truncateAddress(receiveAddress ?? "", 6) }),
3443
- /* @__PURE__ */ jsx5(
3444
- "button",
3445
- {
3446
- className: "ssw-receive-edit-btn",
3447
- onClick: startEditing,
3448
- type: "button",
3449
- "aria-label": "Edit receive address",
3450
- children: /* @__PURE__ */ jsxs5(
3451
- "svg",
3554
+ return /* @__PURE__ */ jsxs5(
3555
+ "div",
3556
+ {
3557
+ className: `ssw-receive-row ssw-receive-row-resolved${showAttention ? " ssw-receive-row-invalid" : ""}`,
3558
+ children: [
3559
+ /* @__PURE__ */ jsx5("span", { className: "ssw-receive-label", children: "Receive address" }),
3560
+ /* @__PURE__ */ jsx5("div", { className: "ssw-receive-resolved-value", children: isResolving ? /* @__PURE__ */ jsx5("span", { className: "ssw-balance-skeleton" }) : showAttention ? (
3561
+ // Locked only - an unlocked row with no address shows the input instead
3562
+ /* @__PURE__ */ jsxs5("span", { className: "ssw-receive-error", children: [
3563
+ "Not valid for ",
3564
+ chainName
3565
+ ] })
3566
+ ) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
3567
+ /* @__PURE__ */ jsx5("span", { className: "ssw-receive-address", children: truncateAddress(receiveAddress ?? "", 6) }),
3568
+ !isLocked && /* @__PURE__ */ jsx5(
3569
+ "button",
3452
3570
  {
3453
- width: "14",
3454
- height: "14",
3455
- viewBox: "0 0 24 24",
3456
- fill: "none",
3457
- stroke: "currentColor",
3458
- strokeWidth: "2",
3459
- children: [
3460
- /* @__PURE__ */ jsx5("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
3461
- /* @__PURE__ */ jsx5("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
3462
- ]
3571
+ className: "ssw-receive-edit-btn",
3572
+ onClick: startEditing,
3573
+ type: "button",
3574
+ "aria-label": "Edit receive address",
3575
+ children: /* @__PURE__ */ jsxs5(
3576
+ "svg",
3577
+ {
3578
+ width: "14",
3579
+ height: "14",
3580
+ viewBox: "0 0 24 24",
3581
+ fill: "none",
3582
+ stroke: "currentColor",
3583
+ strokeWidth: "2",
3584
+ children: [
3585
+ /* @__PURE__ */ jsx5("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
3586
+ /* @__PURE__ */ jsx5("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
3587
+ ]
3588
+ }
3589
+ )
3463
3590
  }
3464
3591
  )
3465
- }
3466
- )
3467
- ] }) })
3468
- ] });
3592
+ ] }) })
3593
+ ]
3594
+ }
3595
+ );
3469
3596
  }
3470
3597
  return /* @__PURE__ */ jsxs5(
3471
3598
  "div",
@@ -3549,39 +3676,23 @@ var ReceiveAddressRow = ({
3549
3676
  };
3550
3677
 
3551
3678
  // src/components/InputStep.tsx
3552
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3679
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3553
3680
  var InputStep = ({
3554
3681
  displayValues,
3555
3682
  onOpenTokenModal,
3556
3683
  onSellAmountChange,
3684
+ onBuyAmountChange,
3557
3685
  onToggleSellFiat,
3558
3686
  onSwapTokens,
3559
3687
  onSelectRate,
3560
3688
  onButtonClick,
3561
3689
  isBuyAssetLocked,
3690
+ isBuyAmountLocked,
3691
+ isReceiveAddressLocked,
3562
3692
  allowShapeshiftRedirect
3563
3693
  }) => {
3564
3694
  const context = SwapMachineCtx.useSelector((s) => s.context);
3565
3695
  const isQuoting = SwapMachineCtx.useSelector((s) => s.matches("quoting"));
3566
- const {
3567
- rates,
3568
- isLoadingRates,
3569
- ratesError,
3570
- sellAssetBalance,
3571
- buyAssetBalance,
3572
- isSellBalanceLoading,
3573
- isBuyBalanceLoading,
3574
- sellUsdValue,
3575
- sellAssetUsdPrice,
3576
- buyUsdValue,
3577
- sellChainInfo,
3578
- buyChainInfo,
3579
- buyAmount,
3580
- buyAssetUsdPrice,
3581
- networkFeeDisplay,
3582
- sellBalanceFiatValue,
3583
- buyBalanceFiatValue
3584
- } = displayValues;
3585
3696
  const {
3586
3697
  sendAddress,
3587
3698
  receiveAddress,
@@ -3591,22 +3702,48 @@ var InputStep = ({
3591
3702
  bitcoin: bitcoin3,
3592
3703
  solana: solana3
3593
3704
  } = useSwapWallet();
3594
- const {
3595
- sellAsset,
3596
- buyAsset,
3597
- selectedRate,
3598
- sellAmount,
3599
- sellAmountBaseUnit,
3600
- isSellAmountFiat,
3601
- sellAmountFiat,
3602
- isSellAssetEvm,
3603
- isSellAssetUtxo,
3604
- isSellAssetSolana
3605
- } = context;
3606
- const buyChainId = buyAsset.chainId;
3705
+ const buyChainId = context.buyAsset.chainId;
3607
3706
  const hasAnyWalletAddress = !!evm.address || !!bitcoin3.address || !!solana3.address;
3608
3707
  const hasActiveWallet = !!receiveAddress || hasAnyWalletAddress || isReceiveAddressResolving;
3609
- const isUnsupportedChain = !isSellAssetEvm && !isSellAssetUtxo && !isSellAssetSolana;
3708
+ const isUnsupportedChain = !context.isSellAssetEvm && !context.isSellAssetUtxo && !context.isSellAssetSolana;
3709
+ const amountBaseUnit = displayValues.isExactOutput ? context.buyAmountBaseUnit : context.sellAmountBaseUnit;
3710
+ const isSellAmountReadOnly = displayValues.isExactOutput && isBuyAmountLocked;
3711
+ const isSellAmountPending = displayValues.isExactOutput && displayValues.isLoadingRates;
3712
+ const isBuyAmountPending = !displayValues.isExactOutput && displayValues.isLoadingRates;
3713
+ const sellAmountCrypto = useMemo9(
3714
+ () => displayValues.sellAmountBaseUnit ? formatAmount(displayValues.sellAmountBaseUnit, context.sellAsset.precision, 6) : "",
3715
+ [displayValues.sellAmountBaseUnit, context.sellAsset.precision]
3716
+ );
3717
+ const buyAmountValue = useMemo9(() => {
3718
+ if (displayValues.isExactOutput) return context.buyAmount;
3719
+ if (!displayValues.buyAmount) return "";
3720
+ return formatAmount(displayValues.buyAmount, context.buyAsset.precision);
3721
+ }, [
3722
+ displayValues.isExactOutput,
3723
+ displayValues.buyAmount,
3724
+ context.buyAmount,
3725
+ context.buyAsset.precision
3726
+ ]);
3727
+ const sellAmountValue = useMemo9(() => {
3728
+ if (!displayValues.isExactOutput) {
3729
+ return context.isSellAmountFiat ? context.sellAmountFiat : context.sellAmount;
3730
+ }
3731
+ if (!context.isSellAmountFiat) return sellAmountCrypto;
3732
+ return cryptoToFiat(
3733
+ displayValues.sellAmountBaseUnit,
3734
+ displayValues.sellAssetUsdPrice ?? "",
3735
+ context.sellAsset.precision
3736
+ );
3737
+ }, [
3738
+ displayValues.isExactOutput,
3739
+ displayValues.sellAmountBaseUnit,
3740
+ displayValues.sellAssetUsdPrice,
3741
+ context.isSellAmountFiat,
3742
+ context.sellAmountFiat,
3743
+ context.sellAmount,
3744
+ context.sellAsset.precision,
3745
+ sellAmountCrypto
3746
+ ]);
3610
3747
  const { text: buttonText, disabled: isButtonDisabled } = useMemo9(() => {
3611
3748
  if (isUnsupportedChain) {
3612
3749
  if (!allowShapeshiftRedirect) return { text: "Route not supported", disabled: true };
@@ -3614,39 +3751,43 @@ var InputStep = ({
3614
3751
  }
3615
3752
  if (!sendAddress) return { text: "Connect Wallet", disabled: false };
3616
3753
  if (!receiveAddress) return { text: "Enter receive address", disabled: true };
3617
- if (!sellAmount) return { text: "Enter an amount", disabled: true };
3618
- if (isLoadingRates) return { text: "Finding rates...", disabled: true };
3619
- if (ratesError) return { text: "No routes available", disabled: true };
3620
- if (!rates?.length) return { text: "No routes found", disabled: true };
3754
+ const drivingAmount = displayValues.isExactOutput ? context.buyAmountBaseUnit : context.sellAmount;
3755
+ if (!drivingAmount || drivingAmount === "0") return { text: "Enter an amount", disabled: true };
3756
+ if (displayValues.isLoadingRates) return { text: "Finding rates...", disabled: true };
3757
+ if (displayValues.ratesError) return { text: "No routes available", disabled: true };
3758
+ if (!displayValues.rates?.length) return { text: "No routes found", disabled: true };
3621
3759
  return { text: "Swap", disabled: false };
3622
3760
  }, [
3623
3761
  isUnsupportedChain,
3624
3762
  allowShapeshiftRedirect,
3625
3763
  sendAddress,
3626
3764
  receiveAddress,
3627
- sellAmount,
3628
- isLoadingRates,
3629
- ratesError,
3630
- rates
3765
+ context.sellAmount,
3766
+ displayValues.isExactOutput,
3767
+ context.buyAmountBaseUnit,
3768
+ displayValues.isLoadingRates,
3769
+ displayValues.ratesError,
3770
+ displayValues.rates
3631
3771
  ]);
3632
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
3772
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
3633
3773
  /* @__PURE__ */ jsxs6("div", { className: "ssw-swap-container", children: [
3634
3774
  /* @__PURE__ */ jsxs6("div", { className: "ssw-token-section ssw-sell", children: [
3635
3775
  /* @__PURE__ */ jsx6("div", { className: "ssw-section-header", children: /* @__PURE__ */ jsx6("span", { className: "ssw-section-label", children: "Sell" }) }),
3636
3776
  /* @__PURE__ */ jsxs6("div", { className: "ssw-input-row", children: [
3637
- isSellAmountFiat && /* @__PURE__ */ jsx6("span", { className: "ssw-fiat-prefix", children: "$" }),
3777
+ context.isSellAmountFiat && /* @__PURE__ */ jsx6("span", { className: "ssw-fiat-prefix", children: "$" }),
3638
3778
  /* @__PURE__ */ jsx6(
3639
3779
  "input",
3640
3780
  {
3641
3781
  type: "text",
3642
- className: "ssw-amount-input",
3643
- placeholder: "0",
3644
- value: isSellAmountFiat ? sellAmountFiat : sellAmount,
3782
+ className: `ssw-amount-input${isSellAmountReadOnly ? " ssw-amount-input-locked" : ""}${isSellAmountPending ? " ssw-amount-input-pending" : ""}`,
3783
+ placeholder: isSellAmountPending ? "..." : "0",
3784
+ value: sellAmountValue,
3785
+ readOnly: isSellAmountReadOnly,
3645
3786
  onChange: (e) => {
3646
3787
  const raw = e.target.value.replace(/[^0-9.]/g, "");
3647
3788
  const parts = raw.split(".");
3648
3789
  const sanitized = parts.length > 2 ? parts[0] + "." + parts.slice(1).join("") : raw;
3649
- onSellAmountChange(sanitized, sellAssetUsdPrice);
3790
+ onSellAmountChange(sanitized, displayValues.sellAssetUsdPrice);
3650
3791
  }
3651
3792
  }
3652
3793
  ),
@@ -3657,10 +3798,17 @@ var InputStep = ({
3657
3798
  onClick: () => onOpenTokenModal("sell"),
3658
3799
  type: "button",
3659
3800
  children: [
3660
- sellAsset.icon ? /* @__PURE__ */ jsx6("img", { src: sellAsset.icon, alt: sellAsset.symbol, className: "ssw-token-icon" }) : /* @__PURE__ */ jsx6("div", { className: "ssw-token-icon-placeholder", children: sellAsset.symbol.charAt(0) }),
3801
+ context.sellAsset.icon ? /* @__PURE__ */ jsx6(
3802
+ "img",
3803
+ {
3804
+ src: context.sellAsset.icon,
3805
+ alt: context.sellAsset.symbol,
3806
+ className: "ssw-token-icon"
3807
+ }
3808
+ ) : /* @__PURE__ */ jsx6("div", { className: "ssw-token-icon-placeholder", children: context.sellAsset.symbol.charAt(0) }),
3661
3809
  /* @__PURE__ */ jsxs6("div", { className: "ssw-token-info", children: [
3662
- /* @__PURE__ */ jsx6("span", { className: "ssw-token-symbol", children: sellAsset.symbol }),
3663
- /* @__PURE__ */ jsx6("span", { className: "ssw-token-chain", children: sellChainInfo?.name ?? sellAsset.networkName ?? sellAsset.name })
3810
+ /* @__PURE__ */ jsx6("span", { className: "ssw-token-symbol", children: context.sellAsset.symbol }),
3811
+ /* @__PURE__ */ jsx6("span", { className: "ssw-token-chain", children: displayValues.sellChainInfo?.name ?? context.sellAsset.networkName ?? context.sellAsset.name })
3664
3812
  ] }),
3665
3813
  /* @__PURE__ */ jsx6(
3666
3814
  "svg",
@@ -3679,14 +3827,14 @@ var InputStep = ({
3679
3827
  )
3680
3828
  ] }),
3681
3829
  /* @__PURE__ */ jsxs6("div", { className: "ssw-section-footer", children: [
3682
- sellAssetUsdPrice ? /* @__PURE__ */ jsxs6(
3830
+ !displayValues.sellAssetUsdPrice ? /* @__PURE__ */ jsx6("span", { className: "ssw-usd-value" }) : /* @__PURE__ */ jsxs6(
3683
3831
  "button",
3684
3832
  {
3685
3833
  type: "button",
3686
3834
  className: "ssw-usd-value ssw-usd-value-toggle",
3687
- onClick: () => onToggleSellFiat(sellAssetUsdPrice),
3835
+ onClick: () => onToggleSellFiat(displayValues.sellAssetUsdPrice),
3688
3836
  children: [
3689
- /* @__PURE__ */ jsx6("span", { children: isSellAmountFiat ? `\u2248 ${sellAmountBaseUnit ? formatAmount(sellAmountBaseUnit, sellAsset.precision, 6) : "0"} ${sellAsset.symbol}` : sellUsdValue }),
3837
+ /* @__PURE__ */ jsx6("span", { children: context.isSellAmountFiat ? `\u2248 ${sellAmountCrypto || "0"} ${context.sellAsset.symbol}` : displayValues.sellUsdValue }),
3690
3838
  /* @__PURE__ */ jsx6(
3691
3839
  "svg",
3692
3840
  {
@@ -3702,13 +3850,14 @@ var InputStep = ({
3702
3850
  )
3703
3851
  ]
3704
3852
  }
3705
- ) : /* @__PURE__ */ jsx6("span", { className: "ssw-usd-value" }),
3706
- hasAnyWalletAddress && (isSellBalanceLoading ? /* @__PURE__ */ jsx6("span", { className: "ssw-balance-skeleton" }) : sellAssetBalance ? /* @__PURE__ */ jsxs6("span", { className: "ssw-balance", children: [
3853
+ ),
3854
+ hasAnyWalletAddress && (displayValues.isSellBalanceLoading ? /* @__PURE__ */ jsx6("span", { className: "ssw-balance-skeleton" }) : displayValues.sellAssetBalance ? /* @__PURE__ */ jsxs6("span", { className: "ssw-balance", children: [
3707
3855
  "Balance: ",
3708
- sellAssetBalance.balanceFormatted,
3709
- sellBalanceFiatValue && /* @__PURE__ */ jsxs6("span", { className: "ssw-balance-fiat", children: [
3710
- " (",
3711
- sellBalanceFiatValue,
3856
+ displayValues.sellAssetBalance.balanceFormatted,
3857
+ displayValues.sellBalanceFiatValue && /* @__PURE__ */ jsxs6("span", { className: "ssw-balance-fiat", children: [
3858
+ " ",
3859
+ "(",
3860
+ displayValues.sellBalanceFiatValue,
3712
3861
  ")"
3713
3862
  ] })
3714
3863
  ] }) : null)
@@ -3742,10 +3891,15 @@ var InputStep = ({
3742
3891
  "input",
3743
3892
  {
3744
3893
  type: "text",
3745
- className: "ssw-amount-input",
3746
- placeholder: "0",
3747
- value: buyAmount ? formatAmount(buyAmount, buyAsset.precision) : "",
3748
- readOnly: true
3894
+ className: `ssw-amount-input${isBuyAmountLocked ? " ssw-amount-input-locked" : ""}${isBuyAmountPending ? " ssw-amount-input-pending" : ""}`,
3895
+ placeholder: isBuyAmountPending ? "..." : "0",
3896
+ value: buyAmountValue,
3897
+ readOnly: isBuyAmountLocked,
3898
+ onChange: (e) => {
3899
+ const raw = e.target.value.replace(/[^0-9.]/g, "");
3900
+ const parts = raw.split(".");
3901
+ onBuyAmountChange(parts.length > 2 ? parts[0] + "." + parts.slice(1).join("") : raw);
3902
+ }
3749
3903
  }
3750
3904
  ),
3751
3905
  /* @__PURE__ */ jsxs6(
@@ -3756,10 +3910,17 @@ var InputStep = ({
3756
3910
  disabled: isBuyAssetLocked,
3757
3911
  type: "button",
3758
3912
  children: [
3759
- buyAsset.icon ? /* @__PURE__ */ jsx6("img", { src: buyAsset.icon, alt: buyAsset.symbol, className: "ssw-token-icon" }) : /* @__PURE__ */ jsx6("div", { className: "ssw-token-icon-placeholder", children: buyAsset.symbol.charAt(0) }),
3913
+ context.buyAsset.icon ? /* @__PURE__ */ jsx6(
3914
+ "img",
3915
+ {
3916
+ src: context.buyAsset.icon,
3917
+ alt: context.buyAsset.symbol,
3918
+ className: "ssw-token-icon"
3919
+ }
3920
+ ) : /* @__PURE__ */ jsx6("div", { className: "ssw-token-icon-placeholder", children: context.buyAsset.symbol.charAt(0) }),
3760
3921
  /* @__PURE__ */ jsxs6("div", { className: "ssw-token-info", children: [
3761
- /* @__PURE__ */ jsx6("span", { className: "ssw-token-symbol", children: buyAsset.symbol }),
3762
- /* @__PURE__ */ jsx6("span", { className: "ssw-token-chain", children: buyChainInfo?.name ?? buyAsset.networkName ?? buyAsset.name })
3922
+ /* @__PURE__ */ jsx6("span", { className: "ssw-token-symbol", children: context.buyAsset.symbol }),
3923
+ /* @__PURE__ */ jsx6("span", { className: "ssw-token-chain", children: displayValues.buyChainInfo?.name ?? context.buyAsset.networkName ?? context.buyAsset.name })
3763
3924
  ] }),
3764
3925
  !isBuyAssetLocked && /* @__PURE__ */ jsx6(
3765
3926
  "svg",
@@ -3778,13 +3939,13 @@ var InputStep = ({
3778
3939
  )
3779
3940
  ] }),
3780
3941
  /* @__PURE__ */ jsxs6("div", { className: "ssw-section-footer", children: [
3781
- /* @__PURE__ */ jsx6("span", { className: "ssw-usd-value", children: buyUsdValue }),
3782
- hasAnyWalletAddress && (isBuyBalanceLoading ? /* @__PURE__ */ jsx6("span", { className: "ssw-balance-skeleton" }) : buyAssetBalance ? /* @__PURE__ */ jsxs6("span", { className: "ssw-balance", children: [
3942
+ /* @__PURE__ */ jsx6("span", { className: "ssw-usd-value", children: displayValues.buyUsdValue }),
3943
+ hasAnyWalletAddress && (displayValues.isBuyBalanceLoading ? /* @__PURE__ */ jsx6("span", { className: "ssw-balance-skeleton" }) : displayValues.buyAssetBalance ? /* @__PURE__ */ jsxs6("span", { className: "ssw-balance", children: [
3783
3944
  "Balance: ",
3784
- buyAssetBalance.balanceFormatted,
3785
- buyBalanceFiatValue && /* @__PURE__ */ jsxs6("span", { className: "ssw-balance-fiat", children: [
3945
+ displayValues.buyAssetBalance.balanceFormatted,
3946
+ displayValues.buyBalanceFiatValue && /* @__PURE__ */ jsxs6("span", { className: "ssw-balance-fiat", children: [
3786
3947
  " (",
3787
- buyBalanceFiatValue,
3948
+ displayValues.buyBalanceFiatValue,
3788
3949
  ")"
3789
3950
  ] })
3790
3951
  ] }) : null)
@@ -3796,26 +3957,30 @@ var InputStep = ({
3796
3957
  receiveAddress,
3797
3958
  isResolving: isReceiveAddressResolving,
3798
3959
  buyChainId,
3960
+ isLocked: isReceiveAddressLocked,
3799
3961
  onSetCustomReceiveAddress: setCustomReceiveAddress
3800
3962
  }
3801
3963
  )
3802
3964
  ] }),
3803
- sellAmountBaseUnit && sellAmountBaseUnit !== "0" && (rates?.length || isLoadingRates) && /* @__PURE__ */ jsx6("div", { className: "ssw-quotes", children: /* @__PURE__ */ jsx6(
3965
+ amountBaseUnit && amountBaseUnit !== "0" && (displayValues.rates?.length || displayValues.isLoadingRates) && /* @__PURE__ */ jsx6("div", { className: "ssw-quotes", children: /* @__PURE__ */ jsx6(
3804
3966
  QuoteSelector,
3805
3967
  {
3806
- rates: rates ?? [],
3807
- selectedRate,
3968
+ rates: displayValues.rates ?? [],
3969
+ selectedRate: context.selectedRate,
3808
3970
  onSelectRate,
3809
- buyAsset,
3810
- sellAsset,
3811
- sellAmountBaseUnit,
3812
- isLoading: isLoadingRates,
3813
- buyAssetUsdPrice
3971
+ buyAsset: context.buyAsset,
3972
+ sellAsset: context.sellAsset,
3973
+ sellAmountBaseUnit: displayValues.sellAmountBaseUnit ?? "0",
3974
+ buyAmountBaseUnit: context.buyAmountBaseUnit ?? "0",
3975
+ isExactOutput: displayValues.isExactOutput,
3976
+ isLoading: displayValues.isLoadingRates,
3977
+ sellAssetUsdPrice: displayValues.sellAssetUsdPrice,
3978
+ buyAssetUsdPrice: displayValues.buyAssetUsdPrice
3814
3979
  }
3815
3980
  ) }),
3816
- networkFeeDisplay && /* @__PURE__ */ jsxs6("div", { className: "ssw-network-fee", children: [
3981
+ displayValues.networkFeeDisplay && /* @__PURE__ */ jsxs6("div", { className: "ssw-network-fee", children: [
3817
3982
  /* @__PURE__ */ jsx6("span", { className: "ssw-network-fee-label", children: "Est. network fee" }),
3818
- /* @__PURE__ */ jsx6("span", { className: "ssw-network-fee-value", children: networkFeeDisplay })
3983
+ /* @__PURE__ */ jsx6("span", { className: "ssw-network-fee-value", children: displayValues.networkFeeDisplay })
3819
3984
  ] }),
3820
3985
  /* @__PURE__ */ jsx6(
3821
3986
  "button",
@@ -3825,7 +3990,7 @@ var InputStep = ({
3825
3990
  onClick: onButtonClick,
3826
3991
  type: "button",
3827
3992
  style: isQuoting ? { opacity: 0.7 } : void 0,
3828
- children: isQuoting ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
3993
+ children: isQuoting ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
3829
3994
  /* @__PURE__ */ jsxs6(
3830
3995
  "svg",
3831
3996
  {
@@ -4012,7 +4177,7 @@ var SettingsModal = ({ isOpen, onClose, onSlippageChange }) => {
4012
4177
 
4013
4178
  // src/components/StatusStep.tsx
4014
4179
  import { useMemo as useMemo10 } from "react";
4015
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4180
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4016
4181
  var ExplorerLink = ({ url }) => /* @__PURE__ */ jsxs8("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "ssw-step-explorer-link", children: [
4017
4182
  "View on Explorer",
4018
4183
  /* @__PURE__ */ jsx8(
@@ -4028,7 +4193,7 @@ var ExplorerLink = ({ url }) => /* @__PURE__ */ jsxs8("a", { href: url, target:
4028
4193
  }
4029
4194
  )
4030
4195
  ] });
4031
- var StatusStep = () => {
4196
+ var StatusStep = ({ isPayment }) => {
4032
4197
  const context = SwapMachineCtx.useSelector((s) => s.context);
4033
4198
  const isPolling = SwapMachineCtx.useSelector((s) => s.matches("polling_status"));
4034
4199
  const isComplete = SwapMachineCtx.useSelector((s) => s.matches("complete"));
@@ -4046,7 +4211,7 @@ var StatusStep = () => {
4046
4211
  [error]
4047
4212
  );
4048
4213
  return /* @__PURE__ */ jsxs8("div", { className: "ssw-step-screen", children: [
4049
- isPolling && /* @__PURE__ */ jsxs8(Fragment4, { children: [
4214
+ isPolling && /* @__PURE__ */ jsxs8(Fragment5, { children: [
4050
4215
  /* @__PURE__ */ jsx8("div", { className: "ssw-step-icon-circle ssw-ic-accent", children: /* @__PURE__ */ jsxs8(
4051
4216
  "svg",
4052
4217
  {
@@ -4067,7 +4232,7 @@ var StatusStep = () => {
4067
4232
  /* @__PURE__ */ jsx8("div", { className: "ssw-step-subtitle", children: "Your swap is being processed\u2026" }),
4068
4233
  explorerUrl && /* @__PURE__ */ jsx8(ExplorerLink, { url: explorerUrl })
4069
4234
  ] }),
4070
- isComplete && /* @__PURE__ */ jsxs8(Fragment4, { children: [
4235
+ isComplete && /* @__PURE__ */ jsxs8(Fragment5, { children: [
4071
4236
  /* @__PURE__ */ jsx8("div", { className: "ssw-step-icon-circle ssw-ic-success", children: /* @__PURE__ */ jsx8(
4072
4237
  "svg",
4073
4238
  {
@@ -4088,7 +4253,7 @@ var StatusStep = () => {
4088
4253
  buyAsset.symbol
4089
4254
  ] }),
4090
4255
  explorerUrl && /* @__PURE__ */ jsx8(ExplorerLink, { url: explorerUrl }),
4091
- /* @__PURE__ */ jsx8("div", { className: "ssw-step-actions", children: /* @__PURE__ */ jsx8(
4256
+ !isPayment && /* @__PURE__ */ jsx8("div", { className: "ssw-step-actions", children: /* @__PURE__ */ jsx8(
4092
4257
  "button",
4093
4258
  {
4094
4259
  className: "ssw-action-btn",
@@ -4098,7 +4263,7 @@ var StatusStep = () => {
4098
4263
  }
4099
4264
  ) })
4100
4265
  ] }),
4101
- isError && /* @__PURE__ */ jsxs8(Fragment4, { children: [
4266
+ isError && /* @__PURE__ */ jsxs8(Fragment5, { children: [
4102
4267
  /* @__PURE__ */ jsx8("div", { className: "ssw-step-icon-circle ssw-ic-error", children: /* @__PURE__ */ jsxs8(
4103
4268
  "svg",
4104
4269
  {
@@ -4141,10 +4306,10 @@ var StatusStep = () => {
4141
4306
  };
4142
4307
 
4143
4308
  // src/components/TokenSelectModal.tsx
4144
- import { bnOrZero as bnOrZero3 } from "@shapeshiftoss/utils";
4309
+ import { bnOrZero as bnOrZero2 } from "@shapeshiftoss/utils";
4145
4310
  import { useCallback as useCallback9, useEffect as useEffect8, useMemo as useMemo11, useRef as useRef6, useState as useState6 } from "react";
4146
4311
  import { Virtuoso } from "react-virtuoso";
4147
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4312
+ import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4148
4313
  var VISIBLE_BUFFER = 10;
4149
4314
  var useLockBodyScroll3 = (isLocked) => {
4150
4315
  useEffect8(() => {
@@ -4325,7 +4490,7 @@ var TokenSelectModal = ({
4325
4490
  const balance = balances[asset.assetId];
4326
4491
  if (balance && balance.balance !== "0") {
4327
4492
  const price = marketData?.[asset.assetId]?.price;
4328
- const fiatValue = price ? bnOrZero3(balance.balance).div(bnOrZero3(10).pow(asset.precision)).times(bnOrZero3(price)).toNumber() : 0;
4493
+ const fiatValue = price ? bnOrZero2(balance.balance).div(bnOrZero2(10).pow(asset.precision)).times(bnOrZero2(price)).toNumber() : 0;
4329
4494
  withBalance.push({ asset, fiatValue });
4330
4495
  } else {
4331
4496
  withoutBalance.push(asset);
@@ -4529,10 +4694,10 @@ var TokenSelectModal = ({
4529
4694
  /* @__PURE__ */ jsx9("span", { className: "ssw-token-symbol", children: asset.symbol }),
4530
4695
  /* @__PURE__ */ jsx9("span", { className: "ssw-token-name", children: chainInfo?.name ?? asset.networkName ?? asset.name })
4531
4696
  ] }),
4532
- /* @__PURE__ */ jsx9("div", { className: "ssw-token-right", children: (evmAddress || bitcoinAddress || solanaAddress) && (loadingAssetIds.has(asset.assetId) ? /* @__PURE__ */ jsx9("span", { className: "ssw-token-balance-skeleton" }) : balance && balance.balance !== "0" ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
4697
+ /* @__PURE__ */ jsx9("div", { className: "ssw-token-right", children: (evmAddress || bitcoinAddress || solanaAddress) && (loadingAssetIds.has(asset.assetId) ? /* @__PURE__ */ jsx9("span", { className: "ssw-token-balance-skeleton" }) : balance && balance.balance !== "0" ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
4533
4698
  marketData?.[asset.assetId]?.price && /* @__PURE__ */ jsxs9("span", { className: "ssw-token-fiat-value", children: [
4534
4699
  "$",
4535
- bnOrZero3(balance.balance).div(bnOrZero3(10).pow(asset.precision)).times(bnOrZero3(marketData[asset.assetId].price)).toNumber().toLocaleString(void 0, {
4700
+ bnOrZero2(balance.balance).div(bnOrZero2(10).pow(asset.precision)).times(bnOrZero2(marketData[asset.assetId].price)).toNumber().toLocaleString(void 0, {
4536
4701
  minimumFractionDigits: 2,
4537
4702
  maximumFractionDigits: 2
4538
4703
  })
@@ -4674,8 +4839,11 @@ var SwapWidgetContent = ({
4674
4839
  showPoweredBy,
4675
4840
  showConnectButton,
4676
4841
  isBuyAssetLocked,
4842
+ isBuyAmountLocked,
4843
+ isReceiveAddressLocked,
4844
+ isPayment,
4677
4845
  partnerCode,
4678
- allowShapeshiftRedirect,
4846
+ canRedirectToShapeshift,
4679
4847
  onSwapSuccess,
4680
4848
  onSwapError,
4681
4849
  sellFilters,
@@ -4699,11 +4867,12 @@ var SwapWidgetContent = ({
4699
4867
  handleSellAssetSelect,
4700
4868
  handleBuyAssetSelect,
4701
4869
  handleSellAmountChange,
4870
+ handleBuyAmountChange,
4702
4871
  handleToggleSellFiat,
4703
4872
  handleSelectRate,
4704
4873
  handleSlippageChange,
4705
4874
  handleButtonClick
4706
- } = useSwapHandlers({ partnerCode, allowShapeshiftRedirect });
4875
+ } = useSwapHandlers({ partnerCode, allowShapeshiftRedirect: canRedirectToShapeshift });
4707
4876
  useSwapQuoting({ apiClient, rates, sellAssetBalance });
4708
4877
  useSwapApproval();
4709
4878
  useSwapExecution();
@@ -4799,17 +4968,20 @@ var SwapWidgetContent = ({
4799
4968
  displayValues,
4800
4969
  onOpenTokenModal: setTokenModalType,
4801
4970
  onSellAmountChange: handleSellAmountChange,
4971
+ onBuyAmountChange: handleBuyAmountChange,
4802
4972
  onToggleSellFiat: handleToggleSellFiat,
4803
4973
  onSwapTokens: handleSwapTokens,
4804
4974
  onSelectRate: handleSelectRate,
4805
4975
  onButtonClick: handleButtonClick,
4806
4976
  isBuyAssetLocked,
4807
- allowShapeshiftRedirect
4977
+ isBuyAmountLocked,
4978
+ isReceiveAddressLocked,
4979
+ allowShapeshiftRedirect: canRedirectToShapeshift
4808
4980
  }
4809
4981
  ),
4810
4982
  (state.matches("approval_needed") || state.matches("approving")) && /* @__PURE__ */ jsx11(ApprovalStep, {}),
4811
4983
  state.matches("executing") && /* @__PURE__ */ jsx11(ExecutionStep, {}),
4812
- (state.matches("polling_status") || state.matches("complete") || state.matches("error")) && /* @__PURE__ */ jsx11(StatusStep, {})
4984
+ (state.matches("polling_status") || state.matches("complete") || state.matches("error")) && /* @__PURE__ */ jsx11(StatusStep, { isPayment })
4813
4985
  ] }),
4814
4986
  showPoweredBy && /* @__PURE__ */ jsxs11("div", { className: "ssw-powered-by", children: [
4815
4987
  "Powered by",
@@ -4838,7 +5010,7 @@ var SwapWidgetContent = ({
4838
5010
  disabledChainIds: (tokenModalType === "buy" ? buyFilters : sellFilters).disabledChainIds ?? [],
4839
5011
  allowedChainIds: (tokenModalType === "buy" ? buyFilters : sellFilters).allowedChainIds,
4840
5012
  allowedAssetIds: (tokenModalType === "buy" ? buyFilters : sellFilters).allowedAssetIds,
4841
- allowShapeshiftRedirect
5013
+ allowShapeshiftRedirect: canRedirectToShapeshift
4842
5014
  }
4843
5015
  ),
4844
5016
  /* @__PURE__ */ jsx11(
@@ -4862,6 +5034,10 @@ var SwapWidgetCore = ({
4862
5034
  showPoweredBy,
4863
5035
  showConnectButton,
4864
5036
  isBuyAssetLocked,
5037
+ defaultReceiveAddress,
5038
+ isReceiveAddressLocked,
5039
+ defaultBuyAmountCryptoBaseUnit,
5040
+ isBuyAmountLocked,
4865
5041
  partnerCode,
4866
5042
  allowShapeshiftRedirect,
4867
5043
  onSwapSuccess,
@@ -4875,7 +5051,9 @@ var SwapWidgetCore = ({
4875
5051
  const evm = useEvmSigning();
4876
5052
  const bitcoin3 = useBitcoinSigning();
4877
5053
  const solana3 = useSolanaSigning();
4878
- const [customReceiveAddress, setCustomReceiveAddress] = useState8("");
5054
+ const [customReceiveAddress, setCustomReceiveAddress] = useState8(
5055
+ defaultReceiveAddress ?? ""
5056
+ );
4879
5057
  const sellChainId = SwapMachineCtx.useSelector((s) => s.context.sellAsset.chainId);
4880
5058
  const buyChainId = SwapMachineCtx.useSelector((s) => s.context.buyAsset.chainId);
4881
5059
  const sellChainType = getChainType(sellChainId);
@@ -4908,14 +5086,30 @@ var SwapWidgetCore = ({
4908
5086
  () => addressForChain(buyChainType),
4909
5087
  [addressForChain, buyChainType]
4910
5088
  );
4911
- const isCustomReceiveAddressValid = useMemo12(
4912
- () => !!customReceiveAddress && validateAddress(customReceiveAddress, buyChainId).valid,
4913
- [customReceiveAddress, buyChainId]
4914
- );
4915
5089
  const receiveAddress = useMemo12(
4916
- () => isCustomReceiveAddressValid ? customReceiveAddress : walletReceiveAddress,
4917
- [isCustomReceiveAddressValid, customReceiveAddress, walletReceiveAddress]
5090
+ () => resolveReceiveAddress({
5091
+ isLocked: isReceiveAddressLocked,
5092
+ defaultAddress: defaultReceiveAddress,
5093
+ customAddress: customReceiveAddress,
5094
+ walletAddress: walletReceiveAddress,
5095
+ buyChainId
5096
+ }),
5097
+ [
5098
+ isReceiveAddressLocked,
5099
+ defaultReceiveAddress,
5100
+ customReceiveAddress,
5101
+ walletReceiveAddress,
5102
+ buyChainId
5103
+ ]
4918
5104
  );
5105
+ useEffect10(() => {
5106
+ if (!isBuyAmountLocked) return;
5107
+ actorRef.send({
5108
+ type: "SET_BUY_AMOUNT",
5109
+ amount: defaultBuyAmountCryptoBaseUnit ? formatAmountForInput(defaultBuyAmountCryptoBaseUnit, defaultBuyAsset.precision) : "",
5110
+ amountBaseUnit: defaultBuyAmountCryptoBaseUnit
5111
+ });
5112
+ }, [isBuyAmountLocked, defaultBuyAmountCryptoBaseUnit, defaultBuyAsset.precision, actorRef]);
4919
5113
  const initialSyncRef = useRef7(false);
4920
5114
  useEffect10(() => {
4921
5115
  if (initialSyncRef.current) return;
@@ -4923,6 +5117,11 @@ var SwapWidgetCore = ({
4923
5117
  actorRef.send({ type: "SET_SELL_ASSET", asset: defaultSellAsset });
4924
5118
  actorRef.send({ type: "SET_BUY_ASSET", asset: defaultBuyAsset });
4925
5119
  actorRef.send({ type: "SET_SLIPPAGE", slippage: defaultSlippage });
5120
+ actorRef.send({
5121
+ type: "SET_BUY_AMOUNT",
5122
+ amount: defaultBuyAmountCryptoBaseUnit ? formatAmountForInput(defaultBuyAmountCryptoBaseUnit, defaultBuyAsset.precision) : "",
5123
+ amountBaseUnit: defaultBuyAmountCryptoBaseUnit
5124
+ });
4926
5125
  }, [actorRef]);
4927
5126
  useEffect10(() => {
4928
5127
  actorRef.send({ type: "SET_SEND_ADDRESS", address: sendAddress });
@@ -4939,6 +5138,7 @@ var SwapWidgetCore = ({
4939
5138
  sendAddress,
4940
5139
  receiveAddress,
4941
5140
  isReceiveAddressResolving,
5141
+ isReceiveAddressBlocked: isReceiveAddressLocked && !receiveAddress,
4942
5142
  customReceiveAddress,
4943
5143
  setCustomReceiveAddress,
4944
5144
  evm,
@@ -4949,12 +5149,16 @@ var SwapWidgetCore = ({
4949
5149
  sendAddress,
4950
5150
  receiveAddress,
4951
5151
  isReceiveAddressResolving,
5152
+ isReceiveAddressLocked,
4952
5153
  customReceiveAddress,
4953
5154
  evm,
4954
5155
  bitcoin3,
4955
5156
  solana3
4956
5157
  ]
4957
5158
  );
5159
+ const hasLockedBuyAmount = isBuyAmountLocked && !!defaultBuyAmountCryptoBaseUnit;
5160
+ const isPayment = hasLockedBuyAmount && isReceiveAddressLocked && !!defaultReceiveAddress;
5161
+ const canRedirectToShapeshift = allowShapeshiftRedirect && !hasLockedBuyAmount && !isReceiveAddressLocked;
4958
5162
  return /* @__PURE__ */ jsx11(SwapWalletProvider, { value: walletValue, children: /* @__PURE__ */ jsx11(
4959
5163
  SwapWidgetContent,
4960
5164
  {
@@ -4962,9 +5166,12 @@ var SwapWidgetCore = ({
4962
5166
  theme,
4963
5167
  showPoweredBy,
4964
5168
  showConnectButton,
4965
- isBuyAssetLocked,
5169
+ isBuyAssetLocked: isBuyAssetLocked || hasLockedBuyAmount,
5170
+ isBuyAmountLocked,
5171
+ isReceiveAddressLocked,
5172
+ isPayment,
4966
5173
  partnerCode,
4967
- allowShapeshiftRedirect,
5174
+ canRedirectToShapeshift,
4968
5175
  onSwapSuccess,
4969
5176
  onSwapError,
4970
5177
  sellFilters,
@@ -4993,6 +5200,10 @@ var SwapWidget = (props) => {
4993
5200
  showPoweredBy: props.showPoweredBy ?? true,
4994
5201
  showConnectButton: props.showConnectButton ?? true,
4995
5202
  isBuyAssetLocked: props.isBuyAssetLocked ?? false,
5203
+ defaultReceiveAddress: props.defaultReceiveAddress,
5204
+ isReceiveAddressLocked: props.isReceiveAddressLocked ?? false,
5205
+ defaultBuyAmountCryptoBaseUnit: props.defaultBuyAmountCryptoBaseUnit,
5206
+ isBuyAmountLocked: props.isBuyAmountLocked ?? false,
4996
5207
  partnerCode: props.partnerCode,
4997
5208
  allowShapeshiftRedirect: props.allowShapeshiftRedirect ?? true,
4998
5209
  onSwapSuccess: props.onSwapSuccess,