basic send added

This commit is contained in:
ianlapham 2020-03-17 16:23:27 -04:00
parent 4e2c5c1e84
commit 655b79569b
14 changed files with 899 additions and 326 deletions

3
src/assets/svg/QR.svg Normal file

@ -0,0 +1,3 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 1.5H1.5V7.5H7.5V1.5ZM1.5 0H0V1.5V7.5V9H1.5H7.5H9V7.5V1.5V0H7.5H1.5ZM4.5 3H3V4.5V6H4.5H6V4.5V3H4.5ZM1.5 19.5V13.5H7.5V19.5H1.5ZM0 12H1.5H7.5H9V13.5V19.5V21H7.5H1.5H0V19.5V13.5V12ZM4.5 15H3V16.5V18H4.5H6V16.5V15H4.5ZM13.5 1.5H19.5V7.5H13.5V1.5ZM12 0H13.5H19.5H21V1.5V7.5V9H19.5H13.5H12V7.5V1.5V0ZM16.5 3H15V4.5V6H16.5H18V4.5V3H16.5ZM16.5 12H12V21H13.5V16.5H15V18H21V12H19.5V13.5H16.5V12ZM18 19.5H16.5V21H18V19.5ZM19.5 19.5H21V21H19.5V19.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

@ -89,6 +89,27 @@ export const ButtonEmpty = styled(Base)`
}
`
export const ButtonWhite = styled(Base)`
border: 1px solid #edeef2;
background-color: ${({ theme }) => theme.panelBackground};
};
color: black;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, '#edeef2')};
}
&:hover {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, '#edeef2')};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, '#edeef2')};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => lighten(0.5, theme.connectedGreen)};
border: 1px solid ${({ theme }) => theme.connectedGreen};
@ -160,7 +181,7 @@ export function ButtonDropwdownLight({ disabled, children, ...rest }) {
export function ButtonRadio({ active, children, ...rest }) {
if (!active) {
return <ButtonEmpty {...rest}>{children}</ButtonEmpty>
return <ButtonWhite {...rest}>{children}</ButtonWhite>
} else {
return <ButtonPrimary {...rest}>{children}</ButtonPrimary>
}

@ -48,10 +48,9 @@ const InputRow = styled.div`
const CurrencySelect = styled.button`
align-items: center;
height: 2.2rem;
font-size: 20px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.zumthorBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.royalBlue)};
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
border: 1px solid
${({ selected, theme, disableTokenSelect }) =>
disableTokenSelect ? theme.buttonBackgroundPlain : selected ? theme.buttonOutlinePlain : theme.royalBlue};
@ -70,7 +69,8 @@ const CurrencySelect = styled.button`
}
:active {
background-color: ${({ theme }) => theme.zumthorBlue};
background-color: ${({ selected, theme }) =>
selected ? darken(0.1, theme.zumthorBlue) : darken(0.1, theme.royalBlue)};
}
`
@ -85,20 +85,20 @@ const StyledDropDown = styled(DropDown)`
height: 35%;
path {
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.royalBlue)};
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
}
`
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: 1.25rem;
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
background-color: ${({ theme }) => theme.inputBackground};
z-index: 1;
`
const Container = styled.div`
border-radius: 1.25rem;
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.inputBackground};
@ -174,7 +174,10 @@ export default function CurrencyInputPanel({
hideBalance = false,
isExchange = false,
exchange = null, // used for double token logo
customBalance = null // used for LP balances instead of token balance
customBalance = null, // used for LP balances instead of token balance
hideInput = false,
showSendWithSwap = false,
onTokenSelectSendWithSwap = null
}) {
const { account, chainId } = useWeb3React()
const { t } = useTranslation()
@ -236,7 +239,7 @@ export default function CurrencyInputPanel({
return (
<InputPanel>
<Container error={!!error}>
<Container error={!!error} hideInput={hideInput}>
{!hideBalance && (
<LabelRow>
<RowBetween>
@ -250,15 +253,19 @@ export default function CurrencyInputPanel({
</RowBetween>
</LabelRow>
)}
<InputRow>
<NumericalInput
value={value}
onUserInput={val => {
onUserInput(field, val)
}}
/>
{!!token?.address && !atMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>}
{renderUnlockButton()}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} hideInput={hideInput}>
{!hideInput && (
<>
<NumericalInput
value={value}
onUserInput={val => {
onUserInput(field, val)
}}
/>
{!!token?.address && !atMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>}
{renderUnlockButton()}
</>
)}
<CurrencySelect
selected={!!token?.address}
onClick={() => {
@ -296,6 +303,8 @@ export default function CurrencyInputPanel({
urlAddedTokens={urlAddedTokens}
field={field}
onTokenSelect={onTokenSelection}
showSendWithSwap={showSendWithSwap}
onTokenSelectSendWithSwap={onTokenSelectSendWithSwap}
/>
)}
</InputPanel>

@ -4,6 +4,7 @@ import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
import QR from '../../assets/svg/QR.svg'
import TokenLogo from '../TokenLogo'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
@ -11,23 +12,23 @@ import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Link } from '../../theme/components'
import { Text } from 'rebass'
import ThemeProvider, { TYPE } from '../../theme'
import { GreyCard } from '../../components/Card'
import { TYPE } from '../../theme'
import { GreyCard, LightCard } from '../../components/Card'
import { ArrowDown, ArrowUp } from 'react-feather'
import { ButtonPrimary, ButtonError, ButtonRadio } from '../Button'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonError, ButtonRadio } from '../Button'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { usePopups } from '../../contexts/Application'
import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '../../hooks'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils'
import { getRouterContract, calculateGasMargin, isAddress, getProviderOrSigner } from '../../utils'
const Wrapper = styled.div`
position: relative;
@ -64,7 +65,66 @@ const InputWrapper = styled(RowBetween)`
border-radius: 8px;
padding: 4px 8px;
border: 1px solid transparent;
border: ${({ active, theme }) => active && '1px solid ' + theme.royalBlue};
border: ${({ active, error, theme }) =>
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
`
const InputGroup = styled(AutoColumn)`
position: relative;
padding: 40px 0;
`
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.outlineGrey};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
const StyledInput = styled.input`
width: ${({ width }) => width};
border: none;
outline: none;
font-size: 20px;
::placeholder {
color: #edeef2;
}
`
const StyledNumerical = styled(NumericalInput)`
text-align: center;
font-size: 48px;
font-weight: 500px;
width: 100%;
::placeholder {
color: #edeef2;
}
`
const MaxButton = styled.button`
position: absolute;
right: 70px;
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.zumthorBlue};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
outline: none;
}
`
enum Field {
@ -91,7 +151,7 @@ function initializeSwapState(inputAddress?: string, outputAddress?: string): Swa
address: inputAddress
},
[Field.OUTPUT]: {
address: '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'
address: outputAddress
}
}
}
@ -192,22 +252,28 @@ const INITIAL_ALLOWED_SLIPPAGE = 200
const DEFAULT_DEADLINE_FROM_NOW = 60 * 15
// used for warning states based on slippage in bips
const ALLOWED_IMPACT_MEDIUM = 100
const ALLOWED_IMPACT_HIGH = 500
const ALLOWED_SLIPPAGE_MEDIUM = 100
const ALLOWED_SLIPPAGE_HIGH = 500
export default function ExchangePage() {
export default function ExchangePage({ sendingInput = false }) {
const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
// adding notifications on txns
const [, addPopup] = usePopups()
const addTransaction = useTransactionAdder()
// sending state
const [sending, setSending] = useState(sendingInput)
const [sendingWithSwap, setSendingWithSwap] = useState(false)
const [recipient, setRecipient] = useState('')
// input details
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
const [tradeError, setTradeError] = useState('') // error for thinsg liek reserve sizes
const [tradeError, setTradeError] = useState('') // error for things like reserve size or route
const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
@ -217,12 +283,18 @@ export default function ExchangePage() {
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
// modal state
const addTransaction = useTransactionAdder()
const [showConfirm, setShowConfirm] = useState(true)
// modal and loading
const [showConfirm, setShowConfirm] = useState(false)
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for user confirmation
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirmed
// advanced settings
const [showAdvanced, setShowAdvanced] = useState(false)
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
const [customSlippage, setCustomSlippage] = useState()
const [customDeadline, setCustomDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW / 60)
const [slippageInputError, setSlippageInputError] = useState(null)
// txn values
const [txHash, setTxHash] = useState()
const [deadline, setDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW)
@ -240,10 +312,9 @@ export default function ExchangePage() {
const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value
// if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
if (tokens[independentField]) {
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try {
const typedValueParsed = parseUnits('0.0001', tokens[independentField].decimals).toString()
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString()
if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) {
@ -313,7 +384,7 @@ export default function ExchangePage() {
})
}, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const MIN_ETHER = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput =
!!userBalances[Field.INPUT] &&
JSBI.greaterThan(
@ -388,16 +459,14 @@ export default function ExchangePage() {
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
// modal state
const [showAdvanced, setShowAdvanced] = useState(true)
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
const [customSlippage, setCustomSlippage] = useState()
const [customDeadline, setCustomDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW / 60)
const [slippageInputError, setSlippageInputError] = useState(null)
// parse the input for custom slippage
function parseCustomInput(val) {
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
if (val > 5) {
setSlippageInputError('Your transaction may be front-run.')
} else {
setSlippageInputError(null)
}
if (acceptableValues.some(a => a.test(val))) {
setCustomSlippage(val)
setAllowedSlippage(val * 100)
@ -412,6 +481,62 @@ export default function ExchangePage() {
}
}
const tokenContract = useTokenContract(tokens[Field.INPUT]?.address)
// function for a pure send
async function onSend() {
setAttemptingTxn(true)
const signer = await getProviderOrSigner(library, account)
// get token contract if needed
let estimate: Function, method: Function, args, value
if (tokens[Field.INPUT] === WETH[chainId]) {
signer
.sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) })
.then(response => {
console.log(response)
setTxHash(response.hash)
addTransaction(response)
setPendingConfirmation(false)
})
.catch(e => {
addPopup(
<AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text>
</AutoColumn>
)
resetModal()
setShowConfirm(false)
})
} else {
estimate = tokenContract.estimate.transfer
method = tokenContract.transfer
args = [recipient, parsedAmounts[Field.INPUT].raw.toString()]
value = ethers.constants.Zero
const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
console.log('error getting gas limit')
})
method(...args, {
value,
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
})
.then(response => {
setTxHash(response.hash)
addTransaction(response)
setPendingConfirmation(false)
})
.catch(e => {
addPopup(
<AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text>
</AutoColumn>
)
resetModal()
setShowConfirm(false)
})
}
}
async function onSwap() {
const routerContract = getRouterContract(chainId, library, account)
setAttemptingTxn(true)
@ -497,7 +622,7 @@ export default function ExchangePage() {
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
})
.then(response => {
setTxHash(response)
setTxHash(response.hash)
addTransaction(response)
setPendingConfirmation(false)
})
@ -513,17 +638,38 @@ export default function ExchangePage() {
}
// errors
const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState('')
const [recipientError, setRecipientError] = useState('')
const [isValid, setIsValid] = useState(false)
const ignoreOutput = sending ? !sendingWithSwap : false
useEffect(() => {
// reset errors
setGeneralError(null)
setInputError(null)
setOutputError(null)
setTradeError(null)
setRecipientError(null)
setIsValid(true)
if (!isAddress(recipient) && sending) {
setRecipientError('Invalid Recipient')
setIsValid(false)
}
if (!parsedAmounts[Field.INPUT]) {
setGeneralError('Enter an amount')
setIsValid(false)
}
if (!parsedAmounts[Field.OUTPUT] && !ignoreOutput) {
setGeneralError('Enter an amount')
setIsValid(false)
}
if (
parsedAmounts[Field.INPUT] &&
exchange &&
@ -534,6 +680,7 @@ export default function ExchangePage() {
}
if (
!ignoreOutput &&
parsedAmounts[Field.OUTPUT] &&
exchange &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw)
@ -542,12 +689,12 @@ export default function ExchangePage() {
setIsValid(false)
}
if (showInputUnlock) {
if (showInputUnlock && !(sending && !sendingWithSwap)) {
setInputError('Approval Needed')
setIsValid(false)
}
if (showOutputUnlock) {
if (showOutputUnlock && !ignoreOutput) {
setOutputError('Approval Needed')
setIsValid(false)
}
@ -560,19 +707,22 @@ export default function ExchangePage() {
setInputError('Insufficient balance.')
setIsValid(false)
}
}, [
exchange,
ignoreOutput,
parsedAmounts,
recipient,
sending,
sendingWithSwap,
showInputUnlock,
showOutputUnlock,
tokens,
userBalances
])
if (
userBalances[Field.OUTPUT] &&
parsedAmounts[Field.OUTPUT] &&
JSBI.lessThan(userBalances[Field.OUTPUT].raw, parsedAmounts[Field.OUTPUT]?.raw)
) {
setOutputError('Insufficient balance.')
setIsValid(false)
}
}, [exchange, parsedAmounts, showInputUnlock, showOutputUnlock, tokens, userBalances])
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_IMPACT_MEDIUM / 100
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_IMPACT_HIGH / 100
// warnings on slippage
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100
function resetModal() {
setPendingConfirmation(true)
@ -581,169 +731,221 @@ export default function ExchangePage() {
}
function modalHeader() {
return (
<AutoColumn gap={'20px'} style={{ marginTop: '40px' }}>
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500}>
{!!slippageAdjustedAmounts[Field.INPUT] && slippageAdjustedAmounts[Field.INPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.INPUT]?.symbol || ''}
if (sending && !sendingWithSwap) {
return (
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<RowBetween>
<Text fontSize={36} fontWeight={500}>
{parsedAmounts[Field.INPUT]?.toFixed(8)}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color="#888D9B" />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500} color={warningHigh ? '#FF6871' : '#2172E5'}>
{!!slippageAdjustedAmounts[Field.OUTPUT] && slippageAdjustedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.OUTPUT]?.symbol || ''}
<TokenLogo address={tokens[Field.INPUT]?.address} size={'30px'} />
</RowBetween>
<ArrowDown size={24} color="#888D9B" />
<TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue>
</AutoColumn>
)
}
if (sending && sendingWithSwap) {
}
if (!sending) {
return (
<AutoColumn gap={'20px'} style={{ marginTop: '40px' }}>
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500}>
{!!slippageAdjustedAmounts[Field.INPUT] && slippageAdjustedAmounts[Field.INPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.INPUT]?.symbol || ''}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color="#888D9B" />
</RowFixed>
</RowBetween>
</AutoColumn>
)
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500} color={warningHigh ? '#FF6871' : '#2172E5'}>
{!!slippageAdjustedAmounts[Field.OUTPUT] && slippageAdjustedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.OUTPUT]?.symbol || ''}
</Text>
</RowFixed>
</RowBetween>
</AutoColumn>
)
}
}
function modalBottom() {
return showAdvanced ? (
<AutoColumn gap="20px">
<Link
onClick={() => {
setShowAdvanced(false)
}}
>
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price slippage</TYPE.main>
<QuestionHelper text="" />
</RowBetween>
<Row>
<ButtonRadio
active={SLIPPAGE_INDEX[1] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[1])
setAllowedSlippage(10)
}}
>
0.1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[2] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[2])
setAllowedSlippage(100)
}}
>
1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[3] === activeIndex}
padding="4px"
borderRadius="8px"
width={'140px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[3])
setAllowedSlippage(200)
}}
>
2% (suggested)
</ButtonRadio>
</Row>
<RowFixed>
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex}>
<NumericalInput
align={customSlippage ? 'right' : 'left'}
value={customSlippage || ''}
onUserInput={val => {
parseCustomInput(val)
setActiveIndex(SLIPPAGE_INDEX[4])
}}
placeHolder="Custom"
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[4])
if (customSlippage) {
parseCustomInput(customSlippage)
}
}}
/>
%
</InputWrapper>
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<NumericalInput
value={customDeadline}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</RowFixed>
</AutoColumn>
) : (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route && route.midPrice && route.midPrice.adjusted.toFixed(8)} ${
tokens[Field.OUTPUT]?.symbol
}`}
</Text>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
</Text>
<ErrorText warningHigh={warningHigh} fontWeight={500}>
{slippageFromTrade && slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
<Text fontSize={20} fontWeight={500}>
{warningHigh ? 'Swap Anyway' : 'Swap'}
</Text>
</ButtonError>
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} ${
tokens[Field.OUTPUT]?.symbol
} or the transaction will revert.`}
</Text>
<AutoColumn justify="center">
if (sending && !sendingWithSwap) {
return (
<AutoColumn>
<ButtonPrimary onClick={onSend}>
<Text color="white" fontSize={20}>
Confirm send
</Text>
</ButtonPrimary>
</AutoColumn>
)
}
if (sending && sendingWithSwap) {
}
if (showAdvanced) {
return (
<AutoColumn gap="20px">
<Link
onClick={() => {
setShowAdvanced(true)
setShowAdvanced(false)
}}
>
Advanced Options
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price impact</TYPE.main>
<QuestionHelper text="" />
</RowBetween>
<Row>
<ButtonRadio
active={SLIPPAGE_INDEX[1] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[1])
setAllowedSlippage(10)
}}
>
0.1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[2] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[2])
setAllowedSlippage(100)
}}
>
1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[3] === activeIndex}
padding="4px"
borderRadius="8px"
width={'140px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[3])
setAllowedSlippage(200)
}}
>
2% (suggested)
</ButtonRadio>
</Row>
<RowFixed>
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
<NumericalInput
align={customSlippage ? 'right' : 'left'}
value={customSlippage || ''}
onUserInput={val => {
parseCustomInput(val)
setActiveIndex(SLIPPAGE_INDEX[4])
}}
placeHolder="Custom"
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[4])
if (customSlippage) {
parseCustomInput(customSlippage)
}
}}
/>
%
</InputWrapper>
{slippageInputError && (
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
Your transaction may be front-run
</TYPE.error>
)}
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<InputWrapper>
<NumericalInput
value={customDeadline}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</InputWrapper>
</RowFixed>
</AutoColumn>
</>
)
)
}
if (!sending) {
return (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route && route.midPrice && route.midPrice.adjusted.toFixed(8)} ${
tokens[Field.OUTPUT]?.symbol
}`}
</Text>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
</Text>
<ErrorText warningHigh={warningHigh} fontWeight={500}>
{slippageFromTrade && slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
<Text fontSize={20} fontWeight={500}>
{warningHigh ? 'Swap Anyway' : 'Swap'}
</Text>
</ButtonError>
<AutoColumn justify="center" gap="20px">
<TYPE.italic textAlign="center" style={{ width: '80%' }}>
{`Output is estimated. You will receive at least ${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(
6
)} ${tokens[Field.OUTPUT]?.symbol} or the transaction will revert.`}
</TYPE.italic>
<Link
onClick={() => {
setShowAdvanced(true)
}}
>
Advanced Options
</Link>
</AutoColumn>
</>
)
}
}
const pendingText = ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
tokens[Field.INPUT]?.symbol
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
const pendingText = sending
? `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}`
: ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} for ${parsedAmounts[
Field.OUTPUT
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
return (
<Wrapper>
@ -756,69 +958,128 @@ export default function ExchangePage() {
attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''}
topContent={() => modalHeader()}
topContent={modalHeader}
bottomContent={modalBottom}
pendingText={pendingText}
title="Confirm Swap"
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
/>
{sending && !sendingWithSwap && (
<>
<InputGroup gap="24px" justify="center">
{!atMaxAmountInput && (
<MaxButton
onClick={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
>
Max
</MaxButton>
)}
<StyledNumerical value={formattedAmounts[Field.INPUT]} onUserInput={val => onUserInput(Field.INPUT, val)} />
{!parsedAmounts[Field.INPUT] && <TYPE.gray>Enter an amount.</TYPE.gray>}
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={val => onUserInput(Field.INPUT, val)}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
onTokenSelectSendWithSwap={address => {
onTokenSelection(Field.OUTPUT, address)
setSendingWithSwap(true)
}}
title={'Input'}
error={inputError}
exchange={exchange}
showUnlock={showInputUnlock}
hideBalance={true}
hideInput={true}
showSendWithSwap={true}
/>
</InputGroup>
</>
)}
<AutoColumn gap={'20px'}>
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
title={'Input'}
error={inputError}
exchange={exchange}
showUnlock={showInputUnlock}
/>
<ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}>
<ArrowDown size="16" color="#2F80ED" />
<ArrowUp size="16" color="#2F80ED" />
</ArrowWrapper>
</ColumnCenter>
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountOutput && onMaxOutput(maxAmountOutput.toExact())
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
title={'Output'}
error={outputError}
exchange={exchange}
showUnlock={showOutputUnlock}
/>
<RowBetween>
<Text fontWeight={500} color="#565A69">
Price
</Text>
<Text fontWeight={500} color="#565A69">
{exchange
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${tokens[Field.OUTPUT].symbol}`
: '-'}
</Text>
</RowBetween>
{warningMedium && (
<RowBetween>
<Text fontWeight={500} color="#565A69">
Slippage
</Text>
<ErrorText fontWeight={500} warningMedium={warningMedium} warningHigh={warningHigh}>
{slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
{(!sending || sendingWithSwap) && (
<>
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
title={'Input'}
error={inputError}
exchange={exchange}
showUnlock={showInputUnlock}
/>
<ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}>
<ArrowDown size="16" color="#2F80ED" />
<ArrowUp size="16" color="#2F80ED" />
</ArrowWrapper>
</ColumnCenter>
<CurrencyInputPanel
field={Field.OUTPUT}
value={formattedAmounts[Field.OUTPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountOutput && onMaxOutput(maxAmountOutput.toExact())
}}
atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]}
onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
title={'Output'}
error={outputError}
exchange={exchange}
showUnlock={showOutputUnlock}
/>
<RowBetween>
<Text fontWeight={500} color="#565A69">
Price
</Text>
<Text fontWeight={500} color="#565A69">
{exchange
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
tokens[Field.OUTPUT].symbol
}`
: '-'}
</Text>
</RowBetween>
{warningMedium && (
<RowBetween>
<Text fontWeight={500} color="#565A69">
Slippage
</Text>
<ErrorText fontWeight={500} warningMedium={warningMedium} warningHigh={warningHigh}>
{slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
)}
</>
)}
{sending && (
<AutoColumn gap="10px">
<LightCard borderRadius={'20px'}>
<RowBetween>
<StyledInput placeholder="Recipient Address" onChange={e => setRecipient(e.target.value)} />
<QRWrapper>
<img src={QR} alt="" />
</QRWrapper>
</RowBetween>
</LightCard>
</AutoColumn>
)}
<ButtonError
onClick={() => {
setShowConfirm(true)
@ -827,10 +1088,14 @@ export default function ExchangePage() {
error={!!warningHigh}
>
<Text fontSize={20} fontWeight={500}>
{inputError
{generalError
? generalError
: inputError
? inputError
: outputError
? outputError
: recipientError
? recipientError
: tradeError
? tradeError
: warningHigh
@ -839,6 +1104,7 @@ export default function ExchangePage() {
</Text>
</ButtonError>
</AutoColumn>
{warningHigh && (
<FixedBottom>
<GreyCard>

@ -5,11 +5,11 @@ const StyledInput = styled.input`
color: ${({ error, theme }) => error && theme.salmonRed};
background-color: ${({ theme }) => theme.inputBackground};
color: ${({ theme }) => theme.textColor};
width: 0;
font-size: 20px;
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.inputBackground};
font-size: ${({ fontSize }) => fontSize && fontSize};
text-align: ${({ align }) => align && align};

@ -1,28 +1,32 @@
import React, { useState, useRef, useMemo, useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { Link as StyledLink } from '../../theme/components'
import { useTranslation } from 'react-i18next'
import { ethers } from 'ethers'
import '@reach/tooltip/styles.css'
import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import '@reach/tooltip/styles.css'
import { Link } from 'react-router-dom'
import { ethers } from 'ethers'
import { isMobile } from 'react-device-detect'
import { withRouter } from 'react-router-dom'
import { JSBI } from '@uniswap/sdk'
import { Link as StyledLink } from '../../theme/components'
import { Text } from 'rebass'
import Column, { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import TokenLogo from '../TokenLogo'
import { CloseIcon } from '../../theme/components'
import DoubleTokenLogo from '../DoubleLogo'
import { useWeb3React } from '../../hooks'
import { isAddress } from '../../utils'
import Modal from '../Modal'
import { useToken, useAllTokens, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import TokenLogo from '../TokenLogo'
import DoubleTokenLogo from '../DoubleLogo'
import Column, { AutoColumn } from '../Column'
import { Text } from 'rebass'
import { Spinner } from '../../theme'
import { CloseIcon } from '../../theme/components'
import { ColumnCenter } from '../../components/Column'
import { RowBetween, RowFixed } from '../Row'
import { isAddress } from '../../utils'
import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances'
import { useTranslation } from 'react-i18next'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useToken, useAllTokens, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
const TokenModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap}
@ -108,7 +112,17 @@ const MenuItem = styled(PaddedItem)`
background-color: ${({ theme }) => theme.tokenRowHover};
}
`
function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens, filterType, hiddenToken }) {
function SearchModal({
history,
isOpen,
onDismiss,
onTokenSelect,
urlAddedTokens,
filterType,
hiddenToken,
showSendWithSwap,
onTokenSelectSendWithSwap
}) {
const { t } = useTranslation()
const { account, chainId } = useWeb3React()
@ -171,6 +185,7 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens
let balance
// only update if we have data
balance = allBalances?.[account]?.[k]
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
@ -203,10 +218,16 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens
})
}, [tokenList, searchQuery])
function _onTokenSelect(address) {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
function _onTokenSelect(address, sendWithSwap = false) {
if (sendWithSwap) {
setSearchQuery('')
onTokenSelectSendWithSwap(address)
onDismiss()
} else {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
}
// manage focus on modal show
@ -340,8 +361,11 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens
INITIAL_TOKENS_CONTEXT[chainId] &&
!INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(address) &&
!urlAdded
const zeroBalance = JSBI.equal(JSBI.BigInt(0), balance.raw)
return (
<MenuItem key={address} onClick={() => _onTokenSelect(address)}>
<MenuItem key={address} onClick={() => (zeroBalance ? _onTokenSelect(address, true) : _onTokenSelect(address))}>
<RowFixed>
<TokenLogo address={address} size={'24px'} style={{ marginRight: '14px' }} />
<Column>
@ -353,7 +377,22 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens
</RowFixed>
<AutoColumn gap="4px" justify="end">
{balance ? (
<Text>{balance ? balance.toSignificant(6) : '-'}</Text>
<Text>
{zeroBalance && showSendWithSwap ? (
<ColumnCenter
justify="center"
style={{ backgroundColor: '#EBF4FF', padding: '8px', borderRadius: '12px' }}
>
<Text textAlign="center" fontWeight={500} color="#2172E5">
Send With Swap
</Text>
</ColumnCenter>
) : balance ? (
balance.toSignificant(6)
) : (
'-'
)}
</Text>
) : account ? (
<SpinnerWrapper src={Circle} alt="loader" />
) : (

@ -71,7 +71,7 @@ export default function InputSlider({ value, onChange }) {
value={typeof value === 'number' ? value : 0}
onChange={onChange}
aria-labelledby="input-slider"
marks={marks}
// marks={marks}
/>
)
}

@ -52,7 +52,7 @@ export default function TokenLogo({ address, size = '24px', ...rest }) {
if (address === 'ETH') {
return <StyledEthereumLogo size={size} {...rest} />
} else if (!error && !BAD_IMAGES[address]) {
path = TOKEN_ICON_API(address.toLowerCase())
path = TOKEN_ICON_API(address?.toLowerCase())
} else {
return (
<Emoji {...rest} size={size}>

@ -1,6 +0,0 @@
import React from 'react'
import ExchangePage from '../../components/ExchangePage'
export default function Send({ initialCurrency, params }) {
return <ExchangePage initialCurrency={initialCurrency} params={params} sending={true} />
}

251
src/pages/Send/index.tsx Normal file

@ -0,0 +1,251 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { TokenAmount, JSBI } from '@uniswap/sdk'
import QR from '../../assets/svg/QR.svg'
import TokenLogo from '../../components/TokenLogo'
import SearchModal from '../../components/SearchModal'
import ExchangePage from '../../components/ExchangePage'
import NumericalInput from '../../components/NumericalInput'
import ConfirmationModal from '../../components/ConfirmationModal'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { LightCard } from '../../components/Card'
import { ArrowDown } from 'react-feather'
import { AutoColumn } from '../../components/Column'
import { ButtonPrimary } from '../../components/Button'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useToken } from '../../contexts/Tokens'
import { RowBetween } from '../../components/Row'
import { useENSName } from '../../hooks'
import { useWeb3React } from '@web3-react/core'
import { useAddressBalance } from '../../contexts/Balances'
import { parseUnits } from '@ethersproject/units'
import { isAddress } from '../../utils'
const CurrencySelect = styled.button`
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
width: ${({ selected }) => (selected ? '128px' : '180px')}
padding: 8px 12px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
border: 1px solid
${({ selected, theme }) => (selected ? theme.outlineGrey : theme.royalBlue)};
border-radius: 8px;
outline: none;
cursor: pointer;
user-select: none;
:hover {
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
}
:focus {
border: 1px solid ${({ selected, theme }) =>
selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue)};
}
:active {
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
}
`
const StyledDropDown = styled(DropDown)`
height: 35%;
path {
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
}
`
const InputGroup = styled(AutoColumn)`
position: relative;
padding: 40px 0;
`
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.outlineGrey};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
const StyledInput = styled.input`
width: ${({ width }) => width};
border: none;
outline: none;
font-size: 20px;
::placeholder {
color: #edeef2;
}
`
const StyledNumerical = styled(NumericalInput)`
text-align: center;
font-size: 48px;
font-weight: 500px;
width: 100%;
::placeholder {
color: #edeef2;
}
`
const MaxButton = styled.button`
position: absolute;
right: 70px;
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.zumthorBlue};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
outline: none;
}
`
export default function Send() {
const { account } = useWeb3React()
// setting for send with swap or regular swap
const [withSwap, setWithSwap] = useState(true)
// modals
const [modalOpen, setModalOpen] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
// token selected
const [activeTokenAddress, setActiveTokenAddress] = useState()
const token = useToken(activeTokenAddress)
// user inputs
const [typedValue, setTypedValue] = useState('')
const [amount, setAmount] = useState(null)
const [recipient, setRecipient] = useState('0x74Aa01d162E6dC6A657caC857418C403D48E2D77')
//ENS
const recipientENS = useENSName(recipient)
// balances
const userBalance = useAddressBalance(account, token)
//errors
const [generalError, setGeneralError] = useState('')
const [amountError, setAmountError] = useState('')
const [recipientError, setRecipientError] = useState('')
function parseInputAmount(newtypedValue) {
setTypedValue(newtypedValue)
if (!!token && newtypedValue !== '' && newtypedValue !== '.') {
const typedValueParsed = parseUnits(newtypedValue, token.decimals).toString()
setAmount(new TokenAmount(token, typedValueParsed))
}
}
function onMax() {
if (userBalance) {
setTypedValue(userBalance.toExact())
setAmount(userBalance)
}
}
const atMax = amount && userBalance && JSBI.equal(amount.raw, userBalance.raw) ? true : false
//error detection
useEffect(() => {
setGeneralError('')
setRecipientError('')
setAmountError('')
if (!amount) {
setGeneralError('Enter an amount')
}
if (!isAddress(recipient)) {
setRecipientError('Enter a valid address')
}
if (!!!token) {
setGeneralError('Select a token')
}
if (amount && userBalance && JSBI.greaterThan(amount.raw, userBalance.raw)) {
setAmountError('Insufficient Balance')
}
}, [recipient, token, amount, userBalance])
const TopContent = () => {
return (
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<RowBetween>
<Text fontSize={36} fontWeight={500}>
{amount?.toFixed(8)}
</Text>
<TokenLogo address={activeTokenAddress} size={'30px'} />
</RowBetween>
<ArrowDown size={24} color="#888D9B" />
<TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue>
</AutoColumn>
)
}
const BottomContent = () => {
return (
<AutoColumn>
<ButtonPrimary>
<Text color="white" fontSize={20}>
Confirm send
</Text>
</ButtonPrimary>
</AutoColumn>
)
}
const [attemptedSend, setAttemptedSend] = useState(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
return withSwap ? (
<ExchangePage sendingInput={true} />
) : (
<>
<SearchModal
isOpen={modalOpen}
onDismiss={() => {
setModalOpen(false)
}}
filterType="tokens"
onTokenSelect={tokenAddress => setActiveTokenAddress(tokenAddress)}
/>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => setShowConfirm(false)}
hash=""
title="Confirm Send"
topContent={TopContent}
bottomContent={BottomContent}
attemptingTxn={attemptedSend}
pendingConfirmation={pendingConfirmation}
pendingText=""
/>
</>
)
}

@ -41,7 +41,7 @@ const Wrapper = styled.div`
const FixedBottom = styled.div`
position: absolute;
bottom: -240px;
bottom: -200px;
width: 100%;
`

@ -2,5 +2,5 @@ import React from 'react'
import ExchangePage from '../../components/ExchangePage'
export default function Swap({ initialCurrency, params }) {
return <ExchangePage initialCurrency={initialCurrency} params={params} />
return <ExchangePage sendingInput={false} />
}

@ -128,6 +128,21 @@ export const TYPE = {
<Text fontWeight={500} color={theme().royalBlue} {...rest}>
{children}
</Text>
),
gray: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().outlineGrey} {...rest}>
{children}
</Text>
),
italic: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}>
{children}
</Text>
),
error: ({ children, error, ...rest }) => (
<Text fontWeight={500} color={error ? theme().salmonRed : theme().mineshaftGray} {...rest}>
{children}
</Text>
)
}

@ -1,25 +0,0 @@
import { getMarketDetails } from '@uniswap/sdk'
import { getMedian, getMean } from './math'
const DAI = 'DAI'
const USDC = 'USDC'
const TUSD = 'TUSD'
const USD_STABLECOINS = [DAI, USDC, TUSD]
function forEachStablecoin(runner) {
return USD_STABLECOINS.map((stablecoin, index) => runner(index, stablecoin))
}
export function getUSDPrice(reserves) {
const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted)
const [median] = getMedian(ethPrices)
const [mean] = getMean(ethPrices)
const [weightedMean] = getMean(
ethPrices,
forEachStablecoin(i => reserves[i].ethReserve.amount)
)
return getMean([median, mean, weightedMean])[0]
}