Skip to content

Commit

Permalink
feat(permit): load pre generated permit info (#3316)
Browse files Browse the repository at this point in the history
* chore: ignore nx cache folder

* feat: add usePreGeneratedPermitInfo

* feat: add usePreGeneratedPermitInfoForToken

* feat: add usePermitCompatibleTokens

* feat: check first whether token is permittable before checking locally

* feat: wire token compatibility onto token widget

* refactor: create a type for PermitCompatibleTokens

* chore: fix cosmos

* refactor: move SWR common settings into a const

* chore: remove stale TODO

* feat: use final url for PermitInfo on token-lists repo

* refactor: simplify conditional
  • Loading branch information
alfetopito authored Nov 3, 2023
1 parent e8c9ba9 commit 46943ad
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 10 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ yalc.lock
# vercel
.vercel

analyse.html
analyse.html

# NX
.nx
3 changes: 3 additions & 0 deletions apps/cowswap-frontend/src/modules/permit/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export const ORDER_TYPE_SUPPORTS_PERMIT: Record<TradeType, boolean> = {
}

export const PENDING_ORDER_PERMIT_CHECK_INTERVAL = ms`1min`

export const PRE_GENERATED_PERMIT_URL =
'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/PermitInfo'
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { TradeType } from 'modules/trade'

import { useIsPermitEnabled } from 'common/hooks/featureFlags/useIsPermitEnabled'

import { usePreGeneratedPermitInfoForToken } from './usePreGeneratedPermitInfoForToken'

import { ORDER_TYPE_SUPPORTS_PERMIT } from '../const'
import { addPermitInfoForTokenAtom, permittableTokensAtom } from '../state/permittableTokensAtom'
import { IsTokenPermittableResult } from '../types'
Expand Down Expand Up @@ -45,11 +47,23 @@ export function useIsTokenPermittable(

const addPermitInfo = useAddPermitInfo()
const permitInfo = usePermitInfo(chainId, isPermitEnabled ? lowerCaseAddress : undefined)
const { permitInfo: preGeneratedInfo, isLoading: preGeneratedIsLoading } = usePreGeneratedPermitInfoForToken(token)

const spender = GP_VAULT_RELAYER[chainId]

useEffect(() => {
if (!chainId || !isPermitEnabled || !lowerCaseAddress || !provider || permitInfo !== undefined || isNative) {
if (
!chainId ||
!isPermitEnabled ||
!lowerCaseAddress ||
!provider ||
permitInfo !== undefined ||
isNative ||
// Do not try to load when pre-generated info is loading
preGeneratedIsLoading ||
// Do not try to load when pre-generated exists
preGeneratedInfo !== undefined
) {
return
}

Expand All @@ -67,13 +81,25 @@ export function useIsTokenPermittable(
addPermitInfo({ chainId, tokenAddress: lowerCaseAddress, permitInfo: result })
}
})
}, [addPermitInfo, chainId, isNative, isPermitEnabled, lowerCaseAddress, permitInfo, provider, spender, tokenName])
}, [
addPermitInfo,
chainId,
isNative,
isPermitEnabled,
lowerCaseAddress,
permitInfo,
preGeneratedInfo,
preGeneratedIsLoading,
provider,
spender,
tokenName,
])

if (isNative) {
return false
}
// TODO: add an updater for this
return permitInfo

return preGeneratedInfo ?? permitInfo
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useAtomValue } from 'jotai'
import { useMemo, useRef } from 'react'

import { useWalletInfo } from '@cowprotocol/wallet'

import { usePreGeneratedPermitInfo } from './usePreGeneratedPermitInfo'

import { permittableTokensAtom } from '../state/permittableTokensAtom'
import { PermitCompatibleTokens } from '../types'

