Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
18 changes: 18 additions & 0 deletions .changeset/firo-network-support.md
Comment thread
reubenyap marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@rosen-bridge/rosen-app': minor
'@rosen-network/firo': minor
'@rosen-ui/firo-wallet': minor
'@rosen-ui/constants': minor
'@rosen-bridge/icons': minor
'@rosen-ui/utils': patch
'@rosen-bridge/rosen-service': patch
'@rosen-bridge/rosen-service2': patch
---

Add Firo network support across the bridge UI.

- Introduce `@rosen-network/firo` and `@rosen-ui/firo-wallet` packages.
- Register Firo in `@rosen-ui/constants`, icons, and address/tx URL helpers.
- Wire Firo into the rosen app (networks, wallets, env).
- Skip unconfigured chains gracefully in `rosen-service` commitment extractor registration.
- Narrow `createChainSpecificDataAdapter` to `keyof Chains` and index `configs.chains` with the same narrower type in `rosen-service2` so the switch remains exhaustive once Firo is added to `NETWORKS_KEYS`.
20 changes: 18 additions & 2 deletions apps/rosen-service/src/commitment/commitment-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,26 @@ export const registerExtractors = async (scanner: ErgoScanner) => {
for (let key of NETWORKS_KEYS) {
const chain =
key == NETWORKS['bitcoin-runes'].key ? BITCOIN_RUNES_CONFIG_KEY : key;
const chainConfig = (configs as Record<string, unknown>)[chain] as
| {
addresses?: { commitment?: string };
tokens?: { rwt?: string };
}
| undefined;

// Skip networks that are present in shared constants but not configured
// in this service.
if (!chainConfig?.addresses?.commitment || !chainConfig?.tokens?.rwt) {
logger.debug(
`Skipping [${chain}] commitment extractor due to missing config`,
);
continue;
}

const commitmentExtractor = new CommitmentExtractor(
`${chain}-commitment-extractor`,
[configs[chain].addresses.commitment],
configs[chain].tokens.rwt,
[chainConfig.addresses.commitment],
chainConfig.tokens.rwt,
dataSource,
await getTokenMap(),
logger.child(`${chain}CommitmentExtractor`),
Expand Down
3 changes: 2 additions & 1 deletion apps/rosen-service2/src/services/assetAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export class AssetAggregatorService extends PeriodicTaskService {
const assetBalances: Partial<Record<NetworkItem, AssetBalance>> = {};
await Promise.all(
Object.keys(configs.chains).map(async (chain) => {
const chainConfig = configs.chains[chain as ChainChoices];
const chainConfig =
configs.chains[chain as keyof typeof configs.chains];
Comment thread
vorujack marked this conversation as resolved.
if (
chain == NETWORKS.ergo.key ||
('active' in chainConfig && chainConfig.active)
Expand Down
4 changes: 2 additions & 2 deletions apps/rosen-service2/src/services/assetDataAdapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { createClient } from '@vercel/kv';
import { configs } from '../configs';
import { TOTAL_SUPPLY_REDIS_KEY } from '../constants';
import { TokensConfig } from '../tokensConfig';
import { ChainChoices, TotalSupply } from '../types';
import { Chains, TotalSupply } from '../types';
import { stringSerializer } from '../utils';
import { DBService } from './db';

Expand Down Expand Up @@ -82,7 +82,7 @@ export class AssetDataAdapterService extends PeriodicTaskService {
* @example
* const adapter = createDataAdapter(NETWORKS.bitcoin.key, { url: "https://blockstream.info" });
*/
protected createChainSpecificDataAdapter = (chain: ChainChoices) => {
protected createChainSpecificDataAdapter = (chain: keyof Chains) => {
const tokenMap = TokensConfig.getInstance().getTokenMap();

const addresses: string[] = [
Expand Down
1 change: 1 addition & 0 deletions apps/rosen/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CARDANO_KOIOS_API=''
ERGO_EXPLORER_API=''
BITCOIN_ESPLORA_API=''
DOGE_BLOCKCYPHER_API=''
FIRO_BLOCKCYPHER_API=''
ETHEREUM_RPC_API=''
BINANCE_RPC_API=''
BITCOIN_RUNES_API=''
Expand Down
2 changes: 2 additions & 0 deletions apps/rosen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@
"@rosen-network/ergo": "^2.5.5",
"@rosen-network/ethereum": "^0.4.5",
"@rosen-network/evm": "^0.3.6",
"@rosen-network/firo": "^0.0.0",
"@rosen-ui/asset-calculator": "^2.3.1",
"@rosen-ui/constants": "^1.0.0",
"@rosen-ui/data-source": "^0.2.3",
"@rosen-ui/eternl-wallet": "^3.1.6",
"@rosen-ui/firo-wallet": "^0.0.0",
"@rosen-ui/lace-wallet": "^3.1.6",
"@rosen-ui/metamask-wallet": "^2.1.8",
"@rosen-ui/my-doge-wallet": "^1.1.8",
Expand Down
12 changes: 12 additions & 0 deletions apps/rosen/src/networks/firo/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FiroNetwork } from '@rosen-network/firo/dist/client';

import { unwrapFromObject } from '@/safeServerAction';

import { LOCK_ADDRESSES } from '../../../configs';
import * as actions from './server';

export const firo = new FiroNetwork({
lockAddress: LOCK_ADDRESSES.firo,
nextHeightInterval: 10,
...unwrapFromObject(actions),
});
1 change: 1 addition & 0 deletions apps/rosen/src/networks/firo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './client';
50 changes: 50 additions & 0 deletions apps/rosen/src/networks/firo/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use server';

import { validateAddress as validateAddressCore } from '@rosen-network/base';
import {
calculateFee as calculateFeeCore,
generateOpReturnData as generateOpReturnDataCore,
generateUnsignedTx as generateUnsignedTxCore,
getAddressBalance as getAddressBalanceCore,
getMaxTransferCreator as getMaxTransferCore,
getMinTransferCreator,
submitTransaction as submitTransactionCore,
} from '@rosen-network/firo';

import { wrap } from '@/safeServerAction';
import { getTokenMap } from '@/tokenMap/getServerTokenMap';

export const calculateFee = wrap(calculateFeeCore, {
cache: 10 * 60 * 1000,
traceKey: 'firo:calculateFee',
});

export const generateOpReturnData = wrap(generateOpReturnDataCore, {
traceKey: 'firo:generateOpReturnData',
});

export const generateUnsignedTx = wrap(generateUnsignedTxCore(getTokenMap), {
traceKey: 'firo:generateUnsignedTx',
});

export const getAddressBalance = wrap(getAddressBalanceCore, {
cache: 3000,
traceKey: 'firo:getAddressBalance',
});

export const getMaxTransfer = wrap(getMaxTransferCore(getTokenMap), {
traceKey: 'firo:getMaxTransfer',
});

export const getMinTransfer = wrap(getMinTransferCreator(getTokenMap), {
traceKey: 'firo:getMinTransfer',
});

export const submitTransaction = wrap(submitTransactionCore, {
traceKey: 'firo:submitTransaction',
});

export const validateAddress = wrap(validateAddressCore, {
cache: Infinity,
traceKey: 'firo:validateAddress',
});
1 change: 1 addition & 0 deletions apps/rosen/src/networks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './cardano';
export * from './doge';
export * from './ergo';
export * from './ethereum';
export * from './firo';
9 changes: 9 additions & 0 deletions apps/rosen/src/wallets/firo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FiroWallet } from '@rosen-ui/firo-wallet';

import { firo } from '@/networks';
import { getTokenMap } from '@/tokenMap/getClientTokenMap';

export const firoWallet = new FiroWallet({
networks: [firo],
getTokenMap,
});
1 change: 1 addition & 0 deletions apps/rosen/src/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './base';

export * from './eternl';
export * from './firo';
export * from './lace';
export * from './metaMask';
export * from './myDoge';
Expand Down
2 changes: 2 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ if [ "$APP" == "rosen" ] || [ "$APP" == "default" ]; then
npm run build --workspace networks/bitcoin
npm run build --workspace networks/bitcoin-runes
npm run build --workspace networks/doge
npm run build --workspace networks/firo
npm run build --workspace networks/cardano
npm run build --workspace networks/ergo
npm run build --workspace networks/ethereum
Expand All @@ -46,6 +47,7 @@ if [ "$APP" == "rosen" ] || [ "$APP" == "default" ]; then
npm run build --workspace wallets/nautilus
npm run build --workspace wallets/okx
npm run build --workspace wallets/my-doge
npm run build --workspace wallets/firo-wallet
npm run build --workspace wallets/xverse
npm run build --workspace wallets/wallet-connect
fi
Expand Down
37 changes: 37 additions & 0 deletions networks/firo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@rosen-network/firo",
"version": "0.0.0",
"private": true,
"description": "This is a private package utilized within Rosen Bridge UI app",
"repository": {
"type": "git",
"url": "git+https://github.com/rosen-bridge/ui.git"
},
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc --build",
"lint": "eslint --fix . && npm run prettify",
"lint:check": "eslint . && npm run prettify:check",
"prettify": "prettier --write . --ignore-path ../../.gitignore",
"prettify:check": "prettier --check . --ignore-path ../../.gitignore",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@rosen-bridge/address-codec": "^1.2.0",
"@rosen-bridge/bitcoin-utxo-selection": "^2.0.3",
"@rosen-bridge/icons": "^3.5.0",
"@rosen-bridge/tokens": "^6.0.0",
"@rosen-network/base": "^0.5.2",
"@rosen-ui/constants": "^1.0.0",
"@rosen-ui/types": "^0.4.1",
"axios": "^1.7.2",
"bitcoinjs-lib": "^6.1.6"
},
"engines": {
"node": ">=22.18.0",
"npm": "11.6.2"
}
}
78 changes: 78 additions & 0 deletions networks/firo/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Firo as FiroIcon } from '@rosen-bridge/icons';
import { Network, NetworkConfig } from '@rosen-network/base';
import { NETWORKS } from '@rosen-ui/constants';

import type { generateUnsignedTx } from './generateUnsignedTx';
import type {
generateOpReturnData,
getAddressBalance,
submitTransaction,
} from './utils';

type FiroNetworkConfig = NetworkConfig & {
generateOpReturnData: typeof generateOpReturnData;
generateUnsignedTx: ReturnType<typeof generateUnsignedTx>;
getAddressBalance: typeof getAddressBalance;
submitTransaction: typeof submitTransaction;
};

export class FiroNetwork implements Network {
public label = NETWORKS.firo.label;

public lockAddress: string;

public logo = FiroIcon;

public name = NETWORKS.firo.key;

public nextHeightInterval: number;

constructor(protected config: FiroNetworkConfig) {
this.nextHeightInterval = config.nextHeightInterval;
this.lockAddress = config.lockAddress;
}

public calculateFee: FiroNetworkConfig['calculateFee'] = (...args) => {
return this.config.calculateFee(...args);
};

public generateOpReturnData: FiroNetworkConfig['generateOpReturnData'] = (
...args
) => {
return this.config.generateOpReturnData(...args);
};

public generateUnsignedTx: FiroNetworkConfig['generateUnsignedTx'] = (
...args
) => {
return this.config.generateUnsignedTx(...args);
};

public getAddressBalance: FiroNetworkConfig['getAddressBalance'] = (
...args
) => {
return this.config.getAddressBalance(...args);
};

public getMaxTransfer: FiroNetworkConfig['getMaxTransfer'] = (...args) => {
return this.config.getMaxTransfer(...args);
};

public getMinTransfer: FiroNetworkConfig['getMinTransfer'] = (...args) => {
return this.config.getMinTransfer(...args);
};

public submitTransaction: FiroNetworkConfig['submitTransaction'] = (
...args
) => {
return this.config.submitTransaction(...args);
};

public toSafeAddress = (address: string): string => {
return address;
};

public validateAddress = (walletAddress: string): Promise<boolean> => {
return this.config.validateAddress(this.name, walletAddress);
};
}
16 changes: 16 additions & 0 deletions networks/firo/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const CONFIRMATION_TARGET = 10;
export const FIRO_TX_BASE_SIZE = 10;
export const FIRO_INPUT_SIZE = 148;
export const FIRO_OUTPUT_SIZE = 34;
export const MINIMUM_UTXO_VALUE = 500000; // 0.005 FIRO in sats
export const FIRO_NETWORK = {
messagePrefix: '\x18Firo Signed Message:\n',
bech32: 'firo',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x52,
scriptHash: 0x07,
wif: 0xd2,
};
Loading
Loading