Skip to content
Merged
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
14 changes: 14 additions & 0 deletions migration/1763453659323-addCitreaMonitoring.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class addCitreaMonitoring1763453659323 {
name = 'addCitreaMonitoring1763453659323'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "monitoring_balance" ADD "citreaBalance" float NOT NULL CONSTRAINT "DF_98f1eabfa79a178eacdc47fe777" DEFAULT 0`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "monitoring_balance" DROP CONSTRAINT "DF_98f1eabfa79a178eacdc47fe777"`);
await queryRunner.query(`ALTER TABLE "monitoring_balance" DROP COLUMN "citreaBalance"`);
}
}
9 changes: 8 additions & 1 deletion src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,15 @@ export class Configuration {
gatewayUrl: process.env.ROOTSTOCK_GATEWAY_URL ?? '',
apiKey: process.env.ALCHEMY_API_KEY ?? '',
chainId: +(process.env.ROOTSTOCK_CHAIN_ID ?? -1),
walletSeed: process.env.ROOTSTOCK_WALLET_SEED ?? '',
},
citrea: {
gatewayUrl: process.env.CITREA_GATEWAY_URL ?? '',
chainId: +(process.env.CITREA_CHAIN_ID ?? -1),
},
};

evm = {
walletSeed: process.env.EVM_WALLET_SEED ?? '',
};

alchemy = {
Expand Down
2 changes: 2 additions & 0 deletions src/integration/blockchain/blockchain.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { ArbitrumModule } from './arbitrum/arbitrum.module';
import { BaseModule } from './base/base.module';
import { BitcoinModule } from './bitcoin/bitcoin.module';
import { CitreaModule } from './citrea/citrea.module';
import { EthereumModule } from './ethereum/ethereum.module';
import { LightningModule } from './lightning/lightning.module';
import { OptimismModule } from './optimism/optimism.module';
Expand All @@ -21,6 +22,7 @@ import { UmaModule } from './uma/uma.module';
PolygonModule,
BaseModule,
RootstockModule,
CitreaModule,
],
controllers: [],
providers: [CryptoService],
Expand Down
27 changes: 27 additions & 0 deletions src/integration/blockchain/citrea/citrea-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Config } from 'src/config/config';
import { EvmUtil } from 'src/subdomains/evm/evm.util';
import { AssetTransferEntity } from 'src/subdomains/master-data/asset/entities/asset-transfer.entity';
import { LightningHelper } from '../lightning/lightning-helper';
import { EvmTokenBalance } from '../shared/evm/dto/evm-token-balance.dto';
import { EvmClient, EvmClientParams } from '../shared/evm/evm-client';

export class CitreaClient extends EvmClient {
constructor(private readonly params: EvmClientParams) {
super(params);
}

async getNativeCoinBalance(): Promise<number> {
const walletAddress = EvmUtil.createWallet({ seed: Config.evm.walletSeed, index: 0 }).address;

const balance = await this.provider.getBalance(walletAddress);
return LightningHelper.btcToSat(EvmUtil.fromWeiAmount(balance.toString()));
}

async getTokenBalance(_asset: AssetTransferEntity): Promise<number> {
throw new Error('Method not implemented.');
}

async getTokenBalances(_assets: AssetTransferEntity[]): Promise<EvmTokenBalance[]> {
throw new Error('Method not implemented.');
}
}
12 changes: 12 additions & 0 deletions src/integration/blockchain/citrea/citrea.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { SharedModule } from 'src/shared/shared.module';
import { AlchemyModule } from 'src/subdomains/alchemy/alchemy.module';
import { EvmRegistryModule } from '../shared/evm/registry/evm-registry.module';
import { CitreaService } from './citrea.service';

@Module({
imports: [SharedModule, EvmRegistryModule, AlchemyModule],
providers: [CitreaService],
exports: [],
})
export class CitreaModule {}
26 changes: 26 additions & 0 deletions src/integration/blockchain/citrea/citrea.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { GetConfig } from 'src/config/config';
import { Blockchain } from 'src/shared/enums/blockchain.enum';
import { HttpService } from 'src/shared/services/http.service';
import { AlchemyService } from 'src/subdomains/alchemy/services/alchemy.service';
import { EvmService } from '../shared/evm/evm.service';
import { CitreaClient } from './citrea-client';

