Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ambire wallet batch transaction skipping swap gas estimation #777

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/features/trident/TridentApproveGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Button from 'app/components/Button'
import { ApprovalState, useApproveCallback } from 'app/hooks/useApproveCallback'
import useBentoMasterApproveCallback, { BentoApprovalState, BentoPermit } from 'app/hooks/useBentoMasterApproveCallback'
import { StandardSignatureData, useTridentLiquidityTokenPermit } from 'app/hooks/useERC20Permit'
import useIsAmbireWC from 'app/hooks/useIsAmbireWC'
import { useActiveWeb3React } from 'app/services/web3'
import { useWalletModalToggle } from 'app/state/application/hooks'
import React, { FC, memo, ReactNode, useCallback, useEffect, useState } from 'react'
Expand Down Expand Up @@ -127,6 +128,7 @@ const TridentApproveGate = ({
const toggleWalletModal = useWalletModalToggle()
const [status, setStatus] = useState<Record<string, ApprovalState>>({})
const [permitError, setPermitError] = useState(false)
const isAmbireWC = useIsAmbireWC()

const { approve, approvalState, getPermit, permit } = useBentoMasterApproveCallback(
withPermit ? masterContractAddress : undefined,
Expand All @@ -138,8 +140,9 @@ const TridentApproveGate = ({
(withPermit ? approvalState === BentoApprovalState.UNKNOWN : false)

const approved =
(Object.values(status).every((el) => el === ApprovalState.APPROVED) || !spendFromWallet) &&
(withPermit ? approvalState === BentoApprovalState.APPROVED : true)
((Object.values(status).every((el) => el === ApprovalState.APPROVED) || !spendFromWallet) &&
(withPermit ? approvalState === BentoApprovalState.APPROVED : true)) ||
isAmbireWC

// If we have a permitError, use the approveCallback as a fallback
const onClick = useCallback(async () => {
Expand All @@ -161,15 +164,16 @@ const TridentApproveGate = ({
<div className="flex flex-col gap-3">
{/*hide bentobox approval if not every inputAmount is greater than than zero*/}
{inputAmounts.every((el) => el?.greaterThan(ZERO)) &&
[BentoApprovalState.NOT_APPROVED, BentoApprovalState.PENDING].includes(approvalState) && (
<Button id="btn-approve" loading={approvalState === BentoApprovalState.PENDING} onClick={onClick}>
[BentoApprovalState.NOT_APPROVED, BentoApprovalState.PENDING].includes(approvalState) &&
!isAmbireWC && (
<Button loading={approvalState === BentoApprovalState.PENDING} id={`btn-approve`} onClick={onClick}>
{i18n._(t`Approve BentoBox`)}
</Button>
)}

{tokenApproveOn &&
inputAmounts.reduce<ReactNode[]>((acc, amount, index) => {
if (!amount?.currency.isNative && amount?.greaterThan(ZERO)) {
if (!amount?.currency.isNative && amount?.greaterThan(ZERO) && !isAmbireWC) {
acc.push(
<TokenApproveButton
id={`btn-approve`}
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/useIsAmbireWC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useActiveWeb3React } from 'app/services/web3'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'

export default function useIsAmbireWC(): boolean {
const res = useActiveWeb3React()
const wcConnector = res?.connector as WalletConnectConnector
return wcConnector?.walletConnectProvider?.signer?.connection?.wc?._peerMeta?.name === 'Ambire Wallet'
}
161 changes: 148 additions & 13 deletions src/hooks/useSwapCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import { BigNumber } from '@ethersproject/bignumber'
import { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber'
import { arrayify, DataOptions, hexlify, Signature, SignatureLike, splitSignature } from '@ethersproject/bytes'
import { AddressZero } from '@ethersproject/constants'
import { HashZero } from '@ethersproject/constants/src.ts/hashes'
import { t } from '@lingui/macro'
import {
ChainId,
Currency,
CurrencyAmount,
MaxUint256,
Percent,
Router as LegacyRouter,
SwapParameters,
toHex,
Token,
Trade as LegacyTrade,
TradeType,
} from '@sushiswap/core-sdk'
Expand All @@ -31,6 +34,7 @@ import {
RouteType,
Trade as TridentTrade,
} from '@sushiswap/trident-sdk'
import WalletConnectProvider from '@walletconnect/ethereum-provider'
import { EIP_1559_ACTIVATION_BLOCK } from 'app/constants'
import { Feature } from 'app/enums'
import { approveMasterContractAction, batchAction, unwrapWETHAction } from 'app/features/trident/actions'
Expand All @@ -39,6 +43,8 @@ import approveAmountCalldata from 'app/functions/approveAmountCalldata'
import { shortenAddress } from 'app/functions/format'
import { calculateGasMargin } from 'app/functions/trade'
import { isAddress, isZero } from 'app/functions/validate'
import { ApprovalState, useApproveCallback } from 'app/hooks/useApproveCallback'
import useBentoMasterApproveCallback, { BentoApprovalState } from 'app/hooks/useBentoMasterApproveCallback'
import { useBentoRebase } from 'app/hooks/useBentoRebases'
import useBlockNumber from 'app/lib/hooks/useBlockNumber'
import { useActiveWeb3React } from 'app/services/web3'
Expand All @@ -53,9 +59,10 @@ import { useMemo } from 'react'

import { SUSHIGUARD_RELAY } from '../config/sushiguard'
import { useArgentWalletContract } from './useArgentWalletContract'
import { useRouterContract, useTridentRouterContract } from './useContract'
import { useBentoBoxContract, useRouterContract, useTokenContract, useTridentRouterContract } from './useContract'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'
import useIsAmbireWC from './useIsAmbireWC'
import useTransactionDeadline from './useTransactionDeadline'

export enum SwapCallbackState {
Expand All @@ -68,6 +75,8 @@ interface SwapCall {
address: string
calldata: string
value: string
skipGasEstimation?: boolean
extra?: any
}

interface SwapCallEstimate {
Expand Down Expand Up @@ -349,8 +358,17 @@ export function useSwapCallArguments(

const tridentRouterContract = useTridentRouterContract()

const isAmbireWC = useIsAmbireWC()
const argentWalletContract = useArgentWalletContract()

const { approvalState: bentoBoxApprovalState } = useBentoMasterApproveCallback(tridentRouterContract?.address, {})
const bentoBoxContract = useBentoBoxContract()
const spender = trade instanceof LegacyTrade ? legacyRouterContract?.address : bentoBoxContract?.address
const [approvalState] = useApproveCallback(trade?.inputAmount?.wrapped, spender)

const tokenAddress = trade?.inputAmount.currency.isToken ? (trade?.inputAmount.currency as Token)?.address : undefined
const tokenContract = useTokenContract(tokenAddress)

const { rebase } = useBentoRebase(trade?.inputAmount.currency)

return useMemo<SwapCall[]>(() => {
Expand Down Expand Up @@ -391,7 +409,31 @@ export function useSwapCallArguments(
}

result = swapMethods.map(({ methodName, args, value }) => {
if (argentWalletContract && trade.inputAmount.currency.isToken) {
if (isAmbireWC && tokenContract && approvalState === ApprovalState.NOT_APPROVED) {
let batchTxs: any[] = []

const approveData = tokenContract?.interface?.encodeFunctionData('approve', [
legacyRouterContract.address,
MaxUint256.toString(),
])
batchTxs.push({
to: tokenContract?.address,
data: approveData,
})

batchTxs.push({
to: legacyRouterContract.address,
data: legacyRouterContract.interface.encodeFunctionData(methodName, args),
})

return {
address: AddressZero,
calldata: '0x0',
value: '0x0',
skipGasEstimation: true,
extra: batchTxs,
}
} else if (argentWalletContract && trade.inputAmount.currency.isToken) {
return {
address: argentWalletContract.address,
calldata: argentWalletContract.interface.encodeFunctionData('wc_multiCall', [
Expand Down Expand Up @@ -459,14 +501,66 @@ export function useSwapCallArguments(
})
)

result.push({
address: tridentRouterContract.address,
calldata: batchAction({
contract: tridentRouterContract,
actions,
}),
value,
} as SwapCall)
if (
isAmbireWC &&
((tokenContract && approvalState === ApprovalState.NOT_APPROVED) ||
bentoBoxApprovalState === BentoApprovalState.NOT_APPROVED)
) {
let batchTxs: any[] = []

if (tokenContract && approvalState === ApprovalState.NOT_APPROVED) {
batchTxs.push({
to: tokenContract?.address,
data: tokenContract?.interface?.encodeFunctionData('approve', [
bentoBoxContract?.address,
MaxUint256.toString(),
]),
})
}

if (bentoBoxApprovalState === BentoApprovalState.NOT_APPROVED) {
batchTxs.push({
to: bentoBoxContract?.address,
data: bentoBoxContract?.interface?.encodeFunctionData('setMasterContractApproval', [
account,
tridentRouterContract.address,
true,
0,
HashZero,
HashZero,
]),
})
}

batchTxs = [
...batchTxs,
...actions
.filter((a) => a)
.map((a) => ({
to: tridentRouterContract.address,
data: a,
})),
]

return [
{
address: AddressZero,
calldata: '0x0',
value: '0x0',
skipGasEstimation: true,
extra: batchTxs,
},
]
} else {
result.push({
address: tridentRouterContract.address,
calldata: batchAction({
contract: tridentRouterContract,
actions,
}),
value,
} as SwapCall)
}

return result
}
Expand All @@ -475,13 +569,18 @@ export function useSwapCallArguments(
}, [
account,
allowedSlippage,
approvalState,
argentWalletContract,
bentoBoxApprovalState,
bentoBoxContract,
chainId,
deadline,
isAmbireWC,
legacyRouterContract,
library,
rebase,
recipient,
tokenContract,
trade,
tridentRouterContract,
tridentTradeContext,
Expand Down Expand Up @@ -570,6 +669,17 @@ export function useSwapCallback(

const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipient, signatureData, tridentTradeContext)

const bentoBoxContract = useBentoBoxContract()
const isAmbireWC = useIsAmbireWC()

const legacyRouterContract = useRouterContract()
const tridentRouterContract = useTridentRouterContract()

const { approvalState: bentoBoxApprovalState } = useBentoMasterApproveCallback(tridentRouterContract?.address, {})

const spender = trade instanceof LegacyTrade ? legacyRouterContract?.address : bentoBoxContract?.address
const [approvalState] = useApproveCallback(trade?.inputAmount?.wrapped, spender)

const addTransaction = useTransactionAdder()

return useMemo(() => {
Expand Down Expand Up @@ -602,7 +712,9 @@ export function useSwapCallback(
console.log('onSwap callback')
const estimatedCalls: SwapCallEstimate[] = await Promise.all(
swapCalls.map((call) => {
const { address, calldata, value } = call
const { address, calldata, value, skipGasEstimation } = call

if (skipGasEstimation) return { call }

const tx =
!value || isZero(value)
Expand Down Expand Up @@ -666,7 +778,7 @@ export function useSwapCallback(
}

const {
call: { address, calldata, value },
call: { address, calldata, value, extra },
} = bestCallOption

console.log('gasEstimate' in bestCallOption ? { gasLimit: calculateGasMargin(bestCallOption.gasEstimate) } : {})
Expand All @@ -683,8 +795,28 @@ export function useSwapCallback(

let privateTx = false
let txResponse: Promise<TransactionResponseLight>

if (!useSushiGuard) {
txResponse = library.getSigner().sendTransaction(txParams)
if (
isAmbireWC &&
(approvalState === ApprovalState.NOT_APPROVED ||
(trade instanceof TridentTrade && bentoBoxApprovalState === BentoApprovalState.NOT_APPROVED))
) {
const wcProvider = library.provider as WalletConnectProvider
txResponse = new Promise<TransactionResponseLight>((resolve, reject) => {
wcProvider.connector
.sendCustomRequest({
method: 'ambire_sendBatchTransaction',
params: extra,
})
.then((res: any) => {
resolve({ hash: res } as TransactionResponseLight)
})
.catch(reject)
})
} else {
txResponse = library.getSigner().sendTransaction(txParams)
}
} else {
// Set flag for transaction adder
privateTx = true
Expand Down Expand Up @@ -821,6 +953,9 @@ export function useSwapCallback(
error: null,
}
}, [
approvalState,
bentoBoxApprovalState,
isAmbireWC,
trade,
library,
account,
Expand Down
3 changes: 3 additions & 0 deletions src/pages/legacy/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { computeFiatValuePriceImpact } from 'app/functions/trade'
import { useAllTokens, useCurrency } from 'app/hooks/Tokens'
import { ApprovalState, useApproveCallbackFromTrade } from 'app/hooks/useApproveCallback'
import useENSAddress from 'app/hooks/useENSAddress'
import useIsAmbireWC from 'app/hooks/useIsAmbireWC'
import useIsArgentWallet from 'app/hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from 'app/hooks/useIsSwapUnsupported'
import useSushiGuardFeature from 'app/hooks/useSushiGuardFeature'
Expand Down Expand Up @@ -272,11 +273,13 @@ const Swap = ({ banners }: SwapProps) => {
)
}, [priceImpact, trade])

const isAmbireWC = useIsAmbireWC()
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 =
!isAmbireWC &&
!isArgentWallet &&
!swapInputError &&
(approvalState === ApprovalState.NOT_APPROVED ||
Expand Down