Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧Add switches to toggle protocols #535

Merged
merged 8 commits into from
Sep 5, 2024
26 changes: 22 additions & 4 deletions src/components/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Analytics } from '@vercel/analytics/react';
import { AppContext, AppContextType, ColorModeContext, SnackbarIconType } from 'contexts';
import { CssBaseline, ThemeProvider } from 'soroswap-ui';
import { AppContext, AppContextType, ColorModeContext, SnackbarIconType, ProtocolsStatus } from 'contexts';
import { Provider } from 'react-redux';
import { theme } from 'soroswap-ui';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import InkathonProvider from 'inkathon/InkathonProvider';
import MainLayout from './Layout/MainLayout';
import MySorobanReactProvider from 'soroban/MySorobanReactProvider';
import store from 'state';
import { SorobanContextType } from '@soroban-react/core';
import { SoroswapThemeProvider } from 'soroswap-ui';
import { Protocols } from 'soroswap-router-sdk';
import { PlatformType } from 'state/routing/types';

export default function Providers({
children,
sorobanReactProviderProps,
Expand All @@ -21,6 +22,19 @@ export default function Providers({

const [maxHops, setMaxHops] = useState<number>(2);

//Defines the default protocols to be used in the app
const defaultProtocols = [
Protocols.SOROSWAP
]
const [protocols, setProtocols] = useState<Protocols[]>(defaultProtocols);

//Defines the default platforms to be used in the app
const defaultProtocolsStatus: ProtocolsStatus[] = [
{ key: Protocols.SOROSWAP, value: true },
{ key: Protocols.PHOENIX, value: false },
{ key: PlatformType.STELLAR_CLASSIC, value: true },
]
const [protocolsStatus, setProtocolsStatus] = useState<ProtocolsStatus[]>(defaultProtocolsStatus);
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
const [snackbarMessage, setSnackbarMessage] = useState<string>('');
const [snackbarTitle, setSnackbarTitle] = useState<string>('Swapped');
Expand Down Expand Up @@ -54,6 +68,10 @@ export default function Providers({
Settings: {
maxHops,
setMaxHops,
protocols,
setProtocols,
protocolsStatus,
setProtocolsStatus,
},
};

Expand Down
124 changes: 124 additions & 0 deletions src/components/Settings/ProtocolsSettings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Expand from 'components/Expand';
import QuestionHelper from 'components/QuestionHelper';
import Row, { RowBetween } from 'components/Row';
import { BodySmall } from 'components/Text';
import { AppContext } from 'contexts';
import { useRouterSDK } from 'functions/generateRoute';
import React, { useContext, useEffect, useState } from 'react'
import { Box, styled, Switch, SwitchProps, Typography, useTheme } from 'soroswap-ui';


export const CustomSwitch = styled((props: SwitchProps) => (
<Switch sx={{ my: 1 }} focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} />
))(({ theme }) => ({
width: 42,
height: 26,
padding: 0,
alignContent: 'center',
alignItems: 'center',
'& .MuiSwitch-switchBase': {
padding: 0,
margin: 3,
'&.Mui-checked': {
transform: 'translateX(16px)',
color: '#8866DD',
'& .MuiSwitch-thumb:before': {
backgroundColor: '#8866DD',
borderRadius: 32,
},
'& + .MuiSwitch-track': {
backgroundColor: theme.palette.background.paper,
opacity: 1,
border: 0,
},
},
},
'& .MuiSwitch-thumb': {
backgroundColor: 'rgba(136, 102, 221, 0.25)',
width: 20,
height: 20,
'&:before': {
content: "''",
position: 'absolute',
width: '100%',
height: '100%',
left: 0,
top: 0,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
},
},
'& .MuiSwitch-track': {
borderRadius: 32,
backgroundColor: theme.palette.background.paper,
opacity: 1,
},
}));


const firstLetterUppercase = (string: string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const ProtocolsSettings = () => {
const { resetRouterSdkCache } = useRouterSDK();
const theme = useTheme();
const [isOpen, setIsOpen] = useState<boolean>(false);
const { protocolsStatus, setProtocolsStatus } = useContext(AppContext).Settings;

const switchProtocolValue = (key: string) => {
const newProtocolsStatus = protocolsStatus.map((protocol) => {
if (protocol.key === key) {
return {
key: protocol.key,
value: !protocol.value,
};
}
return protocol;
});
const hasTrueValue = newProtocolsStatus.some((protocol) => protocol.value);
if (hasTrueValue) {
setProtocolsStatus(newProtocolsStatus);
resetRouterSdkCache();
}
else return;
}

return (

<Expand
testId="protocols-settings"
isOpen={isOpen}
onToggle={() => setIsOpen(!isOpen)}
header={
<Row width="auto">
<Typography color={theme.palette.secondary.main}>Protocols</Typography>
<QuestionHelper
text={
<div>
The protocols Soroswap.finance will use to calculate the most efficient path for your transaction.
</div>
}
/>
</Row>
}
button={<></>}
>
<RowBetween gap="md" width={'100%'}>
<Box sx={{ ml: 2 }} width={'100%'} >
{protocolsStatus.map((option, index) => {
return (
<Row key={index} gap="4px" justify='space-between' align='center'>
<BodySmall fontWeight={100} color={theme.palette.secondary.main} >{firstLetterUppercase(option.key)}</BodySmall>
<CustomSwitch checked={option.value} onClick={() => { switchProtocolValue(option.key) }} color="secondary" />
</Row>
)
})}
</Box>
</RowBetween>

</Expand>

)
}

export default ProtocolsSettings
2 changes: 2 additions & 0 deletions src/components/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useRef, useState } from 'react';
import MaxSlippageSettings from './MaxSlippageSettings';
import MenuButton from './MenuButton';
import MaxHopsSettings from './MaxHopsSettings';
import ProtocolsSettings from './ProtocolsSettings';

const Menu = styled('div')`
position: relative;
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function SettingsTab({
<ExpandColumn>
<MaxSlippageSettings autoSlippage={autoSlippage} />
<MaxHopsSettings />
<ProtocolsSettings />
</ExpandColumn>
</AnimatedDropdown>
</MenuFlyout>
Expand Down
15 changes: 15 additions & 0 deletions src/contexts/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import { Protocols } from 'soroswap-router-sdk';
import { PlatformType } from 'state/routing/types';

type ConnectWalletModalType = {
isConnectWalletModalOpen: boolean;
Expand All @@ -13,6 +15,11 @@ export enum SnackbarIconType {
ERROR,
}

export interface ProtocolsStatus {
key: Protocols | PlatformType;
value: boolean;
}

export type SnackbarContextType = {
openSnackbar: boolean;
snackbarMessage: string;
Expand All @@ -27,6 +34,10 @@ export type SnackbarContextType = {
export type Settings = {
maxHops: number;
setMaxHops: React.Dispatch<React.SetStateAction<number>>;
protocols: Protocols[];
setProtocols: React.Dispatch<React.SetStateAction<Protocols[]>>;
protocolsStatus: ProtocolsStatus[];
setProtocolsStatus: React.Dispatch<React.SetStateAction<ProtocolsStatus[]>>;
};

export type AppContextType = {
Expand Down Expand Up @@ -57,5 +68,9 @@ export const AppContext = React.createContext<AppContextType>({
Settings: {
maxHops: 2,
setMaxHops: () => {},
protocols: [],
setProtocols: () => {},
protocolsStatus: [],
setProtocolsStatus: () => {},
},
});
87 changes: 54 additions & 33 deletions src/functions/generateRoute.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SorobanContextType, useSorobanReact } from '@soroban-react/core';
import { useSorobanReact } from '@soroban-react/core';
import { AppContext } from 'contexts';
import { useFactory } from 'hooks';
import { useAggregator } from 'hooks/useAggregator';
Expand Down Expand Up @@ -57,34 +57,48 @@ export const useRouterSDK = () => {
const { isEnabled: isAggregator } = useAggregator();

const { Settings } = useContext(AppContext);
const { maxHops } = Settings;
const { maxHops, protocols, protocolsStatus } = Settings;

const network = sorobanContext.activeChain?.networkPassphrase as Networks;

const router = useMemo(() => {
const protocols = [Protocols.SOROSWAP];

// if (isAggregator) protocols.push(Protocols.PHOENIX);
const getPairsFns = useMemo(() => {
const routerProtocols = []
if(!shouldUseBackend) return undefined
// here you should add your new supported aggregator protocols
for(let protocol of protocolsStatus){
if(protocol.key === Protocols.SOROSWAP && protocol.value === true){
routerProtocols.push({protocol: Protocols.SOROSWAP, fn: async () => fetchAllSoroswapPairs(network)});
}
if(protocol.key === Protocols.PHOENIX && protocol.value === true){
routerProtocols.push({protocol: Protocols.PHOENIX, fn: async () => fetchAllPhoenixPairs(network)});
}
}
return routerProtocols;
}, [network, protocolsStatus]);

const getProtocols = useMemo(() => {
const newProtocols = [];
for(let protocol of protocolsStatus){
if(protocol.key != PlatformType.STELLAR_CLASSIC && protocol.value === true){
newProtocols.push(protocol.key);
}
}
return newProtocols as Protocols[];
},[protocolsStatus]);

const router = useMemo(() => {
return new Router({
getPairsFns: shouldUseBackend
? [
{
protocol: Protocols.SOROSWAP,
fn: async () => fetchAllSoroswapPairs(network),
},
{
protocol: Protocols.PHOENIX,
fn: async () => fetchAllPhoenixPairs(network),
},
]
: undefined,
pairsCacheInSeconds: 60,
protocols: protocols,
getPairsFns: getPairsFns,
pairsCacheInSeconds: 5,
protocols: getProtocols,
network,
maxHops,
});
}, [network, maxHops, isAggregator]);
}, [network, maxHops, isAggregator, protocolsStatus]);

const isProtocolEnabled = (protocol: any) => {
return protocolsStatus.find((p) => p.key === protocol)?.value;
}

const fromAddressToToken = (address: string) => {
return new Token(network, address, 18);
Expand Down Expand Up @@ -112,16 +126,22 @@ export const useRouterSDK = () => {
);
const quoteCurrency = fromAddressToToken(quoteAsset.contract);

const isHorizonEnabled = isProtocolEnabled(PlatformType.STELLAR_CLASSIC);
const isSoroswapEnabled = isProtocolEnabled(Protocols.SOROSWAP);

const horizonProps = {
assetFrom: amountAsset.currency,
assetTo: quoteAsset,
amount,
tradeType,
};

const horizonPath = (await getHorizonBestPath(horizonProps, sorobanContext)) as BuildTradeRoute;

let sorobanPath: BuildTradeRoute;
let horizonPath: BuildTradeRoute | undefined;
if(isHorizonEnabled){
horizonPath = (await getHorizonBestPath(horizonProps, sorobanContext)) as BuildTradeRoute;
}

let sorobanPath: BuildTradeRoute | undefined;
if (isAggregator) {
sorobanPath = (await router
.routeSplit(currencyAmount, quoteCurrency, tradeType)
Expand All @@ -133,7 +153,7 @@ export const useRouterSDK = () => {
};
return result;
})) as BuildTradeRoute;
} else {
} else if(isSoroswapEnabled){
sorobanPath = (await router
.route(currencyAmount, quoteCurrency, tradeType, factory, sorobanContext as any)
.then((response) => {
Expand All @@ -145,18 +165,19 @@ export const useRouterSDK = () => {
return result;
})) as BuildTradeRoute;
}

const bestPath = getBestPath(horizonPath, sorobanPath, tradeType);
// .then((res) => {
// if (!res) return;
// const response = {
// ...res,
// platform: PlatformType.ROUTER,
// };
// return response;
// });

return bestPath;
};

return { generateRoute, resetRouterSdkCache, maxHops };
};
// .then((res) => {
// if (!res) return;
// const response = {
// ...res,
// platform: PlatformType.ROUTER,
// };
// return response;
// });