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
11 changes: 11 additions & 0 deletions .changeset/firo-asset-calculator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@rosen-ui/asset-calculator': minor
'@rosen-ui/constants': minor
---

Add Firo chain support to asset-calculator

- Register `firo` in the `NETWORKS` constant (no token support, native token `firo`).
- Add `FiroCalculator` that reads locked amounts from the Firo insight-api
(`/insight-api-zcoin/addr/{address}/balance`).
- Add `FiroCalculatorInterface` and register `FiroCalculator` in `AssetCalculator`.
17 changes: 17 additions & 0 deletions .changeset/firo-rosen-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@rosen-bridge/rosen-service': minor
---

Add Firo chain support to rosen-service

- Add `@rosen-bridge/firo-scanner` and `@rosen-bridge/firo-observation-extractor`
as dependencies.
- Wire up a new `FiroRpcScanner` in `scanner-service`, reading from the
configured Firo RPC node.
- Register `FiroRpcObservationExtractor` for the lock address.
- Register the `firo-extractor` event-trigger extractor on the Ergo scanner.
- Register a Firo scanner-sync health check.
- Pass the Firo calculator config (addresses + `explorerUrl`) to
`AssetCalculator`.
- Add `firo` network section plus health-check thresholds and
`calculator.addresses.firo` defaults to `config/default.yaml`.
7 changes: 7 additions & 0 deletions .changeset/firo-utils.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rosen-ui/utils': minor
---

Add Firo URL entries to `getAddressUrl`, `getTxUrl`, and `getTokenUrl`.

Required because `Network = keyof typeof NETWORKS` now includes `'firo'`, and the three maps are exhaustive `{ [key in Network]: ... }` — without these entries, `tsc --noEmit` fails in `packages/utils`. Uses Firo's native explorer (`https://explorer.firo.org`) for address and transaction URLs; token URL is empty (Firo has no native token scheme).
18 changes: 18 additions & 0 deletions apps/rosen-service/config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ doge:
# - url:
# username:
# password:
firo:
addresses:
lock:
eventTrigger:
permit:
fraud:
commitment:
tokens:
rwt:
initialHeight:
explorerUrl: https://explorer.firo.org
rpc:
url:
username:
password:
ethereum:
addresses:
lock:
Expand Down Expand Up @@ -107,6 +122,7 @@ calculator:
doge: []
ethereum: []
binance: []
firo: []
healthCheck:
ergoScannerWarnDiff: 3
ergoScannerCriticalDiff: 5
Expand All @@ -120,6 +136,8 @@ healthCheck:
ethereumScannerCriticalDiff: 5
binanceScannerWarnDiff: 3
binanceScannerCriticalDiff: 5
firoScannerWarnDiff: 3
firoScannerCriticalDiff: 5
interval: 60 # health check update interval (in seconds)
duration: 600 # log duration time check (in seconds)
maxAllowedErrorCount: 1 # maximum allowed error log lines
Expand Down
2 changes: 2 additions & 0 deletions apps/rosen-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"@rosen-bridge/ergo-scanner": "^1.0.2",
"@rosen-bridge/evm-observation-extractor": "^6.0.3",
"@rosen-bridge/evm-scanner": "^1.0.2",
"@rosen-bridge/firo-observation-extractor": "^0.1.2",
"@rosen-bridge/firo-scanner": "^0.1.1",
"@rosen-bridge/health-check": "^8.0.0",
"@rosen-bridge/log-level-check": "^4.0.0",
"@rosen-bridge/scanner-interfaces": "^0.2.2",
Expand Down
4 changes: 4 additions & 0 deletions apps/rosen-service/src/calculator/calculator-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ const start = async () => {
addresses: config.calculator.addresses.doge,
blockcypherUrl: config.doge.blockcypherUrl,
},
{
addresses: config.calculator.addresses.firo,
explorerUrl: config.firo.explorerUrl,
},
dataSource,
logger,
);
Expand Down
24 changes: 24 additions & 0 deletions apps/rosen-service/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,23 @@ const getConfig = () => {
}>
>('doge.rpcConnections'),
},
firo: {
addresses: {
lock: nodeConfig.get<string>('firo.addresses.lock'),
eventTrigger: nodeConfig.get<string>('firo.addresses.eventTrigger'),
permit: nodeConfig.get<string>('firo.addresses.permit'),
fraud: nodeConfig.get<string>('firo.addresses.fraud'),
commitment: nodeConfig.get<string>('firo.addresses.commitment'),
},
initialHeight: nodeConfig.get<number>('firo.initialHeight'),
tokens: {
rwt: nodeConfig.get<string>('firo.tokens.rwt'),
},
explorerUrl: nodeConfig.get<string>('firo.explorerUrl'),
rpcUrl: nodeConfig.get<string>('firo.rpc.url'),
rpcUsername: getOptionalString('firo.rpc.username'),
rpcPassword: getOptionalString('firo.rpc.password'),
},
postgres: {
url: nodeConfig.get<string>('postgres.url'),
logging: nodeConfig.get<boolean>('postgres.logging'),
Expand All @@ -160,6 +177,7 @@ const getConfig = () => {
ethereum: nodeConfig.get<string[]>('calculator.addresses.ethereum'),
binance: nodeConfig.get<string[]>('calculator.addresses.binance'),
doge: nodeConfig.get<string[]>('calculator.addresses.doge'),
firo: nodeConfig.get<string[]>('calculator.addresses.firo'),
},
interval: nodeConfig.get<number>('calculator.interval'),
},
Expand Down Expand Up @@ -200,6 +218,12 @@ const getConfig = () => {
binanceScannerCriticalDiff: nodeConfig.get<number>(
'healthCheck.binanceScannerCriticalDiff',
),
firoScannerWarnDiff: nodeConfig.get<number>(
'healthCheck.firoScannerWarnDiff',
),
firoScannerCriticalDiff: nodeConfig.get<number>(
'healthCheck.firoScannerCriticalDiff',
),
updateInterval: nodeConfig.get<number>('healthCheck.interval'),
logDuration: nodeConfig.get<number>('healthCheck.duration'),
errorLogAllowedCount: nodeConfig.get<number>(
Expand Down
3 changes: 3 additions & 0 deletions apps/rosen-service/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ export const BITCOIN_SCANNER_INTERVAL = 10 * 60 * 1000;
export const DOGE_SCANNER_INTERVAL = 60 * 1000;
export const ETHEREUM_SCANNER_INTERVAL = 60 * 1000;
export const BINANCE_SCANNER_INTERVAL = 10 * 1000;
export const FIRO_SCANNER_INTERVAL = 2 * 60 * 1000;

export const ERGO_SCANNER_LOGGER_NAME = 'ergoScanner';
export const CARDANO_SCANNER_LOGGER_NAME = 'cardanoScanner';
export const BITCOIN_SCANNER_LOGGER_NAME = 'bitcoinScanner';
export const DOGE_SCANNER_LOGGER_NAME = 'dogeScanner';
export const ETHEREUM_SCANNER_LOGGER_NAME = 'ethereumScanner';
export const BINANCE_SCANNER_LOGGER_NAME = 'binanceScanner';
export const FIRO_SCANNER_LOGGER_NAME = 'firoScanner';
export const ERGO_BLOCK_TIME = 120;
export const CARDANO_BLOCK_TIME = 20;
export const BITCOIN_BLOCK_TIME = 600;
export const ETHEREUM_BLOCK_TIME = 12;
export const BINANCE_BLOCK_TIME = 3;
export const DOGE_BLOCK_TIME = 60;
export const FIRO_BLOCK_TIME = 150;

export const BITCOIN_RUNES_CONFIG_KEY = 'bitcoinRunes';
16 changes: 16 additions & 0 deletions apps/rosen-service/src/event-trigger/event-trigger-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const binanceEventTriggerExtractorLogger = logger.child(
const dogeEventTriggerExtractorLogger = logger.child(
'dogeEventTriggerExtractor',
);
const firoEventTriggerExtractorLogger = logger.child(
'firoEventTriggerExtractor',
);

/**
* register event trigger extractors for all chains
Expand Down Expand Up @@ -113,13 +116,25 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
configs.binance.addresses.fraud,
binanceEventTriggerExtractorLogger,
);
const firoEventTriggerExtractor = new EventTriggerExtractor(
'firo-extractor',
dataSource,
ErgoNetworkType.Explorer,
configs.ergo.explorerUrl,
configs.firo.addresses.eventTrigger,
configs.firo.tokens.rwt,
configs.firo.addresses.permit,
configs.firo.addresses.fraud,
firoEventTriggerExtractorLogger,
);
await scanner.registerExtractor(ergoEventTriggerExtractor);
await scanner.registerExtractor(cardanoEventTriggerExtractor);
await scanner.registerExtractor(bitcoinEventTriggerExtractor);
await scanner.registerExtractor(bitcoinRunesEventTriggerExtractor);
await scanner.registerExtractor(dogeEventTriggerExtractor);
await scanner.registerExtractor(ethereumEventTriggerExtractor);
await scanner.registerExtractor(binanceEventTriggerExtractor);
await scanner.registerExtractor(firoEventTriggerExtractor);

logger.debug('event trigger extractors registered', {
scannerName: scanner.name(),
Expand All @@ -131,6 +146,7 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
dogeEventTriggerExtractor.getId(),
ethereumEventTriggerExtractor.getId(),
binanceEventTriggerExtractor.getId(),
firoEventTriggerExtractor.getId(),
],
});
} catch (error) {
Expand Down
13 changes: 13 additions & 0 deletions apps/rosen-service/src/health-check/health-check-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
ERGO_SCANNER_INTERVAL,
ETHEREUM_BLOCK_TIME,
ETHEREUM_SCANNER_INTERVAL,
FIRO_BLOCK_TIME,
FIRO_SCANNER_INTERVAL,
} from '../constants';
import scannerService from '../scanner/scanner-service';
import { getLastSavedBlock } from './health-check-utils';
Expand Down Expand Up @@ -131,6 +133,17 @@ const registerAllHealthChecks = (healthCheck: HealthCheck) => {
),
label: 'binance',
},
{
instance: new ScannerSyncHealthCheckParam(
scannerService.getFiroScanner().name(),
async () => getLastSavedBlock(scannerService.getFiroScanner().name()),
config.healthCheck.firoScannerWarnDiff,
config.healthCheck.firoScannerCriticalDiff,
FIRO_BLOCK_TIME,
FIRO_SCANNER_INTERVAL,
),
label: 'firo',
},
];

for (const { instance, label } of checks) {
Expand Down
41 changes: 41 additions & 0 deletions apps/rosen-service/src/observation/chains/firo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DefaultLogger } from '@rosen-bridge/abstract-logger';
import { FiroRpcObservationExtractor } from '@rosen-bridge/firo-observation-extractor';
import { FiroRpcScanner } from '@rosen-bridge/firo-scanner';

import config from '../../configs';
import dataSource from '../../data-source';
import AppError from '../../errors/AppError';
import { getTokenMap } from '../../utils';

const logger = DefaultLogger.getInstance().child(import.meta.url);

/**
* register an observation extractor for the provided scanner
* @param scanner
*/
export const registerFiroExtractor = async (scanner: FiroRpcScanner) => {
try {
const observationExtractor = new FiroRpcObservationExtractor(
config.firo.addresses.lock,
dataSource,
await getTokenMap(),
logger.child('firoRpcObservationExtractor'),
);

await scanner.registerExtractor(observationExtractor);

logger.debug('firo observation extractor registered', {
scannerName: scanner.name(),
});
} catch (error) {
throw new AppError(
`cannot create or register firo observation extractor due to error: ${error}`,
false,
'error',
error instanceof Error ? error.stack : undefined,
{
scannerName: scanner.name(),
},
);
}
};
2 changes: 2 additions & 0 deletions apps/rosen-service/src/observation/observation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerCardanoExtractor } from './chains/cardano';
import { registerDogeExtractor } from './chains/doge';
import { registerErgoExtractor } from './chains/ergo';
import { registerEthereumExtractor } from './chains/ethereum';
import { registerFiroExtractor } from './chains/firo';

const observationService = {
registerBitcoinExtractor,
Expand All @@ -14,6 +15,7 @@ const observationService = {
registerErgoExtractor,
registerEthereumExtractor,
registerBinanceExtractor,
registerFiroExtractor,
};

export default observationService;
80 changes: 80 additions & 0 deletions apps/rosen-service/src/scanner/chains/firo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { DefaultLogger } from '@rosen-bridge/abstract-logger';
import {
FailoverStrategy,
NetworkConnectorManager,
} from '@rosen-bridge/abstract-scanner';
import {
FiroRpcNetwork,
FiroRpcScanner,
FiroRpcTransaction,
} from '@rosen-bridge/firo-scanner';

import config from '../../configs';
import {
FIRO_SCANNER_INTERVAL,
FIRO_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 = DefaultLogger.getInstance().child(import.meta.url);
const scannerLogger = logger.child(FIRO_SCANNER_LOGGER_NAME);

/**
* Creates and configures a NetworkConnectorManager instance for firo scanner
*/
export const createFiroNetworkConnectorManager = () => {
const networkConnectorManager =
new NetworkConnectorManager<FiroRpcTransaction>(
new FailoverStrategy(),
scannerLogger,
);

networkConnectorManager.addConnector(
new FiroRpcNetwork(
config.firo.rpcUrl,
SCANNER_API_TIMEOUT * 1000,
config.firo.rpcUsername && config.firo.rpcPassword
? {
username: config.firo.rpcUsername,
password: config.firo.rpcPassword,
}
: undefined,
),
);

return networkConnectorManager;
};

/**
* create a firo scanner, initializing it and calling its update method
* periodically
*/
export const startFiroScanner = async () => {
try {
const scanner = new FiroRpcScanner({
dataSource,
initialHeight: config.firo.initialHeight,
logger: scannerLogger,
network: createFiroNetworkConnectorManager(),
});

await observationService.registerFiroExtractor(scanner);

startScanner(scanner, import.meta.url, FIRO_SCANNER_INTERVAL);

logger.debug('firo scanner started');

return scanner;
} catch (error) {
throw new AppError(
`cannot create or start firo scanner due to error: ${error}`,
false,
'error',
error instanceof Error ? error.stack : undefined,
);
}
};
Loading
Loading