@rango-dev/widget-embedded 0.1.10-next.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +29 -0
  3. package/public/index.html +11 -0
  4. package/readme.md +2 -0
  5. package/src/App.tsx +78 -0
  6. package/src/app.css +28 -0
  7. package/src/components/AppRouter.tsx +15 -0
  8. package/src/components/AppRoutes.tsx +32 -0
  9. package/src/components/BottomLogo.tsx +44 -0
  10. package/src/components/Footer.tsx +8 -0
  11. package/src/components/Header.tsx +25 -0
  12. package/src/components/HeaderButtons.tsx +34 -0
  13. package/src/components/Layout.tsx +55 -0
  14. package/src/components/SwitchFromAndTo.tsx +30 -0
  15. package/src/components/TokenInfo.tsx +218 -0
  16. package/src/components/UpdateUrl.tsx +85 -0
  17. package/src/constants/errors.ts +3 -0
  18. package/src/constants/navigationRoutes.ts +14 -0
  19. package/src/constants/numbers.ts +3 -0
  20. package/src/constants/searchParams.ts +7 -0
  21. package/src/globalStyles.ts +16 -0
  22. package/src/hooks/useBestRoute.ts +70 -0
  23. package/src/hooks/useConfirmSwap.ts +234 -0
  24. package/src/hooks/useTheme.ts +37 -0
  25. package/src/index.tsx +12 -0
  26. package/src/mockData/pendingSwap.ts +607 -0
  27. package/src/pages/ConfirmSwapPage.tsx +24 -0
  28. package/src/pages/ConfirmWalletsPage.tsx +51 -0
  29. package/src/pages/HistoryPage.tsx +793 -0
  30. package/src/pages/Home.tsx +176 -0
  31. package/src/pages/LiquiditySourcesPage.tsx +49 -0
  32. package/src/pages/SelectChainPage.tsx +62 -0
  33. package/src/pages/SelectTokenPage.tsx +84 -0
  34. package/src/pages/SettingsPage.tsx +60 -0
  35. package/src/pages/SwapDetailsPage.tsx +9 -0
  36. package/src/pages/WalletsPage.tsx +61 -0
  37. package/src/services/httpService.ts +3 -0
  38. package/src/store/bestRoute.ts +82 -0
  39. package/src/store/meta.ts +35 -0
  40. package/src/store/selectors.ts +19 -0
  41. package/src/store/settings.ts +72 -0
  42. package/src/store/wallets.ts +144 -0
  43. package/src/utils/common.ts +3 -0
  44. package/src/utils/numbers.ts +94 -0
  45. package/src/utils/routing.ts +108 -0
  46. package/src/utils/swap.ts +155 -0
  47. package/src/utils/wallets.ts +345 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## 0.1.10-next.1 (2023-01-11)
