@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.
- package/CHANGELOG.md +12 -0
- package/package.json +29 -0
- package/public/index.html +11 -0
- package/readme.md +2 -0
- package/src/App.tsx +78 -0
- package/src/app.css +28 -0
- package/src/components/AppRouter.tsx +15 -0
- package/src/components/AppRoutes.tsx +32 -0
- package/src/components/BottomLogo.tsx +44 -0
- package/src/components/Footer.tsx +8 -0
- package/src/components/Header.tsx +25 -0
- package/src/components/HeaderButtons.tsx +34 -0
- package/src/components/Layout.tsx +55 -0
- package/src/components/SwitchFromAndTo.tsx +30 -0
- package/src/components/TokenInfo.tsx +218 -0
- package/src/components/UpdateUrl.tsx +85 -0
- package/src/constants/errors.ts +3 -0
- package/src/constants/navigationRoutes.ts +14 -0
- package/src/constants/numbers.ts +3 -0
- package/src/constants/searchParams.ts +7 -0
- package/src/globalStyles.ts +16 -0
- package/src/hooks/useBestRoute.ts +70 -0
- package/src/hooks/useConfirmSwap.ts +234 -0
- package/src/hooks/useTheme.ts +37 -0
- package/src/index.tsx +12 -0
- package/src/mockData/pendingSwap.ts +607 -0
- package/src/pages/ConfirmSwapPage.tsx +24 -0
- package/src/pages/ConfirmWalletsPage.tsx +51 -0
- package/src/pages/HistoryPage.tsx +793 -0
- package/src/pages/Home.tsx +176 -0
- package/src/pages/LiquiditySourcesPage.tsx +49 -0
- package/src/pages/SelectChainPage.tsx +62 -0
- package/src/pages/SelectTokenPage.tsx +84 -0
- package/src/pages/SettingsPage.tsx +60 -0
- package/src/pages/SwapDetailsPage.tsx +9 -0
- package/src/pages/WalletsPage.tsx +61 -0
- package/src/services/httpService.ts +3 -0
- package/src/store/bestRoute.ts +82 -0
- package/src/store/meta.ts +35 -0
- package/src/store/selectors.ts +19 -0
- package/src/store/settings.ts +72 -0
- package/src/store/wallets.ts +144 -0
- package/src/utils/common.ts +3 -0
- package/src/utils/numbers.ts +94 -0
- package/src/utils/routing.ts +108 -0
- package/src/utils/swap.ts +155 -0
- 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
|
+
}
|
package/readme.md
ADDED
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,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
|
+
}
|