mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
remove transfer alby hosted balance + new designs for node table (#1058)
* feat: update ui for balance cards in node page * chore: lower icons size * feat: remove hosted balance card and transfer funds * fix: remove /api/alby/drain * chore: change variable name * fix: remove migrateChannel variable * chore: minor stylings * fix: remove canMigrateFunds * chore: text size for channels table * chore: update copy in firstChannel * fix: update copy to mention fee credits * fix: remove albyBalance check from useOnboardingData * fix: fee credits copy, add link to fee credits guide * chore: improve channels cards stying --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
parent
d10641aee4
commit
cf01b485ed
12 changed files with 97 additions and 360 deletions
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
|
@ -31,7 +30,6 @@ import (
|
|||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/nip47/permissions"
|
||||
"github.com/getAlby/hub/service/keys"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
"github.com/getAlby/hub/utils"
|
||||
"github.com/getAlby/hub/version"
|
||||
)
|
||||
|
|
@ -485,45 +483,6 @@ func (svc *albyOAuthService) GetBalance(ctx context.Context) (*AlbyBalance, erro
|
|||
return balance, nil
|
||||
}
|
||||
|
||||
func (svc *albyOAuthService) DrainSharedWallet(ctx context.Context, lnClient lnclient.LNClient) error {
|
||||
balance, err := svc.GetBalance(ctx)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to fetch shared balance")
|
||||
return err
|
||||
}
|
||||
|
||||
balanceSat := float64(balance.Balance)
|
||||
|
||||
amountSat := int64(math.Floor(
|
||||
balanceSat- // Alby shared node balance in sats
|
||||
(balanceSat*(8.0/1000.0))- // Alby service fee (0.8%)
|
||||
(balanceSat*0.01))) - // Maximum potential routing fees (1%)
|
||||
10 // Alby fee reserve (10 sats)
|
||||
|
||||
if amountSat < 1 {
|
||||
return errors.New("not enough balance remaining")
|
||||
}
|
||||
// limit the maximum to 1M sats to ensure the funds can easily be migrated
|
||||
// the user can migrate more if they still have sats left over
|
||||
amountSat = min(amountSat, 1_000_000)
|
||||
amount := uint64(amountSat * 1000)
|
||||
|
||||
logger.Logger.WithField("amount", amount).WithError(err).Error("Draining Alby shared wallet funds")
|
||||
|
||||
transaction, err := transactions.NewTransactionsService(svc.db, svc.eventPublisher).MakeInvoice(ctx, amount, "Send shared wallet funds to Alby Hub", "", 120, nil, lnClient, nil, nil)
|
||||
if err != nil {
|
||||
logger.Logger.WithField("amount", amount).WithError(err).Error("Failed to make invoice")
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.SendPayment(ctx, transaction.PaymentRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithField("amount", amount).WithError(err).Error("Failed to pay invoice from shared node")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *albyOAuthService) SendPayment(ctx context.Context, invoice string) error {
|
||||
token, err := svc.fetchUserToken(ctx)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ type AlbyOAuthService interface {
|
|||
GetBalance(ctx context.Context) (*AlbyBalance, error)
|
||||
GetMe(ctx context.Context) (*AlbyMe, error)
|
||||
SendPayment(ctx context.Context, invoice string) error
|
||||
DrainSharedWallet(ctx context.Context, lnClient lnclient.LNClient) error
|
||||
UnlinkAccount(ctx context.Context) error
|
||||
RequestAutoChannel(ctx context.Context, lnClient lnclient.LNClient, isPublic bool) (*AutoChannelResponse, error)
|
||||
GetVssAuthToken(ctx context.Context, nodeIdentifier string) (string, error)
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
import React from "react";
|
||||
import { ButtonProps, LoadingButton } from "src/components/ui/loading-button";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { AlbyBalance, Channel } from "src/types";
|
||||
import { request } from "src/utils/request";
|
||||
|
||||
type TransferFundsButtonProps = {
|
||||
channels: Channel[] | undefined;
|
||||
albyBalance: AlbyBalance;
|
||||
onTransferComplete: () => Promise<unknown>;
|
||||
} & ButtonProps;
|
||||
|
||||
export function TransferFundsButton({
|
||||
channels,
|
||||
albyBalance,
|
||||
onTransferComplete,
|
||||
children,
|
||||
...props
|
||||
}: TransferFundsButtonProps) {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
if (!albyBalance) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!channels?.some(
|
||||
(channel) => channel.remoteBalance / 1000 > albyBalance.sats
|
||||
)
|
||||
) {
|
||||
toast({
|
||||
title: "Please increase your receiving capacity first",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await request("/api/alby/drain", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
await onTransferComplete();
|
||||
toast({
|
||||
title:
|
||||
"🎉 Funds from Alby shared wallet transferred to your Alby Hub!",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: "Something went wrong: " + error,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
CardTitle,
|
||||
} from "src/components/ui/card";
|
||||
import { Progress } from "src/components/ui/progress.tsx";
|
||||
import { Separator } from "src/components/ui/separator";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
|
|
@ -32,8 +33,10 @@ export function ChannelsCards({ channels, nodes }: ChannelsCardsProps) {
|
|||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader className="w-full pb-4">Channels</CardHeader>
|
||||
<div className="flex flex-col gap-4 slashed-zero p-4">
|
||||
<CardHeader className="w-full pb-2 text-2xl font-semibold">
|
||||
Channels
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{channels
|
||||
.sort((a, b) =>
|
||||
a.localBalance + a.remoteBalance >
|
||||
|
|
@ -41,7 +44,7 @@ export function ChannelsCards({ channels, nodes }: ChannelsCardsProps) {
|
|||
? -1
|
||||
: 1
|
||||
)
|
||||
.map((channel) => {
|
||||
.map((channel, index) => {
|
||||
const node = nodes?.find(
|
||||
(n) => n.public_key === channel.remotePubkey
|
||||
);
|
||||
|
|
@ -50,16 +53,22 @@ export function ChannelsCards({ channels, nodes }: ChannelsCardsProps) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{index > 0 && <Separator className="mt-6 -mb-2" />}
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<CardTitle className="w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1 whitespace-nowrap text-ellipsis font-semibold overflow-hidden">
|
||||
{alias}
|
||||
<CardHeader className="pb-4 px-0 w-full">
|
||||
<CardTitle className="w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1 whitespace-nowrap text-ellipsis font-semibold truncate leading-normal">
|
||||
{alias}
|
||||
</div>
|
||||
<ChannelDropdownMenu
|
||||
alias={alias}
|
||||
channel={channel}
|
||||
/>
|
||||
</div>
|
||||
<ChannelDropdownMenu alias={alias} channel={channel} />
|
||||
</div>
|
||||
</CardTitle>
|
||||
<CardDescription className="w-full flex flex-col gap-4 mt-4">
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardDescription className="w-full flex flex-col gap-4">
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<p className="text-muted-foreground font-medium">
|
||||
Status
|
||||
|
|
@ -152,46 +161,45 @@ export function ChannelsCards({ channels, nodes }: ChannelsCardsProps) {
|
|||
sats
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-muted-foreground font-medium text-sm">
|
||||
Spending
|
||||
</p>
|
||||
<p className="text-muted-foreground font-medium text-sm">
|
||||
Receiving
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex-1 relative">
|
||||
<Progress
|
||||
value={
|
||||
(channel.localSpendableBalance / 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.localSpendableBalance / 1000 + " sats"
|
||||
}
|
||||
>
|
||||
{formatAmount(channel.localSpendableBalance)} sats
|
||||
</span>
|
||||
<span
|
||||
title={channel.remoteBalance / 1000 + " sats"}
|
||||
>
|
||||
{formatAmount(channel.remoteBalance)} sats
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChannelWarning channel={channel} />
|
||||
</div>
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-muted-foreground font-medium text-sm">
|
||||
Spending
|
||||
</p>
|
||||
<p className="text-muted-foreground font-medium text-sm">
|
||||
Receiving
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center mt-2">
|
||||
<div className="flex-1 relative">
|
||||
<Progress
|
||||
value={
|
||||
(channel.localSpendableBalance / 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.localSpendableBalance / 1000 + " sats"
|
||||
}
|
||||
>
|
||||
{formatAmount(channel.localSpendableBalance)} sats
|
||||
</span>
|
||||
<span title={channel.remoteBalance / 1000 + " sats"}>
|
||||
{formatAmount(channel.remoteBalance)} sats
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChannelWarning channel={channel} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function ChannelsTable({ channels, nodes }: ChannelsTableProps) {
|
|||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Channels</CardTitle>
|
||||
<CardTitle className="text-2xl">Channels</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// src/hooks/useOnboardingData.ts
|
||||
|
||||
import { SUPPORT_ALBY_CONNECTION_NAME } from "src/constants";
|
||||
import { useAlbyBalance } from "src/hooks/useAlbyBalance";
|
||||
import { useAlbyMe } from "src/hooks/useAlbyMe";
|
||||
import { useApps } from "src/hooks/useApps";
|
||||
import { useChannels } from "src/hooks/useChannels";
|
||||
|
|
@ -23,7 +22,6 @@ interface UseOnboardingDataResponse {
|
|||
}
|
||||
|
||||
export const useOnboardingData = (): UseOnboardingDataResponse => {
|
||||
const { data: albyBalance } = useAlbyBalance();
|
||||
const { data: albyMe } = useAlbyMe();
|
||||
const { data: apps } = useApps();
|
||||
const { data: channels } = useChannels();
|
||||
|
|
@ -37,7 +35,7 @@ export const useOnboardingData = (): UseOnboardingDataResponse => {
|
|||
!info ||
|
||||
!nodeConnectionInfo ||
|
||||
!transactions ||
|
||||
(info.albyAccountConnected && (!albyMe || !albyBalance));
|
||||
(info.albyAccountConnected && !albyMe);
|
||||
|
||||
if (isLoading) {
|
||||
return { isLoading: true, checklistItems: [] };
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
CopyIcon,
|
||||
ExternalLinkIcon,
|
||||
Heart,
|
||||
Hotel,
|
||||
HourglassIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
|
|
@ -21,7 +20,6 @@ import { ChannelsTable } from "src/components/channels/ChannelsTable.tsx";
|
|||
import EmptyState from "src/components/EmptyState.tsx";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import FormattedFiatAmount from "src/components/FormattedFiatAmount";
|
||||
import { TransferFundsButton } from "src/components/TransferFundsButton";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
|
|
@ -31,7 +29,6 @@ import { Button } from "src/components/ui/button.tsx";
|
|||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "src/components/ui/card.tsx";
|
||||
|
|
@ -63,11 +60,7 @@ import {
|
|||
TooltipTrigger,
|
||||
} from "src/components/ui/tooltip.tsx";
|
||||
import { useToast } from "src/components/ui/use-toast.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 { ONCHAIN_DUST_SATS } from "src/constants.ts";
|
||||
import { useBalances } from "src/hooks/useBalances.ts";
|
||||
import { useChannels } from "src/hooks/useChannels";
|
||||
import { useIsDesktop } from "src/hooks/useMediaQuery.ts";
|
||||
|
|
@ -82,10 +75,9 @@ 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, mutate: reloadBalances } = useBalances();
|
||||
const { data: albyBalance, mutate: reloadAlbyBalance } = useAlbyBalance();
|
||||
const { data: balances } = useBalances();
|
||||
const navigate = useNavigate();
|
||||
const [nodes, setNodes] = React.useState<Node[]>([]);
|
||||
const [swapInAmount, setSwapInAmount] = React.useState("");
|
||||
|
|
@ -161,9 +153,6 @@ export default function Channels() {
|
|||
}, channels[0]);
|
||||
}
|
||||
|
||||
const showHostedBalance =
|
||||
albyBalance && albyBalance.sats > ALBY_HIDE_HOSTED_BALANCE_LIMIT;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader
|
||||
|
|
@ -456,53 +445,20 @@ export default function Channels() {
|
|||
<div
|
||||
className={cn("flex flex-col sm:flex-row flex-wrap gap-3 slashed-zero")}
|
||||
>
|
||||
{showHostedBalance && (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Alby Hosted Balance
|
||||
</CardTitle>
|
||||
<Hotel className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<div className="text-2xl font-bold">
|
||||
{new Intl.NumberFormat().format(albyBalance.sats)} sats
|
||||
</div>
|
||||
<FormattedFiatAmount amount={albyBalance.sats} />
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end space-x-1">
|
||||
<TransferFundsButton
|
||||
variant="outline"
|
||||
channels={channels}
|
||||
albyBalance={albyBalance}
|
||||
onTransferComplete={() =>
|
||||
Promise.all([
|
||||
reloadAlbyBalance(),
|
||||
reloadBalances(),
|
||||
reloadChannels(),
|
||||
])
|
||||
}
|
||||
>
|
||||
Transfer
|
||||
</TransferFundsButton>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card className="flex flex-1 sm:flex-[2] flex-col">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="font-semibold">Lightning</CardTitle>
|
||||
<ZapIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle className="font-semibold text-2xl">Lightning</CardTitle>
|
||||
<ZapIcon className="h-6 w-6 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex flex-col sm:flex-row pl-0 flex-wrap">
|
||||
<div className="flex flex-col flex-1">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 pr-0">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pr-0">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="flex flex-row gap-1 items-center justify-start text-sm text-secondary-foreground">
|
||||
<div className="flex flex-row gap-1 items-center justify-start text-sm font-medium">
|
||||
Spending Balance
|
||||
<InfoIcon className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
@ -525,7 +481,7 @@ export default function Channels() {
|
|||
)}
|
||||
{balances && (
|
||||
<>
|
||||
<div className="text-2xl font-bold balance sensitive">
|
||||
<div className="text-xl font-medium balance sensitive mb-1">
|
||||
{new Intl.NumberFormat().format(
|
||||
Math.floor(balances.lightning.totalSpendable / 1000)
|
||||
)}{" "}
|
||||
|
|
@ -539,12 +495,12 @@ export default function Channels() {
|
|||
</CardContent>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 pr-0">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pr-0">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="flex flex-row gap-1 items-center justify-start text-sm text-secondary-foreground">
|
||||
<div className="flex flex-row gap-1 items-center justify-start text-sm font-medium">
|
||||
Receive Limit
|
||||
<InfoIcon className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
@ -561,7 +517,7 @@ export default function Channels() {
|
|||
<CardContent className="flex-grow pb-0">
|
||||
{balances && (
|
||||
<>
|
||||
<div className="text-2xl font-bold balance sensitive">
|
||||
<div className="text-xl font-medium balance sensitive mb-1">
|
||||
{new Intl.NumberFormat().format(
|
||||
Math.floor(balances.lightning.totalReceivable / 1000)
|
||||
)}{" "}
|
||||
|
|
@ -578,16 +534,16 @@ export default function Channels() {
|
|||
</Card>
|
||||
<Card className="flex flex-1 flex-col">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="font-semibold">On-Chain</CardTitle>
|
||||
<LinkIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle className="text-2xl font-semibold">On-Chain</CardTitle>
|
||||
<LinkIcon className="h-6 w-6 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 pl-0">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1 pl-0">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div className="flex flex-row gap-1 items-center text-sm text-secondary-foreground">
|
||||
<div className="flex flex-row gap-1 items-center text-sm font-medium">
|
||||
Balance
|
||||
<InfoIcon className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
@ -610,11 +566,11 @@ export default function Channels() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-2xl balance sensitive">
|
||||
<div>
|
||||
{balances && (
|
||||
<>
|
||||
<div className="balance sensitive flex gap-2">
|
||||
<span className="text-2xl font-bold">
|
||||
<div className="mb-1">
|
||||
<span className="text-xl font-medium balance sensitive mb-1 mr-1">
|
||||
{new Intl.NumberFormat().format(
|
||||
Math.floor(balances.onchain.spendable)
|
||||
)}{" "}
|
||||
|
|
@ -639,7 +595,10 @@ export default function Channels() {
|
|||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<FormattedFiatAmount amount={balances.onchain.spendable} />
|
||||
<FormattedFiatAmount
|
||||
amount={balances.onchain.spendable}
|
||||
className="mb-1"
|
||||
/>
|
||||
{balances &&
|
||||
balances.onchain.spendable !== balances.onchain.total && (
|
||||
<p className="text-xs text-muted-foreground animate-pulse">
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ import { request } from "src/utils/request";
|
|||
|
||||
import { MempoolAlert } from "src/components/MempoolAlert";
|
||||
import { PayLightningInvoice } from "src/components/PayLightningInvoice";
|
||||
import {
|
||||
ALBY_HIDE_HOSTED_BALANCE_BELOW,
|
||||
ALBY_MIN_HOSTED_BALANCE_FOR_FIRST_CHANNEL,
|
||||
} from "src/constants";
|
||||
import { ALBY_MIN_HOSTED_BALANCE_FOR_FIRST_CHANNEL } from "src/constants";
|
||||
|
||||
export function FirstChannel() {
|
||||
const { data: info } = useInfo();
|
||||
|
|
@ -92,9 +89,6 @@ export function FirstChannel() {
|
|||
albyBalance &&
|
||||
albyBalance.sats >= ALBY_MIN_HOSTED_BALANCE_FOR_FIRST_CHANNEL;
|
||||
|
||||
const canMigrateFunds =
|
||||
albyBalance && albyBalance.sats >= ALBY_HIDE_HOSTED_BALANCE_BELOW;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader
|
||||
|
|
@ -142,15 +136,22 @@ export function FirstChannel() {
|
|||
{canPayForFirstChannel ? (
|
||||
<>
|
||||
<p>
|
||||
Your Alby hosted balance currently holds{" "}
|
||||
You currently have{" "}
|
||||
<span className="font-medium text-foreground sensitive slashed-zero">
|
||||
{new Intl.NumberFormat().format(albyBalance?.sats)} sats
|
||||
</span>
|
||||
.
|
||||
{new Intl.NumberFormat().format(albyBalance?.sats)} Alby fee
|
||||
credits.
|
||||
</span>{" "}
|
||||
<Link
|
||||
to="https://guides.getalby.com/user-guide/alby-account-and-browser-extension/alby-account/faqs-alby-account/what-are-fee-credits-in-my-alby-account"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
<p>
|
||||
Those funds will be used to open your first lightning channel
|
||||
and then migrated to your Hub spending balance.
|
||||
These fee credits will be applied to open your first Lightning
|
||||
channel.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -204,7 +205,6 @@ export function FirstChannel() {
|
|||
)}
|
||||
<LoadingButton loading={isLoading} onClick={openChannel}>
|
||||
Open Channel
|
||||
{canMigrateFunds && <> and Transfer Funds</>}
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,58 +1,9 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
|
||||
import { Button } from "src/components/ui/button";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { ALBY_HIDE_HOSTED_BALANCE_BELOW } from "src/constants";
|
||||
import { useAlbyBalance } from "src/hooks/useAlbyBalance";
|
||||
|
||||
import { request } from "src/utils/request";
|
||||
|
||||
export function OpenedFirstChannel() {
|
||||
const { data: albyBalance, mutate: reloadAlbyBalance } = useAlbyBalance();
|
||||
const [hasTransferredFunds, setTransferredFunds] = React.useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
// automatically drain Alby balance into new channel if possible
|
||||
// TODO: remove this code once all Alby users have migrated to Alby Hub
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!albyBalance || albyBalance.sats < ALBY_HIDE_HOSTED_BALANCE_BELOW) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasTransferredFunds && albyBalance.sats > 100_000) {
|
||||
// do not transfer all funds in one go in case the user still has a large number of sats
|
||||
// left over - only transfer if the user has ~1% remaining.
|
||||
// A maximum of 1M sats will be transferred in the first request.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await request("/api/alby/drain", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
await reloadAlbyBalance();
|
||||
// This may run multiple times (to drain the final 1%), but we should only show a toast once
|
||||
setTransferredFunds((current) => {
|
||||
if (!current) {
|
||||
toast({
|
||||
description:
|
||||
"🎉 Funds from Alby shared wallet transferred to your Alby Hub!",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to transfer any alby shared wallet funds", error);
|
||||
}
|
||||
})();
|
||||
}, [albyBalance, hasTransferredFunds, reloadAlbyBalance, toast]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center gap-5 p-5 max-w-md items-stretch">
|
||||
<TwoColumnLayoutHeader
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
AlertTriangle,
|
||||
ArrowDownIcon,
|
||||
ArrowLeftRight,
|
||||
ArrowUpIcon,
|
||||
CreditCard,
|
||||
} from "lucide-react";
|
||||
|
|
@ -12,79 +11,34 @@ import ExternalLink from "src/components/ExternalLink";
|
|||
import FormattedFiatAmount from "src/components/FormattedFiatAmount";
|
||||
import Loading from "src/components/Loading";
|
||||
import TransactionsList from "src/components/TransactionsList";
|
||||
import { TransferFundsButton } from "src/components/TransferFundsButton";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "src/components/ui/alert.tsx";
|
||||
import { Button, LinkButton } from "src/components/ui/button";
|
||||
import { ALBY_HIDE_HOSTED_BALANCE_BELOW as ALBY_HIDE_HOSTED_BALANCE_LIMIT } from "src/constants.ts";
|
||||
import { useAlbyBalance } from "src/hooks/useAlbyBalance";
|
||||
import { Button } from "src/components/ui/button";
|
||||
import { useBalances } from "src/hooks/useBalances";
|
||||
import { useChannels } from "src/hooks/useChannels";
|
||||
import { useInfo } from "src/hooks/useInfo";
|
||||
import { useTransactions } from "src/hooks/useTransactions";
|
||||
|
||||
function Wallet() {
|
||||
const { data: info, hasChannelManagement } = useInfo();
|
||||
const { data: balances, mutate: reloadBalances } = useBalances();
|
||||
const { data: balances } = useBalances();
|
||||
const { data: channels } = useChannels();
|
||||
const { data: albyBalance, mutate: reloadAlbyBalance } = useAlbyBalance();
|
||||
const { mutate: reloadTransactions } = useTransactions();
|
||||
|
||||
if (!info || !balances) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const showMigrateCard =
|
||||
albyBalance && albyBalance.sats > ALBY_HIDE_HOSTED_BALANCE_LIMIT;
|
||||
const needsChannels = hasChannelManagement && channels && channels.length < 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader title="Wallet" description="" />
|
||||
{showMigrateCard && (
|
||||
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm p-8">
|
||||
<div className="flex flex-col items-center gap-1 text-center max-w-md">
|
||||
<ArrowLeftRight className="w-10 h-10 text-primary-background" />
|
||||
<h3 className="mt-4 text-lg font-semibold">
|
||||
You still have{" "}
|
||||
<span className="font-bold slashed-zero sensitive">
|
||||
{new Intl.NumberFormat().format(albyBalance.sats)}
|
||||
</span>{" "}
|
||||
sats in your Alby shared wallet
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Transfer funds from your Alby hosted balance.
|
||||
</p>
|
||||
{needsChannels ? (
|
||||
<LinkButton to="/channels/first">Transfer Funds</LinkButton>
|
||||
) : (
|
||||
<TransferFundsButton
|
||||
channels={channels}
|
||||
albyBalance={albyBalance}
|
||||
onTransferComplete={() =>
|
||||
Promise.all([
|
||||
reloadAlbyBalance(),
|
||||
reloadBalances(),
|
||||
reloadTransactions(),
|
||||
])
|
||||
}
|
||||
>
|
||||
Transfer Funds
|
||||
</TransferFundsButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasChannelManagement &&
|
||||
!!channels?.length &&
|
||||
channels?.every(
|
||||
(channel) =>
|
||||
channel.localBalance < channel.unspendablePunishmentReserve * 1000
|
||||
) &&
|
||||
!showMigrateCard && (
|
||||
) && (
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Channel Reserves Unmet</AlertTitle>
|
||||
|
|
@ -99,8 +53,7 @@ function Wallet() {
|
|||
)}
|
||||
{hasChannelManagement &&
|
||||
!!channels?.length &&
|
||||
!balances.lightning.totalReceivable &&
|
||||
!showMigrateCard && (
|
||||
!balances.lightning.totalReceivable && (
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Low receiving capacity</AlertTitle>
|
||||
|
|
@ -112,7 +65,7 @@ function Wallet() {
|
|||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{hasChannelManagement && !channels?.length && !showMigrateCard && (
|
||||
{hasChannelManagement && !channels?.length && (
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Open Your First Channel</AlertTitle>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ func (albyHttpSvc *AlbyHttpService) RegisterSharedRoutes(restrictedApiGroup *ech
|
|||
restrictedApiGroup.GET("/alby/me", albyHttpSvc.albyMeHandler)
|
||||
restrictedApiGroup.GET("/alby/balance", albyHttpSvc.albyBalanceHandler)
|
||||
restrictedApiGroup.POST("/alby/pay", albyHttpSvc.albyPayHandler)
|
||||
restrictedApiGroup.POST("/alby/drain", albyHttpSvc.albyDrainHandler)
|
||||
restrictedApiGroup.POST("/alby/link-account", albyHttpSvc.albyLinkAccountHandler)
|
||||
restrictedApiGroup.POST("/alby/auto-channel", albyHttpSvc.autoChannelHandler)
|
||||
restrictedApiGroup.POST("/alby/unlink-account", albyHttpSvc.unlinkHandler)
|
||||
|
|
@ -172,20 +171,6 @@ func (albyHttpSvc *AlbyHttpService) albyPayHandler(c echo.Context) error {
|
|||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (albyHttpSvc *AlbyHttpService) albyDrainHandler(c echo.Context) error {
|
||||
|
||||
err := albyHttpSvc.albyOAuthSvc.DrainSharedWallet(c.Request().Context(), albyHttpSvc.svc.GetLNClient())
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to drain shared wallet")
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to drain shared wallet: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (albyHttpSvc *AlbyHttpService) albyLinkAccountHandler(c echo.Context) error {
|
||||
var linkAccountRequest alby.AlbyLinkAccountRequest
|
||||
if err := c.Bind(&linkAccountRequest); err != nil {
|
||||
|
|
|
|||
|
|
@ -341,12 +341,6 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
return WailsRequestRouterResponse{Body: &alby.AlbyBalanceResponse{
|
||||
Sats: balance.Balance,
|
||||
}, Error: ""}
|
||||
case "/api/alby/drain":
|
||||
err := app.svc.GetAlbyOAuthSvc().DrainSharedWallet(ctx, app.svc.GetLNClient())
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: nil, Error: ""}
|
||||
case "/api/alby/unlink-account":
|
||||
err := app.svc.GetAlbyOAuthSvc().UnlinkAccount(ctx)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue