diff --git a/.changeset/base-watcher-support.md b/.changeset/base-watcher-support.md new file mode 100644 index 00000000..c338e1f8 --- /dev/null +++ b/.changeset/base-watcher-support.md @@ -0,0 +1,5 @@ +--- +'@rosen-bridge/watcher': minor +--- + +Add Base watcher support and switch the default Base RPC config to a production-provider placeholder. diff --git a/config/default.yaml b/config/default.yaml index 540d78b6..bdb4635b 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -1,5 +1,5 @@ --- -network: '' # which scanner network used in this watcher ergo/cardano/bitcoin/ethereum +network: '' # which scanner network used in this watcher ergo/cardano/bitcoin/base/ethereum observation: # confirmation: 60 # number of required block confirmations to create the commitment after observing an event validThreshold: 259200 # Observations that have not been triggered will not be processed after this period (in seconds, converted to block count based on selected network block time) @@ -58,6 +58,15 @@ cardano: projectId: '' # blockfrost project Id timeout: 10 # blockfrost request timeout interval: 20 # blockfrost check timeout +base: + type: '' # options: rpc + initial: + height: -1 # initial height of scanning + interval: 20 # scanning interval (in seconds) + rpc: + url: '' # rpc url (use a production provider; the public Base endpoint is rate-limited) + timeout: 10 # rpc request timeout (in seconds) + # authToken: # rpc auth token ethereum: type: '' # options: rpc initial: diff --git a/docker/custom-environment-variables.yaml b/docker/custom-environment-variables.yaml index 2847b00b..fe2e68aa 100644 --- a/docker/custom-environment-variables.yaml +++ b/docker/custom-environment-variables.yaml @@ -24,6 +24,9 @@ cardano: authToken: 'KOIOS_AUTH_TOKEN' blockfrost: projectId: 'BLOCKFROST_PROJECT_ID' +base: + rpc: + authToken: 'BASE_RPC_AUTH_TOKEN' ethereum: rpc: authToken: 'ETHEREUM_RPC_AUTH_TOKEN' diff --git a/package-lock.json b/package-lock.json index d0bd473e..2ef9c23f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rosen-bridge/discord-notification": "^1.0.0", "@rosen-bridge/ergo-observation-extractor": "^1.0.4", "@rosen-bridge/ergo-scanner": "^1.0.3", - "@rosen-bridge/evm-observation-extractor": "^6.0.4", + "@rosen-bridge/evm-observation-extractor": "^6.1.0", "@rosen-bridge/evm-scanner": "^1.0.3", "@rosen-bridge/extended-typeorm": "1.0.1", "@rosen-bridge/firo-observation-extractor": "^0.1.2", @@ -3783,8 +3783,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@rosen-bridge/extended-typeorm/node_modules/signal-exit": { "version": "4.1.0", @@ -4866,7 +4865,6 @@ "version": "5.38.0", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.38.0", "@typescript-eslint/types": "5.38.0", @@ -5086,7 +5084,6 @@ "version": "8.10.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5875,7 +5872,6 @@ "version": "4.3.10", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -7258,7 +7254,6 @@ "version": "8.23.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.2", "@humanwhocodes/config-array": "^0.10.4", @@ -11214,7 +11209,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.10.1", "pg-pool": "^3.11.0", @@ -11491,7 +11485,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -12286,7 +12279,6 @@ "version": "3.29.2", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -13136,7 +13128,6 @@ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -13935,7 +13926,6 @@ "version": "4.8.3", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14325,7 +14315,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index 1990b46f..9794b34e 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@rosen-bridge/discord-notification": "^1.0.0", "@rosen-bridge/ergo-observation-extractor": "^1.0.4", "@rosen-bridge/ergo-scanner": "^1.0.3", - "@rosen-bridge/evm-observation-extractor": "^6.0.4", + "@rosen-bridge/evm-observation-extractor": "^6.1.0", "@rosen-bridge/evm-scanner": "^1.0.3", "@rosen-bridge/extended-typeorm": "1.0.1", "@rosen-bridge/firo-observation-extractor": "^0.1.2", diff --git a/src/config/config.ts b/src/config/config.ts index aab91b34..24fb1dbe 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -17,6 +17,7 @@ const supportedNetworks: Array = [ Constants.BITCOIN_CHAIN_NAME, Constants.BITCOIN_RUNES_CHAIN_NAME, Constants.DOGE_CHAIN_NAME, + Constants.BASE_CHAIN_NAME, Constants.ETHEREUM_CHAIN_NAME, Constants.BINANCE_CHAIN_NAME, Constants.FIRO_CHAIN_NAME, @@ -27,6 +28,7 @@ interface ConfigType { cardano: CardanoConfig; bitcoin: BitcoinConfig; bitcoinRunes: BitcoinRunesConfig; + base: BaseConfig; ethereum: EthereumConfig; binance: BinanceConfig; firo: FiroConfig; @@ -235,6 +237,7 @@ class Config { [Constants.BITCOIN_CHAIN_NAME]: Constants.BITCOIN_BLOCK_TIME, [Constants.BITCOIN_RUNES_CHAIN_NAME]: Constants.BITCOIN_BLOCK_TIME, [Constants.CARDANO_CHAIN_NAME]: Constants.CARDANO_BLOCK_TIME, + [Constants.BASE_CHAIN_NAME]: Constants.BASE_BLOCK_TIME, [Constants.BINANCE_CHAIN_NAME]: Constants.BINANCE_BLOCK_TIME, [Constants.ETHEREUM_CHAIN_NAME]: Constants.ETHEREUM_BLOCK_TIME, [Constants.DOGE_CHAIN_NAME]: Constants.DOGE_BLOCK_TIME, @@ -579,6 +582,35 @@ class EthereumConfig { } } +class BaseConfig { + type: string; + initialHeight: number; + interval: number; + rpc?: { + url: string; + timeout: number; + authToken?: string; + }; + + constructor(network: string) { + this.type = config.get('base.type'); + if (network === Constants.BASE_CHAIN_NAME) { + this.initialHeight = getRequiredNumber('base.initial.height'); + this.interval = getRequiredNumber('base.interval'); + if (this.type == Constants.EVM_RPC_TYPE) { + const url = getRequiredString('base.rpc.url'); + const timeout = getRequiredNumber('base.rpc.timeout'); + const authToken = getOptionalString('base.rpc.authToken', undefined); + this.rpc = { url, timeout, authToken }; + } else { + throw new Error( + `Improperly configured. base configuration type is invalid available choices are '${Constants.EVM_RPC_TYPE}'` + ); + } + } + } +} + class BinanceConfig { type: string; initialHeight: number; @@ -747,6 +779,7 @@ const getConfig = (): ConfigType => { const bitcoin = new BitcoinConfig(general.networkWatcher); const bitcoinRunes = new BitcoinRunesConfig(general.networkWatcher); const doge = new DogeConfig(general.networkWatcher); + const base = new BaseConfig(general.networkWatcher); const ethereum = new EthereumConfig(general.networkWatcher); const binance = new BinanceConfig(general.networkWatcher); const firo = new FiroConfig(general.networkWatcher); @@ -762,6 +795,7 @@ const getConfig = (): ConfigType => { bitcoin, bitcoinRunes, doge, + base, ethereum, binance, firo, @@ -783,6 +817,7 @@ export { CardanoConfig, BitcoinConfig, BitcoinRunesConfig, + BaseConfig, EthereumConfig, BinanceConfig, FiroConfig, diff --git a/src/config/constants.ts b/src/config/constants.ts index 099335de..ced173cf 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -16,6 +16,7 @@ export const CARDANO_CHAIN_NAME = 'cardano'; export const BITCOIN_CHAIN_NAME = 'bitcoin'; export const BITCOIN_RUNES_CHAIN_NAME = 'bitcoin-runes'; export const DOGE_CHAIN_NAME = 'doge'; +export const BASE_CHAIN_NAME = 'base'; export const ETHEREUM_CHAIN_NAME = 'ethereum'; export const BINANCE_CHAIN_NAME = 'binance'; export const FIRO_CHAIN_NAME = 'firo'; @@ -32,6 +33,7 @@ export const BINANCE_BLOCK_TIME = 0.75; export const BITCOIN_BLOCK_TIME = 600; export const CARDANO_BLOCK_TIME = 20; export const ERGO_BLOCK_TIME = 120; +export const BASE_BLOCK_TIME = 2; export const ETHEREUM_BLOCK_TIME = 12; export const DOGE_BLOCK_TIME = 60; export const FIRO_BLOCK_TIME = 150; diff --git a/src/jobs/initScanner.ts b/src/jobs/initScanner.ts index 0f1096d2..1da5eb15 100644 --- a/src/jobs/initScanner.ts +++ b/src/jobs/initScanner.ts @@ -13,6 +13,7 @@ const { bitcoin: bitcoinConfig, bitcoinRunes: bitcoinRunesConfig, doge: dogeConfig, + base: baseConfig, ethereum: ethereumConfig, binance: binanceConfig, firo: firoConfig, @@ -75,6 +76,12 @@ export const scannerInit = () => { scanner.getObservationScanner() as EvmRpcScanner ).then(() => null); break; + case Constants.BASE_CHAIN_NAME: + scanningJob( + baseConfig.interval, + scanner.getObservationScanner() as EvmRpcScanner + ).then(() => null); + break; case Constants.BINANCE_CHAIN_NAME: scanningJob( binanceConfig.interval, diff --git a/src/types/config.ts b/src/types/config.ts index 5efedf7c..a714f9d6 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -6,6 +6,7 @@ type NetworkType = | typeof Constants.BITCOIN_CHAIN_NAME | typeof Constants.BITCOIN_RUNES_CHAIN_NAME | typeof Constants.DOGE_CHAIN_NAME + | typeof Constants.BASE_CHAIN_NAME | typeof Constants.ETHEREUM_CHAIN_NAME | typeof Constants.BINANCE_CHAIN_NAME | typeof Constants.FIRO_CHAIN_NAME; diff --git a/src/utils/healthCheck.ts b/src/utils/healthCheck.ts index c9ce9b3a..37583289 100644 --- a/src/utils/healthCheck.ts +++ b/src/utils/healthCheck.ts @@ -24,6 +24,8 @@ import { LogLevelHealthCheck } from '@rosen-bridge/log-level-check'; import { Transaction } from '../api/Transaction'; import { getConfig } from '../config/config'; import { + BASE_BLOCK_TIME, + BASE_CHAIN_NAME, BINANCE_BLOCK_TIME, BINANCE_CHAIN_NAME, BITCOIN_BLOCK_TIME, @@ -238,6 +240,11 @@ class HealthCheckSingleton { chainBlockTime = DOGE_BLOCK_TIME; updateInterval = getConfig().doge.interval; break; + case BASE_CHAIN_NAME: + chainName = BASE_CHAIN_NAME; + chainBlockTime = BASE_BLOCK_TIME; + updateInterval = getConfig().base.interval; + break; case ETHEREUM_CHAIN_NAME: chainName = ETHEREUM_CHAIN_NAME; chainBlockTime = ETHEREUM_BLOCK_TIME; diff --git a/src/utils/networkConnectorManagers.ts b/src/utils/networkConnectorManagers.ts index 5d18785b..bbeda9c3 100644 --- a/src/utils/networkConnectorManagers.ts +++ b/src/utils/networkConnectorManagers.ts @@ -243,7 +243,7 @@ export const createCardanoBlockfrostNetworkConnectorManager = () => { /** * Creates and configures a NetworkConnectorManager instance for EVM-based scanners (Ethereum/Binance) - * @param chainName - The name of the chain to create a connector for (ethereum/binance) + * @param chainName - The name of the chain to create a connector for (base/ethereum/binance) */ export const createEvmNetworkConnectorManager = (chainName: string) => { const networkConnectorManager = @@ -252,7 +252,15 @@ export const createEvmNetworkConnectorManager = (chainName: string) => { evmLogger ); - if (chainName === 'ethereum' && config.ethereum.rpc) { + if (chainName === 'base' && config.base.rpc) { + networkConnectorManager.addConnector( + new EvmRpcNetwork( + config.base.rpc.url, + config.base.rpc.timeout * 1000, + config.base.rpc.authToken || undefined + ) + ); + } else if (chainName === 'ethereum' && config.ethereum.rpc) { networkConnectorManager.addConnector( new EvmRpcNetwork( config.ethereum.rpc.url, diff --git a/src/utils/scanner.ts b/src/utils/scanner.ts index 7e2787a1..f2e158ed 100644 --- a/src/utils/scanner.ts +++ b/src/utils/scanner.ts @@ -40,6 +40,7 @@ import { } from '@rosen-bridge/bitcoin-runes-observation-extractor'; import { + BaseRpcObservationExtractor, BinanceRpcObservationExtractor, EthereumRpcObservationExtractor, } from '@rosen-bridge/evm-observation-extractor'; @@ -50,6 +51,7 @@ import { dataSource } from '../../config/dataSource'; import { BinanceConfig, BitcoinConfig, + BaseConfig, CardanoConfig, Config, EthereumConfig, @@ -126,6 +128,7 @@ class CreateScanner { bitcoin: bitcoinConfig, bitcoinRunes: bitcoinRunesConfig, doge: dogeConfig, + base: baseConfig, ethereum: ethereumConfig, binance: binanceConfig, firo: firoConfig, @@ -162,6 +165,13 @@ class CreateScanner { config.observationStoreRawData ); break; + case Constants.BASE_CHAIN_NAME: + await CreateScanner.instance.createBaseScanner( + baseConfig, + rosenConfig, + config.observationStoreRawData + ); + break; case Constants.BINANCE_CHAIN_NAME: await CreateScanner.instance.createBinanceScanner( binanceConfig, @@ -557,6 +567,32 @@ class CreateScanner { } }; + private createBaseScanner = async ( + baseConfig: BaseConfig, + rosenConfig: RosenConfig, + observationStoreRawData: boolean + ) => { + if (!this.observationScanner) { + if (baseConfig.rpc) { + this.observationScanner = new EvmRpcScanner(Constants.BASE_CHAIN_NAME, { + dataSource, + initialHeight: baseConfig.initialHeight, + network: createEvmNetworkConnectorManager(Constants.BASE_CHAIN_NAME), + logger: loggers.observationScannerLogger, + }); + + const observationExtractor = new BaseRpcObservationExtractor( + rosenConfig.lockAddress, + dataSource, + TokensConfig.getInstance().getTokenMap(), + loggers.observationExtractorLogger, + observationStoreRawData + ); + this.observationScanner.registerExtractor(observationExtractor); + } + } + }; + private createBinanceScanner = async ( binanceConfig: BinanceConfig, rosenConfig: RosenConfig,