Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: edit routing fee dialog #427

Merged
merged 14 commits into from
Aug 21, 2024
Merged
110 changes: 110 additions & 0 deletions frontend/src/components/RoutingFeeDialogContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ExternalLinkIcon } from "lucide-react";
import React from "react";
import ExternalLink from "src/components/ExternalLink";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { toast } from "src/components/ui/use-toast";
import { useChannels } from "src/hooks/useChannels";
import { Channel, UpdateChannelRequest } from "src/types";
import { request } from "src/utils/request";
import {
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog";

type Props = {
channel: Channel;
};

export function RoutingFeeDialogContent({ channel }: Props) {
const currentFee: number = React.useMemo(() => {
return Math.floor(channel.forwardingFeeBaseMsat / 1000);
}, [channel.forwardingFeeBaseMsat]);
const [forwardingFee, setForwardingFee] = React.useState(
currentFee ? currentFee.toString() : ""
);
const { mutate: reloadChannels } = useChannels();

async function updateFee() {
try {
const forwardingFeeBaseMsat = +forwardingFee * 1000;

console.info(
`🎬 Updating channel ${channel.id} with ${channel.remotePubkey}`
);

await request(
`/api/peers/${channel.remotePubkey}/channels/${channel.id}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
forwardingFeeBaseMsat: forwardingFeeBaseMsat,
} as UpdateChannelRequest),
}
);

await reloadChannels();
toast({ title: "Sucessfully updated channel" });
} catch (error) {
console.error(error);
toast({
variant: "destructive",
description: "Something went wrong: " + error,
});
}
}

return (
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Update Channel Routing Fee</AlertDialogTitle>
<AlertDialogDescription>
<p className="mb-4">
Adjust the fee you charge for each payment routed through this
channel. A high fee (e.g. 100,000 sats) can be set to prevent
unwanted routing. No matter the fee, you can still receive payments.{" "}
</p>
<Label htmlFor="fee" className="block mb-2">
Routing Fee (sats)
</Label>
<Input
id="fee"
name="fee"
type="number"
required
autoFocus
min={0}
value={forwardingFee}
onChange={(e) => {
setForwardingFee(e.target.value.trim());
}}
/>
<ExternalLink
to="https://guides.getalby.com/user-guide/v/alby-account-and-browser-extension/alby-hub/faq-alby-hub/how-can-i-change-routing-fees#understanding-routing-fees-and-alby-hub"
className="underline flex items-center mt-4"
>
Learn more about routing fees
<ExternalLinkIcon className="w-4 h-4 ml-2" />
</ExternalLink>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={(parseInt(forwardingFee) || 0) == currentFee}
onClick={updateFee}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
);
}
38 changes: 27 additions & 11 deletions frontend/src/components/channels/ChannelDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
MoreHorizontal,
Trash2,
} from "lucide-react";
import React from "react";
import { CloseChannelDialogContent } from "src/components/CloseChannelDialogContent";
import ExternalLink from "src/components/ExternalLink";
import { RoutingFeeDialogContent } from "src/components/RoutingFeeDialogContent";
import {
AlertDialog,
AlertDialogTrigger,
Expand All @@ -22,16 +24,22 @@ import { Channel } from "src/types";
type ChannelDropdownMenuProps = {
alias: string;
channel: Channel;
editChannel(channel: Channel): void;
};

export function ChannelDropdownMenu({
alias,
channel,
editChannel,
}: ChannelDropdownMenuProps) {
const [dialog, setDialog] = React.useState<"closeChannel" | "routingFee">();

return (
<AlertDialog>
<AlertDialog
onOpenChange={() => {
if (!open) {
setDialog(undefined);
}
}}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="icon" variant="ghost">
Expand All @@ -58,23 +66,31 @@ export function ChannelDropdownMenu({
</ExternalLink>
</DropdownMenuItem>
{channel.public && (
<AlertDialogTrigger asChild>
<DropdownMenuItem
className="flex flex-row items-center gap-2 cursor-pointer"
onClick={() => setDialog("routingFee")}
>
<HandCoins className="h-4 w-4" />
Set Routing Fee
</DropdownMenuItem>
</AlertDialogTrigger>
)}
<AlertDialogTrigger asChild>
<DropdownMenuItem
className="flex flex-row items-center gap-2 cursor-pointer"
onClick={() => editChannel(channel)}
onClick={() => setDialog("closeChannel")}
>
<HandCoins className="h-4 w-4" />
Set Routing Fee
</DropdownMenuItem>
)}
<AlertDialogTrigger asChild>
<DropdownMenuItem className="flex flex-row items-center gap-2 cursor-pointer">
<Trash2 className="h-4 w-4 text-destructive" />
Close Channel
</DropdownMenuItem>
</AlertDialogTrigger>
</DropdownMenuContent>
</DropdownMenu>
<CloseChannelDialogContent alias={alias} channel={channel} />
{dialog === "closeChannel" && (
<CloseChannelDialogContent alias={alias} channel={channel} />
)}
{dialog === "routingFee" && <RoutingFeeDialogContent channel={channel} />}
</AlertDialog>
);
}
13 changes: 2 additions & 11 deletions frontend/src/components/channels/ChannelsCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,9 @@ import { Channel, Node } from "src/types";
type ChannelsCardsProps = {
channels?: Channel[];
nodes?: Node[];
editChannel(channel: Channel): void;
};

export function ChannelsCards({
channels,
nodes,
editChannel,
}: ChannelsCardsProps) {
export function ChannelsCards({ channels, nodes }: ChannelsCardsProps) {
if (!channels?.length) {
return null;
}
Expand Down Expand Up @@ -61,11 +56,7 @@ export function ChannelsCards({
<div className="flex-1 whitespace-nowrap text-ellipsis overflow-hidden">
{alias}
</div>
<ChannelDropdownMenu
alias={alias}
channel={channel}
editChannel={editChannel}
/>
<ChannelDropdownMenu alias={alias} channel={channel} />
</div>
</CardTitle>
<Separator className="mt-5" />
Expand Down
13 changes: 2 additions & 11 deletions frontend/src/components/channels/ChannelsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@ import { ChannelDropdownMenu } from "./ChannelDropdownMenu";
type ChannelsTableProps = {
channels?: Channel[];
nodes?: Node[];
editChannel(channel: Channel): void;
};

export function ChannelsTable({
channels,
nodes,
editChannel,
}: ChannelsTableProps) {
export function ChannelsTable({ channels, nodes }: ChannelsTableProps) {
if (channels && !channels.length) {
return null;
}
Expand Down Expand Up @@ -157,11 +152,7 @@ export function ChannelsTable({
<ChannelWarning channel={channel} />
</TableCell>
<TableCell>
<ChannelDropdownMenu
alias={alias}
channel={channel}
editChannel={editChannel}
/>
<ChannelDropdownMenu alias={alias} channel={channel} />
</TableCell>
</TableRow>
);
Expand Down
56 changes: 4 additions & 52 deletions frontend/src/screens/channels/Channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ import { useRedeemOnchainFunds } from "src/hooks/useRedeemOnchainFunds.ts";
import { useSyncWallet } from "src/hooks/useSyncWallet.ts";
import { copyToClipboard } from "src/lib/clipboard.ts";
import { cn } from "src/lib/utils.ts";
import { Channel, Node, UpdateChannelRequest } from "src/types";
import { Channel, Node } from "src/types";
import { request } from "src/utils/request";

export default function Channels() {
useSyncWallet();
const { data: channels, mutate: reloadChannels } = useChannels();
const { data: channels } = useChannels();
const { data: nodeConnectionInfo } = useNodeConnectionInfo();
const { data: balances } = useBalances();
const { data: albyBalance, mutate: reloadAlbyBalance } = useAlbyBalance();
Expand Down Expand Up @@ -108,46 +108,6 @@ export default function Channels() {
loadNodeStats();
}, [loadNodeStats]);

async function editChannel(channel: Channel) {
try {
const forwardingFeeBaseSats = prompt(
"Enter base forwarding fee in sats",
Math.floor(channel.forwardingFeeBaseMsat / 1000).toString()
);

if (!forwardingFeeBaseSats) {
return;
}

const forwardingFeeBaseMsat = +forwardingFeeBaseSats * 1000;

console.info(
`🎬 Updating channel ${channel.id} with ${channel.remotePubkey}`
);

await request(
`/api/peers/${channel.remotePubkey}/channels/${channel.id}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
forwardingFeeBaseMsat: forwardingFeeBaseMsat,
} as UpdateChannelRequest),
}
);
await reloadChannels();
toast({ title: "Sucessfully updated channel" });
} catch (error) {
console.error(error);
toast({
variant: "destructive",
description: "Something went wrong: " + error,
});
}
}

async function resetRouter() {
try {
const key = prompt(
Expand Down Expand Up @@ -530,17 +490,9 @@ export default function Channels() {
)}

{isDesktop ? (
<ChannelsTable
channels={channels}
nodes={nodes}
editChannel={editChannel}
/>
<ChannelsTable channels={channels} nodes={nodes} />
) : (
<ChannelsCards
channels={channels}
nodes={nodes}
editChannel={editChannel}
/>
<ChannelsCards channels={channels} nodes={nodes} />
)}
</>
);
Expand Down
17 changes: 17 additions & 0 deletions lnclient/lnd/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,22 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
channelOpeningBlockHeight := lndChannel.ChanId >> 40
confirmations := nodeInfo.BlockHeight - uint32(channelOpeningBlockHeight)

var forwardingFee uint32
if !lndChannel.Private {
channelEdge, err := svc.client.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
ChanId: lndChannel.ChanId,
})
if err != nil {
return nil, err
}

if channelEdge.Node1Pub == nodeInfo.IdentityPubkey {
forwardingFee = uint32(channelEdge.Node1Policy.FeeBaseMsat)
} else {
forwardingFee = uint32(channelEdge.Node2Policy.FeeBaseMsat)
}
}

channels[i] = lnclient.Channel{
InternalChannel: lndChannel,
LocalBalance: lndChannel.LocalBalance * 1000,
Expand All @@ -191,6 +207,7 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
UnspendablePunishmentReserve: lndChannel.LocalConstraints.ChanReserveSat,
CounterpartyUnspendablePunishmentReserve: lndChannel.RemoteConstraints.ChanReserveSat,
IsOutbound: lndChannel.Initiator,
ForwardingFeeBaseMsat: forwardingFee,
}
}

Expand Down
Loading