Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4e0d4c6
implemented handshake network
AcoSmrkas Oct 30, 2025
1bb6086
implemented bob-extension
AcoSmrkas Oct 30, 2025
b9ed4ec
bob-extension in ui
AcoSmrkas Oct 30, 2025
18fee3f
implemented handshake asset-calculator
AcoSmrkas Oct 30, 2025
3bd501a
finished initial handshake implementation
AcoSmrkas Oct 30, 2025
30eac27
replaced bob extension with shake wallet
AcoSmrkas Feb 2, 2026
d80552f
implemented bitcoin rune-like lock data send method
AcoSmrkas Feb 4, 2026
7084f30
merge latest dev -> handshake, resolved conflicts
AcoSmrkas Feb 8, 2026
ec678bb
updated handshake block time to 600
AcoSmrkas Feb 8, 2026
fd464b0
updated package versions
AcoSmrkas May 4, 2026
8a68b86
Merge branch 'dev' into handshake
AcoSmrkas May 4, 2026
9b89a97
updated implementation
AcoSmrkas May 4, 2026
dc07d13
address handshake review feedback
AcoSmrkas May 16, 2026
fd06cc5
Merge branch 'dev' into handshake
AcoSmrkas May 19, 2026
c6efe1c
Merge branch 'dev' into handshake
AcoSmrkas Aug 18, 2026
a6c2c84
fix: Use hsd coin index for Handshake locked amounts
AcoSmrkas Aug 18, 2026
46e2d0c
chore: Point Handshake node URL at ergexplorer
AcoSmrkas Aug 18, 2026
71ddf83
fix: Address Handshake review feedback
AcoSmrkas Aug 19, 2026
0572970
fix: Add circular background to Handshake network icon
AcoSmrkas Aug 22, 2026
37eda73
Merge branch 'dev' into handshake
AcoSmrkas Aug 28, 2026
e6bc84b
Merge branch 'dev' into handshake
AcoSmrkas Sep 4, 2026
8192a6f
Merge branch 'dev' into handshake
AcoSmrkas Sep 9, 2026
c09f3cf
ci: Pin npm version fallback so CI works on fork pull requests
AcoSmrkas Sep 9, 2026
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
12 changes: 12 additions & 0 deletions .changeset/handshake-review-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@rosen-bridge/rosen-app': minor
'@rosen-bridge/rosen-service': minor
'@rosen-bridge/icons': minor
'@rosen-network/handshake': minor
'@rosen-ui/asset-calculator': minor
'@rosen-ui/constants': minor
'@rosen-ui/shake-wallet': minor
'@rosen-ui/utils': minor
---

Add Handshake network and Shake wallet support.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ jobs:
node-version-file: .nvmrc
registry-url: 'https://registry.npmjs.org'

- name: Setup npm ${{ vars.NPM_VERSION }}
run: npm i -g npm@${{ vars.NPM_VERSION }}
- name: Setup npm ${{ vars.NPM_VERSION || '11.6.2' }}
run: npm i -g npm@${{ vars.NPM_VERSION || '11.6.2' }}

