src/security/fund.web3.service.ts
Properties |
|
Methods |
|
constructor(navModel: Model<NavDocument>)
|
||||||
|
Defined in src/security/fund.web3.service.ts:65
|
||||||
|
Parameters :
|
| Async addFund |
addFund(fundId: string, email: string)
|
|
Defined in src/security/fund.web3.service.ts:71
|
|
Returns :
unknown
|
| Async getAllFundClients | ||||||
getAllFundClients(fundId: string)
|
||||||
|
Defined in src/security/fund.web3.service.ts:95
|
||||||
|
Parameters :
Returns :
unknown
|
| Async getFundOrders | ||||||
getFundOrders(fundId: string)
|
||||||
|
Defined in src/security/fund.web3.service.ts:82
|
||||||
|
Parameters :
Returns :
unknown
|
| Async getInstrumentById | ||||||
getInstrumentById(instrumentId: string)
|
||||||
|
Defined in src/security/fund.web3.service.ts:185
|
||||||
|
Parameters :
Returns :
unknown
|
| Async getInstrumentsByFundId | ||||||||||||||||||||||||
getInstrumentsByFundId(fundId: string, limit?: number, skip?: number, orderDirection, user?: UserJwtPayload)
|
||||||||||||||||||||||||
|
Defined in src/security/fund.web3.service.ts:125
|
||||||||||||||||||||||||
|
Parameters :
Returns :
unknown
|
| Async getNavs | ||||||
getNavs(instrumentId: string)
|
||||||
|
Defined in src/security/fund.web3.service.ts:285
|
||||||
|
Parameters :
Returns :
unknown
|
| Async updateNav | |||
updateNav(undefined)
|
|||
|
Defined in src/security/fund.web3.service.ts:192
|
|||
|
Parameters :
Returns :
unknown
|
| Private Readonly adminProfileService |
Type : AdminProfileService
|
Decorators :
@Inject(AdminProfileService)
|
|
Defined in src/security/fund.web3.service.ts:62
|
| Private Readonly emailService |
Type : EmailService
|
Decorators :
@Inject(EmailService)
|
|
Defined in src/security/fund.web3.service.ts:65
|
| Private Readonly userService |
Type : UserService
|
Decorators :
@Inject(UserService)
|
|
Defined in src/security/fund.web3.service.ts:59
|
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 };
}
}