File

src/security/fund.web3.service.ts

Index

Properties
Methods

Constructor

constructor(navModel: Model<NavDocument>)
Parameters :
Name Type Optional
navModel Model<NavDocument> No

Methods

Async addFund
addFund(fundId: string, email: string)
Parameters :
Name Type Optional
fundId string No
email string No
Returns : unknown
Async getAllFundClients
getAllFundClients(fundId: string)
Parameters :
Name Type Optional
fundId string No
Returns : unknown
Async getFundOrders
getFundOrders(fundId: string)
Parameters :
Name Type Optional
fundId string No
Returns : unknown
Async getInstrumentById
getInstrumentById(instrumentId: string)
Parameters :
Name Type Optional
instrumentId string No
Returns : unknown
Async getInstrumentsByFundId
getInstrumentsByFundId(fundId: string, limit?: number, skip?: number, orderDirection, user?: UserJwtPayload)
Parameters :
Name Type Optional Default value
fundId string No
limit number Yes
skip number Yes
orderDirection No OrderDirection.asc
user UserJwtPayload Yes
Returns : unknown
Async getNavs
getNavs(instrumentId: string)
Parameters :
Name Type Optional
instrumentId string No
Returns : unknown
Async updateNav
updateNav(undefined)
Parameters :
Name Optional
No
Returns : unknown

Properties

Private Readonly adminProfileService
Type : AdminProfileService
Decorators :
@Inject(AdminProfileService)
Private Readonly emailService
Type : EmailService
Decorators :
@Inject(EmailService)
Private Readonly userService
Type : UserService
Decorators :
@Inject(UserService)
import { AdminProfileService } from "./../admin-profile/admin-profile.service";
import {
  decodeBytes32String,
  encodeBytes32String,
  formatEther,
  formatUnits,
  parseUnits,
} from "ethers";
import {
  Inject,
  Injectable,
  ServiceUnavailableException,
} from "@nestjs/common";

import {
  apolloClient,
  getAllOrdersBySubscribedFundsIdQuery,
  getAllSubscribersQuery,
  getInvestorsIdByInstrumentQuery as getUsersIdByInstrumentQuery,
  transactionSubmitter,
} from "src/common/provider";
import {
  ContractName,
  EndUserRoles,
  IGetAllSubscribersQuery,
  IGetInstrumentsByFundQuery,
  IGetInvestorsIdByInstrumentQuery as IGetUsersIdByInstrumentQuery,
  IGetOrdersQuery,
  INavQuery,
  Instrument,
  OrderDirection,
  TransactionNames,
  UserJwtPayload,
} from "src/common/interfaces";
import {
  getContractByName,
  getRoleAndContractAddressByNameOrAddress,
} from "src/common/provider/web3/web3Provider";
import {
  getInstrumentByIdQuery,
  getInstrumentsByFundQuery,
  getNavsByInstrumentId,
} from "src/common/provider/thegraph/queries/instrument.qgl";
import { ApolloQueryResult } from "@apollo/client";
import { UpdatedNavDto } from "./dto/update-nav.dto";
import { BLOCKCHAIN_EXPLORER, Roles } from "src/common/constants";
import { UserService } from "src/user/user.service";
import { EmailService } from "src/common/provider/mail/email.service";
import { UpdateNaveTemplate } from "src/common/provider/mail/templates/UpdateNav";
import { getFundName } from "src/common/utils";
import { Nav, NavDocument } from "./schemas/nav.schema";
import { InjectModel } from "@nestjs/mongoose";
import { LeanDocument, Model } from "mongoose";
import { CustodialDocument } from "src/shared/custodial/schemas/custodial.schema";

@Injectable()
export class FundWeb3Service {
  @Inject(UserService)
  private readonly userService: UserService;

  @Inject(AdminProfileService)
  private readonly adminProfileService: AdminProfileService;

  @Inject(EmailService)
  private readonly emailService: EmailService;

