File

src/orderbook/orderbook.service.ts

Index

Properties
Methods

Methods

Async addFundNameToOrder
addFundNameToOrder(undefined: literal type)
Parameters :
Name Type Optional
literal type No
Returns : Promise<IOrderWithFundName[]>
Async adminLockOrder
adminLockOrder(undefined: ILockOrder)
Parameters :
Name Type Optional
ILockOrder No
Returns : unknown
Async cancelOrder
cancelOrder(undefined: ICancelOrder)
Parameters :
Name Type Optional
ICancelOrder No
Returns : unknown
Async dealerConfirmOrder
dealerConfirmOrder(undefined: IConfirmOrder)
Parameters :
Name Type Optional
IConfirmOrder No
Returns : unknown
Async dealerCreateOrder
dealerCreateOrder(undefined: ICreateOrder)
Parameters :
Name Type Optional
ICreateOrder No
Returns : unknown
Async dealerLockRedemptionOrder
dealerLockRedemptionOrder(undefined: ILockOrder)
Parameters :
Name Type Optional
ILockOrder No
Returns : unknown
Async dealerLockSubscriptionOrder
dealerLockSubscriptionOrder(undefined: ILockOrder)
Parameters :
Name Type Optional
ILockOrder No
Returns : unknown
Async executeDealerConfirmOrder
executeDealerConfirmOrder(orderDto: OrderConfirmDto, type: OrderType, orderSubgraph: ApolloQueryResult<IGetClosestOrdersToLockQuery>, order: Partial<IOrder>, dealer: LeanDocument<UserDocument>, dealerCustodial: CustodialDocument)
Parameters :
Name Type Optional
orderDto OrderConfirmDto No
type OrderType No
orderSubgraph ApolloQueryResult<IGetClosestOrdersToLockQuery> No
order Partial<IOrder> No
dealer LeanDocument<UserDocument> No
dealerCustodial CustodialDocument No
Returns : unknown
Async fillRedemptionBook
fillRedemptionBook(fillRedemptionBookDto: FillRedemptionBookDto)
Parameters :
Name Type Optional
fillRedemptionBookDto FillRedemptionBookDto No
Returns : unknown
Async forceRedemption
forceRedemption(undefined: IForceRedemptionOrder)
Parameters :
Name Type Optional
IForceRedemptionOrder No
Returns : unknown
Async getAllOrders
getAllOrders(request: AuthRequest, filterOrderDto?: FilterOrderDto)
Parameters :
Name Type Optional
request AuthRequest No
filterOrderDto FilterOrderDto Yes
Returns : Promise<literal type>
Async getMyOrders
getMyOrders(undefined: IGetMyOrders)
Parameters :
Name Type Optional
IGetMyOrders No
Returns : Promise<literal type>
Async getOrderById
getOrderById(undefined: IGetOrderById)
Parameters :
Name Type Optional
IGetOrderById No
Async investorConfirmOrder
investorConfirmOrder(undefined: IConfirmOrder)
Parameters :
Name Type Optional
IConfirmOrder No
Returns : unknown
Async investorCreateOrder
investorCreateOrder(undefined: ICreateOrder)
Parameters :
Name Type Optional
ICreateOrder No
Returns : unknown
Async investorLockRedemptionOrder
investorLockRedemptionOrder(undefined: ILockOrder)
Parameters :
Name Type Optional
ILockOrder No
Returns : unknown
Async investorLockSubscriptionOrder
investorLockSubscriptionOrder(undefined: ILockOrder)
Parameters :
Name Type Optional
ILockOrder No
Returns : unknown
isAllowedToCheckOrder
isAllowedToCheckOrder(order: IOrder, userIdInBytes: string, role: EndUserRoles | "admin")
Parameters :
Name Type Optional
order IOrder No
userIdInBytes string No
role EndUserRoles | "admin" No
Returns : boolean
Async reBalanceSettlement
reBalanceSettlement(undefined)
Parameters :
Name Optional
No
Returns : unknown
Async sendSettlementEmails
sendSettlementEmails(settledGraph: ApolloQueryResult<IGetLastSettledOrderIdQuery>, tx: TransactionResponse, fund: LeanDocument<FundDocument>, type: OrderType)
Parameters :
Name Type Optional
settledGraph ApolloQueryResult<IGetLastSettledOrderIdQuery> No
tx TransactionResponse No
fund LeanDocument<FundDocument> No
type OrderType No
Returns : any
Async settleOrder
settleOrder(undefined: ISettleOrders)
Parameters :
Name Type Optional
ISettleOrders No
Returns : unknown

Properties

Private Readonly adminProfileService
Type : AdminProfileService
Decorators :
@Inject(AdminProfileService)
Private Readonly cacheService
Type : Cache
Decorators :
@Inject(CACHE_MANAGER)
Private Readonly docuSignService
Type : DocuSignService
Decorators :
@Inject(DocuSignService)
Private Readonly emailService
Type : EmailService
Decorators :
@Inject(EmailService)
Private Readonly fundService
Type : FundService
Decorators :
@Inject(FundService)
Private Readonly instrumentService
Type : InstrumentService
Decorators :
@Inject(InstrumentService)
Private Readonly internalCustodialService
Type : InternalCustodialService
Decorators :
@Inject(InternalCustodialService)
Private Readonly settlementService
Type : SettlementService
Decorators :
@Inject(SettlementService)
Private Readonly userService
Type : UserService
Decorators :
@Inject(UserService)
import { UserJwtPayload } from "./../common/interfaces/SharedTypes";
import { InstrumentService } from "./../security/instrument.service";
import { CACHE_MANAGER } from "@nestjs/cache-manager";

import {
  BadRequestException,
  Inject,
  Injectable,
  ServiceUnavailableException,
  UnauthorizedException,
} from "@nestjs/common";
import { ApolloQueryResult } from "@apollo/client";
import {
  TransactionResponse,
  decodeBytes32String,
  encodeBytes32String,
  formatEther,
  formatUnits,
  parseEther,
  parseUnits,
} from "ethers";