- name: Install Dependencies
run: npm ci
Expand Down
14 changes: 14 additions & 0 deletions apps/rosen-service/config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ firo:
host:
port:
reconnectDelay:
handshake:
addresses:
lock:
eventTrigger:
permit:
fraud:
commitment:
tokens:
rwt:
initialHeight:
rpcUrl: https://hsd.ergexplorer.com
ethereum:
addresses:
lock:
Expand Down Expand Up @@ -124,6 +135,7 @@ calculator:
bitcoin: []
bitcoin-runes: []
doge: []
handshake: []
ethereum: []
binance: []
firo: []
Expand All @@ -142,6 +154,8 @@ healthCheck:
binanceScannerCriticalDiff: 5
firoScannerWarnDiff: 3
firoScannerCriticalDiff: 5
handshakeScannerWarnDiff: 3
handshakeScannerCriticalDiff: 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 @@ -41,6 +41,8 @@
"@rosen-bridge/evm-scanner": "^1.1.3",
"@rosen-bridge/firo-observation-extractor": "^1.1.3",
"@rosen-bridge/firo-scanner": "^0.2.3",
"@rosen-bridge/handshake-observation-extractor": "1.0.5",
"@rosen-bridge/handshake-scanner": "0.2.3",
"@rosen-bridge/health-check": "^8.0.0",
"@rosen-bridge/log-level-check": "^4.0.0",
"@rosen-bridge/scanner-interfaces": "^1.0.0",
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 @@ -79,6 +79,10 @@ const start = async () => {
addresses: config.calculator.addresses.firo,
explorerUrl: config.firo.explorerUrl,
},
{
addresses: config.calculator.addresses.handshake,
rpcUrl: config.handshake.rpcUrl,
},
dataSource,
logger,
);
Expand Down
19 changes: 19 additions & 0 deletions apps/rosen-service/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ const getConfig = () => {
electrumxPort: nodeConfig.get<number>('firo.electrumx.port'),
electrumxReconnectDelay: nodeConfig.get<number>('firo.electrumx.reconnectDelay'),
},
handshake: {
addresses: {
lock: nodeConfig.get<string>('handshake.addresses.lock'),
eventTrigger: nodeConfig.get<string>('handshake.addresses.eventTrigger'),
permit: nodeConfig.get<string>('handshake.addresses.permit'),
fraud: nodeConfig.get<string>('handshake.addresses.fraud'),
commitment: nodeConfig.get<string>('handshake.addresses.commitment'),
},
initialHeight: nodeConfig.get<number>('handshake.initialHeight'),
tokens: {
rwt: nodeConfig.get<string>('handshake.tokens.rwt'),
},
rpcUrl: nodeConfig.get<string>('handshake.rpcUrl'),
},
postgres: {
url: nodeConfig.get<string>('postgres.url'),
logging: nodeConfig.get<boolean>('postgres.logging'),
Expand All @@ -165,6 +179,7 @@ const getConfig = () => {
binance: nodeConfig.get<string[]>('calculator.addresses.binance'),
doge: nodeConfig.get<string[]>('calculator.addresses.doge'),
firo: nodeConfig.get<string[]>('calculator.addresses.firo'),
handshake: nodeConfig.get<string[]>('calculator.addresses.handshake'),
},
interval: nodeConfig.get<number>('calculator.interval'),
},
Expand All @@ -191,6 +206,10 @@ const getConfig = () => {
),
firoScannerWarnDiff: nodeConfig.get<number>('healthCheck.firoScannerWarnDiff'),
firoScannerCriticalDiff: nodeConfig.get<number>('healthCheck.firoScannerCriticalDiff'),
handshakeScannerWarnDiff: nodeConfig.get<number>('healthCheck.handshakeScannerWarnDiff'),
handshakeScannerCriticalDiff: nodeConfig.get<number>(
'healthCheck.handshakeScannerCriticalDiff',
),
updateInterval: nodeConfig.get<number>('healthCheck.interval'),
logDuration: nodeConfig.get<number>('healthCheck.duration'),
errorLogAllowedCount: nodeConfig.get<number>('healthCheck.maxAllowedErrorCount'),
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 @@ -7,6 +7,7 @@ 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 HANDSHAKE_SCANNER_INTERVAL = 10 * 60 * 1000;

export const ERGO_SCANNER_LOGGER_NAME = 'ergoScanner';
export const CARDANO_SCANNER_LOGGER_NAME = 'cardanoScanner';
Expand All @@ -15,12 +16,14 @@ 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 HANDSHAKE_SCANNER_LOGGER_NAME = 'handshakeScanner';
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 HANDSHAKE_BLOCK_TIME = 600;

