From 26fc5844931b9dabe1cbee2eaae20bf2a55ac3a8 Mon Sep 17 00:00:00 2001
From: Nitesh Balusu <84944042+niteshbalusu11@users.noreply.github.com>
Date: Tue, 8 Nov 2022 09:53:49 -0500
Subject: [PATCH 1/2] Add pending htlc/channel monitoring Fixes #125
---
.../dashboard/DefaultDashboardContainer.tsx | 18 ++
src/client/dashboard/PendingChart.tsx | 37 ++++
.../app-components/BasicTable.tsx | 43 ++++
.../app-components/index.ts | 4 +-
.../commands/grpc_utils/format_tokens.ts | 33 +++
src/server/commands/grpc_utils/get_pending.ts | 195 ++++++++++++++++++
src/server/commands/grpc_utils/icons.ts | 20 ++
src/server/commands/grpc_utils/index.ts | 3 +
.../commands/grpc_utils/pending_payments.ts | 103 +++++++++
.../commands/grpc_utils/pending_summary.ts | 166 +++++++++++++++
src/server/modules/grpc/grpc.controller.ts | 25 ++-
src/server/modules/grpc/grpc.service.ts | 29 ++-
src/shared/commands.dto.ts | 6 +
13 files changed, 661 insertions(+), 21 deletions(-)
create mode 100644 src/client/dashboard/PendingChart.tsx
create mode 100644 src/client/standard_components/app-components/BasicTable.tsx
create mode 100644 src/server/commands/grpc_utils/format_tokens.ts
create mode 100644 src/server/commands/grpc_utils/get_pending.ts
create mode 100644 src/server/commands/grpc_utils/icons.ts
create mode 100644 src/server/commands/grpc_utils/index.ts
create mode 100644 src/server/commands/grpc_utils/pending_payments.ts
create mode 100644 src/server/commands/grpc_utils/pending_summary.ts
diff --git a/src/client/dashboard/DefaultDashboardContainer.tsx b/src/client/dashboard/DefaultDashboardContainer.tsx
index 0d1311d..103815f 100644
--- a/src/client/dashboard/DefaultDashboardContainer.tsx
+++ b/src/client/dashboard/DefaultDashboardContainer.tsx
@@ -2,6 +2,7 @@ import { Box, Container, Grid, Paper, Toolbar } from '@mui/material';
import BalanceInfo from './BalanceInfo';
import NodeInfo from '~client/dashboard/NodeInfo';
+import PendingChart from './PendingChart';
import RoutingFeeChart from './RoutingFeeChart';
// Renders the default dashboard on page load.
@@ -33,6 +34,7 @@ const DefaultDashboardContainer = () => {
+
{/* Walletinfo */}
{
+
+ {/* Pending Chart */}
+
+
+
+
+
+
{/* Chart */}
{
+ const [data, setData] = useState(undefined);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const postBody = {
+ node: selectedSavedNode(),
+ };
+
+ useLoading({ isLoading: true });
+ const result = await axiosPost({ path: 'grpc/get-pending', postBody });
+ console.log(result);
+ if (!!result) {
+ setData(result);
+ }
+
+ useLoading({ isLoading: false });
+ };
+
+ fetchData();
+ }, []);
+
+ resgisterCharts();
+ return !!data && !!data.length ? : null;
+};
+
+export default PendingChart;
diff --git a/src/client/standard_components/app-components/BasicTable.tsx b/src/client/standard_components/app-components/BasicTable.tsx
new file mode 100644
index 0000000..c8b07d8
--- /dev/null
+++ b/src/client/standard_components/app-components/BasicTable.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react';
+
+import Paper from '@mui/material/Paper';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+
+function createData(data: string) {
+ return { data };
+}
+
+type Args = {
+ rows: string[];
+};
+const BasicTable = ({ rows }: Args) => {
+ const data = rows.map(n => createData(n));
+
+ return (
+
+
+
+
+ Pending Things
+
+
+
+ {data.map(row => (
+
+
+ {row.data}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default BasicTable;
diff --git a/src/client/standard_components/app-components/index.ts b/src/client/standard_components/app-components/index.ts
index 6a1e0cd..9a9777c 100644
--- a/src/client/standard_components/app-components/index.ts
+++ b/src/client/standard_components/app-components/index.ts
@@ -1,4 +1,5 @@
import BasicDatePicker from './BasicDatePicker';
+import BasicTable from './BasicTable';
import CenterFlexBox from './CenterFlexBox';
import ContainerStyle from './ContainerStyle';
import CopyText from './CopyText';
@@ -18,10 +19,11 @@ import StartFlexBoxBlack from './StartFlexBoxBlack';
import Startup from './Startup';
import SubmitButton from './SubmitButton';
export {
+ BasicDatePicker,
+ BasicTable,
CenterFlexBox,
ContainerStyle,
CopyText,
- BasicDatePicker,
PositionedMenu,
ProgressBar,
ReactCron,
diff --git a/src/server/commands/grpc_utils/format_tokens.ts b/src/server/commands/grpc_utils/format_tokens.ts
new file mode 100644
index 0000000..8444802
--- /dev/null
+++ b/src/server/commands/grpc_utils/format_tokens.ts
@@ -0,0 +1,33 @@
+const fullTokensType = 'full';
+const isString = (n: any) => typeof n === 'string';
+const tokensAsBigUnit = (tokens: number) => (tokens / 1e8).toFixed(8);
+
+/** Format tokens for display
+ {
+ [none]:
+ tokens:
+ }
+ @returns
+ {
+ display:
+ }
+*/
+
+type Args = {
+ none?: string;
+ tokens: number;
+};
+const formatTokens = ({ none, tokens }: Args) => {
+ if (isString(none) && !tokens) {
+ return { display: none };
+ }
+
+ // Exit early for tokens environment displays the value with no leading zero
+ if (process.env.PREFERRED_TOKENS_TYPE === fullTokensType) {
+ return { display: tokens.toLocaleString() };
+ }
+
+ return { display: tokensAsBigUnit(tokens) };
+};
+
+export default formatTokens;
diff --git a/src/server/commands/grpc_utils/get_pending.ts b/src/server/commands/grpc_utils/get_pending.ts
new file mode 100644
index 0000000..381f19f
--- /dev/null
+++ b/src/server/commands/grpc_utils/get_pending.ts
@@ -0,0 +1,195 @@
+import { AuthenticatedLnd, getChannels, getHeight, getPendingChannels } from 'lightning';
+import { auto, map } from 'async';
+
+import { getNodeAlias } from 'ln-sync';
+import pendingPayments from './pending_payments';
+import pendingSummary from './pending_summary';
+
+const uniq = arr => Array.from(new Set(arr));
+
+/** Handle pending command
+ {
+ lnd:
+ }
+ @returns Promise
+ {
+ count:
+ htlcs: [{
+ forwarding: [{
+ fee:
+ in_peer:
+ out_peer:
+ tokens:
+ }]
+ from:
+ nodes: [{
+ alias:
+ id:
+ }]
+ sending: [{
+ out_peer:
+ }]
+ }]
+ pending: [{
+ closing: [{
+ partner_public_key:
+ pending_balance:
+ timelock_expiration:
+ }]
+ from:
+ height:
+ nodes: [{
+ alias:
+ id:
+ }]
+ opening: [{
+ is_partner_initiated:
+ local_balance:
+ partner_public_key:
+ remote_balance:
+ transaction_fee:
+ transaction_id:
+ }]
+ }]
+ }
+*/
+type Args = {
+ lnd: AuthenticatedLnd;
+};
+const getPending = async ({ lnd }: Args) => {
+ return (
+ await auto({
+ // Check arguments
+ validate: (cbk: any) => {
+ return cbk();
+ },
+
+ // Get HTLCs in channels
+ getHtlcs: [
+ 'validate',
+ ({}, cbk: any) => {
+ const nodes = [{ lnd }];
+ return map(
+ nodes,
+ ({ lnd }, cbk: any) => {
+ return getChannels({ lnd }, (err, res) => {
+ if (!!err) {
+ return cbk(err);
+ }
+
+ const { forwarding, sending } = pendingPayments({
+ channels: res.channels,
+ });
+
+ const peers = []
+ .concat(forwarding.map(n => n.in_peer))
+ .concat(forwarding.map(n => n.out_peer))
+ .concat(sending.map(n => n.out_peer));
+
+ return map(
+ uniq(peers),
+ (id, cbk) => {
+ return getNodeAlias({ id, lnd }, cbk);
+ },
+ (err, nodes) => {
+ if (!!err) {
+ return cbk(err);
+ }
+
+ return cbk(null, { forwarding, nodes, sending });
+ }
+ );
+ });
+ },
+ cbk
+ );
+ },
+ ],
+
+ // Get pending channels
+ getPending: [
+ 'validate',
+ ({}, cbk: any) => {
+ const nodes = [{ lnd }];
+
+ return map(
+ nodes,
+ ({ lnd }, cbk: any) => {
+ return getPendingChannels({ lnd }, (err, res) => {
+ if (!!err) {
+ return cbk(err);
+ }
+
+ // Pending closing channels
+ const closing = res.pending_channels
+ .filter(n => !!n.is_closing)
+ .map(channel => ({
+ close_transaction_id: channel.close_transaction_id,
+ is_partner_initiated: channel.is_partner_initiated,
+ partner_public_key: channel.partner_public_key,
+ pending_balance: channel.pending_balance,
+ timelock_expiration: channel.timelock_expiration,
+ transaction_id: channel.transaction_id,
+ }));
+
+ // Pending opening channels
+ const opening = res.pending_channels
+ .filter(n => !!n.is_opening)
+ .map(channel => ({
+ is_partner_initiated: channel.is_partner_initiated,
+ local_balance: channel.local_balance,
+ partner_public_key: channel.partner_public_key,
+ remote_balance: channel.remote_balance,
+ transaction_fee: channel.transaction_fee,
+ transaction_id: channel.transaction_id,
+ }));
+
+ const peers = []
+ .concat(closing.map(n => n.partner_public_key))
+ .concat(opening.map(n => n.partner_public_key));
+
+ return map(
+ uniq(peers),
+ (id, cbk) => {
+ return getNodeAlias({ id, lnd }, cbk);
+ },
+ (err, nodes) => {
+ if (!!err) {
+ return cbk(err);
+ }
+
+ return getHeight({ lnd }, (err, res) => {
+ if (!!err) {
+ return cbk(err);
+ }
+
+ const height = res.current_block_height;
+
+ return cbk(null, { closing, height, nodes, opening });
+ });
+ }
+ );
+ });
+ },
+ cbk
+ );
+ },
+ ],
+ // Notify of pending forwards and channels
+ notify: [
+ 'getHtlcs',
+ 'getPending',
+ async ({ getHtlcs, getPending }) => {
+ const summary = pendingSummary({
+ htlcs: getHtlcs,
+ pending: getPending,
+ });
+
+ return summary;
+ },
+ ],
+ })
+ ).notify;
+};
+
+export default getPending;
diff --git a/src/server/commands/grpc_utils/icons.ts b/src/server/commands/grpc_utils/icons.ts
new file mode 100644
index 0000000..2abba26
--- /dev/null
+++ b/src/server/commands/grpc_utils/icons.ts
@@ -0,0 +1,20 @@
+const icons = {
+ balanced_open: 'âī¸',
+ block: 'âš',
+ bot: 'đ¤',
+ chain: 'â',
+ closing: 'âŗ',
+ disconnected: 'đĩ',
+ earn: 'đ°',
+ forwarding: 'đ¸',
+ info: 'âšī¸',
+ liquidity: 'đ',
+ opening: 'âŗ',
+ probe: 'đŊ',
+ rebalance: 'â¯ī¸',
+ receive: 'đĩ',
+ spent: 'âĄī¸',
+ warning: 'â ī¸',
+};
+
+export default icons;
diff --git a/src/server/commands/grpc_utils/index.ts b/src/server/commands/grpc_utils/index.ts
new file mode 100644
index 0000000..7664679
--- /dev/null
+++ b/src/server/commands/grpc_utils/index.ts
@@ -0,0 +1,3 @@
+import getPending from './get_pending';
+
+export { getPending };
diff --git a/src/server/commands/grpc_utils/pending_payments.ts b/src/server/commands/grpc_utils/pending_payments.ts
new file mode 100644
index 0000000..84aebe9
--- /dev/null
+++ b/src/server/commands/grpc_utils/pending_payments.ts
@@ -0,0 +1,103 @@
+const flatten = arr => [].concat(...arr);
+
+/** Derive pending forwards frrom a list of pending payments
+ {
+ channels: [{
+ id:
+ partner_public_key:
+ pending_payments: [{
+ id:
+ [in_channel]:
+ [in_payment]:
+ [is_forward]:
+ is_outgoing:
+ [out_channel]:
+ [out_payment]:
+ [payment]:
+ timeout:
+ tokens:
+ }]
+ }]
+ }
+ @returns
+ {
+ forwarding: [{
+ fee:
+ in_peer:
+ out_peer:
+ payment:
+ timeout:
+ tokens:
+ }]
+ sending: [{
+ out_channel:
+ out_peer:
+ timeout:
+ tokens:
+ }]
+ }
+*/
+const pendingPayments = ({ channels }) => {
+ // Collect all the outbound type HTLCs
+ const sending = flatten(
+ channels.map(channel => {
+ return (channel.pending_payments || [])
+ .filter(n => !n.is_forward && !!n.is_outgoing)
+ .map(payment => ({
+ out_channel: channel.id,
+ out_peer: channel.partner_public_key,
+ timeout: payment.timeout,
+ tokens: payment.tokens,
+ }));
+ })
+ );
+
+ // Collect all the forwarding type HTLCs
+ const forwards = flatten(
+ channels.map(channel => {
+ return (channel.pending_payments || [])
+ .filter(n => !!n.is_forward)
+ .filter(n => !!n.in_channel || !!n.out_channel)
+ .filter(n => !!n.in_payment || !!n.out_payment)
+ .map(payment => ({
+ channel: channel.id,
+ id: payment.id,
+ in_channel: payment.in_channel,
+ in_payment: payment.in_payment,
+ is_outgoing: payment.is_outgoing,
+ payment: payment.payment,
+ timeout: payment.timeout,
+ tokens: payment.tokens,
+ }));
+ })
+ );
+
+ // Outbound forwarding HTLCs
+ const outbound = forwards
+ // Outbound forwards have inbound channels and inbound payment indexes
+ .filter(n => !!n.in_channel && !!n.in_payment && !!n.is_outgoing)
+ // Only evaluate forwards where the inbound channel exists in channels
+ .filter(htlc => channels.find(channel => channel.id === htlc.in_channel))
+ // Only evaluate forwards wherre the inbound payment exists in the payments
+ .filter(htlc => forwards.find(n => n.payment === htlc.in_payment));
+
+ // HTLCs that are being routed
+ const forwarding = outbound.map(htlc => {
+ const inboundChannel = channels.find(n => n.id === htlc.in_channel);
+ const inboundPayment = forwards.find(n => n.payment === htlc.in_payment);
+ const outboundChannel = channels.find(n => n.id === htlc.channel);
+
+ return {
+ fee: inboundPayment.tokens - htlc.tokens,
+ in_peer: inboundChannel.partner_public_key,
+ out_peer: outboundChannel.partner_public_key,
+ payment: htlc.id,
+ timeout: inboundPayment.timeout,
+ tokens: htlc.tokens,
+ };
+ });
+
+ return { forwarding, sending };
+};
+
+export default pendingPayments;
diff --git a/src/server/commands/grpc_utils/pending_summary.ts b/src/server/commands/grpc_utils/pending_summary.ts
new file mode 100644
index 0000000..30c7ed0
--- /dev/null
+++ b/src/server/commands/grpc_utils/pending_summary.ts
@@ -0,0 +1,166 @@
+import { DateTime } from 'luxon';
+import formatTokens from './format_tokens';
+import icons from './icons';
+
+const asRelative = n => n.toRelative({ locale: 'en' });
+const blocksAsEpoch = blocks => Date.now() + blocks * 1000 * 60 * 10;
+const escape = text => text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\$&');
+const flatten = arr => [].concat(...arr);
+const fromNow = ms => (!ms ? undefined : DateTime.fromMillis(ms));
+const nodeAlias = (alias, id) => `${alias} ${id.substring(0, 8)}`.trim();
+const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
+const uniq = arr => Array.from(new Set(arr));
+
+/** Notify of pending channels and HTLCs
+ {
+ count:
+ htlcs: [{
+ forwarding: [{
+ fee:
+ in_peer:
+ out_peer:
+ tokens:
+ }]
+ from:
+ nodes: [{
+ alias:
+ id:
+ }]
+ sending: [{
+ out_peer:
+ }]
+ }]
+ pending: [{
+ closing: [{
+ partner_public_key:
+ pending_balance:
+ timelock_expiration:
+ }]
+ from:
+ height:
+ nodes: [{
+ alias:
+ id:
+ }]
+ opening: [{
+ is_partner_initiated:
+ local_balance:
+ partner_public_key:
+ remote_balance:
+ transaction_fee:
+ transaction_id:
+ }]
+ }]
+ }
+ @returns
+
+*/
+const pendingSummary = ({ htlcs, pending }) => {
+ // Pending closing and opening channels
+ const channels = pending.map(node => {
+ // Opening channels, waiting for confirmation
+ const openingChannels = node.opening.map(opening => {
+ const direction = !!opening.is_partner_initiated ? 'in' : 'out';
+ const funds = [opening.local_balance, opening.remote_balance];
+ const peerId = opening.partner_public_key;
+ const tx = opening.transaction_id;
+ const waiting = `${icons.opening} Waiting`;
+
+ const capacity = sumOf(funds.concat(opening.transaction_fee));
+ const peer = node.nodes.find(n => n.id === peerId);
+
+ const alias = escape(nodeAlias(peer.alias, peer.id));
+ const channel = `${formatTokens({ tokens: capacity }).display} channel`;
+
+ const action = `${direction}bound ${channel}`;
+
+ return `${waiting} for ${action} with ${alias} to confirm: \`${tx}\``;
+ });
+
+ // Closing channels, waiting for coins to return
+ const waitingOnFunds = node.closing
+ .filter(n => !!n.timelock_expiration && !!n.pending_balance)
+ .filter(n => n.timelock_expiration > node.height)
+ .map(closing => {
+ const funds = formatTokens({ tokens: closing.pending_balance }).display;
+ const peerId = closing.partner_public_key;
+ const waitBlocks = closing.timelock_expiration - node.height;
+ const waiting = `${icons.closing} Waiting`;
+
+ const peer = node.nodes.find(n => n.id === peerId);
+ const time = asRelative(fromNow(blocksAsEpoch(waitBlocks)));
+
+ const action = `recover ${funds} ${time} from closing channel`;
+ const alias = nodeAlias(peer.alias, peer.id);
+
+ return `${waiting} to ${action} with ${alias}`;
+ });
+
+ return {
+ from: node.from,
+ channels: flatten([].concat(openingChannels).concat(waitingOnFunds)),
+ };
+ });
+
+ // HTLCs in flight for probing or for forwarding
+ const payments = htlcs.map(node => {
+ // Forwarding an HTLC in one peer and out another
+ const forwarding = node.forwarding.map(forward => {
+ const fee = formatTokens({ tokens: forward.fee }).display;
+ const from = node.nodes.find(n => n.id === forward.in_peer);
+ const to = node.nodes.find(n => n.id === forward.out_peer);
+ const tokens = formatTokens({ tokens: forward.tokens }).display;
+
+ const action = `${tokens} for ${fee} fee`;
+ const forwarding = `${icons.forwarding} Forwarding`;
+ const inPeer = nodeAlias(from.alias, from.id);
+ const outPeer = nodeAlias(to.alias, to.id);
+
+ return `${forwarding} ${action} from ${inPeer} to ${outPeer}`;
+ });
+
+ // Probing out peers
+ const probes = uniq(node.sending.map(n => n.out_peer)).map(key => {
+ const out = node.nodes.find(n => n.id === key);
+
+ return nodeAlias(out.alias, out.id);
+ });
+
+ const probing = !probes.length ? [] : [`${icons.probe} Probing out ${probes.join(', ')}`];
+
+ return { from: node.from, payments: [].concat(forwarding).concat(probing) };
+ });
+
+ const nodes = [];
+
+ // Pending channels for a node
+ channels
+ .filter(node => !!node.channels.length)
+ .forEach(node => {
+ return node.channels.forEach(item => nodes.push({ item, from: node.from }));
+ });
+
+ // Pending payments for a node
+ payments
+ .filter(n => !!n.payments.length)
+ .forEach(node => {
+ return node.payments.forEach(item => nodes.push({ item, from: node.from }));
+ });
+
+ // Exit early when there is nothing pending for any nodes
+ if (!nodes.length) {
+ return [`${icons.bot} No pending payments or channels`];
+ }
+
+ const sections = uniq(nodes.map(n => n.from));
+
+ return flatten(
+ sections.map(from => {
+ const title = [];
+
+ return title.concat(nodes.filter(n => n.from === from).map(n => n.item));
+ })
+ );
+};
+
+export default pendingSummary;
diff --git a/src/server/modules/grpc/grpc.controller.ts b/src/server/modules/grpc/grpc.controller.ts
index 6f69b11..bfcc28d 100644
--- a/src/server/modules/grpc/grpc.controller.ts
+++ b/src/server/modules/grpc/grpc.controller.ts
@@ -1,11 +1,16 @@
-import { Controller, Get, Query } from '@nestjs/common';
-import { grpcDto } from '~shared/commands.dto';
+import { Body, Controller, Get, Post, Query } from '@nestjs/common';
+import { getPendingDto, grpcDto } from '~shared/commands.dto';
import { GrpcService } from './grpc.service';
@Controller()
export class GrpcController {
constructor(private grpcService: GrpcService) {}
+ @Get('api/grpc/get-channel-balance')
+ async getChannelBalance(@Query() args: grpcDto) {
+ return this.grpcService.getChannelBalance(args);
+ }
+
@Get('api/grpc/get-peers')
async getPeers(@Query() args: grpcDto) {
return this.grpcService.getPeers(args);
@@ -16,18 +21,18 @@ export class GrpcController {
return this.grpcService.getPeersAllNodes();
}
- @Get('api/grpc/get-wallet-info')
- async getWalletInfo(@Query() args: grpcDto) {
- return this.grpcService.getWalletInfo(args);
- }
-
- @Get('api/grpc/get-channel-balance')
- async getChannelBalance(@Query() args: grpcDto) {
- return this.grpcService.getChannelBalance(args);
+ @Post('api/grpc/get-pending')
+ async getPending(@Body() args: getPendingDto) {
+ return this.grpcService.getPending(args);
}
@Get('api/grpc/get-saved-nodes')
async getSavedNodes() {
return this.grpcService.getSavedNodes();
}
+
+ @Get('api/grpc/get-wallet-info')
+ async getWalletInfo(@Query() args: grpcDto) {
+ return this.grpcService.getWalletInfo(args);
+ }
}
diff --git a/src/server/modules/grpc/grpc.service.ts b/src/server/modules/grpc/grpc.service.ts
index a749dbe..31076f6 100644
--- a/src/server/modules/grpc/grpc.service.ts
+++ b/src/server/modules/grpc/grpc.service.ts
@@ -1,11 +1,12 @@
import { AuthenticatedLnd, GetChannelBalanceResult, GetWalletInfoResult } from 'lightning';
import { channelBalance, walletInfo } from '~server/commands/grpc_utils/grpc_utils';
+import { getPendingDto, grpcDto } from '~shared/commands.dto';
import { Injectable } from '@nestjs/common';
import { LndService } from '../lnd/lnd.service';
import getPeers from '~server/commands/grpc_utils/get_peers';
+import { getPending } from '~server/commands/grpc_utils';
import { getSavedNodes } from '~server/lnd';
-import { grpcDto } from '~shared/commands.dto';
import { map } from 'async';
type GetPeersArgs = {
@@ -14,6 +15,13 @@ type GetPeersArgs = {
@Injectable()
export class GrpcService {
+ async getChannelBalance(args: grpcDto): Promise<{ result: GetChannelBalanceResult }> {
+ const lnd = await LndService.authenticatedLnd({ node: args.node });
+
+ const { result } = await channelBalance({ lnd });
+ return { result };
+ }
+
async getPeers(args: GetPeersArgs): Promise {
const lnd = await LndService.authenticatedLnd({ node: args.node });
@@ -37,22 +45,23 @@ export class GrpcService {
return { result };
}
- async getWalletInfo(args: grpcDto): Promise<{ result: GetWalletInfoResult }> {
+ async getPending(args: getPendingDto): Promise {
const lnd = await LndService.authenticatedLnd({ node: args.node });
- const { result } = await walletInfo({ lnd });
- return { result };
- }
+ const result = await getPending({ lnd });
- async getChannelBalance(args: grpcDto): Promise<{ result: GetChannelBalanceResult }> {
- const lnd = await LndService.authenticatedLnd({ node: args.node });
-
- const { result } = await channelBalance({ lnd });
- return { result };
+ return result;
}
async getSavedNodes(): Promise<{ result: any }> {
const nodes = await getSavedNodes({});
return { result: nodes.nodes };
}
+
+ async getWalletInfo(args: grpcDto): Promise<{ result: GetWalletInfoResult }> {
+ const lnd = await LndService.authenticatedLnd({ node: args.node });
+
+ const { result } = await walletInfo({ lnd });
+ return { result };
+ }
}
diff --git a/src/shared/commands.dto.ts b/src/shared/commands.dto.ts
index 88bae5a..ee9f696 100644
--- a/src/shared/commands.dto.ts
+++ b/src/shared/commands.dto.ts
@@ -432,6 +432,12 @@ export class forwardsDto {
to: string;
}
+export class getPendingDto {
+ @IsOptional()
+ @IsString()
+ node: string;
+}
+
export class graphDto {
@Transform(({ value }) => toStringArray(value))
@IsOptional()
From dd3e15da787a6f1979164a72458c45fefb1da6c0 Mon Sep 17 00:00:00 2001
From: Nitesh Balusu <84944042+niteshbalusu11@users.noreply.github.com>
Date: Tue, 8 Nov 2022 09:58:16 -0500
Subject: [PATCH 2/2] make table title dynamic
---
src/client/dashboard/PendingChart.tsx | 2 +-
.../standard_components/app-components/BasicTable.tsx | 7 +++++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/client/dashboard/PendingChart.tsx b/src/client/dashboard/PendingChart.tsx
index 16388b7..b827c84 100644
--- a/src/client/dashboard/PendingChart.tsx
+++ b/src/client/dashboard/PendingChart.tsx
@@ -31,7 +31,7 @@ const PendingChart = () => {
}, []);
resgisterCharts();
- return !!data && !!data.length ? : null;
+ return !!data && !!data.length ? : null;
};
export default PendingChart;
diff --git a/src/client/standard_components/app-components/BasicTable.tsx b/src/client/standard_components/app-components/BasicTable.tsx
index c8b07d8..0472f46 100644
--- a/src/client/standard_components/app-components/BasicTable.tsx
+++ b/src/client/standard_components/app-components/BasicTable.tsx
@@ -8,14 +8,17 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
+// Creates a basic table with rows and title.
+
function createData(data: string) {
return { data };
}
type Args = {
rows: string[];
+ title: string;
};
-const BasicTable = ({ rows }: Args) => {
+const BasicTable = ({ rows, title }: Args) => {
const data = rows.map(n => createData(n));
return (
@@ -23,7 +26,7 @@ const BasicTable = ({ rows }: Args) => {
- Pending Things
+ {title}