@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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { persist } from 'zustand/middleware';
|
|
3
|
+
import { immer } from 'zustand/middleware/immer';
|
|
4
|
+
import { useMetaStore } from './meta';
|
|
5
|
+
import createSelectors from './selectors';
|
|
6
|
+
|
|
7
|
+
type Theme = 'auto' | 'dark' | 'light';
|
|
8
|
+
|
|
9
|
+
interface Settings {
|
|
10
|
+
slippage: number;
|
|
11
|
+
customSlippage: number | null;
|
|
12
|
+
infinitApprove: boolean;
|
|
13
|
+
disabledLiquiditySources: string[];
|
|
14
|
+
theme: Theme;
|
|
15
|
+
setSlippage: (slippage: number) => void;
|
|
16
|
+
setCustomSlippage: (customSlippage: number | null) => void;
|
|
17
|
+
toggleInfinitApprove: () => void;
|
|
18
|
+
toggleLiquiditySource: (name: string) => void;
|
|
19
|
+
setTheme: (theme: Theme) => void;
|
|
20
|
+
toggleAllLiquiditySources: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useSettingsStore = createSelectors(
|
|
24
|
+
create<Settings>()(
|
|
25
|
+
persist(
|
|
26
|
+
immer((set) => ({
|
|
27
|
+
slippage: 1,
|
|
28
|
+
customSlippage: null,
|
|
29
|
+
infinitApprove: false,
|
|
30
|
+
disabledLiquiditySources: [],
|
|
31
|
+
theme: 'auto',
|
|
32
|
+
setSlippage: (slippage) =>
|
|
33
|
+
set((state) => {
|
|
34
|
+
state.slippage = slippage;
|
|
35
|
+
}),
|
|
36
|
+
setCustomSlippage: (customSlippage) => {
|
|
37
|
+
return set((state) => {
|
|
38
|
+
state.customSlippage = customSlippage;
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
toggleAllLiquiditySources: () =>
|
|
42
|
+
set((state) => {
|
|
43
|
+
const { swappers } = useMetaStore.getState().meta;
|
|
44
|
+
|
|
45
|
+
if (swappers.length - state.disabledLiquiditySources.length === 0)
|
|
46
|
+
state.disabledLiquiditySources = [];
|
|
47
|
+
else {
|
|
48
|
+
const allSwappers = swappers.map((swapper) => swapper.swapperGroup);
|
|
49
|
+
state.disabledLiquiditySources = allSwappers;
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
toggleInfinitApprove: () =>
|
|
53
|
+
set((state) => {
|
|
54
|
+
state.infinitApprove = !state.infinitApprove;
|
|
55
|
+
}),
|
|
56
|
+
toggleLiquiditySource: (name) =>
|
|
57
|
+
set((state) => {
|
|
58
|
+
state.disabledLiquiditySources = state.disabledLiquiditySources.includes(name)
|
|
59
|
+
? state.disabledLiquiditySources.filter((liquiditySource) => liquiditySource != name)
|
|
60
|
+
: state.disabledLiquiditySources.concat(name);
|
|
61
|
+
}),
|
|
62
|
+
setTheme: (theme) =>
|
|
63
|
+
set((state) => {
|
|
64
|
+
state.theme = theme;
|
|
65
|
+
}),
|
|
66
|
+
})),
|
|
67
|
+
{
|
|
68
|
+
name: 'user-settings',
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { WalletType } from '@rango-dev/wallets-shared';
|
|
2
|
+
import { Token } from 'rango-sdk';
|
|
3
|
+
import { create } from 'zustand';
|
|
4
|
+
import { SelectableWallet } from '../pages/ConfirmWalletsPage';
|
|
5
|
+
import { httpService } from '../services/httpService';
|
|
6
|
+
import {
|
|
7
|
+
getRequiredChains,
|
|
8
|
+
isAccountAndBalanceMatched,
|
|
9
|
+
makeBalanceFor,
|
|
10
|
+
resetBalanceState,
|
|
11
|
+
SelectedWallet,
|
|
12
|
+
} from '../utils/wallets';
|
|
13
|
+
import { useBestRouteStore } from './bestRoute';
|
|
14
|
+
import { useMetaStore } from './meta';
|
|
15
|
+
import createSelectors from './selectors';
|
|
16
|
+
|
|
17
|
+
export interface Account {
|
|
18
|
+
chain: string;
|
|
19
|
+
address: string;
|
|
20
|
+
walletType: WalletType;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TokenBalance = {
|
|
24
|
+
chain: string;
|
|
25
|
+
symbol: string;
|
|
26
|
+
ticker: string;
|
|
27
|
+
address: string | null;
|
|
28
|
+
rawAmount: string;
|
|
29
|
+
decimal: number | null;
|
|
30
|
+
amount: string;
|
|
31
|
+
logo: string | null;
|
|
32
|
+
usdPrice: number | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface Balance {
|
|
36
|
+
balances: TokenBalance[] | null;
|
|
37
|
+
address: string;
|
|
38
|
+
chain: string;
|
|
39
|
+
loading: boolean;
|
|
40
|
+
walletType: WalletType;
|
|
41
|
+
error: boolean;
|
|
42
|
+
explorerUrl: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface WalletsStore {
|
|
46
|
+
accounts: Account[];
|
|
47
|
+
balances: Balance[];
|
|
48
|
+
selectedWallets: SelectedWallet[];
|
|
49
|
+
connectWallet: (accounts: Account[]) => void;
|
|
50
|
+
disconnectWallet: (walletType: WalletType) => void;
|
|
51
|
+
initSelectedWallets: () => void;
|
|
52
|
+
setSelectedWallet: (wallet: SelectableWallet) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const useWalletsStore = createSelectors(
|
|
56
|
+
create<WalletsStore>()((set, get) => ({
|
|
57
|
+
accounts: [],
|
|
58
|
+
balances: [],
|
|
59
|
+
selectedWallets: [],
|
|
60
|
+
connectWallet: (accounts) => {
|
|
61
|
+
const tokens = useMetaStore.getState().meta.tokens;
|
|
62
|
+
|
|
63
|
+
set((state) => ({
|
|
64
|
+
accounts: state.accounts.concat(accounts),
|
|
65
|
+
balances: state.balances.concat(
|
|
66
|
+
accounts.map((account) => ({
|
|
67
|
+
balances: [],
|
|
68
|
+
address: account.address,
|
|
69
|
+
chain: account.chain,
|
|
70
|
+
loading: true,
|
|
71
|
+
walletType: account.walletType,
|
|
72
|
+
error: false,
|
|
73
|
+
explorerUrl: null,
|
|
74
|
+
})),
|
|
75
|
+
),
|
|
76
|
+
}));
|
|
77
|
+
accounts.forEach(async (account) => {
|
|
78
|
+
try {
|
|
79
|
+
const response = await httpService.getWalletsDetails([
|
|
80
|
+
{ address: account.address, blockchain: account.chain },
|
|
81
|
+
]);
|
|
82
|
+
const retrivedBalance = response.wallets[0];
|
|
83
|
+
if (retrivedBalance) {
|
|
84
|
+
set((state) => ({
|
|
85
|
+
balances: state.balances.map((balance) => {
|
|
86
|
+
return isAccountAndBalanceMatched(account, balance)
|
|
87
|
+
? makeBalanceFor(account, retrivedBalance, tokens)
|
|
88
|
+
: balance;
|
|
89
|
+
}),
|
|
90
|
+
}));
|
|
91
|
+
} else throw new Error('Wallet not found');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
set((state) => ({
|
|
94
|
+
balances: state.balances.map((balance) => {
|
|
95
|
+
return isAccountAndBalanceMatched(account, balance)
|
|
96
|
+
? resetBalanceState(balance)
|
|
97
|
+
: balance;
|
|
98
|
+
}),
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
disconnectWallet: (walletType) => {
|
|
104
|
+
set((state) => ({
|
|
105
|
+
accounts: state.accounts.filter((account) => account.walletType !== walletType),
|
|
106
|
+
balances: state.balances.filter((balance) => balance.walletType !== walletType),
|
|
107
|
+
selectedWallets: state.selectedWallets.filter((wallet) => wallet.walletType != walletType),
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
110
|
+
initSelectedWallets: () =>
|
|
111
|
+
set((state) => {
|
|
112
|
+
const requiredChains = getRequiredChains(useBestRouteStore.getState().bestRoute);
|
|
113
|
+
const connectedWallets = state.accounts;
|
|
114
|
+
const selectedWallets: SelectedWallet[] = [];
|
|
115
|
+
requiredChains.forEach((chain) => {
|
|
116
|
+
const anyWalletSelected = !!state.selectedWallets.find(
|
|
117
|
+
(wallet) => wallet.chain === chain,
|
|
118
|
+
);
|
|
119
|
+
if (!anyWalletSelected) {
|
|
120
|
+
const firstWalletWithMatchedChain = connectedWallets.find(
|
|
121
|
+
(wallet) => wallet.chain === chain,
|
|
122
|
+
);
|
|
123
|
+
if (!!firstWalletWithMatchedChain)
|
|
124
|
+
selectedWallets.push({
|
|
125
|
+
address: firstWalletWithMatchedChain.address,
|
|
126
|
+
chain: firstWalletWithMatchedChain.chain,
|
|
127
|
+
walletType: firstWalletWithMatchedChain.walletType,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return { selectedWallets: state.selectedWallets.concat(selectedWallets) };
|
|
132
|
+
}),
|
|
133
|
+
setSelectedWallet: (wallet) =>
|
|
134
|
+
set((state) => ({
|
|
135
|
+
selectedWallets: state.selectedWallets
|
|
136
|
+
.filter((selectedWallet) => selectedWallet.chain !== wallet.chain)
|
|
137
|
+
.concat({
|
|
138
|
+
chain: wallet.chain,
|
|
139
|
+
address: wallet.address,
|
|
140
|
+
walletType: wallet.walletType,
|
|
141
|
+
}),
|
|
142
|
+
})),
|
|
143
|
+
})),
|
|
144
|
+
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { BigNumber } from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
export const percentToString = (p: number, fractions = 0): string => (p * 100).toFixed(fractions);
|
|
4
|
+
export const secondsToString = (s: number): string => {
|
|
5
|
+
const seconds = (s % 60).toString().padStart(2, '0');
|
|
6
|
+
const minutes = parseInt((s / 60).toString())
|
|
7
|
+
.toString()
|
|
8
|
+
.padStart(2, '0');
|
|
9
|
+
return `${minutes}:${seconds}`;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const numberToString = (
|
|
13
|
+
number: BigNumber | string | null,
|
|
14
|
+
minDecimals: number | null = null,
|
|
15
|
+
maxDecimals: number | null = null,
|
|
16
|
+
): string => {
|
|
17
|
+
if (number === null) return '';
|
|
18
|
+
if (number === '') return '';
|
|
19
|
+
const n = new BigNumber(number);
|
|
20
|
+
const roundingMode = 1;
|
|
21
|
+
let maxI = 1000;
|
|
22
|
+
for (let i = 0; i < 60; i++) {
|
|
23
|
+
if (new BigNumber(n.toFixed(i, roundingMode)).eq(n)) {
|
|
24
|
+
maxI = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (n.gte(10000)) return n.toFormat(0, roundingMode);
|
|
30
|
+
if (n.gte(1000))
|
|
31
|
+
return n.toFormat(
|
|
32
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, 1))),
|
|
33
|
+
roundingMode,
|
|
34
|
+
);
|
|
35
|
+
if (n.gte(100))
|
|
36
|
+
return n.toFormat(
|
|
37
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, 1))),
|
|
38
|
+
roundingMode,
|
|
39
|
+
);
|
|
40
|
+
if (n.gte(1))
|
|
41
|
+
return n.toFormat(
|
|
42
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, 2))),
|
|
43
|
+
roundingMode,
|
|
44
|
+
);
|
|
45
|
+
if (n.gte(0.01))
|
|
46
|
+
return n.toFormat(
|
|
47
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, 4))),
|
|
48
|
+
roundingMode,
|
|
49
|
+
);
|
|
50
|
+
for (let i = minDecimals || 4; i < 17; i++)
|
|
51
|
+
if (n.gte(Math.pow(10, -i)))
|
|
52
|
+
return n.toFormat(
|
|
53
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, i))),
|
|
54
|
+
roundingMode,
|
|
55
|
+
);
|
|
56
|
+
if (n.isEqualTo(0)) return '0';
|
|
57
|
+
|
|
58
|
+
return n.toFormat(
|
|
59
|
+
Math.min(maxI, Math.min(maxDecimals || 100, Math.max(minDecimals || 0, 8))),
|
|
60
|
+
roundingMode,
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const convertBigNumberToHex = (value: BigNumber, decimals: number): string => {
|
|
65
|
+
return '0x' + value.shiftedBy(decimals).toString(16);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const uint8ArrayToHex = (buffer: Uint8Array): string => {
|
|
69
|
+
// buffer is an ArrayBuffer
|
|
70
|
+
return [...buffer].map((x) => x.toString(16).padStart(2, '0')).join('');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function dollarToConciseString(num: number | undefined): string {
|
|
74
|
+
if (!num) return '-';
|
|
75
|
+
if (num < 1) return ' < 1$';
|
|
76
|
+
if (num < 1000) return numberToString(new BigNumber(num)) + '$';
|
|
77
|
+
if (num < 10_000) return parseInt((num / 100).toString()) / 10 + 'K';
|
|
78
|
+
if (num < 1_000_000) return parseInt((num / 1000).toString()) + 'K';
|
|
79
|
+
if (num < 100_000_000) return parseInt((num / 100000).toString()) / 10 + 'M';
|
|
80
|
+
return parseInt((num / 1000000).toString()) + 'M';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function removeExtraDecimals(num: string, maxDecimals: number): string {
|
|
84
|
+
try {
|
|
85
|
+
if (!num.includes('.')) return num;
|
|
86
|
+
const [b, f] = num.split('.');
|
|
87
|
+
if (f && f.length > maxDecimals) {
|
|
88
|
+
return `${b}.${f.substring(0, maxDecimals)}`;
|
|
89
|
+
}
|
|
90
|
+
return num;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return num;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SimulationAssetAndAmount,
|
|
3
|
+
SimulationValidationStatus,
|
|
4
|
+
} from '@rango-dev/ui/dist/types/swaps';
|
|
5
|
+
import BigNumber from 'bignumber.js';
|
|
6
|
+
import { BestRouteResponse } from 'rango-sdk';
|
|
7
|
+
import { ZERO } from '../constants/numbers';
|
|
8
|
+
import { numberToString } from './numbers';
|
|
9
|
+
import { SelectedWallet } from './wallets';
|
|
10
|
+
|
|
11
|
+
export const getBestRouteToTokenUsdPrice = (
|
|
12
|
+
bestRoute: BestRouteResponse | null,
|
|
13
|
+
): number | null | undefined =>
|
|
14
|
+
bestRoute?.result?.swaps[bestRoute?.result?.swaps.length - 1].to.usdPrice;
|
|
15
|
+
|
|
16
|
+
export type RouteChangeStatus = {
|
|
17
|
+
isChanged: boolean;
|
|
18
|
+
warningMessage?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const compareRoutes = (
|
|
22
|
+
route1: BestRouteResponse,
|
|
23
|
+
route2: BestRouteResponse,
|
|
24
|
+
inputAmount: string,
|
|
25
|
+
fromTokenUsdPrice: number | null,
|
|
26
|
+
toTokenUsdPrice: number | null,
|
|
27
|
+
): RouteChangeStatus => {
|
|
28
|
+
let outUsdValue: BigNumber = (
|
|
29
|
+
new BigNumber(route2.result?.outputAmount || '0') || ZERO
|
|
30
|
+
).multipliedBy(toTokenUsdPrice || 0);
|
|
31
|
+
if (outUsdValue.isNaN()) outUsdValue = ZERO;
|
|
32
|
+
let inUsdValue: BigNumber = new BigNumber(inputAmount || '0').multipliedBy(
|
|
33
|
+
fromTokenUsdPrice || 0,
|
|
34
|
+
);
|
|
35
|
+
if (inUsdValue.isNaN()) inUsdValue = ZERO;
|
|
36
|
+
const outToInRatio =
|
|
37
|
+
inUsdValue === null || inUsdValue.lte(ZERO)
|
|
38
|
+
? 0
|
|
39
|
+
: outUsdValue === null || outUsdValue.lte(ZERO)
|
|
40
|
+
? 0
|
|
41
|
+
: outUsdValue.div(inUsdValue).minus(1).multipliedBy(100);
|
|
42
|
+
const disableSwapButton =
|
|
43
|
+
(parseInt(outToInRatio?.toFixed(2) || '0') <= -10 &&
|
|
44
|
+
(inUsdValue === null || inUsdValue.gte(new BigNumber(400)))) ||
|
|
45
|
+
(parseInt(outToInRatio?.toFixed(2) || '0') <= -5 &&
|
|
46
|
+
(inUsdValue === null || inUsdValue.gte(new BigNumber(1000))));
|
|
47
|
+
|
|
48
|
+
if (disableSwapButton) {
|
|
49
|
+
return {
|
|
50
|
+
isChanged: true,
|
|
51
|
+
warningMessage: 'Route updated and price impact is too high, try again later!',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const out1 = route1.result?.outputAmount || null;
|
|
56
|
+
const out2 = route2.result?.outputAmount || null;
|
|
57
|
+
if (!!out1 && !!out2) {
|
|
58
|
+
const changePercent = new BigNumber(out2).div(new BigNumber(out1)).minus(1).multipliedBy(100);
|
|
59
|
+
if (changePercent.toNumber() <= -1) {
|
|
60
|
+
return {
|
|
61
|
+
isChanged: true,
|
|
62
|
+
warningMessage: `Output amount changed to ${numberToString(out2)}
|
|
63
|
+
(${numberToString(changePercent, null, 2)}% change).`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const route1Swaps = route1.result?.swaps || [];
|
|
68
|
+
const route2Swaps = route2.result?.swaps || [];
|
|
69
|
+
if (route1Swaps.length !== route2Swaps.length)
|
|
70
|
+
return { isChanged: true, warningMessage: 'Route has been updated.' };
|
|
71
|
+
else {
|
|
72
|
+
for (let i = 0; i < route1Swaps.length; i++) {
|
|
73
|
+
if (route1Swaps[i].swapperId !== route2Swaps[i].swapperId)
|
|
74
|
+
return { isChanged: true, warningMessage: 'Route swappers has been updated.' };
|
|
75
|
+
else if (route1Swaps[i].to.symbol !== route2Swaps[i].to.symbol)
|
|
76
|
+
return {
|
|
77
|
+
isChanged: true,
|
|
78
|
+
warningMessage: 'Route internal coins has been updated.',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { isChanged: false };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getOutToInRatio = (fromUsdValue: BigNumber | null, toUsdValue: BigNumber | null) =>
|
|
86
|
+
fromUsdValue === null || fromUsdValue.lte(ZERO)
|
|
87
|
+
? 0
|
|
88
|
+
: toUsdValue === null || toUsdValue.lte(ZERO)
|
|
89
|
+
? 0
|
|
90
|
+
: toUsdValue.div(fromUsdValue).minus(1).multipliedBy(100);
|
|
91
|
+
|
|
92
|
+
export const outToRatioHasWarning = (fromUsdValue: BigNumber | null, outToInRatio: BigNumber | 0) =>
|
|
93
|
+
(parseInt(outToInRatio?.toFixed(2) || '0') <= -10 &&
|
|
94
|
+
(fromUsdValue === null || fromUsdValue.gte(new BigNumber(200)))) ||
|
|
95
|
+
(parseInt(outToInRatio?.toFixed(2) || '0') <= -5 &&
|
|
96
|
+
(fromUsdValue === null || fromUsdValue.gte(new BigNumber(1000))));
|
|
97
|
+
|
|
98
|
+
export const getRequiredBalanceOfWallet = (
|
|
99
|
+
wallet: SelectedWallet,
|
|
100
|
+
fee: SimulationValidationStatus[] | null,
|
|
101
|
+
): SimulationAssetAndAmount[] | null => {
|
|
102
|
+
if (fee === null) return null;
|
|
103
|
+
const relatedFeeStatus = fee
|
|
104
|
+
?.find((item) => item.blockchain === wallet.chain)
|
|
105
|
+
?.wallets.find((it) => it.address?.toLowerCase() === wallet.address.toLowerCase());
|
|
106
|
+
if (!relatedFeeStatus) return null;
|
|
107
|
+
return relatedFeeStatus.requiredAssets;
|
|
108
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { PendingSwap } from '@rango-dev/ui/dist/containers/History/types';
|
|
2
|
+
import { WalletTypeAndAddress, SwapSavedSettings } from '@rango-dev/ui/dist/types/swaps';
|
|
3
|
+
import BigNumber from 'bignumber.js';
|
|
4
|
+
import { BestRouteResponse } from 'rango-sdk';
|
|
5
|
+
import { Account } from '../store/wallets';
|
|
6
|
+
import { ZERO } from '../constants/numbers';
|
|
7
|
+
|
|
8
|
+
export function getOutputRatio(inputUsdValue: BigNumber, outputUsdValue: BigNumber) {
|
|
9
|
+
if (inputUsdValue.lte(ZERO) || outputUsdValue.lte(ZERO)) return 0;
|
|
10
|
+
return outputUsdValue.div(inputUsdValue).minus(1).multipliedBy(100);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function outputRatioHasWarning(inputUsdValue: BigNumber, outputRatio: BigNumber | 0) {
|
|
14
|
+
return (
|
|
15
|
+
(parseInt(outputRatio.toFixed(2) || '0') <= -10 && inputUsdValue.gte(new BigNumber(400))) ||
|
|
16
|
+
(parseInt(outputRatio.toFixed(2) || '0') <= -5 && inputUsdValue.gte(new BigNumber(1000)))
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hasLimitError(bestRoute: BestRouteResponse | null): boolean {
|
|
21
|
+
return (
|
|
22
|
+
(bestRoute?.result?.swaps || []).filter((swap) => {
|
|
23
|
+
const minimum = !!swap.fromAmountMinValue ? new BigNumber(swap.fromAmountMinValue) : null;
|
|
24
|
+
const maximum = !!swap.fromAmountMaxValue ? new BigNumber(swap.fromAmountMaxValue) : null;
|
|
25
|
+
const isExclusive = swap.fromAmountRestrictionType === 'EXCLUSIVE';
|
|
26
|
+
if (isExclusive) {
|
|
27
|
+
return minimum?.gte(swap.fromAmount) || maximum?.lte(swap.fromAmount);
|
|
28
|
+
} else {
|
|
29
|
+
return minimum?.gt(swap.fromAmount) || maximum?.lt(swap.fromAmount);
|
|
30
|
+
}
|
|
31
|
+
}).length > 0
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getSwapButtonTitle(
|
|
36
|
+
accounts: Account[],
|
|
37
|
+
loading: boolean,
|
|
38
|
+
hasLimitError: boolean,
|
|
39
|
+
highValueLoss: boolean,
|
|
40
|
+
priceImpactCanNotBeComputed: boolean,
|
|
41
|
+
needsToWarnEthOnPath: boolean,
|
|
42
|
+
): string {
|
|
43
|
+
if (loading) return 'Finding Best Route...';
|
|
44
|
+
|
|
45
|
+
if (accounts.length == 0) return 'Connect Wallet';
|
|
46
|
+
else if (hasLimitError) return 'Limit Error';
|
|
47
|
+
else if (highValueLoss) return 'Price impact is too high!';
|
|
48
|
+
else if (priceImpactCanNotBeComputed) return 'USD price is unknown, price impact might be high!';
|
|
49
|
+
else if (needsToWarnEthOnPath) return 'The route goes through Ethereum. Continue?';
|
|
50
|
+
else return 'Swap';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function canComputePriceImpact(
|
|
54
|
+
bestRoute: BestRouteResponse | null,
|
|
55
|
+
inputAmount: string,
|
|
56
|
+
inputUsdValue: BigNumber,
|
|
57
|
+
outputUsdValue: BigNumber,
|
|
58
|
+
) {
|
|
59
|
+
return !(
|
|
60
|
+
(inputUsdValue.lte(ZERO) || outputUsdValue.lte(ZERO)) &&
|
|
61
|
+
!!bestRoute?.result &&
|
|
62
|
+
!!inputAmount &&
|
|
63
|
+
inputAmount !== '0' &&
|
|
64
|
+
parseFloat(inputAmount || '0') !== 0
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function calculatePendingSwap(
|
|
69
|
+
inputAmount: string,
|
|
70
|
+
bestRoute: BestRouteResponse,
|
|
71
|
+
wallets: { [p: string]: WalletTypeAndAddress },
|
|
72
|
+
settings: SwapSavedSettings,
|
|
73
|
+
validateBalanceOrFee: boolean,
|
|
74
|
+
): PendingSwap {
|
|
75
|
+
const simulationResult = bestRoute.result;
|
|
76
|
+
if (!simulationResult) throw Error('Simulation result should not be null');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
creationTime: new Date().getTime().toString(),
|
|
80
|
+
finishTime: null,
|
|
81
|
+
requestId: bestRoute.requestId || '',
|
|
82
|
+
inputAmount: inputAmount,
|
|
83
|
+
wallets,
|
|
84
|
+
status: 'running',
|
|
85
|
+
isPaused: false,
|
|
86
|
+
extraMessage: null,
|
|
87
|
+
extraMessageSeverity: null,
|
|
88
|
+
extraMessageDetail: null,
|
|
89
|
+
extraMessageErrorCode: null,
|
|
90
|
+
networkStatusExtraMessage: null,
|
|
91
|
+
networkStatusExtraMessageDetail: null,
|
|
92
|
+
lastNotificationTime: null,
|
|
93
|
+
settings: settings,
|
|
94
|
+
simulationResult: simulationResult,
|
|
95
|
+
validateBalanceOrFee,
|
|
96
|
+
//TODO: finalize PendingSwap type and remove ts-ignore
|
|
97
|
+
//@ts-ignore
|
|
98
|
+
steps:
|
|
99
|
+
bestRoute.result?.swaps?.map((s, i) => ({
|
|
100
|
+
id: i + 1,
|
|
101
|
+
fromBlockchain: s.from.blockchain,
|
|
102
|
+
fromSymbol: s.from.symbol,
|
|
103
|
+
fromSymbolAddress: s.from.address,
|
|
104
|
+
fromDecimals: s.from.decimals,
|
|
105
|
+
fromAmountPrecision: s.fromAmountPrecision,
|
|
106
|
+
fromAmountMinValue: s.fromAmountMinValue,
|
|
107
|
+
fromAmountMaxValue: s.fromAmountMaxValue,
|
|
108
|
+
toBlockchain: s.to.blockchain,
|
|
109
|
+
fromLogo: s.from.logo,
|
|
110
|
+
toSymbol: s.to.symbol,
|
|
111
|
+
toSymbolAddress: s.to.address,
|
|
112
|
+
toDecimals: s.to.decimals,
|
|
113
|
+
toLogo: s.to.logo,
|
|
114
|
+
startTransactionTime: new Date().getTime(),
|
|
115
|
+
swapperId: s.swapperId,
|
|
116
|
+
expectedOutputAmountHumanReadable: s.toAmount,
|
|
117
|
+
outputAmount: null,
|
|
118
|
+
status: 'created',
|
|
119
|
+
networkStatus: null,
|
|
120
|
+
executedTransactionId: null,
|
|
121
|
+
externalTransactionId: null,
|
|
122
|
+
explorerUrl: null,
|
|
123
|
+
trackingCode: null,
|
|
124
|
+
cosmosTransaction: null,
|
|
125
|
+
solanaTransaction: null,
|
|
126
|
+
starknetTransaction: null,
|
|
127
|
+
starknetApprovalTransaction: null,
|
|
128
|
+
tronTransaction: null,
|
|
129
|
+
tronApprovalTransaction: null,
|
|
130
|
+
evmTransaction: null,
|
|
131
|
+
evmApprovalTransaction: null,
|
|
132
|
+
transferTransaction: null,
|
|
133
|
+
diagnosisUrl: null,
|
|
134
|
+
internalSteps: null,
|
|
135
|
+
fromBlockchainLogo: '',
|
|
136
|
+
toBlockchainLogo: '',
|
|
137
|
+
swapperLogo: '',
|
|
138
|
+
swapperType: '',
|
|
139
|
+
})) || [],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function requiredWallets(route: BestRouteResponse | null) {
|
|
144
|
+
const wallets: string[] = [];
|
|
145
|
+
|
|
146
|
+
route?.result?.swaps.forEach((swap) => {
|
|
147
|
+
const currentStepFromBlockchain = swap.from.blockchain;
|
|
148
|
+
const currentStepToBlockchain = swap.to.blockchain;
|
|
149
|
+
let lastAddedWallet = wallets[wallets.length - 1];
|
|
150
|
+
if (currentStepFromBlockchain != lastAddedWallet) wallets.push(currentStepFromBlockchain);
|
|
151
|
+
lastAddedWallet = wallets[wallets.length - 1];
|
|
152
|
+
if (currentStepToBlockchain != lastAddedWallet) wallets.push(currentStepToBlockchain);
|
|
153
|
+
});
|
|
154
|
+
return wallets;
|
|
155
|
+
}
|