chore: remove unused green and breezlight backends

This commit is contained in:
Roland Bewick 2025-03-15 13:11:36 +07:00
parent 473105ff62
commit e107e05d80
22 changed files with 10 additions and 1508 deletions

View file

@ -30,19 +30,16 @@ jobs:
[
{
arch: x86_64,
breez_arch: linux-amd64,
alby_arch: x86_64-unknown-linux-gnu,
os: ubuntu-20.04,
},
{
arch: armv6,
breez_arch: "",
alby_arch: arm-unknown-linux-gnueabihf,
os: ubuntu-20.04,
},
{
arch: aarch64,
breez_arch: linux-aarch64,
alby_arch: aarch64-unknown-linux-gnu,
os: ubuntu-20.04,
},
@ -113,7 +110,7 @@ jobs:
echo "GOARCH=arm" >> $GITHUB_ENV
echo "GOARM=6" >> $GITHUB_ENV
echo "CC=$GITHUB_WORKSPACE/armv6-unknown-linux-gnueabihf/bin/armv6-unknown-linux-gnueabihf-gcc" >> $GITHUB_ENV
echo "GOTAGS=-tags skip_breez,netgo" >> $GITHUB_ENV
echo "GOTAGS=-tags netgo" >> $GITHUB_ENV
- name: Install ARM64 cross-compiler
if: matrix.build.arch == 'aarch64'
@ -143,10 +140,6 @@ jobs:
if: runner.os == 'Linux'
run: |
mkdir -p ./build/bin/${{ env.PACKAGE_NAME }}/lib
if [ "${{ matrix.build.breez_arch }}" != "" ]; then
cp `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/${{ matrix.build.breez_arch }}/libbreez_sdk_bindings.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
fi
cp `go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go`/glalby/${{ matrix.build.alby_arch }}/libglalby_bindings.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
cp `go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go`/ldk_node/${{ matrix.build.alby_arch }}/libldk_node.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
shell: bash
@ -158,8 +151,6 @@ jobs:
- name: Copy shared libraries to the output directory
if: runner.os == 'macOS'
run: |
lipo -create -output ./build/bin/${{ env.PACKAGE_NAME }}/lib/libbreez_sdk_bindings.dylib `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/darwin-aarch64/libbreez_sdk_bindings.dylib `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/darwin-amd64/libbreez_sdk_bindings.dylib
cp `go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go`/glalby/universal-macos/libglalby_bindings.dylib ./build/bin/${{ env.PACKAGE_NAME }}/lib/
cp `go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go`/ldk_node/universal-macos/libldk_node.dylib ./build/bin/${{ env.PACKAGE_NAME }}/lib/
shell: bash

View file

@ -101,8 +101,6 @@ jobs:
- name: Copy shared libraries
if: runner.os == 'Windows'
run: |
cp `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/windows-amd64/breez_sdk_bindings.dll ./
cp `go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go`/glalby/x86_64-pc-windows-msvc/glalby_bindings.dll ./
cp `go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go`/ldk_node/x86_64-pc-windows-msvc/ldk_node.dll ./
shell: bash
@ -142,8 +140,6 @@ jobs:
- name: Copy DLLs to the output directory
if: runner.os == 'Windows'
run: |
cp breez_sdk_bindings.dll ./build/bin/
cp glalby_bindings.dll ./build/bin/
cp ldk_node.dll ./build/bin/
shell: bash
@ -151,8 +147,6 @@ jobs:
if: runner.os == 'Linux'
run: |
mkdir -p ./build/bin/${{ env.PACKAGE_NAME }}/lib
cp `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/linux-amd64/libbreez_sdk_bindings.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
cp `go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go`/glalby/x86_64-unknown-linux-gnu/libglalby_bindings.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
cp `go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go`/ldk_node/x86_64-unknown-linux-gnu/libldk_node.so ./build/bin/${{ env.PACKAGE_NAME }}/lib/
mkdir -p ./build/bin/${{ env.PACKAGE_NAME }}/bin
mv "./build/bin/${{ env.EXEC_NAME }}" ./build/bin/${{ env.PACKAGE_NAME }}/bin/
@ -167,8 +161,6 @@ jobs:
if: runner.os == 'macOS'
run: |
mkdir -p "./build/bin/${{ env.EXEC_NAME }}.app/Contents/Frameworks"
lipo -create -output "./build/bin/${{ env.EXEC_NAME }}.app/Contents/Frameworks/libbreez_sdk_bindings.dylib" `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/darwin-aarch64/libbreez_sdk_bindings.dylib `go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go`/breez_sdk/lib/darwin-amd64/libbreez_sdk_bindings.dylib
cp `go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go`/glalby/universal-macos/libglalby_bindings.dylib "./build/bin/${{ env.EXEC_NAME }}.app/Contents/Frameworks/"
cp `go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go`/ldk_node/universal-macos/libldk_node.dylib "./build/bin/${{ env.EXEC_NAME }}.app/Contents/Frameworks/"
shell: bash

View file

@ -49,8 +49,6 @@ ENV LD_LIBRARY_PATH=/usr/lib/nwc
#
# # Copy the binaries and entrypoint from the builder image.
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/libbreez_sdk_bindings.so /usr/lib/nwc/
COPY --from=builder /build/libglalby_bindings.so /usr/lib/nwc/
COPY --from=builder /build/libldk_node.so /usr/lib/nwc/
COPY --from=builder /build/main /bin/

View file

@ -138,10 +138,6 @@ For more information refer to:
$ go run -ldflags="-X 'github.com/getAlby/hub/version.Tag=v0.6.0'" cmd/http/main.go
### Windows
Breez SDK requires gcc to build the Breez bindings. Run `choco install mingw` and copy the breez SDK bindings file into the root of this directory (from your go installation directory) as per the [Breez SDK instructions](https://github.com/breez/breez-sdk-go?tab=readme-ov-file#windows). ALSO copy the bindings file into the output directory alongside the .exe in order to run it.
## Optional configuration parameters
The following configuration options can be set as environment variables or in a .env file
@ -356,10 +352,6 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light
- ⚠️ PAYMENT_FAILED error code not supported
### Breez
(Supported methods coming soon)
## Node Distributions
Run NWC on your own node!
@ -488,7 +480,7 @@ At a high level Alby Hub is an [NWC](https://nwc.dev) wallet service which allow
### LNClient
The LNClient interface abstracts the differences between wallet implementations and allows users to run Alby Hub with their preferred wallet, such as LDK, LND, Phoenixd, Cashu, Breez, Greenlight.
The LNClient interface abstracts the differences between wallet implementations and allows users to run Alby Hub with their preferred wallet, such as LDK, LND, Phoenixd, Cashu.
### Transactions Service

View file

@ -889,13 +889,6 @@ func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
return err
}
}
if setupRequest.BreezAPIKey != "" {
err = api.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save breez api key")
return err
}
}
if setupRequest.Mnemonic != "" {
err = api.cfg.SetUpdate("Mnemonic", setupRequest.Mnemonic, setupRequest.UnlockPassword)
if err != nil {
@ -903,13 +896,6 @@ func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
return err
}
}
if setupRequest.GreenlightInviteCode != "" {
err = api.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save greenlight invite code")
return err
}
}
if setupRequest.LNDAddress != "" {
err = api.cfg.SetUpdate("LNDAddress", setupRequest.LNDAddress, setupRequest.UnlockPassword)
if err != nil {

View file

@ -130,14 +130,9 @@ type SetupRequest struct {
LNBackendType string `json:"backendType"`
UnlockPassword string `json:"unlockPassword"`
// Breez / Greenlight
Mnemonic string `json:"mnemonic"`
GreenlightInviteCode string `json:"greenlightInviteCode"`
NextBackupReminder string `json:"nextBackupReminder"`
// Breez fields
BreezAPIKey string `json:"breezApiKey"`
// LND fields
LNDAddress string `json:"lndAddress"`
LNDCertFile string `json:"lndCertFile"`

View file

@ -1,9 +1,7 @@
{
"source": [
"./build/bin/AlbyHub.app",
"./build/bin/AlbyHub.app/Contents/MacOS/AlbyHub",
"./build/bin/AlbyHub.app/Contents/Frameworks/libbreez_sdk_bindings.dylib",
"./build/bin/AlbyHub.app/Contents/Frameworks/libglalby_bindings.dylib"
"./build/bin/AlbyHub.app/Contents/MacOS/AlbyHub"
],
"bundle_id": "com.getalby.AlbyHub",
"sign": {

View file

@ -3,12 +3,8 @@
ARCH="$1"
if [[ "$ARCH" == "amd64" ]]; then
cp "$(go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go)"/breez_sdk/lib/linux-amd64/libbreez_sdk_bindings.so ./
cp "$(go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go)"/glalby/x86_64-unknown-linux-gnu/libglalby_bindings.so ./
cp "$(go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go)"/ldk_node/x86_64-unknown-linux-gnu/libldk_node.so ./
elif [[ "$ARCH" == "arm64" ]]; then
cp "$(go list -m -f "{{.Dir}}" github.com/breez/breez-sdk-go)"/breez_sdk/lib/linux-aarch64/libbreez_sdk_bindings.so ./
cp "$(go list -m -f "{{.Dir}}" github.com/getAlby/glalby-go)"/glalby/aarch64-unknown-linux-gnu/libglalby_bindings.so ./
cp "$(go list -m -f "{{.Dir}}" github.com/getAlby/ldk-node-go)"/ldk_node/aarch64-unknown-linux-gnu/libldk_node.so ./
else
echo "Invalid ARCH value"

View file

@ -2,9 +2,7 @@ package config
const (
LNDBackendType = "LND"
GreenlightBackendType = "GREENLIGHT"
LDKBackendType = "LDK"
BreezBackendType = "BREEZ"
PhoenixBackendType = "PHOENIX"
CashuBackendType = "CASHU"
)

View file

@ -1,40 +0,0 @@
import { RocketIcon } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert";
import { LinkButton } from "src/components/ui/button";
import { ONCHAIN_DUST_SATS } from "src/constants";
import { useBalances } from "src/hooks/useBalances";
import { useInfo } from "src/hooks/useInfo";
export default function BreezRedeem() {
const { data: info } = useInfo();
if (info?.backendType !== "BREEZ") {
return null;
}
return <BreezRedeemInternal />;
}
function BreezRedeemInternal() {
const { data: balances } = useBalances();
if (!balances || balances.onchain.spendable <= ONCHAIN_DUST_SATS) {
return null;
}
return (
<div className="mb-8">
<Alert>
<RocketIcon className="h-4 w-4" />
<AlertTitle>Breez channel closed</AlertTitle>
<AlertDescription>
<div className="mb-2">
One of your Breez channels was closed and you have{" "}
{balances.onchain.spendable} sats to redeem.
</div>
<LinkButton to={"/wallet/withdraw"} size={"sm"}>
Redeem
</LinkButton>
</AlertDescription>
</Alert>
</div>
);
}

View file

@ -12,16 +12,6 @@ export const backendTypeConfigs: Record<BackendType, BackendTypeConfig> = {
hasChannelManagement: true,
hasNodeBackup: false,
},
BREEZ: {
hasMnemonic: true,
hasChannelManagement: false,
hasNodeBackup: false,
},
GREENLIGHT: {
hasMnemonic: true,
hasChannelManagement: true,
hasNodeBackup: false,
},
LDK: {
hasMnemonic: true,
hasChannelManagement: true,

View file

@ -59,9 +59,7 @@ import { SetupFinish } from "src/screens/setup/SetupFinish";
import { SetupNode } from "src/screens/setup/SetupNode";
import { SetupPassword } from "src/screens/setup/SetupPassword";
import { SetupSecurity } from "src/screens/setup/SetupSecurity";
import { BreezForm } from "src/screens/setup/node/BreezForm";
import { CashuForm } from "src/screens/setup/node/CashuForm";
import { GreenlightForm } from "src/screens/setup/node/GreenlightForm";
import { LDKForm } from "src/screens/setup/node/LDKForm";
import { LNDForm } from "src/screens/setup/node/LNDForm";
import { PhoenixdForm } from "src/screens/setup/node/PhoenixdForm";
@ -431,14 +429,6 @@ const routes = [
index: true,
element: <SetupNode />,
},
{
path: "breez",
element: <BreezForm />,
},
{
path: "greenlight",
element: <GreenlightForm />,
},
{
path: "cashu",
element: <CashuForm />,

View file

@ -1,77 +0,0 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import Container from "src/components/Container";
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
import { Button } from "src/components/ui/button";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { useToast } from "src/components/ui/use-toast";
import useSetupStore from "src/state/SetupStore";
export function BreezForm() {
const { toast } = useToast();
const navigate = useNavigate();
const setupStore = useSetupStore();
const [greenlightInviteCode, setGreenlightInviteCode] =
React.useState<string>(setupStore.nodeInfo.greenlightInviteCode || "");
const [breezApiKey, setBreezApiKey] = React.useState<string>(
setupStore.nodeInfo.breezApiKey || ""
);
async function handleSubmit(data: object) {
setupStore.updateNodeInfo({
backendType: "BREEZ",
...data,
});
navigate("/setup/import-mnemonic");
}
function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (!greenlightInviteCode || !breezApiKey) {
toast({
title: "Please fill out all fields",
variant: "destructive",
});
return;
}
handleSubmit({
greenlightInviteCode,
breezApiKey,
});
}
return (
<Container>
<TwoColumnLayoutHeader
title={"Configure Breez"}
description={"Fill out wallet details to finish setup."}
/>
<form className="w-full grid gap-5 mt-6" onSubmit={onSubmit}>
<div className="grid gap-1.5">
<Label htmlFor="greenlight-invite-code">Greenlight Invite Code</Label>
<Input
name="greenlight-invite-code"
onChange={(e) => setGreenlightInviteCode(e.target.value)}
value={greenlightInviteCode}
type="text"
id="greenlight-invite-code"
placeholder="XXXX-YYYY"
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="breez-api-key">Breez API Key</Label>
<Input
name="breez-api-key"
onChange={(e) => setBreezApiKey(e.target.value)}
value={breezApiKey}
autoComplete="off"
type="text"
id="breez-api-key"
/>
</div>
<Button type="submit">Next</Button>
</form>
</Container>
);
}

View file

@ -1,62 +0,0 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import Container from "src/components/Container";
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
import { Button } from "src/components/ui/button";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { useToast } from "src/components/ui/use-toast";
import useSetupStore from "src/state/SetupStore";
export function GreenlightForm() {
const { toast } = useToast();
const navigate = useNavigate();
const setupStore = useSetupStore();
const [greenlightInviteCode, setGreenlightInviteCode] =
React.useState<string>(setupStore.nodeInfo.greenlightInviteCode || "");
async function handleSubmit(data: object) {
setupStore.updateNodeInfo({
backendType: "GREENLIGHT",
...data,
});
navigate("/setup/import-mnemonic");
}
function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (!greenlightInviteCode) {
toast({
title: "Please fill out all fields",
variant: "destructive",
});
return;
}
handleSubmit({
greenlightInviteCode,
});
}
return (
<Container>
<TwoColumnLayoutHeader
title="Configure Greenlight"
description="Fill out wallet details to finish setup."
/>
<form onSubmit={onSubmit} className="w-full grid gap-5 mt-6">
<div className="grid gap-1.5">
<Label htmlFor="greenlight-invite-code">Greenlight Invite Code</Label>
<Input
name="greenlight-invite-code"
onChange={(e) => setGreenlightInviteCode(e.target.value)}
value={greenlightInviteCode}
type="text"
id="greenlight-invite-code"
placeholder="XXXX-YYYY"
/>
</div>
<Button>Next</Button>
</form>
</Container>
);
}

View file

@ -6,7 +6,6 @@ import {
} from "lucide-react";
import { Link } from "react-router-dom";
import AppHeader from "src/components/AppHeader";
import BreezRedeem from "src/components/BreezRedeem";
import ExternalLink from "src/components/ExternalLink";
import FormattedFiatAmount from "src/components/FormattedFiatAmount";
import Loading from "src/components/Loading";
@ -78,7 +77,6 @@ function Wallet() {
</AlertDescription>
</Alert>
)}
<BreezRedeem />
<div className="flex flex-col xl:flex-row justify-between xl:items-center gap-5">
<div className="flex flex-col gap-1 text-center xl:text-left">
<div className="text-5xl font-medium balance sensitive slashed-zero">

View file

@ -11,13 +11,7 @@ import {
WalletMinimal,
} from "lucide-react";
export type BackendType =
| "LND"
| "BREEZ"
| "GREENLIGHT"
| "LDK"
| "PHOENIX"
| "CASHU";
export type BackendType = "LND" | "LDK" | "PHOENIX" | "CASHU";
export type Nip47RequestMethod =
| "get_info"
@ -331,8 +325,6 @@ export type SetupNodeInfo = Partial<{
mnemonic?: string;
nextBackupReminder?: string;
greenlightInviteCode?: string;
breezApiKey?: string;
lndAddress?: string;
lndCertHex?: string;

2
go.mod
View file

@ -6,9 +6,7 @@ toolchain go1.23.3
require (
github.com/adrg/xdg v0.5.3
github.com/breez/breez-sdk-go v0.5.2
github.com/elnosh/gonuts v0.4.0
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59
github.com/getAlby/ldk-node-go v0.0.0-20250313063409-c465f4e6e089
github.com/go-gormigrate/gormigrate/v2 v2.1.3
github.com/labstack/echo/v4 v4.13.3

4
go.sum
View file

@ -61,8 +61,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/breez/breez-sdk-go v0.5.2 h1:frqX14cKTM2egWP7C53u3SzBkYQrgx9P7si/Ig9Droo=
github.com/breez/breez-sdk-go v0.5.2/go.mod h1:EalYMEeQVwRzr6UXnF4QpLlpuWNwQQN9xwtDKNIutBo=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
@ -205,8 +203,6 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59 h1:fSqdXE9uKhLcOOQaLtzN+D8RN3oEcZQkGX5E8PyiKy0=
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59/go.mod h1:ViyJvjlvv0GCesTJ7mb3fBo4G+/qsujDAFN90xZ7a9U=
github.com/getAlby/ldk-node-go v0.0.0-20250313063409-c465f4e6e089 h1:39wtrlg8SxbjJDj/hGdhS4GPlDk9kwxTy36kvrDP5mU=
github.com/getAlby/ldk-node-go v0.0.0-20250313063409-c465f4e6e089/go.mod h1:8BRjtKcz8E0RyYTPEbMS8VIdgredcGSLne8vHDtcRLg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=

View file

@ -1,503 +0,0 @@
//go:build !skip_breez
package breez
import (
"context"
"encoding/hex"
"errors"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/breez/breez-sdk-go/breez_sdk"
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
)
type BreezService struct {
listener *BreezListener
svc *breez_sdk.BlockingBreezServices
pubkey string
}
type BreezListener struct {
}
func (listener BreezListener) Log(l breez_sdk.LogEntry) {
logLevel := logrus.InfoLevel
if l.Level == "TRACE" || l.Level == "DEBUG" || strings.Contains(l.Line, "connection to node lost") || strings.Contains(l.Line, "Restore channel") {
logLevel = logrus.DebugLevel
}
logger.Logger.WithField("level", l.Level).Log(logLevel, l.Line)
}
func (BreezListener) OnEvent(e breez_sdk.BreezEvent) {
log.Printf("received event %#v", e)
}
func NewBreezService(mnemonic, apiKey, inviteCode, workDir string) (result lnclient.LNClient, err error) {
if mnemonic == "" || apiKey == "" || inviteCode == "" || workDir == "" {
return nil, errors.New("one or more required breez configuration are missing")
}
// create dir if not exists
newpath := filepath.Join(workDir)
err = os.MkdirAll(newpath, os.ModePerm)
if err != nil {
return nil, err
}
seed, err := breez_sdk.MnemonicToSeed(mnemonic)
if err != nil {
return nil, err
}
nodeConfig := breez_sdk.NodeConfigGreenlight{
Config: breez_sdk.GreenlightNodeConfig{
InviteCode: &inviteCode,
},
}
listener := BreezListener{}
config := breez_sdk.DefaultConfig(breez_sdk.EnvironmentTypeProduction, apiKey, nodeConfig)
config.WorkingDir = workDir
breez_sdk.SetLogStream(listener)
svc, err := breez_sdk.Connect(breez_sdk.ConnectRequest{
Config: config,
Seed: seed,
}, listener)
if err != nil {
return nil, err
}
nodeInfo, err := svc.NodeInfo()
if err != nil {
return nil, err
}
logger.Logger.WithField("info", nodeInfo).Info("Node info")
logger.Logger.WithFields(logrus.Fields{
"ln balance": nodeInfo.ChannelsBalanceMsat,
"balance": nodeInfo.OnchainBalanceMsat,
"max_payable_msat": nodeInfo.MaxPayableMsat,
"max_receivable_msat": nodeInfo.MaxReceivableMsat,
"max_single_payment_amount_msat": nodeInfo.MaxSinglePaymentAmountMsat,
"connected_peers": nodeInfo.ConnectedPeers,
"inbound_liquidity_msats": nodeInfo.InboundLiquidityMsats,
}).Info("node balances and peers")
return &BreezService{
listener: &listener,
svc: svc,
pubkey: nodeInfo.Id,
}, nil
}
func (bs *BreezService) Shutdown() error {
return bs.svc.Disconnect()
}
func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
if amount != nil {
return nil, errors.New("0-amount invoices not supported")
}
sendPaymentRequest := breez_sdk.SendPaymentRequest{
Bolt11: payReq,
}
resp, err := bs.svc.SendPayment(sendPaymentRequest)
if err != nil {
return nil, err
}
var lnDetails breez_sdk.PaymentDetailsLn
if resp.Payment.Details != nil {
lnDetails, _ = resp.Payment.Details.(breez_sdk.PaymentDetailsLn)
}
return &lnclient.PayInvoiceResponse{
Preimage: lnDetails.Data.PaymentPreimage,
}, nil
}
func (bs *BreezService) SendKeysend(ctx context.Context, amount uint64, destination string, custom_records []lnclient.TLVRecord, preimage string) (*lnclient.PayKeysendResponse, error) {
// TODO: re-enable when passing custom preimage is possible
/*extraTlvs := []breez_sdk.TlvEntry{}
for _, record := range custom_records {
decodedValue, err := hex.DecodeString(record.Value)
if err != nil {
return "", "", 0, err
}
extraTlvs = append(extraTlvs, breez_sdk.TlvEntry{
FieldNumber: record.Type,
Value: decodedValue,
})
}
sendSpontaneousPaymentRequest := breez_sdk.SendSpontaneousPaymentRequest{
NodeId: destination,
AmountMsat: amount,
ExtraTlvs: &extraTlvs,
}
resp, err := bs.svc.SendSpontaneousPayment(sendSpontaneousPaymentRequest)
if err != nil {
return "", "", 0, err
}
var lnDetails breez_sdk.PaymentDetailsLn
if resp.Payment.Details != nil {
lnDetails, _ = resp.Payment.Details.(breez_sdk.PaymentDetailsLn)
}
return lnDetails.Data.PaymentHash, lnDetails.Data.PaymentPreimage, resp.Payment.FeeMsat, nil*/
return nil, errors.New("not supported")
}
func (bs *BreezService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *lnclient.Transaction, err error) {
if expiry == 0 {
expiry = lnclient.DEFAULT_INVOICE_EXPIRY
}
expiry32 := uint32(expiry)
receivePaymentRequest := breez_sdk.ReceivePaymentRequest{
// amount provided in msat
AmountMsat: uint64(amount),
Description: description,
Expiry: &expiry32,
}
resp, err := bs.svc.ReceivePayment(receivePaymentRequest)
if err != nil {
return nil, err
}
tx := &lnclient.Transaction{
Type: "incoming",
Invoice: resp.LnInvoice.Bolt11,
Preimage: hex.EncodeToString(resp.LnInvoice.PaymentSecret),
PaymentHash: resp.LnInvoice.PaymentHash,
FeesPaid: 0,
CreatedAt: int64(resp.LnInvoice.Timestamp),
SettledAt: nil,
Metadata: nil,
}
if resp.LnInvoice.Description != nil {
tx.Description = *resp.LnInvoice.Description
}
if resp.LnInvoice.DescriptionHash != nil {
tx.DescriptionHash = *resp.LnInvoice.DescriptionHash
}
if resp.LnInvoice.AmountMsat != nil {
tx.Amount = int64(*resp.LnInvoice.AmountMsat)
}
invExpiry := int64(resp.LnInvoice.Expiry)
tx.ExpiresAt = &invExpiry
return tx, nil
}
func (bs *BreezService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
log.Printf("p: %v", paymentHash)
payment, err := bs.svc.PaymentByHash(paymentHash)
if err != nil {
return nil, err
}
if payment != nil {
log.Printf("p: %v", payment)
transaction, err = breezPaymentToTransaction(payment)
if err != nil {
return nil, err
}
return transaction, nil
} else {
return nil, errors.New("not found")
}
}
func (bs *BreezService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []lnclient.Transaction, err error) {
request := breez_sdk.ListPaymentsRequest{}
if limit > 0 {
limit32 := uint32(limit)
request.Limit = &limit32
}
if offset > 0 {
offset32 := uint32(offset)
request.Offset = &offset32
}
if from > 0 {
from64 := int64(from)
request.FromTimestamp = &from64
}
if until > 0 {
until64 := int64(until)
request.ToTimestamp = &until64
}
payments, err := bs.svc.ListPayments(request)
if err != nil {
return nil, err
}
transactions = []lnclient.Transaction{}
for _, payment := range payments {
if payment.PaymentType != breez_sdk.PaymentTypeReceived && payment.PaymentType != breez_sdk.PaymentTypeSent {
// skip other types of payments for now
continue
}
transaction, err := breezPaymentToTransaction(&payment)
if err == nil {
transactions = append(transactions, *transaction)
}
}
return transactions, nil
}
func (bs *BreezService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
return &lnclient.NodeInfo{
Alias: "breez",
Color: "",
Pubkey: "",
Network: "mainnet",
BlockHeight: 0,
BlockHash: "",
}, nil
}
func (bs *BreezService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
channels := []lnclient.Channel{}
return channels, nil
}
func (bs *BreezService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
return &lnclient.NodeConnectionInfo{}, nil
}
func (bs *BreezService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
return nil
}
func (bs *BreezService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
return nil, nil
}
func (bs *BreezService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) (*lnclient.CloseChannelResponse, error) {
return nil, nil
}
func breezPaymentToTransaction(payment *breez_sdk.Payment) (*lnclient.Transaction, error) {
var lnDetails breez_sdk.PaymentDetailsLn
if payment.Details != nil {
lnDetails, _ = payment.Details.(breez_sdk.PaymentDetailsLn)
}
var txType string
if payment.PaymentType == breez_sdk.PaymentTypeSent {
txType = "outgoing"
} else {
txType = "incoming"
}
createdAt := payment.PaymentTime
var expiresAt *int64
description := lnDetails.Data.Label
descriptionHash := ""
if lnDetails.Data.Bolt11 != "" {
// TODO: Breez should provide these details so we don't need to manually decode the invoice
paymentRequest, err := decodepay.Decodepay(strings.ToLower(lnDetails.Data.Bolt11))
if err != nil {
log.Printf("Failed to decode bolt11 invoice: %v", payment)
return nil, err
}
createdAt = int64(paymentRequest.CreatedAt)
expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
expiresAt = &expiresAtUnix
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
}
tx := &lnclient.Transaction{
Type: txType,
Invoice: lnDetails.Data.Bolt11,
Preimage: lnDetails.Data.PaymentPreimage,
PaymentHash: lnDetails.Data.PaymentHash,
Amount: int64(payment.AmountMsat),
FeesPaid: int64(payment.FeeMsat),
CreatedAt: createdAt,
ExpiresAt: expiresAt,
Metadata: nil,
Description: description,
DescriptionHash: descriptionHash,
}
if payment.Status == breez_sdk.PaymentStatusComplete {
settledAt := payment.PaymentTime
tx.SettledAt = &settledAt
}
if payment.Description != nil {
tx.Description = *payment.Description
}
return tx, nil
}
func (bs *BreezService) GetNewOnchainAddress(ctx context.Context) (string, error) {
// The below code works but is not needed and there's no Breez UI to support it.
// Plus, it creates complexity with the deposit limits.
/*swapInfo, err := bs.svc.ReceiveOnchain(breez_sdk.ReceiveOnchainRequest{})
if err != nil {
logger.Logger.Errorf("Failed to get onchain address: %v", err)
return "", err
}
logger.Logger.Infof("This address has deposit limits! Min: %d Max: %d", swapInfo.MinAllowedDeposit, swapInfo.MaxAllowedDeposit)
return swapInfo.BitcoinAddress, nil*/
return "", nil
}
func (bs *BreezService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
response, err := bs.svc.NodeInfo()
if err != nil {
logger.Logger.Errorf("Failed to get node info: %v", err)
return nil, err
}
return &lnclient.OnchainBalanceResponse{
Spendable: int64(response.OnchainBalanceMsat) / 1000,
Total: int64(response.OnchainBalanceMsat+response.PendingOnchainBalanceMsat) / 1000,
}, nil
}
func (bs *BreezService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) {
if !sendAll {
return "", errors.New("only send all is supported")
}
if toAddress == "" {
return "", errors.New("No address provided")
}
recommendedFees, err := bs.svc.RecommendedFees()
if err != nil {
logger.Logger.Errorf("Failed to get recommended fees info: %v", err)
return "", err
}
satPerVbyte := uint32(recommendedFees.FastestFee)
prepareReq := breez_sdk.PrepareRedeemOnchainFundsRequest{SatPerVbyte: satPerVbyte, ToAddress: toAddress}
prepareRedeemOnchainFundsResponse, err := bs.svc.PrepareRedeemOnchainFunds(prepareReq)
if err != nil {
logger.Logger.Errorf("Failed to prepare onchain address: %v", err)
return "", err
}
logger.Logger.Infof("PrepareRedeemOnchainFunds response: %#v", prepareRedeemOnchainFundsResponse)
redeemReq := breez_sdk.RedeemOnchainFundsRequest{SatPerVbyte: satPerVbyte, ToAddress: toAddress}
redeemOnchainFundsResponse, err := bs.svc.RedeemOnchainFunds(redeemReq)
if err != nil {
logger.Logger.Errorf("Failed to redeem onchain funds: %v", err)
return "", err
}
logger.Logger.Infof("RedeemOnchainFunds response: %#v", redeemOnchainFundsResponse)
return hex.EncodeToString(redeemOnchainFundsResponse.Txid), nil
}
func (bs *BreezService) ResetRouter(key string) error {
return nil
}
func (bs *BreezService) SendPaymentProbes(ctx context.Context, invoice string) error {
return nil
}
func (bs *BreezService) SendSpontaneousPaymentProbes(ctx context.Context, amountMsat uint64, nodeId string) error {
return nil
}
func (bs *BreezService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
return nil, nil
}
func (bs *BreezService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
return []byte{}, nil
}
func (bs *BreezService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.NodeStatus, err error) {
return &lnclient.NodeStatus{
IsReady: true,
}, nil
}
func (bs *BreezService) SignMessage(ctx context.Context, message string) (string, error) {
resp, err := bs.svc.SignMessage(breez_sdk.SignMessageRequest{
Message: message,
})
if err != nil {
return "", err
}
return resp.Signature, nil
}
func (bs *BreezService) GetBalances(ctx context.Context) (*lnclient.BalancesResponse, error) {
info, err := bs.svc.NodeInfo()
if err != nil {
return nil, err
}
return &lnclient.BalancesResponse{
Onchain: lnclient.OnchainBalanceResponse{
Spendable: int64(info.OnchainBalanceMsat) / 1000,
Total: int64(info.OnchainBalanceMsat+info.PendingOnchainBalanceMsat) / 1000,
},
Lightning: lnclient.LightningBalanceResponse{
TotalSpendable: int64(info.MaxPayableMsat),
TotalReceivable: int64(info.MaxReceivableMsat),
NextMaxSpendable: int64(info.MaxSinglePaymentAmountMsat),
NextMaxReceivable: int64(info.MaxReceivableMsat),
NextMaxSpendableMPP: int64(info.MaxSinglePaymentAmountMsat),
NextMaxReceivableMPP: int64(info.MaxReceivableMsat),
},
}, nil
}
func (bs *BreezService) GetStorageDir() (string, error) {
return "", nil
}
func (bs *BreezService) GetNetworkGraph(ctx context.Context, nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (bs *BreezService) UpdateLastWalletSyncRequest() {}
func (bs *BreezService) UpdateChannel(ctx context.Context, updateChannelRequest *lnclient.UpdateChannelRequest) error {
return nil
}
func (bs *BreezService) DisconnectPeer(ctx context.Context, peerId string) error {
return nil
}
func (bs *BreezService) GetSupportedNIP47Methods() []string {
return []string{"pay_invoice" /*"pay_keysend",*/, "get_balance", "get_budget", "get_info", "make_invoice", "lookup_invoice", "list_transactions", "multi_pay_invoice", "multi_pay_keysend", "sign_message"}
}
func (bs *BreezService) GetSupportedNIP47NotificationTypes() []string {
return []string{}
}
func (bs *BreezService) GetPubkey() string {
return bs.pubkey
}
func (bs *BreezService) GetCustomNodeCommandDefinitions() []lnclient.CustomNodeCommandDef {
return nil
}
func (bs *BreezService) ExecuteCustomNodeCommand(ctx context.Context, command *lnclient.CustomNodeCommandRequest) (*lnclient.CustomNodeCommandResponse, error) {
return nil, nil
}

View file

@ -1,12 +0,0 @@
//go:build skip_breez
package breez
import (
"github.com/getAlby/hub/lnclient"
)
func NewBreezService(mnemonic, apiKey, inviteCode, workDir string) (result lnclient.LNClient, err error) {
panic("not implemented")
return nil, nil
}

View file

@ -1,699 +0,0 @@
package greenlight
import (
"context"
"errors"
"log"
"math/rand"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
// "github.com/getAlby/hub/glalby" // for local development only
"github.com/getAlby/glalby-go/glalby"
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
"github.com/getAlby/hub/config"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
)
type GreenlightService struct {
workdir string
client *glalby.BlockingGreenlightAlbyClient
pubkey string
}
const DEVICE_CREDENTIALS_KEY = "GreenlightCreds"
func NewGreenlightService(cfg config.Config, mnemonic, inviteCode, workDir, encryptionKey string) (result lnclient.LNClient, err error) {
if mnemonic == "" || inviteCode == "" || workDir == "" {
return nil, errors.New("one or more required greenlight configuration are missing")
}
// create dir if not exists
newpath := filepath.Join(workDir)
err = os.MkdirAll(newpath, os.ModePerm)
if err != nil {
log.Printf("Failed to create greenlight working dir: %v", err)
return nil, err
}
var credentials *glalby.GreenlightCredentials
existingDeviceCreds, _ := cfg.Get(DEVICE_CREDENTIALS_KEY, encryptionKey)
if existingDeviceCreds != "" {
credentials = &glalby.GreenlightCredentials{
GlCreds: existingDeviceCreds,
}
logger.Logger.Info("Using saved greenlight credentials")
}
if credentials == nil {
logger.Logger.Info("No greenlight credentials found, attempting to recover existing node")
recoveredCredentials, err := glalby.Recover(mnemonic)
credentials = &recoveredCredentials
if err != nil {
logger.Logger.Errorf("Failed to recover node: %v", err)
logger.Logger.Infof("Trying to register instead...")
recoveredCredentials, err := glalby.Register(mnemonic, inviteCode)
credentials = &recoveredCredentials
if err != nil {
logger.Logger.WithError(err).Error("Failed to register new node")
return nil, err
}
}
if credentials == nil || credentials.GlCreds == "" {
return nil, errors.New("unexpected response from Recover")
}
err = cfg.SetUpdate(DEVICE_CREDENTIALS_KEY, credentials.GlCreds, encryptionKey)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save greenlight credentials")
return nil, err
}
}
client, err := glalby.NewBlockingGreenlightAlbyClient(mnemonic, *credentials)
if err != nil {
log.Printf("Failed to create greenlight alby client: %v", err)
return nil, err
}
if client == nil {
logger.Logger.Error("unexpected response from NewBlockingGreenlightAlbyClient")
}
nodeInfo, err := client.GetInfo()
gs := GreenlightService{
workdir: newpath,
client: client,
pubkey: nodeInfo.Pubkey,
}
if err != nil {
return nil, err
}
log.Printf("Node info: %v", nodeInfo)
return &gs, nil
}
func (gs *GreenlightService) Shutdown() error {
_, err := gs.client.Shutdown()
if err != nil {
logger.Logger.WithError(err).Error("Failed to shutdown greenlight node")
return err
}
return nil
}
func (gs *GreenlightService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
if amount != nil {
return nil, errors.New("0-amount invoices not supported")
}
response, err := gs.client.Pay(glalby.PayRequest{
Bolt11: payReq,
})
if err != nil {
logger.Logger.Errorf("Failed to send payment: %v", err)
return nil, err
}
log.Printf("SendPaymentSync succeeded: %v", response.Preimage)
return &lnclient.PayInvoiceResponse{
Preimage: response.Preimage,
}, nil
}
func (gs *GreenlightService) SendKeysend(ctx context.Context, amount uint64, destination string, custom_records []lnclient.TLVRecord, preimage string) (*lnclient.PayKeysendResponse, error) {
// TODO: re-enable when passing custom preimage is possible
/*extraTlvs := []glalby.TlvEntry{}
for _, customRecord := range custom_records {
extraTlvs = append(extraTlvs, glalby.TlvEntry{
Ty: customRecord.Type,
Value: customRecord.Value, // glalby expects hex-encoded TLV values
})
}
// TODO: support passing custom preimage
response, err := gs.client.KeySend(glalby.KeySendRequest{
Destination: destination,
AmountMsat: &amount,
ExtraTlvs: &extraTlvs,
})
if err != nil {
logger.Logger.Errorf("Failed to send keysend payment: %v", err)
return "", "", 0, err
}
// TODO: get payment hash from response
return "", response.PaymentPreimage, 0, nil*/
return nil, errors.New("not supported")
}
func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *lnclient.Transaction, err error) {
if expiry == 0 {
expiry = lnclient.DEFAULT_INVOICE_EXPIRY
}
uexpiry := uint64(expiry)
// TODO: it seems description hash cannot be passed to greenlight
invoice, err := gs.client.MakeInvoice(glalby.MakeInvoiceRequest{
AmountMsat: uint64(amount),
Description: description,
Label: "label_" + strconv.Itoa(rand.Int()),
Expiry: &uexpiry,
})
if err != nil {
logger.Logger.Errorf("MakeInvoice failed: %v", err)
return nil, err
}
paymentRequest, err := decodepay.Decodepay(strings.ToLower(invoice.Bolt11))
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"invoice": invoice.Bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", invoice.Bolt11)
return nil, err
}
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
expiresAt := int64(invoice.ExpiresAt)
transaction = &lnclient.Transaction{
Type: "incoming",
Invoice: invoice.Bolt11,
Description: description,
DescriptionHash: descriptionHash,
Preimage: "", // TODO: set preimage to enable self-payments
PaymentHash: invoice.PaymentHash,
ExpiresAt: &expiresAt,
Amount: amount,
CreatedAt: int64(paymentRequest.CreatedAt),
}
return transaction, nil
}
func (gs *GreenlightService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *lnclient.Transaction, err error) {
response, err := gs.client.ListInvoices(glalby.ListInvoicesRequest{
PaymentHash: &paymentHash,
})
if err != nil {
logger.Logger.Errorf("ListInvoices failed: %v", err)
return nil, err
}
if len(response.Invoices) == 0 {
return nil, errors.New("invoice not found")
}
invoice := response.Invoices[0]
if invoice.Bolt11 == nil {
return nil, errors.New("not a Bolt11 invoice")
}
transaction, err = gs.greenlightInvoiceToTransaction(&invoice)
if err != nil {
logger.Logger.Errorf("Failed to map invoice: %v", err)
return nil, err
}
return transaction, nil
}
func (gs *GreenlightService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []lnclient.Transaction, err error) {
listInvoicesResponse, err := gs.client.ListInvoices(glalby.ListInvoicesRequest{})
if err != nil {
logger.Logger.Errorf("ListInvoices failed: %v", err)
return nil, err
}
transactions = []lnclient.Transaction{}
if err != nil {
log.Printf("ListInvoices failed: %v", err)
return nil, err
}
for _, invoice := range listInvoicesResponse.Invoices {
if invoice.PaidAt == nil {
// skip unpaid invoices
continue
}
transaction, err := gs.greenlightInvoiceToTransaction(&invoice)
if err != nil {
continue
}
transactions = append(transactions, *transaction)
}
listPaymentsResponse, err := gs.client.ListPayments(glalby.ListPaymentsRequest{})
if err != nil {
logger.Logger.Errorf("ListPayments failed: %v", err)
return nil, err
}
for _, payment := range listPaymentsResponse.Payments {
description := ""
descriptionHash := ""
bolt11 := ""
var expiresAt *int64
if payment.Bolt11 != nil {
// TODO: Greenlight should provide these details so we don't need to manually decode the invoice
bolt11 := *payment.Bolt11
paymentRequest, err := decodepay.Decodepay(strings.ToLower(bolt11))
if err != nil {
log.Printf("Failed to decode bolt11 invoice: %v", bolt11)
return nil, err
}
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
expiresAt = &expiresAtUnix
}
preimage := ""
if payment.Preimage != nil {
preimage = *payment.Preimage
}
var amount int64 = 0
var fee int64 = 0
if payment.AmountSentMsat != nil {
amount = int64(*payment.AmountSentMsat)
if payment.AmountMsat != nil {
fee = amount - int64(*payment.AmountMsat)
}
}
var settledAt *int64
if payment.CompletedAt != nil {
completedAt := int64(*payment.CompletedAt)
settledAt = &completedAt
}
transactions = append(transactions, lnclient.Transaction{
Type: "outgoing",
Invoice: bolt11,
Description: description,
DescriptionHash: descriptionHash,
Preimage: preimage,
PaymentHash: payment.PaymentHash,
ExpiresAt: expiresAt,
Amount: amount,
FeesPaid: fee,
CreatedAt: int64(payment.CreatedAt),
SettledAt: settledAt,
})
}
// sort by created date descending
sort.SliceStable(transactions, func(i, j int) bool {
return transactions[i].CreatedAt > transactions[j].CreatedAt
})
return transactions, nil
}
func (gs *GreenlightService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
nodeInfo, err := gs.client.GetInfo()
if err != nil {
logger.Logger.Errorf("GetInfo failed: %v", err)
return nil, err
}
return &lnclient.NodeInfo{
Alias: nodeInfo.Alias,
Color: nodeInfo.Color,
Pubkey: nodeInfo.Pubkey,
Network: nodeInfo.Network,
BlockHeight: nodeInfo.BlockHeight,
BlockHash: "",
}, nil
}
func (gs *GreenlightService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
if err != nil {
logger.Logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
channels := []lnclient.Channel{}
for _, glChannel := range response.Channels {
if glChannel.ChannelId == nil {
continue
}
var localAmount uint64
if glChannel.OurAmountMsat != nil {
localAmount = *glChannel.OurAmountMsat
}
var totalAmount uint64
if glChannel.AmountMsat != nil {
totalAmount = *glChannel.AmountMsat
}
channels = append(channels, lnclient.Channel{
LocalBalance: int64(localAmount),
LocalSpendableBalance: int64(localAmount),
RemoteBalance: int64(totalAmount - localAmount),
RemotePubkey: glChannel.PeerId,
Id: *glChannel.ChannelId,
Active: glChannel.State == 2,
FundingTxId: glChannel.FundingTxid,
// TODO: add Public property
})
}
return channels, nil
}
func (gs *GreenlightService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
info, err := gs.GetInfo(ctx)
if err != nil {
logger.Logger.Errorf("GetInfo failed: %v", err)
return nil, err
}
return &lnclient.NodeConnectionInfo{
Pubkey: info.Pubkey,
}, nil
}
func (gs *GreenlightService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
var host *string
if connectPeerRequest.Address != "" {
host = &connectPeerRequest.Address
}
var port *uint16
if connectPeerRequest.Port > 0 {
port = &connectPeerRequest.Port
}
_, err := gs.client.ConnectPeer(glalby.ConnectPeerRequest{
Id: connectPeerRequest.Pubkey,
Host: host,
Port: port,
})
if err != nil {
logger.Logger.Errorf("ConnectPeer failed: %v", err)
return err
}
return nil
}
func (gs *GreenlightService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
amountMsat := uint64(openChannelRequest.AmountSats) * 1000
// minConf := uint32(0) //
response, err := gs.client.FundChannel(glalby.FundChannelRequest{
Id: openChannelRequest.Pubkey,
AmountMsat: &amountMsat,
Announce: &openChannelRequest.Public,
// Minconf: &minConf,
})
if err != nil {
logger.Logger.Errorf("OpenChannel failed: %v", err)
return nil, err
}
return &lnclient.OpenChannelResponse{
FundingTxId: response.Txid,
}, nil
}
func (gs *GreenlightService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) (*lnclient.CloseChannelResponse, error) {
_, err := gs.client.Close(glalby.CloseRequest{
Id: closeChannelRequest.ChannelId,
})
if err != nil {
logger.Logger.WithError(err).Error("CloseChannel failed")
return nil, err
}
return &lnclient.CloseChannelResponse{}, nil
}
func (gs *GreenlightService) GetNewOnchainAddress(ctx context.Context) (string, error) {
newAddressResponse, err := gs.client.NewAddress(glalby.NewAddressRequest{})
if err != nil {
logger.Logger.Errorf("NewAddress failed: %v", err)
return "", err
}
if newAddressResponse.Bech32 == nil {
return "", errors.New("no Bech32 in new address response")
}
return *newAddressResponse.Bech32, nil
}
func (gs *GreenlightService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
logger.Logger.WithField("response", response).Debug("Listed funds")
if err != nil {
logger.Logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
var spendableBalance int64 = 0
var pendingBalance int64 = 0
for _, output := range response.Outputs {
if output.AmountMsat != nil {
if output.Status == 1 {
spendableBalance += int64(*output.AmountMsat)
} else {
pendingBalance += int64(*output.AmountMsat)
}
}
}
return &lnclient.OnchainBalanceResponse{
Spendable: spendableBalance / 1000,
Total: (spendableBalance + pendingBalance) / 1000,
}, nil
}
func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) {
if !sendAll {
return "", errors.New("only send all is supported")
}
amountAll := glalby.AmountOrAll(glalby.AmountOrAllAll{})
txId, err := gs.client.Withdraw(glalby.WithdrawRequest{
Destination: toAddress,
Amount: &amountAll,
})
if err != nil {
logger.Logger.WithError(err).Error("Withdraw failed")
return "", err
}
logger.Logger.WithField("txId", txId).Info("Redeeming On-Chain funds")
return txId.Txid, nil
}
func (gs *GreenlightService) SendPaymentProbes(ctx context.Context, invoice string) error {
return nil
}
func (gs *GreenlightService) SendSpontaneousPaymentProbes(ctx context.Context, amountMsat uint64, nodeId string) error {
return nil
}
func (gs *GreenlightService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
return nil, nil
}
func (gs *GreenlightService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
return []byte{}, nil
}
func (gs *GreenlightService) SignMessage(ctx context.Context, message string) (string, error) {
response, err := gs.client.SignMessage(glalby.SignMessageRequest{
Message: message,
})
if err != nil {
logger.Logger.Errorf("SignMessage failed: %v", err)
return "", err
}
return response.Zbase, nil
}
func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.ListInvoicesInvoice) (*lnclient.Transaction, error) {
description := ""
descriptionHash := ""
if invoice.Description != nil {
description = *invoice.Description
}
bolt11 := *invoice.Bolt11
paymentRequest, err := decodepay.Decodepay(strings.ToLower(bolt11))
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"invoice": bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", bolt11)
return nil, err
}
if description == "" {
description = paymentRequest.Description
}
descriptionHash = paymentRequest.DescriptionHash
expiresAt := int64(invoice.ExpiresAt)
var amount int64 = 0
var fee int64 = 0
if invoice.AmountReceivedMsat != nil {
amount = int64(*invoice.AmountReceivedMsat)
if invoice.AmountMsat != nil {
fee = int64(*invoice.AmountMsat) - amount
}
}
preimage := ""
var settledAt *int64
if invoice.PaidAt != nil {
preimage = *invoice.PaymentPreimage
paidAt := int64(*invoice.PaidAt)
settledAt = &paidAt
}
transaction := &lnclient.Transaction{
Type: "incoming",
Invoice: bolt11,
Description: description,
DescriptionHash: descriptionHash,
PaymentHash: invoice.PaymentHash,
ExpiresAt: &expiresAt,
Amount: amount,
FeesPaid: fee,
CreatedAt: int64(paymentRequest.CreatedAt),
Preimage: preimage,
SettledAt: settledAt,
}
return transaction, nil
}
func (gs *GreenlightService) ResetRouter(key string) error {
return nil
}
func (gs *GreenlightService) GetBalances(ctx context.Context) (*lnclient.BalancesResponse, error) {
onchainBalance, err := gs.GetOnchainBalance(ctx)
if err != nil {
logger.Logger.WithError(err).Error("Failed to retrieve onchain balance")
return nil, err
}
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
if err != nil {
logger.Logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
var totalReceivable int64 = 0
var totalSpendable int64 = 0
var nextMaxReceivable int64 = 0
var nextMaxSpendable int64 = 0
var nextMaxReceivableMPP int64 = 0
var nextMaxSpendableMPP int64 = 0
for _, channel := range response.Channels {
if channel.OurAmountMsat != nil && channel.AmountMsat != nil && channel.Connected {
channelReceivable := int64(*channel.AmountMsat - *channel.OurAmountMsat)
channelSpendable := int64(*channel.OurAmountMsat)
nextMaxReceivable = max(nextMaxReceivable, channelReceivable)
nextMaxSpendable = max(nextMaxSpendable, channelSpendable)
nextMaxReceivableMPP += channelReceivable
nextMaxSpendableMPP += channelSpendable
totalReceivable += channelReceivable
totalSpendable += channelSpendable
}
}
return &lnclient.BalancesResponse{
Onchain: *onchainBalance,
Lightning: lnclient.LightningBalanceResponse{
TotalSpendable: totalSpendable,
TotalReceivable: totalReceivable,
NextMaxSpendable: nextMaxSpendable,
NextMaxReceivable: nextMaxReceivable,
NextMaxSpendableMPP: nextMaxSpendableMPP,
NextMaxReceivableMPP: nextMaxReceivableMPP,
},
}, nil
}
func (gs *GreenlightService) GetStorageDir() (string, error) {
return "", nil
}
func (gs *GreenlightService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.NodeStatus, err error) {
return &lnclient.NodeStatus{
IsReady: true,
}, nil
}
func (gs *GreenlightService) GetNetworkGraph(ctx context.Context, nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (gs *GreenlightService) UpdateLastWalletSyncRequest() {}
func (gs *GreenlightService) UpdateChannel(ctx context.Context, updateChannelRequest *lnclient.UpdateChannelRequest) error {
return nil
}
func (gs *GreenlightService) DisconnectPeer(ctx context.Context, peerId string) error {
return nil
}
func (gs *GreenlightService) GetSupportedNIP47Methods() []string {
return []string{"pay_invoice" /*"pay_keysend",*/, "get_balance", "get_budget", "get_info", "make_invoice", "lookup_invoice", "list_transactions", "multi_pay_invoice", "multi_pay_keysend", "sign_message"}
}
func (gs *GreenlightService) GetSupportedNIP47NotificationTypes() []string {
return []string{}
}
func (gs *GreenlightService) GetPubkey() string {
return gs.pubkey
}
func (gs *GreenlightService) GetCustomNodeCommandDefinitions() []lnclient.CustomNodeCommandDef {
return nil
}
func (gs *GreenlightService) ExecuteCustomNodeCommand(ctx context.Context, command *lnclient.CustomNodeCommandRequest) (*lnclient.CustomNodeCommandResponse, error) {
return nil, nil
}

View file

@ -17,9 +17,7 @@ import (
"github.com/getAlby/hub/config"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/lnclient/breez"
"github.com/getAlby/hub/lnclient/cashu"
"github.com/getAlby/hub/lnclient/greenlight"
"github.com/getAlby/hub/lnclient/ldk"
"github.com/getAlby/hub/lnclient/lnd"
"github.com/getAlby/hub/lnclient/phoenixd"
@ -328,19 +326,6 @@ func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) e
svc.startupState = startupState
}
lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetEnv().LDKNetwork, vssToken, setStartupState)
case config.GreenlightBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)
GreenlightWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "greenlight")
lnClient, err = greenlight.NewGreenlightService(svc.cfg, Mnemonic, GreenlightInviteCode, GreenlightWorkdir, encryptionKey)
case config.BreezBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
BreezAPIKey, _ := svc.cfg.Get("BreezAPIKey", encryptionKey)
GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)
BreezWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "breez")
lnClient, err = breez.NewBreezService(Mnemonic, BreezAPIKey, GreenlightInviteCode, BreezWorkdir)
case config.PhoenixBackendType:
PhoenixdAddress, _ := svc.cfg.Get("PhoenixdAddress", encryptionKey)
PhoenixdAuthorization, _ := svc.cfg.Get("PhoenixdAuthorization", encryptionKey)