diff --git a/src/lib/addressbook.ts b/src/lib/addressbook.ts index c5a0b4d..64ab919 100644 --- a/src/lib/addressbook.ts +++ b/src/lib/addressbook.ts @@ -6,6 +6,9 @@ export function getChainWNativeTokenDecimals(chain: Chain): number { if (chain === 'mode') { return 18; } + if (chain === 'scroll') { + return 18; + } const tokens = addressbook.addressBook[chain].tokens; return tokens.WNATIVE.decimals; } @@ -14,6 +17,9 @@ export function getChainWNativeTokenSymbol(chain: Chain): string { if (chain === 'mode') { return 'ETH'; } + if (chain === 'scroll') { + return 'ETH'; + } const tokens = addressbook.addressBook[chain].tokens; return tokens.WNATIVE.symbol; } @@ -22,6 +28,9 @@ export function getChainWNativeTokenAddress(chain: Chain): Hex { if (chain === 'mode') { return '0x4200000000000000000000000000000000000006'; } + if (chain === 'scroll') { + return '0x5300000000000000000000000000000000000004'; + } const tokens = addressbook.addressBook[chain].tokens; return tokens.WNATIVE.address as Hex; } @@ -30,6 +39,9 @@ export function getNetworkId(chain: Chain): number { if (chain === 'mode') { return 34443; } + if (chain === 'scroll') { + return 534352; + } const tokens = addressbook.addressBook[chain].tokens; return tokens.WNATIVE.chainId; } diff --git a/src/lib/chain.ts b/src/lib/chain.ts index da7a112..0bee87f 100644 --- a/src/lib/chain.ts +++ b/src/lib/chain.ts @@ -1,5 +1,5 @@ import { addressBook } from 'blockchain-addressbook'; -export type Chain = keyof typeof addressBook | 'mode'; +export type Chain = keyof typeof addressBook | 'mode' | 'scroll'; -export const allChainIds: Chain[] = [...Object.keys(addressBook), 'mode'] as Chain[]; +export const allChainIds: Chain[] = [...Object.keys(addressBook), 'mode', 'scroll'] as Chain[]; diff --git a/src/lib/config.ts b/src/lib/config.ts index 50e34d9..d363540 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -33,7 +33,7 @@ export const DISABLE_COLLECTOR_FOR_CHAINS: Chain[] = ( process.env.DISABLE_COLLECTOR_FOR_CHAINS ? process.env.DISABLE_COLLECTOR_FOR_CHAINS.split(',') : [] ).filter(chain => allChainIds.includes(chain as Chain)) as Chain[]; export const DISCORD_REPORT_WEBHOOK_URL = process.env.DISCORD_REPORT_WEBHOOK_URL || null; -export const DISCORD_REPORT_ONLY_FOR_CHAINS: Chain[] = ['fraxtal']; +export const DISCORD_REPORT_ONLY_FOR_CHAINS: Chain[] = ['fraxtal', 'mode', 'scroll']; export const DISCORD_RATE_LIMIT_MIN_SECONDS_BETWEEN_REQUESTS = parseInt( process.env.DISCORD_RATE_LIMIT_MIN_SECONDS_BETWEEN_REQUESTS || '10', 10 @@ -668,6 +668,26 @@ export const RPC_CONFIG: Record = { maxAmountOfNativeWei: bigintMultiplyFloat(ONE_ETHER, 5.0), }, }, + scroll: { + ...defaultConfig, + url: RPC_FORCE_URL || process.env.SCROLL_RPC_URL || 'https://rpc.scroll.io', + transaction: { + ...defaultTransactionConfig, + type: 'legacy', + maxNativePerTransactionWei: bigintMultiplyFloat(ONE_ETHER, 0.01), + maxGasPricePerTransactionWei: null, + }, + harvest: { + ...defaultHarvestConfig, + setTransactionGasLimit: false, + }, + unwrap: { + ...defaultUnwrapConfig, + minAmountOfWNativeWei: bigintMultiplyFloat(ONE_ETHER, 0.01), + maxAmountOfNativeWei: bigintMultiplyFloat(ONE_ETHER, 0.01), + setTransactionGasLimit: false, + }, + }, zkevm: { ...defaultConfig, url: RPC_FORCE_URL || process.env.ZKEVM_RPC_URL || 'https://rpc.ankr.com/polygon_zkevm', @@ -897,6 +917,13 @@ export const EXPLORER_CONFIG: Record = { apiKey: process.env.POLYGON_EXPLORER_API_KEY || '', type: 'etherscan', }, + scroll: { + addressLinkTemplate: 'https://scrollscan.com/address/${address}', + transactionLinkTemplate: 'https://scrollscan.com/tx/${hash}', + apiUrl: process.env.SCROLL_EXPLORER_API_URL || 'https://api.scrollscan.com/api', + apiKey: process.env.SCROLL_EXPLORER_API_KEY || '', + type: 'etherscan', + }, zkevm: { addressLinkTemplate: 'https://zkevm.polygonscan.com/address/${address}', transactionLinkTemplate: 'https://zkevm.polygonscan.com/tx/${hash}', diff --git a/src/lib/rpc-client.ts b/src/lib/rpc-client.ts index 939584a..bc67dd3 100644 --- a/src/lib/rpc-client.ts +++ b/src/lib/rpc-client.ts @@ -24,6 +24,7 @@ import { metis, optimism, polygonZkEvm, + scroll, zkSync, } from 'viem/chains'; import { loggingHttpTransport } from './rpc-transport'; @@ -73,6 +74,7 @@ const VIEM_CHAINS: Record = { one: applyConfig('one', harmonyOne), optimism: applyConfig('optimism', optimism), polygon: applyConfig('polygon', polygon), + scroll: applyConfig('scroll', scroll), zkevm: applyConfig('zkevm', polygonZkEvm), zksync: applyConfig('zksync', zkSync), }; diff --git a/src/lib/rpc-transport.ts b/src/lib/rpc-transport.ts index 88c05ca..d3dabc3 100644 --- a/src/lib/rpc-transport.ts +++ b/src/lib/rpc-transport.ts @@ -3,17 +3,19 @@ import { rootLogger } from '../util/logger'; const logger = rootLogger.child({ module: 'rpc-transport' }); -export function loggingHttpTransport( - /** URL of the JSON-RPC API. Defaults to the chain's public RPC URL. */ - url?: string, - config: HttpTransportConfig = {} -): HttpTransport { +export function loggingHttpTransport(url?: string, config: HttpTransportConfig = {}): HttpTransport { return http(url, { - onFetchRequest: request => { - logger.trace({ msg: 'rpc.http: request', data: request }); + onFetchRequest: async request => { + const content = await request.json(); + logger.trace({ msg: 'rpc.http: request', data: content }); + // @ts-ignore: avoid `Body is unusable` error + request.json = async () => content; }, - onFetchResponse(response) { - logger.debug({ msg: 'rpc.http: response', data: response }); + onFetchResponse: async response => { + const content = await response.json(); + logger.debug({ msg: 'rpc.http: response', data: content }); + // @ts-ignore: avoid `Body is unusable` error + response.json = async () => content; }, ...config, });