Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
feat: use multicall for argent wallets in swap and v3 add liquidity (#…
Browse files Browse the repository at this point in the history
…1387)

* use argent wallet contract in swap callback

* maybe working swap callback

* chore(v3): trigger a breaking release

BREAKING CHANGE: trigger a major release for the uniswap interface to indicate it now supports swapping and liquidity provision against uniswap protocol v3

* fix the value

* improve the error coverage

* retry more frequently, couple more error nits

* the is argent call was being sketchy

* get it working for add liquidity

* `0x0` for v2 swaps too

* small nits in position page

* fix import

* fix compiler error
  • Loading branch information
moodysalem authored May 21, 2021
1 parent 99ad4ae commit 562b402
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 20 deletions.
61 changes: 61 additions & 0 deletions src/abis/argent-wallet-contract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "_transactions",
"type": "tuple[]"
}
],
"name": "wc_multiCall",
"outputs": [
{
"internalType": "bytes[]",
"name": "",
"type": "bytes[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_msgHash",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "isValidSignature",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "view",
"type": "function"
}
]
15 changes: 15 additions & 0 deletions src/hooks/useArgentWalletContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ArgentWalletContract } from '../abis/types'
import { useActiveWeb3React } from './web3'
import { useContract } from './useContract'
import useIsArgentWallet from './useIsArgentWallet'
import ArgentWalletContractABI from '../abis/argent-wallet-contract.json'

export function useArgentWalletContract(): ArgentWalletContract | null {
const { account } = useActiveWeb3React()
const isArgentWallet = useIsArgentWallet()
return useContract(
isArgentWallet ? account ?? undefined : undefined,
ArgentWalletContractABI,
true
) as ArgentWalletContract
}
2 changes: 1 addition & 1 deletion src/hooks/useContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { Quoter, UniswapV3Factory, UniswapV3Pool } from 'types/v3'
import { NonfungiblePositionManager } from 'types/v3/NonfungiblePositionManager'
import { V3Migrator } from 'types/v3/V3Migrator'
import { getContract } from 'utils'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Multicall2, Weth } from '../abis/types'
import { Erc20, ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Multicall2, Weth } from '../abis/types'
import { UNI } from '../constants/tokens'
import { useActiveWeb3React } from './web3'

Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useIsArgentWallet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useMemo } from 'react'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
import { useActiveWeb3React } from './web3'
import { useArgentWalletDetectorContract } from './useContract'

