Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions packages/retryable-monitor/__tests__/reportRetryables.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest'
import { ethers } from 'ethers'
import { formatL2Callvalue } from '../reportRetryables'
import { ChildChainTicketReport } from '../types'

describe('reportRetryables', () => {
describe('formatL2Callvalue', () => {
it('should format ETH amounts correctly', async () => {
const ticket = {
deposit: {
amount: ethers.utils.parseEther('12.0').toString(),
symbol: 'ETH',
decimals: 18,
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 12.0 ETH')
})

it('should format USDC amounts with 6 decimals', async () => {
const ticket = {
deposit: {
amount: '12000000', // 12 USDC with 6 decimals
symbol: 'USDC',
decimals: 6,
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 12.0 USDC')
})

it('should handle amounts with default 18 decimals when decimals not specified', async () => {
const ticket = {
deposit: {
amount: ethers.utils.parseEther('12.0').toString(), // 12 with 18 decimals
symbol: 'XAI',
// no decimals field
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 12.0 XAI')
})

it('should handle real transaction data from Arbitrum chain', async () => {
const ticket = {
deposit: {
amount: '12000000000000000000', // 12 ETH in wei
symbol: 'ETH',
decimals: 18,
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 12.0 ETH')
})

it('should handle real USDC transaction data', async () => {
const ticket = {
deposit: {
amount: '120000000', // 12 USDC with 6 decimals
symbol: 'USDC',
decimals: 6,
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 12.0 USDC')
})

it('should handle very small amounts', async () => {
const ticket = {
deposit: {
amount: '1', // 0.000001 USDC
symbol: 'USDC',
decimals: 6,
},
} as ChildChainTicketReport

const result = await formatL2Callvalue(ticket)
expect(result).toBe('\n\t *Child chain callvalue:* 0.000001 USDC')
})
})
})
35 changes: 34 additions & 1 deletion packages/retryable-monitor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,46 @@ const processChildChain = async (
await childChainProvider.getBlock(childChainTxReceipt.blockNumber)
).timestamp

/**
* Get native token symbol - if nativeToken address is set, look up the symbol
*/
let nativeTokenSymbol = 'ETH'
let nativeTokenDecimals = 18

if (
childChain.nativeToken &&
childChain.nativeToken !== '0x0000000000000000000000000000000000000000'
) {
try {
const tokenContract = ERC20__factory.connect(
childChain.nativeToken,
parentChainProvider
)
const [symbol, decimals] = await Promise.all([
tokenContract.symbol(),
tokenContract.decimals(),
])
nativeTokenSymbol = symbol
nativeTokenDecimals = decimals
} catch (e) {
console.warn(
`Failed to get native token info for ${childChain.name}:`,
e
)
}
}

const childChainTicketReport = {
id: retryableMessage.retryableCreationId,
retryTxHash: retryableMessage.retryableCreationId,
createdAtTimestamp: String(timestamp),
createdAtBlockNumber: childChainTxReceipt.blockNumber,
timeoutTimestamp: String(Number(timestamp) + SEVEN_DAYS_IN_SECONDS),
deposit: String(retryableMessage.messageData.l2CallValue), // eth amount
deposit: {
amount: String(retryableMessage.messageData.l2CallValue),
symbol: nativeTokenSymbol,
decimals: nativeTokenDecimals,
},
status: ParentToChildMessageStatus[status],
retryTo: childChainTxReceipt.to,
retryData: retryableMessage.messageData.data,
Expand Down
6 changes: 5 additions & 1 deletion packages/retryable-monitor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"lint": "eslint .",
"build": "rm -rf ./dist && tsc",
"format": "prettier './**/*.{js,json,md,yml,sol,ts}' --write && yarn run lint --fix",
"dev": "yarn build && node ./dist/retryable-monitor/index.js"
"dev": "yarn build && node ./dist/retryable-monitor/index.js",
"test": "vitest"
},
"devDependencies": {
"vitest": "^1.4.0"
}
}
55 changes: 10 additions & 45 deletions packages/retryable-monitor/reportRetryables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ export const reportFailedTicket = async ({
*
*/

let ethPriceCache: number
let tokenPriceCache: { [key: string]: number } = {}

const getTimeDifference = (timestampInSeconds: number) => {
const now = new Date().getTime() / 1000
const difference = timestampInSeconds - now
Expand Down Expand Up @@ -218,10 +215,12 @@ const formatL2ExecutionTX = (
}>`
}

const formatL2Callvalue = async (ticket: ChildChainTicketReport) => {
const ethAmount = ethers.utils.formatEther(ticket.deposit)
const depositWorthInUsd = (+ethAmount * (await getEthPrice())).toFixed(2)
return `\n\t *Child chain callvalue:* ${ethAmount} ETH ($${depositWorthInUsd})`
export const formatL2Callvalue = async (ticket: ChildChainTicketReport) => {
const amount = ethers.utils.formatUnits(
ticket.deposit.amount,
ticket.deposit.decimals || 18 // fallback to 18 decimals if not specified
)
return `\n\t *Child chain callvalue:* ${amount} ${ticket.deposit.symbol}`
}

const formatTokenDepositData = async (
Expand All @@ -237,13 +236,7 @@ const formatTokenDepositData = async (
? ethers.utils.formatUnits(deposit.tokenAmount, deposit.l1Token.decimals)
: '-'

const tokenPriceInUSD = await getTokenPrice(deposit.l1Token.id)
if (tokenPriceInUSD !== undefined) {
const depositWorthInUSD = (+amount * tokenPriceInUSD).toFixed(2)
msg = `${msg} ${amount} ${deposit.l1Token.symbol} (\$${depositWorthInUSD}) (${deposit.l1Token.id})`
} else {
msg = `${msg} ${amount} ${deposit.l1Token.symbol} (${deposit.l1Token.id})`
}
msg = `${msg} ${amount} ${deposit.l1Token.symbol} (${deposit.l1Token.id})`

return msg
}
Expand All @@ -255,9 +248,9 @@ const formatDestination = async (
let msg = `\n\t *Destination:* `
const { CHILD_CHAIN_ADDRESS_PREFIX } = getExplorerUrlPrefixes(childChain)

return `${msg}<${CHILD_CHAIN_ADDRESS_PREFIX + ticket.retryTo}|${
ticket.retryTo
}>`
return ticket.retryTo
? `${msg}<${CHILD_CHAIN_ADDRESS_PREFIX + ticket.retryTo}|${ticket.retryTo}>`
: `${msg}Contract Creation`
}

const formatGasData = async (
Expand Down Expand Up @@ -318,34 +311,6 @@ const formatExpiration = (ticket: ChildChainTicketReport) => {
return msg
}

const getEthPrice = async () => {
if (ethPriceCache !== undefined) {
return ethPriceCache
}

const url =
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'
const response = await axios.get(url)
ethPriceCache = +response.data['ethereum'].usd
return ethPriceCache
}

const getTokenPrice = async (tokenAddress: string) => {
if (tokenPriceCache[tokenAddress] !== undefined) {
return tokenPriceCache[tokenAddress]
}

const url = `https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenAddress}&vs_currencies=usd`

const response = await axios.get(url)
if (response.data[tokenAddress] == undefined) {
return undefined
}

tokenPriceCache[tokenAddress] = +response.data[tokenAddress].usd
return tokenPriceCache[tokenAddress]
}

// Unix timestamp
export const getPastTimestamp = (daysAgoInMs: number) => {
const now = new Date().getTime()
Expand Down
8 changes: 6 additions & 2 deletions packages/retryable-monitor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ export interface ChildChainTicketReport {
createdAtTimestamp: string
createdAtBlockNumber: number
timeoutTimestamp: string
deposit: string
deposit: {
amount: string
symbol: string
decimals?: number
}
status: string
retryTo: string
retryTo: string | null
retryData: string
gasFeeCap: number
gasLimit: number
Expand Down
8 changes: 8 additions & 0 deletions packages/retryable-monitor/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
globals: true,
environment: 'node',
},
})
Loading