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: 10 additions & 4 deletions infrastructure/config/docker/docker-compose-firo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ services:
firod:
image: firoorg/firod:0.14.15.2
restart: unless-stopped
deploy:
resources:
limits:
memory: 2048M
reservations:
memory: 1024M
volumes:
- ./volumes/firo:/home/firod/.firo
ports:
- '8168:8168'
- '8888:8888'
healthcheck:
test: firo-cli -conf=/home/firod/.firo/firo.conf getblockchaininfo || exit 1
test: firo-cli -conf=/home/firod/.firo/firo.conf getblockcount || exit 1
start_period: 120s
interval: 30s
timeout: 60s
retries: 10
interval: 120s
timeout: 10s
retries: 3
command: >
-conf=/home/firod/.firo/firo.conf
17 changes: 13 additions & 4 deletions infrastructure/config/firo/firo.conf
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@ spentindex=0

# Network
onlynet=ipv4

# Performance
dbcache=512
maxconnections=40

# Mempool (defaults: maxmempool=300MB, mempoolexpiry=72h, maxorphantx=100)
maxmempool=100
mempoolexpiry=24
maxorphantx=10

# Performance (defaults: dbcache=450MB, rpcthreads=4, rpcworkqueue=16)
dbcache=256
rpcthreads=8
rpcworkqueue=32
rpcworkqueue=64

# Per-connection buffer limits (value * 1000 bytes; defaults: send=1000, receive=5000)
maxsendbuffer=500
maxreceivebuffer=2000
41 changes: 41 additions & 0 deletions src/integration/blockchain/spark/spark-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ export interface SparkTransaction {
fee?: number;
}

export enum SparkTransferDirection {
INCOMING = 'INCOMING',
OUTGOING = 'OUTGOING',
}

export interface SparkTransfer {
id: string;
amountSats: number;
status: string;
direction: SparkTransferDirection;
senderSparkAddress?: string;
receiverSparkAddress?: string;
createdTime?: Date;
updatedTime?: Date;
}

export interface SparkNodeInfo {
version: string;
testnet: boolean;
Expand Down Expand Up @@ -87,6 +103,31 @@ export class SparkClient extends BlockchainClient {
};
}

async getTransfers(limit = 100, offset = 0): Promise<SparkTransfer[]> {
const wallet = await this.wallet;
const result = await wallet.getTransfers(limit, offset);

return result.transfers.map((t) => ({
id: t.id,
amountSats: t.totalValue,
status: t.status,
direction: t.transferDirection as SparkTransferDirection,
senderSparkAddress: t.senderIdentityPublicKey,
receiverSparkAddress: t.receiverIdentityPublicKey,
createdTime: t.createdTime,
updatedTime: t.updatedTime,
}));
}

async getIncomingTransfers(limit = 100, offset = 0): Promise<SparkTransfer[]> {
const transfers = await this.getTransfers(limit, offset);

// Filter only completed incoming transfers
return transfers.filter(
(t) => t.status === 'TRANSFER_STATUS_COMPLETED' && t.direction === SparkTransferDirection.INCOMING,
);
}

// --- FEE METHODS (always 0 for Spark L2) --- //

