diff --git a/.changeset/curly-carpets-rush.md b/.changeset/curly-carpets-rush.md new file mode 100644 index 00000000..49b3a14e --- /dev/null +++ b/.changeset/curly-carpets-rush.md @@ -0,0 +1,5 @@ +--- +'@rosen-bridge/rosen-service': minor +--- + +Add binance chain scanner with observation and event trigger extractors diff --git a/apps/rosen-service/config/default.yaml b/apps/rosen-service/config/default.yaml index f8377520..17fe7d36 100644 --- a/apps/rosen-service/config/default.yaml +++ b/apps/rosen-service/config/default.yaml @@ -46,6 +46,16 @@ ethereum: rwt: initialHeight: rpcUrl: https://eth-mainnet.public.blastapi.io +binance: + addresses: + lock: + eventTrigger: + permit: + fraud: + tokens: + rwt: + initialHeight: + rpcUrl: https://bsc-mainnet.public.blastapi.io/ # rpcAuthToken: postgres: url: # postgresql://username:password@host:port/databasename diff --git a/apps/rosen-service/src/configs.ts b/apps/rosen-service/src/configs.ts index 80d6fca2..f944cb5b 100644 --- a/apps/rosen-service/src/configs.ts +++ b/apps/rosen-service/src/configs.ts @@ -33,7 +33,7 @@ const getConfig = () => { addresses: { lock: nodeConfig.get('cardano.addresses.lock'), eventTrigger: nodeConfig.get( - 'cardano.addresses.eventTrigger', + 'cardano.addresses.eventTrigger' ), permit: nodeConfig.get('cardano.addresses.permit'), fraud: nodeConfig.get('cardano.addresses.fraud'), @@ -49,7 +49,7 @@ const getConfig = () => { addresses: { lock: nodeConfig.get('bitcoin.addresses.lock'), eventTrigger: nodeConfig.get( - 'bitcoin.addresses.eventTrigger', + 'bitcoin.addresses.eventTrigger' ), permit: nodeConfig.get('bitcoin.addresses.permit'), fraud: nodeConfig.get('bitcoin.addresses.fraud'), @@ -67,7 +67,7 @@ const getConfig = () => { addresses: { lock: nodeConfig.get('ethereum.addresses.lock'), eventTrigger: nodeConfig.get( - 'ethereum.addresses.eventTrigger', + 'ethereum.addresses.eventTrigger' ), permit: nodeConfig.get('ethereum.addresses.permit'), fraud: nodeConfig.get('ethereum.addresses.fraud'), @@ -79,6 +79,22 @@ const getConfig = () => { rpcUrl: nodeConfig.get('ethereum.rpcUrl'), rpcAuthToken: getOptionalString('ethereum.rpcAuthToken'), }, + binance: { + addresses: { + lock: nodeConfig.get('binance.addresses.lock'), + eventTrigger: nodeConfig.get( + 'binance.addresses.eventTrigger' + ), + permit: nodeConfig.get('binance.addresses.permit'), + fraud: nodeConfig.get('binance.addresses.fraud'), + }, + initialHeight: nodeConfig.get('binance.initialHeight'), + tokens: { + rwt: nodeConfig.get('binance.tokens.rwt'), + }, + rpcUrl: nodeConfig.get('binance.rpcUrl'), + rpcAuthToken: getOptionalString('binance.rpcAuthToken'), + }, postgres: { url: nodeConfig.get('postgres.url'), logging: nodeConfig.get('postgres.logging'), @@ -98,7 +114,7 @@ const getConfig = () => { `an error occurred reading some service configs: ${error}`, false, 'error', - error instanceof Error ? error.stack : undefined, + error instanceof Error ? error.stack : undefined ); } }; diff --git a/apps/rosen-service/src/constants.ts b/apps/rosen-service/src/constants.ts index 77be0b9b..edffd938 100644 --- a/apps/rosen-service/src/constants.ts +++ b/apps/rosen-service/src/constants.ts @@ -4,9 +4,11 @@ export const ERGO_SCANNER_INTERVAL = 2 * 60 * 1000; export const CARDANO_SCANNER_INTERVAL = 30 * 1000; export const BITCOIN_SCANNER_INTERVAL = 10 * 60 * 1000; export const ETHEREUM_SCANNER_INTERVAL = 60 * 1000; +export const BINANCE_SCANNER_INTERVAL = 10 * 1000; export const ASSET_CALCULATOR_INTERVAL = 30 * 1000; export const ERGO_SCANNER_LOGGER_NAME = 'ergo-scanner'; export const CARDANO_SCANNER_LOGGER_NAME = 'cardano-scanner'; export const BITCOIN_SCANNER_LOGGER_NAME = 'bitcoin-scanner'; export const ETHEREUM_SCANNER_LOGGER_NAME = 'ethereum-scanner'; +export const BINANCE_SCANNER_LOGGER_NAME = 'binance-scanner'; diff --git a/apps/rosen-service/src/event-trigger/event-trigger-service.ts b/apps/rosen-service/src/event-trigger/event-trigger-service.ts index 7d505c46..7e7cac55 100644 --- a/apps/rosen-service/src/event-trigger/event-trigger-service.ts +++ b/apps/rosen-service/src/event-trigger/event-trigger-service.ts @@ -17,6 +17,8 @@ const bitcoinEventTriggerExtractorLogger = WinstonLogger.getInstance().getLogger('bitcoin-event-trigger-extractor'); const ethereumEventTriggerExtractorLogger = WinstonLogger.getInstance().getLogger('ethereum-event-trigger-extractor'); +const binanceEventTriggerExtractorLogger = + WinstonLogger.getInstance().getLogger('binance-event-trigger-extractor'); /** * register event trigger extractors for all chains @@ -68,11 +70,23 @@ export const registerExtractors = (scanner: ErgoScanner) => { configs.ethereum.addresses.fraud, ethereumEventTriggerExtractorLogger, ); + const binanceEventTriggerExtractor = new EventTriggerExtractor( + 'binance-extractor', + dataSource, + ErgoNetworkType.Explorer, + configs.ergo.explorerUrl, + configs.binance.addresses.eventTrigger, + configs.binance.tokens.rwt, + configs.binance.addresses.permit, + configs.binance.addresses.fraud, + binanceEventTriggerExtractorLogger, + ); scanner.registerExtractor(ergoEventTriggerExtractor); scanner.registerExtractor(cardanoEventTriggerExtractor); scanner.registerExtractor(bitcoinEventTriggerExtractor); scanner.registerExtractor(ethereumEventTriggerExtractor); + scanner.registerExtractor(binanceEventTriggerExtractor); logger.debug('event trigger extractors registered', { scannerName: scanner.name(), @@ -81,6 +95,7 @@ export const registerExtractors = (scanner: ErgoScanner) => { cardanoEventTriggerExtractor.getId(), bitcoinEventTriggerExtractor.getId(), ethereumEventTriggerExtractor.getId(), + binanceEventTriggerExtractor.getId(), ], }); } catch (error) { diff --git a/apps/rosen-service/src/observation/chains/binance.ts b/apps/rosen-service/src/observation/chains/binance.ts new file mode 100644 index 00000000..f557ea3c --- /dev/null +++ b/apps/rosen-service/src/observation/chains/binance.ts @@ -0,0 +1,41 @@ +import { BinanceRpcObservationExtractor } from '@rosen-bridge/evm-observation-extractor'; +import { EvmRpcScanner } from '@rosen-bridge/evm-rpc-scanner'; +import WinstonLogger from '@rosen-bridge/winston-logger'; + +import config from '../../configs'; +import dataSource from '../../data-source'; +import AppError from '../../errors/AppError'; +import { getRosenTokens } from '../../utils'; + +const logger = WinstonLogger.getInstance().getLogger(import.meta.url); + +/** + * register an observation extractor for the provided scanner + * @param scanner + */ +export const registerBinanceExtractor = (scanner: EvmRpcScanner) => { + try { + const observationExtractor = new BinanceRpcObservationExtractor( + config.binance.addresses.lock, + dataSource, + getRosenTokens(), + logger + ); + + scanner.registerExtractor(observationExtractor); + + logger.debug('binance observation extractor registered', { + scannerName: scanner.name(), + }); + } catch (error) { + throw new AppError( + `cannot create or register binance observation extractor due to error: ${error}`, + false, + 'error', + error instanceof Error ? error.stack : undefined, + { + scannerName: scanner.name(), + } + ); + } +}; diff --git a/apps/rosen-service/src/observation/observation-service.ts b/apps/rosen-service/src/observation/observation-service.ts index 58d3ec6e..b7107016 100644 --- a/apps/rosen-service/src/observation/observation-service.ts +++ b/apps/rosen-service/src/observation/observation-service.ts @@ -1,3 +1,4 @@ +import { registerBinanceExtractor } from './chains/binance'; import { registerBitcoinExtractor } from './chains/bitcoin'; import { registerCardanoExtractor } from './chains/cardano'; import { registerErgoExtractor } from './chains/ergo'; @@ -8,6 +9,7 @@ const observationService = { registerCardanoExtractor, registerErgoExtractor, registerEthereumExtractor, + registerBinanceExtractor, }; export default observationService; diff --git a/apps/rosen-service/src/scanner/chains/binance.ts b/apps/rosen-service/src/scanner/chains/binance.ts new file mode 100644 index 00000000..d314cbee --- /dev/null +++ b/apps/rosen-service/src/scanner/chains/binance.ts @@ -0,0 +1,53 @@ +import { EvmRpcScanner } from '@rosen-bridge/evm-rpc-scanner'; +import WinstonLogger from '@rosen-bridge/winston-logger'; + +import config from '../../configs'; +import { + BINANCE_SCANNER_INTERVAL, + BINANCE_SCANNER_LOGGER_NAME, + SCANNER_API_TIMEOUT, +} from '../../constants'; +import dataSource from '../../data-source'; +import AppError from '../../errors/AppError'; +import observationService from '../../observation/observation-service'; +import { startScanner } from '../scanner-utils'; + +const logger = WinstonLogger.getInstance().getLogger(import.meta.url); +const scannerLogger = WinstonLogger.getInstance().getLogger( + BINANCE_SCANNER_LOGGER_NAME +); + +/** + * create a binance scanner, initializing it and calling its update method + * periodically + */ +export const startBinanceScanner = async () => { + try { + const scanner = new EvmRpcScanner( + 'binance', + { + RpcUrl: config.binance.rpcUrl, + dataSource, + initialHeight: config.binance.initialHeight, + timeout: SCANNER_API_TIMEOUT, + }, + scannerLogger, + config.binance.rpcAuthToken + ); + + observationService.registerBinanceExtractor(scanner); + + await startScanner(scanner, import.meta.url, BINANCE_SCANNER_INTERVAL); + + logger.debug('binance scanner started'); + + return scanner; + } catch (error) { + throw new AppError( + `cannot create or start binance scanner due to error: ${error}`, + false, + 'error', + error instanceof Error ? error.stack : undefined + ); + } +}; diff --git a/apps/rosen-service/src/scanner/scanner-service.ts b/apps/rosen-service/src/scanner/scanner-service.ts index 15f9bfd6..c3b2d081 100644 --- a/apps/rosen-service/src/scanner/scanner-service.ts +++ b/apps/rosen-service/src/scanner/scanner-service.ts @@ -1,6 +1,7 @@ import WinstonLogger from '@rosen-bridge/winston-logger/dist/WinstonLogger'; import { handleError } from '../utils'; +import { startBinanceScanner } from './chains/binance'; import { startBitcoinScanner } from './chains/bitcoin'; import { startCardanoScanner } from './chains/cardano'; import { startErgoScanner } from './chains/ergo'; @@ -13,13 +14,19 @@ const logger = WinstonLogger.getInstance().getLogger(import.meta.url); */ const start = async () => { try { - const [ergoScanner, cardanoScanner, bitcoinScanner, ethereumScanner] = - await Promise.all([ - startErgoScanner(), - startCardanoScanner(), - startBitcoinScanner(), - startEthereumScanner(), - ]); + const [ + ergoScanner, + cardanoScanner, + bitcoinScanner, + ethereumScanner, + binanceScanner, + ] = await Promise.all([ + startErgoScanner(), + startCardanoScanner(), + startBitcoinScanner(), + startEthereumScanner(), + startBinanceScanner(), + ]); logger.debug('all scanners started and their extractors registered', { scannerNames: [ @@ -27,6 +34,7 @@ const start = async () => { cardanoScanner.name(), bitcoinScanner.name(), ethereumScanner.name(), + binanceScanner.name(), ], }); } catch (error) {