diff --git a/src/components/Aggregator/Settings.tsx b/src/components/Aggregator/Settings.tsx index d3d53de5..430efae2 100644 --- a/src/components/Aggregator/Settings.tsx +++ b/src/components/Aggregator/Settings.tsx @@ -1,3 +1,4 @@ +import { InfoOutlineIcon } from '@chakra-ui/icons'; import { Button, Checkbox, @@ -12,11 +13,16 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Switch, + Tooltip, useDisclosure } from '@chakra-ui/react'; import { chunk } from 'lodash'; +import { useLocalStorage } from '~/hooks/useLocalStorage'; export const Settings = ({ adapters, disabledAdapters, setDisabledAdapters, onClose: onExternalClose }) => { + const [isDegenModeEnabled, setIsDegenModeEnabled] = useLocalStorage('llamaswap-degenmode', false); + const [cowswapDeadline, setCowswapDeadline] = useLocalStorage('llamaswap-cowswapDeadline', 30); const { isOpen, onClose } = useDisclosure({ defaultIsOpen: true }); const onCloseClick = () => { onExternalClose(); @@ -38,6 +44,40 @@ export const Settings = ({ adapters, disabledAdapters, setDisabledAdapters, onCl Settings + + Degen Mode{' '} + + + + setIsDegenModeEnabled((mode) => !mode)} isChecked={isDegenModeEnabled} /> + + + CowSwap Deadline{' '} + + + + { + const num = Number(d.target.value); + if (num <= 180 && num >= 2 && Number.isInteger(num)) { + setCowswapDeadline(num); + } else { + setCowswapDeadline(30); + } + }} + min={2} + step={1} + type="number" + max="180" + value={cowswapDeadline} + style={{ + width: '3em', + borderRadius: '0.4em', + textAlign: 'end' + }} + />{' '} + minutes + Enabled Aggregators diff --git a/src/components/Aggregator/SwapConfirmation.tsx b/src/components/Aggregator/SwapConfirmation.tsx index 33ec9bb6..2ff28176 100644 --- a/src/components/Aggregator/SwapConfirmation.tsx +++ b/src/components/Aggregator/SwapConfirmation.tsx @@ -12,7 +12,12 @@ import { } from '@chakra-ui/react'; import React, { useState } from 'react'; -const SwapConfiramtion = ({ handleSwap, isUnknownPrice = false, isMaxPriceImpact = false }) => { +const SwapConfiramtion = ({ + handleSwap, + isUnknownPrice = false, + isMaxPriceImpact = false, + isDegenModeEnabled = false +}) => { const { isOpen, onToggle, onClose } = useDisclosure(); const requiredText = isMaxPriceImpact ? 'trade' : 'confirm'; const [value, setValue] = useState(''); @@ -50,14 +55,23 @@ const SwapConfiramtion = ({ handleSwap, isUnknownPrice = false, isMaxPriceImpact
You'll likely lose money.
- Type "{requiredText}" to make a swap. - setValue(e.target.value)} - value={value} - > - diff --git a/src/components/Aggregator/adapters/cowswap/index.ts b/src/components/Aggregator/adapters/cowswap/index.ts index 54b341db..ebb29f65 100644 --- a/src/components/Aggregator/adapters/cowswap/index.ts +++ b/src/components/Aggregator/adapters/cowswap/index.ts @@ -55,13 +55,14 @@ export async function getQuote(chain: string, from: string, to: string, amount: const tokenTo = to === ethers.constants.AddressZero ? nativeToken : to; const tokenFrom = isEthflowOrder ? wrappedTokens[chain] : from; const isBuyOrder = extra.amountOut && extra.amountOut !== '0'; - + const validTo = Math.floor(Date.now() / 1e3) + (extra.deadline ?? 30) * 60; + // Ethflow orders are always sell orders. // Source: https://github.com/cowprotocol/ethflowcontract/blob/v1.0.0/src/libraries/EthFlowOrder.sol#L93-L95 if (isEthflowOrder && isBuyOrder) { throw new Error('buy orders from Ether are not allowed'); } - + // amount should include decimals const data = await fetch(`${chainToId[chain]}/api/v1/quote`, { method: 'POST', @@ -74,6 +75,7 @@ export async function getQuote(chain: string, from: string, to: string, amount: sellTokenBalance: 'erc20', buyTokenBalance: 'erc20', from: extra.userAddress, + validTo, //"priceQuality": "fast", signingScheme: isEthflowOrder ? 'eip1271' : 'eip712', // for selling directly ether, another signature type is required onchainOrder: isEthflowOrder ? true : false, // for selling directly ether, we have to quote for onchain orders diff --git a/src/components/Aggregator/index.tsx b/src/components/Aggregator/index.tsx index 38c29963..e8cbedee 100644 --- a/src/components/Aggregator/index.tsx +++ b/src/components/Aggregator/index.tsx @@ -319,7 +319,9 @@ export function AggregatorContainer({ tokenList, sandwichList }) { const [slippage, setSlippage] = useLocalStorage('llamaswap-slippage', '0.5'); const [lastOutputValue, setLastOutputValue] = useState(null); const [disabledAdapters, setDisabledAdapters] = useLocalStorage('llamaswap-disabledadapters', []); + const [isDegenModeEnabled, _] = useLocalStorage('llamaswap-degenmode', false); const [isSettingsModalOpen, setSettingsModalOpen] = useState(false); + const [cowswapDeadline, _] = useLocalStorage('llamaswap-cowswapDeadline', 30); // mobile states const [uiState, setUiState] = useState(STATES.INPUT); @@ -470,7 +472,8 @@ export function AggregatorContainer({ tokenList, sandwichList }) { toToken: finalSelectedToToken, slippage, isPrivacyEnabled, - amountOut: amountOutWithDecimals + amountOut: amountOutWithDecimals, + deadline: cowswapDeadline } }); @@ -845,7 +848,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { const handleSwap = () => { if (selectedRoute && selectedRoute.price && !slippageIsWorng) { - if (hasMaxPriceImpact) { + if (hasMaxPriceImpact && !isDegenModeEnabled) { toast({ title: 'Price impact is too high!', description: 'Swap is blocked, please try another route.', @@ -1088,7 +1091,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { - ) : hasMaxPriceImpact ? ( + ) : hasMaxPriceImpact && !isDegenModeEnabled ? ( @@ -1128,6 +1131,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) { isUnknownPrice={isUnknownPrice} isMaxPriceImpact={hasMaxPriceImpact} handleSwap={handleSwap} + isDegenModeEnabled={isDegenModeEnabled} /> ) : (