File

src/user/user.service.ts

Index

Properties
Methods

Constructor

constructor(userModel: Model<UserDocument>)
Parameters :
Name Type Optional
userModel Model<UserDocument> No

Methods

Async addNewWallet
addNewWallet(undefined: IAddedWallet)
Parameters :
Name Type Optional
IAddedWallet No
Returns : unknown
Async checkIfUserIsAllowedToFund
checkIfUserIsAllowedToFund(userJwt: UserJwtPayload, fundId: string)
Parameters :
Name Type Optional
userJwt UserJwtPayload No
fundId string No
Returns : unknown
convertToBatch
convertToBatch(setRegistryDataFields: ISetRegistryDataFields)
Parameters :
Name Type Optional
setRegistryDataFields ISetRegistryDataFields No
Returns : IBatch
Async create
create(createUser: UserEntity)
Parameters :
Name Type Optional
createUser UserEntity No
Async deleteByEmail
deleteByEmail(email: string)
Parameters :
Name Type Optional
email string No
Returns : Promise<void>
Async findByIds
findByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : unknown
Async findOne
findOne(id: string, hasOnChainFields?: boolean)
Parameters :
Name Type Optional
id string No
hasOnChainFields boolean Yes
Async findUserByProperty
findUserByProperty(filterUserDto: FilterUserDto, hasSubscribedFundsPopulated?: boolean)
Parameters :
Name Type Optional
filterUserDto FilterUserDto No
hasSubscribedFundsPopulated boolean Yes
Async findUsersByFilter
findUsersByFilter(filterUserDto: FilterUserDto, includeOnChainField?: boolean)
Parameters :
Name Type Optional
filterUserDto FilterUserDto No
includeOnChainField boolean Yes
Returns : unknown
Async findUsersByFilterByUsersBalance
findUsersByFilterByUsersBalance(filterUserDto: FilterUserDto, unconfirmedRedemptionAmountById: Record)
Parameters :
Name Type Optional
filterUserDto FilterUserDto No
unconfirmedRedemptionAmountById Record<string | string> No
Returns : unknown
Async getCustodial
getCustodial(email: string, address?: string)
Parameters :
Name Type Optional
email string No
address string Yes
Returns : unknown
Async getFundSubscriptionDetails
getFundSubscriptionDetails(undefined: IGetFundSubscriptionDetails)
Parameters :
Name Type Optional
IGetFundSubscriptionDetails No
Returns : unknown
Async getInvestorBalanceByInstrument
getInvestorBalanceByInstrument(undefined: IGetInvestorBalanceByInstrument)
Parameters :
Name Type Optional
IGetInvestorBalanceByInstrument No
Returns : unknown
Async getUsersOnChainFields
getUsersOnChainFields(users: LeanDocument<UserDocument>[])
Parameters :
Name Type Optional
users LeanDocument<UserDocument>[] No
Async isDealerAllowed
isDealerAllowed(undefined: IDealerIsAllowed)
Parameters :
Name Type Optional
IDealerIsAllowed No
Returns : unknown
Async setRegistryData
setRegistryData(setRegistryDataFields: ISetRegistryDataFields)
Parameters :
Name Type Optional
setRegistryDataFields ISetRegistryDataFields No
Returns : unknown
Async setUsersInstrumentBalance
setUsersInstrumentBalance(undefined: ISetUsersInstrumentBalance)
Parameters :
Name Type Optional
ISetUsersInstrumentBalance No
Returns : unknown
Async subscribeDealerToNewFund
subscribeDealerToNewFund(dealer: UserDocument, subscribeUserDto: ISubscribeUser)
Parameters :
Name Type Optional
dealer UserDocument No
subscribeUserDto ISubscribeUser No
Returns : unknown
Async subscribeInvestorToNewFund
subscribeInvestorToNewFund(user: UserDocument | UserWithOnChainFields, subscribedFund: string[])
Parameters :
Name Type Optional
user UserDocument | UserWithOnChainFields No
subscribedFund string[] No
Returns : unknown
Async update
update(undefined: IUpdateUser)
Parameters :
Name Type Optional
IUpdateUser No
Async userFilesUploader
userFilesUploader(user: UserEntity)
Parameters :
Name Type Optional
user UserEntity No
Returns : unknown
Async userSubscribeToNewFund
userSubscribeToNewFund(subscribeUserDto: ISubscribeUser)
Parameters :
Name Type Optional
subscribeUserDto ISubscribeUser No
Returns : unknown