@Injectable()
export class CitreaService extends EvmService {
constructor(http: HttpService, alchemyService: AlchemyService) {
const { gatewayUrl, chainId } = GetConfig().blockchain.citrea;

super(CitreaClient, {
http: http,
alchemyService,
gatewayUrl,
apiKey: '',
chainId,
});
}

get blockchain(): Blockchain {
return Blockchain.CITREA;
}
}
2 changes: 1 addition & 1 deletion src/integration/blockchain/rootstock/rootstock-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class RootstockClient extends EvmClient {

const url = `${this.params.gatewayUrl}/${this.params.apiKey ?? ''}`;

const walletAddress = EvmUtil.createWallet({ seed: Config.blockchain.rootstock.walletSeed, index: 0 }).address;
const walletAddress = EvmUtil.createWallet({ seed: Config.evm.walletSeed, index: 0 }).address;

const balanceResult = await http
.post<{ result: number }>(url, {
Expand Down
2 changes: 1 addition & 1 deletion src/integration/blockchain/shared/evm/evm-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export abstract class EvmClient {
private readonly alchemyService: AlchemyService;
private readonly chainId: number;

private readonly provider: ethers.providers.JsonRpcProvider;
protected readonly provider: ethers.providers.JsonRpcProvider;
private readonly tokens = new AsyncCache<Token>();

constructor(params: EvmClientParams) {
Expand Down
1 change: 1 addition & 0 deletions src/shared/enums/blockchain.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum Blockchain {
POLYGON = 'polygon',
BASE = 'base',
ROOTSTOCK = 'rootstock',
CITREA = 'citrea',
}
26 changes: 18 additions & 8 deletions src/subdomains/monitoring/entities/monitoring-balance.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import { Price } from 'src/subdomains/support/dto/price.dto';
import { LightningWalletTotalBalanceDto } from 'src/subdomains/user/application/dto/lightning-wallet.dto';
import { Column, Entity, ManyToOne } from 'typeorm';

export interface MonitoringBlockchainBalance {
onchainBalance: number;
lndOnchainBalance: number;
lightningBalance: number;
rootstockBalance: number;
citreaBalance: number;
}

@Entity('monitoring_balance')
export class MonitoringBalanceEntity extends IEntity {
@ManyToOne(() => AssetAccountEntity, { eager: true })
Expand All @@ -22,6 +30,9 @@ export class MonitoringBalanceEntity extends IEntity {
@Column({ type: 'float', default: 0 })
rootstockBalance: number;

@Column({ type: 'float', default: 0 })
citreaBalance: number;

@Column({ type: 'float', default: 0 })
customerBalance: number;

Expand All @@ -37,28 +48,27 @@ export class MonitoringBalanceEntity extends IEntity {
// --- FACTORY METHODS --- //

static createAsBtcEntity(
onchainBalance: number,
lndOnchainBalance: number,
lightningBalance: number,
rootstockBalance: number,
blockchainBalance: MonitoringBlockchainBalance,
internalBalance: LightningWalletTotalBalanceDto,
customerBalance: LightningWalletTotalBalanceDto,
chfPrice: Price,
): MonitoringBalanceEntity {
const entity = new MonitoringBalanceEntity();

entity.asset = { id: customerBalance.assetId } as AssetAccountEntity;
entity.onchainBalance = onchainBalance;
entity.lndOnchainBalance = lndOnchainBalance;
entity.lightningBalance = lightningBalance;
entity.rootstockBalance = rootstockBalance;
entity.onchainBalance = blockchainBalance.onchainBalance;
entity.lndOnchainBalance = blockchainBalance.lndOnchainBalance;
entity.lightningBalance = blockchainBalance.lightningBalance;
entity.rootstockBalance = blockchainBalance.rootstockBalance;
entity.citreaBalance = blockchainBalance.citreaBalance;
entity.customerBalance = customerBalance.totalBalance;

entity.ldsBalance =
entity.onchainBalance +
entity.lndOnchainBalance +
entity.lightningBalance +
entity.rootstockBalance +
entity.citreaBalance +
internalBalance.totalBalance -
entity.customerBalance;

Expand Down
51 changes: 16 additions & 35 deletions src/subdomains/monitoring/services/monitoring.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable, InternalServerErrorException, OnModuleInit } from '@nestjs/common';
import { BitcoinClient } from 'src/integration/blockchain/bitcoin/bitcoin-client';
import { BitcoinService } from 'src/integration/blockchain/bitcoin/bitcoin.service';
import { CitreaClient } from 'src/integration/blockchain/citrea/citrea-client';
import { LndChannelDto } from 'src/integration/blockchain/lightning/dto/lnd.dto';
import { LightningClient } from 'src/integration/blockchain/lightning/lightning-client';
import { LightningService } from 'src/integration/blockchain/lightning/services/lightning.service';
Expand All @@ -12,7 +13,7 @@ import { QueueHandler } from 'src/shared/utils/queue-handler';
import { AssetService } from 'src/subdomains/master-data/asset/services/asset.service';
import { CoinGeckoService } from 'src/subdomains/pricing/services/coingecko.service';
import { LightningWalletTotalBalanceDto } from 'src/subdomains/user/application/dto/lightning-wallet.dto';
import { MonitoringBalanceEntity } from '../entities/monitoring-balance.entity';
import { MonitoringBalanceEntity, MonitoringBlockchainBalance } from '../entities/monitoring-balance.entity';
import { MonitoringBalanceRepository } from '../repositories/monitoring-balance.repository';
import { MonitoringRepository } from '../repositories/monitoring.repository';

Expand All @@ -23,6 +24,7 @@ export class MonitoringService implements OnModuleInit {
private readonly bitcoinClient: BitcoinClient;
private readonly lightningClient: LightningClient;
private rootstockClient: RootstockClient;
private citreaClient: CitreaClient;

private readonly processBalancesQueue: QueueHandler;

Expand All @@ -43,6 +45,7 @@ export class MonitoringService implements OnModuleInit {

onModuleInit() {
this.rootstockClient = this.evmRegistryService.getClient(Blockchain.ROOTSTOCK) as RootstockClient;
this.citreaClient = this.evmRegistryService.getClient(Blockchain.CITREA) as CitreaClient;
}

// --- LIGHTNING --- //
Expand All @@ -66,10 +69,7 @@ export class MonitoringService implements OnModuleInit {
customerBalances: LightningWalletTotalBalanceDto[],
): Promise<void> {
try {
const onchainBalance = await this.getOnchainBalance();
const lndOnchainBalance = await this.getLndOnchainBalance();
const lightningBalance = await this.getLightningBalance();
const rootstockBalance = await this.getRootstockBalance();
const blockchainBalance = await this.getBlockchainBalances();

const btcAccountAsset = await this.assetService.getBtcAccountAssetOrThrow();
const btcAccountAssetId = btcAccountAsset.id;
Expand All @@ -86,14 +86,7 @@ export class MonitoringService implements OnModuleInit {

const customerFiatBalances = customerBalances.filter((b) => b.assetId !== btcAccountAssetId);

await this.processBtcBalance(
onchainBalance,
lndOnchainBalance,
lightningBalance,
rootstockBalance,
internalBtcBalance,
customerBtcBalance,
);
await this.processBtcBalance(blockchainBalance, internalBtcBalance, customerBtcBalance);
await this.processFiatBalances(customerFiatBalances);
} catch (e) {
this.logger.error('Error while processing balances', e);
Expand All @@ -119,21 +112,15 @@ export class MonitoringService implements OnModuleInit {
}

private async processBtcBalance(
onchainBalance: number,
lndOnchainBalance: number,
lightningBalance: number,
rootstockBalance: number,
blockchainBalance: MonitoringBlockchainBalance,
internalBtcBalance: LightningWalletTotalBalanceDto,
customerBtcBalance: LightningWalletTotalBalanceDto,
) {
const chfPrice = await this.coinGeckoService.getPrice('BTC', 'CHF');
if (!chfPrice.isValid) throw new InternalServerErrorException(`Invalid price from BTC to CHF`);

const btcMonitoringEntity = MonitoringBalanceEntity.createAsBtcEntity(
onchainBalance,
lndOnchainBalance,
lightningBalance,
rootstockBalance,
blockchainBalance,
internalBtcBalance,
customerBtcBalance,
chfPrice,
Expand Down Expand Up @@ -169,20 +156,14 @@ export class MonitoringService implements OnModuleInit {
return balance;
}

private async getOnchainBalance(): Promise<number> {
return this.bitcoinClient.getWalletBalance();
}

private async getLndOnchainBalance(): Promise<number> {
return this.lightningClient.getLndConfirmedWalletBalance();
}

private async getLightningBalance(): Promise<number> {
return this.lightningClient.getLndLightningBalance();
}

private async getRootstockBalance(): Promise<number> {
return this.rootstockClient.getNativeCoinBalance();
private async getBlockchainBalances(): Promise<MonitoringBlockchainBalance> {
return {
onchainBalance: await this.bitcoinClient.getWalletBalance(),
lndOnchainBalance: await this.lightningClient.getLndConfirmedWalletBalance(),
lightningBalance: await this.lightningClient.getLndLightningBalance(),
rootstockBalance: await this.rootstockClient.getNativeCoinBalance(),
citreaBalance: await this.citreaClient.getNativeCoinBalance(),
};
}

private async getChannels(): Promise<LndChannelDto[]> {
Expand Down