Skip to content

Commit

Permalink
feat: integrate zksync paymaster
Browse files Browse the repository at this point in the history
  • Loading branch information
viet-nv committed Mar 14, 2024
1 parent 9f5989a commit 54e888c
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 30 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@blocto/web3-react-connector": "^1.0.0",
"@coinbase/wallet-sdk": "^3.0.4",
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@holdstation/paymaster-helper": "^2.0.20",
"@kybernetwork/oauth2": "1.0.1",
"@kyberswap/krystal-walletconnect-v2": "0.0.1",
"@kyberswap/ks-sdk-classic": "^1.0.3",
Expand Down
27 changes: 25 additions & 2 deletions src/components/SwapForm/SlippageSettingGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import { useMedia } from 'react-use'
import { Flex, Text } from 'rebass'
import styled from 'styled-components'

import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg'
import { Shield } from 'components/Icons'
import SlippageSetting from 'components/SwapForm/SlippageSetting'
import { MouseoverTooltip, TextDashed } from 'components/Tooltip'
import { APP_PATHS } from 'constants/index'
import { useActiveWeb3React } from 'hooks'
import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel'
import useTheme from 'hooks/useTheme'
import { usePaymentToken, useSlippageSettingByPage } from 'state/user/hooks'
import { MEDIA_WIDTHS } from 'theme'

import AddMEVProtectionModal from './AddMEVProtectionModal'
Expand All @@ -31,9 +34,11 @@ const PriceAlertButton = styled.div`
export default function SlippageSettingGroup({
isStablePairSwap,
isWrapOrUnwrap,
onOpenGasToken,
}: {
isStablePairSwap: boolean
isWrapOrUnwrap: boolean
onOpenGasToken?: () => void
}) {
const upToXXSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`)
const theme = useTheme()
Expand All @@ -50,8 +55,10 @@ export default function SlippageSettingGroup({
setShowMevModal(false)
}, [])

