-
{item.text}
-
- {item.isNew && }
-
+
+
+
{item.text}
+
+ {item.isNew && }
-
+
+
- {hasSubMenu ? (
- open ? (
-
- ) : (
-
- )
+ {hasSubMenu ? (
+ open ? (
+
) : (
- <>>
- )}
-
+
+ )
+ ) : (
+ <>>
+ )}
{hasSubMenu && (
= ({
const config = getConfig(chainId);
const showPerps = config['perps']['available'];
+ const showPools = config['pools']['available'];
+
const showHydra = config['hydra']['available'];
const showPerpsV2 = config['perpsV2']['available'];
@@ -375,9 +377,11 @@ const MobileHeader: React.FC = ({
}}
/>
-
- {t('pool')}
-
+ {showPools && (
+
+ {t('pool')}
+
+ )}
= ({
onClick={() => setOpen(false)}
/>
))}
-
- {
- setOpen(false);
- }}
- >
-
-
- Close
-
-
-
+ {
+ setOpen(false);
+ }}
+ >
+
+
+ Close
+
+
-
-
+
+
diff --git a/src/components/Header/StyledMenu.tsx b/src/components/Header/StyledMenu.tsx
index b87eb5f2e..8235dc362 100644
--- a/src/components/Header/StyledMenu.tsx
+++ b/src/components/Header/StyledMenu.tsx
@@ -1,6 +1,5 @@
import * as React from 'react';
-import { styled, alpha } from '@mui/material/styles';
-import Menu, { MenuProps } from '@mui/material/Menu';
+import { MenuProps, styled, Menu } from '@material-ui/core';
export const StyledMenu = styled((props: MenuProps) => (
+ >
+ ) : (
+ {tab.text}
+ )}
+
+ >
+ ))}
+
+ )
)}
{
{showCrossChain && Number(swapType) === SWAP_CROSS_CHAIN && (
)}
- {showLimitOrder && Number(swapType) === SWAP_LIMIT && (
-
- )}
- {swapType === SWAP_TWAP.toString() && }
+ {showLimitOrder &&
+ (Number(swapType) === SWAP_LIMIT ||
+ Number(swapType) === SWAP_TWAP) && }
>
);
diff --git a/src/pages/SwapPage/V3/Swap/index.tsx b/src/pages/SwapPage/V3/Swap/index.tsx
index 8e6335830..78cc5d5e5 100644
--- a/src/pages/SwapPage/V3/Swap/index.tsx
+++ b/src/pages/SwapPage/V3/Swap/index.tsx
@@ -68,7 +68,6 @@ import { useTranslation } from 'react-i18next';
import { useTransactionFinalizer } from 'state/transactions/hooks';
import { getConfig } from 'config/index';
import { useUSDCPriceFromAddress } from 'utils/useUSDCPrice';
-import { useV3TradeTypeAnalyticsCallback } from 'components/Swap/LiquidityHub';
import useNativeConvertCallback, {
ConvertType,
} from 'hooks/useNativeConvertCallback';
@@ -356,17 +355,11 @@ const SwapV3Page: React.FC = () => {
const { price: fromTokenUSDPrice } = useUSDCPriceFromAddress(
currencies[Field.INPUT]?.wrapped.address ?? '',
);
- const onV3TradeAnalytics = useV3TradeTypeAnalyticsCallback(
- currencies,
- allowedSlippage,
- );
-
const isUni = trade?.swaps[0]?.route?.pools[0]?.isUni;
const { walletInfo } = useWalletInfo();
const handleSwap = useCallback(() => {
- onV3TradeAnalytics(formattedAmounts);
if (!swapCallback) {
return;
}
@@ -460,7 +453,6 @@ const SwapV3Page: React.FC = () => {
});
});
}, [
- onV3TradeAnalytics,
formattedAmounts,
swapCallback,
tradeToConfirm,
diff --git a/src/pages/styles/swap.scss b/src/pages/styles/swap.scss
index 4946de4e7..e0fb2e5e4 100644
--- a/src/pages/styles/swap.scss
+++ b/src/pages/styles/swap.scss
@@ -508,3 +508,75 @@ div.buy {
}
}
}
+
+
+/// Orbs ///
+ .orbs_SwapStepTitle {
+ font-size: 15px;
+}
+.LiquidityHubSuccessContent {
+ font-size: 16px;
+ a {
+ text-decoration: none;
+ display: inline-flex;
+ gap: 5px;
+ font-weight: 600;
+ color: white;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ span {
+ text-transform: capitalize;
+ font-size: inherit;
+ }
+
+ img {
+ width: 22px;
+ height: 22px;
+ object-fit: contain;
+ }
+
+}
+.LiquidityHubSettings {
+ display: flex;
+ flex-direction: column;
+
+ .bottom-text {
+ max-width: 500px;
+ font-size: 14px;
+ line-height: 23px;
+
+ img {
+ width: 18px;
+ height: 18px;
+ margin-right: 8px;
+ display: inline;
+ }
+
+ a {
+ text-decoration: none;
+ font-weight: 600;
+ color: #6381e9;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ .more-info {
+ color: inherit;
+ font-weight: 400;
+ text-decoration: underline;
+ }
+ }
+}
+
+.orbs_PoweredBy {
+ margin-top: 30px!important;
+ span {
+ font-size: 15px;
+ }
+}
diff --git a/src/state/index.ts b/src/state/index.ts
index c4904a249..a579f2de5 100755
--- a/src/state/index.ts
+++ b/src/state/index.ts
@@ -5,6 +5,7 @@ import { updateVersion } from './global/actions';
import user from './user/reducer';
import transactions from './transactions/reducer';
import swap from './swap/reducer';
+import twap from './swap/twap/reducer';
import mint from './mint/reducer';
import mintV3 from './mint/v3/reducer';
import lists from './lists/reducer';
@@ -16,7 +17,6 @@ import multicall from './multicall/reducer';
import multicallV3 from './multicall/v3/reducer';
import swapV3 from './swap/v3/reducer';
import zap from './zap/reducer';
-import liquidityHub from './swap/liquidity-hub/reducer';
import singleToken from './singleToken/reducer';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import userBalance from './balance/reducer';
@@ -35,6 +35,7 @@ const store = configureStore({
user,
transactions,
swap,
+ twap,
userBalance,
swapV3,
mint,
@@ -47,7 +48,6 @@ const store = configureStore({
farms,
syrups,
zap,
- liquidityHub,
singleToken,
},
middleware: (getDefaultMiddleware) => [
diff --git a/src/state/lists/hooks.ts b/src/state/lists/hooks.ts
index 4a26040c4..d7aa4438f 100755
--- a/src/state/lists/hooks.ts
+++ b/src/state/lists/hooks.ts
@@ -56,9 +56,6 @@ export type TokenAddressMap = Readonly<
}
>;
-/**
- * An empty result, useful as a default.
- */
const EMPTY_LIST: TokenAddressMap = {
[ChainId.ETHEREUM]: {},
[ChainId.MUMBAI]: {},
diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts
index 59364f969..033cbcccd 100644
--- a/src/state/swap/hooks.ts
+++ b/src/state/swap/hooks.ts
@@ -298,7 +298,7 @@ export function useDerivedSwapInfo(): {
useEffect(() => {
const stableCoins = GlobalData.stableCoins[chainIdToUse];
const stableCoinAddresses =
- stableCoins && stableCoins.length > 0
+ stableCoins && stableCoins?.length > 0
? stableCoins.map((token) => token.address.toLowerCase())
: [];
if (!swapSlippage && !slippageManuallySet) {
diff --git a/src/state/swap/liquidity-hub/actions.ts b/src/state/swap/liquidity-hub/actions.ts
deleted file mode 100644
index 79d55fb29..000000000
--- a/src/state/swap/liquidity-hub/actions.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { createAction } from '@reduxjs/toolkit';
-import { LiquidityHubState } from './reducer';
-
-export const setLiquidityHubState = createAction>(
- 'liquidityHub/setLiquidityHubState',
-);
-
-export const resetLiquidityHubState = createAction(
- 'liquidityHub/resetLiquidityHubState',
-);
diff --git a/src/state/swap/liquidity-hub/hooks.ts b/src/state/swap/liquidity-hub/hooks.ts
deleted file mode 100644
index 10d0de127..000000000
--- a/src/state/swap/liquidity-hub/hooks.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useAppSelector } from 'state/hooks';
-import { AppState, useAppDispatch } from 'state';
-import { resetLiquidityHubState, setLiquidityHubState } from './actions';
-import { LiquidityHubState } from './reducer';
-import { useCallback } from 'react';
-
-export function useLiquidityHubState(): AppState['liquidityHub'] {
- return useAppSelector((state) => {
- return state.liquidityHub;
- });
-}
-
-export const useLiquidityHubActionHandlers = () => {
- const dispatch = useAppDispatch();
-
- const onSetLiquidityHubState = useCallback(
- (payload: Partial) => {
- dispatch(setLiquidityHubState(payload));
- },
- [dispatch],
- );
-
- const onResetLiquidityHubState = useCallback(() => {
- dispatch(resetLiquidityHubState());
- }, [dispatch]);
-
- return {
- onSetLiquidityHubState,
- onResetLiquidityHubState,
- };
-};
diff --git a/src/state/swap/liquidity-hub/reducer.ts b/src/state/swap/liquidity-hub/reducer.ts
deleted file mode 100644
index 78e692ff1..000000000
--- a/src/state/swap/liquidity-hub/reducer.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { createReducer } from '@reduxjs/toolkit';
-import { resetLiquidityHubState, setLiquidityHubState } from './actions';
-
-export interface LiquidityHubState {
- isWon: boolean;
- isLoading: boolean;
- isFailed?: boolean;
- outAmount?: string;
- waitingForApproval?: boolean;
- waitingForSignature?: boolean;
- waitingForWrap?: boolean;
-}
-
-const initialState = {
- isWon: false,
- isLoading: false,
- isFailed: false,
-};
-
-export default createReducer(initialState, (builder) =>
- builder
- .addCase(setLiquidityHubState, (state, { payload }) => {
- return { ...state, ...payload };
- })
- .addCase(resetLiquidityHubState, (state) => {
- return initialState;
- }),
-);
diff --git a/src/state/swap/twap/actions.ts b/src/state/swap/twap/actions.ts
new file mode 100755
index 000000000..4ae7bd166
--- /dev/null
+++ b/src/state/swap/twap/actions.ts
@@ -0,0 +1,98 @@
+import { createAction } from '@reduxjs/toolkit';
+import { SmartRouter, RouterTypes } from 'constants/index';
+import { TimeDuration } from '@orbs-network/twap-sdk';
+
+export enum Field {
+ INPUT = 'INPUT',
+ OUTPUT = 'OUTPUT',
+}
+
+export enum SwapDelay {
+ INIT = 'INIT', // The initial state of the swap cycle before a user interaction
+ USER_INPUT = 'USER_INPUT', // Swap state when a user is inputing a trade
+ USER_INPUT_COMPLETE = 'USER_INPUT_COMPLETE', // To let the app know when to start calculating swaps
+ FETCHING_SWAP = 'FETCHING_SWAP', // To not calculate swaps on every keystroke
+ SWAP_COMPLETE = 'SWAP_COMPLETE', // When the swap is ready to be displayed and fetch a bonus
+ FETCHING_BONUS = 'FETCHING_BONUS', // Checks if the swap has a valid bous route
+ SWAP_REFRESH = 'SWAP_REFRESH', // The last state that lets the app know to refresh routes to check for changes
+}
+
+type SearchSummary = {
+ expectedProfit?: number;
+ expectedUsdProfit?: number;
+ firstTokenAddress?: string;
+ firstTokenAmount?: number;
+ expectedKickbackProfit?: number;
+};
+
+type TransactionArgs = {
+ data: string;
+ destination: string;
+ sender: string;
+ value: string;
+ masterInput: string;
+};
+
+export type DataResponse = {
+ pathFound: boolean;
+ summary?: { searchSummary?: SearchSummary };
+ transactionArgs: TransactionArgs;
+};
+
+export interface RouterTypeParams {
+ routerType: RouterTypes;
+ smartRouter: SmartRouter;
+ bonusRouter?: DataResponse;
+}
+
+export const selectCurrency = createAction<{
+ field: Field;
+ currencyId: string;
+}>('twap/selectCurrency');
+export const switchCurrencies = createAction('twap/switchCurrencies');
+export const typeInput = createAction<{ field: Field; typedValue: string }>(
+ 'twap/typeInput',
+);
+export const setSwapDelay = createAction<{ swapDelay: SwapDelay }>(
+ 'twap/swapDelay',
+);
+
+export const setChunks = createAction<{ chunks: number }>('twap/setChunks');
+
+export const setTradePrice = createAction<{
+ tradePrice?: string;
+}>('twap/setTradePrice');
+
+export const setIsMarketOrder = createAction<{ isMarketOrder: boolean }>(
+ 'twap/setIsMarketOrder',
+);
+
+export const toggleMarketOrder = createAction('twap/toggleMarketOrder');
+
+export const setUpdatingOrders = createAction<{ updatingOrders: boolean }>(
+ 'twap/setUpdatingOrders',
+);
+
+export const setFillDelay = createAction<{ fillDelay: TimeDuration }>(
+ 'twap/setFillDelay',
+);
+
+export const resetTradePrice = createAction('twap/resetTradePrice');
+
+export const setDuration = createAction<{ duration?: TimeDuration }>(
+ 'twap/setDuration',
+);
+
+export const invertTradePrice = createAction<{ isTradePriceInverted: boolean }>(
+ 'twap/invertTradePrice',
+);
+
+export const replaceSwapState = createAction<{
+ typedValue: string;
+ inputCurrencyId?: string;
+ outputCurrencyId?: string;
+ swapDelay: SwapDelay;
+}>('twap/replaceSwapState');
+export const setRecipient = createAction<{ recipient: string | null }>(
+ 'twap/setRecipient',
+);
diff --git a/src/state/swap/twap/hooks.test.ts b/src/state/swap/twap/hooks.test.ts
new file mode 100644
index 000000000..8c80043a6
--- /dev/null
+++ b/src/state/swap/twap/hooks.test.ts
@@ -0,0 +1,116 @@
+import { parse } from 'qs';
+import { queryParametersToSwapState } from '../hooks';
+import { Field } from './actions';
+
+describe('hooks', () => {
+ describe('#queryParametersToSwapState', () => {
+ test('ETH to DAI', () => {
+ expect(
+ queryParametersToSwapState(
+ parse(
+ '?inputCurrency=ETH&outputCurrency=0x6b175474e89094c44da98b954eedeac495271d0f&exactAmount=20.5&exactField=outPUT',
+ { parseArrays: false, ignoreQueryPrefix: true },
+ ),
+ ),
+ ).toEqual({
+ [Field.OUTPUT]: {
+ currencyId: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
+ },
+ [Field.INPUT]: { currencyId: 'ETH' },
+ typedValue: '20.5',
+ independentField: Field.OUTPUT,
+ recipient: null,
+ });
+ });
+
+ test('does not duplicate eth for invalid output token', () => {
+ expect(
+ queryParametersToSwapState(
+ parse('?outputCurrency=invalid', {
+ parseArrays: false,
+ ignoreQueryPrefix: true,
+ }),
+ ),
+ ).toEqual({
+ [Field.INPUT]: { currencyId: '' },
+ [Field.OUTPUT]: { currencyId: 'ETH' },
+ typedValue: '',
+ independentField: Field.INPUT,
+ recipient: null,
+ });
+ });
+
+ test('output ETH only', () => {
+ expect(
+ queryParametersToSwapState(
+ parse('?outputCurrency=eth&exactAmount=20.5', {
+ parseArrays: false,
+ ignoreQueryPrefix: true,
+ }),
+ ),
+ ).toEqual({
+ [Field.OUTPUT]: { currencyId: 'ETH' },
+ [Field.INPUT]: { currencyId: '' },
+ typedValue: '20.5',
+ independentField: Field.INPUT,
+ recipient: null,
+ });
+ });
+
+ test('invalid recipient', () => {
+ expect(
+ queryParametersToSwapState(
+ parse('?outputCurrency=eth&exactAmount=20.5&recipient=abc', {
+ parseArrays: false,
+ ignoreQueryPrefix: true,
+ }),
+ ),
+ ).toEqual({
+ [Field.OUTPUT]: { currencyId: 'ETH' },
+ [Field.INPUT]: { currencyId: '' },
+ typedValue: '20.5',
+ independentField: Field.INPUT,
+ recipient: null,
+ });
+ });
+
+ test('valid recipient', () => {
+ expect(
+ queryParametersToSwapState(
+ parse(
+ '?outputCurrency=eth&exactAmount=20.5&recipient=0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5',
+ {
+ parseArrays: false,
+ ignoreQueryPrefix: true,
+ },
+ ),
+ ),
+ ).toEqual({
+ [Field.OUTPUT]: { currencyId: 'ETH' },
+ [Field.INPUT]: { currencyId: '' },
+ typedValue: '20.5',
+ independentField: Field.INPUT,
+ recipient: '0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5',
+ });
+ });
+ test('accepts any recipient', () => {
+ expect(
+ queryParametersToSwapState(
+ parse(
+ '?outputCurrency=eth&exactAmount=20.5&recipient=bob.argent.xyz',
+ {
+ parseArrays: false,
+ ignoreQueryPrefix: true,
+ },
+ ),
+ ),
+ ).toEqual({
+ [Field.OUTPUT]: { currencyId: 'ETH' },
+ [Field.INPUT]: { currencyId: '' },
+ typedValue: '20.5',
+ independentField: Field.INPUT,
+ recipient: 'bob.argent.xyz',
+ });
+ });
+ });
+});
diff --git a/src/state/swap/twap/hooks.ts b/src/state/swap/twap/hooks.ts
new file mode 100644
index 000000000..b28ac6b7d
--- /dev/null
+++ b/src/state/swap/twap/hooks.ts
@@ -0,0 +1,210 @@
+import { ChainId, Currency, ETHER, Token } from '@uniswap/sdk';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useActiveWeb3React } from 'hooks';
+import useParsedQueryString from 'hooks/useParsedQueryString';
+import { AppDispatch, AppState } from 'state';
+import { TimeDuration } from '@orbs-network/twap-sdk';
+import {
+ Field,
+ invertTradePrice,
+ replaceSwapState,
+ resetTradePrice,
+ selectCurrency,
+ setChunks,
+ setDuration,
+ setFillDelay,
+ setIsMarketOrder,
+ setSwapDelay,
+ setTradePrice,
+ setUpdatingOrders,
+ SwapDelay,
+ switchCurrencies,
+ toggleMarketOrder,
+ typeInput,
+} from './actions';
+import { queryParametersToSwapState } from '../hooks';
+
+export function useTwapState(): AppState['twap'] {
+ return useSelector((state) => state.twap);
+}
+
+export function useTwapSwapActionHandlers(): {
+ onCurrencySelection: (field: Field, currency: Currency) => void;
+ onSwitchTokens: () => void;
+ onUserInput: (field: Field, typedValue: string) => void;
+ onChunksInput: (typedValue: number) => void;
+ onDurationInput: (typedValue?: TimeDuration) => void;
+ onFillDelayInput: (typedValue: TimeDuration) => void;
+ onSetSwapDelay: (swapDelay: SwapDelay) => void;
+ onTradePriceInput: (typedValue?: string) => void;
+ onMarketOrder: (isMarketOrder: boolean) => void;
+ onInvertLimitPrice: (isTradePriceInverted: boolean) => void;
+ onUpdatingOrders: (updatingOrders: boolean) => void;
+ onToggleMarketOrder: () => void;
+ onResetTradePrice: () => void;
+} {
+ const dispatch = useDispatch();
+ const { chainId } = useActiveWeb3React();
+ const chainIdToUse = chainId ? chainId : ChainId.MATIC;
+ const nativeCurrency = ETHER[chainIdToUse];
+ const timer = useRef(null);
+
+ const onCurrencySelection = useCallback(
+ (field: Field, currency: Currency) => {
+ dispatch(
+ selectCurrency({
+ field,
+ currencyId:
+ currency instanceof Token
+ ? currency.address
+ : currency === nativeCurrency
+ ? 'ETH'
+ : '',
+ }),
+ );
+ },
+ [dispatch, nativeCurrency],
+ );
+
+ const onSetSwapDelay = useCallback(
+ (swapDelay: SwapDelay) => {
+ dispatch(setSwapDelay({ swapDelay }));
+ },
+ [dispatch],
+ );
+
+ const onSwitchTokens = useCallback(() => {
+ dispatch(switchCurrencies());
+ }, [dispatch]);
+
+ const onUserInput = useCallback(
+ (field: Field, typedValue: string) => {
+ dispatch(typeInput({ field, typedValue }));
+ if (!typedValue) {
+ onSetSwapDelay(SwapDelay.INIT);
+ return;
+ }
+ onSetSwapDelay(SwapDelay.USER_INPUT);
+ clearTimeout(timer.current);
+ timer.current = setTimeout(() => {
+ onSetSwapDelay(SwapDelay.USER_INPUT_COMPLETE);
+ }, 300);
+ },
+ [dispatch, onSetSwapDelay],
+ );
+
+ const onChunksInput = useCallback(
+ (chunks: number) => {
+ dispatch(setChunks({ chunks }));
+ },
+ [dispatch],
+ );
+
+ const onFillDelayInput = useCallback(
+ (fillDelay: TimeDuration) => {
+ dispatch(setFillDelay({ fillDelay }));
+ },
+ [dispatch],
+ );
+
+ const onDurationInput = useCallback(
+ (duration?: TimeDuration) => {
+ dispatch(setDuration({ duration }));
+ },
+ [dispatch],
+ );
+
+ const onTradePriceInput = useCallback(
+ (tradePrice?: string) => {
+ dispatch(setTradePrice({ tradePrice }));
+ },
+ [dispatch],
+ );
+
+ const onMarketOrder = useCallback(
+ (isMarketOrder: boolean) => {
+ dispatch(setIsMarketOrder({ isMarketOrder }));
+ },
+ [dispatch],
+ );
+
+ const onToggleMarketOrder = useCallback(() => {
+ dispatch(toggleMarketOrder());
+ }, [dispatch]);
+
+ const onInvertLimitPrice = useCallback(
+ (isTradePriceInverted: boolean) => {
+ dispatch(invertTradePrice({ isTradePriceInverted }));
+ },
+ [dispatch],
+ );
+
+ const onUpdatingOrders = useCallback(
+ (updatingOrders: boolean) => {
+ dispatch(setUpdatingOrders({ updatingOrders }));
+ },
+ [dispatch],
+ );
+
+ const onResetTradePrice = useCallback(() => {
+ dispatch(resetTradePrice());
+ }, [dispatch]);
+
+ return {
+ onSwitchTokens,
+ onCurrencySelection,
+ onUserInput,
+ onSetSwapDelay,
+ onChunksInput,
+ onFillDelayInput,
+ onDurationInput,
+ onTradePriceInput,
+ onMarketOrder,
+ onInvertLimitPrice,
+ onUpdatingOrders,
+ onToggleMarketOrder,
+ onResetTradePrice,
+ };
+}
+
+// updates the swap state to use the defaults for a given network
+export function useDefaultsFromURLSearch():
+ | {
+ inputCurrencyId: string | undefined;
+ outputCurrencyId: string | undefined;
+ }
+ | undefined {
+ const { chainId } = useActiveWeb3React();
+ const dispatch = useDispatch();
+ const parsedQs = useParsedQueryString();
+ const [result, setResult] = useState<
+ | {
+ inputCurrencyId: string | undefined;
+ outputCurrencyId: string | undefined;
+ }
+ | undefined
+ >();
+
+ useEffect(() => {
+ if (!chainId) return;
+ const parsed = queryParametersToSwapState(parsedQs);
+
+ dispatch(
+ replaceSwapState({
+ typedValue: parsed.typedValue,
+ inputCurrencyId: parsed[Field.INPUT].currencyId,
+ outputCurrencyId: parsed[Field.OUTPUT].currencyId,
+ swapDelay: SwapDelay.INIT,
+ }),
+ );
+
+ setResult({
+ inputCurrencyId: parsed[Field.INPUT].currencyId,
+ outputCurrencyId: parsed[Field.OUTPUT].currencyId,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dispatch, chainId]);
+
+ return result;
+}
diff --git a/src/state/swap/twap/reducer.ts b/src/state/swap/twap/reducer.ts
new file mode 100755
index 000000000..80f25fc63
--- /dev/null
+++ b/src/state/swap/twap/reducer.ts
@@ -0,0 +1,140 @@
+import { createReducer } from '@reduxjs/toolkit';
+import { TimeDuration } from '@orbs-network/twap-sdk';
+import {
+ Field,
+ replaceSwapState,
+ selectCurrency,
+ SwapDelay,
+ switchCurrencies,
+ typeInput,
+ setSwapDelay,
+ setChunks,
+ setFillDelay,
+ setDuration,
+ invertTradePrice,
+ setUpdatingOrders,
+ setIsMarketOrder,
+ setTradePrice,
+ resetTradePrice,
+} from './actions';
+
+export interface TwapSwapState {
+ readonly typedValue: string;
+ readonly chunks?: number;
+ readonly fillDelay?: TimeDuration;
+ readonly duration?: TimeDuration;
+ readonly tradePrice?: string;
+ readonly isMarketOrder?: boolean;
+ readonly isTradePriceInverted?: boolean;
+ readonly updatingOrders?: boolean;
+
+ readonly [Field.INPUT]: {
+ readonly currencyId: string | undefined;
+ };
+ readonly [Field.OUTPUT]: {
+ readonly currencyId: string | undefined;
+ };
+ readonly swapDelay: SwapDelay;
+}
+
+const initialState: TwapSwapState = {
+ typedValue: '',
+ isMarketOrder: false,
+ [Field.INPUT]: {
+ currencyId: '',
+ },
+ [Field.OUTPUT]: {
+ currencyId: '',
+ },
+ swapDelay: SwapDelay.INIT,
+};
+
+export default createReducer(initialState, (builder) =>
+ builder
+ .addCase(
+ replaceSwapState,
+ (
+ state,
+ {
+ payload: { typedValue, inputCurrencyId, outputCurrencyId, swapDelay },
+ },
+ ) => {
+ return {
+ [Field.INPUT]: {
+ currencyId: inputCurrencyId,
+ },
+ [Field.OUTPUT]: {
+ currencyId: outputCurrencyId,
+ },
+ typedValue: typedValue,
+ swapDelay,
+ };
+ },
+ )
+ .addCase(selectCurrency, (state, { payload: { currencyId, field } }) => {
+ const otherField = field === Field.INPUT ? Field.OUTPUT : Field.INPUT;
+ if (currencyId === state[otherField].currencyId) {
+ // the case where we have to swap the order
+ return {
+ ...state,
+ [field]: { currencyId: currencyId },
+ [otherField]: { currencyId: state[field].currencyId },
+ };
+ } else {
+ // the normal case
+ return {
+ ...state,
+ [field]: { currencyId: currencyId },
+ };
+ }
+ })
+ .addCase(switchCurrencies, (state) => {
+ return {
+ ...state,
+ [Field.INPUT]: { currencyId: state[Field.OUTPUT].currencyId },
+ [Field.OUTPUT]: { currencyId: state[Field.INPUT].currencyId },
+ };
+ })
+ .addCase(typeInput, (state, { payload: { field, typedValue } }) => {
+ return {
+ ...state,
+ independentField: field,
+ typedValue,
+ };
+ })
+
+ .addCase(setSwapDelay, (state, { payload: { swapDelay } }) => {
+ state.swapDelay = swapDelay;
+ })
+
+ .addCase(setChunks, (state, { payload: { chunks } }) => {
+ state.chunks = chunks;
+ })
+ .addCase(setFillDelay, (state, { payload: { fillDelay } }) => {
+ state.fillDelay = fillDelay;
+ })
+ .addCase(setDuration, (state, { payload: { duration } }) => {
+ state.duration = duration;
+ })
+ .addCase(setUpdatingOrders, (state, { payload: { updatingOrders } }) => {
+ state.updatingOrders = updatingOrders;
+ })
+ .addCase(setIsMarketOrder, (state, { payload: { isMarketOrder } }) => {
+ state.isMarketOrder = isMarketOrder;
+ })
+ .addCase(setTradePrice, (state, { payload: { tradePrice } }) => {
+ state.tradePrice = tradePrice;
+ })
+ .addCase(resetTradePrice, (state, { payload }) => {
+ state.tradePrice = undefined;
+ state.isTradePriceInverted = false;
+ })
+
+ .addCase(
+ invertTradePrice,
+ (state, { payload: { isTradePriceInverted } }) => {
+ state.isTradePriceInverted = isTradePriceInverted;
+ state.tradePrice = undefined;
+ },
+ ),
+);
diff --git a/src/utils/useUSDCPrice.ts b/src/utils/useUSDCPrice.ts
index bdd0709df..a022fa4d5 100755
--- a/src/utils/useUSDCPrice.ts
+++ b/src/utils/useUSDCPrice.ts
@@ -6,6 +6,7 @@ import {
WETH,
Token,
Trade,
+ ETHER,
} from '@uniswap/sdk';
import { useMemo } from 'react';
import { PairState, usePairs, usePair } from 'data/Reserves';
@@ -34,6 +35,11 @@ dayjs.extend(weekOfYear);
export default function useUSDCPrice(currency?: Currency): Price | undefined {
const { chainId } = useActiveWeb3React();
+ const token = useMemo(() => wrappedCurrency(currency, chainId), [
+ currency,
+ chainId,
+ ]);
+ const { data: priceUsd } = useFetchEthereumPrice(token?.address);
const amountOut = chainId
? tryParseAmount(chainId, '1', USDC[chainId])
@@ -42,6 +48,20 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
const allowedPairs = useAllCommonPairs(currency, USDC[chainId]);
return useMemo(() => {
+ if (chainId && chainId === ChainId.ETHEREUM) {
+ const amount = tryParseAmount(
+ chainId,
+ priceUsd?.price.toString(),
+ currency,
+ );
+
+ const usdcAmount = tryParseAmount(chainId, '1', USDC[chainId]);
+
+ if (!currency || !amount || !usdcAmount) return;
+
+ return new Price(USDC[chainId], currency, usdcAmount.raw, amount.raw);
+ }
+
if (!currency || !amountOut || !allowedPairs.length) {
return undefined;
}
@@ -57,9 +77,45 @@ export default function useUSDCPrice(currency?: Currency): Price | undefined {
const { numerator, denominator } = trade.route.midPrice;
return new Price(currency, USDC[chainId], denominator, numerator);
- }, [currency, allowedPairs, amountOut, chainId]);
+ }, [currency, allowedPairs, amountOut, chainId, priceUsd]);
}
+const fetchEthereumPrice = async (address: string) => {
+ const tokenAddressWithChainId = `ethereum:${address}`;
+ const url = `https://coins.llama.fi/prices/current/${tokenAddressWithChainId}`;
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ return {
+ address,
+ price: 0,
+ };
+ }
+ const data = await response.json();
+ const coin = data.coins[tokenAddressWithChainId];
+ return {
+ address,
+ price: coin.price || 0,
+ };
+ } catch (error) {
+ console.error(`Failed to fetch price for ${address}:`, error);
+ return {
+ address,
+ price: 0,
+ };
+ }
+};
+
+const useFetchEthereumPrice = (address?: string) => {
+ const { chainId } = useActiveWeb3React();
+
+ return useQuery({
+ queryKey: ['useFetchEthereumPrice', address, chainId],
+ queryFn: () => fetchEthereumPrice(address!),
+ enabled: !!address && chainId === ChainId.ETHEREUM,
+ });
+};
+
const getUSDPricesFromAddresses = async (
chainId: ChainId,
addressStr?: string,
@@ -72,6 +128,12 @@ const getUSDPricesFromAddresses = async (
const v2 = config['v2'] && !onlyV3;
const addresses = addressStr.split('_');
+ if (chainId === ChainId.ETHEREUM) {
+ return await Promise.all(
+ addresses.map(async (address) => fetchEthereumPrice(address)),
+ );
+ }
+
for (const ind of Array.from(
{ length: Math.ceil(addresses.length / 150) },
(_, i) => i,
diff --git a/tsconfig.json b/tsconfig.json
index dd0d7f4ec..2076a3bf4 100755
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,7 +6,6 @@
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
@@ -18,7 +17,7 @@
"jsx": "react-jsx",
"useUnknownInCatchVariables": false,
"downlevelIteration": true,
- "noImplicitAny": false,
+ "noImplicitAny": false
},
- "include": ["src"]
+ "include": ["./src"]
}