@xswap-link/sdk 0.14.1 → 0.15.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.global.js +92 -92
  3. package/dist/index.js +1799 -1725
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1752 -1681
  6. package/dist/index.mjs.map +1 -1
  7. package/localTheme.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/assets/icons/ChainlinkMarkIcon.tsx +4 -5
  10. package/src/assets/icons/CheckCircleThinIcon.tsx +22 -0
  11. package/src/assets/icons/ChevronDownThinIcon.tsx +39 -0
  12. package/src/assets/icons/ChevronDownWideThinIcon.tsx +18 -0
  13. package/src/assets/icons/ChevronsUpDownThinIcon.tsx +21 -0
  14. package/src/assets/icons/CloseIcon.tsx +3 -2
  15. package/src/assets/icons/ErrorCircleThinIcon.tsx +22 -0
  16. package/src/assets/icons/LinkExternalThinIcon.tsx +19 -0
  17. package/src/assets/icons/LoadingRingIcon.tsx +22 -0
  18. package/src/assets/icons/ProgressRingThinIcon.tsx +29 -0
  19. package/src/assets/icons/SearchThinIcon.tsx +15 -0
  20. package/src/assets/icons/SignThinIcon.tsx +15 -0
  21. package/src/assets/icons/WalletCheckThinIcon.tsx +24 -0
  22. package/src/assets/icons/index.ts +11 -0
  23. package/src/components/PoweredBy/index.tsx +1 -1
  24. package/src/components/Swap/Header/index.tsx +2 -4
  25. package/src/components/Swap/HistoryView/ActivityCard/index.tsx +110 -115
  26. package/src/components/Swap/HistoryView/index.tsx +10 -7
  27. package/src/components/Swap/ReorderButton/ReorderButton.tsx +4 -4
  28. package/src/components/Swap/SettingsView/Delivery/index.tsx +19 -17
  29. package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +19 -17
  30. package/src/components/Swap/SettingsView/Slippage/index.tsx +55 -49
  31. package/src/components/Swap/SwapView/ConfirmationView/TokenTiles/index.tsx +69 -0
  32. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Header/index.tsx +13 -25
  33. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Steps/index.tsx +65 -113
  34. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +19 -23
  35. package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +51 -74
  36. package/src/components/Swap/SwapView/FeesPanel/index.tsx +4 -4
  37. package/src/components/Swap/SwapView/SwapButton/index.tsx +0 -29
  38. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +2 -2
  39. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/ChainPicker/index.tsx +3 -3
  40. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +17 -16
  41. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +91 -40
  42. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +4 -4
  43. package/src/components/Swap/SwapView/SwapPanel/UsdValue/index.tsx +2 -2
  44. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +10 -16
  45. package/src/components/Swap/SwapView/SwapPanel/index.tsx +3 -3
  46. package/src/components/Swap/SwapView/WalletPicker/index.tsx +131 -139
  47. package/src/components/Swap/SwapView/index.tsx +11 -7
  48. package/src/components/Swap/WalletPickerView/index.tsx +65 -0
  49. package/src/components/Swap/index.tsx +23 -6
  50. package/src/components/ToggleButton/index.tsx +5 -7
  51. package/src/components/TokenLogo/index.tsx +1 -1
  52. package/src/components/TxModal/index.tsx +14 -4
  53. package/src/components/global.css +18 -1
  54. package/src/constants/index.ts +4 -0
  55. package/src/context/SwapProvider.tsx +106 -19
  56. package/src/utils/validation.ts +9 -4
  57. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon.tsx +0 -13
  58. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/SwapPanel/index.tsx +0 -72
  59. package/src/components/Swap/SwapView/ConfirmationView/TxResult/TokenItem/index.tsx +0 -25
@@ -1,4 +1,4 @@
1
- import { ChevronDownIcon, SearchIcon } from "@src/assets/icons";
1
+ import { ChevronDownThinIcon, SearchThinIcon } from "@src/assets/icons";
2
2
  import { useSwapContext } from "@src/context";
3
3
  import { SwapPanelType } from "@src/components/Swap/SwapView";
4
4
  import { Chain } from "@src/models";
@@ -56,14 +56,15 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
56
56
  return () => document.removeEventListener("mousedown", onPointerDown);
57
57
  }, []);
58
58
 
59
- const previewChains = chains.slice(0, 3);
59
+ // 2x2 icon cluster shown when no network is picked
60
+ const clusterChains = chains.slice(0, 4);
60
61
 
61
62
  return (
62
63
  <div className="relative" ref={containerRef}>
63
64
  <button
64
65
  type="button"
65
66
  onClick={() => setOpen((state) => !state)}
66
- className="flex items-center gap-2 h-12 px-3 py-2 rounded-lg bg-t_bg_tertiary bg-opacity-[0.04] hover:bg-opacity-10 transition-colors"
67
+ className="flex items-center gap-2.5 h-12 px-3 py-2 rounded-lg bg-t_bg_tertiary bg-opacity-[0.04] hover:bg-opacity-10 transition-colors"
67
68
  >
68
69
  {selectedChain ? (
69
70
  <img
@@ -72,13 +73,13 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
72
73
  className="w-5 h-5 rounded-full"
73
74
  />
74
75
  ) : (
75
- <div className="flex -space-x-1.5 shrink-0">
76
- {previewChains.map((chain) => (
76
+ <div className="grid grid-cols-2 gap-0.5 shrink-0">
77
+ {clusterChains.map((chain) => (
77
78
  <img
78
79
  key={chain.chainId}
79
80
  src={chain.image}
80
81
  alt={chain.displayName}
81
- className="w-5 h-5 rounded-full border border-solid border-t_bg_primary bg-t_bg_primary"
82
+ className="w-[11px] h-[11px] rounded-full"
82
83
  />
83
84
  ))}
84
85
  </div>
@@ -89,19 +90,19 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
89
90
  : `${chains.length} Networks`}
90
91
  </p>
91
92
  <div
92
- className={`flex items-center justify-center w-6 h-6 fill-t_text_primary opacity-50 transition-transform [&_svg]:w-3 [&_svg]:h-3 ${
93
+ className={`flex items-center justify-center w-6 h-6 text-t_text_primary opacity-50 transition-transform ${
93
94
  open ? "rotate-180" : ""
94
95
  }`}
95
96
  >
96
- <ChevronDownIcon />
97
+ <ChevronDownThinIcon />
97
98
  </div>
98
99
  </button>
99
100
 
100
101
  {open && (
101
102
  <div className="absolute right-0 top-[calc(100%+8px)] z-20 flex flex-col w-[220px] max-h-[280px] rounded-xl bg-t_bg_primary border border-solid border-t_text_primary border-opacity-10 backdrop-blur-[24px] overflow-hidden shadow-lg">
102
103
  <div className="flex items-center gap-2 px-4 py-3 border-b border-solid border-t_text_primary border-opacity-10">
103
- <div className="fill-t_text_primary opacity-50 [&_svg]:w-3.5 [&_svg]:h-3.5 flex items-center">
104
- <SearchIcon />
104
+ <div className="text-t_text_primary opacity-60 [&_svg]:w-3.5 [&_svg]:h-3.5 flex items-center">
105
+ <SearchThinIcon />
105
106
  </div>
106
107
  <input
107
108
  value={searchValue}
@@ -134,13 +135,13 @@ export const NetworkFilter = ({ type, selectedChain, onSelect }: Props) => {
134
135
  className="w-5 h-5 rounded-full"
135
136
  />
136
137
  ) : (
137
- <div className="flex -space-x-1 shrink-0">
138
- {previewChains.map((previewChain) => (
138
+ <div className="grid grid-cols-2 gap-0.5 shrink-0">
139
+ {clusterChains.map((clusterChain) => (
139
140
  <img
140
- key={previewChain.chainId}
141
- src={previewChain.image}
142
- alt={previewChain.displayName}
143
- className="w-4 h-4 rounded-full border border-solid border-t_bg_primary bg-t_bg_primary"
141
+ key={clusterChain.chainId}
142
+ src={clusterChain.image}
143
+ alt={clusterChain.displayName}
144
+ className="w-[11px] h-[11px] rounded-full"
144
145
  />
145
146
  ))}
146
147
  </div>
@@ -1,6 +1,6 @@
1
1
  import { faCheck } from "@fortawesome/free-solid-svg-icons";
2
2
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
- import { CloseIcon, CopyIcon, SearchIcon } from "@src/assets/icons";
3
+ import { CloseIcon, CopyIcon, SearchThinIcon } from "@src/assets/icons";
4
4
  import {
5
5
  Button,
6
6
  Spinner,
@@ -20,7 +20,7 @@ import {
20
20
  } from "@src/utils";
21
21
  import { BigNumber, ethers } from "ethers";
22
22
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
- import { GroupedVirtuoso } from "react-virtuoso";
23
+ import { Virtuoso } from "react-virtuoso";
24
24
  import { NetworkFilter } from "./NetworkFilter";
25
25
  import { TokenItem } from "./TokenItem";
26
26
  import { useAccount } from "wagmi";
@@ -55,7 +55,11 @@ export const TokenPicker = ({
55
55
  const [loadingCustomTokenData, setLoadingCustomTokenData] = useState(false);
56
56
  const [customTokenData, setCustomTokenData] =
57
57
  useState<ImportedTokenData | null>(null);
58
- const [networkFilter, setNetworkFilter] = useState<Chain | undefined>(chain);
58
+ // Always opens on "All Networks" — the cross-chain token list is the
59
+ // default view even when a chain is already set on the swap panel.
60
+ const [networkFilter, setNetworkFilter] = useState<Chain | undefined>(
61
+ undefined,
62
+ );
59
63
  const currentRouteRequestNonce = useRef<number>(0);
60
64
  const tokenSearchRef = useRef<HTMLInputElement | null>(null);
61
65
 
@@ -295,8 +299,7 @@ export const TokenPicker = ({
295
299
 
296
300
  useEffect(() => {
297
301
  setSearchValue("");
298
- setNetworkFilter(chain);
299
- // eslint-disable-next-line react-hooks/exhaustive-deps
302
+ setNetworkFilter(undefined);
300
303
  }, [isOpen]);
301
304
 
302
305
  const isMatch = (value, searchValue) =>
@@ -422,11 +425,50 @@ export const TokenPicker = ({
422
425
  : [{ label: "RECOMMENDED.", tokens: recommendedTokens }];
423
426
  }, [ownedTokens, recommendedTokens]);
424
427
 
425
- const allTokens = useMemo(
426
- () => tokenGroups.flatMap((group) => group.tokens),
428
+ // The current group's label renders OUTSIDE the scroll container (pinned
429
+ // above it, transparent bg), so rows physically clip on the label's bottom
430
+ // edge — the scroller's top edge. Labels of the following groups stay
431
+ // in-flow and take over the pinned slot once they reach that line.
432
+ const listRows = useMemo(
433
+ () =>
434
+ tokenGroups.flatMap((group, groupIndex) => {
435
+ const rows: (
436
+ | { kind: "label"; label: string; groupLabel: string }
437
+ | { kind: "empty"; groupLabel: string }
438
+ | { kind: "token"; token: TokenOption; groupLabel: string }
439
+ )[] =
440
+ groupIndex === 0
441
+ ? []
442
+ : [
443
+ {
444
+ kind: "label",
445
+ label: group.label,
446
+ groupLabel: group.label,
447
+ },
448
+ ];
449
+ if (group.tokens.length) {
450
+ rows.push(
451
+ ...group.tokens.map((token) => ({
452
+ kind: "token" as const,
453
+ token,
454
+ groupLabel: group.label,
455
+ })),
456
+ );
457
+ } else {
458
+ rows.push({ kind: "empty", groupLabel: group.label });
459
+ }
460
+ return rows;
461
+ }),
427
462
  [tokenGroups],
428
463
  );
429
464
 
465
+ // Index of the topmost (partially) visible row, reported by Virtuoso.
466
+ const [topRowIndex, setTopRowIndex] = useState(0);
467
+ const pinnedGroupLabel =
468
+ listRows[Math.min(topRowIndex, listRows.length - 1)]?.groupLabel ??
469
+ tokenGroups[0]?.label ??
470
+ "";
471
+
430
472
  useEffect(() => {
431
473
  if (isOpen && tokenSearchRef.current) {
432
474
  setTimeout(() => {
@@ -545,8 +587,8 @@ export const TokenPicker = ({
545
587
 
546
588
  <div className="flex items-center justify-between gap-2 h-14 pl-4 pr-1 rounded-xl bg-t_bg_tertiary bg-opacity-[0.03] border border-solid border-t_text_primary border-opacity-5 mb-6">
547
589
  <div className="flex items-center gap-2 flex-1 min-w-0">
548
- <div className="fill-t_text_primary opacity-50 [&_svg]:w-4 [&_svg]:h-4 flex items-center shrink-0">
549
- <SearchIcon />
590
+ <div className="text-t_text_primary opacity-60 flex items-center shrink-0">
591
+ <SearchThinIcon />
550
592
  </div>
551
593
  <input
552
594
  ref={tokenSearchRef}
@@ -603,45 +645,54 @@ export const TokenPicker = ({
603
645
  </div>
604
646
  </div>
605
647
  ) : (
606
- <div className="flex flex-col h-full">
607
- <GroupedVirtuoso
608
- style={{ maxHeight: "700px", height: "100%" }}
609
- groupCounts={tokenGroups.map((group) => group.tokens.length)}
610
- groupContent={(index) => (
611
- // Sticky section label: a near-opaque, blurred, widget-glass-colored
612
- // band rows dissolve under it while scrolling instead
613
- // of showing through, and the band still picks up a hint
614
- // of the page glow like the glass around it.
615
- <div className="backdrop-blur-xl bg-gradient-to-r from-t_bg_secondary/40 to-t_bg_secondary/40 bg-t_bg_primary bg-opacity-75">
616
- <p className="pb-3 text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left">
617
- {tokenGroups[index]?.label}
618
- </p>
619
- {!tokenGroups[index]?.tokens.length && (
620
- <div className="text-center py-1 pb-3 text-sm text-t_text_primary text-opacity-50">
621
- No token found
622
- </div>
623
- )}
624
- </div>
625
- )}
626
- itemContent={(index) => {
627
- const token = allTokens[index];
628
- return (
629
- token && (
648
+ <div className="flex flex-col h-full min-h-0">
649
+ {/* Pinned label of the group at the top of the list; sits
650
+ above the scroller, so rows clip on its bottom edge. */}
651
+ <p className="pb-3 text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left shrink-0">
652
+ {pinnedGroupLabel}
653
+ </p>
654
+ <div className="relative flex flex-col flex-1 min-h-0">
655
+ <Virtuoso
656
+ style={{ maxHeight: "700px", height: "100%" }}
657
+ totalCount={listRows.length}
658
+ rangeChanged={({ startIndex }) => setTopRowIndex(startIndex)}
659
+ itemContent={(index) => {
660
+ const row = listRows[index];
661
+ if (!row) {
662
+ return null;
663
+ }
664
+ if (row.kind === "label") {
665
+ return (
666
+ <p className="pt-2 pb-3 text-[10px] font-medium leading-4 tracking-[0.015em] text-t_text_primary text-opacity-30 text-left">
667
+ {row.label}
668
+ </p>
669
+ );
670
+ }
671
+ if (row.kind === "empty") {
672
+ return (
673
+ <div className="text-center py-1 pb-3 text-sm text-t_text_primary text-opacity-50">
674
+ No token found
675
+ </div>
676
+ );
677
+ }
678
+ return (
630
679
  <TokenItem
631
680
  panelType={type}
632
- token={token}
681
+ token={row.token}
633
682
  selectedChainId={chain?.chainId}
634
683
  selectedTokenAddress={selectedTokenAddress}
635
- resetWarning={getResetWarning(token)}
684
+ resetWarning={getResetWarning(row.token)}
636
685
  onClick={() => {
637
- onSelect(token);
686
+ onSelect(row.token);
638
687
  setModalOpen(false);
639
688
  }}
640
689
  />
641
- )
642
- );
643
- }}
644
- />
690
+ );
691
+ }}
692
+ />
693
+ {/* Scroll fade — rows dissolve towards the widget bottom */}
694
+ <div className="pointer-events-none absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-t_bg_secondary/65 to-t_bg_secondary/0" />
695
+ </div>
645
696
  </div>
646
697
  )}
647
698
  </>
@@ -1,4 +1,4 @@
1
- import { ChevronDownIcon } from "@src/assets/icons";
1
+ import { ChevronDownThinIcon } from "@src/assets/icons";
2
2
  import { TokenLogoWithChain } from "@src/components";
3
3
  import { useSwapContext } from "@src/context";
4
4
  import { Chain, Token } from "@src/models";
@@ -50,7 +50,7 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
50
50
  onClick={() => {
51
51
  setActivePicker(type);
52
52
  }}
53
- className={`flex items-center gap-1 shrink-0 rounded-lg ${
53
+ className={`flex items-center shrink-0 min-h-11 rounded-lg ${
54
54
  locked ? "" : "hover:opacity-80 transition-opacity"
55
55
  } ${className || ""}`}
56
56
  >
@@ -86,8 +86,8 @@ export const TokenPanel = ({ chain, token, type, className }: Props) => {
86
86
  )}
87
87
  </div>
88
88
  {!locked && (
89
- <div className="flex items-center justify-center w-6 h-6 fill-t_text_primary opacity-50 [&_svg]:w-3 [&_svg]:h-3">
90
- <ChevronDownIcon />
89
+ <div className="flex items-center justify-center w-6 h-6 text-t_text_primary opacity-50">
90
+ <ChevronDownThinIcon />
91
91
  </div>
92
92
  )}
93
93
  </button>
@@ -16,11 +16,11 @@ export const UsdValue = ({ type }: Props) => {
16
16
  );
17
17
 
18
18
  if (type === "destination" && isFetchingRoute) {
19
- return <Skeleton className="w-[80px] h-[16px]" />;
19
+ return <Skeleton className="w-[80px] h-[16px] my-1" />;
20
20
  }
21
21
 
22
22
  return (
23
- <p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50 whitespace-nowrap">
23
+ <p className="py-1 text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50 whitespace-nowrap">
24
24
  {valueUsd ? `${valueUsd} USD` : ""}
25
25
  </p>
26
26
  );
@@ -1,4 +1,4 @@
1
- import { ChevronDownIcon } from "@src/assets/icons";
1
+ import { ChevronDownThinSmallIcon } from "@src/assets/icons";
2
2
  import { useWallet } from "@src/context/WalletProvider";
3
3
  import { useSwapContext } from "@src/context";
4
4
  import { SwapPanelType } from "../..";
@@ -27,19 +27,18 @@ export const WalletPanel = ({ chain, className }: Props) => {
27
27
  const openEvmWalletModal = useCallback(() => {
28
28
  // Mirror SwapButton's contract: when the integrator supplied their own
29
29
  // connect handler and did not opt into the built-in picker, defer to it
30
- // (e.g. the host dapp's RainbowKit modal) instead of opening the SDK picker.
30
+ // in every state — the host decides whether to show its connect modal or
31
+ // its account modal (e.g. RainbowKit) for an already-connected wallet.
31
32
  if (!integrationConfig.defaultWalletPicker && hasOnConnectWalletCallback) {
32
33
  onConnectWallet();
33
34
  return;
34
35
  }
35
36
  setOpenEVMWalletModal(true);
36
- evm.connectWallet();
37
37
  }, [
38
38
  integrationConfig.defaultWalletPicker,
39
39
  hasOnConnectWalletCallback,
40
40
  onConnectWallet,
41
41
  setOpenEVMWalletModal,
42
- evm,
43
42
  ]);
44
43
 
45
44
  const openWalletModal = useCallback(() => {
@@ -50,17 +49,10 @@ export const WalletPanel = ({ chain, className }: Props) => {
50
49
  openEvmWalletModal();
51
50
  } else if (chain.ecosystem === "solana") {
52
51
  setOpenSolanaWalletModal(true);
53
- solana.connectWallet();
54
52
  } else if (chain.ecosystem === "sui") {
55
53
  setOpenSuiWalletModal(true);
56
54
  }
57
- }, [
58
- chain,
59
- openEvmWalletModal,
60
- solana,
61
- setOpenSolanaWalletModal,
62
- setOpenSuiWalletModal,
63
- ]);
55
+ }, [chain, openEvmWalletModal, setOpenSolanaWalletModal, setOpenSuiWalletModal]);
64
56
 
65
57
  const address = useMemo(() => {
66
58
  if (!chain) {
@@ -76,23 +68,25 @@ export const WalletPanel = ({ chain, className }: Props) => {
76
68
  return "";
77
69
  }, [chain, evm, solana, sui]);
78
70
 
71
+ // Reserve the button's height before the chain loads so the card
72
+ // doesn't grow when the wallet button appears.
79
73
  if (!chain) {
80
- return null;
74
+ return <div className="h-6" aria-hidden />;
81
75
  }
82
76
 
83
77
  return (
84
78
  <button
85
79
  type="button"
86
80
  onClick={openWalletModal}
87
- className={`flex items-center justify-center gap-0.5 px-2 py-1 rounded bg-t_bg_tertiary bg-opacity-5 hover:bg-opacity-10 transition-colors ${
81
+ className={`flex items-center justify-center px-2 py-1 rounded bg-t_bg_tertiary bg-opacity-5 hover:bg-opacity-10 transition-colors ${
88
82
  className || ""
89
83
  }`}
90
84
  >
91
85
  <p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary whitespace-nowrap">
92
86
  {address ? shortAddress(address) : "Connect"}
93
87
  </p>
94
- <div className="flex items-center justify-center w-4 h-4 fill-t_text_primary opacity-50 [&_svg]:w-2.5 [&_svg]:h-2.5">
95
- <ChevronDownIcon />
88
+ <div className="flex items-center justify-center w-4 h-4 text-t_text_primary opacity-50">
89
+ <ChevronDownThinSmallIcon />
96
90
  </div>
97
91
  </button>
98
92
  );
@@ -25,13 +25,13 @@ export const SwapPanel = ({ type }: Props) => {
25
25
 
26
26
  return (
27
27
  <div
28
- className={`flex flex-col gap-2 px-4 sm:px-6 py-4 rounded-2xl bg-t_bg_secondary bg-opacity-60 ${
28
+ className={`flex flex-col gap-2 px-4 sm:px-6 py-4 rounded-2xl backdrop-blur-[12px] ${
29
29
  type === "source"
30
30
  ? "bg-gradient-to-r from-t_bg_tertiary/5 to-t_bg_tertiary/5 border border-solid border-t_text_primary border-opacity-25"
31
31
  : "bg-gradient-to-r from-t_bg_tertiary/[0.03] to-t_bg_tertiary/[0.03]"
32
32
  }`}
33
33
  >
34
- <div className="flex gap-2 items-center w-full">
34
+ <div className="flex gap-2 items-center w-full min-h-6">
35
35
  <p className="text-[10px] leading-4 tracking-[0.01em] text-t_text_primary text-opacity-50">
36
36
  {type === "source" ? "Source" : "Destination"}
37
37
  </p>
@@ -41,7 +41,7 @@ export const SwapPanel = ({ type }: Props) => {
41
41
  <AmountPanel type={type} />
42
42
  <TokenPanel chain={chain} token={token} type={type} />
43
43
  </div>
44
- <div className="flex items-center justify-between w-full gap-4">
44
+ <div className="flex items-center justify-between w-full gap-4 min-h-6">
45
45
  <UsdValue type={type} />
46
46
  <Balance type={type} />
47
47
  </div>