Skip to content

Commit

Permalink
Merge transactions from indexer and explorer for redundancy (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
zencephalon authored Jan 27, 2024
1 parent 0795149 commit d6b66f0
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 21 deletions.
30 changes: 16 additions & 14 deletions apps/bridge/src/data/useDeposits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import getConfig from 'next/config';
import { DepositItem } from '@eth-optimism/indexer-api';
import { indexerTxToBridgeDeposit } from 'apps/bridge/src/utils/transactions/indexerTxToBridgeDeposit';
import { useChainEnv } from 'apps/bridge/src/utils/hooks/useChainEnv';
import { dedupeTransactions } from 'apps/bridge/src/utils/array/dedupeTransactions';

const { publicRuntimeConfig } = getConfig();

Expand Down Expand Up @@ -43,7 +44,7 @@ async function fetchOPDeposits(address: string) {
);
}

async function fetchCCTPDeposits(address: string, isMainnet: boolean) {
async function fetchExplorerDeposits(address: string, isMainnet: boolean) {
const response = await getJSON<BlockExplorerApiResponse<BlockExplorerTransaction[]>>(
publicRuntimeConfig.l1ExplorerApiUrl,
{
Expand Down Expand Up @@ -79,19 +80,20 @@ export function useDeposits(address: string): {
},
);

const { data: cctpDeposits, isFetched: isCCTPDepositsFetched } = useQuery<BridgeTransaction[]>(
['cctpDeposits', address],
async () => fetchCCTPDeposits(address, isMainnet),
{
enabled: !!address,
suspense: false, // Does suspense work w/ SSR? We'll just not use it.
staleTime: 5000, // Stale after 5 seconds
notifyOnChangeProps: ['data', 'isFetched'],
refetchInterval: 1000 * 30, // Automatically refetch every 30 seconds
},
);
const { data: explorerDeposits, isFetched: isExplorerDepositsFetched } = useQuery<
BridgeTransaction[]
>(['explorerDeposits', address], async () => fetchExplorerDeposits(address, isMainnet), {
enabled: !!address,
suspense: false, // Does suspense work w/ SSR? We'll just not use it.
staleTime: 5000, // Stale after 5 seconds
notifyOnChangeProps: ['data', 'isFetched'],
refetchInterval: 1000 * 30, // Automatically refetch every 30 seconds
});

const deposits = dedupeTransactions([...(opDeposits ?? []), ...(explorerDeposits ?? [])]);

return {
deposits: [...(opDeposits ?? []), ...(cctpDeposits ?? [])],
isFetched: isOPDepositsFetched && isCCTPDepositsFetched,
deposits,
isFetched: isOPDepositsFetched && isExplorerDepositsFetched,
};
}
20 changes: 13 additions & 7 deletions apps/bridge/src/data/useWithdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { BridgeTransaction } from 'apps/bridge/src/types/BridgeTransaction';
import { useChainEnv } from 'apps/bridge/src/utils/hooks/useChainEnv';
import { explorerTxToBridgeWithdrawal } from 'apps/bridge/src/utils/transactions/explorerTxToBridgeWithdrawal';
import {
isExplorerTxETHOrERC20Withdrawal,
isIndexerTxETHOrERC20Withdrawal,
isETHOrERC20OrCCTPWithdrawal,
} from 'apps/bridge/src/utils/transactions/isETHOrERCWithdrawal';
import getConfig from 'next/config';
import { WithdrawalItem } from '@eth-optimism/indexer-api';
import { indexerTxToBridgeWithdrawal } from 'apps/bridge/src/utils/transactions/indexerTxToBridgeWithdrawal';
import { dedupeTransactions } from 'apps/bridge/src/utils/array/dedupeTransactions';

const { publicRuntimeConfig } = getConfig();

Expand Down Expand Up @@ -45,7 +46,7 @@ async function fetchOPWithdrawals(address: string) {
);
}

async function fetchCCTPWithdrawals(address: string, isMainnet: boolean) {
async function fetchExplorerWithdrawals(address: string, isMainnet: boolean) {
const response = await getJSON<BlockExplorerApiResponse<BlockExplorerTransaction[]>>(
// TODO: filter to transactions to the withdraw contract
publicRuntimeConfig.l2ExplorerApiURL,
Expand All @@ -59,7 +60,7 @@ async function fetchCCTPWithdrawals(address: string, isMainnet: boolean) {
);

return explorerTxToBridgeWithdrawals(
response.result.filter((tx) => tx.isError !== '1' && isExplorerTxETHOrERC20Withdrawal(tx)),
response.result.filter((tx) => tx.isError !== '1' && isETHOrERC20OrCCTPWithdrawal(tx)),
);
}

Expand All @@ -82,18 +83,23 @@ export function useWithdrawals(address: string): {
},
);

const { data: cctpWithdrawals, isFetched: isCCTPWithdrawalsFetched } = useQuery<
const { data: explorerWithdrawals, isFetched: isExplorerWithdrawalsFetched } = useQuery<
BridgeTransaction[]
>(['cctpWithdrawals', address], async () => fetchCCTPWithdrawals(address, isMainnet), {
>(['explorerWithdrawals', address], async () => fetchExplorerWithdrawals(address, isMainnet), {
enabled: !!address,
suspense: false, // Does suspense work w/ SSR? We'll just not use it.
staleTime: 5000, // Stale after 5 seconds
notifyOnChangeProps: ['data', 'isFetched'],
refetchInterval: 1000 * 30, // Automatically refetch every 30 seconds
});

const withdrawals = dedupeTransactions([
...(opWithdrawals ?? []),
...(explorerWithdrawals ?? []),
]);

return {
withdrawals: [...(opWithdrawals ?? []), ...(cctpWithdrawals ?? [])],
isFetched: isOPWithdrawalsFetched && isCCTPWithdrawalsFetched,
withdrawals,
isFetched: isOPWithdrawalsFetched && isExplorerWithdrawalsFetched,
};
}
13 changes: 13 additions & 0 deletions apps/bridge/src/utils/array/dedupeTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BridgeTransaction } from 'apps/bridge/src/types/BridgeTransaction';

export function dedupeTransactions(transactions: BridgeTransaction[]) {
const deduped = [];
const hashes = new Set();
for (const transaction of transactions) {
if (!hashes.has(transaction.hash)) {
deduped.push(transaction);
hashes.add(transaction.hash);
}
}
return deduped;
}
56 changes: 56 additions & 0 deletions apps/bridge/src/utils/transactions/isETHOrERC20Deposit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { l1StandardBridgeABI } from '@eth-optimism/contracts-ts';
import { decodeFunctionData } from 'viem';
import { BlockExplorerTransaction } from 'apps/bridge/src/types/API';
import getConfig from 'next/config';
Expand Down Expand Up @@ -45,3 +46,58 @@ export function isIndexerTxETHOrERC20Deposit(tx: DepositItem) {

return Boolean(token);
}

const ETH_DEPOSIT_ADDRESS = (
publicRuntimeConfig?.l1OptimismPortalProxyAddress ?? '0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA'
).toLowerCase();

const ERC20_DEPOSIT_ADDRESS = (
publicRuntimeConfig?.l1BridgeProxyAddress ?? '0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a'
).toLowerCase();

export function isETHOrERC20OrCCTPDeposit(tx: BlockExplorerTransaction) {
// Immediately filter out if tx is not to an address we don't care about
if (
tx.to !== ETH_DEPOSIT_ADDRESS &&
tx.to !== ERC20_DEPOSIT_ADDRESS &&
tx.to !== CCTP_DEPOSIT_ADDRESS
) {
return false;
}

// ETH deposit
if (tx.to === ETH_DEPOSIT_ADDRESS && tx.value !== '0') {
return true;
}

// ERC-20 desposit
if (tx.to === ERC20_DEPOSIT_ADDRESS) {
const { functionName, args } = decodeFunctionData({
abi: l1StandardBridgeABI,
data: tx.input,
});
if (functionName === 'depositERC20' || functionName === 'depositERC20To') {
const token = assetList.find(
(asset) =>
asset.L1chainId === parseInt(publicRuntimeConfig.l1ChainID) &&
asset.L1contract?.toLowerCase() === (args?.[0] as string).toLowerCase() &&
asset.protocol === 'OP',
);
// Return true if this is a depositERC20 call to the L1StandardBridge and is a token the UI supports
return Boolean(token);
}
}

// CCTP deposit
if (tx.to === CCTP_DEPOSIT_ADDRESS && publicRuntimeConfig.cctpEnabled === 'true') {
const { functionName } = decodeFunctionData({
abi: TokenMessenger,
data: tx.input,
});
if (functionName === 'depositForBurn') {
return true;
}
}

return false;
}
51 changes: 51 additions & 0 deletions apps/bridge/src/utils/transactions/isETHOrERCWithdrawal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getAssetListForChainEnv } from 'apps/bridge/src/utils/assets/getAssetLi
import getConfig from 'next/config';
import { decodeFunctionData } from 'viem';

import { l2StandardBridgeABI } from '@eth-optimism/contracts-ts';

const { publicRuntimeConfig } = getConfig();

const assetList = getAssetListForChainEnv();
Expand Down Expand Up @@ -43,3 +45,52 @@ export function isIndexerTxETHOrERC20Withdrawal(tx: WithdrawalItem) {

return Boolean(token);
}

const ETH_WITHDRAWAL_ADDRESS = (
publicRuntimeConfig?.l2L1MessagePasserAddress ?? '0x4200000000000000000000000000000000000016'
).toLowerCase();

const ERC20_WITHDRAWAL_ADDRESS = (
publicRuntimeConfig?.L2StandardBridge ?? '0x4200000000000000000000000000000000000010'
).toLowerCase();

export function isETHOrERC20OrCCTPWithdrawal(tx: BlockExplorerTransaction) {
// Immediately filter out if tx is not to an address we don't care about
if (
tx.to !== ETH_WITHDRAWAL_ADDRESS &&
tx.to !== ERC20_WITHDRAWAL_ADDRESS &&
tx.to !== CCTP_WITHDRAWAL_ADDRESS
) {
return false;
}

// ETH withdrawal
if (tx.to === ETH_WITHDRAWAL_ADDRESS && tx.value !== '0') {
return true;
}

// ERC-20 Withdrawal
if (tx.to === ERC20_WITHDRAWAL_ADDRESS) {
const { functionName, args } = decodeFunctionData({ abi: l2StandardBridgeABI, data: tx.input });
if (functionName === 'withdraw' || functionName === 'withdrawTo') {
const token = assetList.find(
(asset) =>
asset.L2chainId === parseInt(publicRuntimeConfig.l2ChainID) &&
asset.L2contract?.toLowerCase() === ((args?.[0] as string) ?? '').toLowerCase() &&
asset.protocol === 'OP',
);
// Return true if this is a withdraw call to the L2StandardBridge and is a token the UI supports
return Boolean(token);
}
}

// CCTP Withdrawal
if (tx.to === CCTP_WITHDRAWAL_ADDRESS && publicRuntimeConfig.cctpEnabled === 'true') {
const { functionName } = decodeFunctionData({ abi: TokenMessenger, data: tx.input });
if (functionName === 'depositForBurn') {
return true;
}
}

return false;
}

0 comments on commit d6b66f0

Please sign in to comment.