-
Notifications
You must be signed in to change notification settings - Fork 168
/
usePermitAllowance.ts
106 lines (91 loc) · 4.13 KB
/
usePermitAllowance.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { t } from '@lingui/macro'
import { signTypedData } from '@uniswap/conedison/provider/signing'
import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import PERMIT2_ABI from 'abis/permit2.json'
import { Permit2 } from 'abis/types'
import { UserRejectedRequestError, WidgetError, WidgetPromise } from 'errors'
import { useSingleCallResult } from 'hooks/multicall'
import { useContract } from 'hooks/useContract'
import ms from 'ms.macro'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { isUserRejection } from 'utils/jsonRpcError'
import { usePerfEventHandler } from './usePerfEventHandler'
const PERMIT_EXPIRATION = ms`30d`
const PERMIT_SIG_EXPIRATION = ms`30m`
function toDeadline(expiration: number): number {
return Math.floor((Date.now() + expiration) / 1000)
}
export function usePermitAllowance(token?: Token, owner?: string, spender?: string) {
const contract = useContract<Permit2>(PERMIT2_ADDRESS, PERMIT2_ABI)
const inputs = useMemo(() => [owner, token?.address, spender], [owner, spender, token?.address])
// If there is no allowance yet, re-check next observed block.
// This guarantees that the permitAllowance is synced upon submission and updated upon being synced.
const [blocksPerFetch, setBlocksPerFetch] = useState<1>()
const result = useSingleCallResult(contract, 'allowance', inputs, {
blocksPerFetch,
}).result as Awaited<ReturnType<Permit2['allowance']>> | undefined
const rawAmount = result?.amount.toString() // convert to a string before using in a hook, to avoid spurious rerenders
const allowance = useMemo(
() => (token && rawAmount ? CurrencyAmount.fromRawAmount(token, rawAmount) : undefined),
[token, rawAmount]
)
useEffect(() => setBlocksPerFetch(allowance?.equalTo(0) ? 1 : undefined), [allowance])
return useMemo(
() => ({ permitAllowance: allowance, expiration: result?.expiration, nonce: result?.nonce }),
[allowance, result?.expiration, result?.nonce]
)
}
interface Permit extends PermitSingle {
sigDeadline: number
}
export interface PermitSignature extends Permit {
signature: string
}
export function useUpdatePermitAllowance(
token: Token | undefined,
spender: string | undefined,
nonce: number | undefined,
onPermitSignature: (signature: PermitSignature) => void
) {
const { account, chainId, provider } = useWeb3React()
const updatePermitAllowance = useCallback(
() =>
WidgetPromise.from(
async () => {
if (!chainId) throw new Error('missing chainId')
if (!provider) throw new Error('missing provider')
if (!token) throw new Error('missing token')
if (!spender) throw new Error('missing spender')
if (nonce === undefined) throw new Error('missing nonce')
const permit: Permit = {
details: {
token: token.address,
amount: MaxAllowanceTransferAmount,
expiration: toDeadline(PERMIT_EXPIRATION),
nonce,
},
spender,
sigDeadline: toDeadline(PERMIT_SIG_EXPIRATION),
}
const { domain, types, values } = AllowanceTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)
// Use conedison's signTypedData for better x-wallet compatibility.
const signature = await signTypedData(provider.getSigner(account), domain, types, values)
onPermitSignature?.({ ...permit, signature })
},
null,
(error) => {
if (isUserRejection(error)) throw new UserRejectedRequestError()
const symbol = token?.symbol ?? 'Token'
throw new WidgetError({
message: t`${symbol} permit allowance failed: ${(error as any)?.message ?? error}`,
error,
})
}
),
[account, chainId, nonce, onPermitSignature, provider, spender, token]
)
const args = useMemo(() => (token && spender ? { token, spender } : undefined), [spender, token])
return usePerfEventHandler('onPermit2Allowance', args, updatePermitAllowance)
}