Манитон Docs

CFA-Core Service

Основной сервис управления жизненным циклом ЦФА

CFA-Core Service

Статус: Реализован. Основной сервис управления жизненным циклом ЦФА с полной интеграцией с блокчейном.

CFA-Core — это "мозг" платформы, отвечающий за исполнение бизнес-логики, связанной с цифровыми финансовыми активами, в строгом соответствии с 259-ФЗ.

Основные функции

  • Управление выпусками (Issuance): Регистрация решения о выпуске, контроль лимитов эмиссии.
  • Жизненный цикл токенов: Mint (выпуск), Burn (погашение), Transfer (перевод).
  • Смарт-контракты: Генерация параметров и деплой смарт-контрактов для каждого выпуска.
  • Синхронизация с DLT: Гарантия того, что состояние в блокчейне соответствует юридическим требованиям.
  • Интеграция с реестром: Ведение реестра владельцев (совместно с Ledger-DB).

Технологический стек

  • Фреймворк: NestJS
  • База данных: PostgreSQL
  • Блокчейн: Hyperledger Besu (через Besu Connector)
  • Язык контрактов: Solidity

Protobuf Сервис

service CfaRegistryService {
  rpc IssueCfaRub(IssueCfaRubRequest) returns (IssueCfaRubResponse);
  rpc RedeemCfaRub(RedeemCfaRubRequest) returns (RedeemCfaRubResponse);
  rpc IssueInvestmentCfa(IssueInvestmentCfaRequest) returns (IssueInvestmentCfaResponse);
  rpc RedeemInvestmentCfa(RedeemInvestmentCfaRequest) returns (RedeemInvestmentCfaResponse);
  rpc CheckIntegrity(CheckIntegrityRequest) returns (CheckIntegrityResponse);
}

Статусы операций

  • CREATED — операция создана
  • KYC_REQUIRED — требуется проверка KYC
  • COMPLIANCE_CHECK — проверка AML/санкций
  • AWAITING_PAYMENT — ожидание платежа
  • PROCESSING — обработка
  • ONCHAIN_SUBMITTED — отправлена в DLT
  • FINALIZED — завершена в DLT
  • REJECTED — отклонена
  • FAILED — ошибка

Взаимодействие

Входящие события

  • FiatDepositedEvent (из Payments-RU) → Инициация выпуска CFA-RUB.
  • TradeExecutedEvent (из Market Service) → Перевод прав требования в DLT.

Исходящие события

  • CfaIssuedEvent → Сигнал для Ledger-DB обновить балансы.
  • CfaRedeemedEvent → Сигнал о завершении жизненного цикла актива.

Безопасность

Все операции по изменению состояния в DLT проходят через проверку KYC и соответствие лимитам, запрашиваемым у Identity Service.

API (gRPC Connect)

CfaRegistryService

RegisterInstrument

Регистрация нового инструмента (выпуска ЦФА).

message RegisterInstrumentRequest {
  RequestContext context = 1;
  string symbol = 2;
  string name = 3;
  InstrumentType type = 4;
  uint32 decimals = 5;
  string issuer_id = 6;
  string smart_contract_address = 7;
  string currency_code = 8;
}

message RegisterInstrumentResponse {
  string instrument_id = 1;
}

Пример:

const instrument = await cfaCoreService.registerInstrument(
  { requestId, correlationId, idempotencyKey },
  {
    symbol: 'GAZP-BOND-2024',
    name: 'Облигации ПАО "Газпром" 2024',
    type: InstrumentType.CFA_BOND,
    decimals: 2,
    issuerId: 'issuer-456',
    currencyCode: 'RUB',
  },
);

IssueCfaRub

Выпуск CFA-RUB (денежных требований).

message IssueCfaRubRequest {
  RequestContext context = 1;
  string user_id = 2;
  Money amount = 3;
  string payment_id = 4;
}

message IssueCfaRubResponse {
  CfaOperation operation = 1;
}

