Commit 3a34c2ec authored by Moody Salem's avatar Moody Salem

eth swaps kinda working

parent 83b0ef94
......@@ -13,12 +13,14 @@ import { RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact'
import SwapRoute from './SwapRoute'
function TradeSummary({ trade, allowedSlippage }: { trade: V2Trade | V3Trade; allowedSlippage: number }) {
function TradeSummary({ trade }: { trade: V2Trade | V3Trade }) {
const theme = useContext(ThemeContext)
const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade)
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
const [allowedSlippage] = useUserSlippageTolerance()
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
const price = trade.executionPrice
return (
<>
<AutoColumn style={{ padding: '8px 16px' }} gap="8px">
......@@ -97,8 +99,6 @@ export interface AdvancedSwapDetailsProps {
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = Boolean(
(trade && trade instanceof V2Trade && trade.route.pairs.length > 2) ||
(trade instanceof V3Trade && trade.route.pools.length > 2)
......@@ -108,7 +108,7 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
<AutoColumn gap="0px">
{trade && (
<>
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
<TradeSummary trade={trade} />
{showRoute && (
<>
<RowBetween style={{ padding: '4px 16px' }}>
......
import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers'
import { TokenAmount, CurrencyAmount, ETHER, ChainId } from '@uniswap/sdk-core'
import { TokenAmount, CurrencyAmount, ETHER, ChainId, Percent } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useCallback, useMemo } from 'react'
import { V2_ROUTER_ADDRESS } from '../constants'
import { SWAP_ROUTER_ADDRESSES } from '../constants/v3'
import { Field } from '../state/swap/actions'
import { useTransactionAdder, useHasPendingApproval } from '../state/transactions/hooks'
import { computeSlippageAdjustedAmounts } from '../utils/prices'
import { calculateGasMargin } from '../utils'
import { useTokenContract } from './useContract'
import { useActiveWeb3React } from './index'
......@@ -101,12 +99,15 @@ export function useApproveCallback(
}
// wraps useApproveCallback in the context of a swap
export function useApproveCallbackFromTrade(trade?: V2Trade | V3Trade, allowedSlippage = 0) {
export function useApproveCallbackFromTrade(trade: V2Trade | V3Trade | undefined, allowedSlippage: number) {
const { chainId } = useActiveWeb3React()
const swapRouterAddress = SWAP_ROUTER_ADDRESSES[chainId as ChainId]
const amountToApprove = useMemo(
() => (trade ? computeSlippageAdjustedAmounts(trade, allowedSlippage)[Field.INPUT] : undefined),
() => (trade ? trade.maximumAmountIn(new Percent(allowedSlippage, 10_000)) : undefined),
[trade, allowedSlippage]
)
return useApproveCallback(amountToApprove, trade instanceof V2Trade ? V2_ROUTER_ADDRESS : swapRouterAddress)
return useApproveCallback(
amountToApprove,
trade instanceof V2Trade ? V2_ROUTER_ADDRESS : trade instanceof V3Trade ? swapRouterAddress : undefined
)
}
import JSBI from 'jsbi'
import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts'
import { Router, SwapParameters, Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { Percent, TradeType } from '@uniswap/sdk-core'
import { Router, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { ChainId, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { BIPS_BASE, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { SWAP_ROUTER_ADDRESSES } from '../constants/v3'
import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin, isAddress, shortenAddress } from '../utils'
......@@ -23,8 +23,9 @@ export enum SwapCallbackState {
}
interface SwapCall {
contract: Contract
parameters: SwapParameters
address: string
calldata: string
value: string
}
interface SuccessfulCall {
......@@ -55,13 +56,13 @@ function useSwapCallArguments(
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline()
const routerContract = useV2RouterContract()
return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline || !routerContract) return []
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
if (trade instanceof V2Trade) {
if (!routerContract) return []
const swapMethods = []
swapMethods.push(
Router.swapCallParameters(trade, {
......@@ -82,10 +83,28 @@ function useSwapCallArguments(
})
)
}
return swapMethods.map((parameters) => ({ parameters, contract: routerContract }))
return swapMethods.map(({ methodName, args, value }) => ({
address: routerContract.address,
calldata: routerContract.interface.encodeFunctionData(methodName, args),
value,
}))
} else {
// trade is V3Trade
const { value, calldata } = SwapRouter.swapCallParameters(trade, {
recipient,
slippageTolerance: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
deadline: deadline.toNumber(),
})
const swapRouterAddress = SWAP_ROUTER_ADDRESSES[chainId as ChainId]
if (!swapRouterAddress) return []
return [
{
address: swapRouterAddress,
calldata,
value,
},
]
}
return []
}, [account, allowedSlippage, chainId, deadline, library, recipient, routerContract, trade])
}
......@@ -124,13 +143,19 @@ export function useSwapCallback(
callback: async function onSwap(): Promise<string> {
const estimatedCalls: EstimatedSwapCall[] = await Promise.all(
swapCalls.map((call) => {
const {
parameters: { methodName, args, value },
contract,
} = call
const options = !value || isZero(value) ? {} : { value }
const { address, calldata, value } = call
return contract.estimateGas[methodName](...args, options)
const tx =
!value || isZero(value)
? { to: address, data: calldata }
: {
to: address,
data: calldata,
value,
}
return library
.estimateGas(tx)
.then((gasEstimate) => {
return {
call,
......@@ -140,7 +165,8 @@ export function useSwapCallback(
.catch((gasError) => {
console.debug('Gas estimate failed, trying eth_call to extract error', call)
return contract.callStatic[methodName](...args, options)
return library
.call(tx)
.then((result) => {
console.debug('Unexpected successful call after failed estimate gas', call, gasError, result)
return { call, error: new Error('Unexpected issue with estimating the gas. Please try again.') }
......@@ -176,16 +202,18 @@ export function useSwapCallback(
}
const {
call: {
contract,
parameters: { methodName, args, value },
},
call: { address, calldata, value },
gasEstimate,
} = successfulEstimation
return contract[methodName](...args, {
return library
.getSigner()
.sendTransaction({
from: account,
to: address,
data: calldata,
gasLimit: calculateGasMargin(gasEstimate),
...(value && !isZero(value) ? { value, from: account } : { from: account }),
...(value && !isZero(value) ? { value } : {}),
})
.then((response: any) => {
const inputSymbol = trade.inputAmount.currency.symbol
......@@ -218,7 +246,7 @@ export function useSwapCallback(
throw new Error('Transaction rejected.')
} else {
// otherwise, the error was unexpected and we need to convey that
console.error(`Swap failed`, error, methodName, args, value)
console.error(`Swap failed`, error, address, calldata, value)
throw new Error(`Swap failed: ${error.message}`)
}
})
......
......@@ -183,27 +183,23 @@ export default function Swap({ history }: RouteComponentProps) {
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
const [approvalState, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
if (approvalState === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
}, [approvalState, approvalSubmitted])
const maxInputAmount: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxInputAmount = Boolean(maxInputAmount && parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade instanceof V2Trade ? trade : undefined,
allowedSlippage,
recipient
)
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(trade, allowedSlippage, recipient)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
......@@ -272,9 +268,9 @@ export default function Swap({ history }: RouteComponentProps) {
// never show if price impact is above threshold in non expert mode
const showApproveFlow =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
(approvalState === ApprovalState.NOT_APPROVED ||
approvalState === ApprovalState.PENDING ||
(approvalSubmitted && approvalState === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
......@@ -432,10 +428,10 @@ export default function Swap({ history }: RouteComponentProps) {
<AutoColumn style={{ width: '100%' }} gap="12px">
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
disabled={approvalState !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="100%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
altDisabledStyle={approvalState === ApprovalState.PENDING} // show solid button while waiting
confirmed={approvalState === ApprovalState.APPROVED}
>
<AutoRow justify="space-between">
<span style={{ display: 'flex', alignItems: 'center' }}>
......@@ -447,9 +443,9 @@ export default function Swap({ history }: RouteComponentProps) {
{/* we need to shorted this string on mobile */}
{'Allow Uniswap to spend your ' + currencies[Field.INPUT]?.symbol}
</span>
{approval === ApprovalState.PENDING ? (
{approvalState === ApprovalState.PENDING ? (
<Loader stroke="white" />
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
) : approvalSubmitted && approvalState === ApprovalState.APPROVED ? (
<Unlock size="16" stroke="white" />
) : (
<Unlock size="16" stroke="white" />
......@@ -473,7 +469,9 @@ export default function Swap({ history }: RouteComponentProps) {
width="100%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
!isValid ||
approvalState !== ApprovalState.APPROVED ||
(priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
......@@ -484,7 +482,7 @@ export default function Swap({ history }: RouteComponentProps) {
</Text>
</ButtonError>
</AutoColumn>
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{showApproveFlow && <ProgressSteps steps={[approvalState === ApprovalState.APPROVED]} />}
</AutoRow>
) : (
<ButtonError
......
......@@ -8,9 +8,9 @@ import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPA
import { Field } from '../state/swap/actions'
import { basisPointsToPercent } from './index'
const BASE_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000))
const THIRTY_BIPS_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000))
const ONE_HUNDRED_PERCENT = new Percent(JSBI.BigInt(10000), JSBI.BigInt(10000))
const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(BASE_FEE)
const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE)
// computes price breakdown for the trade
export function computeTradePriceBreakdown(
......@@ -46,9 +46,26 @@ export function computeTradePriceBreakdown(
return { priceImpactWithoutFee: priceImpactWithoutFeePercent, realizedLPFee: realizedLPFeeAmount }
} else {
const realizedLPFee = !trade
? undefined
: ONE_HUNDRED_PERCENT.subtract(
trade.route.pools.reduce<Fraction>(
(currentFee: Fraction, pool): Fraction =>
currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 10_000))),
ONE_HUNDRED_PERCENT
)
)
const realizedLPFeeAmount =
realizedLPFee &&
trade &&
(trade.inputAmount instanceof TokenAmount
? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
: CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient))
return {
priceImpactWithoutFee: undefined,
realizedLPFee: undefined,
// TODO: real price impact
priceImpactWithoutFee: new Percent(0),
realizedLPFee: realizedLPFeeAmount,
}
}
}
......@@ -65,11 +82,20 @@ export function computeSlippageAdjustedAmounts(
}
}
const IMPACT_TIERS = [
BLOCKED_PRICE_IMPACT_NON_EXPERT,
ALLOWED_PRICE_IMPACT_HIGH,
ALLOWED_PRICE_IMPACT_MEDIUM,
ALLOWED_PRICE_IMPACT_LOW,
]
export function warningSeverity(priceImpact: Percent | undefined): 0 | 1 | 2 | 3 | 4 {
if (!priceImpact?.lessThan(BLOCKED_PRICE_IMPACT_NON_EXPERT)) return 4
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_HIGH)) return 3
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 2
if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_LOW)) return 1
if (!priceImpact) return 4
let impact = IMPACT_TIERS.length
for (const impactLevel of IMPACT_TIERS) {
if (priceImpact.lessThan(impactLevel)) return impact as 0 | 1 | 2 | 3 | 4
impact--
}
return 0
}
......
......@@ -4158,10 +4158,10 @@
"@uniswap/v2-core" "1.0.1"
"@uniswap/v3-core" "1.0.0-rc.2"
"@uniswap/v3-sdk@^1.0.0-alpha.22":
version "1.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.22.tgz#74acca3b952fc71a103fca14e79ee696f90096ba"
integrity sha512-HoITh2zpxG6xDh3hEtLPrqYAF3alNkPnpZ5mWlIvoql1W/3c2LKMvbsBowj7RGDSeMVK4OJjyE2m+rX9b/EqNw==
"@uniswap/v3-sdk@^1.0.0-alpha.23":
version "1.0.0-alpha.23"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-1.0.0-alpha.23.tgz#0971b38acd46e08d727f17ad8b10b5c21a25b99f"
integrity sha512-ibVW9EnwymIQAHBCCQCorwA5yLzRfQ4OYwafTkD1fHx2UtrZoHVYLBSshMa4tcN1uMAuiglFOQv/IIJ20ZRgyw==
dependencies:
"@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment