Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
feat: channel visualization (#498)
Browse files Browse the repository at this point in the history
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
  • Loading branch information
reneaaron and rolznz committed Jun 25, 2024
1 parent 9fdd71b commit 7d47058
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 43 deletions.
1 change: 1 addition & 0 deletions frontend/src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const badgeVariants = cva(
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
positive: "border-transparent bg-positive text-positive-foreground",
outline: "text-foreground",
},
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export const ALBY_MIN_BALANCE = Math.ceil(
);

export const ONCHAIN_DUST_SATS = 1000;
export const ALBY_HIDE_HOSTED_BALANCE_BELOW = 100;
87 changes: 76 additions & 11 deletions frontend/src/screens/channels/Channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ExternalLinkIcon,
HandCoins,
Hotel,
InfoIcon,
MoreHorizontal,
Trash2,
Unplug,
Expand Down Expand Up @@ -35,6 +36,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "src/components/ui/dropdown-menu.tsx";
import { Progress } from "src/components/ui/progress.tsx";
import {
Table,
TableBody,
Expand All @@ -43,8 +45,17 @@ import {
TableHeader,
TableRow,
} from "src/components/ui/table.tsx";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "src/components/ui/tooltip.tsx";
import { toast } from "src/components/ui/use-toast.ts";
import { ONCHAIN_DUST_SATS } from "src/constants.ts";
import {
ALBY_HIDE_HOSTED_BALANCE_BELOW as ALBY_HIDE_HOSTED_BALANCE_LIMIT,
ONCHAIN_DUST_SATS,
} from "src/constants.ts";
import { useAlbyBalance } from "src/hooks/useAlbyBalance.ts";
import { useBalances } from "src/hooks/useBalances.ts";
import { useChannels } from "src/hooks/useChannels";
Expand All @@ -53,7 +64,7 @@ import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo.ts";
import { useRedeemOnchainFunds } from "src/hooks/useRedeemOnchainFunds.ts";
import { useSyncWallet } from "src/hooks/useSyncWallet.ts";
import { copyToClipboard } from "src/lib/clipboard.ts";
import { formatAmount } from "src/lib/utils.ts";
import { cn, formatAmount } from "src/lib/utils.ts";
import {
Channel,
CloseChannelResponse,
Expand Down Expand Up @@ -248,6 +259,9 @@ export default function Channels() {
}
}

const showHostedBalance =
albyBalance && albyBalance.sats > ALBY_HIDE_HOSTED_BALANCE_LIMIT;

return (
<>
<AppHeader
Expand Down Expand Up @@ -333,8 +347,13 @@ export default function Channels() {
}
></AppHeader>

<div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
{albyBalance && albyBalance?.sats >= 100 && (
<div
className={cn(
"grid grid-cols-1 gap-3",
showHostedBalance ? "xl:grid-cols-4" : "lg:grid-cols-3"
)}
>
{showHostedBalance && (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Expand Down Expand Up @@ -464,8 +483,30 @@ export default function Channels() {
<TableHead className="w-[80px]">Status</TableHead>
<TableHead>Node</TableHead>
<TableHead className="w-[150px]">Capacity</TableHead>
<TableHead className="w-[150px]">Inbound</TableHead>
<TableHead className="w-[150px]">Outbound</TableHead>
<TableHead className="w-[150px]">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<div className="flex flex-row gap-2 items-center">
Reserve
<InfoIcon className="h-4 w-4 shrink-0" />
</div>
</TooltipTrigger>
<TooltipContent className="w-[400px]">
Funds each participant sets aside to discourage cheating
by ensuring each party has something at stake. This
reserve cannot be spent during the channel's lifetime
and typically amounts to 1% of the channel capacity.
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableHead>
<TableHead className="w-[300px]">
<div className="flex flex-row justify-between items-center">
<div>Spending</div>
<div>Receiving</div>
</div>
</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
Expand All @@ -484,7 +525,7 @@ export default function Channels() {
<TableRow key={channel.id}>
<TableCell>
{channel.active ? (
<Badge>Online</Badge>
<Badge variant="positive">Online</Badge>
) : (
<Badge variant="outline">Offline</Badge>
)}{" "}
Expand All @@ -504,12 +545,36 @@ export default function Channels() {
{channel.public ? "Public" : "Private"}
</Badge>
</TableCell>
<TableCell>{formatAmount(capacity)} sats</TableCell>
<TableCell>
{formatAmount(channel.remoteBalance)} sats
<TableCell title={capacity / 1000 + " sats"}>
{formatAmount(capacity)} sats
</TableCell>
<TableCell
title={channel.unspendablePunishmentReserve + " sats"}
>
{formatAmount(
channel.unspendablePunishmentReserve * 1000
)}{" "}
sats
</TableCell>
<TableCell>
{formatAmount(channel.localBalance)} sats
<div className="relative">
<Progress
value={(channel.localBalance / capacity) * 100}
className="h-6 absolute"
/>
<div className="flex flex-row w-full justify-between px-2 text-xs items-center h-6 mix-blend-exclusion text-white">
<span
title={channel.localBalance / 1000 + " sats"}
>
{formatAmount(channel.localBalance)} sats
</span>
<span
title={channel.remoteBalance / 1000 + " sats"}
>
{formatAmount(channel.remoteBalance)} sats
</span>
</div>
</div>
</TableCell>
<TableCell>
<DropdownMenu>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export type Channel = {
confirmations?: number;
confirmationsRequired?: number;
forwardingFeeBaseMsat: number;
unspendablePunishmentReserve: number;
counterpartyUnspendablePunishmentReserve: number;
};

export type UpdateChannelRequest = {
Expand Down
29 changes: 18 additions & 11 deletions lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,18 +792,25 @@ func (ls *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, err
"ForwardingFeeProportionalMillionths": ldkChannel.Config.ForwardingFeeProportionalMillionths(),
}

unspendablePunishmentReserve := uint64(0)
if ldkChannel.UnspendablePunishmentReserve != nil {
unspendablePunishmentReserve = *ldkChannel.UnspendablePunishmentReserve
}

channels = append(channels, lnclient.Channel{
InternalChannel: internalChannel,
LocalBalance: int64(ldkChannel.OutboundCapacityMsat),
RemoteBalance: int64(ldkChannel.InboundCapacityMsat),
RemotePubkey: ldkChannel.CounterpartyNodeId,
Id: ldkChannel.UserChannelId, // CloseChannel takes the UserChannelId
Active: ldkChannel.IsUsable, // superset of ldkChannel.IsReady
Public: ldkChannel.IsPublic,
FundingTxId: fundingTxId,
Confirmations: ldkChannel.Confirmations,
ConfirmationsRequired: ldkChannel.ConfirmationsRequired,
ForwardingFeeBaseMsat: ldkChannel.Config.ForwardingFeeBaseMsat(),
InternalChannel: internalChannel,
LocalBalance: int64(ldkChannel.OutboundCapacityMsat),
RemoteBalance: int64(ldkChannel.InboundCapacityMsat),
RemotePubkey: ldkChannel.CounterpartyNodeId,
Id: ldkChannel.UserChannelId, // CloseChannel takes the UserChannelId
Active: ldkChannel.IsUsable, // superset of ldkChannel.IsReady
Public: ldkChannel.IsPublic,
FundingTxId: fundingTxId,
Confirmations: ldkChannel.Confirmations,
ConfirmationsRequired: ldkChannel.ConfirmationsRequired,
ForwardingFeeBaseMsat: ldkChannel.Config.ForwardingFeeBaseMsat(),
UnspendablePunishmentReserve: unspendablePunishmentReserve,
CounterpartyUnspendablePunishmentReserve: ldkChannel.CounterpartyUnspendablePunishmentReserve,
})
}

Expand Down
22 changes: 12 additions & 10 deletions lnclient/lnd/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,18 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
confirmations := nodeInfo.BlockHeight - uint32(channelOpeningBlockHeight)

channels[i] = lnclient.Channel{
InternalChannel: lndChannel,
LocalBalance: lndChannel.LocalBalance * 1000,
RemoteBalance: lndChannel.RemoteBalance * 1000,
RemotePubkey: lndChannel.RemotePubkey,
Id: strconv.FormatUint(lndChannel.ChanId, 10),
Active: lndChannel.Active,
Public: !lndChannel.Private,
FundingTxId: channelPoint.GetFundingTxidStr(),
Confirmations: &confirmations,
ConfirmationsRequired: &confirmationsRequired,
InternalChannel: lndChannel,
LocalBalance: lndChannel.LocalBalance * 1000,
RemoteBalance: lndChannel.RemoteBalance * 1000,
RemotePubkey: lndChannel.RemotePubkey,
Id: strconv.FormatUint(lndChannel.ChanId, 10),
Active: lndChannel.Active,
Public: !lndChannel.Private,
FundingTxId: channelPoint.GetFundingTxidStr(),
Confirmations: &confirmations,
ConfirmationsRequired: &confirmationsRequired,
UnspendablePunishmentReserve: lndChannel.LocalConstraints.ChanReserveSat,
CounterpartyUnspendablePunishmentReserve: lndChannel.RemoteConstraints.ChanReserveSat,
}
}

Expand Down
24 changes: 13 additions & 11 deletions lnclient/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,19 @@ type LNClient interface {
}

type Channel struct {
LocalBalance int64 `json:"localBalance"`
RemoteBalance int64 `json:"remoteBalance"`
Id string `json:"id"`
RemotePubkey string `json:"remotePubkey"`
FundingTxId string `json:"fundingTxId"`
Active bool `json:"active"`
Public bool `json:"public"`
InternalChannel interface{} `json:"internalChannel"`
Confirmations *uint32 `json:"confirmations"`
ConfirmationsRequired *uint32 `json:"confirmationsRequired"`
ForwardingFeeBaseMsat uint32 `json:"forwardingFeeBaseMsat"`
LocalBalance int64 `json:"localBalance"`
RemoteBalance int64 `json:"remoteBalance"`
Id string `json:"id"`
RemotePubkey string `json:"remotePubkey"`
FundingTxId string `json:"fundingTxId"`
Active bool `json:"active"`
Public bool `json:"public"`
InternalChannel interface{} `json:"internalChannel"`
Confirmations *uint32 `json:"confirmations"`
ConfirmationsRequired *uint32 `json:"confirmationsRequired"`
ForwardingFeeBaseMsat uint32 `json:"forwardingFeeBaseMsat"`
UnspendablePunishmentReserve uint64 `json:"unspendablePunishmentReserve"`
CounterpartyUnspendablePunishmentReserve uint64 `json:"counterpartyUnspendablePunishmentReserve"`
}

type NodeStatus struct {
Expand Down

0 comments on commit 7d47058

Please sign in to comment.