Skip to content

Commit

Permalink
Merge branch 'master' into fs-1030/load-token-lists-for-orbit-chains
Browse files Browse the repository at this point in the history
  • Loading branch information
chrstph-dvx committed Dec 16, 2024
2 parents 5bd0e1a + 6d55520 commit b6911af
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 49 deletions.
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"cheerio": "^1.0.0-rc.12",
"dayjs": "^1.11.8",
"ethers": "^5.6.0",
"exponential-backoff": "^3.1.1",
"graphql": "^16.8.1",
"lodash-es": "^4.17.21",
"next": "^14.2.12",
Expand Down
14 changes: 14 additions & 0 deletions packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { backOff as _backOff, BackoffOptions } from 'exponential-backoff'

const backoffOptions: BackoffOptions = {
startingDelay: 1_000,
timeMultiple: 1.5
}

export function backOff<T>(request: () => Promise<T>): Promise<T> {
return _backOff(request, backoffOptions)
}

export function wait(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
15 changes: 1 addition & 14 deletions packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { constants } from 'ethers'
import { Provider } from '@ethersproject/providers'
import { getArbitrumNetwork } from '@arbitrum/sdk'

Expand All @@ -19,26 +18,14 @@ import {
export async function fetchL2Gateways(l2Provider: Provider) {
const l2Network = await getArbitrumNetwork(l2Provider)

if (!l2Network.tokenBridge) {
return []
}

/* configure gateway addresses for fetching withdrawals */
const { childErc20Gateway, childCustomGateway, childWethGateway } =
l2Network.tokenBridge

const gatewaysToUse = [childErc20Gateway, childCustomGateway]
const gatewaysToUse = []
const l2ArbReverseGateway = l2ArbReverseGatewayAddresses[l2Network.chainId]
const l2DaiGateway = l2DaiGatewayAddresses[l2Network.chainId]
const l2wstETHGateway = l2wstETHGatewayAddresses[l2Network.chainId]
const l2LptGateway = l2LptGatewayAddresses[l2Network.chainId]
const l2MoonGateway = l2MoonGatewayAddresses[l2Network.chainId]
const l2UsdcGateway = l2UsdcGatewayAddresses[l2Network.chainId]

// custom gas token chains will have weth gateway set to address zero
if (childWethGateway !== constants.AddressZero) {
gatewaysToUse.push(childWethGateway)
}
if (l2ArbReverseGateway) {
gatewaysToUse.push(l2ArbReverseGateway)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function fetchETHWithdrawalsFromEventLogs({
l2Provider: Provider
}) {
if (typeof receiver === 'undefined') {
return []
return Promise.resolve([])
}

// funds received by this address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { Provider, BlockTag } from '@ethersproject/providers'
import { Erc20Bridger, EventArgs } from '@arbitrum/sdk'
import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway'

import { getNonce } from '../AddressUtils'

function dedupeEvents(
events: (EventArgs<WithdrawalInitiatedEvent> & {
txHash: string
Expand All @@ -12,6 +10,15 @@ function dedupeEvents(
return [...new Map(events.map(item => [item.txHash, item])).values()]
}

export type FetchTokenWithdrawalsFromEventLogsParams = {
sender?: string
receiver?: string
fromBlock: BlockTag
toBlock: BlockTag
l2Provider: Provider
l2GatewayAddresses?: string[]
}

/**
* Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock].
*
Expand All @@ -30,22 +37,13 @@ export async function fetchTokenWithdrawalsFromEventLogs({
toBlock,
l2Provider,
l2GatewayAddresses = []
}: {
sender?: string
receiver?: string
fromBlock: BlockTag
toBlock: BlockTag
l2Provider: Provider
l2GatewayAddresses?: string[]
}) {
}: FetchTokenWithdrawalsFromEventLogsParams) {
const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider)
const promises: ReturnType<Erc20Bridger['getWithdrawalEvents']>[] = []

const senderNonce = await getNonce(sender, { provider: l2Provider })

l2GatewayAddresses.forEach(gatewayAddress => {
// funds sent by this address
if (sender && senderNonce > 0) {
if (sender) {
promises.push(
erc20Bridger.getWithdrawalEvents(
l2Provider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { constants } from 'ethers'
import { Provider, BlockTag } from '@ethersproject/providers'
import { Erc20Bridger, getArbitrumNetwork } from '@arbitrum/sdk'

import {
fetchTokenWithdrawalsFromEventLogs,
FetchTokenWithdrawalsFromEventLogsParams
} from './fetchTokenWithdrawalsFromEventLogs'
import { getNonce } from '../AddressUtils'
import { fetchL2Gateways } from '../fetchL2Gateways'
import { backOff, wait } from '../ExponentialBackoffUtils'

async function getGateways(provider: Provider): Promise<{
standardGateway: string
wethGateway: string
customGateway: string
otherGateways: string[]
}> {
const network = await getArbitrumNetwork(provider)

const standardGateway = network.tokenBridge?.childErc20Gateway
const customGateway = network.tokenBridge?.childCustomGateway
const wethGateway = network.tokenBridge?.childWethGateway
const otherGateways = await fetchL2Gateways(provider)

return {
standardGateway: standardGateway ?? constants.AddressZero,
wethGateway: wethGateway ?? constants.AddressZero,
customGateway: customGateway ?? constants.AddressZero,
otherGateways
}
}

type TokenWithdrawalQuery = {
params: FetchTokenWithdrawalsFromEventLogsParams
priority: number
}

export type FetchTokenWithdrawalsFromEventLogsSequentiallyParams = {
sender?: string
receiver?: string
provider: Provider
fromBlock?: BlockTag
toBlock?: BlockTag
/**
* How long to delay in-between queries of different priority.
*/
delayMs?: number
}

export type FetchTokenWithdrawalsFromEventLogsSequentiallyResult = Awaited<
ReturnType<Erc20Bridger['getWithdrawalEvents']>
>

export async function fetchTokenWithdrawalsFromEventLogsSequentially({
sender,
receiver,
provider,
fromBlock = 0,
toBlock = 'latest',
delayMs = 2_000
}: FetchTokenWithdrawalsFromEventLogsSequentiallyParams): Promise<FetchTokenWithdrawalsFromEventLogsSequentiallyResult> {
// keep track of priority; increment as queries are added
let priority = 0
// keep track of queries
const queries: TokenWithdrawalQuery[] = []

// helper function to reuse common params
function buildQueryParams({
sender,
receiver,
gateways = []
}: {
sender?: string
receiver?: string
gateways?: string[]
}): TokenWithdrawalQuery['params'] {
return {
sender,
receiver,
fromBlock,
toBlock,
l2Provider: provider,
l2GatewayAddresses: gateways
}
}

// for sanitizing, adding queries and incrementing priority
function addQuery(params: TokenWithdrawalQuery['params']) {
const gateways = params.l2GatewayAddresses ?? []
const gatewaysSanitized = gateways.filter(g => g !== constants.AddressZero)

if (gatewaysSanitized.length === 0) {
return
}

queries.push({
params: { ...params, l2GatewayAddresses: gatewaysSanitized },
priority: ++priority
})
}

const gateways = await getGateways(provider)
const senderNonce = await backOff(() => getNonce(sender, { provider }))

// sender queries; only add if nonce > 0
if (senderNonce > 0) {
addQuery(buildQueryParams({ sender, gateways: [gateways.standardGateway] }))
addQuery(buildQueryParams({ sender, gateways: [gateways.wethGateway] }))
addQuery(buildQueryParams({ sender, gateways: [gateways.customGateway] }))
addQuery(buildQueryParams({ sender, gateways: gateways.otherGateways }))
}

// receiver queries
addQuery(buildQueryParams({ receiver, gateways: [gateways.standardGateway] }))
addQuery(buildQueryParams({ receiver, gateways: [gateways.wethGateway] }))
addQuery(buildQueryParams({ receiver, gateways: [gateways.customGateway] }))
addQuery(buildQueryParams({ receiver, gateways: gateways.otherGateways }))

// for iterating through all priorities in the while loop below
let currentPriority = 1

// final result
const result: FetchTokenWithdrawalsFromEventLogsSequentiallyResult = []

while (currentPriority <= priority) {
const currentPriorityQueries = queries.filter(
query => query.priority === currentPriority
)

const currentPriorityResults = await Promise.all(
currentPriorityQueries.map(query =>
backOff(() => fetchTokenWithdrawalsFromEventLogs(query.params))
)
)

currentPriorityResults.forEach(r => {
result.push(...r)
})

await wait(delayMs)

currentPriority++
}

return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
fetchWithdrawalsFromSubgraph
} from './fetchWithdrawalsFromSubgraph'
import { fetchLatestSubgraphBlockNumber } from '../SubgraphUtils'
import { fetchTokenWithdrawalsFromEventLogs } from './fetchTokenWithdrawalsFromEventLogs'
import { fetchL2Gateways } from '../fetchL2Gateways'

import { Withdrawal } from '../../hooks/useTransactionHistory'
import { attachTimestampToTokenWithdrawal } from './helpers'
import { WithdrawalInitiated } from '../../hooks/arbTokenBridge.types'
import { fetchTokenWithdrawalsFromEventLogsSequentially } from './fetchTokenWithdrawalsFromEventLogsSequentially'
import { backOff, wait } from '../ExponentialBackoffUtils'

export type FetchWithdrawalsParams = {
sender?: string
Expand Down Expand Up @@ -43,8 +44,6 @@ export async function fetchWithdrawals({
const l1ChainID = (await l1Provider.getNetwork()).chainId
const l2ChainID = (await l2Provider.getNetwork()).chainId

const l2GatewayAddresses = await fetchL2Gateways(l2Provider)

if (!fromBlock) {
fromBlock = 0
}
Expand Down Expand Up @@ -85,23 +84,27 @@ export async function fetchWithdrawals({
console.log('Error fetching withdrawals from subgraph', error)
}

const [ethWithdrawalsFromEventLogs, tokenWithdrawalsFromEventLogs] =
await Promise.all([
fetchETHWithdrawalsFromEventLogs({
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
l2Provider: l2Provider
}),
fetchTokenWithdrawalsFromEventLogs({
sender,
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
l2Provider: l2Provider,
l2GatewayAddresses
})
])
const ethWithdrawalsFromEventLogs = await backOff(() =>
fetchETHWithdrawalsFromEventLogs({
receiver,
// not sure why eslint is treating "toBlock" as "number | undefined" here
// even though typescript recognizes it as "number"
fromBlock: toBlock ?? 0 + 1,
toBlock: 'latest',
l2Provider: l2Provider
})
)

await wait(2_000)

const tokenWithdrawalsFromEventLogs =
await fetchTokenWithdrawalsFromEventLogsSequentially({
sender,
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
provider: l2Provider
})

const mappedEthWithdrawalsFromEventLogs: Withdrawal[] =
ethWithdrawalsFromEventLogs.map(tx => {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7545,6 +7545,11 @@ expect@^29.0.0, expect@^29.5.0:
jest-message-util "^29.5.0"
jest-util "^29.5.0"

exponential-backoff@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==

express@^4.17.3:
version "4.21.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915"
Expand Down

0 comments on commit b6911af

Please sign in to comment.