Properties

Private Readonly fundAdminService
Type : FundAdminService
Decorators :
@Inject(FundAdminService)
Private Readonly internalCustodialService
Type : InternalCustodialService
Decorators :
@Inject(InternalCustodialService)
Private Readonly onChainDataFieldService
Type : OnChainDataFieldService
Decorators :
@Inject(OnChainDataFieldService)
Private Readonly s3Service
Type : S3Service
Private Readonly walletService
Type : WalletService
Decorators :
@Inject(WalletService)
import { UserDocument } from "src/user/schemas/user.schema";
import {
  LeanDocument,
  Model,
  ObjectId,
  Types,
  isValidObjectId,
} from "mongoose";

import {
  BadRequestException,
  Inject,
  Injectable,
  ServiceUnavailableException,
  UnauthorizedException,
} from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import {
  decodeBytes32String,
  encodeBytes32String,
  formatEther,
  hexlify,
  keccak256,
  toUtf8Bytes,
} from "ethers";

import type {
  IAddedWallet,
  IDealerIsAllowed,
  IGetFundSubscriptionDetails,
  IGetInvestorBalanceByInstrument,
  ISetUsersInstrumentBalance,
  ISubscribeUser,
  IUpdateUser,
  IUserOnChainDataField,
  UserWithOnChainFields,
} from "./user.interface";
import type {
  IBatch,
  ICmsField,
  IGetAllFundInstrumentsByFundIdAndInvestorIdQuery,
  IGetGroupOfInvestorsRegistryFieldsQuery,
  IGetInstrumentStAddressQuery,
  IGetUserInstrumentBalanceQuery,
  UserJwtPayload,
} from "src/common/interfaces";

import {
  ContractName,
  EndUserRoles,
  QueryNames,
  TransactionNames,
  RegistryName,
} from "src/common/interfaces";

import { S3Service } from "src/common/provider/storage/s3";
import { User } from "./schemas/user.schema";
import { UserEntity } from "./entities/user.entity";
import { WalletService } from "src/wallet/wallet.service";
import { FilterUserDto } from "./dto/filter-user.dto";
import {
  apolloClient,
  getAllFundInstrumentsByFundIdAndInvestorIdQuery,
  getGroupOfInvestorsRegistryFieldsQuery,
  getUserInstrumentBalanceQuery,
  transactionQuery,
  transactionSubmitter,
} from "src/common/provider";

import {
  getContractByName,
  getRoleAndContractAddressByNameOrAddress,
} from "src/common/provider/web3/web3Provider";
import { InternalCustodialService } from "src/shared/custodial/internalCustodial.service";
import {
  DEFAULT_LIBRE_DEALER,
  INIT_SET_BATCH,
  TypeToDefaultValue,
} from "src/common/constants";
import { ApolloQueryResult } from "@apollo/client";
import { getInstrumentStAddressQuery } from "src/common/provider/thegraph/queries/instrument.qgl";
import { BankDetailsDTO } from "src/shared/dto/bank-details.dto";
import { BankDetailsSchema } from "./../shared/schemas/BankDetails.schema";
import { OnChainDataFieldService } from "src/shared/onchainDataFields/onchainDataField.service";
import { DataFieldType } from "src/admin/EternalRegistry.interface";
import type { ISetRegistryDataFields } from "src/profile/profile.interface";
import type { OnChainFieldDTO } from "src/shared/dto/on-chain-field.dto";
import {
  getUserWithUnSignedUrl,
  mergeCmsFields,
  orderGroupRegistryFields,
  uploadCmsFileFields,
} from "src/common/utils";
import { FundAdminService } from "./fundAdmin.service";

@Injectable()
export class UserService {
  @Inject(WalletService)
  private readonly walletService: WalletService;
  @Inject(InternalCustodialService)
  private readonly internalCustodialService: InternalCustodialService;
  @Inject(OnChainDataFieldService)
  private readonly onChainDataFieldService: OnChainDataFieldService;
  @Inject(FundAdminService)
  private readonly fundAdminService: FundAdminService;
  private readonly s3Service: S3Service;

  constructor(
    @InjectModel(User.name) private readonly userModel: Model<UserDocument>,
  ) {
    this.s3Service = new S3Service();
  }