export default function useIsArgentWallet(): boolean {
const { account } = useActiveWeb3React()
const argentWalletDetector = useArgentWalletDetectorContract()
const call = useSingleCallResult(argentWalletDetector, 'isArgentWallet', [account ?? undefined], NEVER_RELOAD)
const inputs = useMemo(() => [account ?? undefined], [account])
const call = useSingleCallResult(argentWalletDetector, 'isArgentWallet', inputs, NEVER_RELOAD)
return call?.result?.[0] ?? false
}
3 changes: 2 additions & 1 deletion src/hooks/useSocksBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export default function useSocksBalance(): JSBI | undefined {
const { account } = useActiveWeb3React()
const socksContract = useSocksController()

const { result } = useSingleCallResult(socksContract, 'balanceOf', [account ?? undefined], NEVER_RELOAD)
const inputs = useMemo(() => [account ?? undefined], [account])
const { result } = useSingleCallResult(socksContract, 'balanceOf', inputs, NEVER_RELOAD)
const data = result?.[0]
return data ? JSBI.BigInt(data.toString()) : undefined
}
Expand Down
65 changes: 58 additions & 7 deletions src/hooks/useSwapCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
import { calculateGasMargin } from '../utils/calculateGasMargin'
import approveAmountCalldata from '../utils/approveAmountCalldata'
import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks'
import { isAddress, shortenAddress } from '../utils'
import isZero from '../utils/isZero'
import { useActiveWeb3React } from './web3'
import { useArgentWalletContract } from './useArgentWalletContract'
import { useV2RouterContract } from './useContract'
import { SignatureData } from './useERC20Permit'
import useTransactionDeadline from './useTransactionDeadline'
Expand Down Expand Up @@ -61,13 +63,15 @@ function useSwapCallArguments(
const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline()
const routerContract = useV2RouterContract()
const argentWalletContract = useArgentWalletContract()

return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []

if (trade instanceof V2Trade) {
if (!routerContract) return []
const swapMethods = []

swapMethods.push(
Router.swapCallParameters(trade, {
feeOnTransfer: false,
Expand All @@ -87,11 +91,30 @@ function useSwapCallArguments(
})
)
}
return swapMethods.map(({ methodName, args, value }) => ({
address: routerContract.address,
calldata: routerContract.interface.encodeFunctionData(methodName, args),
value,
}))
return swapMethods.map(({ methodName, args, value }) => {
if (argentWalletContract && trade.inputAmount.currency.isToken) {
return {
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
[
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), routerContract.address),
{
to: routerContract.address,
value: value,
data: routerContract.interface.encodeFunctionData(methodName, args),
},
],
]),
value: '0x0',
}
} else {
return {
address: routerContract.address,
calldata: routerContract.interface.encodeFunctionData(methodName, args),
value,
}
}
})
} else {
// trade is V3Trade
const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
Expand Down Expand Up @@ -122,7 +145,24 @@ function useSwapCallArguments(
}
: {}),
})

if (argentWalletContract && trade.inputAmount.currency.isToken) {
return [
{
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
[
approveAmountCalldata(trade.maximumAmountIn(allowedSlippage), swapRouterAddress),
{
to: swapRouterAddress,
value: value,
data: calldata,
},
],
]),
value: '0x0',
},
]
}
return [
{
address: swapRouterAddress,
Expand All @@ -131,7 +171,18 @@ function useSwapCallArguments(
},
]
}
}, [account, allowedSlippage, chainId, deadline, library, recipient, routerContract, signatureData, trade])
}, [
account,
allowedSlippage,
argentWalletContract,
chainId,
deadline,
library,
recipient,
routerContract,
signatureData,
trade,
])
}