export function usePermitCompatibleTokens(): PermitCompatibleTokens {
const { chainId } = useWalletInfo()
const localPermitInfo = useAtomValue(permittableTokensAtom)[chainId]
const { allPermitInfo } = usePreGeneratedPermitInfo()

const stableRef = JSON.stringify(Object.keys(localPermitInfo).concat(Object.keys(allPermitInfo)))

const localPermitInfoRef = useRef(localPermitInfo)
localPermitInfoRef.current = localPermitInfo
const preGeneratedPermitInfoRef = useRef(allPermitInfo)
preGeneratedPermitInfoRef.current = allPermitInfo

return useMemo(() => {
const permitCompatibleTokens: PermitCompatibleTokens = {}

for (const address of Object.keys(preGeneratedPermitInfoRef.current)) {
permitCompatibleTokens[address.toLowerCase()] = !!preGeneratedPermitInfoRef.current[address]
}

for (const address of Object.keys(localPermitInfoRef.current)) {
permitCompatibleTokens[address.toLowerCase()] = !!localPermitInfoRef.current[address]
}

return permitCompatibleTokens
// Reducing unnecessary re-renders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stableRef])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const'
import { PermitInfo } from '@cowprotocol/permit-utils'
import { useWalletInfo } from '@cowprotocol/wallet'

import useSWR from 'swr'

import { PRE_GENERATED_PERMIT_URL } from '../const'

/**
* Fetch pre-generated permit info (stored in token-lists repo) for all tokens
*
* Caches result with SWR until a page refresh
*/
export function usePreGeneratedPermitInfo(): {
allPermitInfo: Record<string, PermitInfo>
isLoading: boolean
} {
const { chainId } = useWalletInfo()

const url = `${PRE_GENERATED_PERMIT_URL}.${chainId}.json`

const { data, isLoading } = useSWR(
url,
(url: string): Promise<Record<string, PermitInfo>> => fetch(url).then((r) => r.json()),
{ ...SWR_NO_REFRESH_OPTIONS, fallbackData: {} }
)

return { allPermitInfo: data, isLoading }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Currency } from '@uniswap/sdk-core'

import { Nullish } from 'types'

import { usePreGeneratedPermitInfo } from './usePreGeneratedPermitInfo'

import { IsTokenPermittableResult } from '../types'

/**
* Fetch pre-generated permit info for a single token
*/
export function usePreGeneratedPermitInfoForToken(token: Nullish<Currency>): {
permitInfo: IsTokenPermittableResult
isLoading: boolean
} {
const { allPermitInfo, isLoading } = usePreGeneratedPermitInfo()

const address = token && 'address' in token && token.address.toLowerCase()

const permitInfo = address ? allPermitInfo[address] : undefined

return {
permitInfo,
isLoading,
}
}
1 change: 1 addition & 0 deletions apps/cowswap-frontend/src/modules/permit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './hooks/useAccountAgnosticPermitHookData'
export * from './hooks/useGeneratePermitHook'
export * from './hooks/useIsTokenPermittable'
export * from './hooks/useOrdersPermitStatus'
export * from './hooks/usePermitCompatibleTokens'
export * from './types'
export * from './updaters/PendingPermitUpdater'
export * from './utils/handlePermit'
2 changes: 2 additions & 0 deletions apps/cowswap-frontend/src/modules/permit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ export type GetPermitCacheParams = PermitCacheKeyParams
export type CheckHasValidPendingPermit = (order: ParsedOrder) => Promise<boolean | undefined>

export type OrdersPermitStatus = Record<string, boolean | undefined>

export type PermitCompatibleTokens = Record<string, boolean>
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import { TokenWithLogo } from '@cowprotocol/common-const'
import {
ListState,
useAddList,
useAddUserToken,
useAllListsList,
useAllTokens,
useFavouriteTokens,
useAddUserToken,
useUserAddedTokens,
useUnsupportedTokens,
useUserAddedTokens,
} from '@cowprotocol/tokens'

import styled from 'styled-components/macro'

import { usePermitCompatibleTokens } from 'modules/permit'

import { CowModal } from 'common/pure/Modal'

import { useAllTokensBalances } from '../../hooks/useAllTokensBalances'
Expand Down Expand Up @@ -49,6 +51,7 @@ export function SelectTokenWidget() {
const allTokenLists = useAllListsList()
const [balances, balancesLoading] = useAllTokensBalances()
const unsupportedTokens = useUnsupportedTokens()
const permitCompatibleTokens = usePermitCompatibleTokens()

const closeTokenSelectWidget = useCallback(() => {
updateSelectTokenWidget({
Expand Down Expand Up @@ -129,6 +132,7 @@ export function SelectTokenWidget() {
allTokens={allTokens}
favouriteTokens={favouriteTokens}
balances={balances}
permitCompatibleTokens={permitCompatibleTokens}
balancesLoading={balancesLoading}
onSelectToken={onSelectToken}
onInputPressEnter={onInputPressEnter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const balances = allTokensMock.reduce<TokenAmounts>((acc, token) => {
}, {})

const defaultProps: SelectTokenModalProps = {
permitCompatibleTokens: {},
unsupportedTokens,
allTokens: allTokensMock,
favouriteTokens: favouriteTokensMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { UnsupportedTokensState } from '@cowprotocol/tokens'

import { Edit, X } from 'react-feather'

import { PermitCompatibleTokens } from 'modules/permit'

import * as styledEl from './styled'

import { TokenAmounts } from '../../../tokens'
Expand All @@ -21,15 +23,14 @@ export interface SelectTokenModalProps {
unsupportedTokens: UnsupportedTokensState
balancesLoading: boolean
selectedToken?: string
permitCompatibleTokens: PermitCompatibleTokens
onSelectToken(token: TokenWithLogo): void
onInputPressEnter?(): void
defaultInputValue?: string
onOpenManageWidget(): void
onDismiss(): void
}

const permitCompatibleTokens: { [tokenAddress: string]: boolean } = {} // TODO: Make dynamic

export function SelectTokenModal(props: SelectTokenModalProps) {
const {
defaultInputValue = '',
Expand All @@ -39,6 +40,7 @@ export function SelectTokenModal(props: SelectTokenModalProps) {
balances,
balancesLoading,
unsupportedTokens,
permitCompatibleTokens,
onSelectToken,
onDismiss,
onOpenManageWidget,
Expand Down
3 changes: 2 additions & 1 deletion apps/cowswap-frontend/src/modules/tokensList/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TokenWithLogo } from '@cowprotocol/common-const'

import { PermitCompatibleTokens } from 'modules/permit'
import { TokenAmounts } from 'modules/tokens'

export interface SelectTokenContext {
Expand All @@ -8,5 +9,5 @@ export interface SelectTokenContext {
selectedToken?: string
onSelectToken(token: TokenWithLogo): void
unsupportedTokens: { [tokenAddress: string]: { dateAdded: number } }
permitCompatibleTokens: { [tokenAddress: string]: boolean }
permitCompatibleTokens: PermitCompatibleTokens
}
7 changes: 7 additions & 0 deletions libs/common-const/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ export const SWR_OPTIONS = {
revalidateOnFocus: false,
}

export const SWR_NO_REFRESH_OPTIONS = {
// Cache indefinitely
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: 0,
}

// TODO: show banner warning when PINATA env vars are missing
export const COW_IPFS_OPTIONS: IpfsConfig = {
pinataApiKey: PINATA_API_KEY,
Expand Down

0 comments on commit 46943ad

Please sign in to comment.