├── tests
├── utils
│ ├── storage.json
│ ├── global-teardown.ts
│ ├── spawn_lightning_cluster.ts
│ ├── global-setup.ts
│ ├── spawn_lightning_server.ts
│ ├── global_spawn_lightning.ts
│ ├── spawn_lightning.ts
│ └── constants.ts
├── server
│ ├── certValidityDays.test.ts
│ ├── price.test.ts
│ ├── reconnect.test.ts
│ ├── find.test.ts
│ ├── closed.test.ts
│ ├── chainfees.test.ts
│ ├── utxos.test.ts
│ ├── forwards.test.ts
│ ├── chainDeposit.test.ts
│ ├── call.test.ts
│ ├── chartChainFees.test.ts
│ ├── lnurl.test.ts
│ ├── chartPaymentsReceived.test.ts
│ ├── balance.test.ts
│ ├── peers.test.ts
│ ├── cleanFailedPayments.test.ts
│ ├── graph.test.ts
│ ├── tags.test.ts
│ └── fees.test.ts
└── client
│ ├── fees.test.ts
│ ├── reconnect.test.ts
│ ├── graph.test.ts
│ ├── find.test.ts
│ ├── closed.test.ts
│ ├── open.test.ts
│ ├── utxos.test.ts
│ ├── call.test.ts
│ ├── certValidityDays.test.ts
│ ├── chainDeposit.test.ts
│ ├── forwards.test.ts
│ ├── invoice.test.ts
│ ├── chartChainFees.test.ts
│ ├── createChannelGroup.test.ts
│ ├── joinChannelGroup.test.ts
│ ├── probe.test.ts
│ ├── chartPaymentsReceived.test.ts
│ ├── chartFeesEarned.test.ts
│ ├── quicktools.test.ts
│ ├── balance.test.ts
│ ├── send.test.ts
│ └── cleanFailedPayments.test.ts
├── app-stores
└── umbrel
│ ├── .bosgui
│ └── .gitkeep
│ ├── exports.sh
│ ├── docker-compose.yml
│ └── umbrel-app.yml
├── storage.json
├── .DS_Store
├── next-env.d.ts
├── .dockerignore
├── src
├── client
│ ├── public
│ │ ├── startup.mov
│ │ └── startup.mp4
│ ├── .vscode
│ │ └── settings.json
│ ├── .eslintrc.json
│ ├── app
│ │ ├── Dashboard
│ │ │ └── page.tsx
│ │ ├── Commands
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── pages
│ │ ├── index.tsx
│ │ ├── _app.tsx
│ │ ├── 404.tsx
│ │ └── 500.tsx
│ ├── standard_components
│ │ ├── lndboss
│ │ │ ├── index.ts
│ │ │ ├── RawApiList.tsx
│ │ │ └── TagsList.tsx
│ │ └── app-components
│ │ │ ├── quicktools
│ │ │ └── index.ts
│ │ │ ├── StandardSwitch.tsx
│ │ │ ├── ContainerStyle.tsx
│ │ │ ├── CenterFlexBox.tsx
│ │ │ ├── StandardRouterLink.tsx
│ │ │ ├── SubmitButton.tsx
│ │ │ ├── CopyText.tsx
│ │ │ ├── StartFlexBox.tsx
│ │ │ ├── Startup.tsx
│ │ │ ├── BasicDatePicker.tsx
│ │ │ ├── StandardButtonLink.tsx
│ │ │ ├── ProgressBar.tsx
│ │ │ ├── index.ts
│ │ │ ├── RouteGuard.tsx
│ │ │ ├── ReactCron.tsx
│ │ │ ├── BasicTable.tsx
│ │ │ └── ResponsiveGrid.tsx
│ ├── next-env.d.ts
│ ├── hooks
│ │ ├── useLoading.ts
│ │ └── usePasswordValidation.ts
│ ├── dashboard
│ │ ├── Title.tsx
│ │ ├── PendingChart.tsx
│ │ └── RoutingFeeChart.tsx
│ ├── next.config.js
│ ├── output
│ │ ├── JoinGroupChannelOutput.tsx
│ │ ├── CreateGroupChannelOutput.tsx
│ │ ├── FeesOutput.tsx
│ │ ├── CallOutput.tsx
│ │ ├── FindOutput.tsx
│ │ ├── ReconnectOutput.tsx
│ │ ├── CertValidityDaysOutput.tsx
│ │ ├── GraphOutput.tsx
│ │ ├── EncryptOutput.tsx
│ │ ├── DecryptOutput.tsx
│ │ ├── ChainDepositOutput.tsx
│ │ ├── PriceOutput.tsx
│ │ ├── ChainfeesOutput.tsx
│ │ ├── InvoiceOutput.tsx
│ │ ├── CleanFailedPaymentsOutput.tsx
│ │ └── OpenOutput.tsx
│ ├── utils
│ │ ├── validations
│ │ │ └── validate_join_channel_group_command.ts
│ │ ├── fetch_peers_and_tags.ts
│ │ ├── fee_strategies.ts
│ │ ├── jwt.ts
│ │ ├── cookie.ts
│ │ └── constants.ts
│ ├── tsconfig.json
│ └── register_charts.ts
├── server
│ ├── commands
│ │ ├── grpc_utils
│ │ │ ├── index.ts
│ │ │ ├── icons.ts
│ │ │ └── format_tokens.ts
│ │ ├── lnurl
│ │ │ ├── index.ts
│ │ │ ├── der_encode_signature.ts
│ │ │ └── sign_auth_challenge.ts
│ │ ├── cleanFailedPayments
│ │ │ └── clean_failed_payments_command.ts
│ │ ├── decrypt
│ │ │ └── decrypt_command.ts
│ │ ├── encrypt
│ │ │ └── encrypt_command.ts
│ │ ├── joinGroupChannel
│ │ │ ├── join_channel_group_command.ts
│ │ │ └── spawn_process.ts
│ │ ├── rebalance
│ │ │ ├── encode_rebalance_params.ts
│ │ │ ├── encode_trigger.ts
│ │ │ ├── read_rebalance_file.ts
│ │ │ └── get_triggers.ts
│ │ ├── chainfees
│ │ │ └── chainfees_command.ts
│ │ ├── createChannelGroup
│ │ │ ├── create_channel_group_command.ts
│ │ │ └── spawn_process.ts
│ │ ├── fees
│ │ │ ├── read_fees_file.ts
│ │ │ └── fees_command.ts
│ │ ├── reconnect
│ │ │ └── reconnect_command.ts
│ │ ├── price
│ │ │ └── price_command.ts
│ │ ├── chartChainFees
│ │ │ └── chart_chain_fees_command.ts
│ │ ├── chartPaymentsReceived
│ │ │ └── chart_payments_received_command.ts
│ │ ├── certValidityDays
│ │ │ └── cert_validity_days_command.ts
│ │ ├── chartFeesEarned
│ │ │ └── chart_fees_earned_command.ts
│ │ ├── utxos
│ │ │ └── utxos_command.ts
│ │ ├── accounting
│ │ │ └── accounting_command.ts
│ │ └── invoice
│ │ │ └── invoice_command.ts
│ ├── external_services_utils
│ │ └── index.ts
│ ├── authentication
│ │ ├── index.ts
│ │ └── getAccountInfo.ts
│ ├── settings
│ │ ├── settings.json
│ │ ├── index.ts
│ │ ├── check_amboss_health_setting.ts
│ │ ├── check_scheduled_rebalance_setting.ts
│ │ └── get_settings.file.ts
│ ├── modules
│ │ ├── auth
│ │ │ ├── local-auth.guard.ts
│ │ │ ├── jwt.strategy.ts
│ │ │ ├── jwt-auth.guard.ts
│ │ │ ├── auth.module.ts
│ │ │ └── local.strategy.ts
│ │ ├── grpc
│ │ │ ├── grpc.module.ts
│ │ │ └── grpc.controller.ts
│ │ ├── users
│ │ │ └── users.module.ts
│ │ ├── cron
│ │ │ └── cron.module.ts
│ │ ├── commands
│ │ │ └── commands.module.ts
│ │ ├── fees
│ │ │ ├── fees.module.ts
│ │ │ └── fees.controller.ts
│ │ ├── lnd
│ │ │ ├── lnd.module.ts
│ │ │ └── lnd.service.ts
│ │ ├── boslogger
│ │ │ ├── boslogger.module.ts
│ │ │ └── boslogger.service.ts
│ │ ├── socket
│ │ │ ├── socket.module.ts
│ │ │ └── socket.gateway.ts
│ │ ├── view
│ │ │ ├── view.module.ts
│ │ │ ├── view.controller.ts
│ │ │ └── view.service.ts
│ │ ├── external-services
│ │ │ ├── external-services.module.ts
│ │ │ └── external-services.service.ts
│ │ ├── rebalance
│ │ │ ├── rebalance.module.ts
│ │ │ └── rebalance.controller.ts
│ │ └── credentials
│ │ │ ├── credentials.module.ts
│ │ │ ├── credentials.controller.ts
│ │ │ └── credentials.service.ts
│ ├── lnd
│ │ ├── index.ts
│ │ ├── check_connection.ts
│ │ └── authenticated_lnd.ts
│ ├── app.controller.ts
│ ├── main.ts
│ └── utils
│ │ └── constants.ts
└── shared
│ └── cast.helper.ts
├── nest-cli.json
├── .npmignore
├── .prettierrc
├── tsconfig.build.json
├── .prettierignore
├── docker
├── docker-compose-dev.yaml
├── docker-compose.yaml
├── docker-compose-umbrel.yaml
└── dev.Dockerfile
├── tsconfig.paths.json
├── .editorconfig
├── scripts
└── nest.sh
├── .vscode
└── settings.json
├── tsconfig.json
├── .gitignore
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── on-push-dockerhub.yml
│ ├── on-tag-dockerhub.yml
│ ├── on-tag-dockerhub-root.yml
│ ├── on-tag-github.ignore
│ └── on-push-github.ignore
├── License
├── .env.example
├── arm64.Dockerfile
└── .eslintrc.js
/tests/utils/storage.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app-stores/umbrel/.bosgui/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": [],
3 | "origins": []
4 | }
5 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niteshbalusu11/lndboss/HEAD/.DS_Store
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | dist
4 | tests
5 | playwright-report
6 | .env
7 | github
8 |
--------------------------------------------------------------------------------
/app-stores/umbrel/exports.sh:
--------------------------------------------------------------------------------
1 | export APP_LNDBOSS_IP="10.21.21.47"
2 | export APP_LNDBOSS_PORT="8055"
3 |
--------------------------------------------------------------------------------
/src/client/public/startup.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niteshbalusu11/lndboss/HEAD/src/client/public/startup.mov
--------------------------------------------------------------------------------
/src/client/public/startup.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niteshbalusu11/lndboss/HEAD/src/client/public/startup.mp4
--------------------------------------------------------------------------------
/src/server/commands/grpc_utils/index.ts:
--------------------------------------------------------------------------------
1 | import getPending from './get_pending';
2 |
3 | export { getPending };
4 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src",
4 | "entryFile": "server/main"
5 | }
6 |
--------------------------------------------------------------------------------
/src/server/external_services_utils/index.ts:
--------------------------------------------------------------------------------
1 | import ambossHealthCheck from './amboss_health_check';
2 |
3 | export { ambossHealthCheck };
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | tests
3 | .env
4 | dev.Dockerfile
5 | Dockerfile
6 | docker-compose-dev.yaml
7 | docker-compose-umbrel.yaml
8 | .dockerignore
9 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "es5",
4 | "tabWidth": 2,
5 | "printWidth": 120,
6 | "arrowParens": "avoid"
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "tests", "test", "dist", "**/*spec.ts", "**/*.test.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/src/client/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "../../node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true
4 | }
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | docker-compose.yaml
2 | yarn.lock
3 | package.json
4 | node_modules
5 | dist
6 | Dockerfile
7 | yarn-error.log
8 | LICENSE
9 | README.md
10 |
--------------------------------------------------------------------------------
/src/server/commands/lnurl/index.ts:
--------------------------------------------------------------------------------
1 | import lnurlCommand from './lnurl_command';
2 | import parseUrl from './parse_url';
3 |
4 | export { lnurlCommand, parseUrl };
5 |
--------------------------------------------------------------------------------
/src/server/authentication/index.ts:
--------------------------------------------------------------------------------
1 | import getAccountInfo from './getAccountInfo';
2 | import register from './register';
3 |
4 | export { getAccountInfo, register };
5 |
--------------------------------------------------------------------------------
/src/client/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next",
3 | "rules": {
4 | "react-hooks/rules-of-hooks": "off",
5 | "react-hooks/exhaustive-deps": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/server/settings/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ambossHealthCheck": {
3 | "is_enabled": true
4 | },
5 | "scheduledRebalancing": {
6 | "is_enabled": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/client/app/Dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import MainDashboard from '~client/dashboard/MainDashboard';
4 |
5 | export default function Dashboard() {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/docker/docker-compose-dev.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | lndboss:
3 | build: .
4 | volumes:
5 | - ~/.bosgui:/home/node/.bosgui
6 | - /Users/nitesh/Library/Application Support/Lnd:/home/node/.lnd
7 | ports:
8 | - '8055:8055'
9 |
--------------------------------------------------------------------------------
/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./src",
4 | "paths": {
5 | "~client/*": ["client/*"],
6 | "~server/*": ["server/*"],
7 | "~shared/*": ["shared/*"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/scripts/nest.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd src/server/modules
4 |
5 | while getopts "n:" arg; do
6 | case $arg in
7 | n) Arg=$OPTARG && nest g controller $Arg --no-spec && nest g service $Arg --no-spec && nest g module $Arg --no-spec;;
8 | esac
9 | done
10 |
--------------------------------------------------------------------------------
/src/client/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Startup } from '../standard_components/app-components';
3 |
4 | /*
5 | App starts here
6 | */
7 |
8 | const Home = () => {
9 | return ;
10 | };
11 |
12 | export default Home;
13 |
--------------------------------------------------------------------------------
/src/client/standard_components/lndboss/index.ts:
--------------------------------------------------------------------------------
1 | import PeersAndTagsList from './PeersAndTagsList';
2 | import PeersList from './PeersList';
3 | import RawApiList from './RawApiList';
4 | import TagsList from './TagsList';
5 |
6 | export { PeersAndTagsList, PeersList, RawApiList, TagsList };
7 |
--------------------------------------------------------------------------------
/tests/utils/global-teardown.ts:
--------------------------------------------------------------------------------
1 | import { killGlobalContainer } from './global_spawn_lightning';
2 |
3 | async function globalTeardown() {
4 | await killGlobalContainer();
5 | console.log('======================Stopped Global Container======================');
6 | }
7 |
8 | export default globalTeardown;
9 |
--------------------------------------------------------------------------------
/src/client/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/basic-features/typescript for more information.
7 |
--------------------------------------------------------------------------------
/src/server/modules/auth/local-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { AuthGuard } from '@nestjs/passport';
2 | import { Injectable } from '@nestjs/common';
3 |
4 | // Add a local auth guard to check if the user is logged in before returning a JWT
5 |
6 | @Injectable()
7 | export class LocalAuthGuard extends AuthGuard('local') {}
8 |
--------------------------------------------------------------------------------
/src/server/modules/grpc/grpc.module.ts:
--------------------------------------------------------------------------------
1 | import { GrpcController } from './grpc.controller';
2 | import { GrpcService } from './grpc.service';
3 | import { Module } from '@nestjs/common';
4 |
5 | @Module({
6 | controllers: [GrpcController],
7 | providers: [GrpcService],
8 | })
9 | export class GrpcModule {}
10 |
--------------------------------------------------------------------------------
/src/server/modules/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UsersService } from './users.service';
3 |
4 | // Users Module: Module for the users service
5 |
6 | @Module({
7 | providers: [UsersService],
8 | exports: [UsersService],
9 | })
10 | export class UsersModule {}
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
3 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "editor.formatOnSave": true,
6 | "git.enableCommitSigning": true
7 | }
8 |
--------------------------------------------------------------------------------
/src/server/modules/cron/cron.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, Module } from '@nestjs/common';
2 |
3 | import { CronService } from './cron.service';
4 |
5 | // Global module for cron service
6 |
7 | @Global()
8 | @Module({
9 | providers: [CronService],
10 | exports: [CronService],
11 | })
12 | export class CronModule {}
13 |
--------------------------------------------------------------------------------
/docker/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | lndboss:
3 | image: niteshbalusu/lndboss:latest
4 | volumes:
5 | - ~/.bosgui:/home/node/.bosgui
6 | - /path/to/your/lnd/directory:/home/node/.lnd
7 | ports:
8 | - "8055:8055"
9 | networks:
10 | default:
11 | name: 1_default
12 | external: true
13 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/quicktools/index.ts:
--------------------------------------------------------------------------------
1 | import CreateChainAddress from './CreateChainAddress';
2 | import CreateInvoice from './CreateInvoice';
3 | import PayInvoice from './PayInvoice';
4 | import SendOnchain from './SendOnchain';
5 |
6 | export { CreateChainAddress, CreateInvoice, PayInvoice, SendOnchain };
7 |
--------------------------------------------------------------------------------
/src/client/app/Commands/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { ContainerStyle } from '../../standard_components/app-components';
4 | import React from 'react';
5 |
6 | /*
7 | Renders all commands on home page
8 | */
9 |
10 | const Commands = () => {
11 | return ;
12 | };
13 |
14 | export default Commands;
15 |
--------------------------------------------------------------------------------
/src/client/app/layout.tsx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Next.js',
3 | description: 'Generated by Next.js',
4 | }
5 |
6 | export default function RootLayout({
7 | children,
8 | }: {
9 | children: React.ReactNode
10 | }) {
11 | return (
12 |
13 |
{children}
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/server/modules/commands/commands.module.ts:
--------------------------------------------------------------------------------
1 | import { CommandsController } from './commands.controller';
2 | import { CommandsService } from './commands.service';
3 | import { Module } from '@nestjs/common';
4 |
5 | @Module({
6 | controllers: [CommandsController],
7 | providers: [CommandsService],
8 | })
9 | export class CommandsModule {}
10 |
--------------------------------------------------------------------------------
/src/server/modules/fees/fees.module.ts:
--------------------------------------------------------------------------------
1 | import { FeesController } from './fees.controller';
2 | import { FeesService } from './fees.service';
3 | import { Module } from '@nestjs/common';
4 |
5 | // Module for fees command
6 |
7 | @Module({
8 | controllers: [FeesController],
9 | providers: [FeesService],
10 | })
11 | export class FeesModule {}
12 |
--------------------------------------------------------------------------------
/src/server/modules/lnd/lnd.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, Module } from '@nestjs/common';
2 |
3 | import { LndService } from './lnd.service';
4 |
5 | // Lnd module: Global Module for the Authenticated LND API Object service
6 |
7 | @Global()
8 | @Module({
9 | providers: [LndService],
10 | exports: [LndService],
11 | })
12 | export class LndModule {}
13 |
--------------------------------------------------------------------------------
/src/server/modules/boslogger/boslogger.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, Module } from '@nestjs/common';
2 |
3 | import { BosloggerService } from './boslogger.service';
4 |
5 | // Global module for logger service
6 |
7 | @Global()
8 | @Module({
9 | providers: [BosloggerService],
10 | exports: [BosloggerService],
11 | })
12 | export class BosloggerModule {}
13 |
--------------------------------------------------------------------------------
/src/server/modules/socket/socket.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, Module } from '@nestjs/common';
2 |
3 | import { SocketGateway } from './socket.gateway';
4 |
5 | // Module for the NestJS Websockets
6 |
7 | @Global()
8 | @Module({
9 | controllers: [],
10 | providers: [SocketGateway],
11 | exports: [SocketGateway],
12 | })
13 | export class SocketModule {}
14 |
--------------------------------------------------------------------------------
/src/server/modules/view/view.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { ViewController } from '~server/modules/view/view.controller';
3 | import { ViewService } from '~server/modules/view/view.service';
4 |
5 | @Module({
6 | imports: [],
7 | providers: [ViewService],
8 | controllers: [ViewController],
9 | })
10 | export class ViewModule {}
11 |
--------------------------------------------------------------------------------
/src/server/settings/index.ts:
--------------------------------------------------------------------------------
1 | import checkAmbossHealthSetting from './check_amboss_health_setting';
2 | import checkScheduledRebalanceSetting from './check_scheduled_rebalance_setting';
3 | import getSettingsFile from './get_settings.file';
4 | import writeSettingsFile from './write_settings_file';
5 |
6 | export { checkAmbossHealthSetting, checkScheduledRebalanceSetting, getSettingsFile, writeSettingsFile };
7 |
--------------------------------------------------------------------------------
/tests/utils/spawn_lightning_cluster.ts:
--------------------------------------------------------------------------------
1 | import { spawnLightningCluster } from 'ln-docker-daemons';
2 |
3 | const spawnCluster = async (size: number) => {
4 | const { nodes } = await spawnLightningCluster({ size });
5 |
6 | console.log('============================Lightning Cluster Spawned===============================');
7 |
8 | return nodes;
9 | };
10 |
11 | export default spawnCluster;
12 |
--------------------------------------------------------------------------------
/docker/docker-compose-umbrel.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | lndboss:
3 | image: niteshbalusu/lndboss:latest
4 | volumes:
5 | - ~/.bosgui:/home/node/.bosgui
6 | - ~/umbrel/app-data/lightning/data/lnd:/home/node/.lnd
7 | ports:
8 | - '8055:8055'
9 | extra_hosts:
10 | - 'localhost:10.21.21.9'
11 | networks:
12 | default:
13 | external: true
14 | name: umbrel_main_network
15 |
--------------------------------------------------------------------------------
/src/server/modules/external-services/external-services.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, Module } from '@nestjs/common';
2 |
3 | import { ExternalServicesService } from './external-services.service';
4 |
5 | // Global module for external services
6 |
7 | @Global()
8 | @Module({
9 | providers: [ExternalServicesService],
10 | exports: [ExternalServicesService],
11 | })
12 | export class ExternalServicesModule {}
13 |
--------------------------------------------------------------------------------
/src/server/modules/rebalance/rebalance.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { RebalanceController } from './rebalance.controller';
3 | import { RebalanceService } from './rebalance.service';
4 |
5 | // Rebalance module: Module for the rebalance command
6 |
7 | @Module({
8 | controllers: [RebalanceController],
9 | providers: [RebalanceService],
10 | })
11 | export class RebalanceModule {}
12 |
--------------------------------------------------------------------------------
/src/client/hooks/useLoading.ts:
--------------------------------------------------------------------------------
1 | import Notiflix from 'notiflix';
2 |
3 | // Adds a loading indicator to pages when waiting for a response from the server
4 |
5 | type Args = {
6 | isLoading: boolean;
7 | };
8 |
9 | export const useLoading = ({ isLoading }: Args) => {
10 | if (!!isLoading) {
11 | Notiflix.Loading.dots('Loading...');
12 | }
13 |
14 | if (!isLoading) {
15 | Notiflix.Loading.remove();
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/server/commands/grpc_utils/icons.ts:
--------------------------------------------------------------------------------
1 | const icons = {
2 | balanced_open: '⚖️',
3 | block: '⏹',
4 | bot: '🤖',
5 | chain: '⛓',
6 | closing: '⏳',
7 | disconnected: '😵',
8 | earn: '💰',
9 | forwarding: '💸',
10 | info: 'ℹ️',
11 | liquidity: '🌊',
12 | opening: '⏳',
13 | probe: '👽',
14 | rebalance: '☯️',
15 | receive: '💵',
16 | spent: '⚡️',
17 | warning: '⚠️',
18 | };
19 |
20 | export default icons;
21 |
--------------------------------------------------------------------------------
/src/server/modules/credentials/credentials.module.ts:
--------------------------------------------------------------------------------
1 | import { CredentialsController } from './credentials.controller';
2 | import { CredentialsService } from './credentials.service';
3 | import { Module } from '@nestjs/common';
4 |
5 | // Credentials module: Module for the credentials service
6 |
7 | @Module({
8 | providers: [CredentialsService],
9 | controllers: [CredentialsController],
10 | })
11 | export class CredentialsModule {}
12 |
--------------------------------------------------------------------------------
/src/client/dashboard/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Typography from '@mui/material/Typography';
3 |
4 | // Renders the title of the dashboard.
5 |
6 | interface TitleProps {
7 | children?: React.ReactNode;
8 | }
9 |
10 | const Title = (props: TitleProps) => {
11 | return (
12 |
13 | {props.children}
14 |
15 | );
16 | };
17 |
18 | export default Title;
19 |
--------------------------------------------------------------------------------
/src/client/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const path = require('path');
3 | const dotenv = require('dotenv');
4 | const { homedir } = require('os');
5 |
6 | dotenv.config({ path: path.resolve(process.cwd(), '.env') });
7 | dotenv.config({ path: path.join(homedir(), '.bosgui', '.env') });
8 |
9 | module.exports = {
10 | reactStrictMode: true,
11 | swcMinify: true,
12 | experimental: {
13 | appDir: true,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/tests/utils/global-setup.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import { join } from 'path';
3 | import { startGlobalContainer } from './global_spawn_lightning';
4 |
5 | dotenv.config();
6 |
7 | // Alternatively, read from "../my.env" file.
8 | dotenv.config({ path: join(__dirname, '../../.env') });
9 |
10 | async function globalSetup() {
11 | await startGlobalContainer();
12 | console.log('======================Started Global Container======================');
13 | }
14 |
15 | export default globalSetup;
16 |
--------------------------------------------------------------------------------
/src/client/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { NextRouter } from 'next/router';
2 | import { RouteGuard } from '~client/standard_components/app-components';
3 |
4 | // First page that gets rendered before every page.
5 |
6 | type Props = {
7 | Component: React.ComponentType;
8 | pageProps: any;
9 | router: NextRouter;
10 | };
11 |
12 | const App = ({ Component, pageProps, router }: Props) => {
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/src/client/output/JoinGroupChannelOutput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /*
4 | Renders the output of the join-group-channel command.
5 | */
6 |
7 | type Args = {
8 | data: string | undefined;
9 | };
10 | const JoinGroupChannelOutput = ({ data }: Args) => {
11 | return (
12 |
13 | {!!data &&
{data}}
14 |
15 | );
16 | };
17 |
18 | export default JoinGroupChannelOutput;
19 |
20 | const styles = {
21 | div: {
22 | width: '1100px',
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "declaration": true,
6 | "removeComments": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "allowSyntheticDefaultImports": true,
10 | "target": "es2017",
11 | "sourceMap": true,
12 | "outDir": "./dist",
13 | "incremental": true,
14 | "jsx": "preserve",
15 | "resolveJsonModule": true
16 | },
17 | "include": ["./src/server/**/*.ts", "./src/shared/**/*.ts"]
18 | }
19 |
--------------------------------------------------------------------------------
/src/client/output/CreateGroupChannelOutput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /*
4 | Renders the output of the create-group-channel command.
5 | */
6 |
7 | type Args = {
8 | data: string | undefined;
9 | };
10 | const CreateGroupChannelOutput = ({ data }: Args) => {
11 | return (
12 |
13 | {!!data &&
{data}}
14 |
15 | );
16 | };
17 |
18 | export default CreateGroupChannelOutput;
19 |
20 | const styles = {
21 | div: {
22 | width: '1100px',
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/src/client/output/FeesOutput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StandardTableOutput } from '~client/standard_components/app-components';
3 |
4 | // Renders the output of the bos fees command
5 |
6 | type Props = {
7 | data: {
8 | rows: string[][];
9 | };
10 | };
11 |
12 | const FeesOutput = ({ data }: Props) => {
13 | return (
14 |
15 | {!!data ? :
No Output to display
}
16 |
17 | );
18 | };
19 |
20 | export default FeesOutput;
21 |
--------------------------------------------------------------------------------
/src/server/lnd/index.ts:
--------------------------------------------------------------------------------
1 | import authenticatedLnd from './authenticated_lnd';
2 | import checkConnection from './check_connection';
3 | import getLnds from './get_lnds';
4 | import getSavedCredentials from './get_saved_credentials';
5 | import getSavedNodes from './get_saved_nodes';
6 | import lndCredentials from './lnd_credentials';
7 | import putSavedCredentials from './put_saved_credentials';
8 |
9 | export {
10 | authenticatedLnd,
11 | checkConnection,
12 | getLnds,
13 | getSavedCredentials,
14 | getSavedNodes,
15 | lndCredentials,
16 | putSavedCredentials,
17 | };
18 |
--------------------------------------------------------------------------------
/src/server/modules/view/view.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Req, Res } from '@nestjs/common';
2 | import { Request, Response } from 'express';
3 | import { ViewService } from '~server/modules/view/view.service';
4 | import { Public } from '../../utils/constants';
5 |
6 | @Public()
7 | @Controller('/')
8 | export class ViewController {
9 | constructor(private viewService: ViewService) {}
10 |
11 | @Get('*')
12 | static(@Req() req: Request, @Res() res: Response) {
13 | const handle = this.viewService.getNextServer().getRequestHandler();
14 | handle(req, res);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/client/output/CallOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 |
5 | // Renders output of bos call command
6 |
7 | const styles = {
8 | pre: {
9 | fontWeight: 'bold',
10 | },
11 | };
12 |
13 | type Args = {
14 | data: any[];
15 | };
16 |
17 | const CallOutput = ({ data }: Args) => {
18 | const output = YAML.stringify(data);
19 | return (
20 |
21 | {Object.keys(data).length ?
{output} :
No data found
}
22 |
23 | );
24 | };
25 |
26 | export default CallOutput;
27 |
--------------------------------------------------------------------------------
/src/client/output/FindOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 |
5 | // Renders output of bos find command
6 |
7 | const styles = {
8 | pre: {
9 | fontWeight: 'bold',
10 | },
11 | };
12 |
13 | type Args = {
14 | data: any[];
15 | };
16 |
17 | const FindOutput = ({ data }: Args) => {
18 | const output = YAML.stringify(data);
19 | return (
20 |
21 | {Object.keys(data).length ?
{output} :
No data found
}
22 |
23 | );
24 | };
25 |
26 | export default FindOutput;
27 |
--------------------------------------------------------------------------------
/src/server/modules/credentials/credentials.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Post } from '@nestjs/common';
2 | import { credentialsDto } from '~shared/commands.dto';
3 | import { CredentialsService } from './credentials.service';
4 |
5 | // Credentials Controller: Handles routes to the credentials service
6 |
7 | @Controller('api/credentials')
8 | export class CredentialsController {
9 | constructor(private credentialsService: CredentialsService) {}
10 |
11 | @Post()
12 | async credentials(@Body() args: credentialsDto) {
13 | return await this.credentialsService.post(args);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/client/hooks/usePasswordValidation.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | // Hook for validatating password strength
4 |
5 | export const usePasswordValidation = ({ firstPassword = '', secondPassword = '', requiredLength }) => {
6 | const [validLength, setValidLength] = useState(false);
7 | const [match, setMatch] = useState(false);
8 |
9 | useEffect(() => {
10 | setValidLength(firstPassword.length >= requiredLength);
11 | setMatch(!!firstPassword && firstPassword === secondPassword);
12 | }, [firstPassword, secondPassword, requiredLength]);
13 |
14 | return [validLength, match];
15 | };
16 |
--------------------------------------------------------------------------------
/src/client/output/ReconnectOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 |
5 | // Renders output of bos reconnect command
6 |
7 | const styles = {
8 | pre: {
9 | fontWeight: 'bold',
10 | },
11 | };
12 |
13 | type Args = {
14 | data: any[];
15 | };
16 |
17 | const ReconnectOutput = ({ data }: Args) => {
18 | const output = YAML.stringify(data);
19 | return (
20 |
21 | {Object.keys(data).length ?
{output} :
No data found
}
22 |
23 | );
24 | };
25 |
26 | export default ReconnectOutput;
27 |
--------------------------------------------------------------------------------
/src/client/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import { StandardHomeButtonLink, StartFlexBox } from '../standard_components/app-components';
2 |
3 | import { CssBaseline } from '@mui/material';
4 | import React from 'react';
5 |
6 | // Standard 404 page not found page to be used for all 404 errors
7 |
8 | const styles = {
9 | h1: {
10 | marginTop: '100px',
11 | },
12 | };
13 |
14 | export default function Custom404() {
15 | return (
16 |
17 |
18 |
19 | 404 - Page Not Found
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/client/pages/500.tsx:
--------------------------------------------------------------------------------
1 | import { StandardHomeButtonLink, StartFlexBox } from '../standard_components/app-components';
2 |
3 | import { CssBaseline } from '@mui/material';
4 | import React from 'react';
5 |
6 | // Standard 500 error page to be used for all 500 errors
7 |
8 | const styles = {
9 | h1: {
10 | marginTop: '100px',
11 | },
12 | };
13 |
14 | export default function Custom500() {
15 | return (
16 |
17 |
18 |
19 | 500 - Server-side error occurred
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/client/output/CertValidityDaysOutput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // Render the output of the CertValidityDays command.
4 |
5 | const styles = {
6 | div: {
7 | marginTop: '100px',
8 | marginLeft: '10px',
9 | },
10 | text: {
11 | fontSize: '15px',
12 | fontWeight: 'bold',
13 | },
14 | };
15 |
16 | type Data = {
17 | data: string;
18 | };
19 |
20 | const CertValidityDaysOutput = ({ data }: Data) => {
21 | return (
22 |
23 |
Remaining number of days of certificate validity: {data}
24 |
25 | );
26 | };
27 |
28 | export default CertValidityDaysOutput;
29 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/StandardSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { Switch, alpha, styled } from '@mui/material';
2 | import { blue, green } from '@mui/material/colors';
3 |
4 | /*
5 | Renders the standard ios style switch used in command forms.
6 | */
7 |
8 | const StandardSwitch = styled(Switch)(({ theme }) => ({
9 | '& .MuiSwitch-switchBase.Mui-checked': {
10 | color: blue[600],
11 | '&:hover': {
12 | backgroundColor: alpha(green[500], theme.palette.action.hoverOpacity),
13 | },
14 | },
15 | '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
16 | backgroundColor: green[500],
17 | },
18 | }));
19 |
20 | export default StandardSwitch;
21 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/ContainerStyle.tsx:
--------------------------------------------------------------------------------
1 | import { PositionedMenu, ResponsiveGrid } from './index';
2 |
3 | import CenterFlexBox from './CenterFlexBox';
4 | import CssBaseline from '@mui/material/CssBaseline';
5 | import React from 'react';
6 | import commands from '../../commands';
7 |
8 | /*
9 | Renders the login button and the commands grid on the home page.
10 | */
11 |
12 | const ContainerStyle = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default ContainerStyle;
24 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/CenterFlexBox.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/material';
2 | import React from 'react';
3 |
4 | /*
5 | children: Renders the children passed into the center flex box.
6 | */
7 |
8 | type Props = {
9 | children: React.PropsWithChildren<{ unknown: any }>['children'];
10 | };
11 |
12 | const CenterFlexBox = ({ children }: Props) => {
13 | return (
14 |
22 | {children}
23 |
24 | );
25 | };
26 |
27 | export default CenterFlexBox;
28 |
--------------------------------------------------------------------------------
/tests/server/certValidityDays.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import { certValidityDaysCommand } from '../../src/server/commands/';
4 |
5 | test.describe('Test CertValidityDays command on the node.js side', async () => {
6 | test.beforeAll(async () => {
7 | // Do nothing
8 | });
9 |
10 | test('run ChainDeposit command', async () => {
11 | const args = {
12 | below: 1000,
13 | node: 'testnode1',
14 | };
15 |
16 | const { result } = await certValidityDaysCommand({ below: args.below, node: args.node });
17 | console.log('certValidityDays----', result);
18 |
19 | expect(result).toBeTruthy();
20 | });
21 |
22 | test.afterAll(async () => {
23 | // Do nothing
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/StandardRouterLink.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import React from 'react';
3 |
4 | /*
5 | {
6 | label:
7 | destination:
8 | }
9 | Returns the standard link
10 | */
11 |
12 | const styles = {
13 | link: {
14 | fontSize: '20px',
15 | margin: '0px',
16 | cursor: 'pointer',
17 | color: 'white',
18 | },
19 | };
20 |
21 | type Props = {
22 | label: string;
23 | destination: string;
24 | };
25 |
26 | const StandardRouterLink = ({ label, destination }: Props) => {
27 | return (
28 |
29 | {label}
30 |
31 | );
32 | };
33 | export default StandardRouterLink;
34 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/SubmitButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button, ButtonProps, styled } from '@mui/material';
2 |
3 | import React from 'react';
4 | import { grey } from '@mui/material/colors';
5 |
6 | /*
7 | Renders the standard submit button used in command forms.
8 | */
9 |
10 | const ColorButton = styled(Button)(({ theme }) => ({
11 | color: theme.palette.getContrastText(grey[900]),
12 | backgroundColor: grey[900],
13 | '&:hover': {
14 | backgroundColor: grey[800],
15 | },
16 | marginTop: '30px',
17 | fontWeight: 'bold',
18 | width: '250px',
19 | }));
20 |
21 | const SubmitButton = (props: ButtonProps) => {
22 | return {props.children};
23 | };
24 |
25 | export default SubmitButton;
26 |
--------------------------------------------------------------------------------
/src/server/modules/fees/fees.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Post } from '@nestjs/common';
2 | import { feesDto, feesStrategiesDto } from '~shared/commands.dto';
3 | import { FeesService } from './fees.service';
4 |
5 | // Fees controller: Defines routes for fees command
6 |
7 | @Controller()
8 | export class FeesController {
9 | constructor(private feeService: FeesService) {}
10 |
11 | @Post('api/fees')
12 | async fees(@Body() args: feesDto) {
13 | return this.feeService.feesCommand(args);
14 | }
15 |
16 | @Post('api/fees/save-strategies')
17 | async save(@Body() args: feesStrategiesDto) {
18 | return this.feeService.save(args);
19 | }
20 |
21 | @Post('api/fees/getfile')
22 | async getFile() {
23 | return this.feeService.readFeesFile();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app-stores/umbrel/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | app_proxy:
5 | environment:
6 | APP_HOST: $APP_LNDBOSS_IP
7 | APP_PORT: $APP_LNDBOSS_PORT
8 | web:
9 | image: niteshbalusu/lndboss:v1.15.2@sha256:c6fecdb6a4a0c1960d6ae22eba9ad73abaed45224d3c3cfd3d6f1d04f52a1c36
10 | restart: on-failure
11 | stop_grace_period: 1m
12 | volumes:
13 | - ${APP_LIGHTNING_NODE_DATA_DIR}:/home/node/.lnd:ro
14 | - ${APP_DATA_DIR}/.bosgui:/home/node/.bosgui
15 | environment:
16 | BOS_DATA_PATH: '/home/node/.bosgui'
17 | NODE_ENV: 'production'
18 | PORT: $APP_LNDBOSS_PORT
19 | BOS_DEFAULT_LND_SOCKET: $APP_LIGHTNING_NODE_IP:$APP_LIGHTNING_NODE_GRPC_PORT
20 | networks:
21 | default:
22 | ipv4_address: $APP_LNDBOSS_IP
23 |
--------------------------------------------------------------------------------
/src/client/output/GraphOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 | import { StandardTableOutput } from '~client/standard_components/app-components';
5 |
6 | // Renders the output of the bos graph command
7 |
8 | type Props = {
9 | data: string[][];
10 | summary: object;
11 | };
12 |
13 | const styles = {
14 | pre: {
15 | fontWeight: 'bold',
16 | },
17 | };
18 | const GraphOutput = ({ data, summary }: Props) => {
19 | const output = YAML.stringify(summary);
20 |
21 | return (
22 |
23 |
{output}
24 | {!!data ?
:
No Output to display
}
25 |
26 | );
27 | };
28 |
29 | export default GraphOutput;
30 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/CopyText.tsx:
--------------------------------------------------------------------------------
1 | import ContentCopyIcon from '@mui/icons-material/ContentCopy';
2 | import { CopyToClipboard } from 'react-copy-to-clipboard';
3 | import IconButton from '@mui/material/IconButton';
4 | import React from 'react';
5 | import { useNotify } from '~client/hooks/useNotify';
6 |
7 | // Renders the button to copy the text to the clipboard
8 |
9 | type Args = {
10 | text: string;
11 | };
12 | const CopyText = ({ text }: Args) => {
13 | return (
14 | useNotify({ type: 'success', message: 'Copied to clipboard' })}>
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default CopyText;
23 |
--------------------------------------------------------------------------------
/tests/server/price.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import { getPrices } from '@alexbosworth/fiat';
4 | import request from 'balanceofsatoshis/commands/simple_request';
5 |
6 | test.describe('Test Price command on the node.js side', async () => {
7 | test.beforeAll(async () => {
8 | // Do nothing
9 | });
10 |
11 | test('run Price command', async () => {
12 | const args = {
13 | file: false,
14 | from: 'coinbase',
15 | };
16 | const result = await getPrices({
17 | request,
18 | symbols: ['USD', 'AUD'],
19 | from: args.from,
20 | });
21 |
22 | console.log('price----', result);
23 | expect(result.tickers).toBeTruthy();
24 | });
25 |
26 | test.afterAll(async () => {
27 | // Do nothing
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/server/reconnect.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { reconnectCommand } from '../../src/server/commands/';
5 |
6 | test.describe('Test Reconnect command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run Reconnect command', async () => {
14 | const { result } = await reconnectCommand({ lnd: lightning.lnd });
15 |
16 | console.log('reconnect----', result);
17 |
18 | expect(result).toBeTruthy();
19 | });
20 |
21 | test.afterAll(async () => {
22 | await lightning.kill({});
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/app-stores/umbrel/umbrel-app.yml:
--------------------------------------------------------------------------------
1 | manifestVersion: 1
2 | id: lndboss
3 | category: Lightning Node Management
4 | name: LndBoss
5 | version: '1.15.2'
6 | tagline: A GUI for BalanceOfSatoshis
7 | description: LndBoss is a GUI for BalanceOfSatoshis.
8 | It is a tool that makes it easy to run your favorite
9 | bos commands and helps manage your lightning node.
10 | You can schedule jobs to automatically rebalance channels,
11 | integration with amboss to post updates and much more.
12 | developer: Nitesh Balusu
13 | website: https://github.com/niteshbalusu11
14 | dependencies:
15 | - lightning
16 | repo: https://github.com/niteshbalusu11/lndboss
17 | support: https://t.me/lndboss
18 | port: 8055
19 | gallery:
20 | - 1.jpg
21 | - 2.jpg
22 | - 3.jpg
23 | path: ''
24 | defaultUsername: ''
25 | defaultPassword: ''
26 |
--------------------------------------------------------------------------------
/tests/server/find.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { findCommand } from '../../src/server/commands/';
5 |
6 | test.describe('Test Find command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run Find command', async () => {
14 | const args = {
15 | query: 'alice',
16 | };
17 | const { result } = await findCommand({ args, lnd: lightning.lnd });
18 | console.log('find----', result);
19 | expect(result).toBeTruthy();
20 | });
21 |
22 | test.afterAll(async () => {
23 | await lightning.kill({});
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/client/utils/validations/validate_join_channel_group_command.ts:
--------------------------------------------------------------------------------
1 | const isCode = n => !!n && n.length === 98;
2 | const isNumber = (n: number) => !isNaN(n);
3 |
4 | /** Validate join group channel body
5 | {
6 | code:
7 | max_rate:
8 | }
9 |
10 | @returns boolean
11 | */
12 |
13 | type Args = {
14 | code: string;
15 | max_rate: number;
16 | };
17 | const validateJoinGroupChannelCommand = (args: Args) => {
18 | if (!args.code || !isCode(args.code)) {
19 | throw new Error('Expected Valid Invite Code To Join Group Channel');
20 | }
21 |
22 | if (!args.max_rate || !isNumber(args.max_rate)) {
23 | throw new Error('Expected Numeric Max Fee Rate To Join Group Channel');
24 | }
25 |
26 | return true;
27 | };
28 |
29 | export default validateJoinGroupChannelCommand;
30 |
--------------------------------------------------------------------------------
/src/server/commands/cleanFailedPayments/clean_failed_payments_command.ts:
--------------------------------------------------------------------------------
1 | import { cleanFailedPayments } from 'balanceofsatoshis/wallets';
2 |
3 | /** Clean out failed payments from the wallet
4 |
5 | {
6 | is_dry_run:
7 | lnd:
8 | logger:
9 | }
10 |
11 | @returns via Promise
12 | {
13 | [total_failed_payments_found]:
14 | [total_failed_payments_deleted]:
15 | }
16 | */
17 |
18 | const cleanFailedPaymentsCommand = async ({ args, lnd, logger }) => {
19 | const result = await cleanFailedPayments({
20 | lnd,
21 | logger,
22 | is_dry_run: args.is_dry_run,
23 | });
24 |
25 | return { result };
26 | };
27 |
28 | export default cleanFailedPaymentsCommand;
29 |
--------------------------------------------------------------------------------
/tests/server/closed.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { closedCommand } from '../../src/server/commands';
5 |
6 | test.describe('Test Closed command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run closed command', async () => {
14 | const args = {
15 | limit: 1,
16 | };
17 | const { result } = await closedCommand({ args, lnd: lightning.lnd });
18 | console.log('closed----', result);
19 | expect(result.closes).toBeTruthy();
20 | });
21 |
22 | test.afterAll(async () => {
23 | await lightning.kill({});
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 | .next
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 | /test-results/
21 | /playwright-report/
22 | /playwright/.cache/
23 |
24 | # IDEs and editors
25 | /.idea
26 | .project
27 | .classpath
28 | .c9/
29 | *.launch
30 | .settings/
31 | *.sublime-workspace
32 |
33 | # IDE - VSCode
34 | .vscode/*
35 | !.vscode/settings.json
36 | !.vscode/tasks.json
37 | !.vscode/launch.json
38 | !.vscode/extensions.json
39 | /test-results/
40 | /playwright-report/
41 | /playwright/.cache/
42 |
43 | # Env files
44 | .env
45 | .env.local
46 |
47 | # Docker
48 | lndboss.tar
49 | lndboss.tar.gz
50 | /test-results/
51 | /playwright-report/
52 | /playwright/.cache/
53 |
--------------------------------------------------------------------------------
/src/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.paths.json",
3 | "plugins": [{ "name": "next" }],
4 | "compilerOptions": {
5 | "plugins": [{ "name": "next" }],
6 | "target": "es5",
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": false,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "strictNullChecks": false
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js", "shared/**/*.*", ".next/types/**/*.ts"],
23 | "exclude": ["node_modules", ".next", "server/**/*.*"]
24 | }
25 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request Checklist
2 |
3 | ## Description
4 |
5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
6 |
7 | Fixes # (issue)
8 |
9 | ## Type of change
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 | - [ ] This change bumps dependencies
16 |
17 | ## Checklist if applicable:
18 |
19 | - [ ] Appropriate comments have been added along with code?
20 | - [ ] Test case has been added/updated?
21 | - [ ] ReadMe has been updated?
22 | - [ ] Ran test cases?
23 |
--------------------------------------------------------------------------------
/src/server/modules/auth/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { ExtractJwt, Strategy } from 'passport-jwt';
2 |
3 | import { Injectable } from '@nestjs/common';
4 | import { PassportStrategy } from '@nestjs/passport';
5 | import { jwtConstants } from '../../utils/constants';
6 |
7 | type Payload = {
8 | username: string;
9 | sub: string;
10 | iat: number;
11 | exp: number;
12 | };
13 |
14 | // Local strategy for JWT, validate the user's credentials
15 |
16 | @Injectable()
17 | export class JwtStrategy extends PassportStrategy(Strategy) {
18 | constructor() {
19 | super({
20 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
21 | ignoreExpiration: false,
22 | secretOrKey: jwtConstants.secret,
23 | });
24 | }
25 |
26 | async validate(payload: Payload) {
27 | return { userId: payload.sub, username: payload.username };
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/shared/cast.helper.ts:
--------------------------------------------------------------------------------
1 | export function trim(value: string): string {
2 | return value.trim();
3 | }
4 |
5 | export function toDate(value: string): Date {
6 | return new Date(value);
7 | }
8 |
9 | export function toBoolean(value: string): boolean {
10 | value = value.toLowerCase();
11 |
12 | return !!(value === 'true' || value === '1');
13 | }
14 |
15 | export function toNumber(value: string): number {
16 | const newValue: number = Number.parseInt(value, 10);
17 |
18 | return newValue;
19 | }
20 |
21 | export function toStringArray(value: string | string[] | undefined): string[] {
22 | const { isArray } = Array;
23 | const isString = n => typeof n === 'string';
24 | if (!value) {
25 | return [];
26 | }
27 |
28 | if (!!isArray(value)) {
29 | return value;
30 | }
31 |
32 | if (!!isString(value)) {
33 | return [value];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/utils/spawn_lightning_server.ts:
--------------------------------------------------------------------------------
1 | import { AuthenticatedLnd, getIdentity } from 'lightning';
2 |
3 | import { spawnLightningCluster } from 'ln-docker-daemons';
4 |
5 | export type SpawnLightningServerType = {
6 | lnd: AuthenticatedLnd;
7 | kill: ({}) => Promise;
8 | };
9 |
10 | const spawnLightningServer = async (): Promise => {
11 | // Launch a lightning node
12 | const { nodes } = await spawnLightningCluster({});
13 | const [{ lnd, generate, kill }] = nodes;
14 |
15 | await generate({ count: 5 });
16 |
17 | const publicKey = (await getIdentity({ lnd })).public_key;
18 |
19 | if (!!publicKey) {
20 | console.log('============================Lightning Server Spawned===============================');
21 | }
22 |
23 | // Stop the image
24 | return { lnd, kill };
25 | };
26 |
27 | export { spawnLightningServer };
28 |
--------------------------------------------------------------------------------
/src/server/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Post, UseGuards, Body } from '@nestjs/common';
2 | import { LocalAuthGuard } from '~server/modules/auth/local-auth.guard';
3 | import { AuthService } from '~server/modules/auth/auth.service';
4 | import { authenticationDto } from '~shared/commands.dto';
5 | import { Public } from './utils/constants';
6 |
7 | // App Controller: Handles routes to the auth service
8 |
9 | @Controller()
10 | export class AppController {
11 | constructor(private authService: AuthService) {}
12 |
13 | @Public()
14 | @UseGuards(LocalAuthGuard)
15 | @Post('api/auth/login')
16 | async login(@Body() body: authenticationDto) {
17 | return this.authService.login(body);
18 | }
19 |
20 | @Public()
21 | @Post('api/auth/register')
22 | async register(@Body() body: authenticationDto) {
23 | return this.authService.registerUser(body);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/server/modules/view/view.service.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-duplicates */
2 | import { Injectable, OnModuleInit } from '@nestjs/common';
3 |
4 | import { ConfigService } from '@nestjs/config';
5 | import { NextServer } from 'next/dist/server/next';
6 | import createServer from 'next';
7 |
8 | @Injectable()
9 | export class ViewService implements OnModuleInit {
10 | private server: NextServer;
11 |
12 | constructor(private configService: ConfigService) {}
13 |
14 | async onModuleInit(): Promise {
15 | try {
16 | this.server = createServer({
17 | dev: this.configService.get('NODE_ENV') !== 'production',
18 | dir: './src/client',
19 | });
20 | await this.server.prepare();
21 | } catch (error) {
22 | console.error(error);
23 | }
24 | }
25 |
26 | getNextServer(): NextServer {
27 | return this.server;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/StartFlexBox.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/material';
2 | import React from 'react';
3 |
4 | /*
5 | children: Renders the children passed into the start flex box.
6 | This flexbox is used for command forms that align to the left.
7 | */
8 |
9 | type Props = {
10 | children: React.PropsWithChildren<{ unknown }>['children'];
11 | };
12 |
13 | const StartFlexBox = ({ children }: Props) => {
14 | return (
15 |
26 | {children}
27 |
28 | );
29 | };
30 |
31 | export default StartFlexBox;
32 |
--------------------------------------------------------------------------------
/src/server/modules/auth/jwt-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, Injectable } from '@nestjs/common';
2 |
3 | import { AuthGuard } from '@nestjs/passport';
4 | import { IS_PUBLIC_KEY } from '../../utils/constants';
5 | import { Reflector } from '@nestjs/core';
6 |
7 | /*
8 | Add a local auth guard to check if the user is logged in before returning a JWT
9 | Can Activate is used to set @Public() on certain routes
10 | */
11 |
12 | @Injectable()
13 | export class JwtAuthGuard extends AuthGuard('jwt') {
14 | constructor(private reflector: Reflector) {
15 | super();
16 | }
17 |
18 | canActivate(context: ExecutionContext) {
19 | const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
20 | context.getHandler(),
21 | context.getClass(),
22 | ]);
23 | if (isPublic) {
24 | return true;
25 | }
26 | return super.canActivate(context);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/server/commands/decrypt/decrypt_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { decryptWithNode } from 'balanceofsatoshis/encryption';
5 |
6 | /** Decrypt data from node
7 |
8 | {
9 | encrypted:
10 | lnd:
11 | }
12 |
13 | @returns via Promise
14 | {
15 | message:
16 | with_alias:
17 | with_public_key:
18 | }
19 | */
20 |
21 | type Args = {
22 | args: types.commandDecrypt;
23 | lnd: AuthenticatedLnd;
24 | };
25 | const decryptCommand = async ({ args, lnd }: Args): Promise<{ result: any }> => {
26 | const result = await decryptWithNode({
27 | lnd,
28 | encrypted: args.encrypted,
29 | });
30 |
31 | return { result };
32 | };
33 |
34 | export default decryptCommand;
35 |
--------------------------------------------------------------------------------
/src/server/modules/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { isProduction, jwtConstants } from '../../utils/constants';
2 |
3 | import { AuthService } from './auth.service';
4 | import { JwtModule } from '@nestjs/jwt';
5 | import { JwtStrategy } from './jwt.strategy';
6 | import { LocalStrategy } from './local.strategy';
7 | import { Module } from '@nestjs/common';
8 | import { PassportModule } from '@nestjs/passport';
9 | import { UsersModule } from '../users/users.module';
10 |
11 | // AuthModule: Module for the authentication service
12 |
13 | @Module({
14 | imports: [
15 | UsersModule,
16 | PassportModule,
17 | JwtModule.register({
18 | secret: jwtConstants.secret,
19 | signOptions: { expiresIn: !!isProduction ? process.env.SESSION_DURATION || '40m' : '1h' },
20 | }),
21 | ],
22 | providers: [AuthService, LocalStrategy, JwtStrategy],
23 | exports: [AuthService],
24 | })
25 | export class AuthModule {}
26 |
--------------------------------------------------------------------------------
/src/server/commands/encrypt/encrypt_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { encryptToNode } from 'balanceofsatoshis/encryption';
5 |
6 | /** Encrypt data to a node
7 |
8 | {
9 | lnd:
10 | message:
11 | [to]:
12 | }
13 |
14 | @returns via Promise
15 | {
16 | encrypted:
17 | to:
18 | }
19 | */
20 | type Args = {
21 | args: types.commandEncrypt;
22 | lnd: AuthenticatedLnd;
23 | };
24 |
25 | const encryptCommand = async ({ args, lnd }: Args): Promise<{ result: any }> => {
26 | const result = await encryptToNode({
27 | lnd,
28 | message: args.message,
29 | to: args.to,
30 | });
31 |
32 | return { result };
33 | };
34 |
35 | export default encryptCommand;
36 |
--------------------------------------------------------------------------------
/src/server/modules/auth/local.strategy.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, Injectable } from '@nestjs/common';
2 |
3 | import { AuthService } from './auth.service';
4 | import { PassportStrategy } from '@nestjs/passport';
5 | import { Strategy } from 'passport-local';
6 |
7 | /*
8 | Implementation of the local strategy, validate the user's credentials
9 | Call the auth service to validate the user's credentials
10 | If the user is valid, return the user's JWT
11 | */
12 |
13 | @Injectable()
14 | export class LocalStrategy extends PassportStrategy(Strategy) {
15 | constructor(private authService: AuthService) {
16 | super();
17 | }
18 |
19 | async validate(username: string, password: string): Promise {
20 | const user = await this.authService.validateUser(username, password);
21 | if (!user) {
22 | throw new HttpException('InvalidUsernameOrPassword', 401);
23 | }
24 | return user;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/server/chainfees.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { chainfeesCommand } from '../../src/server/commands/';
5 |
6 | test.describe('Test Chainfees command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run Chainfees command', async () => {
14 | const args = {
15 | blocks: 6,
16 | };
17 | const { result } = await chainfeesCommand({ args, lnd: lightning.lnd });
18 | console.log('chain fees----', result);
19 | expect(result.current_block_hash).toBeTruthy();
20 | expect(result.fee_by_block_target).toBeTruthy();
21 | });
22 |
23 | test.afterAll(async () => {
24 | await lightning.kill({});
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/server/utxos.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { utxosCommand } from '../../src/server/commands';
5 |
6 | test.describe('Test Utxos command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run forwards command', async () => {
14 | const args = {
15 | count_below: 0,
16 | is_confirmed: false,
17 | is_count: false,
18 | min_tokens: 0,
19 | };
20 |
21 | const { result } = await utxosCommand({ args, lnd: lightning.lnd });
22 | console.log('utxos----', result);
23 | expect(result.utxos).toBeTruthy();
24 | });
25 |
26 | test.afterAll(async () => {
27 | await lightning.kill({});
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/server/modules/credentials/credentials.service.ts:
--------------------------------------------------------------------------------
1 | import { checkConnection, putSavedCredentials } from '~server/lnd';
2 |
3 | import { Injectable } from '@nestjs/common';
4 | import { credentialsDto } from '~shared/commands.dto';
5 |
6 | /** Credentials Service: Handles routes to the credentials service
7 | {
8 | cert:
9 | is_default:
10 | macaroon:
11 | node:
12 | socket:
13 | }
14 | @returns via Promise
15 | {
16 | connection:
17 | error:
18 | result:
19 | */
20 |
21 | @Injectable()
22 | export class CredentialsService {
23 | async post(args: credentialsDto) {
24 | const { result } = await putSavedCredentials(args);
25 | const connection = await checkConnection({ node: args.node });
26 | return { connection, result };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/server/commands/grpc_utils/format_tokens.ts:
--------------------------------------------------------------------------------
1 | const fullTokensType = 'full';
2 | const isString = (n: any) => typeof n === 'string';
3 | const tokensAsBigUnit = (tokens: number) => (tokens / 1e8).toFixed(8);
4 |
5 | /** Format tokens for display
6 | {
7 | [none]:
8 | tokens:
9 | }
10 | @returns
11 | {
12 | display:
13 | }
14 | */
15 |
16 | type Args = {
17 | none?: string;
18 | tokens: number;
19 | };
20 | const formatTokens = ({ none, tokens }: Args) => {
21 | if (isString(none) && !tokens) {
22 | return { display: none };
23 | }
24 |
25 | // Exit early for tokens environment displays the value with no leading zero
26 | if (process.env.PREFERRED_TOKENS_TYPE === fullTokensType) {
27 | return { display: tokens.toLocaleString() };
28 | }
29 |
30 | return { display: tokensAsBigUnit(tokens) };
31 | };
32 |
33 | export default formatTokens;
34 |
--------------------------------------------------------------------------------
/tests/server/forwards.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { forwardsCommand } from '../../src/server/commands';
5 |
6 | test.describe('Test Forwards command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run forwards command', async () => {
14 | const args = {
15 | days: 4,
16 | from: 'alice',
17 | sort: 'earned_in',
18 | tags: [],
19 | to: 'bob',
20 | };
21 | const { result } = await forwardsCommand({ args, lnd: lightning.lnd });
22 | console.log('forwards----', result);
23 | expect(result.peers).toBeTruthy();
24 | });
25 |
26 | test.afterAll(async () => {
27 | await lightning.kill({});
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/client/output/EncryptOutput.tsx:
--------------------------------------------------------------------------------
1 | import { CopyText } from '~client/standard_components/app-components';
2 | import React from 'react';
3 |
4 | const substring = n => n.slice(0, 20) + '......' + n.slice(-20);
5 |
6 | /*
7 | Renders the output of the Encrypt command.
8 | */
9 |
10 | type Args = {
11 | data: {
12 | encrypted: string;
13 | to: string;
14 | };
15 | };
16 | const EncryptOutput = ({ data }: Args) => {
17 | return (
18 |
19 |
{`Encrypted: ${substring(data.encrypted)}`}
20 |
21 |
{`To: ${data.to}`}
22 |
23 | );
24 | };
25 |
26 | export default EncryptOutput;
27 |
28 | const styles = {
29 | div: {
30 | marginTop: '100px',
31 | marginLeft: '10px',
32 | },
33 | text: {
34 | fontSize: '15px',
35 | fontWeight: 'bold',
36 | display: 'inline-block',
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/tests/server/chainDeposit.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { chainDepositCommand } from '../../src/server/commands/';
5 |
6 | test.describe('Test ChainDeposit command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run ChainDeposit command', async () => {
14 | const args = {
15 | amount: 1000,
16 | format: 'p2wpkh',
17 | };
18 | const { result } = await chainDepositCommand({ args, lnd: lightning.lnd });
19 | console.log('chain deposit----', result);
20 | expect(result.address).toBeTruthy();
21 | expect(result.url).toBeTruthy();
22 | });
23 |
24 | test.afterAll(async () => {
25 | await lightning.kill({});
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/server/authentication/getAccountInfo.ts:
--------------------------------------------------------------------------------
1 | import { auto } from 'async';
2 | import { homedir } from 'os';
3 | import { join } from 'path';
4 | import { readFile } from 'fs';
5 |
6 | const auth = 'auth.json';
7 | const home = '.bosgui';
8 |
9 | /** Read account info from auth.json
10 |
11 | @returns via Promise
12 | {
13 | result:
14 | }
15 | */
16 |
17 | type Tasks = {
18 | readFile: any;
19 | };
20 |
21 | const getAccountInfo = async (): Promise => {
22 | const result = await auto({
23 | readFile: (cbk: any) => {
24 | const path = join(...[homedir(), home, auth]);
25 |
26 | return readFile(path, (err: any, data: any) => {
27 | // Ignore errors, the file may not exist
28 | if (!!err) {
29 | return cbk();
30 | }
31 |
32 | return cbk(null, data);
33 | });
34 | },
35 | });
36 |
37 | return result.readFile;
38 | };
39 |
40 | export default getAccountInfo;
41 |
--------------------------------------------------------------------------------
/tests/client/fees.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import { setCookie } from '../utils/setAccessToken';
4 | import { testConstants } from '../utils/constants';
5 |
6 | test.describe('Test the Fees command client page', async () => {
7 | test.beforeEach(async ({ page }) => {
8 | await setCookie({ page });
9 | });
10 |
11 | test('test the Fees command page and input values', async ({ page }) => {
12 | await page.goto(testConstants.commandsPage);
13 | await page.click('#Fees');
14 | await expect(page).toHaveTitle('Fees');
15 |
16 | await page.type('#node', 'testnode1');
17 |
18 | await page.click('text=run command');
19 | await page.waitForTimeout(1000);
20 |
21 | await expect(page.locator('#feesOutput')).toBeVisible();
22 | await page.click('text=home');
23 | });
24 |
25 | test.afterEach(async ({ page }) => {
26 | await page.context().clearCookies();
27 | await page.close();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/utils/global_spawn_lightning.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningType, spawnLightning } from './spawn_lightning';
2 |
3 | import { putSavedCredentials } from '../../src/server/lnd';
4 |
5 | let lightning: SpawnLightningType;
6 |
7 | const startGlobalContainer = async () => {
8 | lightning = await spawnLightning();
9 | const node = 'testnode1';
10 |
11 | const { result } = await putSavedCredentials({
12 | auth_type: 'credentials',
13 | cert: lightning.cert,
14 | macaroon: lightning.macaroon,
15 | socket: lightning.socket,
16 | node,
17 | is_default: false,
18 | });
19 |
20 | if (!result) {
21 | throw new Error('Failed to put saved credentials');
22 | }
23 |
24 | return lightning;
25 | };
26 |
27 | const returnGlobalLightning = async () => {
28 | return lightning;
29 | };
30 |
31 | const killGlobalContainer = async () => {
32 | await lightning.kill({});
33 | };
34 |
35 | export { killGlobalContainer, returnGlobalLightning, startGlobalContainer };
36 |
--------------------------------------------------------------------------------
/src/server/modules/lnd/lnd.service.ts:
--------------------------------------------------------------------------------
1 | import { authenticatedLnd, getLnds } from '~server/lnd';
2 |
3 | import { Injectable } from '@nestjs/common';
4 |
5 | /**
6 | Authenticated LND
7 | {
8 | [node]:
9 | }
10 | @returns via Promise
11 | {
12 | lnd:
13 | }
14 |
15 | Authenticated LNDs
16 | {
17 | [nodes]:
18 | }
19 | @returns via Promise
20 | {
21 | lnds:
22 | }
23 | */
24 |
25 | @Injectable()
26 | export class LndService {
27 | // Get a single authenticated LND for a node
28 | static async authenticatedLnd(args: { node: string }) {
29 | const { lnd } = await authenticatedLnd({ node: args.node });
30 |
31 | return lnd;
32 | }
33 |
34 | // Get multiple authenticated LNDs for a node array
35 | static async getLnds(args: { nodes?: string[] }) {
36 | const { lnds } = await getLnds({ nodes: args.nodes });
37 |
38 | return lnds;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/server/lnd/check_connection.ts:
--------------------------------------------------------------------------------
1 | import authenticatedLnd from './authenticated_lnd';
2 | import getSavedCredentials from './get_saved_credentials';
3 | import { verifyAccess } from 'lightning';
4 |
5 | const stringify = (data: any) => JSON.stringify(data);
6 |
7 | /** Check if lnd is connected
8 |
9 | @returns via Promise
10 | {
11 | result:
12 | [error]:
13 | }
14 | */
15 |
16 | type Args = {
17 | node: string;
18 | };
19 |
20 | const checkConnection = async ({ node }: Args): Promise<{ [key: string]: string | boolean }> => {
21 | try {
22 | const permissions = ['info:read'];
23 | const { lnd } = await authenticatedLnd({ node });
24 | const { macaroon } = await getSavedCredentials({ node });
25 | const hasAccess = (await verifyAccess({ lnd, macaroon, permissions })).is_valid;
26 |
27 | return { hasAccess };
28 | } catch (error) {
29 | return { error: stringify(error) };
30 | }
31 | };
32 |
33 | export default checkConnection;
34 |
--------------------------------------------------------------------------------
/tests/client/reconnect.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import { setCookie } from '../utils/setAccessToken';
4 | import { testConstants } from '../utils/constants';
5 |
6 | test.describe('Test the Reconnect command client page', async () => {
7 | test.beforeEach(async ({ page }) => {
8 | await setCookie({ page });
9 | });
10 |
11 | test('test the Reconnect command page and input values', async ({ page }) => {
12 | await page.goto(testConstants.commandsPage);
13 | await page.click('#Reconnect');
14 | await expect(page).toHaveTitle('Reconnect');
15 |
16 | await page.type('#node', 'testnode1');
17 |
18 | await page.click('text=run command');
19 | await page.waitForTimeout(1000);
20 |
21 | await expect(page.locator('#reconnectOutput')).toBeVisible();
22 | await page.click('text=home');
23 | });
24 |
25 | test.afterEach(async ({ page }) => {
26 | await page.context().clearCookies();
27 | await page.close();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/client/standard_components/lndboss/RawApiList.tsx:
--------------------------------------------------------------------------------
1 | import { Autocomplete, TextField } from '@mui/material';
2 |
3 | import React from 'react';
4 | import { rawApi } from '~shared/raw_api';
5 |
6 | // Renders the raw api list for call command
7 |
8 | const styles = {
9 | textField: {
10 | width: '600px',
11 | },
12 | };
13 |
14 | type Args = {
15 | setMethod: (method: string) => void;
16 | };
17 |
18 | const RawApiList = ({ setMethod }: Args) => {
19 | const methods = rawApi.calls.map(call => call.method);
20 |
21 | return (
22 | <>
23 | }
28 | onChange={(_event: any, newValue: any) => {
29 | setMethod(newValue || '');
30 | }}
31 | style={styles.textField}
32 | />
33 | >
34 | );
35 | };
36 |
37 | export default RawApiList;
38 |
--------------------------------------------------------------------------------
/tests/server/call.test.ts:
--------------------------------------------------------------------------------
1 | import { SpawnLightningServerType, spawnLightningServer } from '../utils/spawn_lightning_server';
2 | import { expect, test } from '@playwright/test';
3 |
4 | import { callCommand } from '../../src/server/commands/';
5 |
6 | test.describe('Test Call command on the node.js side', async () => {
7 | let lightning: SpawnLightningServerType;
8 |
9 | test.beforeAll(async () => {
10 | lightning = await spawnLightningServer();
11 | });
12 |
13 | test('run call command', async () => {
14 | const args = {
15 | method: 'createInvoice',
16 | postArgs: {
17 | cltv_delta: 144,
18 | description: 'testdescription',
19 | is_including_private_channels: true,
20 | mtokens: '1000',
21 | },
22 | };
23 | const result = await callCommand({ args, lnd: lightning.lnd });
24 |
25 | console.log('call----', result.call);
26 | expect(result).toBeTruthy();
27 | });
28 |
29 | test.afterAll(async () => {
30 | await lightning.kill({});
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/client/utils/fetch_peers_and_tags.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { axiosGetNoLoading } from './axios';
4 |
5 | const fetchPeersAndTags = async ({}) => {
6 | const list = [];
7 | const tagsQuery: types.commandTags = {
8 | icon: '',
9 | add: '',
10 | id: '',
11 | is_avoided: false,
12 | remove: '',
13 | tag: '',
14 | };
15 |
16 | const [responsePeers, responseTags] = await Promise.all([
17 | axiosGetNoLoading({ path: 'grpc/get-peers-all-nodes', query: {} }),
18 | axiosGetNoLoading({ path: 'tags', query: tagsQuery }),
19 | ]);
20 |
21 | if (!!responsePeers) {
22 | responsePeers.forEach(peers => {
23 | peers.result.forEach(p => {
24 | list.push(`${p.alias}\n${p.public_key}\nNode: ${peers.node}`);
25 | });
26 | });
27 |
28 | if (!!responseTags) {
29 | responseTags.forEach(tag => {
30 | list.push(`Tag: \n${tag.alias}`);
31 | });
32 | }
33 |
34 | return { list };
35 | }
36 | };
37 |
38 | export default fetchPeersAndTags;
39 |
--------------------------------------------------------------------------------
/src/client/output/DecryptOutput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /*
4 | Renders the output of the Decrypt command.
5 | */
6 |
7 | type Args = {
8 | data: {
9 | message: string;
10 | with_alias: string;
11 | with_public_key: string;
12 | };
13 | };
14 | const DecryptOutput = ({ data }: Args) => {
15 | return (
16 |
17 |
18 | Result
19 |
20 |
21 |
{JSON.stringify(data, null, 2)}
22 |
23 | );
24 | };
25 |
26 | export default DecryptOutput;
27 |
28 | const styles = {
29 | headerStyle: {
30 | backgroundColor: '#193549',
31 | padding: '5px 10px',
32 | fontFamily: 'monospace',
33 | color: '#ffc600',
34 | },
35 | preStyle: {
36 | display: 'block',
37 | padding: '10px 30px',
38 | margin: '0',
39 | overflow: 'scroll',
40 | },
41 | style: {
42 | backgroundColor: '#1f4662',
43 | color: '#fff',
44 | fontSize: '12px',
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/src/client/output/ChainDepositOutput.tsx:
--------------------------------------------------------------------------------
1 | import { CopyText } from '~client/standard_components/app-components';
2 | import QRCode from 'qrcode.react';
3 | import React from 'react';
4 |
5 | /*
6 | Renders the output of the ChainDeposit command.
7 | */
8 |
9 | const styles = {
10 | div: {
11 | marginTop: '100px',
12 | marginLeft: '10px',
13 | },
14 | qr: {
15 | height: '250px',
16 | width: '250px',
17 | padding: '5px',
18 | },
19 | text: {
20 | fontSize: '15px',
21 | fontWeight: 'bold',
22 | display: 'inline-block',
23 | },
24 | };
25 |
26 | type Data = {
27 | data: {
28 | address: string;
29 | url: string;
30 | };
31 | };
32 |
33 | const ChainDepositOutput = ({ data }: Data) => {
34 | return (
35 |
36 |
37 |
{data.address}
38 |
39 |
40 | );
41 | };
42 |
43 | export default ChainDepositOutput;
44 |
--------------------------------------------------------------------------------
/src/server/commands/joinGroupChannel/join_channel_group_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { Logger } from 'winston';
5 | import { joinGroupChannel } from 'paid-services';
6 |
7 | /** Join a channel group
8 | {
9 | code:
10 | lnd:
11 | logger:
12 | max_rate:
13 | }
14 | @returns via cbk or Promise
15 | {
16 | transaction_id:
17 | }
18 | */
19 |
20 | type Args = {
21 | args: types.commandJoinChannelGroup;
22 | lnd: AuthenticatedLnd;
23 | logger: Logger;
24 | };
25 | const joinChannelGroupCommand = async ({ args, lnd, logger }: Args) => {
26 | const result = await joinGroupChannel({
27 | lnd,
28 | logger,
29 | code: args.code,
30 | max_rate: args.max_rate,
31 | });
32 |
33 | return { result };
34 | };
35 |
36 | export default joinChannelGroupCommand;
37 |
--------------------------------------------------------------------------------
/src/server/modules/boslogger/boslogger.service.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable } from '@nestjs/common';
2 | import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
3 | import { Logger } from 'winston';
4 |
5 | const stringify = (n: object) => JSON.stringify(n, null, 2);
6 |
7 | /**
8 | Bos Logger Service - Logs Serverside logs to the console
9 | {
10 | message: ,
11 | type: ,
12 | }
13 | */
14 |
15 | @Injectable()
16 | export class BosloggerService {
17 | constructor(@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: Logger) {}
18 |
19 | log({ message, type }: { message: any; type: string }) {
20 | if (type === 'error') {
21 | this.logger.error(message);
22 | }
23 |
24 | if (type === 'warn') {
25 | this.logger.warn(message);
26 | }
27 |
28 | if (type === 'info') {
29 | this.logger.log(message, { level: 'info' });
30 | }
31 |
32 | if (type === 'json') {
33 | this.logger.log(stringify(message), { level: 'info' });
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/on-push-dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: Build on push
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - '**/*.md'
9 | - '**/*.yml'
10 | - '**/*.yaml'
11 | - 'README.md'
12 | - 'CHANGELOG.md'
13 |
14 | jobs:
15 | build:
16 | name: Build image
17 | runs-on: ubuntu-22.04
18 |
19 | steps:
20 | - name: Checkout project
21 | uses: actions/checkout@v3
22 |
23 | - name: Login to DockerHub
24 | uses: docker/login-action@v2
25 | with:
26 | username: ${{ secrets.DOCKER_USER }}
27 | password: ${{ secrets.DOCKER_PASSWORD }}
28 |
29 | - name: Set up QEMU
30 | uses: docker/setup-qemu-action@v2
31 | id: qemu
32 |
33 | - name: Setup Docker buildx action
34 | uses: docker/setup-buildx-action@v2
35 | id: buildx
36 |
37 | - name: Run Docker buildx
38 | run: |
39 | docker buildx build \
40 | --platform linux/amd64,linux/arm64 \
41 | --no-cache -t niteshbalusu/lndboss:latest --push .
42 |
--------------------------------------------------------------------------------
/src/server/modules/external-services/external-services.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, OnModuleInit } from '@nestjs/common';
2 |
3 | import { BosloggerService } from '../boslogger/boslogger.service';
4 | import { CronService } from '../cron/cron.service';
5 | import { ambossHealthCheck } from '~server/external_services_utils';
6 |
7 | /**
8 | @onModuleInit
9 | {
10 | Check if AMBOSS_HEALTH_CHECK ENV variable is set
11 | Ping amboss health check
12 | }
13 |
14 | @pingAmbossHealthCheck
15 | {
16 | logger: ,
17 | }
18 | */
19 |
20 | @Injectable()
21 | export class ExternalServicesService implements OnModuleInit {
22 | constructor(private logger: BosloggerService, private cronService: CronService) {}
23 | async onModuleInit(): Promise {
24 | this.pingAmbossHealthCheck({ logger: this.logger });
25 | }
26 |
27 | async pingAmbossHealthCheck({ logger }) {
28 | try {
29 | await ambossHealthCheck({ logger });
30 | } catch (error) {
31 | logger.log({ type: 'error', message: JSON.stringify(error) });
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/Startup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import { CssBaseline } from '@mui/material';
4 | import Router from 'next/router';
5 | import { clientConstants } from '../../utils/constants';
6 | import { useRouter } from 'next/navigation';
7 |
8 | /*
9 | Render bos startup video for 3 seconds and redirect to the commands page.
10 | */
11 |
12 | const styles: any = {
13 | video: {
14 | position: 'fixed',
15 | right: 0,
16 | bottom: 0,
17 | minWidth: '100%',
18 | minHeight: '100%',
19 | },
20 | };
21 |
22 | const Startup = () => {
23 | const router = useRouter();
24 | useEffect(() => {
25 | const id = setTimeout(() => {
26 | router.push(clientConstants.loginUrl);
27 | }, 3000);
28 |
29 | return () => clearTimeout(id);
30 | }, []);
31 | return (
32 |
33 |
36 |
37 | );
38 | };
39 |
40 | export default Startup;
41 |
--------------------------------------------------------------------------------
/src/server/commands/rebalance/encode_rebalance_params.ts:
--------------------------------------------------------------------------------
1 | import { encodeTlvStream } from 'bolt01';
2 |
3 | const isString = (n: any) => typeof n === 'string';
4 | const typeRebalanceId = '7';
5 | const typeRebalanceData = '8';
6 | const stringToHex = (n: string) => Buffer.from(n, 'hex');
7 |
8 | /** Encode the follow node params
9 |
10 | [0]:
11 | 1:
12 |
13 | {
14 | id:
15 | }
16 |
17 | @throws
18 |
19 |
20 | @returns
21 | {
22 | encoded:
23 | }
24 | */
25 |
26 | type Args = {
27 | data: string;
28 | id: string;
29 | };
30 |
31 | const encodeRebalanceParams = ({ data, id }: Args) => {
32 | if (!isString(id) || !isString(data)) {
33 | throw new Error('ExpectedRebalanceDataAndIdToEncodeFollowParams');
34 | }
35 |
36 | return encodeTlvStream({
37 | records: [
38 | { type: typeRebalanceId, value: stringToHex(id) },
39 | { type: typeRebalanceData, value: stringToHex(data) },
40 | ],
41 | });
42 | };
43 |
44 | export default encodeRebalanceParams;
45 |
--------------------------------------------------------------------------------
/tests/client/graph.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import commands from '../../src/client/commands';
4 | import { setCookie } from '../utils/setAccessToken';
5 | import { testConstants } from '../utils/constants';
6 |
7 | const GraphCommand = commands.find(n => n.value === 'Graph');
8 |
9 | test.describe('Test the Graph command client page', async () => {
10 | test.beforeEach(async ({ page }) => {
11 | await setCookie({ page });
12 | });
13 |
14 | test('test the Graph command page and input values', async ({ page }) => {
15 | await page.goto(testConstants.commandsPage);
16 | await page.click('#Graph');
17 | await expect(page).toHaveTitle('Graph');
18 |
19 | await page.type(`#${GraphCommand?.args?.alias_or_pubkey}`, 'alice');
20 | await page.type('#node', 'testnode1');
21 |
22 | await page.click('text=run command');
23 | await page.waitForTimeout(1000);
24 |
25 | await page.click('text=home');
26 | });
27 |
28 | test.afterEach(async ({ page }) => {
29 | await page.context().clearCookies();
30 | await page.close();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/server/commands/chainfees/chainfees_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { getChainFees } from 'balanceofsatoshis/chain';
5 | import { httpLogger } from '~server/utils/global_functions';
6 |
7 | /** Get chain fees
8 |
9 | Requires that the lnd is built with walletrpc
10 |
11 | {
12 | [blocks]:
13 | lnd:
14 | }
15 |
16 | @returns via Promise
17 | {
18 | current_block_hash:
19 | fee_by_block_target: {
20 | $number:
21 | }
22 | }
23 | */
24 |
25 | type Args = {
26 | args: types.commandChainfees;
27 | lnd: AuthenticatedLnd;
28 | };
29 | const chainfeesCommand = async ({ args, lnd }: Args): Promise<{ result: any }> => {
30 | try {
31 | const result = await getChainFees({
32 | lnd,
33 | blocks: args.blocks,
34 | });
35 |
36 | return { result };
37 | } catch (error) {
38 | httpLogger({ error });
39 | }
40 | };
41 |
42 | export default chainfeesCommand;
43 |
--------------------------------------------------------------------------------
/src/server/modules/rebalance/rebalance.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Get, Post, Query } from '@nestjs/common';
2 | import { deleteRebalanceDto, rebalanceDto, rebalanceScheduleDto } from '~shared/commands.dto';
3 | import { RebalanceService } from './rebalance.service';
4 |
5 | // Rebalance controller: Defines routes for rebalance command
6 |
7 | @Controller()
8 | export class RebalanceController {
9 | constructor(private rebalanceService: RebalanceService) {}
10 | @Post('api/rebalance/schedule')
11 | async scheduleRebalance(@Body() args: rebalanceScheduleDto) {
12 | return this.rebalanceService.scheduleRebalance(args);
13 | }
14 |
15 | @Get('api/rebalance')
16 | async rebalance(@Query() args: rebalanceDto) {
17 | return this.rebalanceService.rebalance(args);
18 | }
19 |
20 | @Get('api/rebalance/getrebalances')
21 | async getRebalances() {
22 | return this.rebalanceService.getRebalances();
23 | }
24 |
25 | @Get('api/rebalance/deleterebalance')
26 | async deleteRebalance(@Query() args: deleteRebalanceDto) {
27 | return this.rebalanceService.deleteRebalance(args);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Nitesh Balusu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/server/commands/createChannelGroup/create_channel_group_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { Logger } from 'winston';
5 | import { createGroupChannel } from 'paid-services';
6 |
7 | /** Create a channel group
8 | {
9 | capacity:
10 | count:
11 | lnd:
12 | logger:
13 | rate:
14 | }
15 | @returns via Promise
16 | {
17 | transaction_id:
18 | }
19 | */
20 |
21 | type Args = {
22 | args: types.commandCreateChannelGroup;
23 | lnd: AuthenticatedLnd;
24 | logger: Logger;
25 | };
26 | const createChannelGroupCommand = async ({ args, lnd, logger }: Args) => {
27 | const result = await createGroupChannel({
28 | lnd,
29 | logger,
30 | capacity: args.capacity,
31 | count: args.count,
32 | members: args.members || [],
33 | rate: args.rate,
34 | });
35 | return { result };
36 | };
37 |
38 | export default createChannelGroupCommand;
39 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/BasicDatePicker.tsx:
--------------------------------------------------------------------------------
1 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
2 | import { DatePicker } from '@mui/x-date-pickers/DatePicker';
3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
4 | import React from 'react';
5 | import TextField from '@mui/material/TextField';
6 | import moment from 'moment';
7 |
8 | // Renders a date picker
9 |
10 | type Args = {
11 | id: string;
12 | label: string;
13 | setValue: (value: string) => void;
14 | value: string;
15 | };
16 | const BasicDatePicker = ({ id, label, setValue, value }: Args) => {
17 | return (
18 |
19 | {
24 | !!newValue ? setValue(moment(newValue).format('YYYY-MM-DD')) : setValue(null);
25 | }}
26 | renderInput={params => }
27 | inputFormat="yyyy-MM-dd"
28 | />
29 |
30 | );
31 | };
32 |
33 | export default BasicDatePicker;
34 |
--------------------------------------------------------------------------------
/tests/client/find.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import commands from '../../src/client/commands';
4 | import { setCookie } from '../utils/setAccessToken';
5 | import { testConstants } from '../utils/constants';
6 |
7 | const FindCommand = commands.find(n => n.value === 'Find');
8 |
9 | test.describe('Test the Find command client page', async () => {
10 | test.beforeEach(async ({ page }) => {
11 | await setCookie({ page });
12 | });
13 |
14 | test('test the Find command page and input values', async ({ page }) => {
15 | await page.goto(testConstants.commandsPage);
16 | await page.click('text=Find');
17 | await expect(page).toHaveTitle('Find');
18 |
19 | await page.type(`#${FindCommand?.args?.query}`, 'alice');
20 | await page.type('#node', 'testnode1');
21 |
22 | await page.click('text=run command');
23 | await page.waitForTimeout(1000);
24 |
25 | await expect(page.locator('#findoutput')).toBeVisible();
26 | await page.click('text=home');
27 | });
28 |
29 | test.afterEach(async ({ page }) => {
30 | await page.context().clearCookies();
31 | await page.close();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/server/commands/fees/read_fees_file.ts:
--------------------------------------------------------------------------------
1 | import { auto } from 'async';
2 | import { homedir } from 'os';
3 | import { join } from 'path';
4 | import { readFile } from 'fs';
5 |
6 | const feesFile = 'fees.json';
7 | const home = '.bosgui';
8 | const { parse } = JSON;
9 |
10 | /** Read the fees.json file
11 |
12 | @returns via Promise
13 | {
14 | data:
15 | }
16 | */
17 | type Tasks = {
18 | readFile: {
19 | data: any;
20 | };
21 | };
22 | const readFeesFile = async ({}) => {
23 | return (
24 | await auto({
25 | readFile: (cbk: any) => {
26 | const filePath = join(...[homedir(), home, feesFile]);
27 | readFile(filePath, (err, res) => {
28 | if (!!err || !res) {
29 | return cbk(null, false);
30 | }
31 |
32 | try {
33 | parse(res.toString());
34 | } catch (err) {
35 | return cbk([400, 'ExpectedValidFeesJsonFileToScheduleFeesCommand']);
36 | }
37 |
38 | return cbk(null, { data: parse(res.toString()) });
39 | });
40 | },
41 | })
42 | ).readFile;
43 | };
44 |
45 | export default readFeesFile;
46 |
--------------------------------------------------------------------------------
/tests/client/closed.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import commands from '../../src/client/commands';
4 | import { setCookie } from '../utils/setAccessToken';
5 | import { testConstants } from '../utils/constants';
6 |
7 | const ClosedCommand = commands.find(n => n.value === 'Closed');
8 |
9 | test.describe('Test the Closed command client page', async () => {
10 | test.beforeEach(async ({ page }) => {
11 | await setCookie({ page });
12 | });
13 |
14 | test('test the Forwards command page and input values', async ({ page }) => {
15 | await page.goto(testConstants.commandsPage);
16 | await page.click('text=Closed');
17 | await expect(page).toHaveTitle('Closed');
18 |
19 | await page.type(`#${ClosedCommand?.flags?.limit}`, '5');
20 | await page.type('#node', 'testnode1');
21 |
22 | await page.click('text=run command');
23 | await page.waitForTimeout(1000);
24 |
25 | await expect(page.locator('#closedoutput')).toBeVisible();
26 | await page.click('text=home');
27 | });
28 |
29 | test.afterEach(async ({ page }) => {
30 | await page.context().clearCookies();
31 | await page.close();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/client/register_charts.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ArcElement,
3 | BarController,
4 | BarElement,
5 | BubbleController,
6 | CategoryScale,
7 | Chart,
8 | Decimation,
9 | DoughnutController,
10 | Filler,
11 | Legend,
12 | LineController,
13 | LineElement,
14 | LinearScale,
15 | LogarithmicScale,
16 | PieController,
17 | PointElement,
18 | PolarAreaController,
19 | RadarController,
20 | RadialLinearScale,
21 | ScatterController,
22 | TimeScale,
23 | TimeSeriesScale,
24 | Title,
25 | Tooltip,
26 | } from 'chart.js';
27 |
28 | const resgisterCharts = () => {
29 | Chart.register(
30 | ArcElement,
31 | LineElement,
32 | BarElement,
33 | PointElement,
34 | BarController,
35 | BubbleController,
36 | DoughnutController,
37 | LineController,
38 | PieController,
39 | PolarAreaController,
40 | RadarController,
41 | ScatterController,
42 | CategoryScale,
43 | LinearScale,
44 | LogarithmicScale,
45 | RadialLinearScale,
46 | TimeScale,
47 | TimeSeriesScale,
48 | Decimation,
49 | Filler,
50 | Legend,
51 | Title,
52 | Tooltip
53 | );
54 | };
55 |
56 | export default resgisterCharts;
57 |
--------------------------------------------------------------------------------
/src/client/dashboard/PendingChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import { BasicTable } from '~client/standard_components/app-components';
4 | import { axiosPost } from '~client/utils/axios';
5 | import resgisterCharts from '~client/register_charts';
6 | import { selectedSavedNode } from '~client/utils/constants';
7 | import { useLoading } from '~client/hooks/useLoading';
8 |
9 | // Calls NestJs Server and returns pending payments and channels
10 |
11 | const PendingChart = () => {
12 | const [data, setData] = useState(undefined);
13 |
14 | useEffect(() => {
15 | const fetchData = async () => {
16 | const postBody = {
17 | node: selectedSavedNode(),
18 | };
19 |
20 | useLoading({ isLoading: true });
21 | const result = await axiosPost({ path: 'grpc/get-pending', postBody });
22 |
23 | if (!!result) {
24 | setData(result);
25 | }
26 |
27 | useLoading({ isLoading: false });
28 | };
29 |
30 | fetchData();
31 | }, []);
32 |
33 | resgisterCharts();
34 | return !!data && !!data.length ? : null;
35 | };
36 |
37 | export default PendingChart;
38 |
--------------------------------------------------------------------------------
/src/client/output/PriceOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 |
5 | const stringify = (obj: any) => JSON.stringify(obj, null, 2);
6 |
7 | // Renders the output of bos price command
8 |
9 | const styles = {
10 | pre: {
11 | fontWeight: 'bold',
12 | },
13 | };
14 |
15 | type Args = {
16 | data: object;
17 | file: boolean;
18 | };
19 |
20 | const ManageOutput = ({ data, file }: Args) => {
21 | if (!data || !Object.keys(data).length) {
22 | return No data found
;
23 | }
24 |
25 | if (!!file) {
26 | return (
27 |
32 | Results are ready, click here to download
33 |
34 | );
35 | }
36 |
37 | const output = YAML.stringify(data);
38 |
39 | return {output};
40 | };
41 |
42 | const PriceOutput = ({ data, file }: Args) => {
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default PriceOutput;
51 |
--------------------------------------------------------------------------------
/tests/client/open.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import { setCookie } from '../utils/setAccessToken';
4 | import { testConstants } from '../utils/constants';
5 |
6 | test.describe('Test the Open command client page', async () => {
7 | test.beforeEach(async ({ page }) => {
8 | await setCookie({ page });
9 | });
10 |
11 | test('test the Open page and input values', async ({ page }) => {
12 | await page.goto(testConstants.commandsPage);
13 | await page.click('text=Open');
14 | await expect(page).toHaveTitle('Open');
15 |
16 | await page.type(`#pubkey-0`, '034f94cccfb1ce5c31e0a367d4ee556f66a865b54b389f15781cacd6ed6611976d');
17 | await page.type(`#amount-0`, '100000');
18 | await page.type(`#address-0`, 'bcrt1qtdgrtstez46e8umx49tq22sh7terccyxjmwt2w');
19 | await page.type(`#give-0`, '100000');
20 | await page.type('#node', 'testnode1');
21 |
22 | await page.click('text=Validate and Open Channels');
23 | await page.waitForTimeout(1000);
24 |
25 | await page.click('text=home');
26 | });
27 |
28 | test.afterEach(async ({ page }) => {
29 | await page.context().clearCookies();
30 | await page.close();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/client/utils/fee_strategies.ts:
--------------------------------------------------------------------------------
1 | export const configs = {
2 | defaultConfig: {
3 | configs: [
4 | {
5 | name: 'Default',
6 | config: {
7 | strategy: 'static',
8 | fee_ppm: 200,
9 | id: ['xxx', 'yyy'],
10 | },
11 | },
12 | ],
13 | },
14 | activityConfig: {
15 | configs: [
16 | {
17 | name: 'Default',
18 | config: {
19 | strategy: 'static',
20 | fee_ppm: 200,
21 | id: ['xxx', 'yyy'],
22 | },
23 | },
24 | {
25 | name: 'low-out-flow',
26 | config: {
27 | activity_period: '15d',
28 | strategy: 'static',
29 | fee_ppm: 100,
30 | },
31 | },
32 | {
33 | name: 'very-low-out-flow',
34 | config: {
35 | activity_period: '7d',
36 | strategy: 'static',
37 | fee_ppm: 100,
38 | },
39 | },
40 | ],
41 | },
42 | staticConfig: {
43 | configs: [
44 | {
45 | name: 'Default',
46 | config: {
47 | strategy: 'static',
48 | fee_ppm: 200,
49 | id: ['xxx', 'yyy'],
50 | },
51 | },
52 | ],
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/src/client/output/ChainfeesOutput.tsx:
--------------------------------------------------------------------------------
1 | import * as YAML from 'json-to-pretty-yaml';
2 |
3 | import React from 'react';
4 |
5 | const stringify = (obj: any) => JSON.stringify(obj, null, 2);
6 |
7 | // Renders the output of bos chainfees command
8 |
9 | const styles = {
10 | pre: {
11 | fontWeight: 'bold',
12 | },
13 | };
14 |
15 | type Args = {
16 | data: object;
17 | file: boolean;
18 | };
19 |
20 | const ManageOutput = ({ data, file }: Args) => {
21 | if (!data || !Object.keys(data).length) {
22 | return No data found
;
23 | }
24 |
25 | if (!!file) {
26 | return (
27 |
32 | Results are ready, click here to download
33 |
34 | );
35 | }
36 |
37 | const output = YAML.stringify(data);
38 |
39 | return {output};
40 | };
41 |
42 | const ChainfeesOutput = ({ data, file }: Args) => {
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default ChainfeesOutput;
51 |
--------------------------------------------------------------------------------
/src/server/commands/fees/fees_command.ts:
--------------------------------------------------------------------------------
1 | import * as types from '~shared/types';
2 |
3 | import { AuthenticatedLnd } from 'lightning';
4 | import { Logger } from 'winston';
5 | import { adjustFees } from 'balanceofsatoshis/routing';
6 | import { readFile } from 'fs';
7 |
8 | /** View and adjust routing fees
9 |
10 | {
11 | [cltv_delta]:
12 | [fee_rate]:
13 | lnd:
14 | logger:
15 | to: []
16 | }
17 |
18 | @returns via cbk or Promise
19 | {
20 | rows: [[]]
21 | }
22 | */
23 | type Args = {
24 | args: types.commandFees;
25 | lnd: AuthenticatedLnd;
26 | logger: Logger;
27 | };
28 | const feesCommand = async ({ args, lnd, logger }: Args) => {
29 | const toArray = !!args.to ? args.to.filter((n: string) => !!n) : [];
30 |
31 | const result = await adjustFees({
32 | lnd,
33 | logger,
34 | cltv_delta: args.cltv_delta || undefined,
35 | fee_rate: args.fee_rate,
36 | fs: { getFile: readFile },
37 | to: toArray,
38 | });
39 |
40 | return { result };
41 | };
42 |
43 | export default feesCommand;
44 |
--------------------------------------------------------------------------------
/tests/utils/spawn_lightning.ts:
--------------------------------------------------------------------------------
1 | import { spawnLightningDocker } from 'ln-docker-daemons';
2 | const address = 'bcrt1qkznf8grqj8ed9xrt8mtxmj5da63cwyk3tl0wh3';
3 | const ports = {
4 | chain_p2p_port: 2345,
5 | chain_rpc_port: 3456,
6 | chain_zmq_block_port: 3345,
7 | chain_zmq_tx_port: 4445,
8 | lightning_p2p_port: 5445,
9 | lightning_rpc_port: 6445,
10 | lightning_tower_port: 8011,
11 | };
12 |
13 | export type SpawnLightningType = {
14 | cert: string;
15 | kill: ({}) => Promise;
16 | macaroon: string;
17 | socket: string;
18 | };
19 |
20 | const spawnLightning = async (): Promise => {
21 | const { cert, kill, macaroon, socket }: SpawnLightningType = await spawnLightningDocker({
22 | chain_p2p_port: ports.chain_p2p_port,
23 | chain_rpc_port: ports.chain_rpc_port,
24 | chain_zmq_block_port: ports.chain_zmq_block_port,
25 | chain_zmq_tx_port: ports.chain_zmq_tx_port,
26 | generate_address: address,
27 | lightning_p2p_port: ports.lightning_p2p_port,
28 | lightning_rpc_port: ports.lightning_rpc_port,
29 | lightning_tower_port: ports.lightning_tower_port,
30 | });
31 |
32 | return { cert, kill, macaroon, socket };
33 | };
34 |
35 | export { spawnLightning };
36 |
--------------------------------------------------------------------------------
/.github/workflows/on-tag-dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: Build on tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - v[0-9]+.[0-9]+.[0-9]+
7 | - v[0-9]+.[0-9]+.[0-9]+-*
8 |
9 | jobs:
10 | build:
11 | name: Build image
12 | runs-on: ubuntu-22.04
13 |
14 | steps:
15 | - name: Checkout project
16 | uses: actions/checkout@v3
17 |
18 | - name: Login to DockerHub
19 | uses: docker/login-action@v2
20 | with:
21 | username: ${{ secrets.DOCKER_USER }}
22 | password: ${{ secrets.DOCKER_PASSWORD }}
23 |
24 | - name: Set up QEMU
25 | uses: docker/setup-qemu-action@v2
26 | id: qemu
27 |
28 | - name: Setup Docker buildx action
29 | uses: docker/setup-buildx-action@v2
30 | id: buildx
31 |
32 | - name: Set env variables
33 | run: |
34 | echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
35 | IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
36 | echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
37 |
38 | - name: Run Docker buildx
39 | run: |
40 | docker buildx build \
41 | --platform linux/amd64,linux/arm64 \
42 | --no-cache -t niteshbalusu/lndboss:$TAG --push .
43 |
--------------------------------------------------------------------------------
/src/server/modules/grpc/grpc.controller.ts:
--------------------------------------------------------------------------------
1 | import { Body, Controller, Get, Post, Query } from '@nestjs/common';
2 | import { getPendingDto, grpcDto } from '~shared/commands.dto';
3 | import { GrpcService } from './grpc.service';
4 |
5 | @Controller()
6 | export class GrpcController {
7 | constructor(private grpcService: GrpcService) {}
8 |
9 | @Get('api/grpc/get-channel-balance')
10 | async getChannelBalance(@Query() args: grpcDto) {
11 | return this.grpcService.getChannelBalance(args);
12 | }
13 |
14 | @Get('api/grpc/get-peers')
15 | async getPeers(@Query() args: grpcDto) {
16 | return this.grpcService.getPeers(args);
17 | }
18 |
19 | @Get('api/grpc/get-peers-all-nodes')
20 | async getPeersAllNodes() {
21 | return this.grpcService.getPeersAllNodes();
22 | }
23 |
24 | @Post('api/grpc/get-pending')
25 | async getPending(@Body() args: getPendingDto) {
26 | return this.grpcService.getPending(args);
27 | }
28 |
29 | @Get('api/grpc/get-saved-nodes')
30 | async getSavedNodes() {
31 | return this.grpcService.getSavedNodes();
32 | }
33 |
34 | @Get('api/grpc/get-wallet-info')
35 | async getWalletInfo(@Query() args: grpcDto) {
36 | return this.grpcService.getWalletInfo(args);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/client/utxos.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test';
2 |
3 | import commands from '../../src/client/commands';
4 | import { setCookie } from '../utils/setAccessToken';
5 | import { testConstants } from '../utils/constants';
6 |
7 | const UtxosCommand = commands.find(n => n.value === 'Utxos');
8 |
9 | test.describe('Test the Utxos command client page', async () => {
10 | test.beforeEach(async ({ page }) => {
11 | await setCookie({ page });
12 | });
13 |
14 | test('test the Utxos command page and input values', async ({ page }) => {
15 | await page.goto(testConstants.commandsPage);
16 | await page.click('text=Utxos');
17 | await expect(page).toHaveTitle('Utxos');
18 |
19 | await page.type(`#${UtxosCommand?.flags?.count_below}`, '0');
20 | await page.type(`#${UtxosCommand?.flags?.size}`, '0');
21 |
22 | await page.type('#node', 'testnode1');
23 |
24 | await page.click('text=run command');
25 | await page.waitForTimeout(1000);
26 |
27 | await expect(page.locator('#utxosoutput')).toBeVisible();
28 | await page.click('text=home');
29 | });
30 |
31 | test.afterEach(async ({ page }) => {
32 | await page.context().clearCookies();
33 | await page.close();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/server/commands/reconnect/reconnect_command.ts:
--------------------------------------------------------------------------------
1 | import { AuthenticatedLnd } from 'lightning';
2 | import { httpLogger } from '~server/utils/global_functions';
3 | import { reconnect } from 'balanceofsatoshis/network';
4 |
5 | /** Get channel peers that are disconnected and attempt to reconnect
6 |
7 | This method will also disconnect peers that are connected, but have inactive
8 | channels.
9 |
10 | {
11 | lnd:
12 | }
13 |
14 | @returns via Promise
15 | {
16 | offline: [{
17 | alias:
18 | public_key:
22 | public_key: => {
38 | try {
39 | const result = await reconnect({ lnd });
40 |
41 | return { result };
42 | } catch (error) {
43 | httpLogger({ error });
44 | }
45 | };
46 |
47 | export default reconnectCommand;
48 |
--------------------------------------------------------------------------------
/src/client/utils/jwt.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import jwt_decode from 'jwt-decode';
3 |
4 | /** Decode the JWT token
5 | {
6 | token: ,
7 | }
8 | @returns
9 | {
10 | boolean:
11 | }
12 | */
13 |
14 | type Decoded = {
15 | exp: number;
16 | iat: number;
17 | username: string;
18 | sub: string;
19 | };
20 |
21 | export const isJwtValid = ({ token }: { token: string }) => {
22 | try {
23 | const decoded: Decoded = jwt_decode(token);
24 |
25 | if (!decoded || !decoded.exp) {
26 | return false;
27 | }
28 | const { exp } = decoded;
29 | const now = new Date().getTime();
30 |
31 | return now < exp * 1000;
32 | } catch (error) {
33 | return false;
34 | }
35 | };
36 |
37 | /** Decode the JWT token
38 | {
39 | token: ,
40 | }
41 | @returns
42 | decoded:
43 | */
44 |
45 | export const jwtDecode = ({ token }: { token: string }) => {
46 | try {
47 | const decoded: Decoded = jwt_decode(token);
48 |
49 | if (!decoded || !decoded.exp) {
50 | return false;
51 | }
52 |
53 | return decoded.exp;
54 | } catch (error) {
55 | return false;
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/.github/workflows/on-tag-dockerhub-root.yml:
--------------------------------------------------------------------------------
1 | name: Build on tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - v[0-9]+.[0-9]+.[0-9]+
7 | - v[0-9]+.[0-9]+.[0-9]+-*
8 |
9 | jobs:
10 | build:
11 | name: Build image
12 | runs-on: ubuntu-22.04
13 |
14 | steps:
15 | - name: Checkout project
16 | uses: actions/checkout@v3
17 |
18 | - name: Set env variables
19 | run: |
20 | echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
21 | IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
22 | echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
23 |
24 | - name: Login to DockerHub
25 | uses: docker/login-action@v2
26 | with:
27 | username: ${{ secrets.DOCKER_USER }}
28 | password: ${{ secrets.DOCKER_PASSWORD }}
29 |
30 | - name: Set up QEMU
31 | uses: docker/setup-qemu-action@v2
32 | id: qemu
33 |
34 | - name: Setup Docker buildx action
35 | uses: docker/setup-buildx-action@v2
36 | id: buildx
37 |
38 | - name: Run Docker buildx
39 | run: |
40 | docker buildx build \
41 | --file arm64.Dockerfile --platform linux/amd64,linux/arm64 \
42 | --no-cache -t niteshbalusu/lndboss:root --push .
43 |
--------------------------------------------------------------------------------
/src/server/modules/socket/socket.gateway.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OnGatewayConnection,
3 | OnGatewayDisconnect,
4 | OnGatewayInit,
5 | SubscribeMessage,
6 | WebSocketGateway,
7 | WebSocketServer,
8 | } from '@nestjs/websockets';
9 | import { Server, Socket } from 'socket.io';
10 |
11 | import { BosloggerService } from '../boslogger/boslogger.service';
12 |
13 | @WebSocketGateway({
14 | cors: {
15 | origin: '*',
16 | },
17 | })
18 | export class SocketGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
19 | constructor(private logger: BosloggerService) {}
20 |
21 | @WebSocketServer() server: Server;
22 |
23 | @SubscribeMessage('msgToServer')
24 | handleMessage(client: Socket, payload: string): void {
25 | this.logger.log({ message: `Message Received: ${client.id}`, type: 'info' });
26 | this.server.emit('msgToClient', payload);
27 | }
28 |
29 | afterInit() {
30 | this.logger.log({ message: 'Socket Gateway Initialized', type: 'info' });
31 | }
32 |
33 | handleDisconnect(client: Socket) {
34 | this.logger.log({ message: `Client disconnected: ${client.id}`, type: 'warn' });
35 | }
36 |
37 | handleConnection(client: Socket) {
38 | this.logger.log({ message: `Client connected: ${client.id}`, type: 'info' });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/client/output/InvoiceOutput.tsx:
--------------------------------------------------------------------------------
1 | import { CopyText } from '~client/standard_components/app-components';
2 | import QRCode from 'qrcode.react';
3 | import React from 'react';
4 |
5 | const substring = n => n.slice(0, 20) + '......' + n.slice(-20);
6 |
7 | /*
8 | Renders the output of the Invoice command.
9 | */
10 |
11 | type Args = {
12 | data: {
13 | request: string;
14 | tokens: number;
15 | };
16 | };
17 | const InvoiceOutput = ({ data }: Args) => {
18 | return (
19 |
20 |
21 |
22 | {substring(data.request)}
23 |
24 |
25 |
26 |
{`Tokens: ${data.tokens}`}
27 |
28 |
29 | );
30 | };
31 |
32 | export default InvoiceOutput;
33 |
34 | const styles = {
35 | div: {
36 | marginTop: '30px',
37 | marginLeft: '10px',
38 | },
39 | qr: {
40 | height: '350px',
41 | width: '350px',
42 | padding: '5px',
43 | },
44 | text: {
45 | fontSize: '15px',
46 | fontWeight: 'bold',
47 | display: 'inline-block',
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/client/standard_components/app-components/StandardButtonLink.tsx:
--------------------------------------------------------------------------------
1 | import { Button, ButtonProps, styled } from '@mui/material';
2 |
3 | import Link from 'next/link';
4 | import React from 'react';
5 | import { purple } from '@mui/material/colors';
6 |
7 | /* Renders the standard link button
8 | {
9 | destination: