src/orderbook/orderbook.service.ts
Properties |
|
Methods |
|
| Async addFundNameToOrder | |||||
addFundNameToOrder(undefined: literal type)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:1370
|
|||||
|
Parameters :
Returns :
Promise<IOrderWithFundName[]>
|
| Async adminLockOrder | |||||
adminLockOrder(undefined: ILockOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:706
|
|||||
|
Parameters :
Returns :
unknown
|
| Async cancelOrder | |||||
cancelOrder(undefined: ICancelOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:1037
|
|||||
|
Parameters :
Returns :
unknown
|
| Async dealerConfirmOrder | |||||
dealerConfirmOrder(undefined: IConfirmOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:450
|
|||||
|
Parameters :
Returns :
unknown
|
| Async dealerCreateOrder | |||||
dealerCreateOrder(undefined: ICreateOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:361
|
|||||
|
Parameters :
Returns :
unknown
|
| Async dealerLockRedemptionOrder | |||||
dealerLockRedemptionOrder(undefined: ILockOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:638
|
|||||
|
Parameters :
Returns :
unknown
|
| Async dealerLockSubscriptionOrder | |||||
dealerLockSubscriptionOrder(undefined: ILockOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:548
|
|||||
|
Parameters :
Returns :
unknown
|
| Async executeDealerConfirmOrder | |||||||||||||||||||||
executeDealerConfirmOrder(orderDto: OrderConfirmDto, type: OrderType, orderSubgraph: ApolloQueryResult<IGetClosestOrdersToLockQuery>, order: Partial<IOrder>, dealer: LeanDocument<UserDocument>, dealerCustodial: CustodialDocument)
|
|||||||||||||||||||||
|
Defined in src/orderbook/orderbook.service.ts:489
|
|||||||||||||||||||||
|
Parameters :
Returns :
unknown
|
| Async fillRedemptionBook | ||||||
fillRedemptionBook(fillRedemptionBookDto: FillRedemptionBookDto)
|
||||||
|
Defined in src/orderbook/orderbook.service.ts:1122
|
||||||
|
Parameters :
Returns :
unknown
|
| Async forceRedemption | |||||
forceRedemption(undefined: IForceRedemptionOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:1007
|
|||||
|
Parameters :
Returns :
unknown
|
| Async getAllOrders | |||||||||
getAllOrders(request: AuthRequest, filterOrderDto?: FilterOrderDto)
|
|||||||||
|
Defined in src/orderbook/orderbook.service.ts:1148
|
|||||||||
|
Parameters :
Returns :
Promise<literal type>
|
| Async getMyOrders | |||||
getMyOrders(undefined: IGetMyOrders)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:1205
|
|||||
|
Parameters :
Returns :
Promise<literal type>
|
| Async getOrderById | |||||
getOrderById(undefined: IGetOrderById)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:1425
|
|||||
|
Parameters :
Returns :
Promise<OrderDetailsInfo>
|
| Async investorConfirmOrder | |||||
investorConfirmOrder(undefined: IConfirmOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:183
|
|||||
|
Parameters :
Returns :
unknown
|
| Async investorCreateOrder | |||||
investorCreateOrder(undefined: ICreateOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:136
|
|||||
|
Parameters :
Returns :
unknown
|
| Async investorLockRedemptionOrder | |||||
investorLockRedemptionOrder(undefined: ILockOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:304
|
|||||
|
Parameters :
Returns :
unknown
|
| Async investorLockSubscriptionOrder | |||||
investorLockSubscriptionOrder(undefined: ILockOrder)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:228
|
|||||
|
Parameters :
Returns :
unknown
|
| isAllowedToCheckOrder | ||||||||||||
isAllowedToCheckOrder(order: IOrder, userIdInBytes: string, role: EndUserRoles | "admin")
|
||||||||||||
|
Defined in src/orderbook/orderbook.service.ts:1191
|
||||||||||||
|
Parameters :
Returns :
boolean
|
| Async reBalanceSettlement | |||
reBalanceSettlement(undefined)
|
|||
|
Defined in src/orderbook/orderbook.service.ts:975
|
|||
|
Parameters :
Returns :
unknown
|
| Async sendSettlementEmails | |||||||||||||||
sendSettlementEmails(settledGraph: ApolloQueryResult<IGetLastSettledOrderIdQuery>, tx: TransactionResponse, fund: LeanDocument<FundDocument>, type: OrderType)
|
|||||||||||||||
|
Defined in src/orderbook/orderbook.service.ts:864
|
|||||||||||||||
|
Parameters :
Returns :
any
|
| Async settleOrder | |||||
settleOrder(undefined: ISettleOrders)
|
|||||
|
Defined in src/orderbook/orderbook.service.ts:784
|
|||||
|
Parameters :
Returns :
unknown
|
| Private Readonly adminProfileService |
Type : AdminProfileService
|
Decorators :
@Inject(AdminProfileService)
|
|
Defined in src/orderbook/orderbook.service.ts:134
|
| Private Readonly cacheService |
Type : Cache
|
Decorators :
@Inject(CACHE_MANAGER)
|
|
Defined in src/orderbook/orderbook.service.ts:128
|
| Private Readonly docuSignService |
Type : DocuSignService
|
Decorators :
@Inject(DocuSignService)
|
|
Defined in src/orderbook/orderbook.service.ts:122
|
| Private Readonly emailService |
Type : EmailService
|
Decorators :
@Inject(EmailService)
|
|
Defined in src/orderbook/orderbook.service.ts:125
|
| Private Readonly fundService |
Type : FundService
|
Decorators :
@Inject(FundService)
|
|
Defined in src/orderbook/orderbook.service.ts:116
|
| Private Readonly instrumentService |
Type : InstrumentService
|
Decorators :
@Inject(InstrumentService)
|
|
Defined in src/orderbook/orderbook.service.ts:119
|
| Private Readonly internalCustodialService |
Type : InternalCustodialService
|
Decorators :
@Inject(InternalCustodialService)
|
|
Defined in src/orderbook/orderbook.service.ts:113
|
| Private Readonly settlementService |
Type : SettlementService
|
Decorators :
@Inject(SettlementService)
|
|
Defined in src/orderbook/orderbook.service.ts:131
|
| Private Readonly userService |
Type : UserService
|
Decorators :
@Inject(UserService)
|
|
Defined in src/orderbook/orderbook.service.ts:110
|
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,
};
}
}