  async create(createUser: UserEntity): Promise<UserDocument> {
    const userWithFiles = await this.userFilesUploader(createUser);

    const { subscribedFunds, ...userRest } = userWithFiles;

    const user = await this.userModel.create({
      ...userRest,
      subscribedFunds:
        userRest.endUserRole === "investor" &&
        userRest.onboardByEmail !== DEFAULT_LIBRE_DEALER
          ? []
          : subscribedFunds,
    });
    if (userRest.endUserRole === "investor") {
      await this.fundAdminService.create(
        subscribedFunds.map((fund) => {
          return {
            userId: user._id,
            fundId: fund._id,
          };
        }),
      );
    }

    return user;
  }

  async deleteByEmail(email: string): Promise<void> {
    const user = await this.userModel.findOne({ email }).lean().exec();
    if (user.endUserRole === EndUserRoles.investor) {
      await this.fundAdminService.deleteAccessRequestForUser(user._id);
    }
    await this.userModel.deleteOne({ email }).exec();
  }

  async findByIds(ids: string[]) {
    const users = await this.userModel
      .find({ _id: { $in: ids } })
      .populate("subscribedFunds")
      .lean()
      .exec();
    return users.map((user) => {
      return { ...user, ...user.bankDetails };
    });
  }

  async findUserByProperty(
    filterUserDto: FilterUserDto,
    hasSubscribedFundsPopulated?: boolean,
  ): Promise<LeanDocument<UserDocument>> {
    if (hasSubscribedFundsPopulated) {
      return this.userModel
        .findOne(filterUserDto)
        .populate("subscribedFunds")
        .lean()
        .exec();
    }
    return this.userModel.findOne(filterUserDto).lean().exec();
  }

  async findOne(
    id: string,
    hasOnChainFields?: boolean,
  ): Promise<UserWithOnChainFields | User> {
    const user = await this.userModel
      .findById(id)
      .populate({ path: "subscribedFunds", select: "title" })
      .lean()
      .exec();

    if (!user) {
      return null;
    }
    const userWitSignedUrl = await getUserWithUnSignedUrl(user);
    return hasOnChainFields
      ? (await this.getUsersOnChainFields([userWitSignedUrl]))[0]
      : userWitSignedUrl;
  }

