diff --git a/batcher-ui/src/actions/exchange.ts b/batcher-ui/src/actions/exchange.ts index 69c38f2a..beacd0ff 100644 --- a/batcher-ui/src/actions/exchange.ts +++ b/batcher-ui/src/actions/exchange.ts @@ -1,4 +1,10 @@ -import { BatcherStatus, CurrentSwap, PriceStrategy, Token } from '@/types'; +import { + BatcherStatus, + CurrentSwap, + PriceStrategy, + Token, + ValidSwap, +} from '@/types'; export const updatePriceStrategy = (priceStrategy: PriceStrategy) => ({ @@ -119,6 +125,17 @@ export const getTokens = () => type: 'GET_TOKENS', }) as const; +export const updateSwaps = (swaps: Map) => + ({ + type: 'UPDATE_SWAPS', + payload: { swaps }, + }) as const; + +export const getSwaps = () => + ({ + type: 'GET_SWAPS', + }) as const; + export type ExchangeActions = | ReturnType | ReturnType @@ -138,4 +155,6 @@ export type ExchangeActions = | ReturnType | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType + | ReturnType; diff --git a/batcher-ui/src/commands/exchange.ts b/batcher-ui/src/commands/exchange.ts index 17c60121..6dc63cc9 100644 --- a/batcher-ui/src/commands/exchange.ts +++ b/batcher-ui/src/commands/exchange.ts @@ -7,6 +7,7 @@ import { getVolumes, getTimeDifferenceInMs, getTokens, + getSwaps, } from '@/utils/utils'; import { getPairsInformation } from '@/utils/token-manager'; import { @@ -19,8 +20,15 @@ import { updateRemainingTime, newError, updateTokens, + updateSwaps, } from '@/actions'; -import { BatcherStatus, CurrentSwap, SwapNames, Token } from '@/types'; +import { + BatcherStatus, + CurrentSwap, + SwapNames, + Token, + ValidSwap, +} from '@/types'; const fetchPairInfosCmd = (pair: string) => Cmd.run( @@ -111,7 +119,10 @@ const fetchTokensCmd = () => { return Cmd.run( async () => { const tokens = await getTokens(); - const mapped: Map = ((tokens as unknown) as Map); + const mapped: Map = tokens as unknown as Map< + string, + Token + >; console.info('Mapped tokens', mapped); return mapped; }, @@ -121,6 +132,26 @@ const fetchTokensCmd = () => { } ); }; + +const fetchSwapsCmd = () => { + return Cmd.run( + async () => { + const swaps = await getSwaps(); + const mapped: Map = swaps as unknown as Map< + string, + ValidSwap + >; + console.info('Mapped swaps', mapped); + return mapped; + }, + { + successActionCreator: updateSwaps, + failActionCreator: (e: string) => newError(e), + } + ); +}; + + export { fetchPairInfosCmd, fetchCurrentBatchNumberCmd, @@ -129,4 +160,5 @@ export { fetchOraclePriceCmd, fetchVolumesCmd, fetchTokensCmd, + fetchSwapsCmd, }; diff --git a/batcher-ui/src/components/batcher/SelectPair.tsx b/batcher-ui/src/components/batcher/SelectPair.tsx index c1778728..d46a4d80 100644 --- a/batcher-ui/src/components/batcher/SelectPair.tsx +++ b/batcher-ui/src/components/batcher/SelectPair.tsx @@ -7,11 +7,16 @@ import { faChevronUp, } from '@fortawesome/free-solid-svg-icons'; import { useSelector } from 'react-redux'; -import { currentSwapSelector } from '@/reducers'; +import { currentSwapSelector, tokensSelector, swapsSelector } from '@/reducers'; import { useDispatch } from 'react-redux'; -import { changePair } from '@/actions'; +import { changePair, newError } from '@/actions'; +import { ValidSwap } from '@/types'; import Image from 'next/image'; -import { getTokensMetadata } from '@/utils/token-manager'; +import { + getLexicographicalPairName, + getTokensMetadata, +} from '@/utils/token-manager'; +import { ensureMapTypeOnSwaps } from '@/utils/utils'; interface SelectPairProps { isFrom: boolean; @@ -21,8 +26,10 @@ const SelectPair = ({ isFrom }: SelectPairProps) => { const { swap, isReverse } = useSelector(currentSwapSelector); const dispatch = useDispatch(); - //const tokens = useSelector(tokensSelector); + const tokens = useSelector(tokensSelector); + const swaps = useSelector(swapsSelector); const [availableTokens, setAvailableTokens] = useState([]); + const [availableSwaps, setAvailableSwaps] = useState([]); const displayValue = useCallback(() => { if (isReverse && isFrom) return swap.to.name; @@ -38,20 +45,54 @@ const SelectPair = ({ isFrom }: SelectPairProps) => { tokens: { name: string; address: string; icon: string | undefined }[] ) => { setAvailableTokens(tokens); + console.info('swaps - before', swaps); + const swapsAsMap: Map = ensureMapTypeOnSwaps(swaps); + console.info('swaps - dispatch', swapsAsMap); + console.info('swaps keys - dispatch', swapsAsMap.keys()); + const swps: string[] = Array.from(swapsAsMap.keys()); + console.info('swps - dispatch', swps); + setAvailableSwaps(swps); } ); - }, []); + }, [dispatch, tokens, swaps]); + + const isValidPair = (pair: string) => availableSwaps.includes(pair); + + const invalidSwap = (pairName: string) => { + dispatch( + newError( + pairName + + ' is not a valid pair. Only ' + + availableSwaps.join() + + 'are supported.' + ) + ); + }; + + const is_reversed = (pair: string, to: string, from: string) => { + const swap = ensureMapTypeOnSwaps(swaps).get(pair); + return to === swap?.swap.to && from === swap?.swap.from; + }; return ( { - //const pair = isFrom ? `${value}-${swap.to.name}` : `${swap.from.name}-${value}`; - //const availableSwap = availableSwaps[pair]; - // const reversed = (!isFrom && value === ${swap.from.name}) || (isFrom && value === ${swap.to.name}); - const pair = 'tzBTC-USDT'; - const reversed = false; - dispatch(changePair(pair, reversed)); + console.info('availableSwaps', availableSwaps); + console.info('swaps', swaps); + console.info('swap', swap); + console.info('value', value); + console.info('isFrom', isFrom); + const from = isFrom ? `${swap.to.name}` : `${value}`; + const to = isFrom ? `${value}` : `${swap.from.token.name}`; + const pairName = getLexicographicalPairName(to, from); + const is_valid = isValidPair(pairName); + if (is_valid) { + const reversed = is_reversed(pairName, to, from); + dispatch(changePair(pairName, reversed)); + } else { + invalidSwap(pairName); + } }} > diff --git a/batcher-ui/src/pages/index.tsx b/batcher-ui/src/pages/index.tsx index fa593fbe..7cf80392 100644 --- a/batcher-ui/src/pages/index.tsx +++ b/batcher-ui/src/pages/index.tsx @@ -4,12 +4,10 @@ import BatcherInfo from '@/components/batcher/BatcherInfo'; import PriceStrategy from '@/components/batcher/PriceStrategy'; import { useSelector, useDispatch } from 'react-redux'; -import { - currentPairSelector, - userAddressSelector, -} from '@/reducers'; +import { currentPairSelector, userAddressSelector } from '@/reducers'; import { getTokens, + getSwaps, fetchUserBalances, batcherUnsetup, getPairsInfos, @@ -38,6 +36,7 @@ const Swap = () => { useEffect(() => { dispatch(getTokens()); + dispatch(getSwaps()); }, [dispatch]); return ( diff --git a/batcher-ui/src/reducers/exchange.ts b/batcher-ui/src/reducers/exchange.ts index 28770aa7..dbbd4e74 100644 --- a/batcher-ui/src/reducers/exchange.ts +++ b/batcher-ui/src/reducers/exchange.ts @@ -12,6 +12,7 @@ import { ExchangeState, PriceStrategy, Token, + ValidSwap, } from '@/types'; import { fetchBatcherStatusCmd, @@ -21,6 +22,7 @@ import { fetchOraclePriceCmd, setupBatcherCmd, fetchTokensCmd, + fetchSwapsCmd, } from '@/commands/exchange'; import { getTimeDifference } from 'src/utils/utils'; @@ -61,6 +63,7 @@ const initialState: ExchangeState = { batchNumber: 0, oraclePrice: 0, tokens: new Map(), + swaps: new Map(), volumes: { sell: Object.keys(PriceStrategy).reduce( (acc, k) => ({ ...acc, [k]: 0 }), @@ -205,6 +208,12 @@ const exchangeReducer = ( return { ...state, tokens: action.payload.tokens }; case 'GET_TOKENS': return loop(state, fetchTokensCmd()); + case 'UPDATE_SWAPS': + console.info('swaps', action.payload.swaps); + console.info('state', state); + return { ...state, swaps: action.payload.swaps }; + case 'GET_SWAPS': + return loop(state, fetchSwapsCmd()); default: return state; } diff --git a/batcher-ui/src/reducers/index.ts b/batcher-ui/src/reducers/index.ts index 21c24e72..510f390a 100644 --- a/batcher-ui/src/reducers/index.ts +++ b/batcher-ui/src/reducers/index.ts @@ -50,6 +50,7 @@ export const oraclePriceSelector = (state: AppState) => state.exchange.oraclePrice; export const tokensSelector = (state: AppState) => state.exchange.tokens; +export const swapsSelector = (state: AppState) => state.exchange.swaps; export const volumesSelector = (state: AppState) => state.exchange.volumes; diff --git a/batcher-ui/src/types/state.ts b/batcher-ui/src/types/state.ts index 223c9bfc..331d827e 100644 --- a/batcher-ui/src/types/state.ts +++ b/batcher-ui/src/types/state.ts @@ -3,7 +3,7 @@ import { PriceStrategy, SwapNames, } from '@/types/contracts/batcher'; -import { ValidTokenAmount } from './contracts/token-manager'; +import { ValidTokenAmount, ValidSwap } from './contracts/token-manager'; export type Token = { address: string | undefined; @@ -45,6 +45,7 @@ export type ExchangeState = { oraclePrice: number; volumes: VolumesState; tokens: Map; + swaps: Map; }; export type WalletState = { diff --git a/batcher-ui/src/utils/token-manager.ts b/batcher-ui/src/utils/token-manager.ts index 19b3c480..af43da49 100644 --- a/batcher-ui/src/utils/token-manager.ts +++ b/batcher-ui/src/utils/token-manager.ts @@ -33,7 +33,8 @@ export const getLexicographicalPairName = ( to: string, from: string ): string => { - if (to > from) { + const comp = to.localeCompare(from); + if (comp < 0) return `${to}-${from}`; } else { return `${from}-${to}`; @@ -197,3 +198,27 @@ export const getTokensFromStorage = async () => { }) ); }; + +export const getSwapsFromStorage = async () => { + const storage = await getTokenManagerStorage(); + const validSwaps = storage['valid_swaps']; + const names = validSwaps.keys; + return Promise.all( + names.map(async swap => { + const t = await getSwapFromBigmap(validSwaps.values, swap); + + const swp = { + from: t.value.swap.from, + to: t.value.swap.to, + }; + + return { + swap: swp, + oracle_address: t.value.oracle_address, + oracle_asset_name: t.value.oracle_asset_name, + oracle_precision: t.value.oracle_precision, + is_disabled_for_deposits: t.value.is_disabled_for_deposits, + }; + }) + ); +} diff --git a/batcher-ui/src/utils/utils.ts b/batcher-ui/src/utils/utils.ts index 88896c75..08b7dad9 100644 --- a/batcher-ui/src/utils/utils.ts +++ b/batcher-ui/src/utils/utils.ts @@ -18,11 +18,14 @@ import { RatesCurrentBigmap, Token, ValidToken, + ValidSwap, ValidTokenAmount, } from '@/types'; import { getTokenManagerStorage, getTokensFromStorage, + getSwapsFromStorage, + getLexicographicalPairName, } from '@/utils/token-manager'; import { NetworkType } from '@airgap/beacon-sdk'; import { getByKey } from '@/utils/local-storage'; @@ -38,6 +41,22 @@ export const getTokens = async () => { }; }; +export const getSwaps = async () => { + const swaps = await getSwapsFromStorage(); + console.info('getSwaps swaps', swaps); + const swapsMap = new Map( + swaps.map((value, index) => [ + getLexicographicalPairName(value.swap.to, value.swap.from), + value, + ])<<<<<<< 410-further-split-out-batcher-and-mm-contracts-to-avoid-size-constraint + ); + console.info('getSwaps swapMap', swapsMap); + + return { + swaps: swapsMap, + }; +}; + export const scaleAmountDown = (amount: number, decimals: number) => { const scale = 10 ** -decimals; return amount * scale; @@ -383,6 +402,23 @@ export const ensureMapTypeOnTokens = ( } }; +export const ensureMapTypeOnSwaps = ( + swaps: Map +): Map => { + const typeOfSwaps = typeof swaps; + console.info('swaps type', typeOfSwaps); + if (swaps instanceof Map) { + return swaps; + } else { + let swps: Map = new Map(); + Object.values(swaps).forEach(v => { + console.info('v', v); + swps = v as Map; + }); + return swps; + } +}; + export const getTimeDifferenceInMs = ( status: BatcherStatus, startTime: string | null @@ -484,8 +520,8 @@ const findTokensForBatch = (batch: BatchBigmap, toks: Map) => { const buyToken = tokens.get(pair.string_0); const sellToken = tokens.get(pair.string_1); const tkns = { - to: { name: buyToken?.name || "", decimals: buyToken?.decimals || 0 }, - from: { name: sellToken?.name || "", decimals: sellToken?.decimals || 0 }, + to: { name: buyToken?.name || '', decimals: buyToken?.decimals || 0 }, + from: { name: sellToken?.name || '', decimals: sellToken?.decimals || 0 }, }; return tkns; };