diff --git a/apps/solowallet/src/db/solowallet.schema.ts b/apps/solowallet/src/db/solowallet.schema.ts index ceebe31..e069742 100644 --- a/apps/solowallet/src/db/solowallet.schema.ts +++ b/apps/solowallet/src/db/solowallet.schema.ts @@ -22,9 +22,15 @@ export class SolowalletDocument extends AbstractDocument { @Prop({ type: String, required: true }) lightning: string; + @Prop({ type: String, required: true, unique: true }) + paymentTracker?: string; + @Prop({ type: String, required: true }) reference: string; } export const SolowalletSchema = SchemaFactory.createForClass(SolowalletDocument); + +// Ensure uniqueness only when paymentTracker is not null +SolowalletSchema.index({ paymentTracker: 1 }, { unique: true, sparse: true }); diff --git a/apps/solowallet/src/solowallet.service.ts b/apps/solowallet/src/solowallet.service.ts index 7a283a5..61a85e1 100644 --- a/apps/solowallet/src/solowallet.service.ts +++ b/apps/solowallet/src/solowallet.service.ts @@ -4,12 +4,13 @@ import { Currency, DepositFundsRequestDto, DepositFundsResponse, + DepositsMeta, fedimint_receive_failure, fedimint_receive_success, FedimintService, fiatToBtc, + FindUserDepositTxsResponse, FindUserTxsRequestDto, - FmInvoice, PaginatedSolowalletTxsResponse, QuoteDto, QuoteRequestDto, @@ -154,20 +155,8 @@ export class SolowalletService { const deposits = allDeposits .slice(selectPage * size, (selectPage + 1) * size + size) .map((deposit) => { - let lightning: FmInvoice; - try { - lightning = JSON.parse(deposit.lightning); - } catch (error) { - this.logger.warn('Error parsing lightning invoice', error); - lightning = { - invoice: '', - operationId: '', - }; - } - return { ...deposit, - lightning, id: deposit._id, status: deposit.status, createdAt: deposit.createdAt.toDateString(), @@ -183,6 +172,38 @@ export class SolowalletService { }; } + private async getDepositsMeta( + userId: string, + ): Promise { + let meta: DepositsMeta = undefined; + try { + meta = await this.wallet + .aggregate([ + { + $match: { + userId: userId, + status: TransactionStatus.COMPLETE, + }, + }, + { + $group: { + _id: '$userId', + totalMsats: { $sum: '$amountMsats' }, + avgMsats: { $avg: '$amountMsats' }, + count: { $sum: 1 }, + }, + }, + ]) + .then((result) => { + return result[0]; + }); + } catch (e) { + this.logger.error('Error getting deposits meta', e); + } + + return meta; + } + async depositFunds({ userId, amountFiat, @@ -219,30 +240,46 @@ export class SolowalletService { userId, amountMsats, amountFiat, - lightning: JSON.stringify(lightning), + lightning: lightning.invoice, + paymentTracker: lightning.operationId, status, reference, }); // listen for payment - this.fedimintService.receive(ReceiveContext.SOLOWALLET, deposit._id); + this.fedimintService.receive( + ReceiveContext.SOLOWALLET, + lightning.operationId, + ); const deposits = await this.getPaginatedUserDeposits({ userId, pagination: { page: 0, size: 10 }, }); + const meta = await this.getDepositsMeta(userId); + return { txId: deposit._id, deposits, + meta, }; } async findUserDeposits({ userId, pagination, - }: FindUserTxsRequestDto): Promise { - return this.getPaginatedUserDeposits({ userId, pagination }); + }: FindUserTxsRequestDto): Promise { + const deposits = await this.getPaginatedUserDeposits({ + userId, + pagination, + }); + const meta = await this.getDepositsMeta(userId); + + return { + deposits, + meta, + }; } @OnEvent(fedimint_receive_success) @@ -251,7 +288,7 @@ export class SolowalletService { operationId, }: ReceivePaymentSuccessEvent) { await this.wallet.findOneAndUpdate( - { _id: operationId }, + { paymentTracker: operationId }, { status: TransactionStatus.COMPLETE, }, @@ -268,11 +305,11 @@ export class SolowalletService { operationId, }: ReceivePaymentFailureEvent) { this.logger.log( - `Failed to eceive lightning payment for ${context} : ${operationId}`, + `Failed to receive lightning payment for ${context} : ${operationId}`, ); await this.wallet.findOneAndUpdate( - { _id: operationId }, + { paymentTracker: operationId }, { state: TransactionStatus.FAILED, }, diff --git a/apps/swap/src/swap.service.ts b/apps/swap/src/swap.service.ts index 04fd359..4969fda 100644 --- a/apps/swap/src/swap.service.ts +++ b/apps/swap/src/swap.service.ts @@ -463,7 +463,7 @@ export class SwapService { operationId, }: ReceivePaymentFailureEvent) { this.logger.log( - `Failed to eceive lightning payment for ${context} : ${operationId}`, + `Failed to receive lightning payment for ${context} : ${operationId}`, ); await this.offramp.findOneAndUpdate( diff --git a/libs/common/src/types/proto/solowallet.ts b/libs/common/src/types/proto/solowallet.ts index aa2eb33..94aaf42 100644 --- a/libs/common/src/types/proto/solowallet.ts +++ b/libs/common/src/types/proto/solowallet.ts @@ -8,7 +8,6 @@ import { GrpcMethod, GrpcStreamMethod } from '@nestjs/microservices'; import { Observable } from 'rxjs'; import { PaginatedRequest, TransactionStatus } from './lib'; -import { FmInvoice } from './lightning'; import { OnrampSwapSource } from './swap'; export interface DepositFundsRequest { @@ -21,6 +20,7 @@ export interface DepositFundsRequest { export interface DepositFundsResponse { txId: string; deposits: PaginatedSolowalletTxsResponse | undefined; + meta?: DepositsMeta | undefined; } export interface SolowalletTx { @@ -29,7 +29,7 @@ export interface SolowalletTx { status: TransactionStatus; amountMsats: number; amountFiat?: number | undefined; - lightning: FmInvoice | undefined; + lightning: string; reference: string; createdAt: string; updatedAt?: string | undefined; @@ -40,6 +40,11 @@ export interface FindUserTxsRequest { pagination: PaginatedRequest | undefined; } +export interface FindUserDepositTxsResponse { + deposits: PaginatedSolowalletTxsResponse | undefined; + meta?: DepositsMeta | undefined; +} + export interface PaginatedSolowalletTxsResponse { /** List of onramp swaps */ transactions: SolowalletTx[]; @@ -51,12 +56,18 @@ export interface PaginatedSolowalletTxsResponse { pages: number; } +export interface DepositsMeta { + totalMsats: number; + avgMsats: number; + count: number; +} + export interface SolowalletServiceClient { depositFunds(request: DepositFundsRequest): Observable; findUserDeposits( request: FindUserTxsRequest, - ): Observable; + ): Observable; } export interface SolowalletServiceController { @@ -70,9 +81,9 @@ export interface SolowalletServiceController { findUserDeposits( request: FindUserTxsRequest, ): - | Promise - | Observable - | PaginatedSolowalletTxsResponse; + | Promise + | Observable + | FindUserDepositTxsResponse; } export function SolowalletServiceControllerMethods() { diff --git a/proto/solowallet.proto b/proto/solowallet.proto index 54ad0e9..f6bffc1 100644 --- a/proto/solowallet.proto +++ b/proto/solowallet.proto @@ -9,7 +9,7 @@ package solowallet; service SolowalletService { rpc DepositFunds(DepositFundsRequest) returns (DepositFundsResponse){} - rpc FindUserDeposits(FindUserTxsRequest) returns (PaginatedSolowalletTxsResponse){} + rpc FindUserDeposits(FindUserTxsRequest) returns (FindUserDepositTxsResponse){} } message DepositFundsRequest { @@ -27,6 +27,7 @@ message DepositFundsRequest { message DepositFundsResponse { string tx_id = 1; PaginatedSolowalletTxsResponse deposits = 2; + optional DepositsMeta meta = 3; } message SolowalletTx { @@ -40,7 +41,7 @@ message SolowalletTx { optional int32 amount_fiat = 5; - lightning.Bolt11 lightning = 6; + string lightning = 6; reserved 7, 8, 9; @@ -56,6 +57,11 @@ message FindUserTxsRequest { lib.PaginatedRequest pagination = 2; } +message FindUserDepositTxsResponse { + PaginatedSolowalletTxsResponse deposits = 1; + optional DepositsMeta meta = 2; +} + message PaginatedSolowalletTxsResponse { // List of onramp swaps repeated SolowalletTx transactions = 1; @@ -66,3 +72,9 @@ message PaginatedSolowalletTxsResponse { // Number of pages given the current page size int32 pages = 4; } + +message DepositsMeta { + int32 total_msats = 1; + float avg_msats = 2; + int32 count = 3; +}