  constructor(
    @InjectModel(Nav.name) private readonly navModel: Model<NavDocument>,
  ) {}

  async addFund(fundId: string, email: string) {
    const adminSigner = await this.adminProfileService.getCustodial(email);
    const tx = await transactionSubmitter({
      signerKey: adminSigner.privateKey,
      transactionName: TransactionNames.addNewFund,
      contractName: ContractName.FundRegistry,
      contractAddress: getContractByName[ContractName.FundRegistry].address,
      args: [Roles.Admin, encodeBytes32String(fundId)],
    });
    return tx;
  }
  async getFundOrders(fundId: string) {
    const orders: ApolloQueryResult<IGetOrdersQuery> = await apolloClient.query(
      {
        query: getAllOrdersBySubscribedFundsIdQuery,
        variables: {
          fundsId: [encodeBytes32String(fundId)],
          type: "",
        },
      },
    );
    return orders.data;
  }

  async getAllFundClients(fundId: string) {
    const orderBooks: ApolloQueryResult<IGetAllSubscribersQuery> =
      await apolloClient.query({
        query: getAllSubscribersQuery,
        variables: {
          fundsId: [encodeBytes32String(fundId)],
          type: "",
        },
      });
    const clientsIdInBytes = orderBooks.data.orderBooks
      .map((orderbook) => {
        const clientIds = orderbook.orders
          .map((order) => {
            if (order.onBehalf.associatedDealer) {
              return [order.onBehalf.id, order.onBehalf.associatedDealer.id];
            }
            return [order.onBehalf.id];
          })
          .flat();
        return clientIds;
      })
      .flat();

    const clientsIdInBytesSet = new Set(clientsIdInBytes);
    const clientsIdInString = Array.from(clientsIdInBytesSet).map((idInBytes) =>
      decodeBytes32String(idInBytes),
    );
    return await this.userService.findByIds(clientsIdInString);
  }

