@unifold/ui-web 0.1.56 → 0.1.57

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.mjs CHANGED
@@ -43714,6 +43714,20 @@ async function refreshIntegrationToken(accessToken, publishableKey) {
43714
43714
  }
43715
43715
  return response.json();
43716
43716
  }
43717
+ async function revokeIntegrationToken(accessToken, publishableKey) {
43718
+ const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43719
+ validatePublishableKey(pk);
43720
+ await fetch(`${API_BASE_URL}/v1/public/integrations/token/revoke`, {
43721
+ method: "POST",
43722
+ headers: {
43723
+ accept: "application/json",
43724
+ "x-publishable-key": pk,
43725
+ "Content-Type": "application/json"
43726
+ },
43727
+ body: JSON.stringify({ access_token: accessToken })
43728
+ }).catch(() => {
43729
+ });
43730
+ }
43717
43731
  async function getIntegrationHoldings(provider, accessToken, publishableKey) {
43718
43732
  const pk = publishableKey || DEFAULT_PUBLISHABLE_KEY;
43719
43733
  validatePublishableKey(pk);
@@ -49387,7 +49401,8 @@ var en_default2 = {
49387
49401
  title: "Transfer Failed",
49388
49402
  tryAgain: "Try Again"
49389
49403
  },
49390
- continue: "Continue"
49404
+ continue: "Continue",
49405
+ disconnect: "Disconnect"
49391
49406
  },
49392
49407
  buyWithCard: {
49393
49408
  onramp: {
@@ -52634,6 +52649,7 @@ function PayWithExchangeButton({
52634
52649
  }
52635
52650
  function ConnectExchangeButton({
52636
52651
  onClick,
52652
+ onDisconnect,
52637
52653
  title,
52638
52654
  subtitle,
52639
52655
  exchanges,
@@ -52646,6 +52662,109 @@ function ConnectExchangeButton({
52646
52662
  setIsTouchDevice("ontouchstart" in window || navigator.maxTouchPoints > 0);
52647
52663
  }, []);
52648
52664
  const isConnected = connectedExchange != null;
52665
+ const handleDisconnectClick = (e) => {
52666
+ e.preventDefault();
52667
+ e.stopPropagation();
52668
+ onDisconnect?.();
52669
+ };
52670
+ const rowSurfaceStyle = {
52671
+ backgroundColor: isHovered ? colors2.cardHover : components.card.backgroundColor,
52672
+ borderRadius: components.card.borderRadius,
52673
+ border: `${components.card.borderWidth}px solid ${components.card.borderColor}`
52674
+ };
52675
+ const iconBlock = isConnected ? connectedExchange.iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52676
+ "img",
52677
+ {
52678
+ src: connectedExchange.iconUrl,
52679
+ alt: connectedExchange.name,
52680
+ width: 36,
52681
+ height: 36,
52682
+ className: "uf-rounded-lg"
52683
+ }
52684
+ ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52685
+ "div",
52686
+ {
52687
+ className: "uf-w-9 uf-h-9 uf-rounded-lg uf-flex uf-items-center uf-justify-center",
52688
+ style: { backgroundColor: colors2.card },
52689
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52690
+ "span",
52691
+ {
52692
+ className: "uf-text-xs uf-font-medium",
52693
+ style: { color: components.card.iconColor },
52694
+ children: connectedExchange.name.slice(0, 2).toUpperCase()
52695
+ }
52696
+ )
52697
+ }
52698
+ ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uf-rounded-lg uf-p-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52699
+ Link2,
52700
+ {
52701
+ className: "uf-w-5 uf-h-5",
52702
+ style: { color: components.card.iconColor }
52703
+ }
52704
+ ) });
52705
+ const titleSubtitleBlock = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "uf-text-left", children: [
52706
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52707
+ "div",
52708
+ {
52709
+ className: "uf-text-sm uf-font-light uf-mb-0.5",
52710
+ style: {
52711
+ color: components.card.titleColor,
52712
+ fontFamily: fonts.regular
52713
+ },
52714
+ children: isConnected ? connectedExchange.name : title
52715
+ }
52716
+ ),
52717
+ isConnected && connectedExchange.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uf-h-3 uf-w-24 uf-bg-muted uf-rounded uf-animate-pulse" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52718
+ "div",
52719
+ {
52720
+ className: "uf-text-xs uf-font-light",
52721
+ style: {
52722
+ color: components.card.subtitleColor,
52723
+ fontFamily: fonts.regular
52724
+ },
52725
+ children: isConnected ? connectedExchange.balanceUsd ? `$${connectedExchange.balanceUsd} \u2022 2 min` : "Deposit from exchange" : subtitle
52726
+ }
52727
+ )
52728
+ ] });
52729
+ if (isConnected && onDisconnect) {
52730
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
52731
+ "div",
52732
+ {
52733
+ onMouseEnter: () => !isTouchDevice && setIsHovered(true),
52734
+ onMouseLeave: () => setIsHovered(false),
52735
+ onTouchStart: () => setIsHovered(false),
52736
+ className: "uf-w-full uf-transition-colors uf-flex uf-items-stretch uf-group",
52737
+ style: rowSurfaceStyle,
52738
+ children: [
52739
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
52740
+ "button",
52741
+ {
52742
+ type: "button",
52743
+ onClick,
52744
+ className: "uf-min-w-0 uf-flex-1 uf-flex uf-items-center uf-gap-3 uf-p-3 uf-pr-1 uf-text-left uf-border-0 uf-bg-transparent hover:uf-bg-transparent",
52745
+ children: [
52746
+ iconBlock,
52747
+ titleSubtitleBlock
52748
+ ]
52749
+ }
52750
+ ),
52751
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uf-flex uf-items-center uf-gap-1 uf-shrink-0 uf-pr-2 uf-pl-0", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52752
+ "button",
52753
+ {
52754
+ type: "button",
52755
+ onClick: handleDisconnectClick,
52756
+ className: "uf-h-auto uf-min-h-8 uf-py-1.5 uf-px-2 uf-text-xs uf-font-light uf-shrink-0 uf-border-0 uf-bg-transparent uf-cursor-pointer hover:uf-opacity-80 uf-transition-opacity",
52757
+ style: {
52758
+ color: colors2.error,
52759
+ fontFamily: fonts.regular
52760
+ },
52761
+ children: i18n2.connectExchange.disconnect
52762
+ }
52763
+ ) })
52764
+ ]
52765
+ }
52766
+ );
52767
+ }
52649
52768
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
52650
52769
  "button",