import {
  ContractName,
  EndUserRoles,
  EventName,
  TransactionNames,
} from "src/common/interfaces";
import type {
  AuthRequest,
  IGeOrderByIdQuery,
  IGetClosestOrdersToLockQuery,
  IGetLastSettledOrderIdQuery,
  IGetOrderAmountAndPaymentTokenByIdQuery,
  IGetOrderAmountAndSecurityTokenByIdQuery,
  IGetOrderBookAddressByInstrumentQuery,
  IGetOrderDetailsQuery,
  IGetOrdersQuery,
  IOrder,
  OrderStatus,
  OrderType,
} from "src/common/interfaces";
import {
  apolloClient,
  eventListener,
  getAllOrdersByDealerIdQuery,
  getAllOrdersByIdsQuery,
  getAllOrdersByInvestorIdQuery,
  getClosestOrdersToLockQuery,
  getLastSettledOrderIdQuery,
  getOrderAmountAndPaymentTokenByIdQuery,
  getOrderAmountAndSecurityTokenByIdQuery,
  getOrderBookAddressByInstrumentQuery,
  getOrderByIdQuery,
  getOrderDetailsQuery,
  getRoleAndContractAddressByNameOrAddress,
  provider,
  transactionSubmitter,
} from "src/common/provider";
import { InternalCustodialService } from "src/shared/custodial/internalCustodial.service";
import { UserService } from "src/user/user.service";
import type {
  ICancelOrder,
  IConfirmOrder,
  ICreateOrder,
  IForceRedemptionOrder,
  IGetMyOrders,
  IGetOrderById,
  ILockOrder,
  IOrderWithFundName,
  IOrderWithInvestor,
  ISettleOrders,
  OrderDetailsInfo,
} from "./orderbook.interface";
import {
  BLOCKCHAIN_EXPLORER,
  PaymentToken,
  Roles,
  zeroAddress,
} from "src/common/constants";
import { FundService } from "src/security/fund.service";
import type { UserWithOnChainFields } from "src/user/user.interface";
import type { OrderReBalanceDto } from "./dto/rebalance-order.dto";
import type { User, UserDocument } from "src/user/schemas/user.schema";
import { LeanDocument } from "mongoose";
import { FillRedemptionBookDto } from "./dto/fill-redemptionbook.dto";
import { FilterOrderDto } from "./dto/filter-order.dto";
import { Fund, FundDocument } from "src/security/schemas/fund.schema";
import { InstrumentDocument } from "src/security/schemas/instrument.schema";
import { DocuSignService } from "src/docusign/docusign.service";
import { EmailService } from "src/common/provider/mail/email.service";
import {
  OrderConfirmedTemplate,
  OrderCreatedTemplate,
  OrderCancelledTemplate,
  OrderSettlementTemplate,
} from "src/common/provider/mail/templates";
import { getFundName } from "src/common/utils";
import type { Cache } from "cache-manager";
import type { OrderConfirmDto } from "./dto/confirm-order.dto";
import type { CustodialDocument } from "src/shared/custodial/schemas/custodial.schema";
import type { IOrderRequestWithDocusignStatus } from "src/docusign/docusign.interface";
import { SettlementService } from "./settlement.service";
import { AdminProfileService } from "src/admin-profile/admin-profile.service";
import { OrderRequestDocument } from "src/request-order/schemas/orderRequest.schema";

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

  @Inject(InternalCustodialService)
  private readonly internalCustodialService: InternalCustodialService;

  @Inject(FundService)
  private readonly fundService: FundService;

  @Inject(InstrumentService)
  private readonly instrumentService: InstrumentService;

  @Inject(DocuSignService)
  private readonly docuSignService: DocuSignService;

  @Inject(EmailService)
  private readonly emailService: EmailService;

  @Inject(CACHE_MANAGER)
  private readonly cacheService: Cache;

  @Inject(SettlementService)
  private readonly settlementService: SettlementService;

  @Inject(AdminProfileService)
  private readonly adminProfileService: AdminProfileService;

  async investorCreateOrder({ request, orderDto, type }: ICreateOrder) {
    const [investor, orderBookGraph]: [
      LeanDocument<UserDocument>,
      ApolloQueryResult<IGetOrderBookAddressByInstrumentQuery>,
    ] = await Promise.all([
      this.userService.findUserByProperty({ email: request.user.email }),
      apolloClient.query({
        query: getOrderBookAddressByInstrumentQuery,
        variables: {
          instrumentId: encodeBytes32String(orderDto.instrumentId),
          type,
        },
      }),
    ]);
    const custody = await this.internalCustodialService.findOne(
      investor.wallets[0].address,
    );
    const amount =
      type === "Subscription"
        ? parseUnits(orderDto.amount.toString(), 6).toString()
        : parseEther(orderDto.amount.toFixed(18)).toString();
    const lockInHint =
      orderBookGraph.data.orderBooks[0].lastOrders.length > 0
        ? orderBookGraph.data.orderBooks[0].lastOrders[0].actualId
        : 0;
    const tx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress: orderBookGraph.data.orderBooks[0].id,
      contractName: ContractName.SubscriptionBook,
      transactionName: TransactionNames.investorCreateOrder,
      args: [amount, lockInHint],
    });
    const { blockNumber } = await provider.waitForTransaction(tx.hash);
    const event = await eventListener({
      eventName: EventName.OrderCreated,
      contractName: ContractName.SubscriptionBook,
      blockNumber,
      address: orderBookGraph.data.orderBooks[0].id,
    });

    const orderId = Number(event.args[0]);
    return {
      tx,
      orderId: `${orderBookGraph.data.orderBooks[0].id}-${orderId}`,
    };
  }

  async investorConfirmOrder({ request, orderDto }: IConfirmOrder) {
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [investor, orderSubgraph]: [
      LeanDocument<UserDocument>,
      ApolloQueryResult<IGetClosestOrdersToLockQuery>,
    ] = await Promise.all([
      this.userService.findUserByProperty({
        email: request.user.email,
      }),
      apolloClient.query({
        query: getClosestOrdersToLockQuery,
        variables: {
          orderId: orderDto.orderId,
          orderBook: contractAddress,
          actualId: Number(orderId),
        },
      }),
    ]);
    if (orderSubgraph.error) {
      throw new ServiceUnavailableException(orderSubgraph.error);
    }

    const custody = await this.internalCustodialService.findOne(
      investor.wallets[0].address,
    );
    const lockInHint =
      orderSubgraph.data.closestOrdersToLock.length > 0
        ? orderSubgraph.data.closestOrdersToLock[0].actualId
        : 0;
    const tx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress,
      contractName: ContractName.SubscriptionBook,
      transactionName: TransactionNames.investorConfirmOrder,
      args: [orderId, lockInHint],
    });
    const docuSignMsg = this.docuSignService.createAnEnvelope({
      dealerEmail: investor.onboardByEmail,
      orderId: orderDto.orderId,
      ...orderDto.agreement,
      isAtomic: true,
    });
    return [tx, docuSignMsg];
  }

  async investorLockSubscriptionOrder({ orderDto, request }: ILockOrder) {
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [investor, orderAmountAndToken]: [
      LeanDocument<UserDocument> | UserWithOnChainFields,
      ApolloQueryResult<IGetOrderAmountAndPaymentTokenByIdQuery>,
    ] = await Promise.all([
      request.user.role === "admin"
        ? this.userService.findOne(orderDto.investorId)
        : this.userService.findUserByProperty({ email: request.user.email }),
      apolloClient.query({
        query: getOrderAmountAndPaymentTokenByIdQuery,
        variables: {
          orderId: orderDto.orderId,
          actualId: Number(orderId),
          orderbookAddress: contractAddress,
        },
      }),
    ]);

    const currentPaymentToken =
      orderAmountAndToken.data.orderBook.paymentTokenAsset.id;
    // If admin try to lock order with stablecoin
    if (
      PaymentToken.includes(currentPaymentToken) &&
      request.user.role === "admin"
    ) {
      throw new UnauthorizedException(
        "This orderbook use stablecoin only dealer can lock for himself.",
      );
    }

    // If dealer try to lock order with settlement token
    if (
      !PaymentToken.includes(currentPaymentToken) &&
      request.user.role === EndUserRoles.dealer
    ) {
      throw new UnauthorizedException(
        "This orderbook use settlement coin only admin can do locks on behalf of dealer.",
      );
    }

    const custody = await this.internalCustodialService.findOne(
      investor.wallets[0].address,
    );
    await transactionSubmitter({
      signerKey: process.env.RELAYER_KEY,
      contractAddress: currentPaymentToken,
      contractName: ContractName.erc20,
      transactionName: TransactionNames.transfer,
      args: [custody.address, orderAmountAndToken.data.order.amount],
    });

    const tx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress: currentPaymentToken,
      contractName: ContractName.erc20,
      transactionName: TransactionNames.approve,
      args: [contractAddress, orderAmountAndToken.data.order.amount],
    });
    await provider.waitForTransaction(tx.hash);
    const lockInHint =
      orderAmountAndToken.data.closestOrdersToLock.length > 0
        ? orderAmountAndToken.data.closestOrdersToLock[0].actualId
        : 0;
    const lockTx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress: contractAddress,
      contractName: ContractName.SubscriptionBook,
      transactionName: TransactionNames.investorLockTokens,
      args: [orderId, lockInHint],
    });
    await this.settlementService.resetVote(contractAddress);

    return lockTx;
  }

  async investorLockRedemptionOrder({ orderDto, request }: ILockOrder) {
    if (orderDto.dealerId) {
      throw new BadRequestException(
        "Shouldn't send dealer ID if you are an investor",
      );
    }
    if (orderDto.investorId) {
      throw new BadRequestException("investor should not send investor Id");
    }
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [investor, orderAmountAndToken]: [
      LeanDocument<UserDocument> | UserWithOnChainFields,
      ApolloQueryResult<IGetOrderAmountAndSecurityTokenByIdQuery>,
    ] = await Promise.all([
      request.user.role === "admin"
        ? this.userService.findOne(orderDto.investorId)
        : this.userService.findUserByProperty({ email: request.user.email }),
      apolloClient.query({
        query: getOrderAmountAndSecurityTokenByIdQuery,
        variables: {
          orderId: orderDto.orderId,
          actualId: Number(orderId),
          orderbookAddress: contractAddress,
        },
      }),
    ]);

    const currentPaymentToken = orderAmountAndToken.data.orderBook.asset.id;

    const custody = await this.internalCustodialService.findOne(
      investor.wallets[0].address,
    );
    const lockInHint =
      orderAmountAndToken.data.closestOrdersToLock.length > 0
        ? orderAmountAndToken.data.closestOrdersToLock[0].actualId
        : 0;
    await (
      await transactionSubmitter({
        signerKey: custody.privateKey,
        contractAddress: currentPaymentToken,
        contractName: ContractName.erc20,
        transactionName: TransactionNames.approve,
        args: [contractAddress, orderAmountAndToken.data.order.amount],
      })
    ).wait();
    const tx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress: contractAddress,
      contractName: ContractName.RedemptionBook,
      transactionName: TransactionNames.investorLockTokens,
      args: [orderId, lockInHint],
    });
    await this.settlementService.resetVote(contractAddress);

    return tx;
  }

  async dealerCreateOrder({ request, orderDto, type }: ICreateOrder) {
    const [investor, dealer, orderBookGraph]: [
      User,
      LeanDocument<UserDocument>,
      ApolloQueryResult<IGetOrderBookAddressByInstrumentQuery>,
    ] = await Promise.all([
      this.userService.findOne(orderDto.investorId) as Promise<User>,
      this.userService.findUserByProperty({ email: request.user.email }),
      apolloClient.query({
        query: getOrderBookAddressByInstrumentQuery,
        variables: {
          instrumentId: encodeBytes32String(orderDto.instrumentId),
          type,
        },
      }),
    ]);

    const custody = await this.internalCustodialService.findOne(
      dealer.wallets[0].address,
    );
    if (!orderBookGraph.data.orderBooks.length) {
      throw new BadRequestException(
        `No instrument with id ${orderDto.instrumentId}`,
      );
    }
    const amount =
      type === "Subscription"
        ? parseUnits(orderDto.amount.toString(), 6).toString()
        : parseEther(orderDto.amount.toFixed(18)).toString();

    const lockInHint =
      orderBookGraph.data.orderBooks[0].lastOrders.length > 0
        ? orderBookGraph.data.orderBooks[0].lastOrders[0].actualId
        : 0;
    const investorIdInBytes = encodeBytes32String(orderDto.investorId);
    const tx = await transactionSubmitter({
      signerKey: custody.privateKey,
      contractAddress: orderBookGraph.data.orderBooks[0].id,
      contractName: ContractName.RedemptionBook,
      transactionName: TransactionNames.dealerCreateOrder,
      args: [
        amount,
        investorIdInBytes,
        investor.wallets[0].address,
        zeroAddress,
        lockInHint,
      ],
    });

    const [, { blockNumber }] = await Promise.all([
      this.emailService.sendEmail({
        from: "yehia@nethermind.io",
        to: [investor.email, dealer.email],
        subject: `Created an order for investor ${investor.name}`,
        html: OrderCreatedTemplate({
          investorName: investor.name,
          orderType: type,
          txHashUrl: `${BLOCKCHAIN_EXPLORER}/tx/${tx.hash}`,
          amount:
            type === "Redemption"
              ? `${orderDto.amount} ${orderBookGraph.data.orderBooks[0].instrument.securityToken.symbol}`
              : `${orderDto.amount} USD`,
          fundName: getFundName(
            investor,
            orderBookGraph.data.orderBooks[0].fund.id,
          ),
          instrumentName:
            orderBookGraph.data.orderBooks[0].instrument.securityToken.name,
          txTime: new Date().toUTCString(),
        }),
      }),
      provider.waitForTransaction(tx.hash),
    ]);

    const event = await eventListener({
      eventName: EventName.OrderCreated,
      contractName: ContractName.RedemptionBook,
      blockNumber,
      address: orderBookGraph.data.orderBooks[0].id,
    });

    const orderId = Number(event.args[0]);

    return {
      tx,
      orderId: `${orderBookGraph.data.orderBooks[0].id}-${orderId}`,
    };
  }

  async dealerConfirmOrder({ request, orderDto, type }: IConfirmOrder) {
    const [contractAddress, orderId] = orderDto.orderId.split("-");

    const [dealer, orderbookSubgraph]: [
      LeanDocument<UserDocument>,
      ApolloQueryResult<IGetClosestOrdersToLockQuery> | undefined,
    ] = await Promise.all([
      this.userService.findUserByProperty({
        email: request.user.email,
      }),
      apolloClient.query({
        query: getClosestOrdersToLockQuery,
        variables: {
          orderId: orderDto.orderId,
          orderBook: contractAddress,
          actualId: Number(orderId),
        },
      }),
    ]);

    if (orderbookSubgraph.errors) {
      throw new ServiceUnavailableException(orderbookSubgraph.errors[0]);
    }

    const [custody, cachedOrder] = await Promise.all([
      this.internalCustodialService.findOne(dealer.wallets[0].address),
      this.cacheService.get(orderDto.orderId),
    ]);

    const order: Partial<IOrder> = orderbookSubgraph.data.order || cachedOrder;
    return await this.executeDealerConfirmOrder(
      orderDto,
      type,
      orderbookSubgraph,
      order,
      dealer,
      custody,
    );
  }
  async executeDealerConfirmOrder(
    orderDto: OrderConfirmDto,
    type: OrderType,
    orderSubgraph: ApolloQueryResult<IGetClosestOrdersToLockQuery>,
    order: Partial<IOrder>,
    dealer: LeanDocument<UserDocument>,
    dealerCustodial: CustodialDocument,
  ) {
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const lockInHint =
      orderSubgraph.data.closestOrdersToLock.length > 0
        ? orderSubgraph.data.closestOrdersToLock[0].actualId
        : 0;

    const investorId = decodeBytes32String(order.onBehalf.id);
    const [tx, investor] = await Promise.all([
      (
        await transactionSubmitter({
          signerKey: dealerCustodial.privateKey,
          contractAddress,
          contractName: ContractName.SubscriptionBook,
          transactionName: TransactionNames.dealerConfirmOrder,
          args: [orderId, zeroAddress, lockInHint],
        })
      ).wait(),

      this.userService.findOne(investorId),
    ]);
    const docuSignMsg = this.docuSignService.createAnEnvelope({
      dealerEmail: dealer.email,
      orderId: orderDto.orderId,
      ...orderDto.agreement,
      isAtomic: true,
    });
    const amount =
      type === "Redemption"
        ? `${formatEther(order.amount)} ${
            orderSubgraph.data.orderBook.instrument.securityToken.symbol
          }`
        : `${formatUnits(order.amount, 6)} USD`;

    await this.emailService.sendEmail({
      from: "yehia@nethermind.io",
      to: [dealer.email, investor.email],
      subject: `Order has been confirmed for investor ${investor.name}`,
      html: OrderConfirmedTemplate({
        orderType: type,
        txHashUrl: `${BLOCKCHAIN_EXPLORER}/tx/${tx.hash}`,
        amount,
        fundName: getFundName(investor, orderSubgraph.data.orderBook.fund.id),
        instrumentName:
          orderSubgraph.data.orderBook.instrument.securityToken.name,
        txTime: new Date().toUTCString(),
      }),
    });

    return [tx, docuSignMsg];
  }

  async dealerLockSubscriptionOrder({ orderDto, request }: ILockOrder) {
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [dealer, investor, orderAmountAndToken]: [
      User,
      User,
      ApolloQueryResult<IGetOrderAmountAndPaymentTokenByIdQuery>,
    ] = await Promise.all([
      request.user.role === "admin"
        ? (this.userService.findOne(orderDto.dealerId) as Promise<User>)
        : this.userService.findUserByProperty({ email: request.user.email }),
      orderDto.isUsingInvestorWallet &&
        (this.userService.findOne(orderDto.investorId) as Promise<User>),
      apolloClient.query({
        query: getOrderAmountAndPaymentTokenByIdQuery,
        variables: {
          orderId: orderDto.orderId,
          orderbookAddress: contractAddress,
        },
      }),
    ]);
    const currentPaymentToken =
      orderAmountAndToken.data.orderBook.paymentTokenAsset.id;

    // If admin try to lock order with stablecoin
    if (
      PaymentToken.includes(currentPaymentToken) &&
      request.user.role === "admin"
    ) {
      throw new UnauthorizedException(
        "This orderbook use stablecoin only dealer can lock for himself.",
      );
    }

    // If dealer try to lock order with settlement token
    if (
      !PaymentToken.includes(currentPaymentToken) &&
      request.user.role === EndUserRoles.dealer
    ) {
      throw new UnauthorizedException(
        "This orderbook use settlement coin only admin can do locks on behalf of dealer.",
      );
    }

    const [dealerCustody, investorCustody] = await Promise.all([
      this.internalCustodialService.findOne(dealer.wallets[0].address),
      orderDto.isUsingInvestorWallet &&
        this.internalCustodialService.findOne(investor.wallets[0].address),
    ]);

    await transactionSubmitter({
      signerKey: investorCustody
        ? investorCustody.privateKey
        : dealerCustody.privateKey,
      contractAddress: currentPaymentToken,
      contractName: ContractName.erc20,
      transactionName: TransactionNames.approve,
      args: [contractAddress, orderAmountAndToken.data.order.amount],
    });

    const tx = await transactionSubmitter({
      signerKey: process.env.RELAYER_KEY,
      contractAddress: currentPaymentToken,
      contractName: ContractName.erc20,
      transactionName: TransactionNames.transfer,
      args: [
        investorCustody ? investorCustody.address : dealerCustody.address,
        orderAmountAndToken.data.order.amount,
      ],
    });
    await provider.waitForTransaction(tx.hash);
    const lockInHint =
      orderAmountAndToken.data.closestOrdersToLock.length > 0
        ? orderAmountAndToken.data.closestOrdersToLock[0].actualId
        : 0;
    const lockTx = await transactionSubmitter({
      signerKey: dealerCustody.privateKey,
      contractAddress: contractAddress,
      contractName: ContractName.SubscriptionBook,
      transactionName: TransactionNames.dealerLockTokens,
      args: [
        orderId,
        investor ? investor.wallets[0].address : zeroAddress,
        lockInHint,
      ],
    });
    await this.settlementService.resetVote(contractAddress);

    return lockTx;
  }

  async dealerLockRedemptionOrder({ orderDto, request }: ILockOrder) {
    if (!orderDto.investorId) {
      throw new BadRequestException("Investor ID is missing");
    }
    if (orderDto.dealerId) {
      throw new BadRequestException("dealer should not send dealer Id");
    }

    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [dealer, investor, orderAmountAndToken]: [
      LeanDocument<UserDocument>,
      LeanDocument<UserDocument>,
      ApolloQueryResult<IGetOrderAmountAndSecurityTokenByIdQuery>,
    ] = await Promise.all([
      request.user.role === "admin"
        ? (this.userService.findOne(orderDto.dealerId) as Promise<User>)
        : this.userService.findUserByProperty({ email: request.user.email }),
      this.userService.findOne(orderDto.investorId) as Promise<User>,
      apolloClient.query({
        query: getOrderAmountAndSecurityTokenByIdQuery,
        variables: {
          orderId: orderDto.orderId,
          actualId: Number(orderId),
          orderbookAddress: contractAddress,
        },
      }),
    ]);

    const currentPaymentToken = orderAmountAndToken.data.orderBook.asset.id;

    const [dealerCustody, investorCustody] = await Promise.all([
      this.internalCustodialService.findOne(dealer.wallets[0].address),
      orderDto.isUsingInvestorWallet &&
        this.internalCustodialService.findOne(investor.wallets[0].address),
    ]);
    const lockInHint =
      orderAmountAndToken.data.closestOrdersToLock.length > 0
        ? orderAmountAndToken.data.closestOrdersToLock[0].actualId
        : 0;
    const order: Partial<IOrder> = orderAmountAndToken.data.order;

    await (
      await transactionSubmitter({
        signerKey: investorCustody
          ? investorCustody.privateKey
          : dealerCustody.privateKey,
        contractAddress: currentPaymentToken,
        contractName: ContractName.erc20,
        transactionName: TransactionNames.approve,
        args: [contractAddress, order.amount],
      })
    ).wait();
    const tx = await transactionSubmitter({
      signerKey: dealerCustody.privateKey,
      contractAddress: contractAddress,
      contractName: ContractName.RedemptionBook,
      transactionName: TransactionNames.dealerLockTokens,
      args: [
        orderId,
        investor ? investor.wallets[0].address : zeroAddress,
        lockInHint,
      ],
    });
    await this.settlementService.resetVote(contractAddress);

    return tx;
  }

  async adminLockOrder({ orderDto, request }: ILockOrder) {
    const isPlatformAdmin = request.user.role === "admin";
    const [contractAddress, orderId] = orderDto.orderId.split("-");
    const [orderAmountAndToken, adminSigner, user]: [
      ApolloQueryResult<IGetOrderAmountAndPaymentTokenByIdQuery>,
      CustodialDocument?,
      LeanDocument<UserDocument>?,
    ] = await Promise.all([
      apolloClient.query({
        query: getOrderAmountAndPaymentTokenByIdQuery,
        variables: {
          orderId: orderDto.orderId,
          actualId: Number(orderId),
          orderbookAddress: contractAddress,
        },
      }),
      isPlatformAdmin
        ? this.adminProfileService.getCustodial(request.user.email)
        : undefined,
      isPlatformAdmin
        ? undefined
        : this.userService.findUserByProperty({
            email: request.user.email,
          }),
    ]);

    let signerKey = adminSigner?.privateKey;

    if (!isPlatformAdmin) {
      signerKey = (
        await this.internalCustodialService.findOne(user.wallets[0].address)
      ).privateKey;
    }

    const { role } = await getRoleAndContractAddressByNameOrAddress({
      transactionName: TransactionNames.adminLockTokens,
      contractName: ContractName.SubscriptionBook,
      contractAddress,
      userAddress:
        request.user.role === "admin"
          ? adminSigner.address
          : user.wallets[0].address,
    });

    if (orderAmountAndToken.errors) {
      throw new ServiceUnavailableException(orderAmountAndToken.errors);
    }

    const currentPaymentToken =
      orderAmountAndToken.data.orderBook.paymentTokenAsset.id;
    const order = orderAmountAndToken.data.order;

    await (
      await transactionSubmitter({
        signerKey: signerKey,
        contractAddress: currentPaymentToken,
        contractName: ContractName.erc20,
        transactionName: TransactionNames.approve,
        args: [contractAddress, order.amount],
      })
    ).wait();
    const lockInHint =
      orderAmountAndToken.data.closestOrdersToLock.length > 0
        ? orderAmountAndToken.data.closestOrdersToLock[0].actualId
        : 0;

    const tx = await transactionSubmitter({
      signerKey,
      contractAddress: contractAddress,
      contractName: ContractName.SubscriptionBook,
      transactionName: TransactionNames.adminLockTokens,
      args: [role, orderId, zeroAddress, lockInHint],
    });
    await this.settlementService.resetVote(contractAddress);

    return tx;
  }

  async settleOrder({ orderDto, type, request }: ISettleOrders) {
    const isPlatformAdmin = request.user.role === "admin";
    const [settledGraph, adminSigner, user]: [
      ApolloQueryResult<IGetLastSettledOrderIdQuery>,
      CustodialDocument?,
      LeanDocument<UserDocument>?,
    ] = await Promise.all([
      apolloClient.query({
        query: getLastSettledOrderIdQuery,
        variables: {
          instrumentId: encodeBytes32String(orderDto.instrumentId),
          type,
        },
      }),
      isPlatformAdmin
        ? this.adminProfileService.getCustodial(request.user.email)
        : undefined,
      isPlatformAdmin
        ? undefined
        : this.userService.findUserByProperty({
            email: request.user.email,
          }),
    ]);
    if (!orderDto.orderId && !settledGraph.data.orderBooks[0].orders.length) {
      throw new BadRequestException("No locked errors to settle");
    }
    const lastOrderId = orderDto.orderId
      ? orderDto.orderId.split("-")[1]
      : settledGraph.data.orderBooks[0].orders[0].id.split("-")[1];

    let signerKey = adminSigner?.privateKey;

    if (!isPlatformAdmin) {
      const settlementProgress = await this.settlementService.approveSettlement(
        {
          orderbookId: settledGraph.data.orderBooks[0].id,
          fundId: settledGraph.data.orderBooks[0].fund.id,
          userJwt: request.user,
          orderId: settledGraph.data.orderBooks[0].orders[0].id,
        },
      );
      if (!settlementProgress.isAllowed) {
        return settlementProgress;
      }
      signerKey = (
        await this.internalCustodialService.findOne(user.wallets[0].address)
      ).privateKey;
    }

    const { role } = await getRoleAndContractAddressByNameOrAddress({
      transactionName: TransactionNames.settleOrders,
      contractName: ContractName.SubscriptionBook,
      contractAddress: settledGraph.data.orderBooks[0].id,
      userAddress:
        request.user.role === "admin"
          ? adminSigner.address
          : user.wallets[0].address,
    });

    const [tx, fund] = await Promise.all([
      transactionSubmitter({
        signerKey,
        contractAddress: settledGraph.data.orderBooks[0].id,
        contractName: ContractName.SubscriptionBook,
        transactionName: TransactionNames.settleOrders,
        args: [lastOrderId, orderDto.percentageToSettle * 100, role],
      }),
      this.fundService.findOne(
        decodeBytes32String(settledGraph.data.orderBooks[0].fund.id),
      ),
    ]);

    await Promise.all([
      this.sendSettlementEmails(settledGraph, tx, fund, type),
      this.settlementService.resetVote(settledGraph.data.orderBooks[0].id),
    ]);

    return tx;
  }

  async sendSettlementEmails(
    settledGraph: ApolloQueryResult<IGetLastSettledOrderIdQuery>,
    tx: TransactionResponse,
    fund: LeanDocument<FundDocument>,
    type: OrderType,
  ) {
    const dealerById: Record<string, User> = {};
    const templateByInvestorId: Record<string, string> = {};
    const dealerIdByInvestorId: Record<string, string> = {};
    await Promise.all(
      settledGraph.data.orderBooks[0].lockedOrders.map(async (order) => {
        const orderNo = order.id.split("-")[1];
        const navPerShare = Number(
          formatUnits(
            settledGraph.data.orderBooks[0].instrument.navs[0].navPerShare,
            6,
          ),
        );
        let amount = `${formatUnits(order.amount, 6)} USD`;
        let shares = Number(formatUnits(order.amount, 6)) * navPerShare;
        let instrumentSharesHolding =
          Number(
            formatUnits(settledGraph.data.orderBooks[0].settledAmount, 6),
          ) / navPerShare;
        const shareUnit =
          settledGraph.data.orderBooks[0].instrument.securityToken.symbol;
        let instrumentSharesInUsd = `${instrumentSharesHolding} USD`;
        if (type === "Redemption") {
          amount = `${formatEther(order.amount)} ${
            settledGraph.data.orderBooks[0].instrument.securityToken.symbol
          }`;
          shares = Number(formatEther(order.amount));
          instrumentSharesHolding = Number(
            formatEther(settledGraph.data.orderBooks[0].settledAmount),
          );
          instrumentSharesInUsd = `${
            instrumentSharesHolding * navPerShare
          } USD`;
        }
        const investor = (await this.userService.findOne(
          decodeBytes32String(order.onBehalf.id),
        )) as UserDocument;
        const emailHtml = OrderSettlementTemplate({
          orderNo,
          investorName: investor.name,
          investorId: investor._id.toString(),
          investorAddress: investor.wallets[0].address,
          orderType: type,
          txHashUrl: `${BLOCKCHAIN_EXPLORER}/tx/${tx.hash}`,
          amount,
          fundName: fund.title,
          instrumentName:
            settledGraph.data.orderBooks[0].instrument.securityToken.name,
          lockDate: new Date(order.lockTime * 1000).toUTCString(),
          txTime: new Date().toUTCString(),
          navDate: new Date(
            settledGraph.data.orderBooks[0].instrument.navs[0].timestamp * 1000,
          ).toUTCString(),
          navPerShare: `${navPerShare} USD`,
          shares: `${shares} ${shareUnit}`,
          instrumentSharesHolding: `${instrumentSharesHolding} ${shareUnit}`,
          instrumentSharesInUsd,
        });
        const dealerId = decodeBytes32String(order.dealer.id);
        dealerIdByInvestorId[investor._id.toString()] = dealerId;
        const initOrderText = `<br> <br> The Investor ${investor.name} with settled order(s): <br/> ${emailHtml} <br><br><br>`;
        const nextOrderText = `<br><br> Order #${orderNo} ${emailHtml} <br><br><br>`;
        if (!dealerById[dealerId]) {
          const dealer = await this.userService.findOne(dealerId);
          dealerById[dealerId] = dealer;
        }
        if (!templateByInvestorId[investor._id.toString()]) {
          templateByInvestorId[investor._id.toString()] = initOrderText;
        } else {
          templateByInvestorId[investor._id.toString()] =
            templateByInvestorId[investor._id.toString()] + nextOrderText;
        }

        await this.emailService.sendEmail({
          from: "yehia@nethermind.io",
          to: [investor.email],
          subject: ` ${type} Order has been Settled`,
          html: emailHtml,
        });
        return true;
      }),
    );
    await Promise.all(
      Object.keys(dealerById).map(async (dealerId) => {
        const investorIds = Object.keys(dealerIdByInvestorId).filter(
          (investorId) => {
            return dealerId === dealerIdByInvestorId[investorId];
          },
        );

        const template = investorIds
          .map((investorId) => {
            return templateByInvestorId[investorId];
          })
          .join("\n");

        return await this.emailService.sendEmail({
          from: "yehia@nethermind.io",
          to: [dealerById[dealerId].email],
          subject: `${type} Order has been Settled`,
          html: template,
        });
      }),
    );
  }

  async reBalanceSettlement({
    instrumentId,
    orderId,
    userJwt,
  }: OrderReBalanceDto & { userJwt: UserJwtPayload }) {
    const [settledGraph, signerKey]: [
      ApolloQueryResult<IGetLastSettledOrderIdQuery>,
      CustodialDocument,
    ] = await Promise.all([
      apolloClient.query({
        query: getLastSettledOrderIdQuery,
        variables: {
          instrumentId: encodeBytes32String(instrumentId),
          type: "Redemption",
        },
      }),
      userJwt.role === "admin"
        ? this.adminProfileService.getCustodial(userJwt.email)
        : this.userService.getCustodial(userJwt.email),
    ]);
    const lastOrderId =
      orderId.split("-")[1] ||
      settledGraph.data.orderBooks[0].orders[0].id.split("-")[1];

    return await transactionSubmitter({
      signerKey: signerKey.privateKey,
      contractAddress: settledGraph.data.orderBooks[0].id,
      contractName: ContractName.RedemptionBook,
      transactionName: TransactionNames.rebalanceSettlements,
      args: [lastOrderId, Roles.Admin],
    });
  }
  async forceRedemption({ userJwt, orderDto, type }: IForceRedemptionOrder) {
    const [orderBookGraph, signerKey]: [
      ApolloQueryResult<IGetOrderBookAddressByInstrumentQuery>,
      CustodialDocument,
    ] = await Promise.all([
      apolloClient.query({
        query: getOrderBookAddressByInstrumentQuery,
        variables: {
          instrumentId: encodeBytes32String(orderDto.instrumentId),
          type,
        },
      }),
      userJwt.role === "admin"
        ? this.adminProfileService.getCustodial(userJwt.email)
        : this.userService.getCustodial(userJwt.email),
    ]);

    const lockInHint =
      orderBookGraph.data.orderBooks[0].closestOrdersToLock.length > 0
        ? orderBookGraph.data.orderBooks[0].closestOrdersToLock[0].actualId
        : 0;

    return await transactionSubmitter({
      signerKey: signerKey.privateKey,
      contractAddress: orderBookGraph.data.orderBooks[0].id,
      contractName: ContractName.RedemptionBook,
      transactionName: TransactionNames.adminForceRedemption,
      args: [orderDto.investorId, Roles.Admin, lockInHint],
    });
  }
  async cancelOrder({ request, orderDto }: ICancelOrder) {
    let transactionName: TransactionNames = TransactionNames.adminCancelOrder;
    const [contractAddress, orderId] = orderDto.orderId.split("-");

    let signerKey: CustodialDocument;
    if (request.user.role !== "admin") {
      transactionName =
        request.user.role === "dealer"
          ? TransactionNames.dealerCancelOrder
          : TransactionNames.investorCancelOrder;
      if (request.user.role === "fundAdmin") {
        transactionName = TransactionNames.adminCancelOrder;
      }

      signerKey = await this.userService.getCustodial(request.user.email);
    } else {
      signerKey = await this.adminProfileService.getCustodial(
        request.user.email,
      );
    }
    const [subgraphOrder, { role }]: [
      ApolloQueryResult<IGetOrderDetailsQuery>,
      { contractAddress: string; role: string },
    ] = await Promise.all([
      apolloClient.query({
        query: getOrderDetailsQuery,
        variables: {
          orderBook: contractAddress,
          orderId: orderDto.orderId,
        },
      }),
      getRoleAndContractAddressByNameOrAddress({
        transactionName: TransactionNames.adminLockTokens,
        contractName: ContractName.SubscriptionBook,
        contractAddress,
        userAddress: signerKey.address,
      }),
    ]);

    if (subgraphOrder.errors) {
      console.error(subgraphOrder.errors[0]);
      throw new ServiceUnavailableException(subgraphOrder.errors);
    }

    const tx = await transactionSubmitter({
      signerKey: signerKey.privateKey,
      contractAddress,
      contractName: ContractName.SubscriptionBook,
      transactionName,
      args:
        transactionName === TransactionNames.adminCancelOrder
          ? [orderId, role]
          : [orderId],
    });
    const order = subgraphOrder.data.order;

    const amount =
      subgraphOrder.data.orderBook.type === "Redemption"
        ? `${formatEther(order.amount)} ${
            subgraphOrder.data.orderBook.instrument.securityToken.symbol
          }`
        : `${formatUnits(order.amount, 6)} USD`;

    const investor = await this.userService.findOne(
      decodeBytes32String(order.onBehalf.id),
    );

    await this.emailService.sendEmail({
      from: "yehia@nethermind.io",
      to: [investor.email],
      subject: `Order has been canceled`,
      html: OrderCancelledTemplate({
        orderType: subgraphOrder.data.orderBook.type,
        txHashUrl: `${BLOCKCHAIN_EXPLORER}/tx/${tx.hash}`,
        fundName: getFundName(investor, subgraphOrder.data.orderBook.fund.id),
        instrumentName:
          subgraphOrder.data.orderBook.instrument.securityToken.name,
        txTime: new Date().toUTCString(),
        amount,
      }),
    });

    return tx;
  }

  async fillRedemptionBook(fillRedemptionBookDto: FillRedemptionBookDto) {
    const orderBookGraph: ApolloQueryResult<IGetOrderBookAddressByInstrumentQuery> =
      await apolloClient.query({
        query: getOrderBookAddressByInstrumentQuery,
        variables: {
          instrumentId: encodeBytes32String(fillRedemptionBookDto.instrumentId),
          type: "Redemption",
        },
      });
    if (!orderBookGraph.data.orderBooks.length) {
      throw new BadRequestException(
        `No instrument with id ${fillRedemptionBookDto.instrumentId}`,
      );
    }
    return await transactionSubmitter({
      signerKey: process.env.RELAYER_KEY,
      contractAddress: PaymentToken,
      contractName: ContractName.erc20,
      transactionName: TransactionNames.mint,
      args: [
        orderBookGraph.data.orderBooks[0].id,
        parseUnits(fillRedemptionBookDto.amount, 6),
      ],
    });
  }

  async getAllOrders(
    request: AuthRequest,
    filterOrderDto?: FilterOrderDto,
  ): Promise<{ orders: IOrderWithFundName[] }> {
    const orderRequests = await this.docuSignService.getContracts(
      request.user,
      {
        limit: filterOrderDto.limit,
        skip: filterOrderDto.skip,
        orderDirection: filterOrderDto.orderDirection,
        ...filterOrderDto,
      },
    );
    const requestByOrderId: Record<string, IOrderRequestWithDocusignStatus> =
      {};
    orderRequests.forEach(
      (orderRequest) => (requestByOrderId[orderRequest.orderId] = orderRequest),
    );

    const ordersSubgraph: ApolloQueryResult<IGetOrdersQuery> =
      await apolloClient.query({
        query: getAllOrdersByIdsQuery,
        variables: {
          orderIds: orderRequests.map((orderRequest) => orderRequest.orderId),
          status: filterOrderDto.orderStatus,
          type: filterOrderDto.orderType,
          orderDirection: filterOrderDto.orderDirection,
        },
      });

    if (ordersSubgraph.errors) {
      throw new ServiceUnavailableException(ordersSubgraph.errors);
    }

    const orders = ordersSubgraph.data.orders;
    const ordersWithFundName: IOrderWithFundName[] =
      await this.addFundNameToOrder({ orders, orderRequests });

    return {
      orders: ordersWithFundName,
    };
  }

  isAllowedToCheckOrder(
    order: IOrder,
    userIdInBytes: string,
    role: EndUserRoles | "admin",
  ) {
    if (role === EndUserRoles.investor) {
      return order.onBehalf.id === userIdInBytes;
    }

    if (role === EndUserRoles.dealer) {
      return order.onBehalf.associatedDealer.id === userIdInBytes;
    }
    return false;
  }
  async getMyOrders({
    request,
    orderType,
    orderDirection,
    orderStatus,
    skip,
    limit,
  }: IGetMyOrders): Promise<{
    orders: IOrder[] | IOrderWithInvestor[];
  }> {
    try {
      if (request.user.role === EndUserRoles.investor) {
        const investor = await this.userService.findUserByProperty({
          email: request.user.email,
        });
        const { data, errors }: ApolloQueryResult<IGetOrdersQuery> =
          await apolloClient.query({
            query: getAllOrdersByInvestorIdQuery,
            variables: {
              investorId: encodeBytes32String(investor._id.toString()),
              status: orderStatus,
              type: orderType,
              first: limit,
              skip: skip * limit,
              orderDirection,
            },
          });
        if (errors) {
          console.error(errors);
          throw new ServiceUnavailableException(errors);
        }
        const { orders } = data;
        let ordersTimeFilter = {};
        if (orders.length) {
          const isAsc =
            orders[0].creationTime < orders[orders.length - 1].creationTime;
          const orderFromTime = isAsc
            ? orders[0].creationTime
            : orders[orders.length - 1].creationTime;

          const orderToTime = isAsc
            ? orders[orders.length - 1].creationTime ||
              orders[orders.length - 1].confirmTime
            : orders[0].creationTime || orders[0].confirmTime;

          ordersTimeFilter = {
            orderFromTime: new Date(
              (Number(orderFromTime) - 60 * 60) * 1000,
            ).toISOString(),
            orderToTime: new Date(
              (Number(orderToTime) + 60 * 60) * 1000,
            ).toISOString(),
          };
        }
        const orderRequests =
          await this.docuSignService.getInvestorSubmittedDocuments({
            receiverEmail: investor.email,
            ...ordersTimeFilter,
          });
        const requestByOrderId: Record<
          string,
          LeanDocument<OrderRequestDocument>
        > = {};
        orderRequests.forEach(
          (orderRequest) =>
            (requestByOrderId[orderRequest.orderId] = orderRequest),
        );

        const ordersWithFund = await this.addFundNameToOrder({
          orders,
          orderRequests,
        });
        return { orders: ordersWithFund };
      }

      const dealer = await this.userService.findUserByProperty({
        email: request.user.email,
      });
      const dealerIdInBytes = encodeBytes32String(dealer._id.toString());
      const ordersSubgraph: ApolloQueryResult<IGetOrdersQuery> =
        await apolloClient.query({
          query: getAllOrdersByDealerIdQuery,
          variables: {
            dealerId: dealerIdInBytes,
            status: orderStatus,
            type: orderType,
            first: limit,
            skip: skip * limit,
            orderDirection,
          },
        });

      const theGraphOrders = ordersSubgraph.data.orders;
      const investorsByIdBytes32: Record<string, UserWithOnChainFields> = {};
      const investorIds = new Set(
        theGraphOrders.map((order) => {
          return decodeBytes32String(order.onBehalf.id);
        }),
      );
      const investors = await this.userService.findByIds(
        Array.from(investorIds),
      );
      investors.forEach((investor) => {
        investorsByIdBytes32[encodeBytes32String(investor._id.toString())] =
          investor;
      });
      let ordersTimeFilter = {};
      if (theGraphOrders.length) {
        const isAsc =
          theGraphOrders[0].creationTime <
          theGraphOrders[theGraphOrders.length - 1].creationTime;
        const orderFromTime = isAsc
          ? theGraphOrders[0].creationTime
          : theGraphOrders[theGraphOrders.length - 1].creationTime;

        const orderToTime = isAsc
          ? theGraphOrders[theGraphOrders.length - 1].creationTime ||
            theGraphOrders[theGraphOrders.length - 1].confirmTime
          : theGraphOrders[0].creationTime || theGraphOrders[0].confirmTime;

        ordersTimeFilter = {
          orderFromTime: new Date(
            (Number(orderFromTime) - 60 * 60) * 1000,
          ).toISOString(),
          orderToTime: new Date(
            (Number(orderToTime) + 60 * 60) * 1000,
          ).toISOString(),
        };
      }
      const orderRequests =
        await this.docuSignService.getDealerSubmittedDocuments({
          customFieldName: "dealerEmail",
          customFieldValue: request.user.email,
          ...ordersTimeFilter,
        });
      const requestByOrderId: Record<
        string,
        LeanDocument<OrderRequestDocument>
      > = {};
      orderRequests.forEach(
        (orderRequest) =>
          (requestByOrderId[orderRequest.orderId] = orderRequest),
      );

      const orders = await this.addFundNameToOrder({
        orders: theGraphOrders,
        orderRequests,
      });

      const ordersWithInvestors: IOrderWithInvestor[] = orders.map((order) => {
        return {
          ...order,
          onBehalf: {
            ...order.onBehalf,
            ...investorsByIdBytes32[order.onBehalf.id],
          },
        };
      });

      return { orders: ordersWithInvestors };
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }
  async addFundNameToOrder({
    orders,
    orderRequests,
  }: {
    orders: IOrder[];
    orderRequests: IOrderRequestWithDocusignStatus[];
  }): Promise<IOrderWithFundName[]> {
    const fundNameByIdInBytes: Record<string, string> = {};

    const requestByOrderId: Record<string, IOrderRequestWithDocusignStatus> =
      {};
    orderRequests.forEach(
      (orderRequest) => (requestByOrderId[orderRequest.orderId] = orderRequest),
    );
    return await Promise.all(
      orders.map(async (order) => {
        const fundIdInBytes = order.orderBook.fund.id;
        let fundTitle = fundNameByIdInBytes[fundIdInBytes];
        if (!fundTitle) {
          fundTitle = await this.cacheService.get(fundIdInBytes);
          if (!fundTitle) {
            const fund = await this.fundService.findOne(
              decodeBytes32String(fundIdInBytes),
            );
            fundTitle = fund.title;
            await this.cacheService.set(fundIdInBytes, fund.title);
            fundNameByIdInBytes[fundIdInBytes] = fundTitle;
          } else {
            fundNameByIdInBytes[fundIdInBytes] = fundTitle;
          }
        }
        const orderWithFundName = {
          ...order,
          orderBook: {
            ...order.orderBook,
            fund: {
              ...order.orderBook.fund,
              title: fundTitle,
            },
          },
        };
        if (orderWithFundName.status === "Confirmed") {
          return {
            ...orderWithFundName,
            status: (requestByOrderId[orderWithFundName.id]?.status ||
              orderWithFundName.status) as OrderStatus,
            optionalComment:
              requestByOrderId[orderWithFundName.id]?.optionalComment || "",
            docuSignStatus: requestByOrderId[order.id]?.docuSignStatus || "",
          };
        }
        return { ...orderWithFundName, optionalComment: "" };
      }),
    );
  }
  async getOrderById({
    request,
    orderId,
  }: IGetOrderById): Promise<OrderDetailsInfo> {
    let subgraphOrder: ApolloQueryResult<IGeOrderByIdQuery>;
    if (request.user.role === EndUserRoles.investor) {
      const investor = await this.userService.findUserByProperty({
        email: request.user.email,
      });
      subgraphOrder = await apolloClient.query({
        query: getOrderByIdQuery,
        variables: {
          orderBookId: orderId.split("-")[0],
          orderId,
          onBehalf: encodeBytes32String(investor._id.toString()),
          dealerId: "",
        },
      });
    } else if (request.user.role === EndUserRoles.dealer) {
      const dealer = await this.userService.findUserByProperty({
        email: request.user.email,
      });

      subgraphOrder = await apolloClient.query({
        query: getOrderByIdQuery,
        variables: {
          orderBookId: orderId.split("-")[0],
          orderId,
          onBehalf: "",
          dealerId: encodeBytes32String(dealer._id.toString()),
        },
      });
    } else {
      subgraphOrder = await apolloClient.query({
        query: getOrderByIdQuery,
        variables: {
          orderBookId: orderId.split("-")[0],
          orderId,
          onBehalf: "",
          dealerId: "",
        },
      });
    }

    if (subgraphOrder.errors) {
      console.error(subgraphOrder.errors);
      throw new ServiceUnavailableException(subgraphOrder.errors);
    }

    const order = subgraphOrder.data.orders[0];

    if (!order) {
      throw new BadRequestException(`Order does not exist with id ${orderId}`);
    }
    const orderDetails = {
      ...order,
      amount: formatUnits(order.amount, 6),
    };
    const [investor, dealer, fund, instrument]: [
      User,
      User,
      Fund,
      InstrumentDocument,
    ] = await Promise.all([
      this.userService.findOne(
        decodeBytes32String(orderDetails.onBehalf.id),
        true,
      ) as Promise<User>,
      this.userService.findOne(
        decodeBytes32String(orderDetails.onBehalf.associatedDealer.id || ""),
      ) as Promise<User>,
      this.fundService.findOne(
        decodeBytes32String(subgraphOrder.data.orderBook.fund.id),
      ),
      this.instrumentService.findOne(
        decodeBytes32String(subgraphOrder.data.orderBook.instrument.id),
      ),
    ]);
    return {
      orderDetails,
      investor,
      dealer,
      fund,
      instrument,
    };
  }
}

results matching ""

    No results matching ""