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
9 changes: 9 additions & 0 deletions .changeset/zcash-asset-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@rosen-ui/asset-data-adapter': minor
'@rosen-bridge/rosen-service2': minor
'@rosen-ui/data-source': patch
---

Read transparent Zcash reserve balances through the Zcash RPC address-balance interface and feed them into the Rosen asset catalogue.

Pin observation extraction to version 1.0.10 so the shared data source and Service2 retain the scanner v2 query interface.
8 changes: 8 additions & 0 deletions .changeset/zcash-file-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@rosen-bridge/rosen-app': minor
'@rosen-bridge/icons': minor
'@rosen-ui/constants': minor
'@rosen-ui/utils': minor
---

Add a Regtest transparent Zcash deposit handoff to local Zallet, verify submitted receipts against the configured node before reporting success, and validate transparent Zcash withdrawal addresses for Ergo-origin transfers.
6 changes: 6 additions & 0 deletions apps/rosen-service2/config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ chains:
firo:
active: false
scanInterval: 60
zcash:
active: false
initialHeight: 1
scanInterval: 75
network: mainnet
branches: []
paths:
contracts: config/contracts.json
healthReport: healthReport.json
45 changes: 45 additions & 0 deletions apps/rosen-service2/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,51 @@
}
}
},
"zcash": {
"type": "object",
"children": {
"active": { "type": "boolean", "default": false },
"initialHeight": { "type": "number", "default": 1 },
"scanInterval": { "type": "number", "default": 75 },
"network": { "type": "string", "default": "mainnet" },
"genesisHash": { "type": "string" },
"branches": {
"type": "array",
"items": {
"type": "object",
"children": {
"height": { "type": "number" },
"branchId": { "type": "string" }
}
}
},
"inspector": {
"type": "object",
"children": {
"path": { "type": "string" },
"sha256": { "type": "string" }
}
},
"adapter": {
"type": "object",
"children": {
"extraAddresses": { "type": "array", "items": { "type": "string" } }
}
},
"rpc": {
"type": "object",
"children": {
"url": {
"type": "string",
"validations": [{ "required": true, "when": { "path": "chains.zcash.active", "value": true } }]
},
"timeout": { "type": "number", "default": 30 },
"username": { "type": "string" },
"password": { "type": "string" }
}
}
}
},
"firo": {
"type": "object",
"children": {
Expand Down
5 changes: 4 additions & 1 deletion apps/rosen-service2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"@rosen-bridge/abstract-extractor": "^3.2.3",
"@rosen-bridge/abstract-logger": "^4.0.0",
"@rosen-bridge/abstract-observation-extractor": "^1.0.10",
"@rosen-bridge/abstract-observation-extractor": "1.0.10",
"@rosen-bridge/abstract-scanner": "^2.0.3",
"@rosen-bridge/address-codec": "^2.1.0",
"@rosen-bridge/address-extractor": "^7.0.8",
Expand All @@ -52,12 +52,15 @@
"@rosen-bridge/health-check": "^8.0.0",
"@rosen-bridge/json-bigint": "^1.1.0",
"@rosen-bridge/log-level-check": "^4.0.0",
"@rosen-bridge/rosen-extractor": "^12.2.0",
"@rosen-bridge/scanner-interfaces": "^1.0.0",
"@rosen-bridge/scanner-sync-check": "^8.2.0",
"@rosen-bridge/service-manager": "^3.0.0",
"@rosen-bridge/token-price-entity": "^0.2.2",
"@rosen-bridge/watcher-data-extractor": "^13.0.10",
"@rosen-bridge/winston-logger": "^3.1.0",
"@rosen-bridge/zcash-observation-extractor": "^0.1.0",
"@rosen-bridge/zcash-scanner": "^0.1.0",
"@rosen-clients/ergo-explorer": "^2.1.3",
"@rosen-ui/asset-aggregator": "^0.1.0",
"@rosen-ui/asset-data-adapter": "0.3.0",
Expand Down
1 change: 1 addition & 0 deletions apps/rosen-service2/src/scanners/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { getCardanoScanner } from './cardano';
export { getDogeScanner } from './doge';
export { getEthereumScanner } from './ethereum';
export { getFiroScanner } from './firo';
export { getZcashScanner } from './zcash';
86 changes: 86 additions & 0 deletions apps/rosen-service2/src/scanners/zcash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { DefaultLogger } from '@rosen-bridge/abstract-logger';
import { FailoverStrategy, NetworkConnectorManager } from '@rosen-bridge/abstract-scanner';
import type { TokenMap } from '@rosen-bridge/extended-tokens';
import type { DataSource } from '@rosen-bridge/extended-typeorm';
import { NativeZcashInspector } from '@rosen-bridge/rosen-extractor';
import { ZcashObservationExtractor } from '@rosen-bridge/zcash-observation-extractor';
import {
FailClosedScannerLogger,
ZcashRpcNetwork,
ZcashRpcScanner,
type ZcashRpcTransaction,
} from '@rosen-bridge/zcash-scanner';

import { configs } from '../configs';

const logger = DefaultLogger.getInstance().child(import.meta.url);
const BRANCH_ID = /^[0-9a-f]{8}$/;

export const getZcashScanner = async (dataSource: DataSource, tokenMap: TokenMap) => {
const config = configs.chains.zcash;
if (
!config.rpc.url ||
!config.genesisHash ||
!config.inspector?.path ||
!config.inspector?.sha256
) {
throw new Error('Active Zcash scanner requires RPC, genesis and native inspector settings');
}
if (!config.branches?.length || config.branches[0].height !== 0) {
throw new Error('Zcash branch schedule must start at height zero');
}
for (let i = 0; i < config.branches.length; i++) {
const branch = config.branches[i];
if (
!Number.isSafeInteger(branch.height) ||
branch.height < 0 ||
!BRANCH_ID.test(branch.branchId) ||
(i > 0 && branch.height <= config.branches[i - 1].height)
) {
throw new Error('Invalid Zcash branch schedule');
}
}

const network = new NetworkConnectorManager<ZcashRpcTransaction>(
new FailoverStrategy(),
logger.child('zcashRpcNetwork'),
);
network.addConnector(
new ZcashRpcNetwork({
rpcUrl: config.rpc.url,
expectedGenesisHash: config.genesisHash,
timeoutMs: (config.rpc.timeout ?? 30) * 1000,
auth:
config.rpc.username && config.rpc.password
? { username: config.rpc.username, password: config.rpc.password }
: undefined,
}),
);

// GeneralScanner reports update errors to its logger without rethrowing.
const errorLogger = new FailClosedScannerLogger();
const scanner = new ZcashRpcScanner({
dataSource,
initialHeight: config.initialHeight,
network,
logger: errorLogger,
});
const extractor = new ZcashObservationExtractor(dataSource, {
network: config.network,
lockAddress: configs.contracts.zcash.addresses.lock,
tokens: tokenMap,
inspector: new NativeZcashInspector({
executablePath: config.inspector.path,
expectedSha256: config.inspector.sha256,
}),
branchIdAtHeight: (height: number) => {
const branch = config.branches.findLast((entry) => entry.height <= height);
if (!branch) throw new Error('Zcash branch schedule does not cover block height');
return branch.branchId;
},
logger: errorLogger,
storeRawData: true,
});
await scanner.registerExtractor(extractor);
return { scanner, errorLogger };
};
9 changes: 9 additions & 0 deletions apps/rosen-service2/src/services/assetDataAdaptersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DogeBlockCypherDataAdapter,
ErgoExplorerDataAdapter,
EthereumEvmRpcDataAdapter,
ZcashRpcDataAdapter,
} from '@rosen-ui/asset-data-adapter';
import type { AssetBalance, ChainsAdapters } from '@rosen-ui/asset-data-adapter/dist/types';
import { NETWORKS, NETWORKS_KEYS } from '@rosen-ui/constants';
Expand Down Expand Up @@ -182,6 +183,14 @@ export class AssetDataAdapterService extends AbstractAssetDataAdapterService {
this.logger.child('dogeBlockCypherDataAdapter'),
);
break;
case NETWORKS.zcash.key:
this.adapters[NETWORKS[chain].key] = new ZcashRpcDataAdapter(
addresses,
tokenMap,
configs.chains.zcash.rpc,
this.logger.child('zcashRpcDataAdapter'),
);
break;
}
}
});
Expand Down
12 changes: 12 additions & 0 deletions apps/rosen-service2/src/services/scannerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getDogeScanner,
getEthereumScanner,
getFiroScanner,
getZcashScanner,
} from 'scanners';

import type { AbstractLogger } from '@rosen-bridge/abstract-logger';
Expand All @@ -13,6 +14,7 @@ import type { TokenMap } from '@rosen-bridge/extended-tokens';
import type { DataSource } from '@rosen-bridge/extended-typeorm';
import { type Dependency, ServiceAction, ServiceStatus } from '@rosen-bridge/service-manager';
import { NETWORKS } from '@rosen-ui/constants';
import type { FailClosedScannerLogger } from '@rosen-bridge/zcash-scanner';

import { configs } from '../configs';
import type { ChainChoices, ChainScannersType, ChainsKeys, ChainsWithScanner } from '../types';
Expand All @@ -28,6 +30,7 @@ export class ScannerService extends AbstractScannerService {
protected scanners: { [k1 in ChainsKeys]?: ChainScannersType } = {};
private dataSource: DataSource;
private tokenMap: TokenMap;
private zcashErrorLogger?: FailClosedScannerLogger;
protected dependencies: Dependency[] = [
{
serviceName: AbstractDBService.name,
Expand Down Expand Up @@ -92,6 +95,7 @@ export class ScannerService extends AbstractScannerService {
* - Ethereum
* - Binance
* - Firo
* - Zcash
*
* Each scanner will be initialized with its event extractors
* and stored in the `scanners` registry for later use.
Expand Down Expand Up @@ -147,6 +151,13 @@ export class ScannerService extends AbstractScannerService {
this.scanners[NETWORKS.firo.key] = await getFiroScanner(this.dataSource, this.tokenMap);
}
break;
case NETWORKS.zcash.key:
if (configs.chains.zcash.active) {
const { scanner, errorLogger } = await getZcashScanner(this.dataSource, this.tokenMap);
this.scanners[NETWORKS.zcash.key] = scanner;
this.zcashErrorLogger = errorLogger;
}
break;
}
}
};
Expand Down Expand Up @@ -192,6 +203,7 @@ export class ScannerService extends AbstractScannerService {
fn: async () => {
if (!(scanner instanceof WebSocketScanner)) {
await scanner.update();
if (chain === NETWORKS.zcash.key) this.zcashErrorLogger?.assertNoErrors();
}
},
interval: configs.chains[chain as ChainsWithScanner].scanInterval * 1000,
Expand Down
1 change: 1 addition & 0 deletions apps/rosen-service2/src/types/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ export const BLOCK_TIMES: Record<ChainsWithScanner, number> = {
ethereum: ETHEREUM_BLOCK_TIME,
binance: BINANCE_BLOCK_TIME,
firo: FIRO_BLOCK_TIME,
zcash: 75,
};
18 changes: 18 additions & 0 deletions apps/rosen-service2/src/types/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ export interface Chains {
ethereum: ChainsEthereum;
binance: ChainsBinance;
firo: ChainsFiro;
zcash: ChainsZcash;
}

export interface ChainsZcash {
active: boolean;
initialHeight: number;
scanInterval: number;
network: 'mainnet' | 'testnet' | 'regtest';
genesisHash: string;
branches: { height: number; branchId: string }[];
inspector: { path: string; sha256: string };
adapter: { extraAddresses: string[] };
rpc: {
url: string;
timeout?: number;
username?: string;
password?: string;
};
}

export interface ChainsFiro {
Expand Down
4 changes: 3 additions & 1 deletion apps/rosen-service2/src/types/scanners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import type { ErgoScanner } from '@rosen-bridge/ergo-scanner';
import type { EvmRpcScanner } from '@rosen-bridge/evm-scanner';
import type { FiroElectrumXScanner } from '@rosen-bridge/firo-scanner';
import type { ZcashRpcScanner } from '@rosen-bridge/zcash-scanner';

export type ChainScannersType =
| ErgoScanner
Expand All @@ -21,4 +22,5 @@ export type ChainScannersType =
| CardanoBlockFrostScanner
| CardanoOgmiosScanner
| BitcoinEsploraScanner
| FiroElectrumXScanner;
| FiroElectrumXScanner
| ZcashRpcScanner;
3 changes: 2 additions & 1 deletion apps/rosen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
"validate-token-icons": "tsx ./scripts/validateTokenIcons"
},
"dependencies": {
"@rosen-bridge/abstract-observation-extractor": "^1.0.10",
"@rosen-bridge/abstract-observation-extractor": "1.0.10",
"@rosen-bridge/abstract-scanner": "^2.0.3",
"@rosen-bridge/address-codec-zcash": "^0.1.0",
"@rosen-bridge/encryption": "^1.0.1",
"@rosen-bridge/extended-typeorm": "^1.1.0",
"@rosen-bridge/icons": "^4.0.0",
Expand Down
Loading