@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
@@ -0,0 +1,85 @@
1
+ import { Token } from 'rango-sdk';
2
+ import { useEffect, useRef } from 'react';
3
+ import { useLocation, useSearchParams } from 'react-router-dom';
4
+ import { useBestRouteStore } from '../store/bestRoute';
5
+ import { useMetaStore } from '../store/meta';
6
+ import { SearchParams } from '../constants/searchParams';
7
+
8
+ function searchParamsToToken(tokens: Token[], searchParams: string | null): Token | null {
9
+ return (
10
+ tokens.find((token) => {
11
+ const symbolAndAddress = searchParams?.split('--');
12
+ if (symbolAndAddress?.length === 1)
13
+ return token.symbol === symbolAndAddress[0] && token.address === null;
14
+ return token.symbol === symbolAndAddress?.[0] && token.address === symbolAndAddress?.[1];
15
+ }) || null
16
+ );
17
+ }
18
+
19
+ export function UpdateUrl() {
20
+ const firstRender = useRef(true);
21
+ const [searchParams, setSearchParams] = useSearchParams();
22
+ const location = useLocation();
23
+
24
+ const fromChain = useBestRouteStore.use.fromChain();
25
+ const toChain = useBestRouteStore.use.toChain();
26
+ const fromToken = useBestRouteStore.use.fromToken();
27
+ const toToken = useBestRouteStore.use.toToken();
28
+ const setFromChain = useBestRouteStore.use.setFromChain();
29
+ const setFromToken = useBestRouteStore.use.setFromToken();
30
+ const setToChain = useBestRouteStore.use.setToChain();
31
+ const setToToken = useBestRouteStore.use.setToToken();
32
+ const inputAmount = useBestRouteStore.use.inputAmount();
33
+ const setInputAmount = useBestRouteStore.use.setInputAmount();
34
+ const loadingStatus = useMetaStore.use.loadingStatus();
35
+ const { blockchains, tokens } = useMetaStore.use.meta();
36
+
37
+ useEffect(() => {
38
+ if (!firstRender.current) {
39
+ const fromChainString = fromChain?.name || '';
40
+ const fromTokenString =
41
+ (fromToken?.symbol || '') + (fromToken?.address ? `--${fromToken?.address}` : '');
42
+ const toChainString = toChain?.name || '';
43
+ const toTokenString =
44
+ (toToken?.symbol || '') + (toToken?.address ? `--${toToken?.address}` : '');
45
+ const fromAmount = inputAmount;
46
+
47
+ setSearchParams(
48
+ {
49
+ ...(fromChainString && { [SearchParams.FROM_CHAIN]: fromChainString }),
50
+ ...(fromTokenString && { [SearchParams.FROM_TOKEN]: fromTokenString }),
51
+ ...(toChainString && { [SearchParams.TO_CHAIN]: toChainString }),
52
+ ...(toTokenString && { [SearchParams.TO_TOKEN]: toTokenString }),
53
+ ...(fromAmount && { [SearchParams.FROM_AMOUNT]: fromAmount.toString() }),
54
+ },
55
+ { replace: true },
56
+ );
57
+ }
58
+ firstRender.current = false;
59
+ }, [location.pathname, inputAmount]);
60
+
61
+ useEffect(() => {
62
+ if (loadingStatus === 'success') {
63
+ const fronChainString = searchParams.get(SearchParams.FROM_CHAIN);
64
+ const fromTokenString = searchParams.get(SearchParams.FROM_TOKEN);
65
+ const toChainString = searchParams.get(SearchParams.TO_CHAIN);
66
+ const toTokenString = searchParams.get(SearchParams.TO_TOKEN);
67
+ const fromAmount = searchParams.get(SearchParams.FROM_AMOUNT);
68
+ const fromChain = blockchains.find((blockchain) => blockchain.name === fronChainString);
69
+ const fromToken = searchParamsToToken(tokens, fromTokenString);
70
+ const toChain = blockchains.find((blockchain) => blockchain.name === toChainString);
71
+ const toToken = searchParamsToToken(tokens, toTokenString);
72
+ if (!!fromChain) {
73
+ setFromChain(fromChain);
74
+ if (!!fromToken) setFromToken(fromToken);
75
+ }
76
+ if (!!toChain) {
77
+ setToChain(toChain);
78
+ if (!!toToken) setToToken(toToken);
79
+ }
80
+ if (fromAmount) setInputAmount(fromAmount);
81
+ }
82
+ }, [loadingStatus]);
83
+
84
+ return null;
85
+ }
@@ -0,0 +1,3 @@
1
+ export const errorMessages = {
2
+ genericServerError: 'Error connecting server, please reload the app and try again',
3
+ };
@@ -0,0 +1,14 @@
1
+ export const navigationRoutes = {
2
+ home: '/',
3
+ fromChain: '/from-chain',
4
+ fromToken: '/from-token',
5
+ toChain: '/to-chain',
6
+ toToken: '/to-token',
7
+ settings: '/settings',
8
+ liquiditySources: '/liquidity-sources',
9
+ history: '/history',
10
+ wallets: '/wallets',
11
+ confirmSwap: '/confirm-swap',
12
+ confirmWallets: '/confirm-wallets',
13
+ swapDetails: '/swap-details',
14
+ };
@@ -0,0 +1,3 @@
1
+ import BigNumber from 'bignumber.js';
2
+
3
+ export const ZERO = new BigNumber(0);
@@ -0,0 +1,7 @@
1
+ export enum SearchParams {
2
+ 'FROM_CHAIN' = 'fromChain',
3
+ 'FROM_TOKEN' = 'fromToken',
4
+ 'TO_CHAIN' = 'toChain',
5
+ 'TO_TOKEN' = 'toToken',
6
+ 'FROM_AMOUNT' = 'fromAmount',
7
+ }
@@ -0,0 +1,16 @@
1
+ import { globalCss } from '@rango-dev/ui';
2
+
3
+ export const globalStyles = globalCss({
4
+ '*': {
5
+ '&::-webkit-scrollbar': { width: '$8' },
6
+ '&::-webkit-scrollbar-thumb': {
7
+ backgroundColor: '$neutrals400',
8
+ },
9
+ '&::-webkit-scrollbar-thumb:hover': {
10
+ backgroundColor: '$neutrals500',
11
+ },
12
+ '&::-webkit-scrollbar-track': {
13
+ backgroundColor: '$neutrals300',
14
+ },
15
+ },
16
+ });
@@ -0,0 +1,70 @@
1
+ import { BestRouteRequest, BestRouteResponse } from 'rango-sdk';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { httpService } from '../services/httpService';
4
+ import { useBestRouteStore } from '../store/bestRoute';
5
+ import { useSettingsStore } from '../store/settings';
6
+
7
+ export function useBestRoute() {
8
+ const fromToken = useBestRouteStore.use.fromToken();
9
+ const toToken = useBestRouteStore.use.toToken();
10
+ const inputAmount = useBestRouteStore.use.inputAmount();
11
+
12
+ const slippage = useSettingsStore.use.slippage();
13
+ const customSlippage = useSettingsStore.use.customSlippage();
14
+ const disabledLiquiditySources = useSettingsStore.use.disabledLiquiditySources();
15
+ const [count, setCount] = useState(0);
16
+ const [loading, setLoading] = useState(false);
17
+ const [error, setError] = useState('');
18
+ const [data, setData] = useState<BestRouteResponse | null>(null);
19
+ const abortControllerRef = useRef<AbortController | null>(null);
20
+
21
+ const retry = () => {
22
+ setCount((prev) => prev + 1);
23
+ };
24
+
25
+ useEffect(() => {
26
+ if (abortControllerRef.current) abortControllerRef.current.abort();
27
+ const abortController = new AbortController();
28
+ abortControllerRef.current = abortController;
29
+ if (!!fromToken && !!toToken && !!inputAmount) {
30
+ if (!!data) setData(null);
31
+ const requestBody: BestRouteRequest = {
32
+ amount: inputAmount?.toString(),
33
+ connectedWallets: [],
34
+ selectedWallets: {},
35
+ checkPrerequisites: false,
36
+ swapperGroups: disabledLiquiditySources,
37
+ swappersGroupsExclude: true,
38
+ from: {
39
+ address: fromToken.address,
40
+ blockchain: fromToken.blockchain,
41
+ symbol: fromToken.symbol,
42
+ },
43
+ to: { address: toToken.address, blockchain: toToken.blockchain, symbol: toToken.symbol },
44
+ };
45
+ setLoading(true);
46
+ httpService
47
+ .getBestRoute(requestBody, { signal: abortControllerRef.current.signal })
48
+ .then((res) => {
49
+ setData(res);
50
+ setLoading(false);
51
+ abortControllerRef.current = null;
52
+ })
53
+ .catch((error) => {
54
+ if (error.code === 'ERR_CANCELED') return;
55
+ setError(error.message);
56
+ setLoading(false);
57
+ });
58
+ }
59
+ }, [
60
+ fromToken,
61
+ toToken,
62
+ inputAmount,
63
+ count,
64
+ slippage,
65
+ customSlippage,
66
+ disabledLiquiditySources.length,
67
+ ]);
68
+
69
+ return { loading, error, data, retry };
70
+ }
@@ -0,0 +1,234 @@
1
+ import { PendingSwap } from '@rango-dev/ui/dist/containers/History/types';
2
+ import {
3
+ BestRouteType,
4
+ SimulationValidationStatus,
5
+ SwapSavedSettings,
6
+ } from '@rango-dev/ui/dist/types/swaps';
7
+ import { WalletType } from '@rango-dev/wallets-shared';
8
+ import BigNumber from 'bignumber.js';
9
+ import { BestRouteRequest, BestRouteResponse } from 'rango-sdk';
10
+ import { useState } from 'react';
11
+ import { httpService } from '../services/httpService';
12
+ import { useBestRouteStore } from '../store/bestRoute';
13
+ import { useSettingsStore } from '../store/settings';
14
+ import { useWalletsStore } from '../store/wallets';
15
+ import { compareRoutes, getRequiredBalanceOfWallet } from '../utils/routing';
16
+ import { calculatePendingSwap } from '../utils/swap';
17
+ import { SelectedWallet } from '../utils/wallets';
18
+
19
+ type CheckFeeAndBalanceResult =
20
+ | {
21
+ hasEnoughBalanceOrSlippage: { balance: boolean; slippage: boolean; routeChanged: boolean };
22
+ bestRoute: BestRouteType;
23
+ }
24
+ | { bestRoute: null }
25
+ | null;
26
+
27
+ export function useConfirmSwap() {
28
+ const fromToken = useBestRouteStore.use.fromToken();
29
+ const toToken = useBestRouteStore.use.toToken();
30
+ const inputAmount = useBestRouteStore.use.inputAmount();
31
+ const bestRoute = useBestRouteStore.use.bestRoute();
32
+ const setBestRoute = useBestRouteStore.use.setBestRoute();
33
+ const accounts = useWalletsStore.use.accounts();
34
+ const selectedWallets = useWalletsStore.use.selectedWallets();
35
+
36
+ const slippage = useSettingsStore.use.slippage();
37
+ const disabledLiquiditySources = useSettingsStore.use.disabledLiquiditySources();
38
+ const [loading, setLoading] = useState(false);
39
+ const [error, setError] = useState('');
40
+ const [warning, setWarning] = useState('');
41
+ const [data, setData] = useState<BestRouteResponse | null>(null);
42
+ const [feeStatus, setFeeStatus] = useState<SimulationValidationStatus[] | null>(null);
43
+ const [bestRouteChanged, setBestRouteChanged] = useState(false);
44
+ const [enoughBalance, setEnoughBalance] = useState<boolean | null>(null);
45
+
46
+ const hasEnoughBalanceOrProperSlippage = (
47
+ route: BestRouteType,
48
+ selectedWallets: SelectedWallet[],
49
+ userSlippage: string,
50
+ routeChanged: boolean,
51
+ ): { balance: boolean; slippage: boolean; routeChanged: boolean } => {
52
+ const fee = route.validationStatus;
53
+
54
+ if (fee === null || fee.length === 0) return { balance: true, slippage: true, routeChanged };
55
+
56
+ for (const wallet of selectedWallets) {
57
+ const requiredAssets = getRequiredBalanceOfWallet(wallet, fee);
58
+ if (!requiredAssets) continue;
59
+
60
+ const hasEnoughBalance = requiredAssets?.map((it) => it.ok).reduce((a, b) => a && b);
61
+ if (!hasEnoughBalance) return { balance: false, slippage: true, routeChanged };
62
+ }
63
+
64
+ const slippages = route.result?.swaps?.map((s) => s.recommendedSlippage);
65
+ const slippageError = (slippages?.filter((s) => !!s?.error)?.length || 0) > 0;
66
+ const minSlippage =
67
+ slippages
68
+ ?.map((s) => parseFloat(s?.slippage.toString() || '0') || 0)
69
+ .filter((s) => s > 0)
70
+ .sort((a, b) => b - a)
71
+ .find(() => true) || null;
72
+ if (slippageError) {
73
+ setError('Server cannot calculated required slippage for your swap');
74
+ return { balance: true, slippage: false, routeChanged };
75
+ } else if (minSlippage !== null && minSlippage > parseFloat(userSlippage)) {
76
+ setError(`Your slippage should be ${minSlippage} at least`);
77
+ return { balance: true, slippage: false, routeChanged };
78
+ }
79
+
80
+ return { balance: true, slippage: true, routeChanged };
81
+ };
82
+
83
+ const checkFeeAndBalance = async (
84
+ selectedWallets: SelectedWallet[],
85
+ ): Promise<CheckFeeAndBalanceResult> => {
86
+ setLoading(true);
87
+ setFeeStatus(null);
88
+
89
+ const selectedWalletsMap = selectedWallets.reduce(
90
+ (selectedWalletsMap: BestRouteRequest['selectedWallets'], selectedWallet) => (
91
+ (selectedWalletsMap[selectedWallet.chain] = selectedWallet.address), selectedWalletsMap
92
+ ),
93
+ {},
94
+ );
95
+
96
+ const connectedWallets: BestRouteRequest['connectedWallets'] = [];
97
+
98
+ accounts.forEach((account) => {
99
+ const chainAndAccounts = connectedWallets.find((wallet) => wallet.blockchain);
100
+ if (!!chainAndAccounts) chainAndAccounts.addresses.push(account.address);
101
+ else connectedWallets.push({ blockchain: account.chain, addresses: [account.address] });
102
+ });
103
+
104
+ const r = await httpService
105
+ .getBestRoute({
106
+ amount: inputAmount!.toString(),
107
+ checkPrerequisites: true,
108
+ from: {
109
+ address: fromToken!.address,
110
+ blockchain: fromToken!.blockchain,
111
+ symbol: fromToken!.symbol,
112
+ },
113
+ to: {
114
+ address: toToken!.address,
115
+ blockchain: toToken!.blockchain,
116
+ symbol: toToken!.symbol,
117
+ },
118
+ connectedWallets,
119
+ selectedWallets: selectedWalletsMap,
120
+ //@ts-ignore
121
+ swapperGroups: disabledLiquiditySources,
122
+ swappersGroupsExclude: true,
123
+ })
124
+ .catch((error) => {
125
+ setError(error.message);
126
+ setLoading(false);
127
+ });
128
+ setLoading(false);
129
+ if (!r || !r.result || !bestRoute) return { bestRoute: null };
130
+ if (!new BigNumber(r.requestAmount).isEqualTo(new BigNumber(inputAmount || '-1'))) return null;
131
+ setFeeStatus(r.validationStatus);
132
+ const routeChangeStatus = compareRoutes(
133
+ bestRoute,
134
+ r,
135
+ inputAmount!.toString(),
136
+ fromToken!.usdPrice,
137
+ toToken!.usdPrice,
138
+ );
139
+ setBestRoute(r);
140
+ const isChanged = routeChangeStatus.isChanged;
141
+ const changeWarningMessage = routeChangeStatus.warningMessage;
142
+ setBestRouteChanged(isChanged);
143
+ setWarning('Best route changed');
144
+ setData(r);
145
+ return {
146
+ hasEnoughBalanceOrSlippage: hasEnoughBalanceOrProperSlippage(
147
+ r,
148
+ selectedWallets,
149
+ slippage.toString(),
150
+ isChanged,
151
+ ),
152
+ bestRoute: r,
153
+ };
154
+ };
155
+
156
+ const swap = () => {
157
+ if (!bestRoute) return;
158
+ if (inputAmount!.toString() === '') return;
159
+ const wallets = selectedWallets.reduce(
160
+ (
161
+ selectedWalletsMap: { [p: string]: { address: string; walletType: WalletType } },
162
+ selectedWallet,
163
+ ) => (
164
+ (selectedWalletsMap[selectedWallet.chain] = {
165
+ address: selectedWallet.address,
166
+ walletType: selectedWallet.walletType,
167
+ }),
168
+ selectedWalletsMap
169
+ ),
170
+ {},
171
+ );
172
+
173
+ const proceedAnyway = enoughBalance !== null;
174
+
175
+ const settings: SwapSavedSettings = {
176
+ slippage: slippage.toString(),
177
+ disabledSwappersGroups: ['Osmosis'],
178
+ disabledSwappersIds: [],
179
+ };
180
+ if (proceedAnyway) {
181
+ const newSwap: PendingSwap = calculatePendingSwap(
182
+ inputAmount!.toString(),
183
+ bestRoute,
184
+ wallets,
185
+ settings,
186
+ false,
187
+ );
188
+ }
189
+
190
+ !proceedAnyway &&
191
+ checkFeeAndBalance(selectedWallets)
192
+ .then((data) => {
193
+ if (!data) {
194
+ setError('confirm swap error');
195
+ return;
196
+ }
197
+
198
+ if (!data.bestRoute) {
199
+ setError('confirm swap error');
200
+ return;
201
+ } else {
202
+ const { hasEnoughBalanceOrSlippage, bestRoute: newBestRoute } = data;
203
+ if (!hasEnoughBalanceOrSlippage) {
204
+ return;
205
+ }
206
+
207
+ setEnoughBalance(
208
+ hasEnoughBalanceOrSlippage.balance && hasEnoughBalanceOrSlippage.slippage,
209
+ );
210
+
211
+ if (
212
+ hasEnoughBalanceOrSlippage.balance &&
213
+ hasEnoughBalanceOrSlippage.slippage &&
214
+ !hasEnoughBalanceOrSlippage.routeChanged
215
+ ) {
216
+ const newSwap: PendingSwap = calculatePendingSwap(
217
+ inputAmount!.toString(),
218
+ newBestRoute,
219
+ wallets,
220
+ settings,
221
+ true,
222
+ );
223
+ } else if (!hasEnoughBalanceOrSlippage.balance) {
224
+ setError('not enough balance');
225
+ }
226
+ }
227
+ })
228
+ .catch((error) => {
229
+ console.log('unexpected error', error);
230
+ });
231
+ };
232
+
233
+ return { loading, error, data, warning, feeStatus, bestRouteChanged, enoughBalance, swap };
234
+ }
@@ -0,0 +1,37 @@
1
+ import { lightTheme, darkTheme } from '@rango-dev/ui';
2
+ import { useState, useEffect } from 'react';
3
+ import { useMetaStore } from '../store/meta';
4
+ import { useSettingsStore } from '../store/settings';
5
+
6
+ export function useTheme() {
7
+ const theme = useSettingsStore.use.theme();
8
+ const fetchMeta = useMetaStore.use.fetchMeta();
9
+
10
+ const [OSTheme, setOSTheme] = useState(lightTheme);
11
+ useEffect(() => {
12
+ (async () => {
13
+ await fetchMeta();
14
+ })();
15
+
16
+ const switchTheme = (event: MediaQueryListEvent) => {
17
+ if (event.matches) setOSTheme(darkTheme);
18
+ else setOSTheme(lightTheme);
19
+ };
20
+
21
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
22
+ setOSTheme(darkTheme);
23
+ }
24
+
25
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', switchTheme);
26
+ return () => {
27
+ window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', switchTheme);
28
+ };
29
+ }, []);
30
+
31
+ const getActiveTheme = () => {
32
+ if (theme === 'auto') return OSTheme;
33
+ else return theme === 'dark' ? darkTheme : lightTheme;
34
+ };
35
+
36
+ return { activeTheme: getActiveTheme() };
37
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import { App } from './App';
5
+
6
+ const container = document.getElementById('app')!;
7
+ const root = createRoot(container);
8
+ root.render(
9
+ <BrowserRouter>
10
+ <App />,
11
+ </BrowserRouter>,
12
+ );