  async getInstrumentsByFundId(
    fundId: string,
    limit?: number,
    skip?: number,
    orderDirection = OrderDirection.asc,
    user?: UserJwtPayload,
  ) {
    let userId = "";
    if (user && user.role !== EndUserRoles.fundAdmin && user.role !== "admin") {
      userId = encodeBytes32String(
        (
          await this.userService.findUserByProperty({ email: user.email })
        )._id.toString(),
      );
    }
    const instruments: ApolloQueryResult<IGetInstrumentsByFundQuery> =
      await apolloClient.query({
        query: getInstrumentsByFundQuery,
        variables: {
          userId,
          fundId: encodeBytes32String(fundId),
          first: limit,
          skip: skip * limit,
          orderDirection,
        },
      });
    const instrumentToBalance: Record<string, number> = {};

    instruments.data.dealerSettlements.forEach((settlement) => {
      if (settlement.type === "Subscription") {
        instrumentToBalance[settlement.orderBook.instrument.id] =
          Number(formatUnits(settlement.amount, 6)) +
          (instrumentToBalance[settlement.orderBook.instrument.id] || 0);
      } else {
        instrumentToBalance[settlement.orderBook.instrument.id] =
          (instrumentToBalance[settlement.orderBook.instrument.id] || 0) -
          Number(formatEther(settlement.amount));
      }
    });
    const subscribedInstrumentsId: string[] = [];
    if (instruments.data.whitelistInfo) {
      instruments.data.whitelistInfo.subscribedInstrument.forEach(
        (instrument) => subscribedInstrumentsId.push(instrument.id),
      );
      return instruments.data.instruments
        .filter((instrument) => subscribedInstrumentsId.includes(instrument.id))
        .map((instrument) => {
          return {
            ...instrument,
            redeemableBalance: instrumentToBalance[instrument.id] || 0,
          };
        });
    }
    return instruments.data.instruments.map((instrument) => {
      return {
        ...instrument,
        redeemableBalance: instrumentToBalance[instrument.id] || 0,
      };
    });
  }
  async getInstrumentById(instrumentId: string) {
    const instrument: ApolloQueryResult<Instrument> = await apolloClient.query({
      query: getInstrumentByIdQuery,
      variables: { instrumentId: encodeBytes32String(instrumentId) },
    });
    return instrument.data;
  }
  async updateNav({
    instrumentId,
    navPerShare,
    isAuditedNav,
    userJwtPayload,
    timestamp,
  }: UpdatedNavDto & { instrumentId: string; userJwtPayload: UserJwtPayload }) {
    const isPlatformAdmin = userJwtPayload.role === "admin";
    const transactionName: TransactionNames = isAuditedNav
      ? TransactionNames.updateAuditedNavPerShare
      : TransactionNames.updateUnauditedNavPerShare;
    let signerKey: CustodialDocument;
    if (isPlatformAdmin) {
      signerKey = await this.adminProfileService.getCustodial(
        userJwtPayload.email,
      );
    } else {
      signerKey = await this.userService.getCustodial(userJwtPayload.email);
    }

    const [{ contractAddress, role }, whitelistInfo]: [
      { contractAddress: string; role: string },
      ApolloQueryResult<IGetUsersIdByInstrumentQuery>,
    ] = await Promise.all([
      getRoleAndContractAddressByNameOrAddress({
        contractName: ContractName.InstrumentRegistry,
        transactionName,
        userAddress: signerKey.address,
      }),
      apolloClient.query({
        query: getUsersIdByInstrumentQuery,
        variables: {
          instrumentId: encodeBytes32String(instrumentId),
        },
      }),
    ]);
    const tx = await transactionSubmitter({
      signerKey: signerKey.privateKey,
      transactionName,
      contractName: ContractName.InstrumentRegistry,
      contractAddress,
      args: [
        role,
        encodeBytes32String(instrumentId),
        parseUnits(navPerShare.toString(), 6),
      ],
    });
    const txTimeInUtc = new Date().toUTCString();
    if (whitelistInfo.errors) {
      console.log(whitelistInfo.errors[0]);
      throw new ServiceUnavailableException(whitelistInfo.errors[0]);
    }
    const users = await this.userService.findByIds(
      whitelistInfo.data.whitelistInfos.map((user) =>
        decodeBytes32String(user.id),
      ),
    );
    if (!users.length) {
      return tx;
    }
    const fundName = getFundName(
      users[0],
      whitelistInfo.data.instrument.fund.id,
    );
    const userPromises = [
      ...users.map(async (user) => {
        return await this.emailService.sendEmail({
          from: "yehia@nethermind.io",
          to: [user.email],
          subject: `${
            isAuditedNav ? "Audit" : "Unaudited"
          } NAV update on ${txTimeInUtc}`,
          html: UpdateNaveTemplate({
            userName: user.name,
            instrumentName: whitelistInfo.data.instrument.securityToken.name,
            fundName,
            amount: navPerShare.toString(),
            txHashUrl: `${BLOCKCHAIN_EXPLORER}/tx/${tx.hash}`,
            txTime: txTimeInUtc,
            isAudited: isAuditedNav,
          }),
        });
      }),
      this.navModel.updateOne(
        { timestamp, instrument: instrumentId, isAuditedNav },
        { instrument: instrumentId, timestamp, navPerShare, isAuditedNav },
        { upsert: true },
      ),
    ];
    await Promise.all(userPromises);
    return tx;
  }

  async getNavs(instrumentId: string) {
    const [navs, dbNavs]: [
      ApolloQueryResult<INavQuery>,
      LeanDocument<NavDocument>[],
    ] = await Promise.all([
      apolloClient.query({
        query: getNavsByInstrumentId,
        variables: { instrumentId: encodeBytes32String(instrumentId) },
      }),
      this.navModel.find().lean().exec(),
    ]);
    return { subgraph: navs.data, dbNavs };
  }
}

results matching ""

    No results matching ""