From 33a0d1d0914632b86519fe6c5c6318f6f363ac19 Mon Sep 17 00:00:00 2001 From: okjodom Date: Mon, 30 Dec 2024 15:33:55 +0300 Subject: [PATCH] feat: extensible bitsacco shares --- apps/api/src/shares/shares.controller.ts | 81 +++++++---- apps/api/src/shares/shares.service.ts | 30 ++-- apps/api/src/swap/swap.controller.ts | 2 +- apps/shares/src/db/shares.repository.ts | 14 +- apps/shares/src/db/shares.schema.ts | 54 ++++++- apps/shares/src/shares.controller.ts | 33 +++-- apps/shares/src/shares.module.ts | 17 ++- apps/shares/src/shares.service.ts | 176 ++++++++++++++++------- libs/common/src/dto/shares.dto.ts | 73 +++++++++- libs/common/src/types/proto/shares.ts | 165 ++++++++++++++++----- proto/shares.proto | 118 ++++++++++++--- 11 files changed, 603 insertions(+), 160 deletions(-) diff --git a/apps/api/src/shares/shares.controller.ts b/apps/api/src/shares/shares.controller.ts index 0eef14e..525510e 100644 --- a/apps/api/src/shares/shares.controller.ts +++ b/apps/api/src/shares/shares.controller.ts @@ -1,6 +1,18 @@ -import { BuySharesDto, GetShareDetailDto } from '@bitsacco/common'; -import { Body, Controller, Get, Logger, Post, Query } from '@nestjs/common'; -import { ApiOperation, ApiBody, ApiQuery } from '@nestjs/swagger'; +import { + OfferSharesDto, + SubscribeSharesDto, + TransferSharesDto, +} from '@bitsacco/common'; +import { + Body, + Controller, + Get, + Logger, + Param, + Post, + Query, +} from '@nestjs/common'; +import { ApiOperation, ApiBody, ApiQuery, ApiParam } from '@nestjs/swagger'; import { SharesService } from './shares.service'; @Controller('shares') @@ -11,32 +23,53 @@ export class SharesController { this.logger.log('SharesController initialized'); } - @Get('detail') - @ApiOperation({ summary: 'Get share details' }) - @ApiQuery({ name: 'user', type: String, required: true }) - getShareDetail(@Query('user') user: string) { - return this.sharesService.getShareDetail({ userId: user }); + @Post('offer') + @ApiOperation({ summary: 'Offer Bitsacco shares' }) + @ApiBody({ + type: OfferSharesDto, + }) + offerShares(@Body() req: OfferSharesDto) { + return this.sharesService.offerShares(req); + } + + @Get('offers') + @ApiOperation({ summary: 'List all share offers' }) + getShareOffers() { + return this.sharesService.getSharesOffers({}); + } + + @Post('subscribe') + @ApiOperation({ summary: 'Subscribe Bitsacco shares' }) + @ApiBody({ + type: SubscribeSharesDto, + }) + subscribeShares(@Body() req: SubscribeSharesDto) { + return this.sharesService.subscribeShares(req); } - @Post('buy') - @ApiOperation({ summary: 'Buy Bitsacco shares' }) + @Post('transfer') + @ApiOperation({ summary: 'Transfer Bitsacco shares' }) @ApiBody({ - type: BuySharesDto, + type: TransferSharesDto, }) - buyShares(@Body() req: BuySharesDto) { - return this.sharesService.buyShares(req); + transferShares(@Body() req: TransferSharesDto) { + return this.sharesService.transferShares(req); } - @Get('subscription') - @ApiOperation({ summary: 'Show Bitsacco share subscription levels' }) - getShareSubscription() { - return this.sharesService.getShareSubscription({}); + @Get('transactions') + @ApiOperation({ summary: 'List all Bitsacco share transactions' }) + allSharesTransactions() { + return this.sharesService.allSharesTransactions({}); } -} -// @ApiQuery({ name: 'currency', enum: SupportedCurrencies, required: true }) -// @ApiQuery({ name: 'amount', type: Number, required: false }) -// getOnrampQuote( -// @Query('currency') currency: SupportedCurrencyType, -// @Query('amount') amount?: number, -// ) { + @Get('transactions/:userId') + @ApiOperation({ + summary: 'List all Bitsacco share transactions for user with given ID', + }) + @ApiParam({ name: 'userId', type: 'string', description: 'User ID' }) + userSharesTransactions(@Param('userId') userId: string) { + return this.sharesService.userSharesTransactions({ + userId, + }); + } +} diff --git a/apps/api/src/shares/shares.service.ts b/apps/api/src/shares/shares.service.ts index 724b134..f217160 100644 --- a/apps/api/src/shares/shares.service.ts +++ b/apps/api/src/shares/shares.service.ts @@ -1,9 +1,11 @@ import { SharesServiceClient, SHARES_SERVICE_NAME, - BuySharesDto, Empty, - GetShareDetailDto, + OfferSharesDto, + SubscribeSharesDto, + TransferSharesDto, + UserSharesDto, } from '@bitsacco/common'; import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { type ClientGrpc } from '@nestjs/microservices'; @@ -19,15 +21,27 @@ export class SharesService implements OnModuleInit { this.grpc.getService(SHARES_SERVICE_NAME); } - getShareDetail(req: GetShareDetailDto) { - return this.client.getShareDetail(req); + offerShares(req: OfferSharesDto) { + return this.client.offerShares(req); } - buyShares(req: BuySharesDto) { - return this.client.buyShares(req); + getSharesOffers(req: Empty) { + return this.client.getSharesOffers(req); } - getShareSubscription(req: Empty) { - return this.client.getShareSubscription({}); + subscribeShares(req: SubscribeSharesDto) { + return this.client.subscribeShares(req); + } + + transferShares(req: TransferSharesDto) { + return this.client.transferShares(req); + } + + userSharesTransactions(req: UserSharesDto) { + return this.client.userSharesTransactions(req); + } + + allSharesTransactions(req: Empty) { + return this.client.allSharesTransactions(req); } } diff --git a/apps/api/src/swap/swap.controller.ts b/apps/api/src/swap/swap.controller.ts index b994562..e553d78 100644 --- a/apps/api/src/swap/swap.controller.ts +++ b/apps/api/src/swap/swap.controller.ts @@ -137,7 +137,7 @@ export class SwapController { } @Get('offramp/all') - @ApiOperation({ summary: 'List offramp swaps' }) + @ApiOperation({ summary: 'List all offramp swaps' }) @ApiQuery({ name: 'page', example: '0', diff --git a/apps/shares/src/db/shares.repository.ts b/apps/shares/src/db/shares.repository.ts index 4f229a8..c7f7daa 100644 --- a/apps/shares/src/db/shares.repository.ts +++ b/apps/shares/src/db/shares.repository.ts @@ -2,7 +2,19 @@ import { Model } from 'mongoose'; import { InjectModel } from '@nestjs/mongoose'; import { Injectable, Logger } from '@nestjs/common'; import { AbstractRepository } from '@bitsacco/common'; -import { SharesDocument } from './shares.schema'; +import { SharesDocument, SharesOfferDocument } from './shares.schema'; + +@Injectable() +export class SharesOfferRepository extends AbstractRepository { + protected readonly logger = new Logger(SharesOfferRepository.name); + + constructor( + @InjectModel(SharesOfferDocument.name) + reservationModel: Model, + ) { + super(reservationModel); + } +} @Injectable() export class SharesRepository extends AbstractRepository { diff --git a/apps/shares/src/db/shares.schema.ts b/apps/shares/src/db/shares.schema.ts index c791838..f74957e 100644 --- a/apps/shares/src/db/shares.schema.ts +++ b/apps/shares/src/db/shares.schema.ts @@ -1,13 +1,65 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { AbstractDocument } from '@bitsacco/common'; +import { + AbstractDocument, + SharesTx, + SharesTxStatus, + type SharesTxTransferMeta, +} from '@bitsacco/common'; + +@Schema({ versionKey: false }) +export class SharesOfferDocument extends AbstractDocument { + @Prop({ type: Number, required: true }) + quantity: number; + + @Prop({ type: Number, required: true, default: 0 }) + subscribedQuantity: number; + + @Prop({ type: Date, required: true, default: Date.now }) + availableFrom: Date; + + @Prop({ type: Date, required: false }) + availableTo?: Date; +} + +export const SharesOfferSchema = + SchemaFactory.createForClass(SharesOfferDocument); @Schema({ versionKey: false }) export class SharesDocument extends AbstractDocument { @Prop({ type: String, required: true }) userId: string; + @Prop({ type: String, required: true }) + offerId: string; + + @Prop({ + type: String, + required: true, + enum: Object.values(SharesTxStatus), + }) + status: SharesTxStatus; + + @Prop({ + type: Object, + required: false, + }) + transfer?: SharesTxTransferMeta; + @Prop({ type: Number, required: true }) quantity: number; } export const SharesSchema = SchemaFactory.createForClass(SharesDocument); + +export function toSharesTx(share: SharesDocument): SharesTx { + return { + id: share._id, + userId: share.userId, + offerId: share.offerId, + quantity: share.quantity, + status: share.status, + transfer: share.transfer, + createdAt: share.createdAt.toDateString(), + updatedAt: share.updatedAt.toDateString(), + }; +} diff --git a/apps/shares/src/shares.controller.ts b/apps/shares/src/shares.controller.ts index c270074..ed2f196 100644 --- a/apps/shares/src/shares.controller.ts +++ b/apps/shares/src/shares.controller.ts @@ -1,10 +1,12 @@ import { Controller } from '@nestjs/common'; import { GrpcMethod } from '@nestjs/microservices'; import { - BuySharesDto, type Empty, - GetShareDetailDto, + OfferSharesDto, SharesServiceControllerMethods, + SubscribeSharesDto, + TransferSharesDto, + UserTxsRequestDto, } from '@bitsacco/common'; import { SharesService } from './shares.service'; @@ -14,17 +16,32 @@ export class SharesController { constructor(private readonly sharesService: SharesService) {} @GrpcMethod() - getShareDetail(request: GetShareDetailDto) { - return this.sharesService.getShareDetail(request); + offerShares(request: OfferSharesDto) { + return this.sharesService.offerShares(request); } @GrpcMethod() - buyShares(request: BuySharesDto) { - return this.sharesService.buyShares(request); + getSharesOffers(_: Empty) { + return this.sharesService.getSharesOffers(); } @GrpcMethod() - getShareSubscription(request: Empty) { - return this.sharesService.getShareSubscrition(request); + subscribeShares(request: SubscribeSharesDto) { + return this.sharesService.subscribeShares(request); + } + + @GrpcMethod() + transferShares(request: TransferSharesDto) { + return this.sharesService.transferShares(request); + } + + @GrpcMethod() + userSharesTransactions(request: UserTxsRequestDto) { + return this.sharesService.userSharesTransactions(request); + } + + @GrpcMethod() + allSharesTransactions(_: Empty) { + return this.sharesService.allSharesTransactions(); } } diff --git a/apps/shares/src/shares.module.ts b/apps/shares/src/shares.module.ts index 767d244..54b5991 100644 --- a/apps/shares/src/shares.module.ts +++ b/apps/shares/src/shares.module.ts @@ -4,7 +4,14 @@ import { DatabaseModule, LoggerModule } from '@bitsacco/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { SharesController } from './shares.controller'; import { SharesService } from './shares.service'; -import { SharesDocument, SharesRepository, SharesSchema } from './db'; +import { + SharesDocument, + SharesOfferDocument, + SharesOfferRepository, + SharesOfferSchema, + SharesRepository, + SharesSchema, +} from './db'; @Module({ imports: [ @@ -19,11 +26,17 @@ import { SharesDocument, SharesRepository, SharesSchema } from './db'; }), DatabaseModule, DatabaseModule.forFeature([ + { name: SharesOfferDocument.name, schema: SharesOfferSchema }, { name: SharesDocument.name, schema: SharesSchema }, ]), LoggerModule, ], controllers: [SharesController], - providers: [SharesService, ConfigService, SharesRepository], + providers: [ + SharesService, + ConfigService, + SharesOfferRepository, + SharesRepository, + ], }) export class SharesModule {} diff --git a/apps/shares/src/shares.service.ts b/apps/shares/src/shares.service.ts index 745ce82..4954da0 100644 --- a/apps/shares/src/shares.service.ts +++ b/apps/shares/src/shares.service.ts @@ -1,88 +1,164 @@ import { Injectable, Logger } from '@nestjs/common'; import { - BuySharesDto, - Empty, - GetShareDetailDto, - ShareDetailResponse, - ShareSubscriptionResponse, + AllSharesOffers, + AllSharesTxsResponse, + OfferSharesDto, + SharesOffer, + SharesTxStatus, + SubscribeSharesDto, + TransferSharesDto, + UserSharesDto, + UserShareTxsResponse, } from '@bitsacco/common'; -import { SharesRepository } from './db'; -import { ConfigService } from '@nestjs/config'; +import { SharesOfferRepository, SharesRepository, toSharesTx } from './db'; @Injectable() export class SharesService { private readonly logger = new Logger(SharesService.name); constructor( + private readonly shareOffers: SharesOfferRepository, private readonly shares: SharesRepository, - private readonly configService: ConfigService, ) { this.logger.log('SharesService created'); } - async getShareDetail({ - userId, - }: GetShareDetailDto): Promise { - const allShares = await this.shares.find({ userId }, { createdAt: -1 }); - const shareHoldings = allShares.reduce( - (sum, share) => sum + share.quantity, - 0, - ); - const shares = allShares.map((share) => ({ - quantity: share.quantity, - createdAt: share.createdAt.toDateString(), - updatedAt: share.updatedAt.toDateString(), + async offerShares({ + quantity, + availableFrom, + availableTo, + }: OfferSharesDto): Promise { + await this.shareOffers.create({ + quantity, + subscribedQuantity: 0, + availableFrom: new Date(availableFrom), + availableTo: availableTo ? new Date(availableTo) : undefined, + }); + + return this.getSharesOffers(); + } + + async getSharesOffers(): Promise { + const offers: SharesOffer[] = ( + await this.shareOffers.find({}, { createdAt: -1 }) + ).map((offer) => ({ + id: offer._id, + quantity: offer.quantity, + subscribedQuantity: offer.subscribedQuantity, + availableFrom: offer.availableFrom.toDateString(), + availableTo: offer.availableTo.toDateString(), + createdAt: offer.createdAt.toDateString(), + updatedAt: offer.updatedAt.toDateString(), })); - const shareSubscription = await this.getShareSubscrition({}); + const totalOfferQuantity = offers.reduce( + (sum, offer) => sum + offer.quantity, + 0, + ); + const totalSubscribedQuantity = offers.reduce( + (sum, offer) => sum + offer.subscribedQuantity, + 0, + ); - const res: ShareDetailResponse = { - userId, - shareHoldings, - shares, - shareSubscription, + const res: AllSharesOffers = { + offers, + totalOfferQuantity, + totalSubscribedQuantity, }; return Promise.resolve(res); } - async buyShares({ + async subscribeShares({ userId, + offerId, quantity, - }: BuySharesDto): Promise { - this.logger.debug(`Buying ${quantity} Bitsacco shares for ${userId}`); + }: SubscribeSharesDto): Promise { + this.logger.debug(`Subscribing ${quantity} Bitsacco shares for ${userId}`); await this.shares.create({ userId, + offerId, quantity, + status: SharesTxStatus.PROPOSED, }); - return this.getShareDetail({ userId }); + return this.userSharesTransactions({ userId }); } - async getShareSubscrition(_: Empty): Promise { - let sharesIssued = this.configService.get('SHARES_ISSUED') || 0; - let sharesSold = 0; - - try { - sharesSold = await this.shares - .aggregate([ - { - $group: { - _id: null, - totalShares: { $sum: { $sum: '$quantity' } }, - }, - }, - ]) - .then((result) => result[0]?.totalShares || 0); - } catch (e) { - this.logger.log('Failed to aggregate shares sold'); - sharesSold = sharesIssued; + async transferShares({ + sharesId, + ...transfer + }: TransferSharesDto): Promise { + // const originShares = await this.userShareTransactions({ + // userId: fromUserId, + // }); + + const originShares = await this.shares.findOne({ _id: sharesId }); + + if (originShares.status !== SharesTxStatus.COMPLETE) { + throw new Error('Shares are not available to transfer'); + } + + if (originShares.quantity < transfer.quantity) { + throw new Error('Not enough shares to transfer'); } + await this.shares.findOneAndUpdate( + { _id: sharesId }, + { + $set: { + quantity: originShares.quantity - transfer.quantity, + updatedAt: Date.now(), + transfer, + }, + }, + ); + + // Assign shares to the recipient + await this.shares.create({ + userId: transfer.toUserId, + offerId: originShares.offerId, + quantity: transfer.quantity, + transfer, + status: SharesTxStatus.COMPLETE, + }); + + return this.userSharesTransactions({ userId: transfer.fromUserId }); + } + + async userSharesTransactions({ + userId, + }: UserSharesDto): Promise { + const shares = (await this.shares.find({ userId }, { createdAt: -1 })).map( + toSharesTx, + ); + + const shareHoldings = shares.reduce( + (sum, share) => sum + share.quantity, + 0, + ); + + const offers = await this.getSharesOffers(); + return { - sharesIssued, - sharesSold, + userId, + shareHoldings, + shares, + offers, + }; + } + + async allSharesTransactions(): Promise { + const shares = (await this.shares.find({}, { createdAt: -1 })).map( + toSharesTx, + ); + + const offers = await this.getSharesOffers(); + + return { + shares, + offers, }; } } diff --git a/libs/common/src/dto/shares.dto.ts b/libs/common/src/dto/shares.dto.ts index 34db900..279c8cd 100644 --- a/libs/common/src/dto/shares.dto.ts +++ b/libs/common/src/dto/shares.dto.ts @@ -1,22 +1,77 @@ import { Type } from 'class-transformer'; -import { IsString, IsNotEmpty, IsPositive, IsNumber } from 'class-validator'; -import { BuySharesRequest, GetShareDetailRequest } from '@bitsacco/common'; +import { + IsString, + IsNotEmpty, + IsPositive, + IsNumber, + IsDateString, +} from 'class-validator'; +import { + OfferSharesRequest, + SubscribeSharesRequest, + TransferSharesRequest, + UserSharesTxsRequest, +} from '@bitsacco/common'; import { ApiProperty } from '@nestjs/swagger'; -export class GetShareDetailDto implements GetShareDetailRequest { +export class OfferSharesDto implements OfferSharesRequest { + @IsPositive() + @IsNumber() + @Type(() => Number) + @ApiProperty({ description: 'Amount of shares to issue', example: 1000 }) + quantity: number; + + @IsNotEmpty() + @IsDateString() + @Type(() => String) + @ApiProperty({ description: 'Start date of availability (ISO 8601 format)', example: '2024-12-30T12:19:04.077Z' }) + availableFrom: string; + + @IsNotEmpty() + @IsDateString() + @Type(() => String) + @ApiProperty({ description: 'End date of availability (ISO 8601 format)', example: '2025-12-30T12:19:04.077Z' }) + availableTo?: string; +} + +export class SubscribeSharesDto implements SubscribeSharesRequest { @IsNotEmpty() @IsString() @Type(() => String) @ApiProperty({ example: '7b158dfd-cb98-40b1-9ed2-a13006a9f670' }) userId: string; + + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '3e158dfd-cb98-40b1-9ed2-a13006a9f430' }) + offerId: string; + + @IsPositive() + @IsNumber() + @Type(() => Number) + @ApiProperty() + quantity: number; } -export class BuySharesDto implements BuySharesRequest { +export class TransferSharesDto implements TransferSharesRequest { @IsNotEmpty() @IsString() @Type(() => String) @ApiProperty({ example: '7b158dfd-cb98-40b1-9ed2-a13006a9f670' }) - userId: string; + fromUserId: string; + + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '8b158dfd-cb98-40b1-9ed2-a13006a9f671' }) + toUserId: string; + + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '3e158dfd-cb98-40b1-9ed2-a13006a9f430' }) + sharesId: string; @IsPositive() @IsNumber() @@ -24,3 +79,11 @@ export class BuySharesDto implements BuySharesRequest { @ApiProperty() quantity: number; } + +export class UserSharesDto implements UserSharesTxsRequest { + @IsNotEmpty() + @IsString() + @Type(() => String) + @ApiProperty({ example: '7b158dfd-cb98-40b1-9ed2-a13006a9f670' }) + userId: string; +} diff --git a/libs/common/src/types/proto/shares.ts b/libs/common/src/types/proto/shares.ts index 128ffd5..0023537 100644 --- a/libs/common/src/types/proto/shares.ts +++ b/libs/common/src/types/proto/shares.ts @@ -9,76 +9,163 @@ import { GrpcMethod, GrpcStreamMethod } from '@nestjs/microservices'; import { Observable } from 'rxjs'; import { Empty } from './lib'; -export interface GetShareDetailRequest { - userId: string; +export enum SharesTxStatus { + PROPOSED = 0, + PROCESSING = 1, + APPROVED = 2, + COMPLETE = 3, + FAILED = 4, + UNRECOGNIZED = -1, } -export interface BuySharesRequest { - userId: string; +export interface OfferSharesRequest { + /** Number of shares to issue */ quantity: number; + /** Date from which the shares will be available for subscription */ + availableFrom: string; + /** + * Date until which the shares will be available for subscription + * Shares can be sold out before this availability date lapses + */ + availableTo?: string | undefined; } -export interface ShareDetailResponse { - userId: string; - shareHoldings: number; - shares: ShareDetails[]; - shareSubscription: ShareSubscriptionResponse | undefined; +export interface SharesOffer { + id: string; + /** Number of shares issued */ + quantity: number; + /** Number of shares subscribed by members */ + subscribedQuantity: number; + /** Date from which the shares will be available for subscription */ + availableFrom: string; + /** + * Date until which the shares will be available for subscription + * Shares can be sold out before this availability date lapses + */ + availableTo?: string | undefined; + createdAt: string; + updatedAt?: string | undefined; +} + +export interface AllSharesOffers { + offers: SharesOffer[]; + totalOfferQuantity: number; + totalSubscribedQuantity: number; } -export interface ShareDetails { - /** Number of shared purchased */ +export interface SharesTx { + id: string; + userId: string; + offerId: string; quantity: number; - /** Unix timestamp for when the shares were purchased */ + status: SharesTxStatus; + transfer?: SharesTxTransferMeta | undefined; createdAt: string; updatedAt?: string | undefined; } -export interface ShareSubscriptionResponse { - /** Total shares issued */ - sharesIssued: number; - /** Total shares subscribed */ - sharesSold: number; +export interface SharesTxTransferMeta { + fromUserId: string; + toUserId: string; + quantity: number; +} + +export interface SubscribeSharesRequest { + userId: string; + offerId: string; + quantity: number; +} + +export interface TransferSharesRequest { + fromUserId: string; + toUserId: string; + sharesId: string; + quantity: number; +} + +export interface UserSharesTxsRequest { + userId: string; +} + +export interface UserShareTxsResponse { + userId: string; + shareHoldings: number; + shares: SharesTx[]; + offers: AllSharesOffers | undefined; +} + +export interface AllSharesTxsResponse { + shares: SharesTx[]; + offers: AllSharesOffers | undefined; } export interface SharesServiceClient { - getShareDetail( - request: GetShareDetailRequest, - ): Observable; + offerShares(request: OfferSharesRequest): Observable; - buyShares(request: BuySharesRequest): Observable; + getSharesOffers(request: Empty): Observable; - getShareSubscription(request: Empty): Observable; + subscribeShares( + request: SubscribeSharesRequest, + ): Observable; + + transferShares( + request: TransferSharesRequest, + ): Observable; + + userSharesTransactions( + request: UserSharesTxsRequest, + ): Observable; + + allSharesTransactions(request: Empty): Observable; } export interface SharesServiceController { - getShareDetail( - request: GetShareDetailRequest, + offerShares( + request: OfferSharesRequest, + ): Promise | Observable | AllSharesOffers; + + getSharesOffers( + request: Empty, + ): Promise | Observable | AllSharesOffers; + + subscribeShares( + request: SubscribeSharesRequest, + ): + | Promise + | Observable + | UserShareTxsResponse; + + transferShares( + request: TransferSharesRequest, ): - | Promise - | Observable - | ShareDetailResponse; + | Promise + | Observable + | UserShareTxsResponse; - buyShares( - request: BuySharesRequest, + userSharesTransactions( + request: UserSharesTxsRequest, ): - | Promise - | Observable - | ShareDetailResponse; + | Promise + | Observable + | UserShareTxsResponse; - getShareSubscription( + allSharesTransactions( request: Empty, ): - | Promise - | Observable - | ShareSubscriptionResponse; + | Promise + | Observable + | AllSharesTxsResponse; } export function SharesServiceControllerMethods() { return function (constructor: Function) { const grpcMethods: string[] = [ - 'getShareDetail', - 'buyShares', - 'getShareSubscription', + 'offerShares', + 'getSharesOffers', + 'subscribeShares', + 'transferShares', + 'userSharesTransactions', + 'allSharesTransactions', ]; for (const method of grpcMethods) { const descriptor: any = Reflect.getOwnPropertyDescriptor( diff --git a/proto/shares.proto b/proto/shares.proto index 0c69cac..814a14c 100644 --- a/proto/shares.proto +++ b/proto/shares.proto @@ -5,38 +5,114 @@ import "lib.proto"; package shares; service SharesService { - rpc GetShareDetail(GetShareDetailRequest) returns (ShareDetailResponse); - rpc BuyShares(BuySharesRequest) returns (ShareDetailResponse); - rpc GetShareSubscription(lib.Empty) returns (ShareSubscriptionResponse); + rpc OfferShares(OfferSharesRequest) returns (AllSharesOffers); + rpc GetSharesOffers(lib.Empty) returns (AllSharesOffers); + rpc SubscribeShares(SubscribeSharesRequest) returns (UserShareTxsResponse); + rpc TransferShares(TransferSharesRequest) returns (UserShareTxsResponse); + rpc UserSharesTransactions(UserSharesTxsRequest) returns (UserShareTxsResponse); + rpc AllSharesTransactions(lib.Empty) returns (AllSharesTxsResponse); } -message GetShareDetailRequest { - string user_id = 1; +message OfferSharesRequest { + // Number of shares to issue + int32 quantity = 1; + + // Date from which the shares will be available for subscription + string available_from = 2; + + // Date until which the shares will be available for subscription + // Shares can be sold out before this availability date lapses + optional string available_to = 3; +} + +message SharesOffer { + string id = 1; + + // Number of shares issued + int32 quantity = 2; + + // Number of shares subscribed by members + int32 subscribed_quantity = 3; + + // Date from which the shares will be available for subscription + string available_from = 4; + + // Date until which the shares will be available for subscription + // Shares can be sold out before this availability date lapses + optional string available_to = 5; + + reserved 6, 7, 8, 9, 10; + + string created_at = 11; + + optional string updated_at = 12; +} + +message AllSharesOffers { + repeated SharesOffer offers = 1; + int32 total_offer_quantity = 2; + int32 total_subscribed_quantity = 3; +} + +message SharesTx { + string id = 1; + + string user_id = 2; + + string offer_id = 3; + + int32 quantity = 4; + + SharesTxStatus status = 5; + + optional SharesTxTransferMeta transfer = 6; + + reserved 7, 8, 9, 10; + + string created_at = 11; + + optional string updated_at = 12; +} + +enum SharesTxStatus { + PROPOSED = 0; + PROCESSING = 1; + APPROVED = 2; + COMPLETE = 3; + FAILED = 4; } -message BuySharesRequest { +message SharesTxTransferMeta { + string from_user_id = 1; + string to_user_id = 2; + int32 quantity = 3; +} + +message SubscribeSharesRequest { string user_id = 1; + string offer_id = 2; int32 quantity = 3; } -message ShareDetailResponse { +message TransferSharesRequest { + string from_user_id = 1; + string to_user_id = 2; + string shares_id = 3; + int32 quantity = 4; +} + +message UserSharesTxsRequest { string user_id = 1; - int32 share_holdings = 2; - repeated ShareDetails shares = 3; - ShareSubscriptionResponse share_subscription = 4; } -message ShareDetails { - // Number of shared purchased - int32 quantity = 1; - // Unix timestamp for when the shares were purchased - string createdAt = 11; - optional string updatedAt = 12; +message UserShareTxsResponse { + string user_id = 1; + int32 share_holdings = 2; + repeated SharesTx shares = 3; + AllSharesOffers offers = 4; } -message ShareSubscriptionResponse { - // Total shares issued - int32 shares_issued = 1; - // Total shares subscribed - int32 shares_sold = 2; +message AllSharesTxsResponse { + repeated SharesTx shares = 1; + AllSharesOffers offers = 2; }