async getNativeFee(): Promise<number> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class BlockchainAdapter implements LiquidityBalanceIntegration {
break;

case Blockchain.LIGHTNING:
case Blockchain.SPARK:
case Blockchain.FIRO:
case Blockchain.MONERO:
await this.updateCoinOnlyBalance(assets);
Expand Down
7 changes: 6 additions & 1 deletion src/subdomains/generic/kyc/services/kyc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,12 @@ export class KycService {

if (user.kycLevel >= KycLevel.LEVEL_50) {
kycStep.complete();
} else if (missingCompletedSteps.length === 1) {
} else if (
!missingCompletedSteps.length ||
(missingCompletedSteps.length === 1 &&
missingCompletedSteps[0] === KycStepName.DFX_APPROVAL &&
kycStep.name !== KycStepName.DFX_APPROVAL)
) {
kycStep.manualReview();
} else {
kycStep.onHold();
Expand Down
6 changes: 6 additions & 0 deletions src/subdomains/supporting/dex/dex.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DexOptimismService } from './services/dex-optimism.service';
import { DexPolygonService } from './services/dex-polygon.service';
import { DexSepoliaService } from './services/dex-sepolia.service';
import { DexSolanaService } from './services/dex-solana.service';
import { DexSparkService } from './services/dex-spark.service';
import { DexTronService } from './services/dex-tron.service';
import { DexZanoService } from './services/dex-zano.service';
import { DexService } from './services/dex.service';
Expand Down Expand Up @@ -60,6 +61,7 @@ import { SepoliaCoinStrategy as SepoliaCoinStrategyCL } from './strategies/check
import { SepoliaTokenStrategy as SepoliaTokenStrategyCL } from './strategies/check-liquidity/impl/sepolia-token.strategy';
import { SolanaCoinStrategy as SolanaCoinStrategyCL } from './strategies/check-liquidity/impl/solana-coin.strategy';
import { SolanaTokenStrategy as SolanaTokenStrategyCL } from './strategies/check-liquidity/impl/solana-token.strategy';
import { SparkStrategy as SparkStrategyCL } from './strategies/check-liquidity/impl/spark.strategy';
import { TronCoinStrategy as TronCoinStrategyCL } from './strategies/check-liquidity/impl/tron-coin.strategy';
import { TronTokenStrategy as TronTokenStrategyCL } from './strategies/check-liquidity/impl/tron-token.strategy';
import { ZanoCoinStrategy as ZanoCoinStrategyCL } from './strategies/check-liquidity/impl/zano-coin.strategy';
Expand Down Expand Up @@ -95,6 +97,7 @@ import { SepoliaCoinStrategy as SepoliaCoinStrategyPL } from './strategies/purch
import { SepoliaTokenStrategy as SepoliaTokenStrategyPL } from './strategies/purchase-liquidity/impl/sepolia-token.strategy';
import { SolanaCoinStrategy as SolanaCoinStrategyPL } from './strategies/purchase-liquidity/impl/solana-coin.strategy';
import { SolanaTokenStrategy as SolanaTokenStrategyPL } from './strategies/purchase-liquidity/impl/solana-token.strategy';
import { SparkStrategy as SparkStrategyPL } from './strategies/purchase-liquidity/impl/spark.strategy';
import { TronCoinStrategy as TronCoinStrategyPL } from './strategies/purchase-liquidity/impl/tron-coin.strategy';
import { TronTokenStrategy as TronTokenStrategyPL } from './strategies/purchase-liquidity/impl/tron-token.strategy';
import { ZanoCoinStrategy as ZanoCoinStrategyPL } from './strategies/purchase-liquidity/impl/zano-coin.strategy';
Expand Down Expand Up @@ -175,6 +178,7 @@ import { ZanoStrategy as ZanoStrategyS } from './strategies/supplementary/impl/z
DexCitreaService,
DexCitreaTestnetService,
DexLightningService,
DexSparkService,
DexFiroService,
DexMoneroService,
DexZanoService,
Expand All @@ -193,6 +197,7 @@ import { ZanoStrategy as ZanoStrategyS } from './strategies/supplementary/impl/z
BitcoinStrategyCL,
BitcoinTestnet4StrategyCL,
LightningStrategyCL,
SparkStrategyCL,
FiroCoinStrategyCL,
MoneroStrategyCL,
ZanoCoinStrategyCL,
Expand Down Expand Up @@ -227,6 +232,7 @@ import { ZanoStrategy as ZanoStrategyS } from './strategies/supplementary/impl/z
BitcoinTestnet4StrategyPL,
FiroStrategyPL,
MoneroStrategyPL,
SparkStrategyPL,
ZanoCoinStrategyPL,
ZanoTokenStrategyPL,
ArbitrumCoinStrategyPL,
Expand Down
35 changes: 35 additions & 0 deletions src/subdomains/supporting/dex/services/dex-spark.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
import { SparkClient } from 'src/integration/blockchain/spark/spark-client';
import { SparkService } from 'src/integration/blockchain/spark/spark.service';
import { Util } from 'src/shared/utils/util';
import { LiquidityOrder } from '../entities/liquidity-order.entity';
import { LiquidityOrderRepository } from '../repositories/liquidity-order.repository';

@Injectable()
export class DexSparkService {
private readonly sparkClient: SparkClient;

constructor(
private readonly liquidityOrderRepo: LiquidityOrderRepository,
sparkService: SparkService,
) {
this.sparkClient = sparkService.getDefaultClient();
}

async checkAvailableTargetLiquidity(inputAmount: number): Promise<[number, number]> {
const pendingAmount = await this.getPendingAmount();
const availableAmount = await this.sparkClient.getNativeCoinBalance();

return [inputAmount, availableAmount - pendingAmount];
}

private async getPendingAmount(): Promise<number> {
const pendingOrders = await this.liquidityOrderRepo.findBy({
isComplete: false,
targetAsset: { dexName: 'BTC', blockchain: Blockchain.SPARK },
});

return Util.sumObjValue<LiquidityOrder>(pendingOrders, 'estimatedTargetAmount');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Injectable } from '@nestjs/common';
import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
import { Asset, AssetCategory, AssetType } from 'src/shared/models/asset/asset.entity';
import { AssetService } from 'src/shared/models/asset/asset.service';
import { CheckLiquidityRequest, CheckLiquidityResult } from '../../../interfaces';
import { DexSparkService } from '../../../services/dex-spark.service';
import { CheckLiquidityUtil } from '../utils/check-liquidity.util';
import { CheckLiquidityStrategy } from './base/check-liquidity.strategy';

@Injectable()
export class SparkStrategy extends CheckLiquidityStrategy {
constructor(
private readonly assetService: AssetService,
private readonly dexSparkService: DexSparkService,
) {
super();
}

get blockchain(): Blockchain {
return Blockchain.SPARK;
}

get assetType(): AssetType {
return undefined;
}

get assetCategory(): AssetCategory {
return undefined;
}

async checkLiquidity(request: CheckLiquidityRequest): Promise<CheckLiquidityResult> {
const { context, correlationId, referenceAsset, referenceAmount: bitcoinAmount } = request;

if (referenceAsset.dexName === 'BTC') {
const [targetAmount, availableAmount] = await this.dexSparkService.checkAvailableTargetLiquidity(bitcoinAmount);

return CheckLiquidityUtil.createNonPurchasableCheckLiquidityResult(
request,
targetAmount,
availableAmount,
await this.feeAsset(),
);
}

throw new Error(
`Only native coin reference is supported by Spark CheckLiquidity strategy. Provided reference asset: ${referenceAsset.dexName} Context: ${context}. CorrelationID: ${correlationId}`,
);
}

protected getFeeAsset(): Promise<Asset> {
return this.assetService.getSparkCoin();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
import { Asset, AssetCategory, AssetType } from 'src/shared/models/asset/asset.entity';
import { DfxLogger } from 'src/shared/services/dfx-logger';
import { NoPurchaseStrategy } from './base/no-purchase.strategy';

@Injectable()
export class SparkStrategy extends NoPurchaseStrategy {
protected readonly logger = new DfxLogger(SparkStrategy);

get blockchain(): Blockchain {
return Blockchain.SPARK;
}

get assetType(): AssetType {
return undefined;
}

get assetCategory(): AssetCategory {
return undefined;
}

get dexName(): string {
return undefined;
}

protected getFeeAsset(): Promise<Asset> {
return this.assetService.getSparkCoin();
}
}
6 changes: 5 additions & 1 deletion src/subdomains/supporting/log/log-job.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ export class LogJobService {
const pendingPayIns = await this.payInService.getPendingPayIns();
const pendingBuyFiat = await this.buyFiatService.getPendingTransactions();
const pendingBuyCrypto = await this.buyCryptoService.getPendingTransactions();
const payoutSentBuyCryptoIds = await this.payoutService.getRecentPayoutSentCorrelationIds(
PayoutOrderContext.BUY_CRYPTO,
);
const filteredPendingBuyCrypto = pendingBuyCrypto.filter((tx) => !payoutSentBuyCryptoIds.has(tx.id.toString()));
const pendingBankTx = await this.bankTxService.getPendingTx();
const pendingBankTxRepeat = await this.bankTxRepeatService.getPendingTx();
const pendingBankTxReturn = await this.bankTxReturnService.getPendingTx();
Expand Down Expand Up @@ -822,7 +826,7 @@ export class LogJobService {
const manualDebtPosition = manualDebtPositions.find((p) => p.assetId === curr.id)?.value ?? 0;

const { input: buyFiat, output: buyFiatPass } = this.getPendingAmounts([curr], pendingBuyFiat);
const { input: buyCrypto, output: buyCryptoPass } = this.getPendingAmounts([curr], pendingBuyCrypto);
const { input: buyCrypto, output: buyCryptoPass } = this.getPendingAmounts([curr], filteredPendingBuyCrypto);

const bankTxNull = this.getPendingAmounts(
[curr],
Expand Down
12 changes: 11 additions & 1 deletion src/subdomains/supporting/payout/services/payout.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DfxCron } from 'src/shared/utils/cron';
import { Util } from 'src/shared/utils/util';
import { MailContext, MailType } from 'src/subdomains/supporting/notification/enums';
import { NotificationService } from 'src/subdomains/supporting/notification/services/notification.service';
import { FindOptionsRelations, IsNull, MoreThan, Not } from 'typeorm';
import { FindOptionsRelations, In, IsNull, MoreThan, Not } from 'typeorm';
import { MailRequest } from '../../notification/interfaces';
import { PayoutOrder, PayoutOrderContext, PayoutOrderStatus } from '../entities/payout-order.entity';
import { PayoutOrderFactory } from '../factories/payout-order.factory';
Expand Down Expand Up @@ -79,6 +79,16 @@ export class PayoutService {
};
}

async getRecentPayoutSentCorrelationIds(context: PayoutOrderContext): Promise<Set<string>> {
const since = new Date(Date.now() - 3600_000); // 1 hour
const orders = await this.payoutOrderRepo.findBy({
context,
status: In([PayoutOrderStatus.PAYOUT_PENDING, PayoutOrderStatus.COMPLETE]),
updated: MoreThan(since),
});
return new Set(orders.map((o) => o.correlationId));
}

async estimateFee(targetAsset: Asset, address: string, amount: number, asset: Asset): Promise<FeeResult> {
const prepareStrategy = this.prepareStrategyRegistry.getPrepareStrategy(targetAsset);
const payoutStrategy = this.payoutStrategyRegistry.getPayoutStrategy(targetAsset);
Expand Down
Loading