RedeemCfaRub

Погашение CFA-RUB.

message RedeemCfaRubRequest {
  RequestContext context = 1;
  string user_id = 2;
  Money amount = 3;
  string payout_id = 4;
}

message RedeemCfaRubResponse {
  CfaOperation operation = 1;
}

IssueInvestmentCfa

Выпуск инвестиционных ЦФА.

message IssueInvestmentCfaRequest {
  RequestContext context = 1;
  string instrument_id = 2;
  string investor_id = 3;
  MonetaryAmount amount = 4;
  string bookbuilding_id = 5;
}

message IssueInvestmentCfaResponse {
  CfaOperation operation = 1;
}

TransferCfa

Перевод ЦФА между пользователями.

message TransferCfaRequest {
  RequestContext context = 1;
  string instrument_id = 2;
  string from_user_id = 3;
  string to_user_id = 4;
  MonetaryAmount amount = 5;
  string trade_id = 6;
}

message TransferCfaResponse {
  CfaOperation operation = 1;
}

ListHolders

Получение списка держателей ЦФА.

message ListHoldersRequest {
  RequestContext context = 1;
  string instrument_id = 2;
  PaginationRequest pagination = 3;
}

message ListHoldersResponse {
  repeated HolderBalance holders = 1;
  PaginationResponse pagination = 2;
}

CheckIntegrity

Проверка целостности реестра.

message CheckIntegrityRequest {
  RequestContext context = 1;
  uint64 block_number = 2;
  string block_hash = 3;
  HashAlgorithm algorithm = 4;
}

message CheckIntegrityResponse {
  bool valid = 1;
  string computed_hash = 2;
}

События Kafka

Исходящие (maniton.cfa.events.v1)

CfaIssuedEvent

message CfaIssuedEvent {
  RequestContext context = 1;
  string operation_id = 2;
  string instrument_id = 3;
  string holder_id = 4;
  MonetaryAmount amount = 5;
  string tx_hash = 6;
  google.protobuf.Timestamp issued_at = 7;
}

CfaRedeemedEvent

message CfaRedeemedEvent {
  RequestContext context = 1;
  string operation_id = 2;
  string instrument_id = 3;
  string holder_id = 4;
  MonetaryAmount amount = 5;
  string tx_hash = 6;
  google.protobuf.Timestamp redeemed_at = 7;
}

CfaTransferredEvent

message CfaTransferredEvent {
  RequestContext context = 1;
  string operation_id = 2;
  string instrument_id = 3;
  string from_user_id = 4;
  string to_user_id = 5;
  MonetaryAmount amount = 6;
  string tx_hash = 7;
  google.protobuf.Timestamp transferred_at = 8;
}

InstrumentRegisteredEvent

message InstrumentRegisteredEvent {
  RequestContext context = 1;
  string instrument_id = 2;
  string symbol = 3;
  string name = 4;
  InstrumentType type = 5;
  string issuer_id = 6;
  google.protobuf.Timestamp registered_at = 7;
}

Входящие

  • maniton.payments.events.v1: FiatDepositedEvent
  • maniton.market.events.v1: TradeExecutedEvent

Use Cases

IssueCfaRubUseCase

Выпуск CFA-RUB после поступления фиатных средств.

