From b12d7ec0b6262836f2870b0f84d1b798e922c583 Mon Sep 17 00:00:00 2001 From: Forte11Cuba Date: Wed, 15 Oct 2025 23:27:31 -0600 Subject: [PATCH] Add LNbits Lightning backend integration --- .env.example | 5 +- src/server/config/configuration.ts | 2 + src/server/config/configuration.type.ts | 2 + .../modules/lightning/lightning.enums.ts | 1 + .../lightning/lightning/lightning.module.ts | 3 +- .../lightning/lightning.service.spec.ts | 15 ++ .../lightning/lightning/lightning.service.ts | 11 +- .../lightning/lnbits/lnbits.helpers.ts | 28 +++ .../modules/lightning/lnbits/lnbits.module.ts | 14 ++ .../lightning/lnbits/lnbits.service.spec.ts | 116 ++++++++++ .../lightning/lnbits/lnbits.service.ts | 215 ++++++++++++++++++ .../lightning/walletkit/lnwalletkit.module.ts | 3 +- .../walletkit/lnwalletkit.service.spec.ts | 11 + .../walletkit/lnwalletkit.service.ts | 9 +- 14 files changed, 428 insertions(+), 7 deletions(-) create mode 100644 src/server/modules/lightning/lnbits/lnbits.helpers.ts create mode 100644 src/server/modules/lightning/lnbits/lnbits.module.ts create mode 100644 src/server/modules/lightning/lnbits/lnbits.service.spec.ts create mode 100644 src/server/modules/lightning/lnbits/lnbits.service.ts diff --git a/.env.example b/.env.example index fd4cd57a0..f6ec71947 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,7 @@ BITCOIN_RPC_PASSWORD=btc.rpc.pass # -------------------------------------------- # Lightning Configs (optional) # -------------------------------------------- -# valid types: lnd | cln +# valid types: lnd | cln | lnbits LIGHTNING_TYPE=lnd LIGHTNING_RPC_HOST=localhost LIGHTNING_RPC_PORT=8447 @@ -35,6 +35,9 @@ LIGHTNING_CERT=/path/to/cert # cln configs #LIGHTNING_KEY=/path/to/client.key #LIGHTNING_CA=/path/to/ca.pem +# lnbits configs +#LIGHTNING_API_URL=http://your.lnbits.server +#LIGHTNING_API_KEY=your_wallet_api_key_here # -------------------------------------------- # Taproot Configs (optional) diff --git a/src/server/config/configuration.ts b/src/server/config/configuration.ts index c0f7fcd93..bef2debe5 100644 --- a/src/server/config/configuration.ts +++ b/src/server/config/configuration.ts @@ -45,6 +45,8 @@ export const config = (): Config => { cert: process.env.LIGHTNING_CERT, key: process.env.LIGHTNING_KEY, ca: process.env.LIGHTNING_CA, + api_url: replaceLocalhostInDocker(process.env.LIGHTNING_API_URL), + api_key: process.env.LIGHTNING_API_KEY, }; const taproot_assets = { diff --git a/src/server/config/configuration.type.ts b/src/server/config/configuration.type.ts index 3488c43f8..52f0e63e9 100644 --- a/src/server/config/configuration.type.ts +++ b/src/server/config/configuration.type.ts @@ -27,6 +27,8 @@ export type Config = { cert: string; key: string; ca: string; + api_url: string; + api_key: string; }; taproot_assets: { type: string; diff --git a/src/server/modules/lightning/lightning.enums.ts b/src/server/modules/lightning/lightning.enums.ts index 8615f64bd..5a1488aa3 100644 --- a/src/server/modules/lightning/lightning.enums.ts +++ b/src/server/modules/lightning/lightning.enums.ts @@ -1,6 +1,7 @@ export enum LightningType { LND = 'lnd', CLN = 'cln', + LNBITS = 'lnbits', } export enum LightningAddressType { diff --git a/src/server/modules/lightning/lightning/lightning.module.ts b/src/server/modules/lightning/lightning/lightning.module.ts index eef8ec098..fd3b771e4 100644 --- a/src/server/modules/lightning/lightning/lightning.module.ts +++ b/src/server/modules/lightning/lightning/lightning.module.ts @@ -4,11 +4,12 @@ import {Module} from '@nestjs/common'; import {FetchModule} from '@server/modules/fetch/fetch.module'; import {LndModule} from '@server/modules/lightning/lnd/lnd.module'; import {ClnModule} from '@server/modules/lightning/cln/cln.module'; +import {LnbitsModule} from '@server/modules/lightning/lnbits/lnbits.module'; /* Local Dependencies */ import {LightningService} from './lightning.service'; @Module({ - imports: [FetchModule, LndModule, ClnModule], + imports: [FetchModule, LndModule, ClnModule, LnbitsModule], providers: [LightningService], exports: [LightningService], }) diff --git a/src/server/modules/lightning/lightning/lightning.service.spec.ts b/src/server/modules/lightning/lightning/lightning.service.spec.ts index 6b8d42393..efc2bf825 100644 --- a/src/server/modules/lightning/lightning/lightning.service.spec.ts +++ b/src/server/modules/lightning/lightning/lightning.service.spec.ts @@ -5,6 +5,7 @@ import {ConfigService} from '@nestjs/config'; /* Native Dependencies */ import {LndService} from '@server/modules/lightning/lnd/lnd.service'; import {ClnService} from '@server/modules/lightning/cln/cln.service'; +import {LnbitsService} from '@server/modules/lightning/lnbits/lnbits.service'; /* Local Dependencies */ import {LightningService} from './lightning.service'; import {OrchardErrorCode} from '@server/modules/error/error.types'; @@ -14,6 +15,7 @@ describe('LightningService', () => { let config_service: jest.Mocked; let lnd_service: jest.Mocked; let cln_service: jest.Mocked; + let lnbits_service: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -30,6 +32,18 @@ describe('LightningService', () => { mapClnRequest: jest.fn(), }, }, + { + provide: LnbitsService, + useValue: { + initializeLightningClient: jest.fn(), + mapLnbitsInfo: jest.fn(), + mapLnbitsChannelBalance: jest.fn(), + mapLnbitsRequest: jest.fn(), + getLnbitsInfo: jest.fn(), + getLnbitsBalance: jest.fn(), + decodeLnbitsInvoice: jest.fn(), + }, + }, ], }).compile(); @@ -37,6 +51,7 @@ describe('LightningService', () => { config_service = module.get(ConfigService); lnd_service = module.get(LndService); cln_service = module.get(ClnService); + lnbits_service = module.get(LnbitsService); }); it('should be defined', () => { diff --git a/src/server/modules/lightning/lightning/lightning.service.ts b/src/server/modules/lightning/lightning/lightning.service.ts index 31d8eb10f..5b91cbc50 100644 --- a/src/server/modules/lightning/lightning/lightning.service.ts +++ b/src/server/modules/lightning/lightning/lightning.service.ts @@ -6,6 +6,7 @@ import {OrchardErrorCode} from '@server/modules/error/error.types'; import {LightningType} from '@server/modules/lightning/lightning.enums'; import {LndService} from '@server/modules/lightning/lnd/lnd.service'; import {ClnService} from '@server/modules/lightning/cln/cln.service'; +import {LnbitsService} from '@server/modules/lightning/lnbits/lnbits.service'; /* Local Dependencies */ import {LightningInfo, LightningChannelBalance, LightningRequest} from './lightning.types'; @@ -14,22 +15,25 @@ export class LightningService implements OnModuleInit { private readonly logger = new Logger(LightningService.name); private grpc_client: any = null; + private http_client: any = null; private type: LightningType; constructor( private configService: ConfigService, private lndService: LndService, private clnService: ClnService, + private lnbitsService: LnbitsService, ) {} public async onModuleInit() { this.type = this.configService.get('lightning.type'); - this.initializeGrpcClients(); + this.initializeClients(); } - private initializeGrpcClients() { + private initializeClients() { if (this.type === 'lnd') this.grpc_client = this.lndService.initializeLightningClient(); if (this.type === 'cln') this.grpc_client = this.clnService.initializeLightningClient(); + if (this.type === 'lnbits') this.http_client = this.lnbitsService.initializeLightningClient(); } private makeGrpcRequest(method: string, request: any): Promise { @@ -49,6 +53,7 @@ export class LightningService implements OnModuleInit { async getLightningInfo(): Promise { if (this.type === 'lnd') return this.makeGrpcRequest('GetInfo', {}); if (this.type === 'cln') return this.clnService.mapClnInfo(await this.makeGrpcRequest('Getinfo', {})); + if (this.type === 'lnbits') return this.lnbitsService.mapLnbitsInfo(await this.lnbitsService.getLnbitsInfo()); } async getLightningChannelBalance(): Promise { @@ -59,10 +64,12 @@ export class LightningService implements OnModuleInit { await this.makeGrpcRequest('ListPeerChannels', {}), ); } + if (this.type === 'lnbits') return this.lnbitsService.mapLnbitsChannelBalance(await this.lnbitsService.getLnbitsBalance()); } async getLightningRequest(request: string): Promise { if (this.type === 'lnd') return this.lndService.mapLndRequest(await this.makeGrpcRequest('DecodePayReq', {pay_req: request})); if (this.type === 'cln') return this.clnService.mapClnRequest(await this.makeGrpcRequest('Decode', {string: request})); + if (this.type === 'lnbits') return this.lnbitsService.mapLnbitsRequest(await this.lnbitsService.decodeLnbitsInvoice(request)); } } diff --git a/src/server/modules/lightning/lnbits/lnbits.helpers.ts b/src/server/modules/lightning/lnbits/lnbits.helpers.ts new file mode 100644 index 000000000..177917f03 --- /dev/null +++ b/src/server/modules/lightning/lnbits/lnbits.helpers.ts @@ -0,0 +1,28 @@ +/* Application Dependencies */ +import {LightningRequestType} from '@server/modules/lightning/lightning.enums'; + +export function mapRequestDescription(description: string | null): string | null { + if (!description) return null; + if (description === '') return null; + return description; +} + +export function mapRequestExpiry(request: any): number | null { + if (request?.expires_at) return Number(request.expires_at); + if (request?.expiry) return Number(request.expiry); + return null; +} + +export function mapRequestType(type?: string): LightningRequestType { + return LightningRequestType.BOLT11_INVOICE; +} + +export function mapLnbitsError(error: any): string { + if (error?.detail) { + if (Array.isArray(error.detail)) { + return error.detail.map((d: any) => d.msg || d).join(', '); + } + return error.detail; + } + return error?.message || 'Unknown LNbits error'; +} \ No newline at end of file diff --git a/src/server/modules/lightning/lnbits/lnbits.module.ts b/src/server/modules/lightning/lnbits/lnbits.module.ts new file mode 100644 index 000000000..5319076ec --- /dev/null +++ b/src/server/modules/lightning/lnbits/lnbits.module.ts @@ -0,0 +1,14 @@ +/* Core Dependencies */ +import {Module} from '@nestjs/common'; +/* Application Dependencies */ +import {CredentialModule} from '@server/modules/credential/credential.module'; +import {FetchModule} from '@server/modules/fetch/fetch.module'; +/* Local Dependencies */ +import {LnbitsService} from './lnbits.service'; + +@Module({ + imports: [CredentialModule, FetchModule], + providers: [LnbitsService], + exports: [LnbitsService], +}) +export class LnbitsModule {} \ No newline at end of file diff --git a/src/server/modules/lightning/lnbits/lnbits.service.spec.ts b/src/server/modules/lightning/lnbits/lnbits.service.spec.ts new file mode 100644 index 000000000..4512e61ed --- /dev/null +++ b/src/server/modules/lightning/lnbits/lnbits.service.spec.ts @@ -0,0 +1,116 @@ +/* Core Dependencies */ +import {Test, TestingModule} from '@nestjs/testing'; +import {expect} from '@jest/globals'; +import {ConfigService} from '@nestjs/config'; +/* Application Dependencies */ +import {CredentialService} from '@server/modules/credential/credential.service'; +import {FetchService} from '@server/modules/fetch/fetch.service'; +/* Local Dependencies */ +import {LnbitsService} from './lnbits.service'; + +describe('LnbitsService', () => { + let lnbits_service: LnbitsService; + let config_service: jest.Mocked; + let credential_service: jest.Mocked; + let fetch_service: jest.Mocked; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + LnbitsService, + {provide: ConfigService, useValue: {get: jest.fn()}}, + {provide: CredentialService, useValue: {loadPemOrPath: jest.fn()}}, + {provide: FetchService, useValue: {fetchWithProxy: jest.fn()}}, + ], + }).compile(); + + lnbits_service = module.get(LnbitsService); + config_service = module.get(ConfigService); + credential_service = module.get(CredentialService); + fetch_service = module.get(FetchService); + }); + + it('should be defined', () => { + expect(lnbits_service).toBeDefined(); + }); + + describe('initializeLightningClient', () => { + it('should initialize successfully with valid credentials', () => { + config_service.get.mockReturnValueOnce('http://localhost:5000'); // api_url + config_service.get.mockReturnValueOnce('test_api_key'); // api_key + credential_service.loadPemOrPath.mockReturnValue(Buffer.from('test_api_key')); + + const client = lnbits_service.initializeLightningClient(); + expect(client).toBe(lnbits_service); + }); + + it('should throw error with missing credentials', () => { + config_service.get.mockReturnValue(null); + + expect(() => lnbits_service.initializeLightningClient()).toThrow('Failed to initialize LNbits client'); + }); + }); + + describe('mapLnbitsRequest', () => { + it('should map LNbits request correctly', () => { + const lnbits_request = { + type: 'bolt11', + valid: true, + description: 'Test payment', + expires_at: 1234567890, + }; + + const result = lnbits_service.mapLnbitsRequest(lnbits_request); + + expect(result.valid).toBe(true); + expect(result.description).toBe('Test payment'); + expect(result.expiry).toBe(1234567890); + }); + }); + + describe('mapLnbitsInfo', () => { + it('should map LNbits info correctly', async () => { + const lnbits_info = { + version: '1.0.0', + identity_pubkey: 'test_pubkey', + alias: 'Test Node', + block_height: 800000, + testnet: false, + }; + + const result = await lnbits_service.mapLnbitsInfo(lnbits_info); + + expect(result.version).toBe('1.0.0'); + expect(result.identity_pubkey).toBe('test_pubkey'); + expect(result.alias).toBe('Test Node'); + expect(result.block_height).toBe(800000); + expect(result.testnet).toBe(false); + expect(result.num_active_channels).toBe(0); // LNbits no maneja canales + }); + }); + + describe('mapLnbitsChannelBalance', () => { + it('should map LNbits balance correctly', async () => { + const balance = 100000; // 100k sats + + const result = await lnbits_service.mapLnbitsChannelBalance(balance); + + expect(result.balance).toBe('100000'); + expect(result.local_balance.sat).toBe('100000'); + expect(result.local_balance.msat).toBe('100000000'); + expect(result.remote_balance.sat).toBe('0'); + }); + }); + + describe('mapLnbitsAddresses', () => { + it('should map bitcoin address correctly', async () => { + const address = 'bc1qtest123456789abcdef'; + + const result = await lnbits_service.mapLnbitsAddresses(address); + + expect(result.account_with_addresses).toHaveLength(1); + expect(result.account_with_addresses[0].addresses[0].address).toBe(address); + expect(result.account_with_addresses[0].name).toBe('LNbits Onchain'); + }); + }); +}); \ No newline at end of file diff --git a/src/server/modules/lightning/lnbits/lnbits.service.ts b/src/server/modules/lightning/lnbits/lnbits.service.ts new file mode 100644 index 000000000..5a419e68b --- /dev/null +++ b/src/server/modules/lightning/lnbits/lnbits.service.ts @@ -0,0 +1,215 @@ +/* Core Dependencies */ +import {Injectable, Logger} from '@nestjs/common'; +import {ConfigService} from '@nestjs/config'; +/* Application Dependencies */ +import {CredentialService} from '@server/modules/credential/credential.service'; +import {FetchService} from '@server/modules/fetch/fetch.service'; +/* Native Dependencies */ +import { + LightningInfo, + LightningChannelBalance, + LightningRequest, +} from '@server/modules/lightning/lightning/lightning.types'; +import {LightningAddresses} from '@server/modules/lightning/walletkit/lnwalletkit.types'; +import {LightningAddressType} from '@server/modules/lightning/lightning.enums'; +/* Local Dependencies */ +import {mapRequestType, mapRequestDescription, mapRequestExpiry, mapLnbitsError} from './lnbits.helpers'; + +@Injectable() +export class LnbitsService { + private readonly logger = new Logger(LnbitsService.name); + private api_url: string; + private api_key: string; + + constructor( + private configService: ConfigService, + private credentialService: CredentialService, + private fetchService: FetchService, + ) {} + + private getApiCredentials() { + this.api_url = this.configService.get('lightning.api_url'); + this.api_key = this.configService.get('lightning.api_key'); + + if (!this.api_url || !this.api_key) { + this.logger.warn('Missing LNbits API credentials, connection cannot be established'); + return null; + } + + if (this.api_key && this.api_key.includes('/')) { + const loaded_key_buffer = this.credentialService.loadPemOrPath(this.api_key); + if (loaded_key_buffer) { + this.api_key = loaded_key_buffer.toString('utf8').trim(); + } + } + + return {api_url: this.api_url, api_key: this.api_key}; + } + + private async makeHttpRequest(endpoint: string, options: RequestInit = {}): Promise { + const credentials = this.getApiCredentials(); + if (!credentials) { + throw new Error('LNbits API credentials not configured'); + } + + const url = `${credentials.api_url}${endpoint}`; + const headers = { + 'X-Api-Key': credentials.api_key, + 'Content-Type': 'application/json', + ...options.headers, + }; + + try { + const response = await this.fetchService.fetchWithProxy(url, { + ...options, + headers, + }); + + if (!response.ok) { + let errorMessage = `HTTP ${response.status}`; + try { + const errorData = await response.json(); + errorMessage = mapLnbitsError(errorData); + } catch { + errorMessage = await response.text() || errorMessage; + } + this.logger.error(`LNbits API error: ${response.status} - ${errorMessage}`); + throw new Error(`LNbits API error: ${response.status} - ${errorMessage}`); + } + + return await response.json(); + } catch (error) { + this.logger.error(`LNbits request failed: ${error.message}`); + throw error; + } + } + + public initializeLightningClient(): LnbitsService { + const credentials = this.getApiCredentials(); + if (credentials) { + this.logger.log('LNbits HTTP client initialized'); + return this; + } + throw new Error('Failed to initialize LNbits client'); + } + + public initializeWalletKitClient(): LnbitsService { + return this.initializeLightningClient(); + } + + public async getLnbitsInfo(): Promise { + return this.makeHttpRequest('/lndhub/ext/getinfo'); + } + + public async getLnbitsBalance(): Promise { + const walletData = await this.makeHttpRequest('/api/v1/wallet'); + return Math.floor(walletData.balance / 1000).toString(); + } + + public async decodeLnbitsInvoice(invoice: string): Promise { + return this.makeHttpRequest(`/lndhub/ext/decodeinvoice?invoice=${encodeURIComponent(invoice)}`); + } + + public async getLnbitsBtcAddress(): Promise { + return this.makeHttpRequest('/lndhub/ext/getbtc'); + } + + public async getLnbitsTransactions(): Promise { + return this.makeHttpRequest('/lndhub/ext/gettxs'); + } + + public async getLnbitsPendingTransactions(): Promise { + return this.makeHttpRequest('/lndhub/ext/getpending'); + } + + public async mapLnbitsInfo(info: any): Promise { + return { + version: info?.version || 'LNbits', + commit_hash: '', + identity_pubkey: info?.identity_pubkey || '', + alias: info?.alias || 'LNbits Wallet', + color: '#000000', + num_pending_channels: 0, + num_active_channels: 0, + num_inactive_channels: 0, + num_peers: 0, + block_height: info?.block_height || 0, + block_hash: info?.block_hash || '', + best_header_timestamp: info?.best_header_timestamp || 0, + synced_to_chain: true, + synced_to_graph: true, + testnet: info?.testnet || false, + chains: [{chain: 'bitcoin', network: info?.testnet ? 'testnet' : 'mainnet'}], + uris: [], + require_htlc_interceptor: false, + store_final_htlc_resolutions: false, + features: {}, + }; + } + + public async mapLnbitsChannelBalance(balance: any): Promise { + const balanceStr = balance?.toString() || '0'; + + return { + balance: balanceStr, + pending_open_balance: '0', + local_balance: {sat: balanceStr, msat: (parseInt(balanceStr) * 1000).toString()}, + remote_balance: {sat: '0', msat: '0'}, + unsettled_local_balance: {sat: '0', msat: '0'}, + unsettled_remote_balance: {sat: '0', msat: '0'}, + pending_open_local_balance: {sat: '0', msat: '0'}, + pending_open_remote_balance: {sat: '0', msat: '0'}, + custom_channel_data: Buffer.alloc(0), + }; + } + + public mapLnbitsRequest(request: any): LightningRequest { + return { + type: mapRequestType(request?.type), + valid: request?.valid ?? true, // LNbits generalmente retorna invoices vĂ¡lidos + expiry: mapRequestExpiry(request), + description: mapRequestDescription(request?.description), + offer_quantity_max: request?.offer_quantity_max ?? null, + }; + } + + public async mapLnbitsAddresses(btcAddress: any): Promise { + const address = typeof btcAddress === 'string' ? btcAddress : btcAddress?.address; + + if (!address) { + return {account_with_addresses: []}; + } + + return { + account_with_addresses: [ + { + name: 'LNbits Onchain', + address_type: this.detectAddressType(address), + derivation_path: '', + addresses: [ + { + address: address, + is_internal: 'false', + balance: 0, + derivation_path: '', + public_key: Buffer.alloc(0), + }, + ], + }, + ], + }; + } + + private detectAddressType(address: string): LightningAddressType { + if (address.startsWith('bc1q') || address.startsWith('tb1q')) { + return LightningAddressType.WITNESS_PUBKEY_HASH; + } + if (address.startsWith('3') || address.startsWith('2')) { + return LightningAddressType.NESTED_WITNESS_PUBKEY_HASH; + } + if (address.startsWith('bc1p') || address.startsWith('tb1p')) { + return LightningAddressType.TAPROOT_PUBKEY; + } + return LightningAddressType.UNKOWN; + } +} \ No newline at end of file diff --git a/src/server/modules/lightning/walletkit/lnwalletkit.module.ts b/src/server/modules/lightning/walletkit/lnwalletkit.module.ts index 0aa910eaf..7e80265a0 100644 --- a/src/server/modules/lightning/walletkit/lnwalletkit.module.ts +++ b/src/server/modules/lightning/walletkit/lnwalletkit.module.ts @@ -4,11 +4,12 @@ import {Module} from '@nestjs/common'; import {FetchModule} from '@server/modules/fetch/fetch.module'; import {LndModule} from '@server/modules/lightning/lnd/lnd.module'; import {ClnModule} from '@server/modules/lightning/cln/cln.module'; +import {LnbitsModule} from '@server/modules/lightning/lnbits/lnbits.module'; /* Local Dependencies */ import {LightningWalletKitService} from './lnwalletkit.service'; @Module({ - imports: [FetchModule, LndModule, ClnModule], + imports: [FetchModule, LndModule, ClnModule, LnbitsModule], providers: [LightningWalletKitService], exports: [LightningWalletKitService], }) diff --git a/src/server/modules/lightning/walletkit/lnwalletkit.service.spec.ts b/src/server/modules/lightning/walletkit/lnwalletkit.service.spec.ts index 23b7c2c36..d4b5af656 100644 --- a/src/server/modules/lightning/walletkit/lnwalletkit.service.spec.ts +++ b/src/server/modules/lightning/walletkit/lnwalletkit.service.spec.ts @@ -5,6 +5,7 @@ import {ConfigService} from '@nestjs/config'; /* Native Dependencies */ import {LndService} from '@server/modules/lightning/lnd/lnd.service'; import {ClnService} from '@server/modules/lightning/cln/cln.service'; +import {LnbitsService} from '@server/modules/lightning/lnbits/lnbits.service'; /* Local Dependencies */ import {LightningWalletKitService} from './lnwalletkit.service'; import {OrchardErrorCode} from '@server/modules/error/error.types'; @@ -14,6 +15,7 @@ describe('LightningWalletKitService', () => { let config_service: jest.Mocked; let lnd_service: jest.Mocked; let cln_service: jest.Mocked; + let lnbits_service: jest.Mocked; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -22,6 +24,14 @@ describe('LightningWalletKitService', () => { {provide: ConfigService, useValue: {get: jest.fn()}}, {provide: LndService, useValue: {initializeWalletKitClient: jest.fn()}}, {provide: ClnService, useValue: {initializeWalletKitClient: jest.fn(), mapClnAddresses: jest.fn()}}, + { + provide: LnbitsService, + useValue: { + initializeWalletKitClient: jest.fn(), + mapLnbitsAddresses: jest.fn(), + getLnbitsBtcAddress: jest.fn(), + }, + }, ], }).compile(); @@ -29,6 +39,7 @@ describe('LightningWalletKitService', () => { config_service = module.get(ConfigService); lnd_service = module.get(LndService); cln_service = module.get(ClnService); + lnbits_service = module.get(LnbitsService); }); it('should be defined', () => { diff --git a/src/server/modules/lightning/walletkit/lnwalletkit.service.ts b/src/server/modules/lightning/walletkit/lnwalletkit.service.ts index 257724298..708db81b8 100644 --- a/src/server/modules/lightning/walletkit/lnwalletkit.service.ts +++ b/src/server/modules/lightning/walletkit/lnwalletkit.service.ts @@ -6,6 +6,7 @@ import {OrchardErrorCode} from '@server/modules/error/error.types'; import {LightningType} from '@server/modules/lightning/lightning.enums'; import {LndService} from '@server/modules/lightning/lnd/lnd.service'; import {ClnService} from '@server/modules/lightning/cln/cln.service'; +import {LnbitsService} from '@server/modules/lightning/lnbits/lnbits.service'; /* Local Dependencies */ import {LightningAddresses} from './lnwalletkit.types'; @@ -14,22 +15,25 @@ export class LightningWalletKitService implements OnModuleInit { private readonly logger = new Logger(LightningWalletKitService.name); private grpc_client: any = null; + private http_client: any = null; private type: LightningType; constructor( private configService: ConfigService, private lndService: LndService, private clnService: ClnService, + private lnbitsService: LnbitsService, ) {} public async onModuleInit() { this.type = this.configService.get('lightning.type'); - this.initializeGrpcClients(); + this.initializeClients(); } - private initializeGrpcClients() { + private initializeClients() { if (this.type === 'lnd') this.grpc_client = this.lndService.initializeWalletKitClient(); if (this.type === 'cln') this.grpc_client = this.clnService.initializeWalletKitClient(); + if (this.type === 'lnbits') this.http_client = this.lnbitsService.initializeWalletKitClient(); } private makeGrpcRequest(method: string, request: any): Promise { @@ -48,5 +52,6 @@ export class LightningWalletKitService implements OnModuleInit { async getLightningAddresses(): Promise { if (this.type === 'lnd') return this.makeGrpcRequest('ListAddresses', {}); if (this.type === 'cln') return this.clnService.mapClnAddresses(await this.makeGrpcRequest('ListAddresses', {})); + if (this.type === 'lnbits') return this.lnbitsService.mapLnbitsAddresses(await this.lnbitsService.getLnbitsBtcAddress()); } }