-
-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: extract event getting code to its own function / file
- Merge all event getting code into a single function rather than multiple dependent hooks - Add start of chain-specific event getting - Small misc fixes
- Loading branch information
Showing
13 changed files
with
156 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { ERC721_ABI, PERMIT2_ABI } from 'lib/abis'; | ||
import eventsDB from 'lib/databases/events'; | ||
import { sortTokenEventsChronologically } from 'lib/utils'; | ||
import { isNullish } from 'lib/utils'; | ||
import { addressToTopic, apiLogin } from 'lib/utils'; | ||
import { type DocumentedChainId, getChainConfig } from 'lib/utils/chains'; | ||
import { parseApprovalForAllLog, parseApprovalLog, parsePermit2Log, parseTransferLog } from 'lib/utils/events'; | ||
import { type TokenEvent, generatePatchedAllowanceEvents } from 'lib/utils/events'; | ||
import { getOpenSeaProxyAddress } from 'lib/utils/whois'; | ||
import { type Address, getAbiItem, toEventSelector } from 'viem'; | ||
|
||
// Note: ideally I would have included this in the 'Chain' class, but this causes circular dependency issues nd issues with Edge runtime | ||
// So we use this separate file instead to configure token event getting per chain. | ||
|
||
export const getTokenEvents = async (chainId: DocumentedChainId, address: Address): Promise<TokenEvent[]> => { | ||
const override = ChainOverrides[chainId]; | ||
if (override) return override(chainId, address); | ||
return getTokenEventsDefault(chainId, address); | ||
}; | ||
|
||
type TokenEventsGetter = (chainId: DocumentedChainId, address: Address) => Promise<TokenEvent[]>; | ||
|
||
const ChainOverrides: Record<number, TokenEventsGetter> = {}; | ||
|
||
const getTokenEventsDefault = async (chainId: DocumentedChainId, address: Address): Promise<TokenEvent[]> => { | ||
// Assemble prerequisites | ||
|
||
const chain = getChainConfig(chainId); | ||
const publicClient = chain.createViemPublicClient(); | ||
const logsProvider = chain.getLogsProvider(); | ||
|
||
const [openSeaProxyAddress, fromBlock, toBlock, isLoggedIn] = await Promise.all([ | ||
getOpenSeaProxyAddress(address), | ||
0, | ||
publicClient.getBlockNumber().then((blockNumber) => Number(blockNumber)), | ||
apiLogin(), | ||
]); | ||
|
||
if (!isLoggedIn) { | ||
throw new Error('Failed to create an API session'); | ||
} | ||
|
||
// Create required event filters | ||
|
||
const getErc721EventSelector = (eventName: 'Transfer' | 'Approval' | 'ApprovalForAll') => { | ||
return toEventSelector(getAbiItem({ abi: ERC721_ABI, name: eventName })); | ||
}; | ||
|
||
const getPermit2EventSelector = (eventName: 'Permit' | 'Approval' | 'Lockdown') => { | ||
return toEventSelector(getAbiItem({ abi: PERMIT2_ABI, name: eventName })); | ||
}; | ||
|
||
const addressTopic = addressToTopic(address); | ||
|
||
const transferToFilter = { topics: [getErc721EventSelector('Transfer'), null, addressTopic], fromBlock, toBlock }; | ||
const transferFromFilter = { topics: [getErc721EventSelector('Transfer'), addressTopic], fromBlock, toBlock }; | ||
const approvalFilter = { topics: [getErc721EventSelector('Approval'), addressTopic], fromBlock, toBlock }; | ||
const approvalForAllFilter = { | ||
topics: [getErc721EventSelector('ApprovalForAll'), addressTopic], | ||
fromBlock, | ||
toBlock, | ||
}; | ||
|
||
const permit2ApprovalFilter = { topics: [getPermit2EventSelector('Approval'), addressTopic], fromBlock, toBlock }; | ||
const permit2PermitFilter = { topics: [getPermit2EventSelector('Permit'), addressTopic], fromBlock, toBlock }; | ||
const permit2LockdownFilter = { topics: [getPermit2EventSelector('Lockdown'), addressTopic], fromBlock, toBlock }; | ||
|
||
// Fetch events | ||
const [transferTo, transferFrom, approval, approvalForAllUnpatched, permit2Approval, permit2Permit, permit2Lockdown] = | ||
await Promise.all([ | ||
eventsDB.getLogs(logsProvider, transferToFilter, chainId, 'Transfer (to)'), | ||
eventsDB.getLogs(logsProvider, transferFromFilter, chainId, 'Transfer (from)'), | ||
eventsDB.getLogs(logsProvider, approvalFilter, chainId, 'Approval'), | ||
eventsDB.getLogs(logsProvider, approvalForAllFilter, chainId, 'ApprovalForAll'), | ||
eventsDB.getLogs(logsProvider, permit2ApprovalFilter, chainId, 'Permit2 Approval'), | ||
eventsDB.getLogs(logsProvider, permit2PermitFilter, chainId, 'Permit2 Permit'), | ||
eventsDB.getLogs(logsProvider, permit2LockdownFilter, chainId, 'Permit2 Lockdown'), | ||
]); | ||
|
||
// Manually patch the ApprovalForAll events | ||
const approvalForAll = [ | ||
...approvalForAllUnpatched, | ||
...generatePatchedAllowanceEvents(address, openSeaProxyAddress ?? undefined, [ | ||
...approval, | ||
...approvalForAllUnpatched, | ||
...transferFrom, | ||
...transferTo, | ||
]), | ||
]; | ||
|
||
// Parse events. We put ApprovalForAll first to ensure that incorrect ERC721 contracts like CryptoStrikers are handled correctly | ||
const parsedEvents = [ | ||
...approvalForAll.map((log) => parseApprovalForAllLog(log, chainId)), | ||
...approval.map((log) => parseApprovalLog(log, chainId)), | ||
...permit2Approval.map((log) => parsePermit2Log(log, chainId)), | ||
...permit2Permit.map((log) => parsePermit2Log(log, chainId)), | ||
...permit2Lockdown.map((log) => parsePermit2Log(log, chainId)), | ||
...transferFrom.map((log) => parseTransferLog(log, chainId, address)), | ||
...transferTo.map((log) => parseTransferLog(log, chainId, address)), | ||
]; | ||
|
||
// We sort the events in reverse chronological order to ensure that the most recent events are processed first | ||
return sortTokenEventsChronologically(parsedEvents.filter((event) => !isNullish(event))).reverse(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,14 @@ | ||
import { ERC721_ABI } from 'lib/abis'; | ||
import { addressToTopic, isNullish, sortTokenEventsChronologically } from 'lib/utils'; | ||
import { | ||
generatePatchedAllowanceEvents, | ||
parseApprovalForAllLog, | ||
parseApprovalLog, | ||
parsePermit2Log, | ||
parseTransferLog, | ||
} from 'lib/utils/events'; | ||
import { useMemo } from 'react'; | ||
import { type Address, getAbiItem, toEventSelector } from 'viem'; | ||
import { useLogsFullBlockRange } from '../useLogsFullBlockRange'; | ||
import { useOpenSeaProxyAddress } from '../useOpenSeaProxyAddress'; | ||
import { usePermit2Events } from './usePermit2Events'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { getTokenEvents } from 'lib/chains/events'; | ||
import { isNullish } from 'lib/utils'; | ||
import type { Address } from 'viem'; | ||
|
||
export const useEvents = (address: Address, chainId: number) => { | ||
const { openSeaProxyAddress, isLoading: isOpenSeaProxyAddressLoading } = useOpenSeaProxyAddress(address); | ||
const { data, isLoading, error } = useQuery({ | ||
queryKey: ['events', address, chainId], | ||
queryFn: () => getTokenEvents(chainId, address), | ||
enabled: !isNullish(address) && !isNullish(chainId), | ||
}); | ||
|
||
const getErc721EventSelector = (eventName: 'Transfer' | 'Approval' | 'ApprovalForAll') => { | ||
return toEventSelector(getAbiItem({ abi: ERC721_ABI, name: eventName })); | ||
}; | ||
|
||
const addressTopic = address ? addressToTopic(address) : undefined; | ||
const transferToFilter = addressTopic && { topics: [getErc721EventSelector('Transfer'), null, addressTopic] }; | ||
const transferFromFilter = addressTopic && { topics: [getErc721EventSelector('Transfer'), addressTopic] }; | ||
const approvalFilter = addressTopic && { topics: [getErc721EventSelector('Approval'), addressTopic] }; | ||
const approvalForAllFilter = addressTopic && { topics: [getErc721EventSelector('ApprovalForAll'), addressTopic] }; | ||
|
||
const { | ||
data: transferTo, | ||
isLoading: isTransferToLoading, | ||
error: transferToError, | ||
} = useLogsFullBlockRange('Transfer (to)', chainId, transferToFilter); | ||
|
||
const { | ||
data: transferFrom, | ||
isLoading: isTransferFromLoading, | ||
error: transferFromError, | ||
} = useLogsFullBlockRange('Transfer (from)', chainId, transferFromFilter); | ||
|
||
const { | ||
data: approval, | ||
isLoading: isApprovalLoading, | ||
error: approvalError, | ||
} = useLogsFullBlockRange('Approval', chainId, approvalFilter); | ||
|
||
const { | ||
data: approvalForAllUnpatched, | ||
isLoading: isApprovalForAllLoading, | ||
error: approvalForAllError, | ||
} = useLogsFullBlockRange('ApprovalForAll', chainId, approvalForAllFilter); | ||
|
||
const { | ||
events: permit2Approval, | ||
isLoading: isPermit2ApprovalLoading, | ||
error: permit2ApprovalError, | ||
} = usePermit2Events(address, chainId); | ||
|
||
// Manually patch the ApprovalForAll events | ||
const approvalForAll = useMemo(() => { | ||
if (!transferFrom || !transferTo || !approval || !approvalForAllUnpatched) return undefined; | ||
return [ | ||
...approvalForAllUnpatched, | ||
...generatePatchedAllowanceEvents(address, openSeaProxyAddress ?? undefined, [ | ||
...approval, | ||
...approvalForAllUnpatched, | ||
...transferFrom, | ||
...transferTo, | ||
]), | ||
]; | ||
}, [transferFrom, transferTo, approval, approvalForAllUnpatched, openSeaProxyAddress, address]); | ||
|
||
const isEventsLoading = isTransferFromLoading || isTransferToLoading || isApprovalLoading || isApprovalForAllLoading; | ||
const isLoading = isOpenSeaProxyAddressLoading || isEventsLoading || isPermit2ApprovalLoading; | ||
const eventsError = transferFromError || transferToError || approvalError || approvalForAllError; | ||
const error = eventsError || permit2ApprovalError; | ||
|
||
const events = useMemo(() => { | ||
if (!transferFrom || !transferTo || !approval || !approvalForAll || !permit2Approval) return undefined; | ||
if (error || isLoading) return undefined; | ||
|
||
const parsedEvents = [ | ||
// We put ApprovalForAll first to ensure that incorrect ERC721 contracts like CryptoStrikers are handled correctly | ||
...approvalForAll.map((log) => parseApprovalForAllLog(log, chainId)), | ||
...approval.map((log) => parseApprovalLog(log, chainId)), | ||
...permit2Approval.map((log) => parsePermit2Log(log, chainId)), | ||
...transferFrom.map((log) => parseTransferLog(log, chainId, address)), | ||
...transferTo.map((log) => parseTransferLog(log, chainId, address)), | ||
]; | ||
|
||
// We sort the events in reverse chronological order to ensure that the most recent events are processed first | ||
return sortTokenEventsChronologically(parsedEvents.filter((event) => !isNullish(event))).reverse(); | ||
}, [transferFrom, transferTo, approval, approvalForAll, permit2Approval, error, isLoading, address, chainId]); | ||
|
||
return { events, isLoading, error }; | ||
return { events: data, isLoading, error }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.