Commit a323a5c4 authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feat(widgets): convenience fee (#3231)

* feat(widgets): support convenience fee in trades (#3219)

* feat(widgets): support convenience fee in trades

* update call signature

* pr feedback

* set default convenience fee to undefined

* pr feedback
parent 43931dd6
......@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { SwapRouter, Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { FeeOptions, SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from 'constants/addresses'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react'
......@@ -36,7 +36,8 @@ export function useSwapCallArguments(
allowedSlippage: Percent,
recipientAddressOrName: string | null | undefined,
signatureData: SignatureData | null | undefined,
deadline: BigNumber | undefined
deadline: BigNumber | undefined,
feeOptions: FeeOptions | undefined
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
......@@ -98,6 +99,7 @@ export function useSwapCallArguments(
} else {
// swap options shared by v3 and v2+v3 swap routers
const sharedSwapOptions = {
fee: feeOptions,
recipient,
slippageTolerance: allowedSlippage,
...(signatureData
......@@ -167,15 +169,16 @@ export function useSwapCallArguments(
]
}
}, [
trade,
recipient,
library,
account,
allowedSlippage,
argentWalletContract,
chainId,
deadline,
feeOptions,
library,
recipient,
routerContract,
allowedSlippage,
argentWalletContract,
signatureData,
trade,
])
}
......@@ -33,7 +33,7 @@ export function useSwapCallback(
state,
callback: libCallback,
error,
} = useLibSwapCallBack(trade, allowedSlippage, recipient, signatureData, deadline)
} = useLibSwapCallBack({ trade, allowedSlippage, recipientAddressOrName: recipient, signatureData, deadline })
const callback = useMemo(() => {
if (!libCallback || !trade) {
......
......@@ -2,8 +2,9 @@ import { t } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_MEDIUM } from 'constants/misc'
import { useAtom } from 'jotai'
import { integratorFeeAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { useAtomValue } from 'jotai/utils'
import { MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { feeOptionsAtom } from 'lib/state/swap'
import styled, { Color, ThemedText } from 'lib/theme'
import { useMemo } from 'react'
import { currencyId } from 'utils/currencyId'
......@@ -45,13 +46,19 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
const priceImpact = useMemo(() => computeRealizedPriceImpact(trade), [trade])
const integrator = window.location.hostname
const [integratorFee] = useAtom(integratorFeeAtom)
const feeOptions = useAtomValue(feeOptionsAtom)
const details = useMemo(() => {
// @TODO(ianlapham): Check that provider fee is even a valid list item
return [
// [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`],
[t`${integrator} fee`, integratorFee && `${integratorFee} ${currencyId(inputCurrency)}`],
[
t`${integrator} fee`,
feeOptions &&
`${outputAmount.multiply(feeOptions.fee).toSignificant(2)} ${
outputCurrency.symbol || currencyId(outputCurrency)
}`,
],
[
t`Price impact`,
`${priceImpact.toFixed(2)}%`,
......@@ -77,7 +84,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
function isDetail(detail: unknown[]): detail is [string, string, Color | undefined] {
return Boolean(detail[1])
}
}, [allowedSlippage, inputCurrency, integrator, integratorFee, outputCurrency.symbol, priceImpact, trade])
}, [allowedSlippage, inputCurrency, integrator, feeOptions, outputAmount, outputCurrency, priceImpact, trade])
return (
<>
{details.map(([label, detail, color]) => (
......
......@@ -25,6 +25,18 @@ function Fixture() {
}
}, [color, setColor])
const [convenienceFee] = useValue('convenienceFee', { defaultValue: 100 })
const FEE_RECIPIENT_OPTIONS = [
'',
'0x1D9Cd50Dde9C19073B81303b3d930444d11552f7',
'0x0dA5533d5a9aA08c1792Ef2B6a7444E149cCB0AD',
'0xE6abE059E5e929fd17bef158902E73f0FEaCD68c',
]
const [convenienceFeeRecipient] = useSelect('convenienceFeeRecipient', {
options: FEE_RECIPIENT_OPTIONS,
defaultValue: FEE_RECIPIENT_OPTIONS[1],
})
const optionsToAddressMap: Record<string, string> = {
none: '',
Native: 'NATIVE',
......@@ -52,11 +64,13 @@ function Fixture() {
return (
<Swap
tokenList={tokens}
convenienceFee={convenienceFee}
convenienceFeeRecipient={convenienceFeeRecipient}
defaultInputAddress={optionsToAddressMap[defaultInput]}
defaultInputAmount={defaultInputAmount}
defaultOutputAddress={optionsToAddressMap[defaultOutput]}
defaultOutputAmount={defaultOutputAmount}
tokenList={tokens}
/>
)
}
......
......@@ -51,6 +51,7 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
currencies: { [Field.INPUT]: inputCurrency },
currencyBalances: { [Field.INPUT]: inputCurrencyBalance },
currencyAmounts: { [Field.INPUT]: inputCurrencyAmount, [Field.OUTPUT]: outputCurrencyAmount },
feeOptions,
} = useSwapInfo()
const independentField = useAtomValue(independentFieldAtom)
......@@ -118,13 +119,14 @@ export default function SwapButton({ disabled }: SwapButtonProps) {
const { signatureData } = useERC20PermitFromTrade(optimizedTrade, allowedSlippage, deadline)
// the callback to execute the swap
const { callback: swapCallback } = useSwapCallback(
optimizedTrade,
const { callback: swapCallback } = useSwapCallback({
trade: optimizedTrade,
allowedSlippage,
account ?? null,
recipientAddressOrName: account ?? null,
signatureData,
deadline
)
deadline,
feeOptions,
})
//@TODO(ianlapham): add a loading state, process errors
const setDisplayTxHash = useUpdateAtom(displayTxHashAtom)
......
import { Trans } from '@lingui/macro'
import { TokenInfo } from '@uniswap/token-lists'
import { useAtom } from 'jotai'
import useSwapDefaults from 'lib/hooks/swap/useSwapDefaults'
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
import useSyncConvenienceFee from 'lib/hooks/swap/useSyncConvenienceFee'
import useSyncSwapDefaults from 'lib/hooks/swap/useSyncSwapDefaults'
import { usePendingTransactions } from 'lib/hooks/transactions'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import useTokenList from 'lib/hooks/useTokenList'
......@@ -47,7 +48,8 @@ export interface SwapProps {
export default function Swap(props: SwapProps) {
useTokenList(props.tokenList)
useSwapDefaults(props)
useSyncSwapDefaults(props)
useSyncConvenienceFee(props)
const { active, account } = useActiveWeb3React()
const [boundary, setBoundary] = useState<HTMLDivElement | null>(null)
......
......@@ -3,6 +3,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useENS from 'hooks/useENS'
import { SignatureData } from 'hooks/useERC20Permit'
......@@ -17,18 +18,40 @@ export enum SwapCallbackState {
VALID,
}
interface UseSwapCallbackReturns {
state: SwapCallbackState
callback: null | (() => Promise<TransactionResponse>)
error: ReactNode | null
}
interface UseSwapCallbackArgs {
trade: AnyTrade | undefined // trade to execute, required
allowedSlippage: Percent // in bips
recipientAddressOrName: string | null | undefined // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined
deadline: BigNumber | undefined
feeOptions?: FeeOptions
}
// returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
trade: AnyTrade | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null | undefined, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined,
deadline: BigNumber | undefined
): { state: SwapCallbackState; callback: null | (() => Promise<TransactionResponse>); error: ReactNode | null } {
export function useSwapCallback({
trade,
allowedSlippage,
recipientAddressOrName,
signatureData,
deadline,
feeOptions,
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
const { account, chainId, library } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData, deadline)
const swapCalls = useSwapCallArguments(
trade,
allowedSlippage,
recipientAddressOrName,
signatureData,
deadline,
feeOptions
)
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls)
const { address: recipientAddress } = useENS(recipientAddressOrName)
......
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
import { maxSlippageAtom } from 'lib/state/settings'
import { Field, swapAtom } from 'lib/state/swap'
import { feeOptionsAtom, Field, swapAtom } from 'lib/state/swap'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ReactNode, useEffect, useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
......@@ -23,6 +24,7 @@ interface SwapInfo {
state: TradeState
}
allowedSlippage: Percent
feeOptions: FeeOptions | undefined
}
const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
......@@ -42,6 +44,8 @@ function useComputeSwapInfo(): SwapInfo {
[Field.OUTPUT]: outputCurrency,
} = useAtomValue(swapAtom)
const feeOptions = useAtomValue(feeOptionsAtom)
const to = account
const relevantTokenBalances = useCurrencyBalances(
......@@ -139,8 +143,9 @@ function useComputeSwapInfo(): SwapInfo {
inputError,
trade,
allowedSlippage,
feeOptions,
}),
[currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage]
[currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage, feeOptions]
)
}
......@@ -150,6 +155,7 @@ const swapInfoAtom = atom<SwapInfo>({
currencyAmounts: {},
trade: { state: TradeState.INVALID },
allowedSlippage: new Percent(0),
feeOptions: undefined,
})
export function SwapInfoUpdater() {
......
import { Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useUpdateAtom } from 'jotai/utils'
import { feeOptionsAtom } from 'lib/state/swap'
import { useEffect } from 'react'
interface FeeOptionsArgs {
convenienceFee?: number
convenienceFeeRecipient?: string | string | { [chainId: number]: string }
}
export default function useSyncConvenienceFee({ convenienceFee, convenienceFeeRecipient }: FeeOptionsArgs) {
const { chainId } = useActiveWeb3React()
const updateFeeOptions = useUpdateAtom(feeOptionsAtom)
useEffect(() => {
if (convenienceFee && convenienceFeeRecipient) {
if (typeof convenienceFeeRecipient === 'string') {
updateFeeOptions({
fee: new Percent(convenienceFee, 10_000),
recipient: convenienceFeeRecipient,
})
return
}
if (chainId && convenienceFeeRecipient[chainId]) {
updateFeeOptions({
fee: new Percent(convenienceFee, 10_000),
recipient: convenienceFeeRecipient[chainId],
})
return
}
}
updateFeeOptions(undefined)
}, [chainId, convenienceFee, convenienceFeeRecipient, updateFeeOptions])
}
......@@ -31,7 +31,7 @@ interface UseSwapDefaultsArgs {
defaultOutputAmount?: string
}
export default function useSwapDefaults({
export default function useSyncSwapDefaults({
defaultInputAddress,
defaultInputAmount,
defaultOutputAddress,
......
......@@ -8,8 +8,7 @@ export const MIN_HIGH_SLIPPAGE = new Percent(1, 100)
interface Settings {
maxSlippage: Percent | 'auto' // auto will cause slippage to resort to default calculation
transactionTtl: number | undefined // transaction ttl in minutes
integratorFee: number | undefined
transactionTtl: number | undefined
mockTogglable: boolean
clientSideRouter: boolean // whether to use the client-side router or query the remote API
}
......@@ -17,7 +16,6 @@ interface Settings {
const initialSettings: Settings = {
maxSlippage: 'auto',
transactionTtl: undefined,
integratorFee: undefined,
mockTogglable: true,
clientSideRouter: false,
}
......@@ -25,6 +23,5 @@ const initialSettings: Settings = {
export const settingsAtom = atomWithReset(initialSettings)
export const maxSlippageAtom = pickAtom(settingsAtom, 'maxSlippage')
export const transactionTtlAtom = pickAtom(settingsAtom, 'transactionTtl')
export const integratorFeeAtom = pickAtom(settingsAtom, 'integratorFee')
export const mockTogglableAtom = pickAtom(settingsAtom, 'mockTogglable', setTogglable)
export const clientSideRouterAtom = pickAtom(settingsAtom, 'clientSideRouter')
import { Currency } from '@uniswap/sdk-core'
import { FeeOptions } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { atom } from 'jotai'
......@@ -27,3 +28,5 @@ export const independentFieldAtom = pickAtom(swapAtom, 'independentField')
// If set to a transaction hash, that transaction will display in a status dialog.
export const displayTxHashAtom = atom<string | undefined>(undefined)
export const feeOptionsAtom = atom<FeeOptions | undefined>(undefined)
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