/**
Expand Down
49 changes: 40 additions & 9 deletions src/pages/AddLiquidity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AlertTriangle, AlertCircle } from 'react-feather'
import ReactGA from 'react-ga'
import { ZERO_PERCENT } from '../../constants/misc'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses'
import { useArgentWalletContract } from '../../hooks/useArgentWalletContract'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
Expand All @@ -18,6 +19,7 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { RowBetween, RowFixed } from '../../components/Row'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
import approveAmountCalldata from '../../utils/approveAmountCalldata'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import Review from './Review'
import { useActiveWeb3React } from '../../hooks/web3'
Expand Down Expand Up @@ -170,13 +172,15 @@ export default function AddLiquidity({
{}
)

const argentWalletContract = useArgentWalletContract()

// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_A],
argentWalletContract ? undefined : parsedAmounts[Field.CURRENCY_A],
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
)
const [approvalB, approveBCallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_B],
argentWalletContract ? undefined : parsedAmounts[Field.CURRENCY_B],
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
)

Expand Down Expand Up @@ -208,12 +212,36 @@ export default function AddLiquidity({
createPool: noLiquidity,
})

const txn = {
let txn: { to: string; data: string; value: string } = {
to: NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId],
data: calldata,
value,
}

if (argentWalletContract) {
const amountA = parsedAmounts[Field.CURRENCY_A]
const amountB = parsedAmounts[Field.CURRENCY_B]
const batch = [
...(amountA && amountA.currency.isToken
? [approveAmountCalldata(amountA, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId])]
: []),
...(amountB && amountB.currency.isToken
? [approveAmountCalldata(amountB, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId])]
: []),
{
to: txn.to,
data: txn.data,
value: txn.value,
},
]
const data = argentWalletContract.interface.encodeFunctionData('wc_multiCall', [batch])
txn = {
to: argentWalletContract.address,
data,
value: '0x0',
}
}

setAttemptingTxn(true)

library
Expand Down Expand Up @@ -244,6 +272,7 @@ export default function AddLiquidity({
})
})
.catch((error) => {
console.error('Failed to send transaction', error)
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
Expand Down Expand Up @@ -351,8 +380,10 @@ export default function AddLiquidity({
)

// we need an existence check on parsed amounts for single-asset deposits
const showApprovalA = approvalA !== ApprovalState.APPROVED && !!parsedAmounts[Field.CURRENCY_A]
const showApprovalB = approvalB !== ApprovalState.APPROVED && !!parsedAmounts[Field.CURRENCY_B]
const showApprovalA =
!argentWalletContract && approvalA !== ApprovalState.APPROVED && !!parsedAmounts[Field.CURRENCY_A]
const showApprovalB =
!argentWalletContract && approvalB !== ApprovalState.APPROVED && !!parsedAmounts[Field.CURRENCY_B]

return (
<ScrollablePage>
Expand Down Expand Up @@ -418,7 +449,7 @@ export default function AddLiquidity({
id="add-liquidity-input-tokena"
showCommonBases
/>
<div style={{ width: '12px' }}></div>
<div style={{ width: '12px' }} />

<CurrencyDropdown
value={formattedAmounts[Field.CURRENCY_B]}
Expand Down Expand Up @@ -571,7 +602,7 @@ export default function AddLiquidity({
<TYPE.body fontWeight={500} textAlign="center" fontSize={20}>
<HoverInlineText
maxCharacters={20}
text={invertPrice ? price.invert().toSignificant(5) : price.toSignificant(5)}
text={invertPrice ? price.invert().toSignificant(6) : price.toSignificant(6)}
/>{' '}
</TYPE.body>
<TYPE.main fontWeight={500} textAlign="center" fontSize={12}>
Expand Down Expand Up @@ -697,8 +728,8 @@ export default function AddLiquidity({
}}
disabled={
!isValid ||
(approvalA !== ApprovalState.APPROVED && !depositADisabled) ||
(approvalB !== ApprovalState.APPROVED && !depositBDisabled)
(!argentWalletContract && approvalA !== ApprovalState.APPROVED && !depositADisabled) ||
(!argentWalletContract && approvalB !== ApprovalState.APPROVED && !depositBDisabled)
}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Pool/PositionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function CurrentPriceCard({
<AutoColumn gap="8px" justify="center">
<ExtentsText>{t('Current price')}</ExtentsText>
<TYPE.mediumHeader textAlign="center">
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(5)}{' '}
{(inverted ? pool.token1Price : pool.token0Price).toSignificant(6)}{' '}
</TYPE.mediumHeader>
<ExtentsText>{currencyQuote?.symbol + ' per ' + currencyBase?.symbol}</ExtentsText>
</AutoColumn>
Expand Down
4 changes: 4 additions & 0 deletions src/pages/Swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useAppro
import { V3TradeState } from '../../hooks/useBestV3Trade'
import useENSAddress from '../../hooks/useENSAddress'
import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useSwapCallback } from '../../hooks/useSwapCallback'
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
Expand Down Expand Up @@ -302,9 +303,12 @@ export default function Swap({ history }: RouteComponentProps) {
)
}, [priceImpact, trade])

const isArgentWallet = useIsArgentWallet()

// show approve flow when: no error on inputs, not approved or pending, or approved in current session
// never show if price impact is above threshold in non expert mode
const showApproveFlow =
!isArgentWallet &&
!swapInputError &&
(approvalState === ApprovalState.NOT_APPROVED ||
approvalState === ApprovalState.PENDING ||
Expand Down
Loading

0 comments on commit 562b402

Please sign in to comment.