From 1e8f2c0339d4d95acfd09547dbe6127be0071ca1 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:04:54 +0000 Subject: [PATCH 1/4] feat: fetch token deposits from event logs --- .../__tests__/fetchDepositsTestHelpers.ts | 9 +- .../fetchTokenDepositsFromEventLogs.test.ts | 102 +++++++++++++++ .../fetchTokenDepositsFromEventLogs.ts | 121 ++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchTokenDepositsFromEventLogs.test.ts create mode 100644 packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts diff --git a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts index 838231508e..6db44162a8 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts @@ -3,7 +3,14 @@ const sender = '0x5d64a0fd6af0d76a7ed189d4061ffa6823fbf97e' const baseQuery = { sender, l2ChainId: 42161, - pageSize: 100 + childChainId: 42161, + parentChainId: 1, + pageSize: 100, + parentGatewayAddresses: [ + '0xa3A7B6F88361F48403514059F1F16C8E78d60EeC', // Parent Standard Gateway + '0xcEe284F754E854890e311e3280b767F80797180d', // Parent Custom Gateway + '0xd92023E9d9911199a6711321D1277285e6d4e2db' // Parent WETH Gateway + ] } export function getQueryCoveringClassicOnlyWithoutResults() { diff --git a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchTokenDepositsFromEventLogs.test.ts b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchTokenDepositsFromEventLogs.test.ts new file mode 100644 index 0000000000..ada0dd4368 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchTokenDepositsFromEventLogs.test.ts @@ -0,0 +1,102 @@ +import { fetchTokenDepositsFromEventLogs } from '../fetchTokenDepositsFromEventLogs' +import { + getQueryCoveringClassicAndNitroWithResults, + getQueryCoveringClassicOnlyWithoutResults, + getQueryCoveringClassicOnlyWithResults +} from './fetchDepositsTestHelpers' + +const TIMEOUT = 30_000 + +describe('fetchTokenDepositsFromEventLogs', () => { + it( + 'fetches no token deposits from event logs pre-nitro', + async () => { + const result = await fetchTokenDepositsFromEventLogs( + getQueryCoveringClassicOnlyWithoutResults() + ) + + expect(result).toHaveLength(0) + }, + TIMEOUT + ) + + it( + 'fetches some token deposits from event logs pre-nitro', + async () => { + const result = await fetchTokenDepositsFromEventLogs({ + ...getQueryCoveringClassicOnlyWithResults(), + sender: '0x981EA2202d2d33D26583E96f6e6449C1F9b6Bbb1' + }) + + expect(result).toHaveLength(1) + expect(result).toEqual( + expect.arrayContaining([ + // 14387620 + expect.objectContaining({ + txHash: + '0xc112c54c261dabbd919132149226fc8a4c0196948e18dda947aa06e8b8b69064' + }) + ]) + ) + }, + TIMEOUT + ) + + it( + 'fetches some token deposits from event logs pre-nitro and post-nitro', + async () => { + const result1 = await fetchTokenDepositsFromEventLogs({ + ...getQueryCoveringClassicAndNitroWithResults(), + sender: '0x5049FEB67800e5BA82626c9f78FF9C458E0Eb66f' + }) + + expect(result1).toHaveLength(1) + expect(result1).toEqual( + expect.arrayContaining([ + // 15417417 + expect.objectContaining({ + txHash: + '0xd9e72eba4548811425fd57def8aad238fcfdb0ced5a2af1d6b99f48ef51d52f0' + }) + ]) + ) + + const result2 = await fetchTokenDepositsFromEventLogs({ + ...getQueryCoveringClassicAndNitroWithResults(), + sender: '0x8594D8e9483473626908648A5539D9d65Ca2fe8d' + }) + + expect(result2).toHaveLength(149) + }, + TIMEOUT + ) +}) + +it( + 'fetches some token deposits from event logs post-nitro', + async () => { + const result = await fetchTokenDepositsFromEventLogs({ + ...getQueryCoveringClassicOnlyWithResults(), + sender: '0x07aE8551Be970cB1cCa11Dd7a11F47Ae82e70E67', + fromBlock: 21502346, + toBlock: 21502471 + }) + + expect(result).toHaveLength(2) + expect(result).toEqual( + expect.arrayContaining([ + // 14310032 + expect.objectContaining({ + txHash: + '0x583bea616664fa32f68d25822bb64f18c8186221c35919a567b3de5eb9c1ae7e' + }), + // 14309826 + expect.objectContaining({ + txHash: + '0x012ef30f19eb89951590790f6e2882fe6a5dc7671e8cbddb2b09b6efd7ad892a' + }) + ]) + ) + }, + TIMEOUT +) diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts new file mode 100644 index 0000000000..a59138b1d7 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts @@ -0,0 +1,121 @@ +import { EventArgs, EventFetcher } from '@arbitrum/sdk' +import { L1ERC20Gateway__factory } from '@arbitrum/sdk/dist/lib/abi/factories/L1ERC20Gateway__factory' +import { DepositInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L1ERC20Gateway' +import { BlockTag } from '@ethersproject/providers' + +import { getProviderForChainId } from '@/token-bridge-sdk/utils' + +/** + * Get the parent network events created by a deposit + * @param parentChainId + * @param gatewayAddress + * @param filter + * @param parentTokenAddress + * @param fromAddress + * @param toAddress + * @returns + */ +export async function getDepositEvents( + parentChainId: number, + gatewayAddress: string, + filter: { fromBlock: BlockTag; toBlock: BlockTag }, + parentTokenAddress?: string, + fromAddress?: string, + toAddress?: string +): Promise<(EventArgs & { txHash: string })[]> { + const parentProvider = getProviderForChainId(parentChainId) + + const eventFetcher = new EventFetcher(parentProvider) + const events = ( + await eventFetcher.getEvents( + L1ERC20Gateway__factory, + contract => + contract.filters.DepositInitiated( + null, // parentToken + fromAddress || null, // _from + toAddress || null // _to + ), + { ...filter, address: gatewayAddress } + ) + ).map(a => ({ txHash: a.transactionHash, ...a.event })) + + return parentTokenAddress + ? events.filter( + log => + log.l1Token.toLocaleLowerCase() === + parentTokenAddress.toLocaleLowerCase() + ) + : events +} + +function dedupeEvents( + events: (EventArgs & { + txHash: string + })[] +) { + return [...new Map(events.map(item => [item.txHash, item])).values()] +} + +export type FetchTokenDepositsFromEventLogsParams = { + sender?: string + receiver?: string + fromBlock: BlockTag + toBlock: BlockTag + parentChainId: number + parentGatewayAddresses?: string[] +} + +/** + * Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock]. + * + * @param query Query params + * @param query.sender Address that initiated the withdrawal + * @param query.receiver Address that received the funds + * @param query.fromBlock Start at this block number (including) + * @param query.toBlock Stop at this block number (including) + * @param query.childChainId child chain id + * @param query.parentGatewayAddresses L2 gateway addresses to use + */ +export async function fetchTokenDepositsFromEventLogs({ + sender, + receiver, + fromBlock, + toBlock, + parentChainId, + parentGatewayAddresses = [] +}: FetchTokenDepositsFromEventLogsParams) { + const promises: ReturnType[] = [] + + parentGatewayAddresses.forEach(gatewayAddress => { + // funds sent by this address + if (sender) { + promises.push( + getDepositEvents( + parentChainId, + gatewayAddress, + { fromBlock, toBlock }, + undefined, + sender, + undefined + ) + ) + } + + // funds received by this address + if (receiver) { + promises.push( + getDepositEvents( + parentChainId, + gatewayAddress, + { fromBlock, toBlock }, + undefined, + undefined, + receiver + ) + ) + } + }) + + // when getting funds received by this address we will also get duplicate txs returned in 'funds sent by this address' + return dedupeEvents((await Promise.all(promises)).flat()) +} From 69f7c339d60e0d4ebf9fb3c10b72f2d643da35ac Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:49:15 +0000 Subject: [PATCH 2/4] update --- .../fetchDepositsFromSubgraph.test.ts | 26 ++++++++++++------- .../__tests__/fetchDepositsTestHelpers.ts | 10 +++---- .../fetchTokenDepositsFromEventLogs.ts | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsFromSubgraph.test.ts b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsFromSubgraph.test.ts index 9f9c6c0d5b..634e90681a 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsFromSubgraph.test.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsFromSubgraph.test.ts @@ -5,19 +5,26 @@ import { } from './fetchDepositsTestHelpers' import { fetchDepositsFromSubgraph } from '../fetchDepositsFromSubgraph' +const defaults = { + sender: '0x5d64a0fd6af0d76a7ed189d4061ffa6823fbf97e', + pageSize: 100 +} + describe('fetchDepositsFromSubgraph', () => { it('fetches no deposits from subgraph pre-nitro', async () => { - const result = await fetchDepositsFromSubgraph( - getQueryCoveringClassicOnlyWithoutResults() - ) + const result = await fetchDepositsFromSubgraph({ + ...defaults, + ...getQueryCoveringClassicOnlyWithoutResults() + }) expect(result).toHaveLength(0) }) it('fetches some deposits from subgraph pre-nitro', async () => { - const result = await fetchDepositsFromSubgraph( - getQueryCoveringClassicOnlyWithResults() - ) + const result = await fetchDepositsFromSubgraph({ + ...defaults, + ...getQueryCoveringClassicOnlyWithResults() + }) expect(result).toHaveLength(3) expect(result).toEqual( @@ -39,9 +46,10 @@ describe('fetchDepositsFromSubgraph', () => { }) it('fetches some deposits from subgraph pre-nitro and post-nitro', async () => { - const result = await fetchDepositsFromSubgraph( - getQueryCoveringClassicAndNitroWithResults() - ) + const result = await fetchDepositsFromSubgraph({ + ...defaults, + ...getQueryCoveringClassicAndNitroWithResults() + }) expect(result).toHaveLength(4) expect(result).toEqual( diff --git a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts index 6db44162a8..46ca11ae3f 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts @@ -1,15 +1,13 @@ -const sender = '0x5d64a0fd6af0d76a7ed189d4061ffa6823fbf97e' +import { getArbitrumNetwork } from '@arbitrum/sdk' const baseQuery = { - sender, l2ChainId: 42161, childChainId: 42161, parentChainId: 1, - pageSize: 100, parentGatewayAddresses: [ - '0xa3A7B6F88361F48403514059F1F16C8E78d60EeC', // Parent Standard Gateway - '0xcEe284F754E854890e311e3280b767F80797180d', // Parent Custom Gateway - '0xd92023E9d9911199a6711321D1277285e6d4e2db' // Parent WETH Gateway + getArbitrumNetwork(42161).tokenBridge!.parentErc20Gateway, + getArbitrumNetwork(42161).tokenBridge!.parentCustomGateway, + getArbitrumNetwork(42161).tokenBridge!.parentWethGateway ] } diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts index a59138b1d7..5279f511aa 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts @@ -66,7 +66,7 @@ export type FetchTokenDepositsFromEventLogsParams = { } /** - * Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock]. + * Fetches initiated token deposits from event logs in range of [fromBlock, toBlock]. * * @param query Query params * @param query.sender Address that initiated the withdrawal From ea9a9aa3b160e97cbcc2d019eb50e1f2a38e9714 Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:06:44 +0000 Subject: [PATCH 3/4] update --- .../deposits/fetchTokenDepositsFromEventLogs.ts | 13 ++++--------- .../src/util/deposits/helpers.ts | 11 ++++++++++- .../fetchTokenWithdrawalsFromEventLogs.ts | 15 +++++---------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts index 5279f511aa..d7f3f8c1c2 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts @@ -4,6 +4,7 @@ import { DepositInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L1ERC20Gateway import { BlockTag } from '@ethersproject/providers' import { getProviderForChainId } from '@/token-bridge-sdk/utils' +import { dedupeEvents } from './helpers' /** * Get the parent network events created by a deposit @@ -48,14 +49,6 @@ export async function getDepositEvents( : events } -function dedupeEvents( - events: (EventArgs & { - txHash: string - })[] -) { - return [...new Map(events.map(item => [item.txHash, item])).values()] -} - export type FetchTokenDepositsFromEventLogsParams = { sender?: string receiver?: string @@ -117,5 +110,7 @@ export async function fetchTokenDepositsFromEventLogs({ }) // when getting funds received by this address we will also get duplicate txs returned in 'funds sent by this address' - return dedupeEvents((await Promise.all(promises)).flat()) + return dedupeEvents( + (await Promise.all(promises)).flat() + ) } diff --git a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts index 59d14ced25..e8a406d4f5 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts @@ -6,7 +6,8 @@ import { ParentToChildMessageReader, ParentToChildMessageReaderClassic, EthL1L3DepositStatus, - Erc20L1L3DepositStatus + Erc20L1L3DepositStatus, + EventArgs } from '@arbitrum/sdk' import { utils } from 'ethers' @@ -675,3 +676,11 @@ export const getParentToChildMessageDataFromParentTxHash = async ({ // post-nitro deposit - both eth + token return getNitroDepositMessage() } + +export function dedupeEvents( + events: (EventArgs & { + txHash: string + })[] +) { + return [...new Map(events.map(item => [item.txHash, item])).values()] +} diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts index 8296e2085f..d4e5bd24fa 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts @@ -1,14 +1,7 @@ import { Provider, BlockTag } from '@ethersproject/providers' -import { Erc20Bridger, EventArgs } from '@arbitrum/sdk' +import { Erc20Bridger } from '@arbitrum/sdk' import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway' - -function dedupeEvents( - events: (EventArgs & { - txHash: string - })[] -) { - return [...new Map(events.map(item => [item.txHash, item])).values()] -} +import { dedupeEvents } from '../deposits/helpers' export type FetchTokenWithdrawalsFromEventLogsParams = { sender?: string @@ -72,5 +65,7 @@ export async function fetchTokenWithdrawalsFromEventLogs({ }) // when getting funds received by this address we will also get duplicate txs returned in 'funds sent by this address' - return dedupeEvents((await Promise.all(promises)).flat()) + return dedupeEvents( + (await Promise.all(promises)).flat() + ) } From 4f7631ca545b5e2fe48f40bc1163979be06cee8e Mon Sep 17 00:00:00 2001 From: Fionna <13184582+fionnachan@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:15:18 +0000 Subject: [PATCH 4/4] update according to review comments --- .../src/components/TransactionHistory/helpers.ts | 11 ++++++++++- .../deposits/__tests__/fetchDepositsTestHelpers.ts | 8 +++++--- .../util/deposits/fetchTokenDepositsFromEventLogs.ts | 2 +- .../arb-token-bridge-ui/src/util/deposits/helpers.ts | 11 +---------- .../withdrawals/fetchTokenWithdrawalsFromEventLogs.ts | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts index 3a10fb6929..4d1f7e16f9 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts @@ -6,7 +6,8 @@ import { ParentToChildMessageStatus, ParentToChildMessageReader, ChildTransactionReceipt, - ChildToParentTransactionEvent + ChildToParentTransactionEvent, + EventArgs } from '@arbitrum/sdk' import { @@ -594,3 +595,11 @@ export function getDestinationNetworkTxId(tx: MergedTransaction) { ? tx.childToParentMsgData?.uniqueId.toString() : tx.parentToChildMsgData?.childTxId } + +export function dedupeEvents( + events: (EventArgs & { + txHash: string + })[] +) { + return [...new Map(events.map(item => [item.txHash, item])).values()] +} diff --git a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts index 46ca11ae3f..76f0a0a011 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/__tests__/fetchDepositsTestHelpers.ts @@ -1,13 +1,15 @@ import { getArbitrumNetwork } from '@arbitrum/sdk' +const arbSepoliaTokenBridge = getArbitrumNetwork(42161).tokenBridge! + const baseQuery = { l2ChainId: 42161, childChainId: 42161, parentChainId: 1, parentGatewayAddresses: [ - getArbitrumNetwork(42161).tokenBridge!.parentErc20Gateway, - getArbitrumNetwork(42161).tokenBridge!.parentCustomGateway, - getArbitrumNetwork(42161).tokenBridge!.parentWethGateway + arbSepoliaTokenBridge.parentErc20Gateway, + arbSepoliaTokenBridge.parentCustomGateway, + arbSepoliaTokenBridge.parentWethGateway ] } diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts index d7f3f8c1c2..5dc2a45d55 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchTokenDepositsFromEventLogs.ts @@ -4,7 +4,7 @@ import { DepositInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L1ERC20Gateway import { BlockTag } from '@ethersproject/providers' import { getProviderForChainId } from '@/token-bridge-sdk/utils' -import { dedupeEvents } from './helpers' +import { dedupeEvents } from '../../components/TransactionHistory/helpers' /** * Get the parent network events created by a deposit diff --git a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts index e8a406d4f5..59d14ced25 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts @@ -6,8 +6,7 @@ import { ParentToChildMessageReader, ParentToChildMessageReaderClassic, EthL1L3DepositStatus, - Erc20L1L3DepositStatus, - EventArgs + Erc20L1L3DepositStatus } from '@arbitrum/sdk' import { utils } from 'ethers' @@ -676,11 +675,3 @@ export const getParentToChildMessageDataFromParentTxHash = async ({ // post-nitro deposit - both eth + token return getNitroDepositMessage() } - -export function dedupeEvents( - events: (EventArgs & { - txHash: string - })[] -) { - return [...new Map(events.map(item => [item.txHash, item])).values()] -} diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts index d4e5bd24fa..5c0dc96069 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts @@ -1,7 +1,7 @@ import { Provider, BlockTag } from '@ethersproject/providers' import { Erc20Bridger } from '@arbitrum/sdk' import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway' -import { dedupeEvents } from '../deposits/helpers' +import { dedupeEvents } from '../../components/TransactionHistory/helpers' export type FetchTokenWithdrawalsFromEventLogsParams = { sender?: string