const [paymentToken] = usePaymentToken()
const { isSlippageControlPinned } = useSlippageSettingByPage()
const isPartnerSwap = window.location.pathname.startsWith(APP_PATHS.PARTNER_SWAP)
const rightButton =
let rightButton =
chainId === ChainId.MAINNET && wallet.isConnected && !isPartnerSwap ? (
<PriceAlertButton onClick={addMevProtectionHandler}>
<Shield size={14} color={theme.subText} />
Expand All @@ -61,9 +68,25 @@ export default function SlippageSettingGroup({
</PriceAlertButton>
) : null

if (chainId === ChainId.ZKSYNC && !isPartnerSwap) {
rightButton = (
<Flex alignItems="center" width="fit-content" role="button" sx={{ cursor: 'pointer' }} onClick={onOpenGasToken}>
<MouseoverTooltip text="You can pay network fees in tokens other than Ether">
<TextDashed>
<Trans>Gas Token</Trans>
</TextDashed>
</MouseoverTooltip>
<Text fontWeight="500" marginLeft="6px" color={theme.text}>
{paymentToken ? paymentToken.symbol : 'ETH'}
</Text>
<DropdownSVG />
</Flex>
)
}

return (
<Flex alignItems="flex-start" fontSize={12} color={theme.subText} justifyContent="space-between">
{isWrapOrUnwrap ? (
{isWrapOrUnwrap || !isSlippageControlPinned ? (
<>
<div />
{rightButton}
Expand Down
9 changes: 7 additions & 2 deletions src/components/SwapForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export type SwapFormProps = {

onChangeCurrencyIn: (c: Currency) => void
onChangeCurrencyOut: (c: Currency) => void
goToSettingsView: () => void
customChainId?: ChainId
omniView?: boolean
onOpenGasToken?: () => void
}

const SwapForm: React.FC<SwapFormProps> = props => {
Expand All @@ -106,6 +106,7 @@ const SwapForm: React.FC<SwapFormProps> = props => {
onChangeCurrencyOut,
customChainId,
omniView,
onOpenGasToken,
} = props

const { chainId: walletChainId } = useActiveWeb3React()
Expand Down Expand Up @@ -262,7 +263,11 @@ const SwapForm: React.FC<SwapFormProps> = props => {
{isDegenMode && !isWrapOrUnwrap && (
<AddressInputPanel id="recipient" value={recipient} onChange={setRecipient} />
)}
<SlippageSettingGroup isWrapOrUnwrap={isWrapOrUnwrap} isStablePairSwap={isStablePairSwap} />
<SlippageSettingGroup
isWrapOrUnwrap={isWrapOrUnwrap}
isStablePairSwap={isStablePairSwap}
onOpenGasToken={onOpenGasToken}
/>
<FeeControlGroup />
</Flex>
</Wrapper>
Expand Down
143 changes: 143 additions & 0 deletions src/components/swapv2/GasTokenSetting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { ChainId, Token } from '@kyberswap/ks-sdk-core'
import { Trans } from '@lingui/macro'
import { rgba } from 'polished'
import { ArrowLeft } from 'react-feather'
import { Box, Flex, Text } from 'rebass'

import CurrencyLogo from 'components/CurrencyLogo'
import Divider from 'components/Divider'
import { NativeCurrencies } from 'constants/tokens'
import useTheme from 'hooks/useTheme'
import { usePaymentToken } from 'state/user/hooks'
import { useCurrencyBalances, useNativeBalance } from 'state/wallet/hooks'

const tokens = [
new Token(ChainId.ZKSYNC, '0xed4040fd47629e7c8fbb7da76bb50b3e7695f0f2', 18, 'HOLD', 'HOLD'),
new Token(ChainId.ZKSYNC, '0x493257fd37edb34451f62edf8d2a0c418852ba4c', 6, 'USDT', 'USDT'),
new Token(ChainId.ZKSYNC, '0x3355df6d4c9c3035724fd0e3914de96a5a83aaf4', 6, 'USDC', 'USDC'),
new Token(ChainId.ZKSYNC, '0xbbeb516fb02a01611cbbe0453fe3c580d7281011', 8, 'wBTC', 'wBTC'),
]

export default function GasTokenSetting({ onBack }: { onBack: () => void }) {
const theme = useTheme()
const ethBalance = useNativeBalance()
const balances = useCurrencyBalances(tokens)

const [paymentToken, setPaymentToken] = usePaymentToken()

return (
<>
<Flex sx={{ gap: '6px', cursor: 'pointer' }} alignItems="center" role="button" onClick={onBack}>
<ArrowLeft size="24px" color={theme.subText} />
<Text fontSize="20px" fontWeight="500">
Gas Token
</Text>
</Flex>

<Flex
color={theme.subText}
justifyContent="space-between"
fontSize={14}
marginTop="0.75rem"
marginBottom="0.5rem"
>
<Text>
<Trans>Token</Trans>
</Text>
<Text>
<Trans>Est. Gas Fee</Trans>
</Text>
</Flex>

<Divider />

<Flex
justifyContent="space-between"
marginY="0.5rem"
role="button"
onClick={() => {
setPaymentToken(null)
onBack()
}}
sx={{
cursor: 'pointer',
}}
>
<Flex alignItems="center" sx={{ gap: '6px' }}>
<CurrencyLogo currency={NativeCurrencies[ChainId.ZKSYNC]} size="24px" />
<div>
<Text fontSize={16}>ETH</Text>
<Text fontSize={14} color={theme.subText}>
{ethBalance?.toSignificant(6)} ETH
</Text>
</div>
</Flex>
</Flex>

<Divider />

<Flex marginTop="0.75rem" alignItems="center" sx={{ gap: '8px' }}>
<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="EvStationRoundedIcon" width="24px">
<path
d="m19.77 7.23.01-.01-3.19-3.19c-.29-.29-.77-.29-1.06 0-.29.29-.29.77 0 1.06l1.58 1.58c-1.05.4-1.76 1.47-1.58 2.71.16 1.1 1.1 1.99 2.2 2.11.47.05.88-.03 1.27-.2v7.21c0 .55-.45 1-1 1s-1-.45-1-1V14c0-1.1-.9-2-2-2h-1V5c0-1.1-.9-2-2-2H6c-1.1 0-2 .9-2 2v15c0 .55.45 1 1 1h8c.55 0 1-.45 1-1v-6.5h1.5v4.86c0 1.31.94 2.5 2.24 2.63 1.5.15 2.76-1.02 2.76-2.49V9c0-.69-.28-1.32-.73-1.77zM18 10c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zM8 16.12V13.5H6.83c-.38 0-.62-.4-.44-.74l2.67-5c.24-.45.94-.28.94.24v3h1.14c.38 0 .62.41.43.75l-2.64 4.62c-.25.44-.93.26-.93-.25z"
fill="white"
/>
</svg>

<Text fontSize={14} fontWeight="500">
Paymaster
</Text>
</Flex>
<Text fontSize={14} color={theme.subText} marginTop="4px" marginBottom="1rem">
<Trans>Pay network fees in the token of your choice.</Trans>
</Text>

{tokens.map((item, index) => (
<Flex
justifyContent="space-between"
key={item.address}
role="button"
onClick={() => {
setPaymentToken(item)
onBack()
}}
padding="8px 16px"
marginX="-1rem"
sx={{
cursor: 'pointer',
background: paymentToken?.address === item.address ? rgba(theme.primary, 0.15) : 'transparent',
'&:hover': {
background: theme.buttonBlack,
},
}}
>
<Flex alignItems="center" sx={{ gap: '6px' }}>
<CurrencyLogo currency={item} size="24px" />
<div>
<Flex alignItems="center" sx={{ gap: '6px' }}>
<Text fontSize={16}>{item.symbol}</Text>
{index === 0 && (
<Box
sx={{
borderRadius: '999px',
background: rgba(theme.primary, 0.2),
color: theme.primary,
fontSize: '12px',
fontWeight: 500,
padding: '4px 8px',
}}
>
20% OFF
</Box>
)}
</Flex>
<Text fontSize={14} color={theme.subText}>
{balances[index]?.toSignificant(6) || '0'} {item.symbol}
</Text>
</div>
</Flex>
</Flex>
))}
</>
)
}
53 changes: 46 additions & 7 deletions src/hooks/useApproveCallback.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { MaxUint256 } from '@ethersproject/constants'
import { Currency, CurrencyAmount, TokenAmount } from '@kyberswap/ks-sdk-core'
import { t } from '@lingui/macro'
import { Interface } from 'ethers/lib/utils'
import JSBI from 'jsbi'
import { useCallback, useMemo } from 'react'

import { NotificationType } from 'components/Announcement/type'
import ERC20_ABI from 'constants/abis/erc20.json'
import { useTokenAllowance } from 'data/Allowances'
import { useNotify } from 'state/application/hooks'
import { Field } from 'state/swap/actions'
import { useHasPendingApproval, useTransactionAdder } from 'state/transactions/hooks'
import { TRANSACTION_TYPE } from 'state/transactions/type'
import { usePaymentToken } from 'state/user/hooks'
import { calculateGasMargin } from 'utils'
import { Aggregator } from 'utils/aggregator'
import { friendlyError } from 'utils/errorMessage'
import { computeSlippageAdjustedAmounts } from 'utils/prices'
import { paymasterExecute } from 'utils/sendTransaction'

import { useActiveWeb3React } from './index'
import { useTokenSigningContract } from './useContract'

const ERC20Interface = new Interface(ERC20_ABI)

export enum ApprovalState {
UNKNOWN = 'UNKNOWN',
NOT_APPROVED = 'NOT_APPROVED',
Expand Down Expand Up @@ -61,6 +67,7 @@ export function useApproveCallback(

const tokenContract = useTokenSigningContract(token?.address)
const addTransactionWithType = useTransactionAdder()
const [paymentToken] = usePaymentToken()

const approve = useCallback(
async (customAmount?: CurrencyAmount<Currency>): Promise<void> => {
Expand Down Expand Up @@ -105,15 +112,36 @@ export function useApproveCallback(
approvedAmount = amountToApprove.quotient.toString()
} catch {
estimatedGas = await tokenContract.estimateGas.approve(spender, '0')
return tokenContract.approve(spender, '0', {
gasLimit: calculateGasMargin(estimatedGas),
})
return paymentToken?.address
? paymasterExecute(
paymentToken.address,
{
from: account,
to: token.address,
data: ERC20Interface.encodeFunctionData('approve', [spender, '0']),
},
calculateGasMargin(estimatedGas).toNumber(),
)
: tokenContract.approve(spender, '0', {
gasLimit: calculateGasMargin(estimatedGas),
})
}
}

const response = await tokenContract.approve(spender, approvedAmount, {
gasLimit: calculateGasMargin(estimatedGas),
})
const gasLimit = calculateGasMargin(estimatedGas)
const response = await (paymentToken?.address
? paymasterExecute(
paymentToken.address,
{
from: account,
to: token.address,
data: ERC20Interface.encodeFunctionData('approve', [spender, approvedAmount]),
},
gasLimit.toNumber(),
)
: tokenContract.approve(spender, approvedAmount, {
gasLimit: calculateGasMargin(estimatedGas),
}))
addTransactionWithType({
hash: response.hash,
type: TRANSACTION_TYPE.APPROVE,
Expand All @@ -136,7 +164,18 @@ export function useApproveCallback(
)
}
},
[approvalState, token, tokenContract, amountToApprove, spender, addTransactionWithType, forceApprove, notify],
[
account,
approvalState,
token,
tokenContract,
amountToApprove,
spender,
addTransactionWithType,
forceApprove,
notify,
paymentToken?.address,
],
)

return [approvalState, approve, currentAllowance]
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/useSwapCallbackV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useActiveWeb3React, useWeb3React } from 'hooks/index'
import useENS from 'hooks/useENS'
import { useTransactionAdder } from 'state/transactions/hooks'
import { TRANSACTION_TYPE, TransactionExtraInfo2Token } from 'state/transactions/type'
import { useUserSlippageTolerance } from 'state/user/hooks'
import { usePaymentToken, useUserSlippageTolerance } from 'state/user/hooks'
import { ChargeFeeBy } from 'types/route'
import { isAddress, shortenAddress } from 'utils'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
Expand Down Expand Up @@ -113,6 +113,8 @@ const useSwapCallbackV3 = (isPermitSwap?: boolean) => {
[addTransactionWithType, getSwapData],
)

const [paymentToken] = usePaymentToken()

const swapCallbackForEVM = useCallback(
async (routerAddress: string | undefined, encodedSwapData: string | undefined) => {
if (!account || !inputAmount || !routerAddress || !encodedSwapData) {
Expand All @@ -130,12 +132,13 @@ const useSwapCallbackV3 = (isPermitSwap?: boolean) => {
name: ErrorName.SwapError,
wallet: walletKey,
},
paymentToken: paymentToken?.address,
})
if (response?.hash === undefined) throw new Error('sendTransaction returned undefined.')
handleSwapResponse(response)
return response?.hash
},
[account, handleSwapResponse, inputAmount, library, walletKey],
[account, handleSwapResponse, inputAmount, library, walletKey, paymentToken?.address],
)

return swapCallbackForEVM
Expand Down
Loading

0 comments on commit 54e888c

Please sign in to comment.