Skip to content

Commit

Permalink
refactor: solowallet deposit to ln
Browse files Browse the repository at this point in the history
  • Loading branch information
okjodom committed Dec 1, 2024
1 parent bcaf99f commit ec1503c
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 36 deletions.
12 changes: 6 additions & 6 deletions apps/api/src/swap/swap.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ClientGrpc } from '@nestjs/microservices';
import {
Currency,
fiatToBtc,
type QuoteRequest,
QuoteRequestDto,
type QuoteResponse,
SWAP_SERVICE_NAME,
SwapServiceClient,
Expand All @@ -27,7 +27,7 @@ describe('SwapService', () => {
getQuote: jest
.fn()
.mockImplementation(
({ from, to, amount }: QuoteRequest): QuoteResponse => {
({ from, to, amount }: QuoteRequestDto): QuoteResponse => {
return {
id: mock_id,
from,
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('SwapService', () => {
},
},
target: {
invoice: {
payout: {
invoice: 'lnbc1000u1p0j7j0pp5',
},
},
Expand All @@ -176,7 +176,7 @@ describe('SwapService', () => {
},
},
target: {
invoice: {
payout: {
invoice: 'lnbc1000u1p0j7j0pp5',
},
},
Expand Down Expand Up @@ -233,7 +233,7 @@ describe('SwapService', () => {
amountFiat: '100',
target: {
currency: Currency.KES,
destination: {
payout: {
phone: '0700000000',
},
},
Expand All @@ -248,7 +248,7 @@ describe('SwapService', () => {
amountFiat: '100',
target: {
currency: Currency.KES,
destination: {
payout: {
phone: '0700000000',
},
},
Expand Down
4 changes: 4 additions & 0 deletions apps/solowallet/.env.manual
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ NODE_ENV='development'
SOLOWALLET_GRPC_URL='0.0.0.0:4080'
SWAP_GRPC_URL='0.0.0.0:4040'
DATABASE_URL=mongodb://bs:[email protected]:27017
FEDIMINT_CLIENTD_BASE_URL=http://swap-clientd:7070
FEDIMINT_CLIENTD_PASSWORD=fmcdpass
FEDIMINT_FEDERATION_ID=596ab5a5456376f925d145a2ef038cc5a70b99be7d2d0eb30feca5e849ecd351
FEDIMINT_GATEWAY_ID=03187672262e17300f4822bb64c18ef74a266ebe1780631543b188537f9cf4a904
3 changes: 3 additions & 0 deletions apps/solowallet/src/db/solowallet.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class SolowalletDocument extends AbstractDocument {
})
status: TransactionStatus;

@Prop({ type: String, required: true })
lightning: string;

@Prop({ type: String, required: true })
reference: string;
}
Expand Down
20 changes: 19 additions & 1 deletion apps/solowallet/src/solowallet.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import {
DatabaseModule,
FedimintService,
LoggerModule,
SWAP_SERVICE_NAME,
} from '@bitsacco/common';
Expand All @@ -15,6 +16,8 @@ import {
SolowalletRepository,
SolowalletSchema,
} from './db';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { HttpModule } from '@nestjs/axios';

@Module({
imports: [
Expand All @@ -25,13 +28,18 @@ import {
SOLOWALLET_GRPC_URL: Joi.string().required(),
SWAP_GRPC_URL: Joi.string().required(),
DATABASE_URL: Joi.string().required(),
FEDIMINT_CLIENTD_BASE_URL: Joi.string().required(),
FEDIMINT_CLIENTD_PASSWORD: Joi.string().required(),
FEDIMINT_FEDERATION_ID: Joi.string().required(),
FEDIMINT_GATEWAY_ID: Joi.string().required(),
}),
}),
DatabaseModule,
DatabaseModule.forFeature([
{ name: SolowalletDocument.name, schema: SolowalletSchema },
]),
LoggerModule,
HttpModule,
ClientsModule.registerAsync([
{
name: SWAP_SERVICE_NAME,
Expand All @@ -46,8 +54,18 @@ import {
inject: [ConfigService],
},
]),
EventEmitterModule.forRoot({
global: true,
delimiter: '.',
verboseMemoryLeak: true,
}),
],
controllers: [SolowalletController],
providers: [SolowalletService, ConfigService, SolowalletRepository],
providers: [
SolowalletService,
ConfigService,
SolowalletRepository,
FedimintService,
],
})
export class SolowalletModule {}
107 changes: 94 additions & 13 deletions apps/solowallet/src/solowallet.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import {
CreateOnrampSwapDto,
Currency,
DepositFundsRequestDto,
DepositFundsResponse,
FedimintService,
fiatToBtc,
FindUserTxsRequestDto,
FmInvoice,
PaginatedSolowalletTxsResponse,
SolowalletTx,
QuoteDto,
QuoteRequestDto,
QuoteResponse,
SWAP_SERVICE_NAME,
SwapResponse,
SwapServiceClient,
Expand All @@ -23,13 +28,56 @@ export class SolowalletService {

constructor(
private readonly wallet: SolowalletRepository,
private readonly fedimintService: FedimintService,
@Inject(SWAP_SERVICE_NAME) private readonly swapGrpc: ClientGrpc,
) {
this.logger.log('SolowalletService created');
this.swapService =
this.swapGrpc.getService<SwapServiceClient>(SWAP_SERVICE_NAME);
}

private async getQuote({ from, to, amount }: QuoteRequestDto): Promise<{
quote: QuoteDto | null;
amountMsats: number;
}> {
return firstValueFrom(
this.swapService
.getQuote({
from,
to,
amount,
})
.pipe(
tap((quote: QuoteResponse) => {
this.logger.log(`Quote: ${quote}`);
}),
map((quote: QuoteResponse) => {
const { amountMsats } = fiatToBtc({
amountFiat: Number(amount),
btcToFiatRate: Number(quote.rate),
});

return {
quote: {
id: quote.id,
refreshIfExpired: true,
},
amountMsats,
};
}),
)
.pipe(
catchError((error) => {
this.logger.error('Error geeting quote:', error);
return of({
amountMsats: 0,
quote: null,
});
}),
),
);
}

private async initiateSwap(fiatDeposit: CreateOnrampSwapDto): Promise<{
status: TransactionStatus;
amountMsats: number;
Expand Down Expand Up @@ -88,12 +136,26 @@ export class SolowalletService {

const deposits = allDeposits
.slice(selectPage * size, (selectPage + 1) * size + size)
.map((deposit) => ({
...deposit,
id: deposit._id,
createdAt: deposit.createdAt.toDateString(),
updatedAt: deposit.updatedAt.toDateString(),
}));
.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,
createdAt: deposit.createdAt.toDateString(),
updatedAt: deposit.updatedAt.toDateString(),
};
});

return {
transactions: deposits,
Expand All @@ -105,22 +167,41 @@ export class SolowalletService {

async depositFunds({
userId,
fiatDeposit,
amountFiat,
reference,
onramp,
}: DepositFundsRequestDto): Promise<DepositFundsResponse> {
const { status, reference, amountMsats, amountFiat } = fiatDeposit
? await this.initiateSwap(fiatDeposit)
const { quote, amountMsats } = await this.getQuote({
from: onramp?.currency || Currency.KES,
to: Currency.BTC,
amount: amountFiat.toString(),
});

const lightning = await this.fedimintService.invoice(
amountMsats,
reference,
);

const { status } = onramp
? await this.initiateSwap({
quote,
amountFiat: amountFiat.toString(),
reference,
source: onramp,
target: {
payout: lightning,
},
})
: {
status: TransactionStatus.PENDING,
reference: '',
amountMsats: 0,
amountFiat: 0,
};

this.logger.log(status);
const deposit = await this.wallet.create({
userId,
amountMsats,
amountFiat,
lightning: JSON.stringify(lightning),
status,
reference,
});
Expand Down
4 changes: 2 additions & 2 deletions apps/swap/src/swap.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
CreateOfframpSwapDto,
type FindSwapRequest,
type PaginatedRequest,
type QuoteRequest,
SwapServiceController,
SwapServiceControllerMethods,
QuoteRequestDto,
} from '@bitsacco/common';
import { SwapService } from './swap.service';

Expand All @@ -17,7 +17,7 @@ export class SwapController implements SwapServiceController {
constructor(private readonly swapService: SwapService) {}

@GrpcMethod()
getQuote(request: QuoteRequest) {
getQuote(request: QuoteRequestDto) {
return this.swapService.getQuote(request);
}

Expand Down
7 changes: 6 additions & 1 deletion apps/swap/src/swap.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { HttpModule } from '@nestjs/axios';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { CacheModule } from '@nestjs/cache-manager';
import { CustomStore, DatabaseModule, FedimintService, LoggerModule } from '@bitsacco/common';
import {
CustomStore,
DatabaseModule,
FedimintService,
LoggerModule,
} from '@bitsacco/common';
import { SwapController } from './swap.controller';
import { SwapService } from './swap.service';
import { FxService } from './fx/fx.service';
Expand Down
7 changes: 6 additions & 1 deletion apps/swap/src/swap.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
FedimintService,
SupportedCurrencyType,
PaginatedRequestDto,
QuoteRequestDto,
} from '@bitsacco/common';
import { v4 as uuidv4 } from 'uuid';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
Expand Down Expand Up @@ -63,7 +64,11 @@ export class SwapService {
this.logger.log('SwapService initialized');
}

async getQuote({ from, to, amount }: QuoteRequest): Promise<QuoteResponse> {
async getQuote({
from,
to,
amount,
}: QuoteRequestDto): Promise<QuoteResponse> {
try {
if (amount && isNaN(Number(amount))) {
throw new Error('Amount must be a number');
Expand Down
28 changes: 23 additions & 5 deletions libs/common/src/dto/solowallet.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Type } from 'class-transformer';
import { IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import {
IsNotEmpty,
IsNumber,
IsString,
Min,
ValidateNested,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { CreateOnrampSwapDto } from './swap.dto';
import { OnrampSwapSourceDto } from './swap.dto';
import { DepositFundsRequest, FindUserTxsRequest } from '../types';
import { PaginatedRequestDto } from './lib.dto';

Expand All @@ -12,10 +18,22 @@ export class DepositFundsRequestDto implements DepositFundsRequest {
@ApiProperty({ example: '7b158dfd-cb98-40b1-9ed2-a13006a9f670' })
userId: string;

@IsNotEmpty()
@IsString()
@Type(() => String)
@ApiProperty()
reference: string;

@ApiProperty()
@IsNumber()
@Min(1)
@ApiProperty({ example: 2 })
amountFiat: number;

@ValidateNested()
@Type(() => CreateOnrampSwapDto)
@ApiProperty({ type: CreateOnrampSwapDto })
fiatDeposit?: CreateOnrampSwapDto;
@Type(() => OnrampSwapSourceDto)
@ApiProperty({ type: OnrampSwapSourceDto })
onramp?: OnrampSwapSourceDto;
}

export class FindUserTxsRequestDto implements FindUserTxsRequest {
Expand Down
Loading

0 comments on commit ec1503c

Please sign in to comment.