async execute(request: IssueCfaRubRequest): Promise<IssueCfaRubResponse> {
  const { context, userId, amount, paymentId } = request;

  // 1. Проверка идемпотентности
  const existing = await this.operationRepository.findByIdempotencyKey(
    context.idempotencyKey
  );

  if (existing) {
    return { operation: existing };
  }

  // 2. Проверка лимитов
  const limitsCheck = await this.identityClient.checkLimits(
    context,
    userId,
    LimitType.DEPOSIT,
    amount,
    OperationType.ISSUANCE
  );

  if (!limitsCheck.allowed) {
    const rejectionReason = limitsCheck.reason?.includes('KYC')
      ? RejectionReasonCode.KYC_REQUIRED
      : RejectionReasonCode.LIMIT_EXCEEDED;

    const operation = new CfaOperation(
      crypto.randomUUID(),
      OperationType.ISSUANCE,
      OperationStatus.REJECTED,
      userId,
      null,
      amount.amount.value,
      amount.currencyCode,
      context.idempotencyKey,
      paymentId
    );
    operation.rejectionReason = rejectionReason;
    operation.rejectionMessage = limitsCheck.reason;

    await this.operationRepository.save(operation);
    await this.auditPublisher.log({
      resource: AuditResourceType.CFA_ISSUANCE,
      action: CFA_AUDIT_ACTIONS.ISSUANCE_REJECTED,
      userId,
      reason: limitsCheck.reason,
    });

    return { operation };
  }

  // 3. Создание операции
  const operation = new CfaOperation(
    crypto.randomUUID(),
    OperationType.ISSUANCE,
    OperationStatus.PROCESSING,
    userId,
    null,
    amount.amount.value,
    amount.currencyCode,
    context.idempotencyKey,
    paymentId
  );

  await this.operationRepository.save(operation);

  // 4. Генерация транзакции
  const txPayload = this.transactionBuilder.buildMintTx({
    to: '0x' + userId.replace(/-/g, '').substring(0, 40),
    value: amount.amount.value,
  });

  // 5. Подпись транзакции
  const signedTx = await this.web3Signer.sign({
    payload: txPayload,
    chainId: this.configService.get('besu.chainId'),
  });

  // 6. Отправка в блокчейн
  const txHash = await this.besuCommand.submitTransaction({
    context,
    payload: signedTx,
  });

  // 7. Обновление статуса
  operation.status = OperationStatus.ONCHAIN_SUBMITTED;
  operation.onchainTxHash = txHash;
  await this.operationRepository.save(operation);

  // 8. Публикация события
  await this.eventPublisher.publish({
    type: 'IssuanceStarted',
    payload: operation,
  });

  return { operation };
}

FinalizeOperationUseCase

Финализация операции после подтверждения транзакции в блокчейне.

async execute(txHash: string): Promise<void> {
  // 1. Получение receipt
  const receipt = await this.besuCommand.getTransactionReceipt({
    context: { requestId, correlationId, idempotencyKey },
    txHash,
  });

  // 2. Получение операции
  const operation = await this.operationRepository.findByTxHash(txHash);

  if (!operation) {
    throw new Error('Operation not found');
  }

  // 3. Проверка статуса транзакции
  if (receipt.status === BesuTxStatus.REVERTED) {
    operation.status = OperationStatus.REJECTED;
    operation.rejectionReason = RejectionReasonCode.INTEGRATION_ERROR;
    operation.rejectionMessage = 'Transaction reverted';

    await this.operationRepository.save(operation);

    await this.eventPublisher.publish({
      type: 'IssuanceFailed',
      payload: operation,
    });

    return;
  }

  // 4. Финализация операции
  operation.status = OperationStatus.FINALIZED;
  operation.finalizedAt = new Date();
  await this.operationRepository.save(operation);

  // 5. Публикация события
  await this.eventPublisher.publish({
    type: 'CfaIssued',
    payload: {
      operationId: operation.operationId,
      instrumentId: 'CFA-RUB',
      holderId: operation.userId,
      amount: operation.amount,
      txHash,
      issuedAt: new Date(),
    },
  });

  // 6. Аудит
  await this.auditPublisher.log({
    resource: AuditResourceType.CFA_ISSUANCE,
    action: CFA_AUDIT_ACTIONS.ISSUANCE_FINALIZED,
    userId: operation.userId,
    operationId: operation.operationId,
    txHash,
  });
}

Мониторинг

Метрики

import { Counter, Histogram, Gauge } from 'prom-client';