7
+
8
+ **Note:** Version bump only for package @rango-dev/widget-embedded
9
+
10
+ ## 0.1.10-next.0 (2023-01-10)
11
+
12
+ **Note:** Version bump only for package @rango-dev/widget-embedded
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@rango-dev/widget-embedded",
3
+ "version": "0.1.10-next.69",
4
+ "license": "MIT",
5
+ "source": "public/index.html",
6
+ "browserslist": "> 0.5%, last 2 versions, not dead",
7
+ "scripts": {
8
+ "dev": "parcel serve -p 3000",
9
+ "build": "parcel build"
10
+ },
11
+ "dependencies": {
12
+ "@rango-dev/provider-all": "^0.1.11-next.69",
13
+ "@rango-dev/ui": "^0.1.10-next.69",
14
+ "@rango-dev/wallets-core": "^0.1.11-next.69",
15
+ "bignumber.js": "^9.1.1",
16
+ "immer": "^9.0.19",
17
+ "mitt": "^3.0.0",
18
+ "parcel": "^2.8.0",
19
+ "rango-sdk": "^0.1.12",
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
+ "react-router-dom": "^6.8.0",
23
+ "zustand": "^4.3.2"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "gitHead": "35fad4db1d491a9589a8215b0c4a4e2f541d07f2"
29
+ }
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Rango Embedded Widget</title>
6
+ </head>
7
+ <body>
8
+ <div id="app"></div>
9
+ <script type="module" src="../src/index.tsx"></script>
10
+ </body>
11
+ </html>
package/readme.md ADDED
@@ -0,0 +1,2 @@
1
+
2
+ Widget will use `components`, `queues` and `wallets` to make a react app to be embedded as widget.
package/src/App.tsx ADDED
@@ -0,0 +1,78 @@
1
+ import { SwapContainer } from '@rango-dev/ui';
2
+ import React from 'react';
3
+ import { AppRouter } from './components/AppRouter';
4
+ import { useMetaStore } from './store/meta';
5
+ import './app.css';
6
+ import { Events, Provider } from '@rango-dev/wallets-core';
7
+ import { allProviders } from '@rango-dev/provider-all';
8
+ import { EventHandler } from '@rango-dev/wallets-core/dist/wallet';
9
+ import { isEvmBlockchain, Network } from '@rango-dev/wallets-shared';
10
+ import { prepareAccountsForWalletStore, walletAndSupportedChainsNames } from './utils/wallets';
11
+ import { useWalletsStore } from './store/wallets';
12
+ import { Layout } from './components/Layout';
13
+ import { globalStyles } from './globalStyles';
14
+ import { useTheme } from './hooks/useTheme';
15
+
16
+ const providers = allProviders();
17
+ interface Token {
18
+ name: string;
19
+ contractAddress?: string;
20
+ }
21
+
22
+ //todo: update interface and update widget state based on WidgetProps change
23
+ export type WidgetProps = {
24
+ fromChain?: string;
25
+ fromToken?: string;
26
+ toChain?: string;
27
+ toToken?: string;
28
+ fromAmount?: string;
29
+ slippage?: number;
30
+ chains?: string[];
31
+ tokens?: Token[];
32
+ liquiditySources?: string[];
33
+ theme: 'dark' | 'light' | 'auto';
34
+ };
35
+
36
+ export function App() {
37
+ globalStyles();
38
+ const { activeTheme } = useTheme();
39
+ const { blockchains } = useMetaStore.use.meta();
40
+ const disconnectWallet = useWalletsStore.use.disconnectWallet();
41
+ const connectWallet = useWalletsStore.use.connectWallet();
42
+ const evmBasedChainNames = blockchains
43
+ //@ts-ignore
44
+ .filter(isEvmBlockchain)
45
+ .map((chain) => chain.name);
46
+
47
+ const onUpdateState: EventHandler = (type, event, value, state, supportedChains) => {
48
+ if (event === Events.ACCOUNTS) {
49
+ if (value) {
50
+ const supportedChainNames: Network[] | null =
51
+ //@ts-ignore
52
+ walletAndSupportedChainsNames(supportedChains);
53
+ const data = prepareAccountsForWalletStore(
54
+ type,
55
+ value,
56
+ evmBasedChainNames,
57
+ supportedChainNames,
58
+ );
59
+ connectWallet(data);
60
+ } else {
61
+ disconnectWallet(type);
62
+ }
63
+ }
64
+ };
65
+
66
+ return (
67
+ //@ts-ignore
68
+ <Provider allBlockChains={blockchains} providers={providers} onUpdateState={onUpdateState}>
69
+ <div id="pageContainer" className={activeTheme}>
70
+ <SwapContainer>
71
+ <AppRouter>
72
+ <Layout />
73
+ </AppRouter>
74
+ </SwapContainer>
75
+ </div>
76
+ </Provider>
77
+ );
78
+ }
package/src/app.css ADDED
@@ -0,0 +1,28 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
2
+ * {
3
+ font-family: 'Roboto';
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ list-style-type: none;
8
+ }
9
+
10
+ input::-webkit-outer-spin-button,
11
+ input::-webkit-inner-spin-button {
12
+ -webkit-appearance: none;
13
+ margin: 0;
14
+ }
15
+ input[type='number'] {
16
+ -moz-appearance: textfield;
17
+ }
18
+
19
+ #app {
20
+ height: 100vh;
21
+ }
22
+ #pageContainer {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ height: 100%;
27
+ width: 100%;
28
+ }
@@ -0,0 +1,15 @@
1
+ import React, { Fragment, PropsWithChildren } from 'react';
2
+ import { MemoryRouter, useInRouterContext } from 'react-router';
3
+ import { UpdateUrl } from './UpdateUrl';
4
+
5
+ export function AppRouter({ children }: PropsWithChildren) {
6
+ const isRouterInContext = useInRouterContext();
7
+ const Router = isRouterInContext ? Fragment : MemoryRouter;
8
+
9
+ return (
10
+ <>
11
+ <Router>{children}</Router>
12
+ {isRouterInContext && <UpdateUrl />}
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { useRoutes } from 'react-router-dom';
3
+ import { navigationRoutes } from '../constants/navigationRoutes';
4
+ import { ConfirmSwapPage } from '../pages/ConfirmSwapPage';
5
+ import { HistoryPage } from '../pages/HistoryPage';
6
+ import { Home } from '../pages/Home';
7
+ import { LiquiditySourcePage } from '../pages/LiquiditySourcesPage';
8
+ import { SelectChainPage } from '../pages/SelectChainPage';
9
+ import { SelectTokenPage } from '../pages/SelectTokenPage';
10
+ import { SettingsPage } from '../pages/SettingsPage';
11
+ import { WalletsPage } from '../pages/WalletsPage';
12
+ import { ConfirmWalletsPage } from '../pages/ConfirmWalletsPage';
13
+ import { SwapDetailsPage } from '../pages/SwapDetailsPage';
14
+
15
+ export const AppRoutes = () =>
16
+ useRoutes([
17
+ { path: navigationRoutes.home, element: <Home /> },
18
+ { path: navigationRoutes.fromChain, element: <SelectChainPage type="from" /> },
19
+ { path: navigationRoutes.toChain, element: <SelectChainPage type="to" /> },
20
+ { path: navigationRoutes.fromToken + '/*', element: <SelectTokenPage type="from" /> },
21
+ { path: navigationRoutes.toToken, element: <SelectTokenPage type="to" /> },
22
+ { path: navigationRoutes.settings, element: <SettingsPage /> },
23
+ {
24
+ path: navigationRoutes.liquiditySources,
25
+ element: <LiquiditySourcePage />,
26
+ },
27
+ { path: navigationRoutes.history, element: <HistoryPage /> },
28
+ { path: navigationRoutes.wallets, element: <WalletsPage /> },
29
+ { path: navigationRoutes.confirmSwap, element: <ConfirmSwapPage /> },
30
+ { path: navigationRoutes.confirmWallets, element: <ConfirmWalletsPage /> },
31
+ { path: navigationRoutes.swapDetails, element: <SwapDetailsPage /> },
32
+ ]);
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { styled } from '@rango-dev/ui/src/theme';
3
+ import { Typography } from '@rango-dev/ui';
4
+
5
+ const Container = styled('div', {
6
+ display: 'flex',
7
+ width: '100%',
8
+ justifyContent: 'end',
9
+ alignItems: 'center',
10
+ paddingTop: '$16',
11
+ });
12
+
13
+ const Logo = styled('svg', {
14
+ fill: '$foreground',
15
+ width: '$24',
16
+ margin: '0 $8 0 $16',
17
+ });
18
+
19
+ const StyledAnchor = styled('a', {
20
+ display: 'flex',
21
+ justifyContent: 'center',
22
+ alignItems: 'center',
23
+ textDecoration: 'none',
24
+ });
25
+
26
+ export function BottomLogo() {
27
+ return (
28
+ <Container>
29
+ <Typography variant="caption">Powered By</Typography>
30
+ <StyledAnchor href="https://rango.exchange" target="_blank">
31
+ <Logo xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.68 29">
32
+ <defs></defs>
33
+ <g id="Layer_2" data-name="Layer 2">
34
+ <g id="Layer_1-2" data-name="Layer 1">
35
+ <path d="M33.68,18.05A17.93,17.93,0,0,0,28.34,5.29,18.31,18.31,0,0,0,15.45,0H13.54V7.41L6.07,0H.69L9.83,9.07H0V11A17.93,17.93,0,0,0,5.34,23.71,18.32,18.32,0,0,0,18.24,29h1.9V21.59l7.38,7.31h5.39l-9-9h9.82ZM17.37,16.16H12.49a1.93,1.93,0,0,0-1.35.55,1.89,1.89,0,0,0-.56,1.34,1.85,1.85,0,0,0,.56,1.33,1.93,1.93,0,0,0,1.35.55h3.84V25.1A14.43,14.43,0,0,1,8,21a14.21,14.21,0,0,1-4.1-8.2H21.2a1.86,1.86,0,0,0,.74-.13,1.92,1.92,0,0,0,.64-.4,2,2,0,0,0,.43-.62,1.88,1.88,0,0,0,0-1.47,2,2,0,0,0-.43-.62,1.92,1.92,0,0,0-.64-.4,1.86,1.86,0,0,0-.74-.13H17.35V3.9A14.43,14.43,0,0,1,25.64,8a14.19,14.19,0,0,1,4.11,8.2Zm2.12-5.27a1.43,1.43,0,0,1,.26-.83,1.52,1.52,0,0,1,.67-.56,1.55,1.55,0,0,1,.88-.08,1.51,1.51,0,0,1,.77.4,1.54,1.54,0,0,1,.42.77,1.41,1.41,0,0,1-.09.86,1.47,1.47,0,0,1-.55.68,1.56,1.56,0,0,1-.84.25,1.52,1.52,0,0,1-1.52-1.49Z" />
36
+ <path d="M21.7,11.57a.68.68,0,0,0-.12-.38.64.64,0,0,0-.31-.25.68.68,0,0,0-.75.15.61.61,0,0,0-.19.35.62.62,0,0,0,0,.4.67.67,0,0,0,.25.3.69.69,0,0,0,.39.12.7.7,0,0,0,.49-.2A.68.68,0,0,0,21.7,11.57Z" />
37
+ </g>
38
+ </g>
39
+ </Logo>
40
+ <Typography variant="body2">RANGO</Typography>
41
+ </StyledAnchor>
42
+ </Container>
43
+ );
44
+ }
@@ -0,0 +1,8 @@
1
+ import { styled } from '@rango-dev/ui';
2
+
3
+ export const Footer = styled('div', {
4
+ display: 'flex',
5
+ flexDirection: 'column',
6
+ width: '100%',
7
+ paddingTop: '$16',
8
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { styled, Typography } from '@rango-dev/ui';
3
+ import { HeaderButtons } from './HeaderButtons';
4
+
5
+ export const HeaderContainer = styled('div', {
6
+ display: 'flex',
7
+ justifyContent: 'space-between',
8
+ alignItems: 'center',
9
+ width: '100%',
10
+ padding: '$16 0',
11
+ });
12
+
13
+ export interface PropTypes {
14
+ onClickRefresh: () => void;
15
+ }
16
+
17
+ export function Header(props: PropTypes) {
18
+ const { onClickRefresh } = props;
19
+ return (
20
+ <HeaderContainer>
21
+ <Typography variant="h4">SWAP</Typography>
22
+ <HeaderButtons onClickRefresh={onClickRefresh} />
23
+ </HeaderContainer>
24
+ );
25
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { SettingsIcon, Tooltip, styled, Button, HistoryIcon, RetryIcon } from '@rango-dev/ui';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { navigationRoutes } from '../constants/navigationRoutes';
5
+ import { PropTypes } from './Header';
6
+
7
+ const ButtonsContainer = styled('div', {
8
+ display: 'flex',
9
+ });
10
+
11
+ export function HeaderButtons(props: PropTypes) {
12
+ const { onClickRefresh } = props;
13
+ const navigate = useNavigate();
14
+
15
+ return (
16
+ <ButtonsContainer>
17
+ <Tooltip content="Retry">
18
+ <Button variant="ghost" onClick={onClickRefresh.bind(null)}>
19
+ <RetryIcon size={28} />
20
+ </Button>
21
+ </Tooltip>
22
+ <Tooltip content="Transactions History">
23
+ <Button variant="ghost" onClick={() => navigate(navigationRoutes.history)}>
24
+ <HistoryIcon size={24} />
25
+ </Button>
26
+ </Tooltip>
27
+ <Tooltip content="Settings">
28
+ <Button variant="ghost" onClick={() => navigate(navigationRoutes.settings)}>
29
+ <SettingsIcon size={24} />
30
+ </Button>
31
+ </Tooltip>
32
+ </ButtonsContainer>
33
+ );
34
+ }
@@ -0,0 +1,55 @@
1
+ import { Button, AddWalletIcon, Typography, styled } from '@rango-dev/ui';
2
+ import React from 'react';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { useWallets } from '@rango-dev/wallets-core';
5
+ import { navigationRoutes } from '../constants/navigationRoutes';
6
+ import { AppRoutes } from './AppRoutes';
7
+ import { useWalletsStore } from '../store/wallets';
8
+ import { calculateWalletUsdValue, getSelectableWallets } from '../utils/wallets';
9
+
10
+ const Header = styled('div', {
11
+ display: 'flex',
12
+ width: '100%',
13
+ justifyContent: 'end',
14
+ });
15
+
16
+ const WalletImage = styled('img', {
17
+ width: '$24',
18
+ height: '$24',
19
+ marginLeft: -15,
20
+ marginRight: '$6',
21
+ borderRadius: '99999px',
22
+ });
23
+
24
+ export function Layout() {
25
+ const navigate = useNavigate();
26
+ const { balances, accounts, selectedWallets } = useWalletsStore();
27
+ const { getWalletInfo } = useWallets();
28
+ const filterSelelectedWallets = getSelectableWallets(accounts, selectedWallets, getWalletInfo);
29
+ const totalBalance = calculateWalletUsdValue(balances);
30
+
31
+ return (
32
+ <>
33
+ <Header>
34
+ <Button
35
+ size="small"
36
+ suffix={<AddWalletIcon size={20} />}
37
+ variant="ghost"
38
+ flexContent
39
+ onClick={() => navigate(navigationRoutes.wallets)}>
40
+ {accounts?.length ? (
41
+ filterSelelectedWallets.map((selectedWallet, index) => (
42
+ <WalletImage key={index} src={selectedWallet.image} />
43
+ ))
44
+ ) : (
45
+ <></>
46
+ )}
47
+ <Typography variant="body2">
48
+ {!accounts?.length ? 'Connect Wallet' : `$${totalBalance || 0}`}
49
+ </Typography>
50
+ </Button>
51
+ </Header>
52
+ <AppRoutes />
53
+ </>
54
+ );
55
+ }
@@ -0,0 +1,30 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useSearchParams } from 'react-router-dom';
3
+ import { SearchParams } from '../constants/searchParams';
4
+ import { useBestRouteStore } from '../store/bestRoute';
5
+
6
+ export function SwithFromAndTo({ count }: { count: number }) {
7
+ const firstRender = useRef(true);
8
+ const [searchParams, setSearchParams] = useSearchParams();
9
+ const outputAmount = useBestRouteStore.use.outputAmount();
10
+
11
+ useEffect(() => {
12
+ if (!firstRender.current) {
13
+ const fromChainString = searchParams.get(SearchParams.FROM_CHAIN);
14
+ const fromTokenString = searchParams.get(SearchParams.FROM_TOKEN);
15
+ const toChainString = searchParams.get(SearchParams.TO_CHAIN);
16
+ const toTokenString = searchParams.get(SearchParams.TO_TOKEN);
17
+ setSearchParams({
18
+ ...(toChainString && { [SearchParams.FROM_CHAIN]: toChainString }),
19
+ ...(toTokenString && { [SearchParams.FROM_TOKEN]: toTokenString }),
20
+ ...(fromChainString && { [SearchParams.TO_CHAIN]: fromChainString }),
21
+ ...(fromTokenString && { [SearchParams.TO_TOKEN]: fromTokenString }),
22
+ ...(outputAmount && { [SearchParams.FROM_AMOUNT]: outputAmount.toString() }),
23
+ });
24
+ } else {
25
+ firstRender.current = false;
26
+ }
27
+ }, [count]);
28
+
29
+ return null;
30
+ }
@@ -0,0 +1,218 @@
1
+ import React from 'react';
2
+ import { AngleDownIcon, Button, InfoCircleIcon, styled, TextField, Typography } from '@rango-dev/ui';
3
+ import { useMetaStore } from '../store/meta';
4
+ import { BlockchainMeta, Token } from 'rango-sdk';
5
+ import { useNavigate } from 'react-router-dom';
6
+ import { useBestRouteStore } from '../store/bestRoute';
7
+ import { numberToString } from '../utils/numbers';
8
+ import BigNumber from 'bignumber.js';
9
+ import { getBalanceFromWallet } from '../utils/wallets';
10
+ import { useWalletsStore } from '../store/wallets';
11
+
12
+ type PropTypes = (
13
+ | { type: 'From'; inputAmount: string; onAmountChange: (amount: string) => void }
14
+ | {
15
+ type: 'To';
16
+ outputAmount: BigNumber | null;
17
+ outputUsdValue: BigNumber | null;
18
+ }
19
+ ) & { chain: BlockchainMeta | null; token: Token | null };
20
+
21
+ const Box = styled('div', {
22
+ display: 'flex',
23
+ flexDirection: 'column',
24
+ width: '100%',
25
+ });
26
+
27
+ const Container = styled('div', {
28
+ boxSizing: 'border-box',
29
+ backgroundColor: '$neutrals300',
30
+ borderRadius: '$5',
31
+ padding: '$8 0',
32
+
33
+ '.form': {
34
+ display: 'flex',
35
+ width: '100%',
36
+ padding: '$8 $16',
37
+ },
38
+ });
39
+
40
+ const StyledImage = styled('img', {
41
+ width: '$24',
42
+ maxHeight: '$24',
43
+ });
44
+
45
+ const Options = styled('div', {
46
+ display: 'flex',
47
+ justifyContent: 'flex-end',
48
+ padding: '0 $8',
49
+ });
50
+
51
+ const ImagePlaceholder = styled('span', {
52
+ width: '24px',
53
+ height: '24px',
54
+ backgroundColor: '$neutrals300',
55
+ borderRadius: '99999px',
56
+ });
57
+
58
+ const OutputContainer = styled('div', {
59
+ height: '$48',
60
+ borderRadius: '$5',
61
+ flexGrow: 1,
62
+ width: '70%',
63
+ backgroundColor: '$background',
64
+ border: '1px solid transparent',
65
+ position: 'relative',
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ paddingLeft: '$8',
69
+ paddingRight: '$8',
70
+ });
71
+
72
+ export function TokenInfo(props: PropTypes) {
73
+ const { type, chain, token } = props;
74
+ const loadingStatus = useMetaStore.use.loadingStatus();
75
+ const fromChain = useBestRouteStore.use.fromChain();
76
+ const toChain = useBestRouteStore.use.toChain();
77
+ const inputUsdValue = useBestRouteStore.use.inputUsdValue();
78
+ const fromToken = useBestRouteStore.use.fromToken();
79
+ const setInputAmount = useBestRouteStore.use.setInputAmount();
80
+ const bestRoute = useBestRouteStore.use.bestRoute();
81
+ const inputAmount = useBestRouteStore.use.inputAmount();
82
+ const balances = useWalletsStore.use.balances();
83
+ const navigate = useNavigate();
84
+
85
+ const tokenBalance =
86
+ !!fromChain && !!fromToken
87
+ ? numberToString(
88
+ getBalanceFromWallet(balances, fromChain?.name, fromToken?.symbol, fromToken?.address)
89
+ ?.amount || '0',
90
+ 8,
91
+ )
92
+ : '0';
93
+
94
+ const tokenBalanceReal =
95
+ !!fromChain && !!fromToken
96
+ ? numberToString(
97
+ getBalanceFromWallet(balances, fromChain?.name, fromToken?.symbol, fromToken?.address)
98
+ ?.amount || '0',
99
+ getBalanceFromWallet(balances, fromChain?.name, fromToken?.symbol, fromToken?.address)
100
+ ?.decimal,
101
+ )
102
+ : '0';
103
+
104
+ const ItemSuffix = (
105
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
106
+ {loadingStatus === 'failed' && <InfoCircleIcon color="error" size={24} />}
107
+ <AngleDownIcon />
108
+ </div>
109
+ );
110
+
111
+ return (
112
+ <Box>
113
+ <div>
114
+ <Typography variant="body2">{type}</Typography>
115
+ </div>
116
+ <Container>
117
+ {props.type === 'From' && (
118
+ <Options>
119
+ <div
120
+ className="balance"
121
+ onClick={() => {
122
+ if (tokenBalance !== '0') setInputAmount(tokenBalanceReal.split(',').join(''));
123
+ }}>
124
+ <Button variant="ghost" size="small">
125
+ <Typography variant="body2">{`Max: ${tokenBalance} ${
126
+ fromToken?.symbol || ''
127
+ }`}</Typography>
128
+ </Button>
129
+ </div>
130
+ </Options>
131
+ )}
132
+ <div className="form">
133
+ <Button
134
+ onClick={() => {
135
+ navigate(`/${props.type.toLowerCase()}-chain`);
136
+ }}
137
+ variant="outlined"
138
+ disabled={loadingStatus === 'failed'}
139
+ loading={loadingStatus === 'loading'}
140
+ prefix={
141
+ loadingStatus === 'success' && chain ? (
142
+ <StyledImage src={chain.logo} />
143
+ ) : (
144
+ <ImagePlaceholder />
145
+ )
146
+ }
147
+ suffix={ItemSuffix}
148
+ align="start"
149
+ size="large"
150
+ style={{ marginRight: '.5rem' }}>
151
+ {loadingStatus === 'success' && chain ? chain.name : 'Chain'}
152
+ </Button>
153
+ <Button
154
+ onClick={() => {
155
+ navigate(`/${props.type.toLowerCase()}-token`);
156
+ }}
157
+ variant="outlined"
158
+ disabled={
159
+ loadingStatus === 'failed' ||
160
+ (type === 'From' && !fromChain) ||
161
+ (type === 'To' && !toChain)
162
+ }
163
+ loading={loadingStatus === 'loading'}
164
+ prefix={
165
+ loadingStatus === 'success' && token ? (
166
+ <StyledImage src={token.image} />
167
+ ) : (
168
+ <ImagePlaceholder />
169
+ )
170
+ }
171
+ suffix={ItemSuffix}
172
+ size="large"
173
+ align="start"
174
+ style={{ marginRight: '.5rem' }}>
175
+ {loadingStatus === 'success' && token ? token.symbol : 'Token'}
176
+ </Button>
177
+ {props.type === 'From' ? (
178
+ <TextField
179
+ type="number"
180
+ size="large"
181
+ autoFocus
182
+ placeholder="0"
183
+ style={{
184
+ width: '70%',
185
+ position: 'relative',
186
+ backgroundColor: '$background !important',
187
+ }}
188
+ suffix={
189
+ <span style={{ position: 'absolute', right: '4px', bottom: '2px' }}>
190
+ <Typography variant="caption">{`$${numberToString(inputUsdValue)}`}</Typography>
191
+ </span>
192
+ }
193
+ value={props.inputAmount || ''}
194
+ onChange={
195
+ props.type === 'From'
196
+ ? (event) => {
197
+ props.onAmountChange(event.target.value);
198
+ }
199
+ : undefined
200
+ }
201
+ />
202
+ ) : (
203
+ <OutputContainer>
204
+ <Typography variant="body1">
205
+ {bestRoute ? `≈ ${numberToString(props.outputAmount)}` : inputAmount ? '?' : '0'}
206
+ </Typography>
207
+ <span style={{ position: 'absolute', right: '4px', bottom: '2px' }}>
208
+ <Typography variant="caption">{`$${numberToString(
209
+ props.outputUsdValue,
210
+ )}`}</Typography>
211
+ </span>
212
+ </OutputContainer>
213
+ )}
214
+ </div>
215
+ </Container>
216
+ </Box>
217
+ );
218
+ }