From da754158a1e6347308457f995d3cfb80c1a1c09b Mon Sep 17 00:00:00 2001 From: david-sun-venus Date: Wed, 24 Dec 2025 20:02:59 +0800 Subject: [PATCH 1/5] chore: tailwindcss v4 upgrade (#5152) * build(evm,ui): upgrade tailwind to v4, update mui theme * refactor(evm): tailwind v4 classNames migration (perf by upgrade tool) * refactor(evm): unnecessary className changes done by upgrade tool * build: update yarn lock * fix(evm): modify unichain-theme class in html tag * fix(evm,ui): update configs and fix ui glitches, revert unnecessary className changes * refactor(landing,ui): upgrade tailwind to v4 in landing app * feat: add pointer to buttons + refactor Delimiter * style(landing): fix style differences, remove normalize.css * build(none): update yarn.lock and changeset * build(evm,landing): fix cicd check/lint errors * fix: opacity class + add chain ID to transaction history --------- Co-authored-by: therealemjy --- .changeset/chatty-geese-float.md | 8 + apps/evm/package.json | 22 +- apps/evm/postcss.config.js | 6 - apps/evm/src/App/ThemeHandler/index.tsx | 4 +- apps/evm/src/assets/styles/components.css | 0 apps/evm/src/assets/styles/index.css | 128 ++--- .../useGetAccountTransactionHistory.ts | 10 +- apps/evm/src/components/Accordion/index.tsx | 2 +- .../Apy/PrimeBadge/PrimeApy/index.tsx | 2 +- apps/evm/src/components/Carousel/index.tsx | 4 +- apps/evm/src/components/Delimiter/index.tsx | 2 +- apps/evm/src/components/EModeIcon/index.tsx | 2 +- .../src/components/HealthFactorPill/index.tsx | 2 +- .../src/components/MarkdownEditor/index.tsx | 4 +- apps/evm/src/components/Modal/index.tsx | 2 +- apps/evm/src/components/Notice/index.tsx | 2 +- apps/evm/src/components/ProgressBar/styles.ts | 6 + apps/evm/src/components/Select/index.tsx | 4 +- apps/evm/src/components/Slider/index.tsx | 2 +- apps/evm/src/components/Tabs/index.tsx | 4 +- apps/evm/src/components/TextField/index.tsx | 2 +- .../src/components/TokenListWrapper/index.tsx | 2 +- apps/evm/src/components/Tooltip/index.tsx | 2 +- .../AddTokenToWalletButton/index.tsx | 2 +- .../containers/CopyAddressButton/index.tsx | 2 +- .../containers/ImportablePositions/index.tsx | 2 +- .../src/containers/Layout/Footer/index.tsx | 2 +- .../Layout/Header/MarketInfo/index.tsx | 12 +- .../TopBar/XsControls/NavLink/index.tsx | 2 +- .../Layout/Header/TopBar/XsControls/index.tsx | 2 +- .../src/containers/Layout/Header/index.tsx | 2 +- .../containers/Layout/ScrollToTop/index.tsx | 4 +- .../Layout/Sidebar/NavLink/index.tsx | 2 +- .../ReadableActionSignature/index.tsx | 2 +- apps/evm/src/containers/SwapDetails/index.tsx | 2 +- .../NotificationCenter/index.tsx | 2 +- .../Positions/Tables/EModeHeader/index.tsx | 2 +- .../src/pages/Account/PrimeBanner/index.tsx | 5 +- apps/evm/src/pages/Bridge/index.tsx | 2 +- .../ActionAccordion/index.tsx | 7 +- .../Status/Warning/index.tsx | 2 +- .../GovernanceProposal/Status/index.tsx | 2 +- apps/evm/src/pages/Governance/index.tsx | 2 +- .../src/pages/Market/AssetWarning/index.tsx | 2 +- .../evm/src/pages/Market/MarketCard/index.tsx | 2 +- .../Market/OperationForm/BoostForm/index.tsx | 2 +- .../Repay/RepayWithCollateralForm/index.tsx | 2 +- .../EMode/EModeGroup/EModeGroupCard/index.tsx | 2 +- .../src/pages/Proposal/Description/index.tsx | 2 +- apps/evm/tailwind.config.ts | 17 - apps/evm/vite.config.mts | 3 +- apps/landing/package.json | 20 +- apps/landing/postcss.config.js | 6 - apps/landing/src/assets/styles/index.css | 67 ++- .../src/components/Layout/Footer/Footer.tsx | 2 +- .../Layout/Header/Header.module.css | 4 +- .../src/components/Layout/Header/Header.tsx | 2 +- .../src/components/Legal/index.module.css | 10 +- apps/landing/src/components/Legal/index.tsx | 6 +- apps/landing/src/index.tsx | 1 - .../pages/Home/Benefits/Benefits.module.css | 6 +- .../Home/Governance/Governance.module.css | 4 +- .../src/pages/Home/Governance/index.tsx | 4 +- .../src/pages/Home/Intro/Intro.module.css | 4 +- .../src/pages/Home/Market/Market.module.css | 21 +- apps/landing/src/pages/Home/Market/Market.tsx | 8 +- .../Home/Protection/Protection.module.css | 4 +- .../src/pages/Home/Protection/index.tsx | 10 +- .../landing/src/pages/Home/Safety/Auditor.tsx | 2 +- .../src/pages/Home/Safety/Safety.module.css | 8 +- .../Home/VenusPrime/VenusPrime.module.css | 2 +- .../src/pages/Home/VenusPrime/index.tsx | 4 +- .../landing/src/pages/PrivacyPolicy/index.tsx | 120 ++--- apps/landing/src/pages/TermsOfUse/index.tsx | 228 ++++---- apps/landing/tailwind.config.ts | 25 - apps/landing/vite.config.mts | 3 +- packages/ui/package.json | 8 +- packages/ui/src/components/Button/index.tsx | 2 +- packages/ui/src/theme.css | 106 +++- packages/ui/src/theme.ts | 26 +- yarn.lock | 489 ++++++++++-------- 81 files changed, 820 insertions(+), 696 deletions(-) create mode 100644 .changeset/chatty-geese-float.md delete mode 100644 apps/evm/postcss.config.js create mode 100644 apps/evm/src/assets/styles/components.css delete mode 100644 apps/evm/tailwind.config.ts delete mode 100644 apps/landing/postcss.config.js delete mode 100644 apps/landing/tailwind.config.ts diff --git a/.changeset/chatty-geese-float.md b/.changeset/chatty-geese-float.md new file mode 100644 index 0000000000..e7e9cbeab2 --- /dev/null +++ b/.changeset/chatty-geese-float.md @@ -0,0 +1,8 @@ +--- +"@venusprotocol/chains": minor +"@venusprotocol/landing": minor +"@venusprotocol/ui": minor +"@venusprotocol/evm": minor +--- + +tailwind css upgrade to v4 diff --git a/apps/evm/package.json b/apps/evm/package.json index 02e87bf3ad..a7f7663ef8 100644 --- a/apps/evm/package.json +++ b/apps/evm/package.json @@ -41,6 +41,7 @@ "@rhinestone/module-sdk": "0.2.7", "@sentry/react": "^10.19.0", "@sentry/vite-plugin": "^4.0.1", + "@tailwindcss/vite": "^4.1.18", "@tanstack/react-query": "^5.48.0", "@venusprotocol/chains": "*", "@venusprotocol/ui": "*", @@ -48,7 +49,6 @@ "@yornaath/batshit": "^0.12.0", "bignumber.js": "^9.1.1", "buffer": "^6.0.3", - "clsx": "^2.0.0", "compare-versions": "^6.1.0", "connectkit": "^1.8.2", "copy-to-clipboard": "^3.3.3", @@ -75,7 +75,7 @@ "react-router": "^7.6.0", "react-uid": "^2.3.3", "recharts": "2.15.3", - "tailwind-merge": "^3.1.0", + "tailwindcss": "^4.1.18", "viem": "^2.23.1", "wagmi": "^2.15.6", "yup": "^1.2.0", @@ -123,7 +123,6 @@ "@venusprotocol/venus-protocol": "10.0.0", "@vitejs/plugin-react": "^4.3.3", "@vitest/coverage-v8": "^2.1.5", - "autoprefixer": "^10.4.16", "c8": "^10.0.0", "fs": "^0.0.1-security", "genversion": "^3.1.1", @@ -144,11 +143,10 @@ "rollup-plugin-visualizer": "^6.0.1", "storybook": "^8.6.14", "stylelint": "^15.10.3", - "tailwind-scrollbar": "3.1.0", - "tailwindcss": "^3.3.3", - "tailwindcss-animate": "^1.0.7", + "tailwind-scrollbar": "^4.0.2", "tsconfig-paths": "^4.2.0", "tsx": "^4.1.2", + "tw-animate-css": "^1.4.0", "typescript": "^5.1.6", "vite": "^6.2.5", "vite-plugin-svgr": "^4.3.0", @@ -157,15 +155,7 @@ "whatwg-fetch": "^3.6.18" }, "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "production": [">0.2%", "not dead", "not op_mini all"], + "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] } } diff --git a/apps/evm/postcss.config.js b/apps/evm/postcss.config.js deleted file mode 100644 index 12a703d900..0000000000 --- a/apps/evm/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/evm/src/App/ThemeHandler/index.tsx b/apps/evm/src/App/ThemeHandler/index.tsx index dff8a7fbda..0762336755 100644 --- a/apps/evm/src/App/ThemeHandler/index.tsx +++ b/apps/evm/src/App/ThemeHandler/index.tsx @@ -7,10 +7,10 @@ export const ThemeHandler: React.FC = () => { // Change theme based on active chain useEffect(() => { - document.body.classList.remove('unichain-theme'); + document.documentElement.classList.remove('unichain-theme'); if (isOnUnichain) { - document.body.classList.add('unichain-theme'); + document.documentElement.classList.add('unichain-theme'); } }, [isOnUnichain]); diff --git a/apps/evm/src/assets/styles/components.css b/apps/evm/src/assets/styles/components.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/evm/src/assets/styles/index.css b/apps/evm/src/assets/styles/index.css index 10902922c2..35a9917116 100755 --- a/apps/evm/src/assets/styles/index.css +++ b/apps/evm/src/assets/styles/index.css @@ -1,76 +1,84 @@ -@import '@venusprotocol/ui/fonts/proximaNova'; -@import '@venusprotocol/ui/theme'; +@import 'tailwindcss' source("../../../src"); -@tailwind base; -@tailwind components; -@tailwind utilities; +/* let tailwindcss include ui components */ +@source "../../../../../packages/ui"; -@layer utilities { - .scrollbar-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - } +@import "tw-animate-css"; +@plugin 'tailwind-scrollbar'; - .scrollbar-hidden::-webkit-scrollbar { - display: none; +@import '@venusprotocol/ui/fonts/proximaNova' layer(base); +@import '@venusprotocol/ui/theme' layer(theme); + +@layer base { + body { + font-family: var(--font-proxima-nova), sans-serif; + background-color: var(--color-background); + color: var(--color-offWhite); } -} -body { - font-family: var(--font-proxima-nova), sans-serif; - background-color: theme('colors.background'); - color: theme('colors.offWhite'); -} + * { + &::-webkit-scrollbar { + width: 4px; + } -input { - color: inherit; + /* Track */ + &::-webkit-scrollbar-track { + border-radius: 4px; + background-color: var(--color-background); + } - &::-webkit-input-placeholder { - /* Chrome/Opera/Safari */ - padding-right: 22px; - } - &::-moz-placeholder { - /* Firefox 19+ */ - padding-right: 22px; - } - &:-moz-placeholder { - /* Firefox 18- */ - padding-right: 22px; - } + /* Handle */ + &::-webkit-scrollbar-thumb { + background-color: var(--color-grey); + border-radius: 4px; + } - /* Hide arrows from input number */ - /* Chrome, Safari, Edge, Opera */ - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - /* Firefox */ - &[type='number'] { - -moz-appearance: textfield; + /* Handle on hover */ + &::-webkit-scrollbar-thumb:hover { + background-color: var(--color-offWhite); + } } } -* { - &::-webkit-scrollbar { - width: 4px; - } +@layer components { + input { + color: inherit; - /* Track */ - &::-webkit-scrollbar-track { - border-radius: 4px; - background-color: theme('colors.background'); - } + &::-webkit-input-placeholder { + /* Chrome/Opera/Safari */ + padding-right: 22px; + } - /* Handle */ - &::-webkit-scrollbar-thumb { - background-color: theme('colors.grey'); - border-radius: 4px; - } + &::-moz-placeholder { + /* Firefox 19+ */ + padding-right: 22px; + } + + &:-moz-placeholder { + /* Firefox 18- */ + padding-right: 22px; + } + + /* Hide arrows from input number */ + /* Chrome, Safari, Edge, Opera */ + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } - /* Handle on hover */ - &::-webkit-scrollbar-thumb:hover { - background-color: theme('colors.offWhite'); + /* Firefox */ + &[type='number'] { + -moz-appearance: textfield; + } } } + +@utility scrollbar-hidden { + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } +} \ No newline at end of file diff --git a/apps/evm/src/clients/api/queries/getAccountTransactionHistory/useGetAccountTransactionHistory.ts b/apps/evm/src/clients/api/queries/getAccountTransactionHistory/useGetAccountTransactionHistory.ts index d09e7a817d..6665356bac 100644 --- a/apps/evm/src/clients/api/queries/getAccountTransactionHistory/useGetAccountTransactionHistory.ts +++ b/apps/evm/src/clients/api/queries/getAccountTransactionHistory/useGetAccountTransactionHistory.ts @@ -2,6 +2,7 @@ import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; import FunctionKey from 'constants/functionKey'; import { useChainId } from 'libs/wallet'; +import type { ChainId } from 'types'; import { type GetAccountTransactionHistoryInput, type GetAccountTransactionHistoryOutput, @@ -19,7 +20,12 @@ type Options = QueryObserverOptions< Error, GetAccountTransactionHistoryOutput, GetAccountTransactionHistoryOutput, - [FunctionKey.GET_ACCOUNT_TRANSACTION_HISTORY, TrimmedGetAccountTransactionHistoryInput] + [ + FunctionKey.GET_ACCOUNT_TRANSACTION_HISTORY, + TrimmedGetAccountTransactionHistoryInput & { + chainId: ChainId; + }, + ] >; export const useGetAccountTransactionHistory = ( @@ -35,7 +41,7 @@ export const useGetAccountTransactionHistory = ( }; return useQuery({ - queryKey: [FunctionKey.GET_ACCOUNT_TRANSACTION_HISTORY, params], + queryKey: [FunctionKey.GET_ACCOUNT_TRANSACTION_HISTORY, { ...params, chainId }], queryFn: () => getAccountTransactionHistory(extendedParams), ...options, }); diff --git a/apps/evm/src/components/Accordion/index.tsx b/apps/evm/src/components/Accordion/index.tsx index 3e3abdb52e..0b9183e41f 100644 --- a/apps/evm/src/components/Accordion/index.tsx +++ b/apps/evm/src/components/Accordion/index.tsx @@ -20,7 +20,7 @@ export const Accordion: React.FC = ({ return (
{slidesCount > 1 && ( -
+
{Array.from({ length: slidesCount }).map((_s, i) => ( diff --git a/apps/evm/src/components/Slider/index.tsx b/apps/evm/src/components/Slider/index.tsx index 680d25963e..29d5158d3f 100644 --- a/apps/evm/src/components/Slider/index.tsx +++ b/apps/evm/src/components/Slider/index.tsx @@ -46,7 +46,7 @@ export const Slider: React.FC = ({ diff --git a/apps/evm/src/components/Tabs/index.tsx b/apps/evm/src/components/Tabs/index.tsx index 0ee89d39d2..2ed1c7fa26 100644 --- a/apps/evm/src/components/Tabs/index.tsx +++ b/apps/evm/src/components/Tabs/index.tsx @@ -66,7 +66,7 @@ export const Tabs = ({ type="button" key={tab.id} className={cn( - 'hover:text-offWhite', + 'hover:text-offWhite cursor-pointer', activeTab.id === tab.id ? 'text-offWhite' : 'text-grey', )} > @@ -84,7 +84,7 @@ export const Tabs = ({ ))}
- +
)} diff --git a/apps/evm/src/components/TextField/index.tsx b/apps/evm/src/components/TextField/index.tsx index ef21e66c46..7e13c2b99e 100644 --- a/apps/evm/src/components/TextField/index.tsx +++ b/apps/evm/src/components/TextField/index.tsx @@ -98,7 +98,7 @@ export const TextField: React.FC = forwardRef = ({ )} -
+
); }; diff --git a/apps/evm/src/components/Tooltip/index.tsx b/apps/evm/src/components/Tooltip/index.tsx index fbd8031020..e0742ad963 100644 --- a/apps/evm/src/components/Tooltip/index.tsx +++ b/apps/evm/src/components/Tooltip/index.tsx @@ -40,7 +40,7 @@ export const Tooltip = ({ className, content, children, ...props }: TooltipProps e.preventDefault()} - className={cn('block p-3 z-[9999] shadow', !isMdOrUp && 'hidden')} + className={cn('block p-3 z-9999 shadow', !isMdOrUp && 'hidden')} > {content} diff --git a/apps/evm/src/containers/AddTokenToWalletButton/index.tsx b/apps/evm/src/containers/AddTokenToWalletButton/index.tsx index 5f0e15ecbc..5073ef0700 100644 --- a/apps/evm/src/containers/AddTokenToWalletButton/index.tsx +++ b/apps/evm/src/containers/AddTokenToWalletButton/index.tsx @@ -31,7 +31,7 @@ export const AddTokenToWalletButton: React.FC = ({ onClick={() => (isUserConnected ? addTokenToWallet(token) : copyToClipboard(token.address))} {...otherProps} > - + ); }; diff --git a/apps/evm/src/containers/CopyAddressButton/index.tsx b/apps/evm/src/containers/CopyAddressButton/index.tsx index bb0532ebd5..09b27d265b 100644 --- a/apps/evm/src/containers/CopyAddressButton/index.tsx +++ b/apps/evm/src/containers/CopyAddressButton/index.tsx @@ -22,7 +22,7 @@ export const CopyAddressButton: React.FC = ({ @@ -128,7 +132,11 @@ export const MarketInfo = () => { )}
- diff --git a/apps/evm/src/containers/Layout/Header/TopBar/XsControls/NavLink/index.tsx b/apps/evm/src/containers/Layout/Header/TopBar/XsControls/NavLink/index.tsx index 6d947537f6..af2220008f 100644 --- a/apps/evm/src/containers/Layout/Header/TopBar/XsControls/NavLink/index.tsx +++ b/apps/evm/src/containers/Layout/Header/TopBar/XsControls/NavLink/index.tsx @@ -47,7 +47,7 @@ export const NavLink: React.FC = ({
{badgeNumber && ( -
+
{badgeNumber}
)} diff --git a/apps/evm/src/containers/Layout/Header/TopBar/XsControls/index.tsx b/apps/evm/src/containers/Layout/Header/TopBar/XsControls/index.tsx index 3da7a9ed31..bdb8b9e5bc 100644 --- a/apps/evm/src/containers/Layout/Header/TopBar/XsControls/index.tsx +++ b/apps/evm/src/containers/Layout/Header/TopBar/XsControls/index.tsx @@ -62,7 +62,7 @@ export const XsControls: React.FC = () => { onClick={toggleMobileMenu} type="button" className={cn( - 'hover:bg-lightGrey active:bg-lightGrey ml-5 flex h-9 w-9 flex-none items-center justify-center rounded-lg p-0 border-lightGrey', + 'hover:bg-lightGrey active:bg-lightGrey ml-5 flex h-9 w-9 flex-none items-center justify-center rounded-lg p-0 border-lightGrey cursor-pointer', !isOnMarketPage && 'border bg-cards', isOnMarketPage && 'bg-background/40 hover:bg-background/40 active:bg-background/40', )} diff --git a/apps/evm/src/containers/Layout/Header/index.tsx b/apps/evm/src/containers/Layout/Header/index.tsx index 3bf23496fd..dbad6fc935 100644 --- a/apps/evm/src/containers/Layout/Header/index.tsx +++ b/apps/evm/src/containers/Layout/Header/index.tsx @@ -34,7 +34,7 @@ export const Header: React.FC = () => { className={cn( // The gradient will only be visible when a background color is applied. It is built this // way to support gradient background using a solid background color - 'relative flex-shrink-0 transition-all duration-500 bg-gradient-to-b from-background/60 to-background', + 'relative shrink-0 transition-all duration-500 bg-linear-to-b from-background/60 to-background', )} style={ isOnMarketPage && gradientAccentColor diff --git a/apps/evm/src/containers/Layout/ScrollToTop/index.tsx b/apps/evm/src/containers/Layout/ScrollToTop/index.tsx index f31295e8e1..12acbd630a 100644 --- a/apps/evm/src/containers/Layout/ScrollToTop/index.tsx +++ b/apps/evm/src/containers/Layout/ScrollToTop/index.tsx @@ -15,8 +15,8 @@ const ScrollToTop = forwardRef((_, ref) => { ref={ref} className={cn( 'bg-lightGrey fixed bottom-3 right-3 h-10 w-10 rounded-full border-0 p-0 shadow transition-all lg:hidden', - isVisible ? 'opacity-1' : 'pointer-events-none opacity-0', - isCloseToBottom ? '-translate-y-[125%]' : '-translate-y-0', + isVisible ? 'opacity-100' : 'pointer-events-none opacity-0', + isCloseToBottom ? '-translate-y-[125%]' : 'translate-y-0', )} onClick={() => scrollElem?.scrollTo({ behavior: 'smooth', top: 0 })} > diff --git a/apps/evm/src/containers/Layout/Sidebar/NavLink/index.tsx b/apps/evm/src/containers/Layout/Sidebar/NavLink/index.tsx index e5211afb7f..c17c90da5f 100644 --- a/apps/evm/src/containers/Layout/Sidebar/NavLink/index.tsx +++ b/apps/evm/src/containers/Layout/Sidebar/NavLink/index.tsx @@ -50,7 +50,7 @@ export const NavLink: React.FC = ({
{badgeNumber && ( -
+
{badgeNumber}
)} diff --git a/apps/evm/src/containers/ReadableActionSignature/index.tsx b/apps/evm/src/containers/ReadableActionSignature/index.tsx index 6ad373b712..45df8d4a0d 100644 --- a/apps/evm/src/containers/ReadableActionSignature/index.tsx +++ b/apps/evm/src/containers/ReadableActionSignature/index.tsx @@ -29,7 +29,7 @@ export const ReadableActionSignature: React.FC = ( }); return ( -
+
= ({

-
diff --git a/apps/evm/src/libs/notifications/NotificationCenter/index.tsx b/apps/evm/src/libs/notifications/NotificationCenter/index.tsx index 461141228a..346d06ddd2 100644 --- a/apps/evm/src/libs/notifications/NotificationCenter/index.tsx +++ b/apps/evm/src/libs/notifications/NotificationCenter/index.tsx @@ -14,7 +14,7 @@ const NotificationCenter: React.FC = () => { return createPortal(
-

+

We primarily rely on the legal basis of contract performance to process your Personal Data in connection with your use of our Services. For certain optional features, we may also rely on your consent or our legitimate interests, as appropriate.

-

Connect Using Third-Party Platforms

+

Connect Using Third-Party Platforms

-

+

We offer you a seamless connection to our Services through third-party platforms, such as MetaMask, Coinbase Wallet or Wallet Connect. You can easily access our Services using your existing wallet from these platforms. By using or continuing to use your third-party account @@ -104,9 +104,9 @@ export const PrivacyPolicy: React.FC = () => ( third-party platform.

-

Recruitment and Employment

+

Recruitment and Employment

-

+

If you apply for a position with us or become employed by us, we collect and process your Personal Data for purposes including recruitment, onboarding, employment administration, compliance with legal obligations, and other employment-related activities. Personal Data may @@ -114,7 +114,7 @@ export const PrivacyPolicy: React.FC = () => ( available sources such as LinkedIn.

-

+

We process the following categories of Personal Data for recruitment and employment purposes:

@@ -134,21 +134,21 @@ export const PrivacyPolicy: React.FC = () => ( -

+

During recruitment, we rely on your consent to process your Personal Data. Once employed, we rely on the necessity to perform your contract, comply with legal obligations, and our legitimate interests in managing the employment relationship.

-

Participation in Our Events

+

Participation in Our Events

-

+

When you register or attend our event, we collect some Personal Data to help manage your participation. This may include your name, contact details, company information, job title, industry, location, LinkedIn profile, payment details, and billing address.

-

We use this information to:

+

We use this information to:

  • Process your event registration and ticketing
  • @@ -160,29 +160,29 @@ export const PrivacyPolicy: React.FC = () => (
  • Support appropriate marketing efforts by our event sponsors
-

+

We process your Personal Data based on the agreement you enter when registering, and on our legitimate interest in delivering and improving our events. Where required, we will seek your consent for marketing purposes or when sharing your details with sponsors.

-

+

We rely on the legal basis of contract performance to process your Personal Data for event registration and participation. In addition, we process certain data based on our legitimate interest in enhancing our services. Where required, we will obtain your consent for marketing activities and for sharing your information with event sponsors.

-

+

Please note that photography and videography may take place during the event for promotional and archival purposes. By attending our event, you agree that VENUS and our partners may use your image in marketing materials, social media posts, or future event promotions as part of our efforts to share and celebrate the event.

-

Personal Data collected automatically from you:

+

Personal Data collected automatically from you:

-

+

In certain circumstances, we may collect Personal Data automatically from you when you use our Services, in accordance with applicable laws. This may include device, log, and usage data, which helps us enhance your experience, provide customer support, improve the performance of @@ -190,9 +190,9 @@ export const PrivacyPolicy: React.FC = () => ( preventing fraud.

-

Log and Usage Data

+

Log and Usage Data

-

+

When you access or use our Services, our servers automatically collect service-related, diagnostic, usage, and performance data, which is recorded in log files. This log data may include your IP address, device information, browser type, settings, and details about your @@ -201,30 +201,30 @@ export const PrivacyPolicy: React.FC = () => ( (such as "crash dumps"), and hardware settings.

-

+

Our legal basis for processing this data is our legitimate interest in improving our Services, ensuring the security of our Services, and maintaining a safe environment for our users, including fraud monitoring and prevention.

-

Aggregated and Anonymized Data

+

Aggregated and Anonymized Data

-

+

We also use aggregated or anonymized data to improve our Services. This involves analyzing general usage trends, gathering feedback, and conducting research without identifying individual users. For example, we may create overall usage reports for specific regions without containing Personal Data.

-

+

We will not use your Personal Data for purposes that are incompatible with the purposes of which you have been informed, unless it is required or authorized by law, or it is in your own vital interest to do so.

-

3. How and Why We Share Your Data

+

3. How and Why We Share Your Data

-

+

Information about our users is an important part of our business and we are not in the business of selling our user’s Personal Data to others. We may transfer Personal Data to our service providers or third parties in connection with Venus’s operation of its business, as @@ -233,12 +233,12 @@ export const PrivacyPolicy: React.FC = () => ( improvement of website-related services and features, and performance of maintenance services.

-

+

Third Party Services providers must only process the Personal Data in accordance with our contractual agreements and only as permitted by applicable data protection laws.

-

+

We may also share Personal Data with the following persons or in the following circumstances:

@@ -279,7 +279,7 @@ export const PrivacyPolicy: React.FC = () => ( -

+

Our Services and websites may contain links that enable you to connect with various third-party websites, applications, and other external platforms (referred to as "Third Party Platforms"). These Third Party Platforms act as independent data controllers and their @@ -290,16 +290,16 @@ export const PrivacyPolicy: React.FC = () => ( terms and privacy policies, and not by this Privacy Notice.

-

4. International Transfer of Personal Data

+

4. International Transfer of Personal Data

-

+

We maintain servers hosted by our trusted service providers, and your information may be processed on servers located outside of your country of residence. Additionally, we may transfer your Personal Data to our Affiliates, third-party partners, and service providers located in various countries around the world.

-

+

In instances where we process your Personal Data on servers outside your country or transfer it to third countries or international organizations beyond your country of residence, we implement appropriate technical, organizational, and contractual safeguards to ensure that @@ -309,9 +309,9 @@ export const PrivacyPolicy: React.FC = () => ( Data as outlined in this Notice.

-

5. Data Security

+

5. Data Security

-

+

We recognize that information security is a crucial component of data privacy. We are committed to making sure your information is protected in accordance with applicable laws and our data privacy policies. Although no data transmission, including over the Internet or @@ -320,32 +320,32 @@ export const PrivacyPolicy: React.FC = () => ( from unauthorized access, use, disclosure, alteration, or destruction.

-

+

The information you provide to us is stored on secure servers managed by us or our trusted service providers. Access to and use of this information are governed by our internal security policies and standards, or those agreed upon with our service providers, all in alignment with industry best practices.

-

6. Data Retention

+

6. Data Retention

-

+

We keep your Personal Data to enable your continued use of our Services, for as long as it is required in order to fulfill the relevant purposes described in this Privacy Notice as may be required by law such as for tax and accounting purposes, or to resolve disputes and/or legal claims or as otherwise communicated to you.

-

+

When we have no ongoing legitimate business or legal requirement to retain your Personal Data, we will either delete or anonymise such information, or, if this is not possible (for example, because your Personal Data has been stored in backup archives), then we will securely store your Personal Data and isolate it from any further processing until deletion is possible.

-

7. What Privacy Rights Do You Have?

+

7. What Privacy Rights Do You Have?

-

Your Privacy Rights

+

Your Privacy Rights

  • @@ -393,9 +393,9 @@ export const PrivacyPolicy: React.FC = () => (
-

Submit Privacy Request

+

Submit Privacy Request

-

+

Alternatively, to exercise your rights, you can submit a request using our{' '} ( or contact us via email at privacy[at]venus.io.

-

+

We will respond as quickly as possible. If we are unable to respond within 30 days, we will inform you in writing of the timeline for our response. If we cannot fulfill your request, we will explain the reasons (unless prohibited by applicable laws).

-

+

If you have any questions or concerns about how we collect and process your Personal Data, or if you wish to withdraw your consent for any processing, please contact us.

-

8. Children

+

8. Children

-

+

We do not knowingly solicit data from or market to any persons under 18 years of age. By using the Services, you represent that you are at least 18 years old. If we become aware that we have collected Personal Data from someone under 18, we will deactivate the account in question @@ -429,9 +429,9 @@ export const PrivacyPolicy: React.FC = () => ( is from a user under the age of 18, please contact us using the contact information below.

-

9. Contact Information

+

9. Contact Information

-

+

Our support team is available to direct any questions related to your Personal Data. You can contact us through our{' '} ( collection and processing of your Personal Data.

-

10. Conditions of Use, Notices and Revisions

+

10. Conditions of Use, Notices and Revisions

-

+

If you choose to use our Services, your use and any dispute over privacy is subject to this Privacy Notice and our Terms of Use. If you have any concerns about privacy at Venus, please contact us and we will try our best to resolve it. You also have the right to contact your local Data Protection Authority.

-

+

We reserve the right to update and revise this Notice at any time. We will review this Privacy Notice from time to time to make sure it complies with applicable laws and conforms to changes in our business. If we do revise this Privacy Notice, we will update the “Last Updated” date @@ -463,7 +463,7 @@ export const PrivacyPolicy: React.FC = () => ( and will do our best to notify you.

-

+

Please review this Privacy Notice regularly to ensure that you are aware of its terms. Your continued use of our Services after an amendment to our Privacy Notice constitutes your acceptance to the amended terms. diff --git a/apps/landing/src/pages/TermsOfUse/index.tsx b/apps/landing/src/pages/TermsOfUse/index.tsx index dbfce4478a..a519d5bddc 100644 --- a/apps/landing/src/pages/TermsOfUse/index.tsx +++ b/apps/landing/src/pages/TermsOfUse/index.tsx @@ -4,9 +4,9 @@ const LAST_UPDATED_AT = 'September 18, 2025'; export const TermsOfUse: React.FC = () => ( -

1. Acceptance of Terms

+

1. Acceptance of Terms

-

+

These Terms of Use ("Terms") govern your access to and use of the Venus Protocol platform and all related services (collectively, the "Platform"). By accessing or using the Platform through https://venus.io/, any subdomains, mobile applications, APIs, or any other means @@ -15,18 +15,18 @@ export const TermsOfUse: React.FC = () => ( from accessing or using the Platform and must discontinue use immediately.

-

2. Description of the Platform

+

2. Description of the Platform

-

+

Venus Protocol operates as a decentralized, non-custodial, algorithmic money market and synthetic stablecoin protocol deployed on public blockchain infrastructure. The Platform enables users to engage with lending, borrowing, minting, and other decentralized finance ("DeFi") activities via smart contracts.

-

3. Eligibility and Jurisdiction

+

3. Eligibility and Jurisdiction

-

+

You are solely responsible for understanding and complying with any and all laws, rules, and regulations that may apply to you in connection with your use of the Platform. You may not access or use the Platform if you are a resident, citizen, or agent of, or incorporated in, @@ -34,9 +34,9 @@ export const TermsOfUse: React.FC = () => ( or prohibited for any reason.

-

4. User Representation and Warranties

+

4. User Representation and Warranties

-

By using the Platform, you represent and warrant that:

+

By using the Platform, you represent and warrant that:

  • You are of legal age and capacity to enter into these Terms;
  • @@ -49,50 +49,50 @@ export const TermsOfUse: React.FC = () => (
-

5. Anti-Money Laundering and Sanctions Compliance

+

5. Anti-Money Laundering and Sanctions Compliance

-

5.1 Prohibited Persons

+

5.1 Prohibited Persons

-

+

You represent that you are not: (a) subject to economic sanctions administered by OFAC, EU, UN, or other applicable authorities; (b) located in, or a resident of, any sanctioned jurisdiction; or (c) otherwise prohibited from accessing the Platform under applicable law.

-

5.2 Source of Funds

+

5.2 Source of Funds

-

+

You represent that any digital assets used on the Platform are obtained through lawful means and are not derived from illegal activities.

-

5.3 Monitoring

+

5.3 Monitoring

-

+

Venus Protocol reserves the right to implement compliance measures, including transaction monitoring and blocking access from certain jurisdictions or addresses.

-

6. Prohibited Uses

+

6. Prohibited Uses

-

+

You may not use the Platform to engage in activities that are unlawful, fraudulent, threatening, or otherwise violate these Terms or the rights of others, or to circumvent sanctions, conduct money laundering, or any prohibited transaction under applicable law.

-

7. No Financial, Legal, or Tax Advice

+

7. No Financial, Legal, or Tax Advice

-

+

Nothing on the Platform constitutes, or is intended to constitute, financial, investment, legal, or tax advice. Venus Protocol does not act as your advisor or fiduciary. You are solely responsible for evaluating the merits and risks of any transaction and should consult your own professional advisors as appropriate.

-

8. Non-Custodial and Autonomous Nature

+

8. Non-Custodial and Autonomous Nature

-

+

Venus Protocol is a non-custodial, autonomous platform. Your interactions are directly with deployed smart contracts; neither Venus Protocol nor its developers, affiliates, or any associated party will ever take possession or control of your assets. You are solely @@ -100,11 +100,11 @@ export const TermsOfUse: React.FC = () => ( cannot restore or recover lost assets or access.

-

9. Fees and Tax

+

9. Fees and Tax

-

9.1 Non-Intermediary Status for Blockchain Transactions

+

9.1 Non-Intermediary Status for Blockchain Transactions

-

+

Venus Protocol does not serve as an intermediary, broker, agent, or custodian for blockchain transactions. Given the decentralized and non-custodial architecture of the underlying technology, we do not act as intermediaries, agents, advisors, or custodians, and we do not @@ -118,18 +118,18 @@ export const TermsOfUse: React.FC = () => ( initiate.

-

9.2 Limited Transaction Information

+

9.2 Limited Transaction Information

-

+

You acknowledge that Venus Protocol does not possess information regarding all Venus Protocol transactions beyond what is publicly available or obtainable via the blockchain. However, we may collect certain information regarding users of the Platform in accordance with our Privacy Notice.

-

9.3 Blockchain Network Fees and Associated Costs

+

9.3 Blockchain Network Fees and Associated Costs

-

+

Transactions executed via blockchain networks may incur various fees imposed by third parties for access to and utilization of such permissionless networks. These fees may include, without limitation: @@ -155,7 +155,7 @@ export const TermsOfUse: React.FC = () => ( -

+

You acknowledge and agree that certain blockchain network fees may be non-refundable under all circumstances, including but not limited to instances where a transaction is reverted, fails to execute, or is otherwise unsuccessful. You are solely responsible for understanding and @@ -165,9 +165,9 @@ export const TermsOfUse: React.FC = () => ( functionality, or suitability of any third-party services, fee structures, or transactions.

-

9.4 Tax Obligations and Responsibilities

+

9.4 Tax Obligations and Responsibilities

-

+

Transactions executed via blockchain networks may incur various fees imposed by third parties for access to and utilization of such permissionless networks. These fees may include, without limitation: @@ -193,7 +193,7 @@ export const TermsOfUse: React.FC = () => ( -

+

You acknowledge and agree that certain blockchain network fees may be non-refundable under all circumstances, including but not limited to instances where a transaction is reverted, fails to execute, or is otherwise unsuccessful. You are solely responsible for understanding and @@ -203,92 +203,96 @@ export const TermsOfUse: React.FC = () => ( functionality, or suitability of any third-party services, fee structures, or transactions.

-

10. Technology and Protocol Risks

+

10. Technology and Protocol Risks

-

+

You acknowledge and accept substantial risks inherent in decentralized protocols and smart contracts, including but not limited to:

-

10.1 Smart Contract Risks

+

10.1 Smart Contract Risks

-

+

Software vulnerabilities, exploits, bugs, unexpected behaviors, and potential economic attacks on protocol mechanisms.

-

10.2 Infrastructure Risks

+

10.2 Infrastructure Risks

-

+

Blockchain network congestion, hard forks, validator failures, and potential network splits or reorganizations.

-

10.3 Oracle and Price Feed Risks

+

10.3 Oracle and Price Feed Risks

-

+

Reliance on external price oracles that may be manipulated, delayed, or provide inaccurate data, potentially affecting liquidations and protocol operations.

-

10.4 Governance Risks

+

10.4 Governance Risks

-

+

Changes to protocol parameters through governance mechanisms that may adversely affect your positions or the protocol's operation.

-

10.5 Interoperability Risks

+

10.5 Interoperability Risks

-

Risks arising from interactions with other protocols, bridges, or cross-chain mechanisms.

+

+ Risks arising from interactions with other protocols, bridges, or cross-chain mechanisms. +

-

+

The Platform is provided "AS IS" and "AS AVAILABLE," without any express or implied warranties of merchantability, fitness for a particular purpose, or non-infringement.

-

11. Market, Volatility, and Liquidation Risks

+

11. Market, Volatility, and Liquidation Risks

-

11.1 Market Volatility

+

11.1 Market Volatility

-

+

Digital assets and stablecoins are inherently volatile and may experience rapid and substantial price fluctuations.

-

11.2 Liquidation Mechanics

+

11.2 Liquidation Mechanics

-

+

You understand that positions may be liquidated automatically when collateral ratios fall below required thresholds, potentially resulting in partial or total loss of collateral.

-

11.3 Slippage and MEV

+

11.3 Slippage and MEV

-

+

Transactions may be subject to slippage, front-running, or maximum extractable value (MEV) extraction by third parties.

-

11.4 Stablecoin Risks

+

11.4 Stablecoin Risks

-

+

Synthetic stablecoins may depeg from their intended value due to market conditions, protocol mechanics, or external factors.

-

11.5 Impermanent Loss

+

11.5 Impermanent Loss

-

Certain activities may expose you to impermanent loss or other opportunity costs.

+

+ Certain activities may expose you to impermanent loss or other opportunity costs. +

-

+

You are solely responsible for monitoring your positions, understanding protocol mechanics, and managing risk exposure at all times.

-

12. Regulatory Uncertainty

+

12. Regulatory Uncertainty

-

+

The legal and regulatory environment for decentralized protocols and digital assets is unsettled and subject to significant change. Applicable laws, regulations, or enforcement actions may affect your access to, or use of, the Platform. Venus Protocol does not guarantee @@ -296,84 +300,84 @@ export const TermsOfUse: React.FC = () => ( liability for your compliance with relevant laws and regulations.

-

13. No Guarantee of Platform Continuity

+

13. No Guarantee of Platform Continuity

-

+

Venus Protocol may upgrade, modify, suspend, or discontinue any aspect of the Platform at any time, with or without notice, and disclaims any liability for the unavailability or modification of the Platform.

-

14. Third-Party Services and Integrations

+

14. Third-Party Services and Integrations

-

+

The Platform may reference, integrate, or provide access to third-party protocols, services, or content ("Third-Party Services"). Venus Protocol does not endorse, control, or assume responsibility for any Third-Party Services, and you access or use them entirely at your own risk.

-

15. DAO and Governance

+

15. DAO and Governance

-

+

Some aspects of the Platform may be governed or influenced by decentralized autonomous organization ("DAO") mechanisms and on-chain community proposals. Outcomes of protocol governance or DAO proposals are not controlled by Venus Protocol and are implemented automatically via smart contracts.

-

16. Privacy and Data Protection

+

16. Privacy and Data Protection

-

16.1 Data Collection

+

16.1 Data Collection

-

+

While Venus Protocol operates as a decentralized protocol, certain information may be collected through interfaces, analytics, or third-party services. Our{' '} Privacy Notice, incorporated by reference, governs such data collection and use.

-

16.2 Blockchain Transparency

+

16.2 Blockchain Transparency

-

+

You acknowledge that blockchain transactions are publicly visible and permanent. Venus Protocol cannot control or modify blockchain data once recorded.

-

16.3 Compliance

+

16.3 Compliance

-

+

Users in jurisdictions with specific data protection laws (for example, GDPR, CCPA, etc.) should review our Privacy Notice for applicable rights and procedures.

-

17. Intellectual Property

+

17. Intellectual Property

-

+

All Platform content, trademarks, logos, and materials are the property of Venus Protocol or their respective owners, and are protected by applicable intellectual property laws. You may not use any proprietary material without express authorization, except as necessary for legitimate use of the Platform.

-

18. Electronic Communications

+

18. Electronic Communications

-

+

By using the Platform, you consent to receive communications electronically. You agree that all agreements, notices, disclosures, and other communications provided electronically satisfy any legal requirement that such communications be in writing.

-

19. Updates and Amendments

+

19. Updates and Amendments

-

+

Venus Protocol may update or amend these Terms at any time. Continued use of the Platform after changes become effective constitutes your acceptance of the revised Terms.

-

20. Limitation of Liability

+

20. Limitation of Liability

-

+

To the maximum extent permitted by law, in no event will Venus Protocol or its affiliates, developers, contributors, or operators be liable for any direct, indirect, incidental, special, exemplary, or consequential damages—arising from or related to your use or inability @@ -382,35 +386,35 @@ export const TermsOfUse: React.FC = () => ( such damages.

-

21. Indemnification

+

21. Indemnification

-

+

You agree to indemnify, defend, and hold harmless Venus Protocol, its affiliates, developers, contributors, or operators from and against any and all claims, liabilities, damages, losses, costs, expenses, or fees arising from your violation of these Terms or your use of the Platform.

-

22. Force Majeure

+

22. Force Majeure

-

+

Venus Protocol shall not be liable for any failure or delay in performance due to circumstances beyond its reasonable control, including but not limited to blockchain network failures, regulatory actions, natural disasters, cyberattacks, or other force majeure events.

-

23. Dispute Resolution and Arbitration

+

23. Dispute Resolution and Arbitration

-

23.1 Governing Law

+

23.1 Governing Law

-

+

These Terms shall be governed by and construed in accordance with the laws of Hong Kong, without regard to its conflict of law principles.

-

23.2 Mandatory Arbitration

+

23.2 Mandatory Arbitration

-

+

Subject to applicable law requirements or where you are provided with alternative legal choices, you and Venus Protocol agree that any dispute, claim, or controversy arising out of or relating to these Terms or your use of the Platform ("Dispute") shall be resolved through @@ -419,37 +423,39 @@ export const TermsOfUse: React.FC = () => ( in effect, which are incorporated herein by reference.

-

23.3 Arbitration Venue

+

23.3 Arbitration Venue

-

Hong Kong shall serve as the seat of arbitration proceedings.

+

Hong Kong shall serve as the seat of arbitration proceedings.

-

23.4 Arbitrator Selection

+

23.4 Arbitrator Selection

-

+

The arbitral tribunal shall comprise one (1) arbitrator selected in accordance with the applicable HKIAC Administered Arbitration Rules.

-

23.5 Proceedings Language

+

23.5 Proceedings Language

-

All arbitration proceedings shall be conducted in the English language.

+

All arbitration proceedings shall be conducted in the English language.

-

23.6 Arbitrator Authority

+

23.6 Arbitrator Authority

-

+

You and Venus Protocol acknowledge that the arbitrator shall possess exclusive authority to determine their own jurisdiction, including but not limited to any challenges regarding the existence, scope, or validity of this arbitration agreement, or the arbitrability of any Dispute.

-

23.7 Survival of Arbitration Provisions

+

23.7 Survival of Arbitration Provisions

-

These arbitration provisions shall remain in effect following termination of these Terms.

+

+ These arbitration provisions shall remain in effect following termination of these Terms. +

-

23.8 Time Limitations for Claims

+

23.8 Time Limitations for Claims

-

+

Any arbitration proceeding against Venus Protocol must be initiated by filing and serving a Notice of Arbitration pursuant to HKIAC procedures within one (1) year from the date you first discovered or reasonably should have discovered the alleged act, omission, or default giving @@ -462,9 +468,9 @@ export const TermsOfUse: React.FC = () => ( applicable laws and service rules.

-

23.9 Notice Requirements

+

23.9 Notice Requirements

-

+

Should Venus Protocol initiate arbitration proceedings against you, notice will be provided to the email address or mailing address you have furnished to us. You acknowledge that any communication sent to such email or mailing address shall constitute effective notice for all @@ -472,30 +478,30 @@ export const TermsOfUse: React.FC = () => ( maintaining current and accurate contact information with Venus Protocol.

-

24. Severability

+

24. Severability

-

+

If any provision of these Terms is held to be invalid or unenforceable, such provision will be severed and the remainder of the Terms will remain in full force and effect.

-

25. Survival

+

25. Survival

-

+

The following sections shall survive termination of these Terms: Sections 7-13, 16, 19-20, 22-23, and any other provisions that by their nature should survive termination.

-

26. Entire Agreement

+

26. Entire Agreement

-

+

These Terms constitute the entire agreement between you and Venus Protocol regarding your use of the Platform, superseding any prior agreements or understandings.

-

27. Contact Information

+

27. Contact Information

-

+

If you have any questions about these Terms, please contact us at{' '} contact@venus.io.

diff --git a/apps/landing/tailwind.config.ts b/apps/landing/tailwind.config.ts deleted file mode 100644 index fd134e01a6..0000000000 --- a/apps/landing/tailwind.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -import path from 'node:path'; -import tailwindConfig from '@venusprotocol/ui/tailwind-config'; - -export default { - presets: [tailwindConfig], - theme: { - extend: { - screens: { - sm: '640px', - md: '840px', - }, - }, - }, - content: [ - './index.html', - './src/**/*.{js,ts,jsx,tsx}', - ...tailwindConfig.content.map(dir => - path.join(path.dirname(require.resolve('@venusprotocol/ui')), dir), - ), - ], - test: { - passWithNoTests: true, - }, -}; diff --git a/apps/landing/vite.config.mts b/apps/landing/vite.config.mts index 2e2a29e474..d5d37027d0 100644 --- a/apps/landing/vite.config.mts +++ b/apps/landing/vite.config.mts @@ -1,10 +1,11 @@ +import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import svgr from 'vite-plugin-svgr'; import viteTsConfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ - plugins: [react(), viteTsConfigPaths(), svgr()], + plugins: [react(), viteTsConfigPaths(), svgr(), tailwindcss()], resolve: { alias: { // Import raw source so dApp is in charge of compiling token and chain icons diff --git a/packages/ui/package.json b/packages/ui/package.json index c34c7cbba2..d47f6129ae 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -22,12 +22,14 @@ }, "devDependencies": { "@venusprotocol/typescript-config": "*", - "tailwindcss": ">=3.3.0" + "tailwindcss": ">=4.1.18" }, "dependencies": { - "@radix-ui/react-slot": "^1.1.2" + "@radix-ui/react-slot": "^1.1.2", + "clsx": "^2.1.1", + "tailwind-merge": "^3.4.0" }, "peerDependencies": { - "tailwindcss": ">=3.3.0" + "tailwindcss": ">=4.0.0" } } diff --git a/packages/ui/src/components/Button/index.tsx b/packages/ui/src/components/Button/index.tsx index 8edbdbc68b..cf7806f166 100644 --- a/packages/ui/src/components/Button/index.tsx +++ b/packages/ui/src/components/Button/index.tsx @@ -80,7 +80,7 @@ export const ButtonWrapper: React.FC = ({ return ( =3.3.0: - version "4.0.9" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.0.9.tgz#f6626cee837aabe9e54c29b230b6fb0ed36fe965" - integrity sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw== - -tailwindcss@^3.3.3: - version "3.4.17" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63" - integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og== - dependencies: - "@alloc/quick-lru" "^5.2.0" - arg "^5.0.2" - chokidar "^3.6.0" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.3.2" - glob-parent "^6.0.2" - is-glob "^4.0.3" - jiti "^1.21.6" - lilconfig "^3.1.3" - micromatch "^4.0.8" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.1.1" - postcss "^8.4.47" - postcss-import "^15.1.0" - postcss-js "^4.0.1" - postcss-load-config "^4.0.2" - postcss-nested "^6.2.0" - postcss-selector-parser "^6.1.2" - resolve "^1.22.8" - sucrase "^3.35.0" +tailwindcss@4.1.18, tailwindcss@>=4.1.18, tailwindcss@^4.1.18: + version "4.1.18" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.18.tgz#f488ba47853abdb5354daf9679d3e7791fc4f4e3" + integrity sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw== + +tapable@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== tar@^6.1.11, tar@^6.1.2: version "6.2.1" @@ -17991,20 +18066,6 @@ then-request@^6.0.0: promise "^8.0.0" qs "^6.4.0" -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - thread-stream@^0.15.1: version "0.15.2" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" @@ -18239,11 +18300,6 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - ts-invariant@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c" @@ -18369,6 +18425,11 @@ turbo@^2.0.3: turbo-windows-64 "2.6.1" turbo-windows-arm64 "2.6.1" +tw-animate-css@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz#b4a06f68244cba39428aa47e65e6e4c0babc21ee" + integrity sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ== + tweetnacl-util@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" @@ -19465,7 +19526,7 @@ yaml@^2.2.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== -yaml@^2.3.1, yaml@^2.3.4: +yaml@^2.3.1: version "2.6.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== From e6c82fe7cfa945bccd74c140dc70de8f894e36c0 Mon Sep 17 00:00:00 2001 From: Maxime Julian <44675210+therealemjy@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:45:37 +0100 Subject: [PATCH 2/5] fix: only filter by user assets if they have some (#5196) refactor: always show user assets only filter --- .changeset/fancy-lines-raise.md | 5 +++++ apps/evm/src/containers/MarketTable/index.tsx | 13 +++++-------- .../containers/MarketTable/useControls/index.tsx | 8 -------- 3 files changed, 10 insertions(+), 16 deletions(-) create mode 100644 .changeset/fancy-lines-raise.md diff --git a/.changeset/fancy-lines-raise.md b/.changeset/fancy-lines-raise.md new file mode 100644 index 0000000000..b838cde9ee --- /dev/null +++ b/.changeset/fancy-lines-raise.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": patch +--- + +always show user assets only filter diff --git a/apps/evm/src/containers/MarketTable/index.tsx b/apps/evm/src/containers/MarketTable/index.tsx index 2b06840e18..0d58c1db4a 100644 --- a/apps/evm/src/containers/MarketTable/index.tsx +++ b/apps/evm/src/containers/MarketTable/index.tsx @@ -62,7 +62,6 @@ export const MarketTable: React.FC = ({ const { assets: filteredAssets, pausedAssetsExist, - userHasAssets, searchValue, onSearchValueChange, showPausedAssets, @@ -152,13 +151,11 @@ export const MarketTable: React.FC = ({ /> )} - {userHasAssets && ( - setShowUserAssetsOnly(!showUserAssetsOnly)} - value={showUserAssetsOnly} - label={t('marketTable.userAssetsOnlyToggle.label')} - /> - )} + setShowUserAssetsOnly(!showUserAssetsOnly)} + value={showUserAssetsOnly} + label={t('marketTable.userAssetsOnlyToggle.label')} + /> {userEModeGroup && ( { - const isUserAsset = asset.userWalletBalanceTokens.isGreaterThan(0); - - if (isUserAsset && !userHasAssets) { - userHasAssets = true; - } - const isPaused = isAssetPaused({ disabledTokenActions: asset.disabledTokenActions }); if (isPaused && !pausedAssetsExist) { pausedAssetsExist = true; @@ -86,7 +79,6 @@ export const useControls = ({ setShowUserEModeAssetsOnly, onSearchValueChange: setSearchValue, pausedAssetsExist, - userHasAssets, showPausedAssets, showUserAssetsOnly, showUserEModeAssetsOnly, From 74a98fad1ba55f86e8395f8017d2d822ff2bd9d6 Mon Sep 17 00:00:00 2001 From: Maxime Julian <44675210+therealemjy@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:29:11 +0100 Subject: [PATCH 3/5] fix: rocket illustration resolution (#5190) --- .changeset/bumpy-words-learn.md | 5 +++++ .../Pool/Assets/AdBanner/BoostBanner/index.tsx | 2 +- .../Pool/Assets/AdBanner/BoostBanner/rocket.png | Bin 0 -> 31096 bytes .../Pool/Assets/AdBanner/BoostBanner/rocket.svg | 10 ---------- 4 files changed, 6 insertions(+), 11 deletions(-) create mode 100644 .changeset/bumpy-words-learn.md create mode 100644 apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.png delete mode 100644 apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.svg diff --git a/.changeset/bumpy-words-learn.md b/.changeset/bumpy-words-learn.md new file mode 100644 index 0000000000..2933f67c09 --- /dev/null +++ b/.changeset/bumpy-words-learn.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": patch +--- + +fix rocket illustration resolution diff --git a/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/index.tsx b/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/index.tsx index 0ef4a6b2d5..ba2a545363 100644 --- a/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/index.tsx +++ b/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/index.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'libs/translations'; import { Banner } from '../Banner'; -import rocketIllustration from './rocket.svg'; +import rocketIllustration from './rocket.png'; const LEARN_MORE_URL = 'https://docs-v4.venus.io/guides/leveraged-positions'; diff --git a/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.png b/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2874188d9d82e9a755baea3abbbcc8e9b5dc31 GIT binary patch literal 31096 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 P00000NkvXXu0mjfAwNK8 literal 0 HcmV?d00001 diff --git a/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.svg b/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.svg deleted file mode 100644 index 7d38924ebb..0000000000 --- a/apps/evm/src/pages/Pool/Assets/AdBanner/BoostBanner/rocket.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - From 3edef2d75e13bdebf7c5b95be4b05d936e07fb3c Mon Sep 17 00:00:00 2001 From: david-sun-venus Date: Tue, 30 Dec 2025 20:53:58 +0800 Subject: [PATCH 4/5] feat(evm): swap and supply --- .../__tests__/index.spec.ts | 37 +++----- .../mutations/useSwapTokensAndSupply/index.ts | 89 ++++++++++--------- .../queries/getSwapQuote/useGetSwapQuote.ts | 4 +- .../src/hooks/useIsFeatureEnabled/index.tsx | 2 +- .../src/libs/analytics/useAnalytics/types.ts | 2 +- .../config/externalAbis/SwapRouterV2.json | 67 ++++++++++++++ apps/evm/src/libs/contracts/config/index.ts | 8 ++ .../OperationForm/BoostForm/useForm/index.tsx | 6 +- .../BoostForm/useForm/useFormValidation.ts | 5 +- .../OperationDetails/SwapDetails/index.tsx | 10 +-- .../OperationForm/OperationDetails/index.tsx | 4 +- .../RepayWithCollateralForm/useForm/index.tsx | 6 +- .../useForm/useFormValidation.ts | 5 +- .../OperationForm/SupplyForm/Notice.tsx | 4 +- .../SupplyForm/SubmitSection/index.tsx | 4 +- .../__tests__/indexIntegratedSwap.spec.tsx | 42 ++++++--- .../Market/OperationForm/SupplyForm/index.tsx | 45 +++++++--- .../SupplyForm/useForm/index.tsx | 8 +- .../OperationForm/SupplyForm/useForm/types.ts | 9 +- .../SupplyForm/useForm/useFormValidation.ts | 30 ++----- .../OperationForm/SwapSummary/index.tsx | 12 +-- apps/evm/src/types/index.ts | 2 + .../getSwapToTokenAmountReceived/index.ts | 6 +- 23 files changed, 249 insertions(+), 158 deletions(-) create mode 100644 apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json diff --git a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/__tests__/index.spec.ts b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/__tests__/index.spec.ts index 4b96835b9e..737945e6bb 100644 --- a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/__tests__/index.spec.ts +++ b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/__tests__/index.spec.ts @@ -1,5 +1,5 @@ import fakeAccountAddress from '__mocks__/models/address'; -import { usdc, xvs } from '__mocks__/models/tokens'; +import { bnb, usdc, xvs } from '__mocks__/models/tokens'; import { vXvs } from '__mocks__/models/vTokens'; import BigNumber from 'bignumber.js'; import { queryClient } from 'clients/api'; @@ -18,7 +18,7 @@ const mockPoolComptrollerAddress = '0x456' as Address; const mockPoolName = 'Test Pool'; const mockSwap = { - direction: 'exactAmountIn' as const, + direction: 'exact-in' as const, fromToken: xvs, toToken: usdc, fromTokenAmountSoldMantissa: new BigNumber(1000), @@ -87,18 +87,14 @@ describe('useSwapTokensAndSupply', () => { ` { "abi": Any, - "address": "0xfakeSwapRouterContractAddress", + "address": "0xfakeSwapRouterV2ContractAddress", "args": [ "0x6d6F697e34145Bb95c54E77482d97cc261Dc237E", - 1000n, - 900n, - [ - "0xdef", - "0xghi", - ], - 1747386407n, + "1000", + "900", + undefined, ], - "functionName": "swapExactTokensForTokensAndSupply", + "functionName": "swapAndSupply", } `, ); @@ -110,7 +106,6 @@ describe('useSwapTokensAndSupply', () => { [ "Tokens swapped and supplied", { - "exchangeRate": 1, "fromTokenAmountTokens": 1e-15, "fromTokenSymbol": "XVS", "poolName": "Test Pool", @@ -140,7 +135,7 @@ describe('useSwapTokensAndSupply', () => { ); const { fn } = (useSendTransaction as Mock).mock.calls[0][0]; - const res = await fn({ swap: mockSwap }); + const res = await fn({ swap: { ...mockSwap, fromToken: bnb } }); expect(res).toMatchInlineSnapshot( { @@ -149,18 +144,14 @@ describe('useSwapTokensAndSupply', () => { ` { "abi": Any, - "address": "0xfakeSwapRouterContractAddress", + "address": "0xfakeSwapRouterV2ContractAddress", "args": [ "0x6d6F697e34145Bb95c54E77482d97cc261Dc237E", - 1000n, - 900n, - [ - "0xdef", - "0xghi", - ], - 1747386407n, + "900", + undefined, ], - "functionName": "swapExactTokensForTokensAndSupply", + "functionName": "swapNativeAndSupply", + "value": "1000", } `, ); @@ -180,7 +171,7 @@ describe('useSwapTokensAndSupply', () => { ); const invalidMockSwap = { - direction: 'exactAmountOut' as const, + direction: 'exact-out' as const, fromToken: { isNative: false, address: '0xdef' as Address, diff --git a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts index bfce51e519..6076110caa 100644 --- a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts +++ b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts @@ -4,17 +4,16 @@ import { DEFAULT_SLIPPAGE_TOLERANCE_PERCENTAGE } from 'constants/swap'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; import { useAnalytics } from 'libs/analytics'; -import { swapRouterAbi } from 'libs/contracts'; +import { swapRouterV2Abi } from 'libs/contracts'; import { VError } from 'libs/errors'; import { useAccountAddress, useChainId } from 'libs/wallet'; -import type { Swap, VToken } from 'types'; +import type { ExactInSwapQuote, ExactOutSwapQuote, SwapQuote, VToken } from 'types'; import { convertMantissaToTokens } from 'utilities/convertMantissaToTokens'; -import { generateTransactionDeadline } from 'utilities/generateTransactionDeadline'; import type { Address, ContractFunctionArgs } from 'viem'; export interface SwapTokensAndSupplyInput { swapRouterContractAddress: Address; - swap: Swap; + swap: SwapQuote; vToken: VToken; } @@ -37,22 +36,22 @@ export const useSwapTokensAndSupply = ( const { captureAnalyticEvent } = useAnalytics(); const { address: swapRouterContractAddress } = useGetContractAddress({ - name: 'SwapRouter', - poolComptrollerContractAddress: poolComptrollerAddress, + name: 'SwapRouterV2', + // poolComptrollerContractAddress: poolComptrollerAddress, }); return useSendTransaction< TrimmedSwapTokensAndSupplyInput, - typeof swapRouterAbi, - 'swapExactTokensForTokensAndSupply' | 'swapExactBNBForTokensAndSupply', + typeof swapRouterV2Abi, + 'swapAndSupply' | 'swapNativeAndSupply', ContractFunctionArgs< - typeof swapRouterAbi, + typeof swapRouterV2Abi, 'nonpayable' | 'payable', - 'swapExactTokensForTokensAndSupply' | 'swapExactBNBForTokensAndSupply' + 'swapAndSupply' | 'swapNativeAndSupply' > >({ fn: ({ swap }: TrimmedSwapTokensAndSupplyInput) => { - const transactionDeadline = generateTransactionDeadline(); + // const transactionDeadline = generateTransactionDeadline(); if (!swapRouterContractAddress) { throw new VError({ @@ -62,38 +61,34 @@ export const useSwapTokensAndSupply = ( } // Sell fromTokens to supply as many toTokens as possible - if ( - swap.direction === 'exactAmountIn' && - !swap.fromToken.isNative && - !swap.toToken.isNative - ) { + if (swap.direction === 'exact-in' && !swap.fromToken.isNative && !swap.toToken.isNative) { return { - abi: swapRouterAbi, + abi: swapRouterV2Abi, address: swapRouterContractAddress, - functionName: 'swapExactTokensForTokensAndSupply' as const, + functionName: 'swapAndSupply' as const, args: [ vToken.address, - BigInt(swap.fromTokenAmountSoldMantissa.toFixed()), - BigInt(swap.minimumToTokenAmountReceivedMantissa.toFixed()), - swap.routePath as Address[], - transactionDeadline, + swap.fromTokenAmountSoldMantissa, + swap.minimumToTokenAmountReceivedMantissa, + swap.callData, + // transactionDeadline, ], } as const; } // Sell BNBs to supply as many toTokens as possible - if (swap.direction === 'exactAmountIn' && swap.fromToken.isNative && !swap.toToken.isNative) { + if (swap.direction === 'exact-in' && swap.fromToken.isNative && !swap.toToken.isNative) { return { - abi: swapRouterAbi, + abi: swapRouterV2Abi, address: swapRouterContractAddress, - functionName: 'swapExactBNBForTokensAndSupply' as const, + functionName: 'swapNativeAndSupply' as const, args: [ vToken.address, - BigInt(swap.minimumToTokenAmountReceivedMantissa.toFixed()), - swap.routePath as Address[], - transactionDeadline, + swap.minimumToTokenAmountReceivedMantissa, + swap.callData, + // transactionDeadline, ], - value: BigInt(swap.fromTokenAmountSoldMantissa.toFixed()), + value: swap.fromTokenAmountSoldMantissa, } as const; } @@ -103,27 +98,33 @@ export const useSwapTokensAndSupply = ( }); }, onConfirmed: async ({ input }) => { + // TODO: resolve args + if (!input?.swap) return; captureAnalyticEvent('Tokens swapped and supplied', { poolName, fromTokenSymbol: input.swap.fromToken.symbol, - fromTokenAmountTokens: convertMantissaToTokens({ - token: input.swap.fromToken, - value: - input.swap.direction === 'exactAmountIn' - ? input.swap.fromTokenAmountSoldMantissa - : input.swap.expectedFromTokenAmountSoldMantissa, - }).toNumber(), + fromTokenAmountTokens: ( + convertMantissaToTokens({ + token: input.swap.fromToken, + value: + input.swap.direction === 'exact-in' + ? (input.swap as ExactInSwapQuote).fromTokenAmountSoldMantissa + : (input.swap as ExactOutSwapQuote).expectedFromTokenAmountSoldMantissa, + }) ?? '0' + ).toNumber(), toTokenSymbol: input.swap.toToken.symbol, - toTokenAmountTokens: convertMantissaToTokens({ - token: input.swap.toToken, - value: - input.swap.direction === 'exactAmountIn' - ? input.swap.expectedToTokenAmountReceivedMantissa - : input.swap.toTokenAmountReceivedMantissa, - }).toNumber(), + toTokenAmountTokens: ( + convertMantissaToTokens({ + token: input.swap.toToken, + value: + input.swap.direction === 'exact-in' + ? (input.swap as ExactInSwapQuote).expectedToTokenAmountReceivedMantissa + : (input.swap as ExactOutSwapQuote).toTokenAmountReceivedMantissa, + }) ?? '0' + ).toNumber(), priceImpactPercentage: input.swap.priceImpactPercentage, slippageTolerancePercentage: DEFAULT_SLIPPAGE_TOLERANCE_PERCENTAGE, - exchangeRate: input.swap.exchangeRate.toNumber(), + // exchangeRate: input.swap.exchangeRate.toNumber(), }); queryClient.invalidateQueries({ diff --git a/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts b/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts index 8774c3c56f..36386b8c0b 100644 --- a/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts +++ b/apps/evm/src/clients/api/queries/getSwapQuote/useGetSwapQuote.ts @@ -2,8 +2,8 @@ import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; import FunctionKey from 'constants/functionKey'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; -import type { VError } from 'libs/errors'; import { useChainId } from 'libs/wallet'; +import type { SwapQuoteError } from 'types'; import { callOrThrow, generatePseudoRandomRefetchInterval } from 'utilities'; import { type GetApproximateOutSwapQuoteInput, @@ -20,7 +20,7 @@ export type TrimmedGetSwapQuoteInput = type Options = QueryObserverOptions< GetSwapQuoteOutput, - VError<'swapQuote' | 'interaction'>, + SwapQuoteError, GetSwapQuoteOutput, GetSwapQuoteOutput, [FunctionKey.GET_SWAP_QUOTE, TrimmedGetSwapQuoteInput] diff --git a/apps/evm/src/hooks/useIsFeatureEnabled/index.tsx b/apps/evm/src/hooks/useIsFeatureEnabled/index.tsx index aba9a195e5..6d1704b5ef 100644 --- a/apps/evm/src/hooks/useIsFeatureEnabled/index.tsx +++ b/apps/evm/src/hooks/useIsFeatureEnabled/index.tsx @@ -2,7 +2,7 @@ import { useChainId } from 'libs/wallet'; import { ChainId } from 'types'; export const featureFlags = { - integratedSwap: [ChainId.SEPOLIA], + integratedSwap: [ChainId.BSC_MAINNET], prime: [ ChainId.BSC_MAINNET, ChainId.BSC_TESTNET, diff --git a/apps/evm/src/libs/analytics/useAnalytics/types.ts b/apps/evm/src/libs/analytics/useAnalytics/types.ts index 7afdc59bc7..04cc6208be 100644 --- a/apps/evm/src/libs/analytics/useAnalytics/types.ts +++ b/apps/evm/src/libs/analytics/useAnalytics/types.ts @@ -41,7 +41,7 @@ type SwapAndSupply = AnalyticEvent & { toTokenAmountTokens: number; priceImpactPercentage: number; slippageTolerancePercentage: number; - exchangeRate: number; + exchangeRate?: number; }; type CollateralChange = AnalyticEvent & { diff --git a/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json b/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json new file mode 100644 index 0000000000..ee259516a8 --- /dev/null +++ b/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json @@ -0,0 +1,67 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_logic", "type": "address" }, + { "internalType": "address", "name": "admin_", "type": "address" }, + { "internalType": "bytes", "name": "_data", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "previousAdmin", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAdmin", "type": "address" } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "beacon", "type": "address" }], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "implementation", "type": "address" } + ], + "name": "Upgraded", + "type": "event" + }, + { "stateMutability": "payable", "type": "fallback" }, + { + "inputs": [], + "name": "admin", + "outputs": [{ "internalType": "address", "name": "admin_", "type": "address" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [{ "internalType": "address", "name": "implementation_", "type": "address" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newImplementation", "type": "address" }], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newImplementation", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/apps/evm/src/libs/contracts/config/index.ts b/apps/evm/src/libs/contracts/config/index.ts index 1da620cf8f..6f0f32a65e 100644 --- a/apps/evm/src/libs/contracts/config/index.ts +++ b/apps/evm/src/libs/contracts/config/index.ts @@ -126,6 +126,7 @@ import nexusAbi from './externalAbis/Nexus.json'; import nexusAccountFactoryAbi from './externalAbis/NexusAccountFactory.json'; import nexusBoostrapAbi from './externalAbis/NexusBootstrap.json'; import pancakePairV2Abi from './externalAbis/PancakePairV2.json'; +import swapRouterV2Abi from './externalAbis/SwapRouterV2.json'; import vBnbAbi from './externalAbis/VBnb.json'; import zyFiVaultAbi from './externalAbis/ZyFiVault.json'; @@ -729,6 +730,13 @@ export const contracts: ContractConfig[] = [ abi: pancakePairV2Abi as Abi, }, // SwapRouter contract + { + name: 'SwapRouterV2', + abi: swapRouterV2Abi as Abi, + address: { + [ChainId.BSC_MAINNET]: '0x511fa4e04d47e2d80db9fd334359740be022aa35', + }, + }, { name: 'SwapRouter', abi: swapRouterAbi as Abi, diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx index 05f9920d77..03565482f7 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/index.tsx @@ -1,5 +1,5 @@ -import { type VError, handleError } from 'libs/errors'; -import type { Asset, Pool, SwapQuote } from 'types'; +import { handleError } from 'libs/errors'; +import type { Asset, Pool, SwapQuote, SwapQuoteError } from 'types'; import type BigNumber from 'bignumber.js'; import type { FormError } from '../../types'; @@ -17,7 +17,7 @@ export interface UseFormInput { setFormValues: (setter: (currentFormValues: FormValues) => FormValues) => void; initialFormValues: FormValues; swapQuote?: SwapQuote; - getSwapQuoteError?: VError<'swapQuote' | 'interaction'>; + getSwapQuoteError?: SwapQuoteError; expectedSuppliedAmountTokens?: BigNumber; simulatedPool?: Pool; } diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts index 333bf238bc..ac248aeefd 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/useForm/useFormValidation.ts @@ -6,9 +6,8 @@ import { HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; -import type { VError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; -import type { Asset, Pool, SwapQuote } from 'types'; +import type { Asset, Pool, SwapQuote, SwapQuoteError } from 'types'; import { formatTokensToReadableValue } from 'utilities'; import type { FormError } from '../../types'; import type { FormErrorCode, FormValues } from './types'; @@ -19,7 +18,7 @@ interface UseFormValidationInput { formValues: FormValues; limitTokens: BigNumber; swapQuote?: SwapQuote; - getSwapQuoteError?: VError<'swapQuote' | 'interaction'>; + getSwapQuoteError?: SwapQuoteError; expectedSuppliedAmountTokens?: BigNumber; simulatedPool?: Pool; } diff --git a/apps/evm/src/pages/Market/OperationForm/OperationDetails/SwapDetails/index.tsx b/apps/evm/src/pages/Market/OperationForm/OperationDetails/SwapDetails/index.tsx index 6e239a61c0..95768a3a15 100644 --- a/apps/evm/src/pages/Market/OperationForm/OperationDetails/SwapDetails/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/OperationDetails/SwapDetails/index.tsx @@ -3,12 +3,12 @@ import { Accordion } from 'components'; import { SwapDetails as SwapDetailsComp } from 'containers/SwapDetails'; import useConvertMantissaToReadableTokenString from 'hooks/useConvertMantissaToReadableTokenString'; import { useTranslation } from 'libs/translations'; -import type { Swap } from 'types'; +import type { ExactOutSwapQuote, Swap, SwapQuote } from 'types'; import TEST_IDS from '../testIds'; export interface SwapDetailsProps extends React.HTMLAttributes { action: 'repay' | 'supply'; - swap: Swap; + swap: Swap | SwapQuote; className?: string; } @@ -17,15 +17,15 @@ export const SwapDetails: React.FC = ({ swap, action, ...other const readableToTokenAmountReceived = useConvertMantissaToReadableTokenString({ value: - swap.direction === 'exactAmountIn' + swap.direction === 'exactAmountIn' || swap.direction === 'exact-in' ? swap.expectedToTokenAmountReceivedMantissa - : swap.toTokenAmountReceivedMantissa, + : (swap as ExactOutSwapQuote).toTokenAmountReceivedMantissa, // TODO: type check? token: swap.toToken, }); const getAccordionTitle = () => { if (action === 'repay') { - return swap.direction === 'exactAmountIn' + return swap.direction === 'exactAmountIn' || swap.direction === 'exact-in' ? t('operationForm.swapDetails.value.estimatedAmount', { value: readableToTokenAmountReceived, }) diff --git a/apps/evm/src/pages/Market/OperationForm/OperationDetails/index.tsx b/apps/evm/src/pages/Market/OperationForm/OperationDetails/index.tsx index 0337fd5684..da48a14d25 100644 --- a/apps/evm/src/pages/Market/OperationForm/OperationDetails/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/OperationDetails/index.tsx @@ -1,6 +1,6 @@ import { BalanceUpdates, Delimiter } from 'components'; import { AccountData } from 'containers/AccountData'; -import type { BalanceMutation, Pool, Swap, TokenAction } from 'types'; +import type { BalanceMutation, Pool, Swap, SwapQuote, TokenAction } from 'types'; import { ApyBreakdown } from '../ApyBreakdown'; import { SwapDetails } from './SwapDetails'; @@ -11,7 +11,7 @@ export interface OperationDetailsProps { simulatedPool?: Pool; showApyBreakdown?: boolean; isUsingSwap?: boolean; - swap?: Swap; + swap?: Swap | SwapQuote; } export const OperationDetails: React.FC = ({ diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx index e04bc97af6..853835166c 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/index.tsx @@ -1,5 +1,5 @@ -import { type VError, handleError } from 'libs/errors'; -import type { Asset, Pool, SwapQuote } from 'types'; +import { handleError } from 'libs/errors'; +import type { Asset, Pool, SwapQuote, SwapQuoteError } from 'types'; import type BigNumber from 'bignumber.js'; import useIsMounted from 'hooks/useIsMounted'; @@ -21,7 +21,7 @@ export interface UseFormInput { isUsingSwap: boolean; simulatedPool?: Pool; swapQuote?: SwapQuote; - getSwapQuoteError?: VError<'swapQuote' | 'interaction'>; + getSwapQuoteError?: SwapQuoteError; } interface UseFormOutput { diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts index 861fa02a80..575ebe62f1 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/useForm/useFormValidation.ts @@ -6,9 +6,8 @@ import { HEALTH_FACTOR_MODERATE_THRESHOLD, } from 'constants/healthFactor'; import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; -import type { VError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; -import type { Asset, Pool, SwapQuote } from 'types'; +import type { Asset, Pool, SwapQuote, SwapQuoteError } from 'types'; import type { FormError } from '../../../types'; import type { FormErrorCode, FormValues } from './types'; @@ -19,7 +18,7 @@ interface UseFormValidationInput { isUsingSwap: boolean; simulatedPool?: Pool; swapQuote?: SwapQuote; - getSwapQuoteError?: VError<'swapQuote' | 'interaction'>; + getSwapQuoteError?: SwapQuoteError; } interface UseFormValidationOutput { diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/Notice.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/Notice.tsx index 4adc5addd4..d5a4991c0c 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/Notice.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/Notice.tsx @@ -1,10 +1,10 @@ import { NoticeWarning } from 'components'; import { HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; import { useTranslation } from 'libs/translations'; -import type { Swap } from 'types'; +import type { SwapQuote } from 'types'; export interface NoticeProps { - swap?: Swap; + swap?: SwapQuote; } const Notice: React.FC = ({ swap }) => { diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/SubmitSection/index.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/SubmitSection/index.tsx index bb26c14cfe..211908de6d 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/SubmitSection/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/SubmitSection/index.tsx @@ -5,7 +5,7 @@ import { cn } from '@venusprotocol/ui'; import { ApproveTokenSteps, type ApproveTokenStepsProps, PrimaryButton } from 'components'; import { HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; import { useTranslation } from 'libs/translations'; -import type { Swap, Token } from 'types'; +import type { Swap, SwapQuote, Token } from 'types'; import { SwitchChain } from 'containers/SwitchChain'; import SwapSummary from '../../SwapSummary'; @@ -23,7 +23,7 @@ export interface SubmitSectionProps { isApproveFromTokenLoading: ApproveTokenStepsProps['isApproveTokenLoading']; isFromTokenWalletSpendingLimitLoading: ApproveTokenStepsProps['isWalletSpendingLimitLoading']; isRevokeFromTokenWalletSpendingLimitLoading: boolean; - swap?: Swap; + swap?: Swap | SwapQuote; formError?: FormError; isUsingSwap: boolean; } diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx index 73cee187bc..1ac4e0f0b1 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx @@ -19,7 +19,14 @@ import useGetSwapInfo from 'hooks/useGetSwapInfo'; import useGetSwapTokenUserBalances from 'hooks/useGetSwapTokenUserBalances'; import { type UseIsFeatureEnabledInput, useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { en } from 'libs/translations'; -import type { Asset, BalanceMutation, Pool, Swap, TokenBalance } from 'types'; +import type { + Asset, + BalanceMutation, + ExactInSwapQuote, + Pool, + SwapQuote, + TokenBalance, +} from 'types'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; import { areTokensEqual } from 'utilities'; @@ -33,6 +40,8 @@ import { import { fakeAsset, fakePool } from '../__testUtils__/fakeData'; import SUPPLY_FORM_TEST_IDS from '../testIds'; +BigNumber.config({ EXPONENTIAL_AT: 1e9 }); + const fakeBusdWalletBalanceMantissa = new BigNumber(FAKE_BUSD_BALANCE_TOKENS).multipliedBy( new BigNumber(10).pow(busd.decimals), ); @@ -47,16 +56,23 @@ const fakeMarginWithSupplyCapMantissa = fakeMarginWithSupplyCapTokens.multiplied new BigNumber(10).pow(xvs.decimals), ); -const fakeSwap: Swap = { +const fakeSwap: SwapQuote = { fromToken: busd, - fromTokenAmountSoldMantissa: fakeBusdAmountBellowWalletBalanceMantissa, + fromTokenAmountSoldMantissa: BigInt( + fakeBusdAmountBellowWalletBalanceMantissa.integerValue().toString(), + ), toToken: xvs, - expectedToTokenAmountReceivedMantissa: fakeMarginWithSupplyCapMantissa, - minimumToTokenAmountReceivedMantissa: fakeMarginWithSupplyCapMantissa, - exchangeRate: fakeMarginWithSupplyCapMantissa.div(fakeBusdAmountBellowWalletBalanceMantissa), - routePath: [busd.address, xvs.address], + expectedToTokenAmountReceivedMantissa: BigInt( + fakeMarginWithSupplyCapMantissa.integerValue().toString(), + ), + minimumToTokenAmountReceivedMantissa: BigInt( + fakeMarginWithSupplyCapMantissa.integerValue().toString(), + ), + // exchangeRate: fakeMarginWithSupplyCapMantissa.div(fakeBusdAmountBellowWalletBalanceMantissa), + // routePath: [busd.address, xvs.address], priceImpactPercentage: 0.001, - direction: 'exactAmountIn', + direction: 'exact-in', + callData: '0x', }; vi.mock('hooks/useGetSwapTokenUserBalances'); @@ -225,9 +241,11 @@ describe('SupplyForm - Feature flag enabled: integratedSwap', () => { }); it('disables submit button if amount entered in input would have a higher value than supply cap after swapping', async () => { - const customFakeSwap: Swap = { + const customFakeSwap: Partial = { ...fakeSwap, - expectedToTokenAmountReceivedMantissa: fakeMarginWithSupplyCapMantissa.plus(1), + expectedToTokenAmountReceivedMantissa: BigInt( + fakeMarginWithSupplyCapMantissa.plus(1).toString(), + ), }; (useGetSwapInfo as Mock).mockImplementation(() => ({ @@ -424,7 +442,7 @@ describe('SupplyForm - Feature flag enabled: integratedSwap', () => { }); it('displays warning notice and set correct submit button label if the swap has a high price impact', async () => { - const customFakeSwap: Swap = { + const customFakeSwap: SwapQuote = { ...fakeSwap, priceImpactPercentage: HIGH_PRICE_IMPACT_THRESHOLD_PERCENTAGE, }; @@ -468,7 +486,7 @@ describe('SupplyForm - Feature flag enabled: integratedSwap', () => { }); it('disables submit button when price impact has reached the maximum tolerated', async () => { - const customFakeSwap: Swap = { + const customFakeSwap: SwapQuote = { ...fakeSwap, priceImpactPercentage: MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, }; diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx index 665f21e4fd..5ebece5dd1 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { cn } from '@venusprotocol/ui'; -import { useSupply, useSwapTokensAndSupply } from 'clients/api'; +import { useGetSwapQuote, useSupply, useSwapTokensAndSupply } from 'clients/api'; import { Delimiter, LabeledInlineContent, @@ -13,14 +13,13 @@ import { } from 'components'; import { useCollateral } from 'hooks/useCollateral'; import useFormatTokensToReadableValue from 'hooks/useFormatTokensToReadableValue'; -import useGetSwapInfo from 'hooks/useGetSwapInfo'; import useGetSwapTokenUserBalances from 'hooks/useGetSwapTokenUserBalances'; import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import useTokenApproval from 'hooks/useTokenApproval'; import { VError, handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { useAccountAddress, useAccountChainId, useChainId } from 'libs/wallet'; -import type { Asset, BalanceMutation, Pool, Swap, SwapError, TokenBalance } from 'types'; +import type { Asset, BalanceMutation, Pool, SwapQuote, SwapQuoteError, TokenBalance } from 'types'; import { areTokensEqual, convertMantissaToTokens, @@ -30,10 +29,12 @@ import { isCollateralActionDisabled, } from 'utilities'; +import { NULL_ADDRESS } from 'constants/address'; import { ConnectWallet } from 'containers/ConnectWallet'; import { SwitchChainNotice } from 'containers/SwitchChainNotice'; import useDebounceValue from 'hooks/useDebounceValue'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; +import { useGetUserSlippageTolerance } from 'hooks/useGetUserSlippageTolerance'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; import { useAnalytics } from 'libs/analytics'; import { ApyBreakdown } from '../ApyBreakdown'; @@ -70,8 +71,8 @@ export interface SupplyFormUiProps isUsingSwap: boolean; onSubmitSuccess?: () => void; fromTokenWalletSpendingLimitTokens?: BigNumber; - swap?: Swap; - swapError?: SwapError; + swap?: SwapQuote; + swapError?: SwapQuoteError; } export const SupplyFormUi: React.FC = ({ @@ -615,13 +616,31 @@ const SupplyForm: React.FC = ({ ); const debouncedFormAmountTokens = useDebounceValue(formValues.amountTokens); + const fromTokenAmountTokens = new BigNumber(debouncedFormAmountTokens || 0); - const swapInfo = useGetSwapInfo({ - fromToken: formValues.fromToken, - fromTokenAmountTokens: debouncedFormAmountTokens, - toToken: asset.vToken.underlyingToken, - direction: 'exactAmountIn', + const { address: leverageManagerContractAddress } = useGetContractAddress({ + name: 'LeverageManager', }); + const { userSlippageTolerancePercentage } = useGetUserSlippageTolerance(); + const { + data: getSwapQuoteData, + error: getSwapQuoteError, + isLoading: isGetSwapQuoteLoading, + } = useGetSwapQuote( + { + fromToken: formValues.fromToken, + fromTokenAmountTokens, + toToken: asset.vToken.underlyingToken, + direction: 'exact-in', + recipientAddress: leverageManagerContractAddress || NULL_ADDRESS, + slippagePercentage: userSlippageTolerancePercentage, + }, + { + enabled: + isUsingSwap && !!leverageManagerContractAddress && fromTokenAmountTokens.isGreaterThan(0), + }, + ); + const swapQuote = getSwapQuoteData?.swapQuote; return ( = ({ integratedSwapTokenBalances={integratedSwapTokenBalancesData} onSubmit={onSubmit} isSubmitting={isSubmitting} - swap={swapInfo.swap} - swapError={swapInfo.error} - isSwapLoading={swapInfo.isLoading} + swap={swapQuote} + swapError={getSwapQuoteError} + isSwapLoading={isGetSwapQuoteLoading} isFromTokenApproved={isFromTokenApproved} approveFromToken={approveFromToken} isApproveFromTokenLoading={isApproveFromTokenLoading} diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/index.tsx index 2d2596c5c0..3d1dceb138 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/index.tsx @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js'; import { handleError, isUserRejectedTxError } from 'libs/errors'; -import type { Asset, Swap, SwapError, Token } from 'types'; +import type { Asset, SwapQuote, SwapQuoteError, Token } from 'types'; import { useAnalytics } from 'libs/analytics'; import { calculateAmountDollars } from '../../calculateAmountDollars'; @@ -17,7 +17,7 @@ export interface UseFormInput { onSubmit: (input: { fromToken: Token; fromTokenAmountTokens: string; - swap?: Swap; + swap?: SwapQuote; }) => Promise; formValues: FormValues; setFormValues: (setter: (currentFormValues: FormValues) => FormValues) => void; @@ -26,8 +26,8 @@ export interface UseFormInput { isFromTokenApproved?: boolean; fromTokenWalletSpendingLimitTokens?: BigNumber; fromTokenUserWalletBalanceTokens?: BigNumber; - swap?: Swap; - swapError?: SwapError; + swap?: SwapQuote; + swapError?: SwapQuoteError; } interface UseFormOutput { diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/types.ts b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/types.ts index 3a05d067ad..4dfb5f0e1c 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/types.ts +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/types.ts @@ -11,7 +11,8 @@ export type FormErrorCode = | 'HIGHER_THAN_SUPPLY_CAP' | 'HIGHER_THAN_WALLET_BALANCE' | 'HIGHER_THAN_WALLET_SPENDING_LIMIT' - | 'SWAP_INSUFFICIENT_LIQUIDITY' - | 'SWAP_WRAPPING_UNSUPPORTED' - | 'SWAP_UNWRAPPING_UNSUPPORTED' - | 'SWAP_PRICE_IMPACT_TOO_HIGH'; + // | 'SWAP_INSUFFICIENT_LIQUIDITY' + // | 'SWAP_WRAPPING_UNSUPPORTED' + // | 'SWAP_UNWRAPPING_UNSUPPORTED' + | 'SWAP_PRICE_IMPACT_TOO_HIGH' + | 'NO_SWAP_QUOTE_FOUND'; diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/useFormValidation.ts b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/useFormValidation.ts index 75a1212b45..68f895fad3 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/useFormValidation.ts +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/useForm/useFormValidation.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; import { useTranslation } from 'libs/translations'; -import type { Asset, Swap, SwapError } from 'types'; +import type { Asset, SwapQuote, SwapQuoteError } from 'types'; import { formatTokensToReadableValue } from 'utilities'; import { getSwapToTokenAmountReceivedTokens } from 'utilities/getSwapToTokenAmountReceived'; import type { FormError } from '../../types'; @@ -16,8 +16,8 @@ interface UseFormValidationInput { fromTokenWalletSpendingLimitTokens?: BigNumber; isFromTokenApproved?: boolean; isUsingSwap: boolean; - swap?: Swap; - swapError?: SwapError; + swap?: SwapQuote; + swapError?: SwapQuoteError; } interface UseFormValidationOutput { @@ -38,25 +38,11 @@ const useFormValidation = ({ const { t } = useTranslation(); const formError: FormError | undefined = useMemo(() => { - const swapErrorMapping: { - [key: string]: FormError; - } = { - INSUFFICIENT_LIQUIDITY: { - code: 'SWAP_INSUFFICIENT_LIQUIDITY', - message: t('operationForm.error.insufficientSwapLiquidity'), - }, - WRAPPING_UNSUPPORTED: { - code: 'SWAP_WRAPPING_UNSUPPORTED', - message: t('operationForm.error.wrappingUnsupported'), - }, - UNWRAPPING_UNSUPPORTED: { - code: 'SWAP_UNWRAPPING_UNSUPPORTED', - message: t('operationForm.error.unwrappingUnsupported'), - }, - }; - - if (isUsingSwap && swapError && swapError in swapErrorMapping) { - return swapErrorMapping[swapError]; + if (isUsingSwap && swapError?.code === 'noSwapQuoteFound') { + return { + code: 'NO_SWAP_QUOTE_FOUND', + message: t('operationForm.error.noSwapQuoteFound'), + }; } if ( diff --git a/apps/evm/src/pages/Market/OperationForm/SwapSummary/index.tsx b/apps/evm/src/pages/Market/OperationForm/SwapSummary/index.tsx index 34d79cfaca..4a80ef133c 100644 --- a/apps/evm/src/pages/Market/OperationForm/SwapSummary/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SwapSummary/index.tsx @@ -1,14 +1,14 @@ import { useMemo } from 'react'; import { useTranslation } from 'libs/translations'; -import type { Swap } from 'types'; +import type { ExactOutSwapQuote, Swap, SwapQuote } from 'types'; import { convertMantissaToTokens } from 'utilities'; import TEST_IDS from './testIds'; export interface SwapSummaryProps { type: 'supply' | 'repay'; - swap?: Swap; + swap?: Swap | SwapQuote; } export const SwapSummary: React.FC = ({ swap, type }) => { @@ -20,13 +20,13 @@ export const SwapSummary: React.FC = ({ swap, type }) => { } const fromTokenAmountMantissa = - swap.direction === 'exactAmountIn' + swap.direction === 'exactAmountIn' || swap.direction === 'exact-in' ? swap.fromTokenAmountSoldMantissa - : swap.expectedFromTokenAmountSoldMantissa; + : (swap as ExactOutSwapQuote).expectedFromTokenAmountSoldMantissa; // TODO: type check? const toTokenAmountMantissa = - swap.direction === 'exactAmountIn' + swap.direction === 'exactAmountIn' || swap.direction === 'exact-in' ? swap.expectedToTokenAmountReceivedMantissa - : swap.toTokenAmountReceivedMantissa; + : (swap as ExactOutSwapQuote).toTokenAmountReceivedMantissa; // TODO: type check? const readableFromTokenAmount = convertMantissaToTokens({ value: fromTokenAmountMantissa, diff --git a/apps/evm/src/types/index.ts b/apps/evm/src/types/index.ts index 68e200d000..4006d00ff6 100644 --- a/apps/evm/src/types/index.ts +++ b/apps/evm/src/types/index.ts @@ -2,6 +2,7 @@ import type { Token as PSToken } from '@pancakeswap/sdk'; import type { ChainId, Token, VToken } from '@venusprotocol/chains'; import type { Omit } from '@wagmi/core/internal'; import type BigNumber from 'bignumber.js'; +import type { VError } from 'libs/errors'; import type { Address, ByteArray, Hex } from 'viem'; export { ChainId, type Token, type VToken } from '@venusprotocol/chains'; @@ -587,6 +588,7 @@ export interface ApproximateOutSwapQuote extends SwapQuoteBase { } export type SwapQuote = ExactInSwapQuote | ExactOutSwapQuote | ApproximateOutSwapQuote; +export type SwapQuoteError = VError<'swapQuote' | 'interaction'> | null; export type ImportableProtocol = 'aave'; diff --git a/apps/evm/src/utilities/getSwapToTokenAmountReceived/index.ts b/apps/evm/src/utilities/getSwapToTokenAmountReceived/index.ts index ab73328f44..983261df18 100644 --- a/apps/evm/src/utilities/getSwapToTokenAmountReceived/index.ts +++ b/apps/evm/src/utilities/getSwapToTokenAmountReceived/index.ts @@ -1,10 +1,10 @@ -import type { Swap } from 'types'; +import type { Swap, SwapQuote } from 'types'; import { convertMantissaToTokens } from 'utilities'; -export const getSwapToTokenAmountReceivedTokens = (swap?: Swap) => { +export const getSwapToTokenAmountReceivedTokens = (swap?: Swap | SwapQuote) => { if (swap) { const swapToTokenAmountReceivedMantissa = - swap.direction === 'exactAmountOut' + swap.direction === 'exactAmountOut' || swap.direction === 'exact-out' ? swap.toTokenAmountReceivedMantissa : swap.expectedToTokenAmountReceivedMantissa; From 5e17de5db3111c853a386034f886286e6a1b5411 Mon Sep 17 00:00:00 2001 From: david-sun-venus Date: Mon, 5 Jan 2026 12:12:58 +0800 Subject: [PATCH 5/5] feat(evm): update contract abi, fix type error --- .../mutations/useSwapTokensAndSupply/index.ts | 1 + .../config/externalAbis/SwapRouterV2.json | 246 ++++++++++++++++-- 2 files changed, 226 insertions(+), 21 deletions(-) diff --git a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts index 6076110caa..5b5a0ab4ab 100644 --- a/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts +++ b/apps/evm/src/clients/api/mutations/useSwapTokensAndSupply/index.ts @@ -68,6 +68,7 @@ export const useSwapTokensAndSupply = ( functionName: 'swapAndSupply' as const, args: [ vToken.address, + swap.toToken.address, swap.fromTokenAmountSoldMantissa, swap.minimumToTokenAmountReceivedMantissa, swap.callData, diff --git a/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json b/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json index ee259516a8..1ce30e5595 100644 --- a/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json +++ b/apps/evm/src/libs/contracts/config/externalAbis/SwapRouterV2.json @@ -1,67 +1,271 @@ [ { "inputs": [ - { "internalType": "address", "name": "_logic", "type": "address" }, - { "internalType": "address", "name": "admin_", "type": "address" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } + { "internalType": "contract IComptroller", "name": "_comptroller", "type": "address" }, + { "internalType": "contract SwapHelper", "name": "_swapHelper", "type": "address" }, + { "internalType": "contract IWBNB", "name": "_wrappedNative", "type": "address" }, + { "internalType": "address", "name": "_nativeVToken", "type": "address" } ], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } + ], + "name": "InsufficientAmountOut", + "type": "error" + }, + { "inputs": [], "name": "InsufficientBalance", "type": "error" }, + { + "inputs": [{ "internalType": "address", "name": "vToken", "type": "address" }], + "name": "MarketNotListed", + "type": "error" + }, + { "inputs": [], "name": "NativeTransferFailed", "type": "error" }, + { "inputs": [], "name": "NoTokensReceived", "type": "error" }, + { + "inputs": [{ "internalType": "uint256", "name": "errorCode", "type": "uint256" }], + "name": "RepayFailed", + "type": "error" + }, + { + "inputs": [{ "internalType": "uint256", "name": "errorCode", "type": "uint256" }], + "name": "SupplyFailed", + "type": "error" + }, + { "inputs": [], "name": "SwapFailed", "type": "error" }, + { + "inputs": [{ "internalType": "address", "name": "sender", "type": "address" }], + "name": "UnauthorizedNativeSender", + "type": "error" + }, + { "inputs": [], "name": "ZeroAddress", "type": "error" }, + { "inputs": [], "name": "ZeroAmount", "type": "error" }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, { "anonymous": false, "inputs": [ - { "indexed": false, "internalType": "address", "name": "previousAdmin", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "newAdmin", "type": "address" } + { "indexed": true, "internalType": "address", "name": "user", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "vToken", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "tokenIn", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "tokenOut", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountRepaid", "type": "uint256" } ], - "name": "AdminChanged", + "name": "SwapAndRepay", "type": "event" }, { "anonymous": false, - "inputs": [{ "indexed": true, "internalType": "address", "name": "beacon", "type": "address" }], - "name": "BeaconUpgraded", + "inputs": [ + { "indexed": true, "internalType": "address", "name": "user", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "vToken", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "tokenIn", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "tokenOut", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amountSupplied", "type": "uint256" } + ], + "name": "SwapAndSupply", "type": "event" }, { "anonymous": false, "inputs": [ - { "indexed": true, "internalType": "address", "name": "implementation", "type": "address" } + { "indexed": true, "internalType": "address", "name": "receiver", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "Upgraded", + "name": "SweepNative", "type": "event" }, - { "stateMutability": "payable", "type": "fallback" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "token", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "receiver", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "SweepToken", + "type": "event" + }, + { + "inputs": [], + "name": "COMPTROLLER", + "outputs": [{ "internalType": "contract IComptroller", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_VTOKEN", + "outputs": [{ "internalType": "contract IVBNB", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SWAP_HELPER", + "outputs": [{ "internalType": "contract SwapHelper", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WRAPPED_NATIVE", + "outputs": [{ "internalType": "contract IWBNB", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], - "name": "admin", - "outputs": [{ "internalType": "address", "name": "admin_", "type": "address" }], + "name": "acceptOwnership", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], - "name": "implementation", - "outputs": [{ "internalType": "address", "name": "implementation_", "type": "address" }], + "name": "initialize", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{ "internalType": "address", "name": "newImplementation", "type": "address" }], - "name": "upgradeTo", + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "newImplementation", "type": "address" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } + { "internalType": "address", "name": "vToken", "type": "address" }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "bytes", "name": "swapCallData", "type": "bytes" } ], - "name": "upgradeToAndCall", + "name": "swapAndRepay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vToken", "type": "address" }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, + { "internalType": "bytes", "name": "swapCallData", "type": "bytes" } + ], + "name": "swapAndRepayFull", "outputs": [], "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { "internalType": "address", "name": "vToken", "type": "address" }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "bytes", "name": "swapCallData", "type": "bytes" } + ], + "name": "swapAndSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vToken", "type": "address" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "bytes", "name": "swapCallData", "type": "bytes" } + ], + "name": "swapNativeAndRepay", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "vToken", "type": "address" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "bytes", "name": "swapCallData", "type": "bytes" } + ], + "name": "swapNativeAndSupply", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "contract IERC20Upgradeable", "name": "token", "type": "address" } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "stateMutability": "payable", "type": "receive" } ]