export const cfaIssuancesTotal = new Counter({
  name: 'cfa_issuances_total',
  help: 'Total number of CFA issuances',
  labelNames: ['type', 'status'],
});

export const cfaRedemptionsTotal = new Counter({
  name: 'cfa_redemptions_total',
  help: 'Total number of CFA redemptions',
  labelNames: ['type', 'status'],
});

export const cfaTransfersTotal = new Counter({
  name: 'cfa_transfers_total',
  help: 'Total number of CFA transfers',
  labelNames: ['instrument_id'],
});

export const blockchainTransactionsTotal = new Counter({
  name: 'blockchain_transactions_total',
  help: 'Total number of blockchain transactions',
  labelNames: ['status'],
});

export const issuanceDuration = new Histogram({
  name: 'issuance_duration_seconds',
  help: 'Time spent on CFA issuance',
  buckets: [1, 5, 10, 30, 60, 120, 300],
});

Логи

this.logger.log('CFA issued', {
  operationId: operation.operationId,
  userId: operation.userId,
  instrumentId: 'CFA-RUB',
  amount: operation.amount,
  txHash: operation.onchainTxHash,
});

this.logger.log('CFA transferred', {
  operationId: operation.operationId,
  instrumentId: operation.instrumentId,
  fromUserId: operation.fromUserId,
  toUserId: operation.toUserId,
  amount: operation.amount,
  txHash: operation.onchainTxHash,
});

Интеграции

Besu Connector

Взаимодействие с блокчейном через Besu Connector.

interface BesuCommand {
  submitTransaction(payload: TransactionPayload): Promise<string>;
  getTransactionReceipt(txHash: string): Promise<TransactionReceipt>;
  getBlock(number: number): Promise<Block>;
  verifyBlockHash(number: number, hash: string): Promise<boolean>;
}

Identity Service

Проверка KYC и лимитов перед операциями.

interface IdentityService {
  getUserProfile(context: RequestContext, userId: string): Promise<UserProfile>;
  checkLimits(
    context: RequestContext,
    userId: string,
    limitType: LimitType,
    amount: Money,
    operationType: OperationType,
  ): Promise<{ allowed: boolean; reason?: string }>;
  checkSanctions(
    context: RequestContext,
    userId: string,
    payload: SubmitKycPayload,
  ): Promise<SanctionsCheck>;
}

Ledger Service

Операционный учет операций.

interface LedgerService {
  createOperation(request: CreateOperationRequest): Promise<CreateOperationResponse>;
  getOperation(operationId: string): Promise<GetOperationResponse>;
  createHold(request: CreateHoldRequest): Promise<CreateHoldResponse>;
  consumeHold(holdId: string): Promise<ConsumeHoldResponse>;
}

Troubleshooting

Проблема: Транзакция не проходит в блокчейне

Диагностика:

# Проверка статуса транзакции
curl -X POST http://besu-connector:3004/getTransactionReceipt \
  -H "Content-Type: application/json" \
  -d '{"tx_hash": "0x..."}'

# Проверка блоков
curl -X POST http://besu-connector:3004/getBlock \
  -H "Content-Type: application/json" \
  -d '{"block_number": 12345}'

# Проверка газа
curl -X POST http://besu-connector:3004/estimateGas \
  -H "Content-Type: application/json" \
  -d '{"to": "0x...", "data": "0x..."}'

Решение:

  1. Проверьте, что газ достаточен
  2. Проверьте, что nonce верный
  3. Проверьте, что контракт существует

Проблема: Выпуск не проходит

Диагностика:

# Проверка лимитов
curl http://auth-service:3001/identity/limits/$USER_ID

# Проверка KYC
curl http://auth-service:3001/identity/users/$USER_ID

# Проверка операции
curl http://cfa-core-service:3002/operations/$OPERATION_ID

Решение:

  1. Проверьте KYC статус
  2. Проверьте лимиты
  3. Проверьте санкционные списки

Дополнительные ресурсы

On this page