From 8064dd8ede4670ed2905f7c4b06fd492e8bb9aed Mon Sep 17 00:00:00 2001 From: Jordan Frankfurt Date: Thu, 3 Feb 2022 09:38:42 -0600 Subject: [PATCH] feat(widgets): support convenience fee in trades (#3219) * feat(widgets): support convenience fee in trades * update call signature * pr feedback --- src/hooks/useSwapCallArguments.tsx | 17 +++++---- src/hooks/useSwapCallback.tsx | 2 +- src/lib/components/Swap/SwapButton.tsx | 12 ++++--- src/lib/components/Swap/index.tsx | 6 ++-- src/lib/hooks/swap/useSwapCallback.tsx | 32 ++++++++++++----- src/lib/hooks/swap/useSwapInfo.tsx | 10 ++++-- src/lib/hooks/swap/useSyncConvenienceFee.ts | 35 +++++++++++++++++++ ...SwapDefaults.ts => useSyncSwapDefaults.ts} | 2 +- src/lib/state/swap.ts | 9 ++++- 9 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 src/lib/hooks/swap/useSyncConvenienceFee.ts rename src/lib/hooks/swap/{useSwapDefaults.ts => useSyncSwapDefaults.ts} (98%) diff --git a/src/hooks/useSwapCallArguments.tsx b/src/hooks/useSwapCallArguments.tsx index dcb3c3d0b5..d94b5c355c 100644 --- a/src/hooks/useSwapCallArguments.tsx +++ b/src/hooks/useSwapCallArguments.tsx @@ -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, + fee: 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, recipient, slippageTolerance: allowedSlippage, ...(signatureData @@ -167,15 +169,16 @@ export function useSwapCallArguments( ] } }, [ - trade, - recipient, - library, account, + allowedSlippage, + argentWalletContract, chainId, deadline, + fee, + library, + recipient, routerContract, - allowedSlippage, - argentWalletContract, signatureData, + trade, ]) } diff --git a/src/hooks/useSwapCallback.tsx b/src/hooks/useSwapCallback.tsx index 338de52872..16dbcfd425 100644 --- a/src/hooks/useSwapCallback.tsx +++ b/src/hooks/useSwapCallback.tsx @@ -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) { diff --git a/src/lib/components/Swap/SwapButton.tsx b/src/lib/components/Swap/SwapButton.tsx index e06a90b1b6..85c7869c1b 100644 --- a/src/lib/components/Swap/SwapButton.tsx +++ b/src/lib/components/Swap/SwapButton.tsx @@ -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 }, + fee, } = 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, + fee, + }) //@TODO(ianlapham): add a loading state, process errors const setDisplayTxHash = useUpdateAtom(displayTxHashAtom) diff --git a/src/lib/components/Swap/index.tsx b/src/lib/components/Swap/index.tsx index 325b139012..7f6c6ecc5a 100644 --- a/src/lib/components/Swap/index.tsx +++ b/src/lib/components/Swap/index.tsx @@ -1,8 +1,9 @@ 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(null) diff --git a/src/lib/hooks/swap/useSwapCallback.tsx b/src/lib/hooks/swap/useSwapCallback.tsx index c1e6938d93..0acb93a249 100644 --- a/src/lib/hooks/swap/useSwapCallback.tsx +++ b/src/lib/hooks/swap/useSwapCallback.tsx @@ -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,33 @@ export enum SwapCallbackState { VALID, } +interface UseSwapCallbackReturns { + state: SwapCallbackState + callback: null | (() => Promise) + 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 + fee?: 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); error: ReactNode | null } { +export function useSwapCallback({ + trade, + allowedSlippage, + recipientAddressOrName, + signatureData, + deadline, + fee, +}: UseSwapCallbackArgs): UseSwapCallbackReturns { const { account, chainId, library } = useActiveWeb3React() - const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData, deadline) + const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName, signatureData, deadline, fee) const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls) const { address: recipientAddress } = useENS(recipientAddressOrName) diff --git a/src/lib/hooks/swap/useSwapInfo.tsx b/src/lib/hooks/swap/useSwapInfo.tsx index d612d6eb8a..ba990cb7a3 100644 --- a/src/lib/hooks/swap/useSwapInfo.tsx +++ b/src/lib/hooks/swap/useSwapInfo.tsx @@ -1,11 +1,12 @@ 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 { DEFAULT_FEE_OPTIONS, 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 + fee: FeeOptions } const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { @@ -42,6 +44,8 @@ function useComputeSwapInfo(): SwapInfo { [Field.OUTPUT]: outputCurrency, } = useAtomValue(swapAtom) + const fee = useAtomValue(feeOptionsAtom) + const to = account const relevantTokenBalances = useCurrencyBalances( @@ -139,8 +143,9 @@ function useComputeSwapInfo(): SwapInfo { inputError, trade, allowedSlippage, + fee, }), - [currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage] + [currencies, currencyBalances, currencyAmounts, inputError, trade, allowedSlippage, fee] ) } @@ -150,6 +155,7 @@ const swapInfoAtom = atom({ currencyAmounts: {}, trade: { state: TradeState.INVALID }, allowedSlippage: new Percent(0), + fee: DEFAULT_FEE_OPTIONS, }) export function SwapInfoUpdater() { diff --git a/src/lib/hooks/swap/useSyncConvenienceFee.ts b/src/lib/hooks/swap/useSyncConvenienceFee.ts new file mode 100644 index 0000000000..ff12d8a0cc --- /dev/null +++ b/src/lib/hooks/swap/useSyncConvenienceFee.ts @@ -0,0 +1,35 @@ +import { Percent } from '@uniswap/sdk-core' +import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { useUpdateAtom } from 'jotai/utils' +import { DEFAULT_FEE_OPTIONS, 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(DEFAULT_FEE_OPTIONS) + }, [chainId, convenienceFee, convenienceFeeRecipient, updateFeeOptions]) +} diff --git a/src/lib/hooks/swap/useSwapDefaults.ts b/src/lib/hooks/swap/useSyncSwapDefaults.ts similarity index 98% rename from src/lib/hooks/swap/useSwapDefaults.ts rename to src/lib/hooks/swap/useSyncSwapDefaults.ts index edad2538f4..3d2eaac64e 100644 --- a/src/lib/hooks/swap/useSwapDefaults.ts +++ b/src/lib/hooks/swap/useSyncSwapDefaults.ts @@ -31,7 +31,7 @@ interface UseSwapDefaultsArgs { defaultOutputAmount?: string } -export default function useSwapDefaults({ +export default function useSyncSwapDefaults({ defaultInputAddress, defaultInputAmount, defaultOutputAddress, diff --git a/src/lib/state/swap.ts b/src/lib/state/swap.ts index 9626c7cbad..eb7875fb9c 100644 --- a/src/lib/state/swap.ts +++ b/src/lib/state/swap.ts @@ -1,4 +1,5 @@ -import { Currency } from '@uniswap/sdk-core' +import { Currency, Percent } 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,9 @@ export const independentFieldAtom = pickAtom(swapAtom, 'independentField') // If set to a transaction hash, that transaction will display in a status dialog. export const displayTxHashAtom = atom(undefined) + +export const DEFAULT_FEE_OPTIONS = { + fee: new Percent(0), + recipient: '', +} +export const feeOptionsAtom = atom(DEFAULT_FEE_OPTIONS)