52651
52770
  {
@@ -52654,67 +52773,11 @@ function ConnectExchangeButton({
52654
52773
  onMouseLeave: () => setIsHovered(false),
52655
52774
  onTouchStart: () => setIsHovered(false),
52656
52775
  className: "uf-w-full uf-transition-colors uf-p-3 uf-flex uf-items-center uf-justify-between uf-group",
52657
- style: {
52658
- backgroundColor: isHovered ? colors2.cardHover : components.card.backgroundColor,
52659
- borderRadius: components.card.borderRadius,
52660
- border: `${components.card.borderWidth}px solid ${components.card.borderColor}`
52661
- },
52776
+ style: rowSurfaceStyle,
52662
52777
  children: [
52663
52778
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "uf-flex uf-items-center uf-gap-3", children: [
52664
- isConnected ? connectedExchange.iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52665
- "img",
52666
- {
52667
- src: connectedExchange.iconUrl,
52668
- alt: connectedExchange.name,
52669
- width: 36,
52670
- height: 36,
52671
- className: "uf-rounded-lg"
52672
- }
52673
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52674
- "div",
52675
- {
52676
- className: "uf-w-9 uf-h-9 uf-rounded-lg uf-flex uf-items-center uf-justify-center",
52677
- style: { backgroundColor: colors2.card },
52678
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52679
- "span",
52680
- {
52681
- className: "uf-text-xs uf-font-medium",
52682
- style: { color: components.card.iconColor },
52683
- children: connectedExchange.name.slice(0, 2).toUpperCase()
52684
- }
52685
- )
52686
- }
52687
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uf-rounded-lg uf-p-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52688
- Link2,
52689
- {
52690
- className: "uf-w-5 uf-h-5",
52691
- style: { color: components.card.iconColor }
52692
- }
52693
- ) }),
52694
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "uf-text-left", children: [
52695
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52696
- "div",
52697
- {
52698
- className: "uf-text-sm uf-font-light uf-mb-0.5",
52699
- style: {
52700
- color: components.card.titleColor,
52701
- fontFamily: fonts.regular
52702
- },
52703
- children: isConnected ? connectedExchange.name : title
52704
- }
52705
- ),
52706
- isConnected && connectedExchange.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "uf-h-3 uf-w-24 uf-bg-muted uf-rounded uf-animate-pulse" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
52707
- "div",
52708
- {
52709
- className: "uf-text-xs uf-font-light",
52710
- style: {
52711
- color: components.card.subtitleColor,
52712
- fontFamily: fonts.regular
52713
- },
52714
- children: isConnected ? connectedExchange.balanceUsd ? `$${connectedExchange.balanceUsd} \u2022 2 min` : "Deposit from exchange" : subtitle
52715
- }
52716
- )
52717
- ] })
52779
+ iconBlock,
52780
+ titleSubtitleBlock
52718
52781
  ] }),
52719
52782
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "uf-flex uf-items-center uf-gap-1.5", children: [
52720
52783
  !isConnected && exchanges && exchanges.length > 0 ? exchanges.slice(0, 4).map((ex, i) => {
@@ -55519,6 +55582,65 @@ function useProjectConfig({
55519
55582
  });
55520
55583
  return { projectConfig, isLoading };
55521
55584
  }
55585
+ function useSupportedDepositTokens(publishableKey, options2) {
55586
+ const hasDestination = options2?.destination_token_address && options2?.destination_chain_id && options2?.destination_chain_type;
55587
+ const filteredOptions = {
55588
+ ...hasDestination ? {
55589
+ destination_token_address: options2.destination_token_address,
55590
+ destination_chain_id: options2.destination_chain_id,
55591
+ destination_chain_type: options2.destination_chain_type
55592
+ } : {},
55593
+ ...options2?.product_type ? { product_type: options2.product_type } : {}
55594
+ };
55595
+ const hasFilteredOptions = Object.keys(filteredOptions).length > 0;
55596
+ return useQuery({
55597
+ queryKey: [
55598
+ "unifold",
55599
+ "supportedDepositTokens",
55600
+ publishableKey,
55601
+ filteredOptions?.destination_token_address ?? null,
55602
+ filteredOptions?.destination_chain_id ?? null,
55603
+ filteredOptions?.destination_chain_type ?? null,
55604
+ filteredOptions?.product_type ?? null
55605
+ ],
55606
+ queryFn: () => getSupportedDepositTokens(
55607
+ publishableKey,
55608
+ hasFilteredOptions ? filteredOptions : void 0
55609
+ ),
55610
+ staleTime: 1e3 * 60 * 5,
55611
+ // 5 minutes — token list rarely changes
55612
+ gcTime: 1e3 * 60 * 30,
55613
+ // 30 minutes in cache
55614
+ refetchOnMount: false,
55615
+ refetchOnWindowFocus: false
55616
+ });
55617
+ }
55618
+ function useIntegrationTransferDefaultToken({
55619
+ params,
55620
+ publishableKey,
55621
+ enabled = true
55622
+ }) {
55623
+ return useQuery({
55624
+ queryKey: [
55625
+ "unifold",
55626
+ "integrationTransferDefaultToken",
55627
+ publishableKey,
55628
+ params?.integration_provider ?? null,
55629
+ params?.source_currency ?? null,
55630
+ params?.destination_token_address ?? null,
55631
+ params?.destination_chain_id ?? null,
55632
+ params?.destination_chain_type ?? null,
55633
+ params?.country_code ?? null,
55634
+ params?.subdivision_code ?? null
55635
+ ],
55636
+ queryFn: () => getIntegrationTransferDefaultToken(params, publishableKey),
55637
+ enabled: enabled && !!params,
55638
+ staleTime: 1e3 * 60 * 5,
55639
+ gcTime: 1e3 * 60 * 30,
55640
+ refetchOnMount: false,
55641
+ refetchOnWindowFocus: false
55642
+ });
55643
+ }
55522
55644
  var INTEGRATIONS_STORAGE_KEY = "unifold:integrations:connected";
55523
55645
  function getStoredIntegrations() {
55524
55646
  try {
@@ -55563,6 +55685,23 @@ function CoinbaseConnect({
55563
55685
  const { colors: colors2, fonts, components } = useTheme();
55564
55686
  const { projectConfig } = useProjectConfig({ publishableKey });
55565
55687
  const { userIpInfo } = useUserIp2();
55688
+ const { data: supportedTokensData, isLoading: supportedTokensLoading } = useSupportedDepositTokens(publishableKey, {
55689
+ destination_token_address: destinationTokenAddress,
55690
+ destination_chain_id: destinationChainId,
55691
+ destination_chain_type: destinationChainType
55692
+ });
55693
+ const supportedSymbols = (0, import_react18.useMemo)(() => {
55694
+ const set = /* @__PURE__ */ new Set();
55695
+ supportedTokensData?.data.forEach((token) => set.add(token.symbol.toLowerCase()));
55696
+ return set;
55697
+ }, [supportedTokensData]);
55698
+ const stablecoinSymbols = (0, import_react18.useMemo)(() => {
55699
+ const set = /* @__PURE__ */ new Set();
55700
+ supportedTokensData?.data.forEach((token) => {
55701
+ if (token.is_stablecoin) set.add(token.symbol.toLowerCase());
55702
+ });
55703
+ return set;
55704
+ }, [supportedTokensData]);
55566
55705
  const appName = projectConfig?.project_name ?? "Unifold";
55567
55706
  const t12 = i18n2.connectExchange;
55568
55707
  const initialView = skipToHoldings && getStoredIntegrationToken(IntegrationProvider.COINBASE) ? "holdings" : "select_exchange";
@@ -55585,7 +55724,43 @@ function CoinbaseConnect({
55585
55724
  const [confirmResult, setConfirmResult] = (0, import_react18.useState)(null);
55586
55725
  const [showTransferDetails, setShowTransferDetails] = (0, import_react18.useState)(false);
55587
55726
  const [transferDepositWalletId, setTransferDepositWalletId] = (0, import_react18.useState)(void 0);
55588
- const [minDepositUsd, setMinDepositUsd] = (0, import_react18.useState)(0);
55727
+ const exchangeSupportedCurrencies = (0, import_react18.useMemo)(() => {
55728
+ const set = /* @__PURE__ */ new Set();
55729
+ selectedExchange?.supported_currencies.forEach((c) => set.add(c.toLowerCase()));
55730
+ return set;
55731
+ }, [selectedExchange]);
55732
+ const defaultTokenParams = (0, import_react18.useMemo)(
55733
+ () => selectedAsset ? {
55734
+ integration_provider: IntegrationProvider.COINBASE,
55735
+ source_currency: selectedAsset.currency.toLowerCase(),
55736
+ destination_token_address: destinationTokenAddress,
55737
+ destination_chain_id: destinationChainId,
55738
+ destination_chain_type: destinationChainType,
55739
+ country_code: userIpInfo?.alpha2,
55740
+ subdivision_code: userIpInfo?.subdivisionCode ?? void 0
55741
+ } : null,
55742
+ [selectedAsset, destinationTokenAddress, destinationChainId, destinationChainType, userIpInfo?.alpha2, userIpInfo?.subdivisionCode]
55743
+ );
55744
+ const { data: defaultTokenData, isLoading: defaultTokenLoading } = useIntegrationTransferDefaultToken({
55745
+ params: defaultTokenParams,
55746
+ publishableKey
55747
+ });
55748
+ const sortedHoldings = (0, import_react18.useMemo)(() => {
55749
+ const supported = [];
55750
+ const unsupported = [];
55751
+ holdings.forEach((account) => {
55752
+ const currencyLower = account.currency.toLowerCase();
55753
+ const isSupported = (supportedSymbols.size === 0 || supportedSymbols.has(currencyLower)) && (exchangeSupportedCurrencies.size === 0 || exchangeSupportedCurrencies.has(currencyLower));
55754
+ if (isSupported) supported.push(account);
55755
+ else unsupported.push(account);
55756
+ });
55757
+ return [...supported, ...unsupported];
55758
+ }, [holdings, supportedSymbols, exchangeSupportedCurrencies]);
55759
+ const selectedHoldingIsSupported = (0, import_react18.useMemo)(() => {
55760
+ if (!selectedHolding) return false;
55761
+ const currencyLower = selectedHolding.currency.toLowerCase();
55762
+ return (supportedSymbols.size === 0 || supportedSymbols.has(currencyLower)) && (exchangeSupportedCurrencies.size === 0 || exchangeSupportedCurrencies.has(currencyLower));
55763
+ }, [selectedHolding, supportedSymbols, exchangeSupportedCurrencies]);
55589
55764
  const exchangeName = selectedExchange?.service_provider_display_name || "Exchange";
55590
55765
  const {
55591
55766
  executions: depositExecutions,
@@ -55701,53 +55876,17 @@ function CoinbaseConnect({
55701
55876
  if (pollRef.current) clearInterval(pollRef.current);
55702
55877
  };
55703
55878
  }, []);
55704
- (0, import_react18.useEffect)(() => {
55705
- if (view !== "enter_amount" || !selectedAsset) return;
55706
- let cancelled = false;
55707
- const fetchMinDeposit = async () => {
55708
- try {
55709
- const [defaultToken, supportedTokens] = await Promise.all([
55710
- getIntegrationTransferDefaultToken(
55711
- {
55712
- integration_provider: IntegrationProvider.COINBASE,
55713
- source_currency: selectedAsset.currency.toLowerCase(),
55714
- destination_token_address: destinationTokenAddress,
55715
- destination_chain_id: destinationChainId,
55716
- destination_chain_type: destinationChainType,
55717
- country_code: userIpInfo?.alpha2,
55718
- subdivision_code: userIpInfo?.subdivisionCode ?? void 0
55719
- },
55720
- publishableKey
55721
- ),
55722
- getSupportedDepositTokens(publishableKey, {
55723
- destination_token_address: destinationTokenAddress,
55724
- destination_chain_id: destinationChainId,
55725
- destination_chain_type: destinationChainType
55726
- })
55727
- ]);
55728
- if (cancelled) return;
55729
- const supportedToken = supportedTokens.data.find(
55730
- (t13) => t13.symbol.toLowerCase() === selectedAsset.currency.toLowerCase()
55731
- );
55732
- if (supportedToken && supportedToken.chains.length > 0) {
55733
- const matchingChain = supportedToken.chains.find(
55734
- (c) => c.chain_name.toLowerCase() === defaultToken.source_network.toLowerCase()
55735
- );
55736
- setMinDepositUsd(
55737
- matchingChain?.minimum_deposit_amount_usd ?? Math.min(...supportedToken.chains.map((c) => c.minimum_deposit_amount_usd))
55738
- );
55739
- } else {
55740
- setMinDepositUsd(0);
55741
- }
55742
- } catch {
55743
- if (!cancelled) setMinDepositUsd(0);
55744
- }
55745
- };
55746
- fetchMinDeposit();
55747
- return () => {
55748
- cancelled = true;
55749
- };
55750
- }, [view, selectedAsset, publishableKey, destinationTokenAddress, destinationChainId, destinationChainType, userIpInfo?.alpha2, userIpInfo?.subdivisionCode]);
55879
+ const minDepositUsd = (0, import_react18.useMemo)(() => {
55880
+ if (!selectedAsset || !defaultTokenData) return 0;
55881
+ const supportedToken = supportedTokensData?.data.find(
55882
+ (t13) => t13.symbol.toLowerCase() === selectedAsset.currency.toLowerCase()
55883
+ );
55884
+ if (!supportedToken || supportedToken.chains.length === 0) return 0;
55885
+ const matchingChain = supportedToken.chains.find(
55886
+ (c) => c.chain_name.toLowerCase() === defaultTokenData.source_network.toLowerCase()
55887
+ );
55888
+ return matchingChain?.minimum_deposit_amount_usd ?? Math.min(...supportedToken.chains.map((c) => c.minimum_deposit_amount_usd));
55889
+ }, [selectedAsset, supportedTokensData, defaultTokenData]);
55751
55890
  const handleSelectExchange = (exchange) => {
55752
55891
  if (!exchange.enabled) return;
55753
55892
  setSelectedExchange(exchange);
@@ -55792,7 +55931,6 @@ function CoinbaseConnect({
55792
55931
  const handleSelectAsset = (asset) => {
55793
55932
  setSelectedAsset(asset);
55794
55933
  setSendAmount("");
55795
- setMinDepositUsd(0);
55796
55934
  transitionTo("enter_amount");
55797
55935
  };
55798
55936
  const handleCreateTransfer = async () => {
@@ -55804,18 +55942,10 @@ function CoinbaseConnect({
55804
55942
  const cryptoAmount = (usdAmount / rate).toFixed(8);
55805
55943
  setIsLoading(true);
55806
55944
  try {
55807
- const { source_network, source_chain_type } = await getIntegrationTransferDefaultToken(
55808
- {
55809
- integration_provider: IntegrationProvider.COINBASE,
55810
- source_currency: selectedAsset.currency.toLowerCase(),
55811
- destination_token_address: destinationTokenAddress,
55812
- destination_chain_id: destinationChainId,
55813
- destination_chain_type: destinationChainType,
55814
- country_code: userIpInfo?.alpha2,
55815
- subdivision_code: userIpInfo?.subdivisionCode ?? void 0
55816
- },
55817
- publishableKey
55818
- );
55945
+ if (!defaultTokenData) {
55946
+ throw new Error("Transfer details not ready. Please try again.");
55947
+ }
55948
+ const { source_network, source_chain_type } = defaultTokenData;
55819
55949
  const matchingWallet = wallets.find((w) => w.chain_type === source_chain_type);
55820
55950
  const depositAddress = matchingWallet?.address ?? recipientAddress ?? "";
55821
55951
  if (!depositAddress) {
@@ -55967,13 +56097,12 @@ function CoinbaseConnect({
55967
56097
  break;
55968
56098
  }
55969
56099
  };
55970
- const STABLECOIN_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT", "DAI", "BUSD", "TUSD", "USDP", "GUSD", "FRAX", "LUSD", "PYUSD", "EURC", "USDS"]);
55971
56100
  const formatCryptoAmount = (amount, currency) => {
55972
56101
  const num = parseFloat(amount);
55973
56102
  if (isNaN(num)) return `${amount} ${currency.toUpperCase()}`;
55974
- const isStable = STABLECOIN_SYMBOLS.has(currency.toUpperCase());
55975
- const maxDecimals = isStable ? 2 : 8;
55976
- return `${num.toLocaleString(void 0, { minimumFractionDigits: isStable ? 2 : 0, maximumFractionDigits: maxDecimals })} ${currency.toUpperCase()}`;
56103
+ const isStable = stablecoinSymbols.has(currency.toLowerCase());
56104
+ const maxDecimals = isStable ? 2 : 6;
56105
+ return `${num.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: maxDecimals })} ${currency.toUpperCase()}`;
55977
56106
  };
55978
56107
  const viewTransitionStyle = {
55979
56108
  opacity: isTransitioning ? 0 : 1,
@@ -56575,7 +56704,7 @@ function CoinbaseConnect({
56575
56704
  className: "uf-w-6 uf-h-6 uf-animate-spin",
56576
56705
  style: { color: colors2.foregroundMuted }
56577
56706
  }
56578
- ) }) : holdings.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uf-text-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
56707
+ ) }) : sortedHoldings.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "uf-text-center uf-py-8", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
56579
56708
  "div",
56580
56709
  {
56581
56710
  className: "uf-text-sm",
@@ -56585,17 +56714,19 @@ function CoinbaseConnect({
56585
56714
  },
56586
56715
  children: "No holdings found"
56587
56716
  }
56588
- ) }) : holdings.map((account) => {
56717
+ ) }) : sortedHoldings.map((account) => {
56589
56718
  const iconUrl = account.icon_urls?.find((u) => u.format === "svg")?.url || account.icon_urls?.find((u) => u.format === "png")?.url || account.icon_url;
56590
- const isSelected = selectedHolding?.id === account.id;
56719
+ const currencyLower = account.currency.toLowerCase();
56720
+ const isSupported = (supportedSymbols.size === 0 || supportedSymbols.has(currencyLower)) && (exchangeSupportedCurrencies.size === 0 || exchangeSupportedCurrencies.has(currencyLower));
56721
+ const isSelected = isSupported && selectedHolding?.id === account.id;
56591
56722
  const usdValue = account.amount_usd ? parseFloat(account.amount_usd) : null;
56592
- const formattedUsd = usdValue != null && usdValue > 0 ? `$${usdValue.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : null;
56723
+ const formattedUsd = usdValue != null && usdValue >= 0.01 ? `$${usdValue.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : usdValue != null && usdValue < 0.01 && usdValue > 0 ? "< $0.01" : "$0.00";
56593
56724
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
56594
56725
  "button",
56595
56726
  {
56596
56727
  onClick: () => setSelectedHolding(account),
56597
- disabled: isLoading,
56598
- className: "uf-w-full uf-transition-colors uf-p-3 uf-flex uf-items-center uf-justify-between",
56728
+ disabled: isLoading || !isSupported,
56729
+ className: `uf-w-full uf-transition-colors uf-p-3 uf-flex uf-items-center uf-justify-between ${!isSupported ? "uf-cursor-not-allowed uf-opacity-50" : ""}`,
56599
56730
  style: {
56600
56731
  backgroundColor: isSelected ? colors2.primary + "20" : components.card.backgroundColor,
56601
56732
  borderRadius: components.list.rowBorderRadius,
@@ -56685,9 +56816,9 @@ function CoinbaseConnect({
56685
56816
  "button",
56686
56817
  {
56687
56818
  onClick: () => {
56688
- if (selectedHolding) handleSelectAsset(selectedHolding);
56819
+ if (selectedHolding && selectedHoldingIsSupported) handleSelectAsset(selectedHolding);
56689
56820
  },
56690
- disabled: !selectedHolding || isLoading,
56821
+ disabled: !selectedHolding || isLoading || supportedTokensLoading || exchangesLoading || !selectedHoldingIsSupported,
56691
56822
  className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed",
56692
56823
  style: {
56693
56824
  backgroundColor: colors2.primary,
@@ -56711,22 +56842,16 @@ function CoinbaseConnect({
56711
56842
  const isValidAmount = inputUsdNum > 0 && (maxUsdAmount == null || inputUsdNum <= maxUsdAmount) && inputUsdNum >= minDepositUsd;
56712
56843
  const displayValue = sendAmount || "0";
56713
56844
  const formattedBalance = formatCryptoAmount(selectedAsset.amount, selectedAsset.currency);
56714
- const balanceDisplay = maxUsdAmount != null && maxUsdAmount > 0 ? `$${maxUsdAmount.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} (${formattedBalance})` : formattedBalance;
56845
+ const balanceSubtitle = maxUsdAmount != null && maxUsdAmount > 0 ? `Balance: $${maxUsdAmount.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} (${formattedBalance})` : `Balance: ${formattedBalance}`;
56715
56846
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
56716
56847
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
56717
56848
  DepositHeader,
56718
56849
  {
56719
56850
  title: "Enter Amount",
56851
+ subtitle: balanceSubtitle,
56720
56852
  showBack: true,
56721
56853
  onBack: handleBack,
56722
- onClose,
56723
- showBalance: true,
56724
- balanceAddress: recipientAddress,
56725
- balanceChainType: destinationChainType === "ethereum" || destinationChainType === "solana" || destinationChainType === "bitcoin" ? destinationChainType : void 0,
56726
- balanceChainId: destinationChainId,
56727
- balanceTokenAddress: destinationTokenAddress,
56728
- projectName: projectConfig?.project_name,
56729
- publishableKey
56854
+ onClose
56730
56855
  }
56731
56856
  ),
56732
56857
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "uf-flex uf-flex-col uf-pb-4", style: viewTransitionStyle, children: [
@@ -56848,7 +56973,7 @@ function CoinbaseConnect({
56848
56973
  "button",
56849
56974
  {
56850
56975
  onClick: handleCreateTransfer,
56851
- disabled: !isValidAmount || isLoading,
56976
+ disabled: !isValidAmount || isLoading || defaultTokenLoading || !defaultTokenData,
56852
56977
  className: "uf-w-full uf-py-3 uf-text-sm uf-font-medium uf-transition-colors disabled:uf-opacity-50 disabled:uf-cursor-not-allowed uf-flex uf-items-center uf-justify-center uf-gap-2",
56853
56978
  style: {
56854
56979
  backgroundColor: colors2.primary,
@@ -56857,7 +56982,7 @@ function CoinbaseConnect({
56857
56982
  borderRadius: components.button.borderRadius,
56858
56983
  border: `${components.button.borderWidth}px solid ${components.button.borderColor}`
56859
56984
  },
56860
- children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(LoaderCircle, { className: "uf-w-4 uf-h-4 uf-animate-spin" }) : "Review"
56985
+ children: isLoading || defaultTokenLoading ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(LoaderCircle, { className: "uf-w-4 uf-h-4 uf-animate-spin" }) : "Review"
56861
56986
  }
56862
56987
  )
56863
56988
  ] })
@@ -57490,39 +57615,6 @@ function useAddressValidation({
57490
57615
  error: error ?? null
57491
57616
  };
57492
57617
  }
57493
- function useSupportedDepositTokens(publishableKey, options2) {
57494
- const hasDestination = options2?.destination_token_address && options2?.destination_chain_id && options2?.destination_chain_type;
57495
- const filteredOptions = {
57496
- ...hasDestination ? {
57497
- destination_token_address: options2.destination_token_address,
57498
- destination_chain_id: options2.destination_chain_id,
57499
- destination_chain_type: options2.destination_chain_type
57500
- } : {},
57501
- ...options2?.product_type ? { product_type: options2.product_type } : {}
57502
- };
57503
- const hasFilteredOptions = Object.keys(filteredOptions).length > 0;
57504
- return useQuery({
57505
- queryKey: [
57506
- "unifold",
57507
- "supportedDepositTokens",
57508
- publishableKey,
57509
- filteredOptions?.destination_token_address ?? null,
57510
- filteredOptions?.destination_chain_id ?? null,
57511
- filteredOptions?.destination_chain_type ?? null,
57512
- filteredOptions?.product_type ?? null
57513
- ],
57514
- queryFn: () => getSupportedDepositTokens(
57515
- publishableKey,
57516
- hasFilteredOptions ? filteredOptions : void 0
57517
- ),
57518
- staleTime: 1e3 * 60 * 5,
57519
- // 5 minutes — token list rarely changes
57520
- gcTime: 1e3 * 60 * 30,
57521
- // 30 minutes in cache
57522
- refetchOnMount: false,
57523
- refetchOnWindowFocus: false
57524
- });
57525
- }
57526
57618
  function ThemeStyleInjector({
57527
57619
  children,
57528
57620
  className
@@ -59907,7 +59999,7 @@ function SelectTokenView({
59907
59999
  ),
59908
60000
  walletInfoProp ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-flex uf-w-full uf-justify-center uf-mb-6", children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(WalletWithNetworkBadge, { walletInfo: walletInfoProp }) }) : null,
59909
60001
  /* @__PURE__ */ (0, import_jsx_runtime69.jsxs)("div", { className: "uf-flex uf-min-h-0 uf-flex-1 uf-flex-col", children: [
59910
- /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-h-[220px] uf-shrink-0 uf-overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:uf-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-space-y-2", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-12", children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
60002
+ /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-h-[300px] uf-shrink-0 uf-overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:uf-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-space-y-2", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime69.jsx)("div", { className: "uf-flex uf-items-center uf-justify-center uf-py-12", children: /* @__PURE__ */ (0, import_jsx_runtime69.jsx)(
59911
60003
  LoaderCircle,
59912
60004
  {
59913
60005
  className: "uf-w-6 uf-h-6 uf-animate-spin",
@@ -62346,11 +62438,12 @@ function DepositModal({
62346
62438
  return sum + (isNaN(usd) ? 0 : usd);
62347
62439
  }, 0);
62348
62440
  const balanceUsd = totalUsd > 0 ? totalUsd.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : null;
62349
- setConnectedExchange((prev) => ({ ...prev, balanceUsd, isLoading: false }));
62441
+ setConnectedExchange((prev) => prev ? { ...prev, balanceUsd, isLoading: false } : null);
62350
62442
  };
62351
62443
  getIntegrationHoldings(IntegrationProvider.COINBASE, stored.access_token, publishableKey).then(processHoldings).catch(async () => {
62352
62444
  try {
62353
62445
  const refreshResult = await refreshIntegrationToken(stored.access_token, publishableKey);
62446
+ if (!getStoredIntegrationToken(IntegrationProvider.COINBASE)) return;
62354
62447
  setStoredIntegrationToken({
62355
62448
  integration_provider: IntegrationProvider.COINBASE,
62356
62449
  access_token: refreshResult.access_token,
@@ -62359,6 +62452,7 @@ function DepositModal({
62359
62452
  const retryResult = await getIntegrationHoldings(IntegrationProvider.COINBASE, refreshResult.access_token, publishableKey);
62360
62453
  processHoldings(retryResult);
62361
62454
  } catch {
62455
+ if (!getStoredIntegrationToken(IntegrationProvider.COINBASE)) return;
62362
62456
  clearStoredIntegrationToken(IntegrationProvider.COINBASE);
62363
62457
  setConnectedExchange(null);
62364
62458
  }
@@ -62526,6 +62620,14 @@ function DepositModal({
62526
62620
  setBrowserWalletInfo(null);
62527
62621
  setBrowserWalletModalOpen(false);
62528
62622
  };
62623
+ const handleExchangeDisconnect = () => {
62624
+ const stored = getStoredIntegrationToken(IntegrationProvider.COINBASE);
62625
+ if (stored) {
62626
+ revokeIntegrationToken(stored.access_token, publishableKey);
62627
+ }
62628
+ clearStoredIntegrationToken(IntegrationProvider.COINBASE);
62629
+ setConnectedExchange(null);
62630
+ };
62529
62631
  const handleClose = () => {
62530
62632
  onOpenChange(false);
62531
62633
  if (resetViewTimeoutRef.current) {
@@ -62731,6 +62833,7 @@ function DepositModal({
62731
62833
  setCoinbaseSkipToHoldings(true);
62732
62834
  setView("coinbase_connect");
62733
62835
  },
62836
+ onDisconnect: handleExchangeDisconnect,
62734
62837
  title: i18n2.connectExchange.title,
62735
62838
  subtitle: i18n2.connectExchange.subtitle,
62736
62839
  exchanges: integrationExchanges,
@@ -62935,34 +63038,37 @@ function DepositModal({
62935
63038
  ),
62936
63039
  depositPoweredByFooter
62937
63040
  ] })
62938
- ] }) : view === "coinbase_connect" ? /* @__PURE__ */ (0, import_jsx_runtime75.jsx)(
62939
- CoinbaseConnect,
62940
- {
62941
- publishableKey,
62942
- userId,
62943
- wallets,
62944
- recipientAddress,
62945
- destinationTokenAddress: destinationTokenAddress ?? "",
62946
- destinationChainId: destinationChainId ?? "",
62947
- destinationChainType: destinationChainType ?? "",
62948
- onTransferSuccess: (result) => {
62949
- onDepositSuccess?.({
62950
- message: "Transfer completed via Coinbase Connect",
62951
- transaction: result
62952
- });
62953
- },
62954
- onTransferError: (error) => {
62955
- onDepositError?.({
62956
- message: error.message,
62957
- error
62958
- });
62959
- },
62960
- onBack: handleBack,
62961
- onClose: handleClose,
62962
- skipToHoldings: coinbaseSkipToHoldings,
62963
- onExecutionsChange: setDepositExecutions
62964
- }
62965
- ) : view === "cashapp" ? /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)(import_jsx_runtime75.Fragment, { children: [
63041
+ ] }) : view === "coinbase_connect" ? /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("div", { className: "uf-flex uf-flex-col uf-gap-1.5", children: [
63042
+ /* @__PURE__ */ (0, import_jsx_runtime75.jsx)(
63043
+ CoinbaseConnect,
63044
+ {
63045
+ publishableKey,
63046
+ userId,
63047
+ wallets,
63048
+ recipientAddress,
63049
+ destinationTokenAddress: destinationTokenAddress ?? "",
63050
+ destinationChainId: destinationChainId ?? "",
63051
+ destinationChainType: destinationChainType ?? "",
63052
+ onTransferSuccess: (result) => {
63053
+ onDepositSuccess?.({
63054
+ message: "Transfer completed via Coinbase Connect",
63055
+ transaction: result
63056
+ });
63057
+ },
63058
+ onTransferError: (error) => {
63059
+ onDepositError?.({
63060
+ message: error.message,
63061
+ error
63062
+ });
63063
+ },
63064
+ onBack: handleBack,
63065
+ onClose: handleClose,
63066
+ skipToHoldings: coinbaseSkipToHoldings,
63067
+ onExecutionsChange: setDepositExecutions
63068
+ }
63069
+ ),
63070
+ depositPoweredByFooter
63071
+ ] }) : view === "cashapp" ? /* @__PURE__ */ (0, import_jsx_runtime75.jsxs)(import_jsx_runtime75.Fragment, { children: [
62966
63072
  /* @__PURE__ */ (0, import_jsx_runtime75.jsx)(
62967
63073
  DepositHeader,
62968
63074
  {