  async findUsersByFilter(
    filterUserDto: FilterUserDto,
    includeOnChainField?: boolean,
  ) {
    try {
      const { limit, skip, orderDirection, ...restOfFilter } = filterUserDto;

      const arraysFilter = Object.keys(restOfFilter).filter((userFilter) =>
        Array.isArray(restOfFilter[userFilter]),
      );
      const arrayQuery = {};
      arraysFilter.forEach((arrayFilter) => {
        arrayQuery[arrayFilter] = {
          $in: restOfFilter[arrayFilter].map((filterValue) => {
            if (isValidObjectId(filterValue)) {
              return Types.ObjectId.createFromHexString(filterValue);
            }
            return filterValue;
          }),
        };
      });
      const users: LeanDocument<UserDocument>[] = await this.userModel
        .find(
          { ...restOfFilter, ...arrayQuery },
          restOfFilter.selectors ? restOfFilter.selectors.join(" ") : "",
        )
        .limit(limit)
        .skip(skip * limit)
        .sort({ created: orderDirection })
        .populate({ path: "subscribedFunds", select: "title fundType _id" })
        .lean()
        .exec();
      if (
        (restOfFilter.selectors &&
          !restOfFilter.selectors.includes("onChainFields")) ||
        !includeOnChainField
      ) {
        return users;
      }

      return await this.getUsersOnChainFields(users);
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }
  async getUsersOnChainFields(
    users: LeanDocument<UserDocument>[],
  ): Promise<UserWithOnChainFields[]> {
    const usersIdInBytes = users.map((user) =>
      encodeBytes32String(user._id.toString()),
    );
    const dataFieldsInfo: ApolloQueryResult<IGetGroupOfInvestorsRegistryFieldsQuery> =
      await apolloClient.query({
        query: getGroupOfInvestorsRegistryFieldsQuery,
        variables: {
          ids: usersIdInBytes,
          contractAddress: getContractByName["JurisdictionRegistry"].address,
        },
      });
    const hashes: string[] = [];
    const getFieldByHashAndUserId: Record<
      string,
      Record<string, IUserOnChainDataField>
    > = {};
    const isJurisdictionHash: Record<string, boolean> = {};
    dataFieldsInfo.data.userFields.forEach((field) => {
      if (!getFieldByHashAndUserId[field.fieldId]) {
        getFieldByHashAndUserId[field.fieldId] = {};
      }
      getFieldByHashAndUserId[field.fieldId][field.key] = {
        userId: field.fieldId,
        fieldName: "", // we didn't fetch it yet from the db
        fieldType: field.type,
        fieldValue: field.value,
      };
      hashes.push(field.key);
    });

    dataFieldsInfo.data.fieldsToLink.forEach((field) => {
      isJurisdictionHash[field.key] = true;
      hashes.push(field.key);
    });

    const onChainFields = await this.onChainDataFieldService.findByHashes(
      hashes,
    );
    onChainFields.forEach((field) => {
      Object.keys(getFieldByHashAndUserId).forEach((userId) => {
        if (getFieldByHashAndUserId[userId][field.hash]) {
          getFieldByHashAndUserId[userId][field.hash] = {
            ...getFieldByHashAndUserId[userId][field.hash],
            fieldName: field.name,
          };
        }
        if (getFieldByHashAndUserId[userId][field.linkedHash]) {
          getFieldByHashAndUserId[userId][field.linkedHash] = {
            ...getFieldByHashAndUserId[userId][field.linkedHash],
            fieldName: field.linkedName,
          };
        }
        // if key doesn't exist mean it is from the Jurisdiction values and we didn't set the value onchain before yet
        else {
          getFieldByHashAndUserId[userId][field.linkedHash] = {
            userId,
            fieldName: field.linkedName,
            fieldType: field.linkedType,
            fieldValue: TypeToDefaultValue[field.linkedType],
          };
        }
      });
    });
    return users.map((user) => {
      const userIdInBytes = encodeBytes32String(user._id.toString());
      const allFieldsKey = getFieldByHashAndUserId[userIdInBytes]
        ? Object.keys(getFieldByHashAndUserId[userIdInBytes])
        : [];
      const onChainFields: OnChainFieldDTO[] = allFieldsKey
        .filter(
          (key) => getFieldByHashAndUserId[userIdInBytes][key].fieldName.length,
        )
        .map((key) => {
          const { fieldName, fieldType, fieldValue } =
            getFieldByHashAndUserId[userIdInBytes][key];
          return {
            name: fieldName,
            type: fieldType as DataFieldType,
            value: fieldValue,
          };
        });

      return {
        ...user,
        onChainFields,
      };
    });
  }
  async findUsersByFilterByUsersBalance(
    filterUserDto: FilterUserDto,
    unconfirmedRedemptionAmountById: Record<string, string>,
  ) {
    try {
      const [users, getInstrumentStAddress]: [
        LeanDocument<UserDocument>[] | UserWithOnChainFields[],
        ApolloQueryResult<IGetInstrumentStAddressQuery>,
      ] = await Promise.all([
        this.findUsersByFilter(filterUserDto, true),
        apolloClient.query({
          query: getInstrumentStAddressQuery,
          variables: {
            instrumentId: encodeBytes32String(filterUserDto.instrumentId),
          },
        }),
      ]);
      return this.setUsersInstrumentBalance({
        users: users as UserDocument[],
        tokenAddress:
          getInstrumentStAddress.data.instruments[0].securityToken.id,
        unconfirmedRedemptionAmountById,
      });
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  async userSubscribeToNewFund(subscribeUserDto: ISubscribeUser) {
    try {
      const updateAction = subscribeUserDto.isSubscribed
        ? { $addToSet: { subscribedFunds: subscribeUserDto.fundId } }
        : { $pull: { subscribedFunds: subscribeUserDto.fundId } };

      const user = await this.userModel
        .findOne({
          _id: subscribeUserDto.userId,
        })
        .exec();

      if (!user) {
        throw new BadRequestException(
          `No user with id ${subscribeUserDto.userId}`,
        );
      }
      if (
        user.endUserRole === EndUserRoles.investor &&
        subscribeUserDto.isSubscribed
      ) {
        return await this.subscribeInvestorToNewFund(user, [
          subscribeUserDto.fundId,
        ]);
      }
      if (
        user.endUserRole === EndUserRoles.dealer &&
        subscribeUserDto.isSubscribed
      ) {
        if (!subscribeUserDto.instrumentId) {
          throw new BadRequestException(
            "For dealer you need to include the instrument id",
          );
        }
        await this.subscribeDealerToNewFund(user, subscribeUserDto);
      }
      const updatedUser = await this.userModel
        .findOneAndUpdate({ _id: subscribeUserDto.userId }, updateAction, {
          new: true,
        })
        .populate("subscribedFunds")
        .exec();

      return updatedUser;
    } catch (error) {
      console.error(error);
      if (error.status === 401) {
        throw new BadRequestException(error);
      }
      throw new ServiceUnavailableException(error);
    }
  }

  async setUsersInstrumentBalance({
    tokenAddress,
    users,
    unconfirmedRedemptionAmountById,
  }: ISetUsersInstrumentBalance) {
    return Promise.all(
      users.map(async (user) => {
        const balance = await transactionQuery({
          contractAddress: tokenAddress,
          queryName: QueryNames.balanceOf,
          contractName: ContractName.erc20,
          args: [user.wallets[0].address],
        });
        return {
          ...user,
          unconfirmedRedemption:
            unconfirmedRedemptionAmountById[user._id.toString()],
          balance: formatEther(balance),
        };
      }),
    );
  }

  async subscribeDealerToNewFund(
    dealer: UserDocument,
    subscribeUserDto: ISubscribeUser,
  ) {
    if (!subscribeUserDto.instrumentId) {
      throw new BadRequestException(
        "Dealer need an instrument Id to subscribe",
      );
    }
    try {
      const dealerId = encodeBytes32String(dealer.id);
      const { contractAddress, role } =
        await getRoleAndContractAddressByNameOrAddress({
          contractName: ContractName.DealerRegistry,
          transactionName: TransactionNames.allowDealer,
          userAddress: process.env.ADMIN_WALLET_ADDRESS,
        });
      const tx = await transactionSubmitter({
        signerKey: process.env.RELAYER_KEY,
        contractAddress,
        contractName: ContractName.DealerRegistry,
        transactionName: TransactionNames.allowDealer,
        args: [
          role,
          dealerId,
          encodeBytes32String(subscribeUserDto.instrumentId),
          subscribeUserDto.isSubscribed,
        ],
      });

      return tx;
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  async update({
    id,
    updateUserDto,
    userJwtPayload,
  }: IUpdateUser): Promise<UserDocument> {
    const cmsFields: ICmsField[] = [];
    if (updateUserDto.cmsFields) {
      cmsFields.push(...(await uploadCmsFileFields(updateUserDto.cmsFields)));
    }
    const bankDetails: Pick<
      InstanceType<typeof BankDetailsDTO>,
      keyof BankDetailsDTO
    > = Object.keys(updateUserDto)
      .filter((key) => BankDetailsSchema.requiredPaths().includes(key))
      .reduce((obj, key) => {
        return {
          ...obj,
          [`bankDetails.${key}`]: updateUserDto[key],
        };
      }, {} as Pick<InstanceType<typeof BankDetailsDTO>, keyof BankDetailsDTO>);

    if (updateUserDto.onChainFields) {
      await this.setRegistryData({
        registryName: RegistryName.InvestorRegistry,
        registries: orderGroupRegistryFields(
          updateUserDto.onChainFields.map((registry) => {
            return {
              id: encodeBytes32String(id),
              dataFieldType: registry.type,
              dataFieldName: registry.name,
              dataFieldValue: registry.value,
            };
          }),
        ),
        userJwtPayload,
        onchainFields: [],
      });
    }
    let legalDocuments;
    const user = await this.userModel.findById(id).exec();

    if (updateUserDto.legalDocuments) {
      legalDocuments = await this.s3Service.upload(
        updateUserDto.legalDocuments,
        `legalDocuments-${
          updateUserDto.legalDocuments.originalname.split(".")[0]
        }-${new Date().getTime()}-${user.endUserRole}.${
          updateUserDto.legalDocuments.originalname.split(".")[1]
        }`,
      );
    }

    const updateFilter = {
      ...updateUserDto,
      ...bankDetails,
      legalDocuments,
      cmsFields: mergeCmsFields(user.cmsFields, cmsFields),
    };

    return this.userModel.findByIdAndUpdate(id, updateFilter, { new: true });
  }

  async subscribeInvestorToNewFund(
    user: UserDocument | UserWithOnChainFields,
    subscribedFund: string[],
  ) {
    return await this.fundAdminService.create(
      subscribedFund.map((fund) => {
        return {
          fundId: Types.ObjectId.createFromHexString(
            fund,
          ) as unknown as ObjectId,
          userId: user._id.toString(),
        };
      }),
    );
  }

  async getFundSubscriptionDetails({
    userId,
    fundId,
  }: IGetFundSubscriptionDetails) {
    const investorSubscriptionInfo: ApolloQueryResult<IGetAllFundInstrumentsByFundIdAndInvestorIdQuery> =
      await apolloClient.query({
        query: getAllFundInstrumentsByFundIdAndInvestorIdQuery,
        variables: {
          userId: encodeBytes32String(userId),
          fundId: encodeBytes32String(fundId),
        },
      });
    if (investorSubscriptionInfo.errors) {
      console.error(investorSubscriptionInfo.errors[0]);
      throw new ServiceUnavailableException(investorSubscriptionInfo.errors);
    }
    const instrumentDetails: {
      instrumentId: string;
      instrumentName: string;
      isInvestorSubscribed: boolean;
    }[] = [];
    const isSubscribedToInstrument: Record<string, boolean> = {};
    investorSubscriptionInfo.data.whitelistInfo?.subscribedInstrument.forEach(
      (instrument) => {
        isSubscribedToInstrument[instrument.id] = true;
      },
    );

    investorSubscriptionInfo.data.instruments.forEach((instrument) => {
      instrumentDetails.push({
        instrumentId: decodeBytes32String(instrument.id),
        instrumentName: instrument.securityToken.name,
        isInvestorSubscribed: isSubscribedToInstrument[instrument.id] || false,
      });
    });
    return instrumentDetails;
  }

  async addNewWallet({ id, walletLabel }: IAddedWallet) {
    const newWallet = await this.walletService.create({
      walletLabel: walletLabel || "Wallet One",
    });
    this.userModel.findByIdAndUpdate(id, {
      $push: { wallets: newWallet },
    });
    return newWallet;
  }

  async userFilesUploader(user: UserEntity) {
    if (!user.legalDocuments) {
      return user;
    }
    const legalDocuments = await this.s3Service.upload(
      user.legalDocuments,
      `legalDocuments-${
        user.legalDocuments.originalname.split(".")[0]
      }-${new Date().getTime()}-${user.endUserRole}.${
        user.legalDocuments.originalname.split(".")[1]
      }`,
    );
    const cmsFields = await uploadCmsFileFields(user.cmsFields);
    const userWithUploadedFiles: User = {
      ...user,
      legalDocuments,
      endUserRole: user.endUserRole.toString(),
      cmsFields,
    };
    return userWithUploadedFiles;
  }

  async isDealerAllowed({ investorId, dealerEmail, fundId }: IDealerIsAllowed) {
    const [user, dealer] = await Promise.all([
      this.userModel.findById(investorId),
      this.findUserByProperty({ email: dealerEmail }),
    ]);
    if (!user) {
      return false;
    }
    let isSubscribedToTheFund = true;

    if (fundId) {
      isSubscribedToTheFund =
        dealer.subscribedFunds.find((fund) => fund.toString() === fundId) !==
        undefined;
    }
    return user.onboardByEmail === dealerEmail && isSubscribedToTheFund;
  }
  async checkIfUserIsAllowedToFund(userJwt: UserJwtPayload, fundId: string) {
    if (userJwt.role === "admin") {
      return true;
    }
    const user = await this.findUserByProperty({
      email: userJwt.email,
    });
    if (
      user.subscribedFunds.findIndex((fund) => {
        return fund.toString() === fundId;
      }) === -1
    ) {
      throw new UnauthorizedException(
        `${user.endUserRole} is not subscribed to this fund`,
      );
    }
  }
  async setRegistryData(setRegistryDataFields: ISetRegistryDataFields) {
    try {
      const batch = this.convertToBatch(setRegistryDataFields);

      let signerKey = process.env.RELAYER_KEY;
      let user: LeanDocument<UserDocument>;
      if (setRegistryDataFields.userJwtPayload.role !== "admin") {
        user = await this.findUserByProperty({
          email: setRegistryDataFields.userJwtPayload.email,
        });
        signerKey = (
          await this.internalCustodialService.findOne(user.wallets[0].address)
        ).privateKey;
      }
      const { contractAddress, role } =
        await getRoleAndContractAddressByNameOrAddress({
          contractName: ContractName.EternalRegistry,
          transactionName: TransactionNames.setBatch,
          contractAddress:
            getContractByName[setRegistryDataFields.registryName].address,
          userAddress:
            setRegistryDataFields.userJwtPayload.role !== "admin"
              ? user.wallets[0].address
              : process.env.ADMIN_WALLET_ADDRESS,
        });
      return await Promise.all([
        transactionSubmitter({
          signerKey,
          transactionName: TransactionNames.setBatch,
          contractName: ContractName.EternalRegistry,
          contractAddress,
          args: [
            batch._id,
            batch._keys,
            batch._boolValues.map((boolValue) => boolValue === "true"),
            batch._uintValues,
            batch._addressValues,
            batch._stringValues,
            batch._bytesValues,
            role,
          ],
        }),
        this.onChainDataFieldService.create(
          setRegistryDataFields.onchainFields,
        ),
      ]);
    } catch (error) {
      console.log(error);
      throw new ServiceUnavailableException(error);
    }
  }
  convertToBatch(setRegistryDataFields: ISetRegistryDataFields): IBatch {
    const batch = JSON.parse(JSON.stringify(INIT_SET_BATCH)); // to avoid shadow referencing the immutable object
    setRegistryDataFields.registries.forEach(
      async ({ id, dataFieldType, dataFieldName, dataFieldValue }) => {
        batch._id.push(id);
        batch._keys.push(keccak256(hexlify(toUtf8Bytes(dataFieldName))));
        if (dataFieldType === DataFieldType.Bool) {
          batch._boolValues.push(dataFieldValue);
        }
        if (dataFieldType === DataFieldType.Uint) {
          batch._uintValues.push(dataFieldValue);
        }
        if (dataFieldType === DataFieldType.Address) {
          batch._addressValues.push(dataFieldValue);
        }
        if (dataFieldType === DataFieldType.String) {
          batch._stringValues.push(dataFieldValue);
        }
        if (dataFieldType === DataFieldType.Bytes) {
          batch._bytesValues.push(dataFieldValue);
        }
      },
    );

    return batch;
  }
  async getCustodial(email: string, address?: string) {
    if (address) {
      return this.internalCustodialService.findOne(address);
    }

    const userByEmail = await this.findUserByProperty({ email });

    return this.internalCustodialService.findOne(
      userByEmail.wallets[0].address,
    );
  }

  async getInvestorBalanceByInstrument({
    instrumentId,
    investor,
  }: IGetInvestorBalanceByInstrument) {
    const whitelistInfo: ApolloQueryResult<IGetUserInstrumentBalanceQuery> =
      await apolloClient.query({
        query: getUserInstrumentBalanceQuery,
        variables: {
          instrumentId: encodeBytes32String(instrumentId),
          investorId: encodeBytes32String(investor._id.toString()),
        },
      });
    if (whitelistInfo.error) {
      new ServiceUnavailableException(whitelistInfo.error);
    }
    if (!whitelistInfo.data.instrument) {
      new BadRequestException("Incorrect instrument ID");
    }
    let shares = 0;
    await Promise.all(
      investor.wallets.map(async (wallet) => {
        const balance = await transactionQuery({
          contractAddress: whitelistInfo.data.instrument.securityToken.id,
          queryName: QueryNames.balanceOf,
          contractName: ContractName.erc20,
          args: [wallet.address],
        });
        shares += Number(formatEther(balance));
      }),
    );

    return {
      shares,
      unConfirmedRedeemedAmount: formatEther(
        whitelistInfo.data.whitelistInfo.unConfirmedRedeemedAmount,
      ),
      nav: whitelistInfo.data.instrument?.navs.length
        ? whitelistInfo.data.instrument.navs[0].navPerShare
        : 0,
      name: whitelistInfo.data.instrument.securityToken.name,
      symbol: whitelistInfo.data.instrument.securityToken.symbol,
    };
  }
}

results matching ""

    No results matching ""