export const BITCOIN_RUNES_CONFIG_KEY = 'bitcoinRunes';
14 changes: 14 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 @@ -16,6 +16,7 @@ const ethereumEventTriggerExtractorLogger = logger.child('ethereumEventTriggerEx
const binanceEventTriggerExtractorLogger = logger.child('binanceEventTriggerExtractor');
const dogeEventTriggerExtractorLogger = logger.child('dogeEventTriggerExtractor');
const firoEventTriggerExtractorLogger = logger.child('firoEventTriggerExtractor');
const handshakeEventTriggerExtractorLogger = logger.child('handshakeEventTriggerExtractor');

/**
* register event trigger extractors for all chains
Expand Down Expand Up @@ -111,6 +112,17 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
configs.firo.addresses.fraud,
firoEventTriggerExtractorLogger,
);
const handshakeEventTriggerExtractor = new EventTriggerExtractor(
'handshake-extractor',
dataSource,
ErgoNetworkType.Explorer,
configs.ergo.explorerUrl,
configs.handshake.addresses.eventTrigger,
configs.handshake.tokens.rwt,
configs.handshake.addresses.permit,
configs.handshake.addresses.fraud,
handshakeEventTriggerExtractorLogger,
);
await scanner.registerExtractor(ergoEventTriggerExtractor);
await scanner.registerExtractor(cardanoEventTriggerExtractor);
await scanner.registerExtractor(bitcoinEventTriggerExtractor);
Expand All @@ -119,6 +131,7 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
await scanner.registerExtractor(ethereumEventTriggerExtractor);
await scanner.registerExtractor(binanceEventTriggerExtractor);
await scanner.registerExtractor(firoEventTriggerExtractor);
await scanner.registerExtractor(handshakeEventTriggerExtractor);

logger.debug('event trigger extractors registered', {
scannerName: scanner.name(),
Expand All @@ -131,6 +144,7 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
ethereumEventTriggerExtractor.getId(),
binanceEventTriggerExtractor.getId(),
firoEventTriggerExtractor.getId(),
handshakeEventTriggerExtractor.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 @@ -23,6 +23,8 @@ import {
ETHEREUM_SCANNER_INTERVAL,
FIRO_BLOCK_TIME,
FIRO_SCANNER_INTERVAL,
HANDSHAKE_BLOCK_TIME,
HANDSHAKE_SCANNER_INTERVAL,
} from '../constants';
import scannerService from '../scanner/scanner-service';
import { getLastSavedBlock } from './health-check-utils';
Expand Down Expand Up @@ -136,6 +138,17 @@ const registerAllHealthChecks = (healthCheck: HealthCheck) => {
),
label: 'firo',
},
{
instance: new ScannerSyncHealthCheckParam(
scannerService.getHandshakeScanner().name(),
async () => getLastSavedBlock(scannerService.getHandshakeScanner().name()),
config.healthCheck.handshakeScannerWarnDiff,
config.healthCheck.handshakeScannerCriticalDiff,
HANDSHAKE_BLOCK_TIME,
HANDSHAKE_SCANNER_INTERVAL,
),
label: 'handshake',
},
];

for (const { instance, label } of checks) {
Expand Down
41 changes: 41 additions & 0 deletions apps/rosen-service/src/observation/chains/handshake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DefaultLogger } from '@rosen-bridge/abstract-logger';
import { HandshakeRpcObservationExtractor } from '@rosen-bridge/handshake-observation-extractor';
import type { HandshakeRpcScanner } from '@rosen-bridge/handshake-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 registerHandshakeExtractor = async (scanner: HandshakeRpcScanner) => {
try {
const observationExtractor = new HandshakeRpcObservationExtractor(
config.handshake.addresses.lock,
dataSource,
await getTokenMap(),
logger.child('handshakeRpcObservationExtractor'),
);

await scanner.registerExtractor(observationExtractor);

logger.debug('handshake observation extractor registered', {
scannerName: scanner.name(),
});
} catch (error) {
throw new AppError(
`cannot create or register handshake 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 @@ -6,11 +6,13 @@ import { registerDogeExtractor } from './chains/doge';
import { registerErgoExtractor } from './chains/ergo';
import { registerEthereumExtractor } from './chains/ethereum';
import { registerFiroExtractor } from './chains/firo';
import { registerHandshakeExtractor } from './chains/handshake';

const observationService = {
registerBitcoinExtractor,
registerBitcoinRunesExtractor,
registerDogeExtractor,
registerHandshakeExtractor,
registerCardanoExtractor,
registerErgoExtractor,
registerEthereumExtractor,
Expand Down
67 changes: 67 additions & 0 deletions apps/rosen-service/src/scanner/chains/handshake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { DefaultLogger } from '@rosen-bridge/abstract-logger';
import { FailoverStrategy, NetworkConnectorManager } from '@rosen-bridge/abstract-scanner';
import {
HandshakeRpcNetwork,
HandshakeRpcScanner,
type HandshakeRpcTransaction,
} from '@rosen-bridge/handshake-scanner';

import config from '../../configs';
import {
HANDSHAKE_SCANNER_INTERVAL,
HANDSHAKE_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(HANDSHAKE_SCANNER_LOGGER_NAME);

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

networkConnectorManager.addConnector(
new HandshakeRpcNetwork(config.handshake.rpcUrl, SCANNER_API_TIMEOUT * 1000),
);

return networkConnectorManager;
};

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

await observationService.registerHandshakeExtractor(scanner);

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

logger.debug('handshake scanner started');

return scanner;
} catch (error) {
throw new AppError(
`cannot create or start handshake scanner due to error: ${error}`,
false,
'error',
error instanceof Error ? error.stack : undefined,
);
}
};
7 changes: 7 additions & 0 deletions apps/rosen-service/src/scanner/scanner-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { CardanoKoiosScanner } from '@rosen-bridge/cardano-scanner';
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 { HandshakeRpcScanner } from '@rosen-bridge/handshake-scanner';

import { handleError } from '../utils';
import { startBinanceScanner } from './chains/binance';
Expand All @@ -13,6 +14,7 @@ import { startDogeScanner } from './chains/doge';
import { startErgoScanner } from './chains/ergo';
import { startEthereumScanner } from './chains/ethereum';
import { startFiroScanner } from './chains/firo';
import { startHandshakeScanner } from './chains/handshake';

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

Expand All @@ -24,6 +26,7 @@ let ethereumScanner: EvmRpcScanner;
let binanceScanner: EvmRpcScanner;
let dogeScanner: DogeRpcScanner;
let firoScanner: FiroElectrumXScanner;
let handshakeScanner: HandshakeRpcScanner;

/**
* start all scanners and register their extractors
Expand All @@ -38,6 +41,7 @@ const start = async () => {
binanceScanner,
dogeScanner,
firoScanner,
handshakeScanner,
] = await Promise.all([
startErgoScanner(),
startCardanoScanner(),
Expand All @@ -46,6 +50,7 @@ const start = async () => {
startBinanceScanner(),
startDogeScanner(),
startFiroScanner(),
startHandshakeScanner(),
]);

logger.debug('all scanners started and their extractors registered', {
Expand All @@ -57,6 +62,7 @@ const start = async () => {
binanceScanner.name(),
dogeScanner.name(),
firoScanner.name(),
handshakeScanner.name(),
],
});
} catch (error) {
Expand All @@ -74,6 +80,7 @@ const scannerService = {
getBinanceScanner: () => binanceScanner,
getDogeScanner: () => dogeScanner,
getFiroScanner: () => firoScanner,
getHandshakeScanner: () => handshakeScanner,
};

export default scannerService;
1 change: 1 addition & 0 deletions apps/rosen/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ETHEREUM_RPC_API=''
BINANCE_RPC_API=''
BITCOIN_RUNES_API=''
BITCOIN_RUNES_SECRET=''
HANDSHAKE_RPC_API=''

NEXT_PUBLIC_FEE_CONFIG_TOKEN_ID=''

Expand Down
Loading